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.5.1.tar.gz (51.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.5.1-cp312-cp312-macosx_26_0_arm64.whl (68.3 kB view details)

Uploaded CPython 3.12macOS 26.0+ ARM64

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

Hashes for darwin_perf-0.5.1.tar.gz
Algorithm Hash digest
SHA256 c8ab614f7feca4e5686c71f125ff7dac86bc25a89799563a9708b759579ec19a
MD5 4fd582d83301ca0df7c668bd6775c07c
BLAKE2b-256 733c4610e3910ca788eaaefe33cfd28f2b73e1ba411a39f0d2de35450f3a2e13

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for darwin_perf-0.5.1-cp312-cp312-macosx_26_0_arm64.whl
Algorithm Hash digest
SHA256 99a9990e92a69d942b722e324306ee80ff3c0585060a0a6f025456b55086b4fc
MD5 5745aea711b86f1db8799545a9285424
BLAKE2b-256 f96f8b7f0179d52320449718b41f3d8aabc030681314772e6ac531887ba78480

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