devin-lai's picture
Release v0.4.0: selective MLX model downloads
3da88e3 verified
Raw History Blame Contribute Delete
3.48 kB
"""macOS process memory accounting, including Metal allocations.
Physical footprint, resident size and MLX allocation counters overlap. They
must be reported separately. Sampling can miss short peaks; the lifetime
high-water counter includes all earlier requests in a persistent process.
"""
from __future__ import annotations
import ctypes
import os
import threading
class _RUsageV4(ctypes.Structure):
# Exact macOS SDK sys/resource.h rusage_info_v4 layout.
_fields_ = [("uuid", ctypes.c_uint8 * 16)] + [
(name, ctypes.c_uint64)
for name in (
"user_time",
"system_time",
"pkg_idle_wkups",
"interrupt_wkups",
"pageins",
"wired_size",
"resident_size",
"phys_footprint",
"proc_start_abstime",
"proc_exit_abstime",
"child_user_time",
"child_system_time",
"child_pkg_idle_wkups",
"child_interrupt_wkups",
"child_pageins",
"child_elapsed_abstime",
"diskio_bytesread",
"diskio_byteswritten",
"cpu_time_qos_default",
"cpu_time_qos_maintenance",
"cpu_time_qos_background",
"cpu_time_qos_utility",
"cpu_time_qos_legacy",
"cpu_time_qos_user_initiated",
"cpu_time_qos_user_interactive",
"billed_system_time",
"serviced_system_time",
"logical_writes",
"lifetime_max_phys_footprint",
"instructions",
"cycles",
"billed_energy",
"serviced_energy",
"interval_max_phys_footprint",
"runnable_time",
)
]
_proc = ctypes.CDLL("/usr/lib/libproc.dylib", use_errno=True)
_proc.proc_pid_rusage.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_void_p]
_proc.proc_pid_rusage.restype = ctypes.c_int
def footprint():
info = _RUsageV4()
if _proc.proc_pid_rusage(os.getpid(), 4, ctypes.byref(info)):
raise OSError(ctypes.get_errno(), "proc_pid_rusage")
return {
"physical_bytes": info.phys_footprint,
"lifetime_peak_physical_bytes": info.lifetime_max_phys_footprint,
"resident_bytes": info.resident_size,
}
class FootprintMonitor:
"""Sample current footprint; lifetime high water includes earlier runs."""
def __init__(self, interval=0.1):
self.interval = interval
self.samples = []
self.stop_event = threading.Event()
def _run(self):
while not self.stop_event.wait(self.interval):
self.samples.append(footprint())
def __enter__(self):
self.samples.append(footprint())
self.worker = threading.Thread(target=self._run, daemon=True)
self.worker.start()
return self
def __exit__(self, *_):
self.stop_event.set()
self.worker.join()
self.samples.append(footprint())
def result(self):
return {
"sampled_peak_physical_bytes": max(
s["physical_bytes"] for s in self.samples
),
"lifetime_peak_physical_bytes": self.samples[-1][
"lifetime_peak_physical_bytes"
],
"sample_interval_s": self.interval,
"sample_count": len(self.samples),
"scope": "Parent process only; includes Metal; excludes compiler XPC services. Counters overlap; do not sum.",
}