energy meters: Linux RAPL and NVML on real hardware; an NVML counter past its board is Implausible #129
Merged
dcharlot
merged 10 commits from 2026-10-01 18:33:48 -04:00
energy/meters-on-real-linux-hardware into main 2026-10-01
energy meters: document the hardware tests' helper functions
Some checks failed
WAI energy receipts / energy receipts · linux (pull_request) Successful in 39s
wai formal verification (integer core) / kani (pull_request) Successful in 3m56s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 53s
WAI neural decoders end-to-end / synthetic fixtures → Rust decode → reference (±4) (pull_request) Successful in 1m4s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 25s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 2m33s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 21m57s
standards workspaces build + test / aac-rs (pull_request) Successful in 8m24s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 4m4s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 2m34s
standards workspaces build + test / map-rs (pull_request) Successful in 4m15s
standards workspaces build + test / mesh-rs (pull_request) Successful in 2m34s
standards workspaces build + test / openpay (pull_request) Successful in 15m42s
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Successful in 1m25s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 5m21s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 1m2s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 2m58s
standards workspaces build + test / arl-rs (pull_request) Successful in 3m19s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 5m44s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 7m23s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 9m0s
standards workspaces build + test / sense-rs (pull_request) Successful in 6m7s
standards workspaces build + test / macOS energy backend (jcp-energy) (pull_request) Successful in 7m49s
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Successful in 1m0s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 4m51s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 56s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 2m22s
wai cross-architecture byte-equality / windows-x86_64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · windows (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · windows (pull_request) Has been cancelled
jouleclaw's public-surface audit failed #129 on seven helpers in jouleclaw-energy's rapl_hardware.rs that had no doc comment: the bracket's zone sums, the masked view, and the watch summary. Document them, and the same helpers in the other crates' copies of the module, so the copies stay alike. Co-Authored-By: Claude Opus 5.5 <[email protected]>
jouleclaw-energy: the GPU plausibility rule is dead code without nvml, and says so
A build without the nvml feature (the workspace's own cargo check) warned that GpuWatch, plausible_gpu_counter and its constants are never used. Co-Authored-By: Claude Opus 5.5 <[email protected]>
proof-cascade: the RAPL hardware test takes 0 as the crate's 'inside the idle noise'
A mutation run on jetson-hub while its CI jobs held the package near its power limit (load average 11) read 0 µJ a call through both windows: the busy window drew no more than the idle one, which the crate documents as a bracket's 'inside the idle noise', not a defect. The test asserted a non-zero figure; it now logs the load and keeps the assertions that hold whatever the load: the calls' total within the oracle's package-only energy around the measurement, the HwShunt grade and the package domain. Co-Authored-By: Claude Opus 5.5 <[email protected]>
eoc-meter: the GPU counter is never the cascade's counter, and one past its board is Implausible
Two defects of eoc's NVML path (feature cuda), both seen on jetson-hub's RTX 4050 Laptop GPU (driver 595.84). detect() ranked NVML first, so on a host with a live NVML counter every consumer (eoc-cascade, eoc-local, eoc-streaming, eoc-agent) was handed the GPU's counter and graded its advance Measured, with no coverage field to say the CPU's share of the work went unread: on jetson-hub detect() returned nvml over a live RAPL. By the family's rule a figure that does not cover the work it is booked to is an estimate in a format without coverage. detect() now has no NVML rung, and NvmlCounter is no longer a JouleCounter (read_gpu_microjoules() reads it as GPU telemetry), so no consumer can grade it Measured. JouleSource and the README say so. And the counter does not count the board's energy while it is polled: at the probe's 100 ms wait it ran at 81-136 W while the board drew 13-15 W against a 65 W maximum limit. The probe now reads the board's draw with each sample and its maximum power limit once (gpu_plausibility), and a live counter past 1.5x the limit, past twice the mean draw plus 5 W, or beside a draw reading past 1.5x the limit, is CounterStatus::Implausible and NvmlCounter is not constructed. Before: tests/nvml_hardware.rs failed on jetson-hub (status=Live against an oracle verdict of Implausible, 119.4 W counter, 13-13.5 W draw; detect()=nvml). After: status=Implausible, constructed=false, detect()=rapl; 58 lib tests with cuda (57 without), the NVML and RAPL hardware tests pass there with and without cuda. Co-Authored-By: Claude Opus 5.5 <[email protected]>
jouleclaw-energy: an NVML counter that runs past its board is Implausible, not HwShunt
On jetson-hub's RTX 4050 Laptop GPU (driver 595.84) the cumulative-energy counter advances in every 100 ms probe interval, so the counter was Live and every reading HwShunt over Gpu. But it does not count the board's energy while it is polled: at the probe's 100 ms tick it ran at 127-305 W while the board drew 5.5-13.4 W against a 65 W maximum limit, and a 1 s span read 44.8 W of HwShunt. The probe now reads every GPU's power draw with each sample and its maximum power limit once (liveness::GpuWatch), and a live counter one of whose GPUs ran past 1.5x its limit, past twice its board's mean draw plus 5 W, or beside a draw reading itself past 1.5x the limit, is CounterLiveness::Implausible: the counter falls to the power-draw integral, graded ModelBased, as for a counter that is not live. A bound whose reading is missing is not applied. The module doc's 50 ms republication period is corrected to the ~100 ms measured. Before: tests/nvml_hardware.rs failed on jetson-hub, liveness=Live against an oracle verdict of Implausible (304.5 W counter, 5.5-7.7 W draw). After: liveness=Implausible, grade ModelBased; 56 lib tests (the rule's among them) and the NVML, RAPL and conformance tests pass there with --features rapl,nvml. Co-Authored-By: Claude Opus 5.5 <[email protected]>
jcp-energy: an NVML counter that runs past its board is Implausible, not Live
On jetson-hub's RTX 4050 Laptop GPU (driver 595.84) the total-energy counter advances in every 100 ms probe interval, so nvml_status() was Live and the NVML rung would grade its spans HwShunt (Partial). But the counter does not count the board's energy while it is polled: at the probe's 100 ms wait it ran at 72-136 W while the board drew 12-14 W against a 65 W maximum limit. The probe now reads the board's power draw with every sample and its maximum power limit once, and a live counter that ran past 1.5x the limit, past twice the board's mean draw plus 5 W, or beside a draw reading itself past 1.5x the limit (590 W in the first second after the GPU wakes) is CounterStatus::Implausible, which no rung admits. A bound whose reading is missing is not applied. Before: tests/nvml_hardware.rs failed on jetson-hub, status=Live against an oracle verdict of Implausible (135.8 W counter, 13.5 W draw). After: status=Implausible; 32 lib tests (the rule's among them), the NVML and RAPL hardware tests pass there with --features nvml. Co-Authored-By: Claude Opus 5.5 <[email protected]>
sandbox-meter: an NVML counter that runs past its board is Implausible, not Live
On jetson-hub's RTX 4050 Laptop GPU (driver 595.84) the total-energy counter is republished every ~100 ms and advances in every probe interval, so the probe called it Live and every span carried a Gpu record graded HwShunt. But the counter does not count the board's energy while it is polled: at the probe's 150 ms tick it ran at 44-103 W while the board drew 10-14 W against a 65 W maximum limit (304 W at a 100 ms tick; another process polling every 10 ms drove it to 415 W in an unpolled reader's span), and a 1 s span with the meter's two reads came out at 22-31 W. The probe now reads each GPU's power draw with every sample and its maximum power limit once, and a live run whose counter ran past 1.5x the limit, past twice the board's mean draw plus 5 W (NVML's stated draw accuracy), or whose draw reading itself was past 1.5x the limit (590 W for the first second after the GPU wakes) is CounterStatus::Implausible: the NVML probe is not built, and the meter has no GPU reading. A bound whose reading is missing is not applied. Before: tests/nvml_hardware.rs failed on jetson-hub, status=Live against an oracle verdict of Implausible (57.8 W counter, 13.1-13.6 W draw). After: status=Implausible, sources [rapl-powercap]; 104 lib tests (the new rule and run tests among them), the NVML and RAPL hardware tests and sandbox-exec-microvm's pass there. Co-Authored-By: Claude Opus 5.5 <[email protected]>
energy meters: NVML hardware tests against an oracle of their own
eoc-meter (cuda), sandbox-meter, jcp-energy (nvml) and jouleclaw-energy (nvml) each get a test binary that opens libnvidia-ml itself (dlopen and its own symbols, not the crate's bindings), measures how often the total-energy counter is republished (about every 100 ms on jetson-hub's RTX 4050 Laptop, driver 595.84), samples it at the crate's own tick by the sustained-advance rule, and holds it to its board through nvidia-smi: no faster than 1.5x the maximum power limit, the board's draw within that bound too, and no faster than twice the draw plus 5 W. The status must agree with that verdict; a live counter's figure must be graded as the family's rule says for a GPU-only figure. On jetson-hub all four fail as committed: the counter advances in every interval, so each crate calls it Live and grades it as hardware, but polled at the probes' 100-150 ms ticks it runs at 58-305 W while the board draws 5.5-13.6 W (max limit 65 W). The fixes follow. Co-Authored-By: Claude Opus 5.5 <[email protected]>
jcp-energy: the fresh-process pin expects only the probes the ladder reached
On a Linux host whose RAPL is live (jetson-hub, i9-13900H) the ladder selects RAPL and never asks NVML or the per-process counter, so with the nvml feature the pin's 'NVML probe ran once' failed there: 'the NVML liveness probe ran 0 times' (left 0, right 1). The expectation now follows the detection's own record of which statuses were asked. Run on jetson-hub: 30 lib tests pass with and without nvml (29 + this one failed before). Co-Authored-By: Claude Opus 5.5 <[email protected]>
energy meters: Linux RAPL hardware tests against an oracle of their own
Each of the five meter crates (and the S5 executor's power meters) gets a test binary, cfg(target_os = "linux"), that reads /sys/class/powercap itself -- every zone's name, energy_uj and max_energy_range_uj -- and applies the sustained-advance rule to the top-level package-<n> zones on its own. The crate's RAPL status must agree with that verdict; where live, the meter's figure over a CPU burn must sit inside the oracle's package-only bracket (outer and inner reads around the meter's), below package+psys and package+core wherever the bracket can resolve them, and be graded as the crate's hardware grade with honest coverage. Where the counters cannot be read the status must say so; the CI container's view (Docker masks /sys/devices/virtual/powercap) is replayed where the crate takes a root. Run on jetson-hub (i9-13900H: intel-rapl:0 package-0, :0:0 core, :1 psys, intel-rapl-mmio:0 root-only): all live, all inside the bracket. Co-Authored-By: Claude Opus 5.5 <[email protected]>