System performance monitoring for macOS Apple Silicon — GPU, CPU, memory, energy, temperature, disk I/O via Mach APIs and AppleSMC. No sudo needed.
Project description
darwin-perf
System performance monitoring for macOS Apple Silicon — GPU, CPU, memory, energy, temperature, and disk I/O via Mach APIs, IORegistry, and AppleSMC. No sudo needed.
Install
pip install darwin-perf
Quick Start
Temperatures (instant, no sudo)
from darwin_perf import temperatures
t = temperatures()
print(f"CPU: {t['cpu_avg']:.1f}°C GPU: {t['gpu_avg']:.1f}°C")
# CPU: 40.1°C GPU: 36.5°C
# Individual sensors
for name, temp in t['cpu_sensors'].items():
print(f" {name}: {temp:.1f}°C")
CPU Power & Frequency
from darwin_perf import cpu_power
c = cpu_power(interval=0.5)
print(f"CPU Power: {c['cpu_power_w']:.2f}W")
for name, cl in c['clusters'].items():
print(f" {name}: {cl['freq_mhz']} MHz, {cl['active_pct']:.0f}%")
# ECPU: 663 MHz, 100% active
# PCPU: 1272 MHz, 100% active
GPU Power & Frequency
from darwin_perf import gpu_power
g = gpu_power(interval=0.5)
print(f"GPU: {g['gpu_power_w']:.2f}W {g['gpu_freq_mhz']}MHz")
print(f"Throttled: {g['throttled']}")
for s in g['frequency_states']:
mhz = s.get('freq_mhz', '?')
print(f" {s['state']}: {mhz}MHz ({s['residency_pct']:.1f}%)")
Per-Process GPU/CPU Utilization
from darwin_perf import snapshot
for proc in snapshot():
print(f"{proc['name']:20s} GPU {proc['gpu_percent']:5.1f}% "
f"CPU {proc['cpu_percent']:5.1f}% {proc['memory_mb']:.0f}MB "
f"{proc['energy_w']:.1f}W")
Full System Snapshot (everything in one call)
from darwin_perf import snapshot
s = snapshot(interval=1.0, system=True, detailed=True)
# s['cpu'] — cpu_power_w, clusters (ECPU/PCPU freq + residency)
# s['gpu'] — gpu_power_w, gpu_freq_mhz, throttled, frequency_states
# s['temperatures'] — cpu_avg, gpu_avg, system_avg, per-sensor dicts
# s['memory'] — memory_total, memory_used, memory_available, ...
# s['gpu_stats'] — device_utilization, model, gpu_core_count
# s['processes'] — per-process GPU%, CPU%, memory, energy, IPC, disk I/O
System-Wide Stats
from darwin_perf import system_stats, system_gpu_stats
sys = system_stats() # instant, ~3µs
print(f"RAM: {sys['memory_used']/1e9:.1f} / {sys['memory_total']/1e9:.1f} GB")
print(f"CPU idle: {sys['cpu_idle_pct']:.1f}%")
gpu = system_gpu_stats()
print(f"{gpu['model']} ({gpu['gpu_core_count']} cores)")
print(f"Device utilization: {gpu['device_utilization']}%")
GpuMonitor (continuous monitoring)
from darwin_perf import GpuMonitor
mon = GpuMonitor() # monitors the current process
for batch in dataloader:
train(batch)
print(f"GPU: {mon.sample():.1f}%")
# Or as a context manager with background sampling:
with GpuMonitor() as mon:
mon.start(interval=2.0)
train()
print(mon.summary()) # {'gpu_pct_avg': 42.1, 'gpu_pct_max': 87.3, ...}
Low-Level Access
from darwin_perf import gpu_clients, proc_info
# All GPU clients — includes Metal/GL/CL API type
for c in gpu_clients():
ns = c['gpu_ns'] / 1e9
print(f"PID {c['pid']} ({c['name']}): {ns:.1f}s [{c['api']}]")
# Per-process stats (CPU, memory, energy, disk I/O, threads)
info = proc_info(1234)
print(f"Memory: {info['memory']/1e6:.0f}MB, Energy: {info['energy_nj']/1e9:.1f}J")
CLI
# Live monitoring
darwin-perf # per-process GPU/CPU monitor
darwin-perf --tui # rich terminal UI (pip install darwin-perf[tui])
darwin-perf --gui # floating window (pip install darwin-perf[gui])
darwin-perf -i 1 # 1-second updates
darwin-perf --pid 1234 # specific PID
# Streaming output
darwin-perf --json # one JSON line per update (pipe to jq, etc.)
darwin-perf --csv # CSV with header (pipe to file for spreadsheets)
# Recording & export
darwin-perf --record session.jsonl # capture full system state
darwin-perf --record session.jsonl -n 60 # record 60 samples
darwin-perf --export session.jsonl # → _system.csv + _processes.csv
darwin-perf --replay session.jsonl # replay with original timing
Record → Export workflow
Record a training run, then analyze in a spreadsheet or pandas:
# 1. Record during workload
darwin-perf --record training.jsonl -i 2
# 2. Export to CSV
darwin-perf --export training.jsonl
# 3. Produces:
# training_system.csv — CPU/GPU power, temps, memory per sample
# training_processes.csv — per-process GPU%, CPU%, memory, IPC per sample
import pandas as pd
df = pd.read_csv("training_system.csv")
df.plot(x="epoch", y=["cpu_power_w", "gpu_power_w", "temp_cpu_avg", "temp_gpu_avg"])
API Reference
Python API
| Function | Description |
|---|---|
snapshot() |
Per-process GPU/CPU utilization, memory, energy |
snapshot(system=True) |
Full system: CPU/GPU power, temps, memory + processes |
snapshot(detailed=True) |
Extended fields: IPC, wakeups, peak memory, disk I/O |
temperatures() |
Instant thermal sensors via AppleSMC (CPU, GPU, system) |
cpu_power(interval) |
CPU power (W), ECPU/PCPU frequency + P-state residency |
gpu_power(interval) |
GPU power (W), frequency (MHz), throttle, P-state residency |
system_stats() |
System memory + CPU ticks (instant, ~3µs) |
system_gpu_stats() |
GPU utilization %, model, core count, VRAM |
GpuMonitor |
Continuous per-process GPU % with background thread |
C Extension Functions
| Function | Description |
|---|---|
gpu_clients() |
All GPU-active processes: [{pid, name, gpu_ns, api}, ...] |
gpu_time_ns(pid) |
Cumulative GPU nanoseconds for a PID |
gpu_time_ns_multi(pids) |
Batch GPU ns for multiple PIDs (single IORegistry scan) |
cpu_time_ns(pid) |
Cumulative CPU nanoseconds (user + system) |
proc_info(pid) |
Full process stats (CPU, memory, energy, disk, threads) |
temperatures() |
Thermal sensors via AppleSMC |
cpu_power(interval) |
CPU package power + per-cluster frequency states |
gpu_power(interval) |
GPU power, frequency, throttle, temperature |
gpu_freq_table() |
GPU DVFS frequency table (MHz per P-state) |
system_stats() |
System memory + CPU via Mach APIs |
system_gpu_stats() |
GPU performance statistics from IORegistry |
ppid(pid) |
Parent process ID (-1 on error) |
How It Works
GPU per-process metrics: Apple doesn't provide a public API for this. The data exists in the IORegistry — every Metal command queue creates an AGXDeviceUserClient with accumulatedGPUTime in nanoseconds. Sample twice, divide by elapsed time = utilization %. World-readable, no sudo.
CPU/GPU power & frequency: Uses libIOReport.dylib (same data source as powermetrics). Subscribes to "Energy Model" and "CPU Stats"/"GPU Stats" groups. No sudo.
Temperatures: Reads SMC keys (Tp*=CPU, Tg*=GPU, Ts*=system) via IOServiceOpen("AppleSMC") + IOConnectCallStructMethod. 48+ sensors on M4 Max. No sudo.
System stats: host_statistics64() for memory, host_statistics(HOST_CPU_LOAD_INFO) for CPU ticks, proc_pid_rusage(RUSAGE_INFO_V6) for per-process metrics. All unprivileged.
Requirements
- macOS with Apple Silicon (M1/M2/M3/M4/M5)
- Python 3.9+
- Zero dependencies
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file darwin_perf-0.5.1.tar.gz.
File metadata
- Download URL: darwin_perf-0.5.1.tar.gz
- Upload date:
- Size: 51.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8ab614f7feca4e5686c71f125ff7dac86bc25a89799563a9708b759579ec19a
|
|
| MD5 |
4fd582d83301ca0df7c668bd6775c07c
|
|
| BLAKE2b-256 |
733c4610e3910ca788eaaefe33cfd28f2b73e1ba411a39f0d2de35450f3a2e13
|
File details
Details for the file darwin_perf-0.5.1-cp312-cp312-macosx_26_0_arm64.whl.
File metadata
- Download URL: darwin_perf-0.5.1-cp312-cp312-macosx_26_0_arm64.whl
- Upload date:
- Size: 68.3 kB
- Tags: CPython 3.12, macOS 26.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
99a9990e92a69d942b722e324306ee80ff3c0585060a0a6f025456b55086b4fc
|
|
| MD5 |
5745aea711b86f1db8799545a9285424
|
|
| BLAKE2b-256 |
f96f8b7f0179d52320449718b41f3d8aabc030681314772e6ac531887ba78480
|