Skip to content
WattMate Guides How it works What we measure Pricing Download the trial

Why four fifths of your Mac's CPU energy has no owner

Updated 23 August 2026 · measured on an M3 Max on 30 July 2026. This one is written for people building the same kind of tool. Who measures this.

macOS will tell you how much energy a process has used. The counter is cumulative, it is cheap to read, and it dies with the process. Anything born and finished between two samples was never in any snapshot, and on an idle Mac that turns out to be most of the work.

The counter and its hole

Per-process attribution starts at proc_pid_rusage(pid, RUSAGE_INFO_V6).ri_energy_nj. Sample every ten seconds, diff by matching pid, and you have energy per process. What you do not have is anything that lived and died inside those ten seconds.

Measured, summing every live process against the CPU energy channel:

LoadSum over live processesShare of CPU energy channel
Idle machine7.1 to 8.4 W20 to 23%
Building an app14.9 W64%

On the quiet machine four fifths of processor energy had no owner. A Swift build makes the mechanism obvious: 161 distinct swift-frontend pids across 164 windows. Every one of them compiled something, used power and was gone before the next sample. All of it landed in a row labelled "system, no owner".

The ledger that outlives the task

XNU groups tasks into a coalition: the application, its helpers, its XPC services, its forks and every descendant. The coalition carries a ledger, and when a task dies its energy stays in that ledger.

Nothing about this is private knowledge. powermetrics --show-process-coalition prints the same figures from the same syscall, and Chrome calls it in shipping builds. No root, no entitlement. There is no public header, so the three calls go through dlsym:

CallWhat it gives
proc_listcoalitions(1, 0, buf, sz)Every resource-type coalition, including the empty ones
coalition_info_resource_usage(cid, buf, 512)The ledger itself
proc_pidinfo(pid, 20, …)Which coalition a pid belongs to

The empty coalitions are the whole point

The obvious way to enumerate coalitions is to walk live pids and collect the coalitions they belong to. Chromium does exactly that, and it loses precisely the case that matters: an application whose every process has already exited.

Counted on a live machine: 1600 coalitions, of which only 729 had a live task. The other 871 carry the energy of processes that no longer exist. Enumerate through live pids and that energy stays invisible.

What to do with the remainder

Per coalition, per tick: take the ledger delta, subtract the sum of deltas from its own live members, and what remains belonged to tasks that died. Give it to the coalition leader rather than splitting it across members, because the work was caused by the application as a whole and belongs in its row.

Two rules keep that honest. A coalition seen for the first time cannot be booked whole: its counter has been running since before you were watching, so the first sighting sets a baseline and books nothing. And the leader has to be chosen deliberately, not taken as whichever member happened to be enumerated first.

What it changed

MeasureBeforeAfter
Share of CPU energy with a name20 to 23%91%
"CPU, no owner" row on batterymost of the channel1.3 to 1.9 W
Named pool coming from the ledger—86 to 89%

The cost is small enough to state precisely: proc_listcoalitions takes 0.089 ms, reading 1600 ledgers takes 2.0 ms. About 2 ms per tick against the 0.95 ms already spent walking proc_pid_rusage. At a ten second tick that is 0.02% of one core, and it wakes no new timer.

The rule this broke

The old invariant was that a row never receives more energy than its own counter recorded. That rule is what produced the hole: it is exactly correct per process and exactly wrong per application, because an application is not one process. Chrome held a median of 16 processes on one Mac and 19 on another, peaking at 40.

Graphics energy has the same shape of problem and no such fix. macOS gives a total for GPU energy and no owner for it, so any split between apps is inferred from GPU time rather than measured. We say so on every page that prints a number. How we count has the rest of the method, including the parts that stay unsolved.

Related: what each app actually costs, measured · watts by workload · what macOS means by "using significant energy" · all the guides

WattMate

Battery diagnostics for Apple silicon Macs.
Everything is measured and computed on your Mac.

Privacy Terms Refunds How we count Who measures this Guides Support

© 2026 WattMate. WattMate is not affiliated with Apple Inc. macOS, Mac and Apple silicon are trademarks of Apple Inc.