open-standards/mesh/mesh-rs
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Transaction Science 660ab68a04 jouleclaw-energy: a live counter of this process's own CPU energy, kept out of detect_meter
On macOS 27.0.1 an unprivileged process gets no live CPU energy from IOReport:
its CPU rails are frozen on an M5 Max and an M3 Ultra, so `detect_meter` falls
to the load-average estimator there. The kernel does keep one live CPU energy
figure an unprivileged process can read: `proc_pid_rusage(getpid(),
RUSAGE_INFO_V6)` returns `ri_energy_nj`, the cumulative energy it attributes to
this process's threads. Public libproc API, no privilege. Measured here (M5 Max,
macOS 27.0.1, 2026-09-30, host heavily loaded): it changes about every 0.6 ms
(86 changes in 50 ms of polling); 200 ms of one thread's CPU time read 0.448 J;
a 200 ms sleep read 137 µJ.

The trap is coverage. It reads this process's threads and nothing else: not
other processes, not this process's children, not the GPU, ANE or DRAM, this
process's own GPU work included. A figure from it is a hardware reading of work
that ran on this process's threads, and would bill zero for a model served by a
subprocess. JouleClaw SPEC §1.3: a figure must not claim coverage of a domain
its counter does not read.

- `process::ProcessCpuCounter` (macOS, no feature) reads it over a new domain,
  `EnergyDomain::ProcessCpu`, documented to exactly that scope, graded HwShunt
  like RAPL and NVML, and only while live: the shared sustained-advance rule,
  with the probing thread burning 25 ms of its own CPU between samples, probed
  once per process and cached (`process_cpu_liveness`). Whether AMX work a
  thread drives is in the kernel's figure is not verified, and the docs say so.
- A window over which it did not advance by a whole µJ is not a reading:
  `ProcessCpuCounter::measure` returns the new `EnergyError::NotAdvanced`,
  never a zero; a failed read or a counter going backwards is an error too.
- `detect_meter` does not include it, and says why. `detect_in_process_meter`
  returns it when live, else exactly `detect_meter`; its docs limit it to work
  on this process's own threads. `CompositeMeter::names` lists what a
  composite holds, so a test can say what is not in it.
- mesh-meter decides the new domain, as its exhaustive match requires: a
  per-process figure does not cover a mesh/1 request, so through `HwMeter` it
  would be an estimate. `HwMeter::detect` uses `detect_meter`, so it never
  meets this counter unless bridged by hand.

Tests. The hardware checks run in `tests/process_cpu.rs`, one test in its own
process, because the counter reads every thread of the process and concurrent
tests would be in the figure: live, probed once across four threads; 200 ms of
one thread's CPU reads 0.2 to 20 J, HwShunt over ProcessCpu; a 200 ms sleep
reads under 0.1 J or is NotAdvanced; `detect_meter` holds apple-ioreport,
apple-silicon and estimation and not process-cpu, while the in-process meter
selects it; a busy `/bin/sh` child that burned 0.744 J (its own counter, read
independently at the SDK's offset) put 264 µJ in this process's window, which
spans its kill and reap. In the lib: the struct matches the SDK layout (464
bytes, energy at 336, checked with the C compiler); a counter given Frozen or
Unavailable reads and measures nothing; the production probe over a held
reader is Frozen after the budget and over a failing one Unavailable; the
window rule; and a live one-sided burn check.

Ran: `cargo test -p jouleclaw-energy` with default features and with
`ioreport,rapl,nvml`; mesh-meter with `hw-meter`, `hw-meter-apple`,
`hw-meter-nvml`; clippy clean on the new code.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-30 22:21:00 -04:00
..
crates jouleclaw-energy: a live counter of this process's own CPU energy, kept out of detect_meter 2026-09-30 22:21:00 -04:00
Cargo.lock chore(deps): cargo update across the workspace; eoc toolchain → 1.98.0 2026-08-26 07:39:26 -04:00
Cargo.toml msrv: raise the rust-version floor to 1.97 across the workspaces 2026-07-23 00:17:08 -04:00