Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

darwin_perf-0.4.1.tar.gz (43.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

darwin_perf-0.4.1-cp312-cp312-macosx_26_0_arm64.whl (59.3 kB view details)

Uploaded CPython 3.12macOS 26.0+ ARM64

File details

Details for the file darwin_perf-0.4.1.tar.gz.

File metadata

  • Download URL: darwin_perf-0.4.1.tar.gz
  • Upload date:
  • Size: 43.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for darwin_perf-0.4.1.tar.gz
Algorithm Hash digest
SHA256 29cdd6635149524ab46c0a52813e24099c2806a22ae0b6ec9b34113026601415
MD5 504dc9f3033febd8730dda673f476873
BLAKE2b-256 43ffce63036735ceb92d1bf579a78d797656afd1a1a9ce5217f9b88e06777415

See more details on using hashes here.

File details

Details for the file darwin_perf-0.4.1-cp312-cp312-macosx_26_0_arm64.whl.

File metadata

File hashes

Hashes for darwin_perf-0.4.1-cp312-cp312-macosx_26_0_arm64.whl
Algorithm Hash digest
SHA256 d5eb172a38fd718299ec37d26443ec2de44e076e296b4818a2883fceea6c2d05
MD5 b338964a010e9b964171cd322ff46c74
BLAKE2b-256 44002f7b2a688511a20979d66d1c11cb0ad5acbf2abddd92543336c541b3112f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page