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.2.tar.gz (51.1 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.2-cp312-cp312-macosx_26_0_arm64.whl (68.2 kB view details)

Uploaded CPython 3.12macOS 26.0+ ARM64

File details

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

File metadata

  • Download URL: darwin_perf-0.5.2.tar.gz
  • Upload date:
  • Size: 51.1 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.2.tar.gz
Algorithm Hash digest
SHA256 c474cafd6e1b0f49421dd222d9a6eb7fffc7a1c21e04b665033a8c44e975247c
MD5 b08d7cf88d491126d794bca1d27dbe9c
BLAKE2b-256 86080b487d831a535da7f3fbbf6394def49da9b0759326cc1b6490a79d8a1f81

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for darwin_perf-0.5.2-cp312-cp312-macosx_26_0_arm64.whl
Algorithm Hash digest
SHA256 d0409c15d71a0b0326f7be2f21b9be092c066497e2df626b9a1176de00725848
MD5 2cb67eaf4da8a10556ef3632cb1266b6
BLAKE2b-256 89b9d2ee412fb0415a5339757d8c8bea92a6665df3d990c57648027ab83cf6d3

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