The kernel adds a thread's CPU energy to its process's count
(proc_pid_rusage ri_energy_nj) only when the thread leaves a core, and a
process's read of its own count does not take the reading thread off its
core. MacosProcessEnergyCounter read the count as it stood, so a span
bracketed by two reads held up to a scheduling quantum of what the
thread spent before the span and lacked the span's own tail, and every
consumer here tags a positive delta of the counter Measured.
Measured on an M3 Ultra (macOS 27.0.1, load average about 14) against
the tests' own reader, which sleeps before each read until the process's
context-switch count rose; three runs of 40 spans of each kind:
- 1 ms of the reading thread's CPU between two reads: 39, 40 and 40
spans read nothing or under two thirds of the work;
- a 2 ms sleep between two reads, straight after 8 ms of CPU: 40, 40 and
37 spans read the burn before them, about 41 mJ for a sleep.
read_microjoules now sleeps (50 us, then 200 us, 1 ms, 5 ms) until the
reading thread's own energy count (proc_pidinfo PROC_PIDTHREADCOUNTS,
read only in the one layout known: 88 bytes, two performance levels
here) rose across a sleep, and only then reads the process. A read whose
thread was not seen to leave is an error, not a short count: eoc-cascade,
eoc-agent and eoc-local all take a failed read as no span and keep their
estimate. process_energy_status is Unavailable, with no probe run, where
the thread's count does not read, and the liveness probe still samples
with no sleep. The rule and the layout decoding are pure and compiled on
every platform.
With it, on that host, quiet and under 40 busy loops (110 runs): every
1 ms span read 79% or more of the tests' window around it (98% when
quiet) and no sleep read more than 3% of the window that held the burn.
A read costs 83 us at the median on the quiet host and 226 us under the
busy loops, against under 1 us; detect_in_process's docs say so, and
that a batch of short operations is bracketed by one span, not one each.
tests/process_energy_short_span.rs is the test that fails without it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>