Power, GPU, and thermal on a Mac. For the shared helper + launchd deployment mechanics, see Privileged Metrics (overview); this guide is the macOS specifics.
| Metric | Source | Needs root? |
|---|---|---|
gpu.util, power.gpu |
IOReport energy counters | no |
power.total (whole-system) |
SMC PSTR ("System Total Power") |
no |
power.cpu |
IOReport per-domain (base dies) or raw SMC cluster keys (Pro/Max/Ultra) | no |
power.npu |
IOReport ANE per-domain, where the SoC exposes it | no |
| full thermal, gaps IOReport misses | powermetrics (via the helper) |
yes |
Power standardizes on power.cpu / power.gpu / power.total across the fleet.
On macOS power.total is the SMC whole-system figure (it already includes the
GPU, so it is not a sum of the rails).
Apple Silicon is the one platform where GPU power and whole-system power are available with no sudo — IOReport and SMC are both unprivileged. A normal daemon reads them in-process:
./bin/heimdall-daemon --once | grep -E "gpu|power"
# e.g. power.gpu=1.19W power.total=18.4W gpu.util=54%power.total is read in a strict order so a phantom reading never shadows a real
one:
- SMC
PSTR— the authoritative whole-system rail (what mactop/btop use). powermetrics— fills the package figure when SMC is absent.- IOReport per-domain sum — last resort only.
This ordering exists because of the Pro/Max quirk below.
M-series Pro/Max 0 W note: on Apple Silicon Pro/Max SoCs the IOReport "Energy Model" per-domain CPU/ANE channels read 0, and only GPU shows a sub-watt figure — so the energy-sum collapses to ~0 W. Reading SMC
PSTRfirst is what keeps an M3 Max from reporting 0 W. The variable is the SoC, not the Heimdall version. See ADR 0020.
CPU power on Pro/Max: IOReport and
powermetricsreport0for CPU on Pro/Max/Ultra, but the raw SMC per-cluster keys still carry it — Heimdall reads those (see the next section).power.cpuonly staysunavailableon a chip whose SMC keys aren't mapped yet.
Heimdall reads Apple power from two unprivileged sources and layers them:
- IOReport "Energy Model" — per-domain CPU/GPU/ANE. On base dies (M1–M4)
this populates
power.cpu. On Pro/Max/Ultra the CPU and ANE channels read 0 (both IOReport andpowermetricsreturnCPU Power: 0 mWwith the cores pegged) — so IOReport alone can't give CPU power there. - Raw SMC keys — the same source Stats reads. Pro/Max dies still expose CPU
power under the per-cluster power keys (the P-clusters
PC02+PC42plus the E-core/fabric railsPC03+PC43on an M3 Max), even though the energy model reports 0. When IOReport gives no CPU figure, Heimdall sums those SMC cluster keys and reportspower.cputaggedCPU complex (SMC).
So power.cpu is real on both tiers now — from IOReport on base dies, from
the SMC cluster keys on Pro/Max. It only falls back to
no per-domain CPU power (IOReport + SMC) when neither source responds (a chip
whose SMC cluster keys we haven't mapped — the keys are reverse-engineered and can
differ per generation, exactly like the temperature keys). power.total (SMC
PSTR) always carries the whole-machine draw regardless.
GPU power is a valid IOReport channel on every Apple Silicon SoC, so it is
always reported — including a few milliwatts at idle (since v2.4.3 the idle GPU
shows as 0 W instead of being dropped).
Per-cluster split: where the SMC cluster keys are mapped (Pro/Max), the P/E
rails are also surfaced as power.cpu.pcluster / power.cpu.ecluster, and the
top view shows a cpu clusters: P … W · E … W line under the CPU figure. Base
dies (IOReport CPU power, no mapped cluster keys) simply don't get the line.
The core layout itself is the unprivileged cpu.topology metric (sysctl
hw.perflevel*; the E-cluster owns the low core ids — verified live).
Verified live across two SoCs and macOS builds:
| Machine | SoC | macOS (Darwin) | power.cpu source |
value |
|---|---|---|---|---|
| Mac mini | Apple M4 (base) | 26.6 / Darwin 25.6 | IOReport | ~0.5 W idle |
| MacBook Pro | Apple M3 Max | 27.0 / Darwin 27.0 | SMC PC02+PC42+PC03+PC43 |
~18–20 W under full load |
IOReport (and SMC) are reached through cgo. The local make build-tui enables
CGO on macOS by default, so a locally built daemon gets IOReport. The
CGO-free cross-compiled binaries fall back to powermetrics (which needs sudo).
If you want unprivileged GPU/power, build on the Mac.
Apple Silicon is unified memory — the GPU has no discrete VRAM to report. Since
v2.2.5 gpu.vram reads unavailable with the detail unified memory (no discrete VRAM), so the panel explains the dash instead of silently omitting the
metric. GPU utilisation (gpu.util) and power (power.gpu) are still
reported from IOReport as above.
Accelerator power is the vendor-neutral power.npu (Apple's ANE). The legacy
power.ane key is still accepted and normalised on ingest, so a mixed fleet
with older daemons keeps rendering. npu.util reads unavailable — Apple
exposes no ANE utilisation counter.
For the always-on layout (helper as a root LaunchDaemon, daemon as you, sharing
a heimdall group and a socket under /usr/local/var/heimdall), follow
Run both as launchd services
in the overview guide. The helper only adds full thermal and any power IOReport
missed — GPU/util and whole-system power already work without it.