From bf3e890a533cd375152a73c833811b3a7d2ebffb Mon Sep 17 00:00:00 2001 From: RDouglas <81336713+RDouglasSharp@users.noreply.github.com> Date: Tue, 14 Jul 2026 12:49:33 +0100 Subject: [PATCH] =?UTF-8?q?resource=5Fplan:=20macOS=20memory=5Favailable()?= =?UTF-8?q?=20via=20vm=5Fstat=20=E2=80=94=20fixes=200-on-macOS=20budget=20?= =?UTF-8?q?(#150)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- c/resource_plan.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/c/resource_plan.py b/c/resource_plan.py index 22da7e0..b5571d3 100644 --- a/c/resource_plan.py +++ b/c/resource_plan.py @@ -116,6 +116,30 @@ def memory_available(): return total_kb.value * 1024 except OSError: pass + # macOS: no /proc and not win32. Sum the reclaimable pages reported by vm_stat + # (free + inactive + speculative + purgeable) — the same "reclaimable without swapping" + # definition the C engine's compat_meminfo uses. Fall back to total RAM (never 0 on a Mac). + if sys.platform == "darwin": + try: + out = subprocess.run(["vm_stat"], text=True, capture_output=True, timeout=5).stdout + page_match = re.search(r"page size of (\d+) bytes", out) + page = int(page_match.group(1)) if page_match else os.sysconf("SC_PAGE_SIZE") + pages = 0 + for key in ("Pages free", "Pages inactive", "Pages speculative", "Pages purgeable"): + match = re.search(rf"{key}:\s+(\d+)\.", out) + if match: + pages += int(match.group(1)) + if pages: + return pages * page + except (OSError, subprocess.SubprocessError, ValueError): + pass + try: + total = subprocess.run(["sysctl", "-n", "hw.memsize"], text=True, + capture_output=True, timeout=5).stdout.strip() + if total: + return int(total) + except (OSError, subprocess.SubprocessError, ValueError): + pass return 0