From 05f60d68dd01adf3dcbdad3bf7e2791d5813554b Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 20 Oct 2026 04:06:41 -0400 Subject: [PATCH] jouleclaw-energy: Implausible says what it now means, and a lint CI could not see MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things, all in this crate, found by looking at `cargo doc` and clippy output that no CI step reads. **1. `CounterLiveness::Implausible` understated itself.** #341 made the GPU plausibility bound two-sided, so the variant is produced for a counter that accrues far *less* than its board's telemetry says it drew as well as for one that runs faster than the board can draw. Its doc comment still described only the fast direction — the enum's own documentation understating the variant, which is the defect family this crate keeps surfacing, here introduced by my own omission. The doc now names both arms and says which one matters at an energy ceiling: a counter reading low would otherwise be graded as hardware and then spend a budget it is not measuring, while a counter running fast fails safe. **2. A clippy error CI structurally could not see.** `cargo clippy -p jouleclaw-energy` with **no features** fails `-D warnings` on `vec_init_then_push` at `meter.rs:266`. CI's clippy step lints this crate as `--features ioreport` only, and with any meter feature on there are several pushes so the lint does not fire. The default build is a different program — every hardware meter in `detect_meter` is behind a `cfg` — and a lint pass that never compiles it cannot speak for it. The lint is right for that configuration and wrong as a rewrite: with features on, `vec![..]` would be wrong. So it is allowed at the function with the reason written down, rather than restructured to satisfy one cfg at the others' expense. **3. The blind spot itself.** `standards-build.yml` now lints the default build of `jouleclaw-energy` beside the featured one, so this class of error cannot hide there again. This is the same shape as `check-tests-in-ci` catching a test no job runs: a check that compiles one configuration says nothing about the others. Also de-links `plausible_gpu_counter` in the corrected comment, which is `pub(crate)` and cannot be linked from a public item's docs — one of this crate's nine `cargo doc` warnings, and the only one inside text being changed here. The remaining eight are **not** touched. They link private or platform-gated items in `process.rs`, which is `#[cfg(target_os = "macos")]`, and in `rapl.rs`, so which of them appear depends on the target. `cargo doc` is not in this workspace's CI gate, and rewriting another author's docs across a cfg boundary from a host that sees only one side is how a fix becomes a regression on the other. Verified: clippy clean with **and** without `ioreport`; 70 tests pass. Co-Authored-By: Claude Opus 5 --- .github/workflows/standards-build.yml | 6 +++++ .../crates/jouleclaw-energy/src/liveness.rs | 22 +++++++++++++------ .../crates/jouleclaw-energy/src/meter.rs | 6 +++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/.github/workflows/standards-build.yml b/.github/workflows/standards-build.yml index 95d96037a..bcaa95ed4 100644 --- a/.github/workflows/standards-build.yml +++ b/.github/workflows/standards-build.yml @@ -579,4 +579,10 @@ jobs: lint proof/proof-rs -p proof-cascade -p proof-solvers lint eoc/eoc-rs -p eoc-meter lint jouleclaw/jouleclaw-rs -p jouleclaw-energy --features ioreport + # And the DEFAULT build. Linting only the featured one hid a real + # clippy error for as long as it existed: every hardware meter in + # `detect_meter` is behind a `cfg`, so the default build is a + # different program, and a lint pass that never compiles it cannot + # speak for it. + lint jouleclaw/jouleclaw-rs -p jouleclaw-energy lint sandbox/sandbox-rs -p sandbox-meter -p sandbox-proof diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/src/liveness.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/src/liveness.rs index 6ee4c1324..b67819732 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/src/liveness.rs +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/src/liveness.rs @@ -59,13 +59,21 @@ pub enum CounterLiveness { /// intervals, four in every five of those observed, over at least 300 ms, /// while the probing thread burned CPU. Live, - /// Every channel kept advancing, but a GPU counter (NVML's) ran faster - /// than its board can draw power over the probe: past 1.5 times the - /// board's maximum power limit, or past twice the board's own power draw - /// plus 5 W ([`plausible_gpu_counter`]). It is not counting the board's - /// energy, so its figure would not be a reading of the GPU's work. Seen on - /// an RTX 4050 Laptop GPU (driver 595.84), whose counter runs at 4 to 30 - /// times the board's draw while it is polled. + /// Every channel kept advancing, but a GPU counter (NVML's) did not count + /// its board's energy over the probe — in **either** direction, because + /// the bound is two-sided (`plausible_gpu_counter`): + /// + /// - it ran **faster** than the board can draw power: past 1.5 times the + /// board's maximum power limit, or past twice the board's own power draw + /// plus 5 W. Seen on an RTX 4050 Laptop GPU (driver 595.84), whose + /// counter runs at 4 to 30 times the board's draw while it is polled; + /// - or it accrued **far less** than the board's own telemetry says it + /// drew, below a quarter of that draw once 5 W of slack is taken off it. + /// This is the direction that matters at an energy ceiling, since a + /// counter reading low would otherwise be graded as hardware and then + /// spend a budget it is not measuring. + /// + /// Either way its figure would not be a reading of the GPU's work. Implausible, /// Every channel kept advancing and the counter could not be judged, /// because the board's own power telemetry was unusable over the probe: a diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/src/meter.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/src/meter.rs index 80dd9024e..00f87b1ff 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/src/meter.rs +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/src/meter.rs @@ -262,6 +262,12 @@ impl EnergyMeter for EstimationMeter { /// reads its cumulative-energy counter as a hardware reading only when that is /// live, and otherwise its power-draw integral, graded `ModelBased`. Each of /// those probes also runs once per process, on the first calling thread. +// Every hardware meter below is behind a `cfg`, so in the default build the +// only push left is the estimator's and clippy reads the vector as one that +// should have been a `vec![..]`. With any meter feature on there are several +// pushes and the lint does not fire — which is why CI never saw it: the clippy +// step lints this crate with `--features ioreport` only. +#[allow(clippy::vec_init_then_push)] pub fn detect_meter() -> CompositeMeter { let mut meters: Vec> = Vec::new(); -- 2.54.0 (Apple Git-157)