meters: clippy-clean in every Linux configuration, and a CI gate for it #650

Merged
dcharlot merged 6 commits from meters/clippy-clean into main 2026-11-06 02:39:54 -05:00
11 changed files with 87 additions and 31 deletions

View file

@ -223,6 +223,12 @@ jobs:
working-directory: joule-code/examples/l3-jouleclaw
run: cargo test
# Its own code only (`--no-deps`): the JouleClaw crates it builds through
# path dependencies are linted by their own workspace.
- name: L3 JouleClaw binding example lints clean
working-directory: joule-code/examples/l3-jouleclaw
run: cargo clippy --all-targets --no-deps -- -D warnings
# The MOQT relay, at draft-22 and, through its bridge, -18 and -16 (tests/drafts.rs,
# tests/publish_done_streams.rs). Every test is on loopback: two publishers and three
# subscribers over native QUIC, WebTransport over HTTP/3 and the WebSocket fallback; a

View file

@ -77,12 +77,12 @@ jobs:
energy: ${{ steps.plan.outputs.energy }}
env:
WORKSPACES: |
- { name: jcp-rs, dir: jcp/jcp-rs, runner: linux, conformance: jcp-conformance, feature_tests: "-p jcp-energy --features nvml" }
- { name: jcp-rs, dir: jcp/jcp-rs, runner: linux, conformance: jcp-conformance, feature_tests: "-p jcp-energy --features nvml", meter_lint: "jcp-energy jcp-energy:nvml" }
# sandbox-tee's certificate-chain verifiers sit behind features (their
# X.509 and RSA dependencies are heavy), so a plain `cargo test` never
# runs their tests: `amd-kds` (SEV-SNP's VCEK to AMD's root) and
# `intel-pcs` (TDX's PCK to Intel's root).
- { name: sandbox-rs, dir: sandbox/sandbox-rs, runner: linux, conformance: sandbox-conformance, feature_tests: "-p sandbox-tee --features amd-kds,intel-pcs" }
- { name: sandbox-rs, dir: sandbox/sandbox-rs, runner: linux, conformance: sandbox-conformance, feature_tests: "-p sandbox-tee --features amd-kds,intel-pcs", meter_lint: "sandbox-meter" }
- { name: smart-byte-rs, dir: smart-byte/smart-byte-rs, runner: linux }
- { name: pool-rs, dir: pool/pool-rs, runner: linux }
# `clippy: true` adds the zero-warning gate below. It is opt-in per
@ -139,7 +139,7 @@ jobs:
# able to fail becomes a finding rather than staying green.
- { name: proof-rs, dir: proof/proof-rs, runner: linux, z3: true, conformance: proof-conformance, applied: proof/conformance/v1/joulespec.toml }
- { name: joulehook-rs, dir: joulehook/joulehook-rs, runner: linux }
- { name: eoc-rs, dir: eoc/eoc-rs, runner: linux, feature_tests: "-p eoc-meter --features cuda" }
- { name: eoc-rs, dir: eoc/eoc-rs, runner: linux, feature_tests: "-p eoc-meter --features cuda", meter_lint: "eoc-meter eoc-meter:cuda" }
# mesh-openai's `serve_example` test (the reference server seals what
# its meter read, never a fixture's figure) has required-features
# `server`, so `cargo test --workspace` builds none of it; naming the
@ -163,7 +163,7 @@ jobs:
# failing for no apparent reason, check `df -h /mnt/lima-colima-txarm`
# on the txarm VM before suspecting the code: a full cache volume
# fails whatever happens to be building, not whatever changed.
- { name: jouleclaw-rs, dir: jouleclaw/jouleclaw-rs, runner: linux-arm64, rustflags: "-C target-feature=+fp16", cache_suffix: "-nodebug", feature_tests: "-p jouleclaw-energy --features rapl,nvml" }
- { name: jouleclaw-rs, dir: jouleclaw/jouleclaw-rs, runner: linux-arm64, rustflags: "-C target-feature=+fp16", cache_suffix: "-nodebug", feature_tests: "-p jouleclaw-energy --features rapl,nvml", meter_lint: "jouleclaw-energy jouleclaw-energy:rapl jouleclaw-energy:nvml jouleclaw-energy:rapl,nvml" }
steps:
- uses: actions/checkout@v5
with:
@ -456,6 +456,28 @@ jobs:
RUSTFLAGS: ${{ matrix.rustflags }}
run: cargo test ${{ matrix.feature_tests }}
# The meter crates' Linux side: RAPL (powercap and the MSRs), NVML and
# CUDA are Linux code or sit behind features no workspace member enables,
# and the macOS lint step lints these crates only as macOS builds them.
# So no job linted them here, and jouleclaw-energy's `rapl` build carried
# a dead function, and four crates' NVML hardware tests a clippy error,
# for as long as they existed. Each `meter_lint` entry is a package, or
# `<package>:<features>`, linted on the workspace's own runner.
- name: clippy on the Linux meter code
if: matrix.meter_lint
working-directory: ${{ matrix.dir }}
shell: bash
env:
RUSTFLAGS: ${{ matrix.rustflags }}
run: |
set -eu
for config in ${{ matrix.meter_lint }}; do
package=${config%%:*}
features=()
[ "$config" = "$package" ] || features=(--features "${config#*:}")
cargo clippy -p "$package" "${features[@]}" --all-targets --no-deps -- -D warnings
done
# The page-set packages are not committed; their catalogue is, naming
# each package by its content. Any change to what they are built from —
# the sources, the app's translations and gloss audits, the symbol

View file

@ -138,9 +138,13 @@ mod nvml_oracle {
}
}
/// One GPU as `nvidia-smi` gives it, in watts: (maximum power limit,
/// enforced limit, draw), each `None` where it gave none.
pub type SmiPower = (Option<f64>, Option<f64>, Option<f64>);
/// `nvidia-smi`'s view of every GPU: (maximum power limit, enforced limit,
/// draw), in watts; `None` without `nvidia-smi`.
pub fn smi() -> Option<Vec<(Option<f64>, Option<f64>, Option<f64>)>> {
pub fn smi() -> Option<Vec<SmiPower>> {
let out = std::process::Command::new("nvidia-smi")
.args(["--query-gpu=power.max_limit,enforced.power.limit,power.draw", "--format=csv,noheader,nounits"])
.output()
@ -158,7 +162,7 @@ mod nvml_oracle {
/// GPU `i`'s ceiling in watts: `nvidia-smi`'s maximum power limit, else
/// NVML's.
pub fn ceiling_w(nvml: &Nvml, smi: &Option<Vec<(Option<f64>, Option<f64>, Option<f64>)>>, i: usize) -> Option<f64> {
pub fn ceiling_w(nvml: &Nvml, smi: &Option<Vec<SmiPower>>, i: usize) -> Option<f64> {
smi.as_ref()
.and_then(|s| s.get(i))
.and_then(|(max, _, _)| *max)
@ -319,7 +323,7 @@ mod nvml_oracle {
let window = last_at;
let rate_w: Vec<f64> =
first.iter().zip(&prev).map(|(a, b)| b.saturating_sub(*a) as f64 / 1000.0 / window.as_secs_f64().max(1e-9)).collect();
let draw = |s: &Option<Vec<(Option<f64>, Option<f64>, Option<f64>)>>, i: usize| s.as_ref().and_then(|s| s.get(i)).and_then(|t| t.2);
let draw = |s: &Option<Vec<SmiPower>>, i: usize| s.as_ref().and_then(|s| s.get(i)).and_then(|t| t.2);
let draws_w: Vec<Vec<f64>> = (0..n).map(|i| [draw(&before, i), draw(&after, i)].into_iter().flatten().collect()).collect();
let ceiling_w: Vec<Option<f64>> = (0..n).map(|i| ceiling_w(&nvml, &after, i)).collect();
// The verdict that speaks for the run is the most severe of its GPUs',

View file

@ -6312,9 +6312,9 @@ impl FabricMeter {
/// Which fabrics this meter can read, and whose work each figure counts.
pub fn readable(&self) -> Vec<(Fabric, Attribution)> {
let mut out = Vec::new();
#[cfg(target_os = "macos")]
{
let mut out = Vec::new();
if self.process.is_some() {
out.push((Fabric::Cpu, Attribution::Process));
}
@ -6324,8 +6324,12 @@ impl FabricMeter {
out.push((Fabric::Npu, Attribution::Machine));
}
}
out
}
#[cfg(not(target_os = "macos"))]
{
Vec::new()
}
out
}
/// Run `work`, and report each fabric's energy over it.
@ -6352,9 +6356,9 @@ impl FabricMeter {
let out = work();
let span_ns = started.elapsed().as_nanos();
let mut readings = Vec::new();
#[cfg(target_os = "macos")]
{
let readings = {
let mut readings = Vec::new();
let cpu_after = cpu_before.and_then(|_| self.process.as_ref()?.read_energy_nj());
if let (Some(b), Some(a)) = (cpu_before, cpu_after) {
readings.push(FabricEnergy {
@ -6383,8 +6387,13 @@ impl FabricMeter {
});
}
}
}
let _ = span_ns;
readings
};
#[cfg(not(target_os = "macos"))]
let readings = {
let _ = span_ns;
Vec::new()
};
(out, readings)
}
}
@ -6461,11 +6470,8 @@ impl FabricSource {
/// cannot be measured" are different facts and a survey that blurs them is
/// worthless. See [`FabricStatus::NotImplemented`].
pub fn has_backend(self) -> bool {
match self {
FabricSource::ProcRusage | FabricSource::IoReport | FabricSource::Rapl => true,
FabricSource::Nvml => cfg!(feature = "nvml"),
_ => false,
}
matches!(self, FabricSource::ProcRusage | FabricSource::IoReport | FabricSource::Rapl)
|| (matches!(self, FabricSource::Nvml) && cfg!(feature = "nvml"))
}
}

View file

@ -137,9 +137,13 @@ mod nvml_oracle {
}
}
/// One GPU as `nvidia-smi` gives it, in watts: (maximum power limit,
/// enforced limit, draw), each `None` where it gave none.
pub type SmiPower = (Option<f64>, Option<f64>, Option<f64>);
/// `nvidia-smi`'s view of every GPU: (maximum power limit, enforced limit,
/// draw), in watts; `None` without `nvidia-smi`.
pub fn smi() -> Option<Vec<(Option<f64>, Option<f64>, Option<f64>)>> {
pub fn smi() -> Option<Vec<SmiPower>> {
let out = std::process::Command::new("nvidia-smi")
.args(["--query-gpu=power.max_limit,enforced.power.limit,power.draw", "--format=csv,noheader,nounits"])
.output()
@ -157,7 +161,7 @@ mod nvml_oracle {
/// GPU `i`'s ceiling in watts: `nvidia-smi`'s maximum power limit, else
/// NVML's.
pub fn ceiling_w(nvml: &Nvml, smi: &Option<Vec<(Option<f64>, Option<f64>, Option<f64>)>>, i: usize) -> Option<f64> {
pub fn ceiling_w(nvml: &Nvml, smi: &Option<Vec<SmiPower>>, i: usize) -> Option<f64> {
smi.as_ref()
.and_then(|s| s.get(i))
.and_then(|(max, _, _)| *max)
@ -318,7 +322,7 @@ mod nvml_oracle {
let window = last_at;
let rate_w: Vec<f64> =
first.iter().zip(&prev).map(|(a, b)| b.saturating_sub(*a) as f64 / 1000.0 / window.as_secs_f64().max(1e-9)).collect();
let draw = |s: &Option<Vec<(Option<f64>, Option<f64>, Option<f64>)>>, i: usize| s.as_ref().and_then(|s| s.get(i)).and_then(|t| t.2);
let draw = |s: &Option<Vec<SmiPower>>, i: usize| s.as_ref().and_then(|s| s.get(i)).and_then(|t| t.2);
let draws_w: Vec<Vec<f64>> = (0..n).map(|i| [draw(&before, i), draw(&after, i)].into_iter().flatten().collect()).collect();
let ceiling_w: Vec<Option<f64>> = (0..n).map(|i| ceiling_w(&nvml, &after, i)).collect();
// The verdict that speaks for the run is the most severe of its GPUs',

View file

@ -724,7 +724,9 @@ mod tests {
assert_eq!(note, "estimated by apple-silicon from host load");
}
_ => {
assert!(!cfg!(all(target_os = "macos", target_arch = "aarch64")), "a Mac with no host counter: {name}");
if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
panic!("a Mac with no host counter: {name}");
}
assert_eq!((name, from_counter), ("none", false));
assert_eq!(e, Energy::from_tokens(7, 9));
}

View file

@ -245,7 +245,7 @@ fn count_before(stderr: &str, unit: &str) -> Option<u32> {
for line in stderr.lines() {
if let Some(idx) = line.find(&needle) {
let prefix = &line[..idx];
let token = prefix.rsplit(|c: char| c == '/' || c == ' ').find(|s| !s.is_empty());
let token = prefix.rsplit(['/', ' ']).find(|s| !s.is_empty());
if let Some(n) = token.and_then(|t| t.trim().parse::<u32>().ok()) {
return Some(n);
}

View file

@ -43,6 +43,10 @@ impl LlmBackend for OneMatmul {
/// declaring none), switch the fabric on for this process, resolve one L3
/// request, and return its receipt energy with the ledger window it was priced
/// from. Call once per test process: the fabric is discovered once.
// Each test binary compiles this module on its own, and the seeded test does
// not price a row, so there this helper is unused (as `resolve_seeded` is in
// the others).
#[allow(dead_code)]
pub fn resolve_priced_at(tag: &str, grade: Option<EnergyProvenance>) -> (Energy, Vec<LedgerEntry>) {
let path = std::path::Path::new(env!("CARGO_TARGET_TMPDIR")).join(format!("l3-fabric-grade-{tag}.json"));
let declared = grade.map(|g| format!(r#","provenance":"{g:?}""#)).unwrap_or_default();

View file

@ -551,13 +551,13 @@ fn cached(set: &[PathBuf], probe: impl FnOnce() -> CounterLiveness) -> CounterLi
/// caller is the cache in [`package_liveness`].
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
fn probe_packages(packages: &[Package]) -> CounterLiveness {
probe_packages_stepping(packages, || {})
crate::liveness::probe(|| open_sampler(packages), PROBE_TICK)
}
/// [`probe_packages`], with `before_burn` run at the start of every tick
/// ([`crate::liveness::probe_stepping`]): how a test advances a fake tree in
/// step with the probe.
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
#[cfg(test)]
fn probe_packages_stepping(packages: &[Package], before_burn: impl FnMut()) -> CounterLiveness {
crate::liveness::probe_stepping(|| open_sampler(packages), PROBE_TICK, before_burn)
}

View file

@ -139,9 +139,13 @@ mod nvml_oracle {
}
}
/// One GPU as `nvidia-smi` gives it, in watts: (maximum power limit,
/// enforced limit, draw), each `None` where it gave none.
pub type SmiPower = (Option<f64>, Option<f64>, Option<f64>);
/// `nvidia-smi`'s view of every GPU: (maximum power limit, enforced limit,
/// draw), in watts; `None` without `nvidia-smi`.
pub fn smi() -> Option<Vec<(Option<f64>, Option<f64>, Option<f64>)>> {
pub fn smi() -> Option<Vec<SmiPower>> {
let out = std::process::Command::new("nvidia-smi")
.args(["--query-gpu=power.max_limit,enforced.power.limit,power.draw", "--format=csv,noheader,nounits"])
.output()
@ -159,7 +163,7 @@ mod nvml_oracle {
/// GPU `i`'s ceiling in watts: `nvidia-smi`'s maximum power limit, else
/// NVML's.
pub fn ceiling_w(nvml: &Nvml, smi: &Option<Vec<(Option<f64>, Option<f64>, Option<f64>)>>, i: usize) -> Option<f64> {
pub fn ceiling_w(nvml: &Nvml, smi: &Option<Vec<SmiPower>>, i: usize) -> Option<f64> {
smi.as_ref()
.and_then(|s| s.get(i))
.and_then(|(max, _, _)| *max)
@ -320,7 +324,7 @@ mod nvml_oracle {
let window = last_at;
let rate_w: Vec<f64> =
first.iter().zip(&prev).map(|(a, b)| b.saturating_sub(*a) as f64 / 1000.0 / window.as_secs_f64().max(1e-9)).collect();
let draw = |s: &Option<Vec<(Option<f64>, Option<f64>, Option<f64>)>>, i: usize| s.as_ref().and_then(|s| s.get(i)).and_then(|t| t.2);
let draw = |s: &Option<Vec<SmiPower>>, i: usize| s.as_ref().and_then(|s| s.get(i)).and_then(|t| t.2);
let draws_w: Vec<Vec<f64>> = (0..n).map(|i| [draw(&before, i), draw(&after, i)].into_iter().flatten().collect()).collect();
let ceiling_w: Vec<Option<f64>> = (0..n).map(|i| ceiling_w(&nvml, &after, i)).collect();
// The verdict that speaks for the run is the most severe of its GPUs',

View file

@ -139,9 +139,13 @@ mod nvml_oracle {
}
}
/// One GPU as `nvidia-smi` gives it, in watts: (maximum power limit,
/// enforced limit, draw), each `None` where it gave none.
pub type SmiPower = (Option<f64>, Option<f64>, Option<f64>);
/// `nvidia-smi`'s view of every GPU: (maximum power limit, enforced limit,
/// draw), in watts; `None` without `nvidia-smi`.
pub fn smi() -> Option<Vec<(Option<f64>, Option<f64>, Option<f64>)>> {
pub fn smi() -> Option<Vec<SmiPower>> {
let out = std::process::Command::new("nvidia-smi")
.args(["--query-gpu=power.max_limit,enforced.power.limit,power.draw", "--format=csv,noheader,nounits"])
.output()
@ -159,7 +163,7 @@ mod nvml_oracle {
/// GPU `i`'s ceiling in watts: `nvidia-smi`'s maximum power limit, else
/// NVML's.
pub fn ceiling_w(nvml: &Nvml, smi: &Option<Vec<(Option<f64>, Option<f64>, Option<f64>)>>, i: usize) -> Option<f64> {
pub fn ceiling_w(nvml: &Nvml, smi: &Option<Vec<SmiPower>>, i: usize) -> Option<f64> {
smi.as_ref()
.and_then(|s| s.get(i))
.and_then(|(max, _, _)| *max)
@ -320,7 +324,7 @@ mod nvml_oracle {
let window = last_at;
let rate_w: Vec<f64> =
first.iter().zip(&prev).map(|(a, b)| b.saturating_sub(*a) as f64 / 1000.0 / window.as_secs_f64().max(1e-9)).collect();
let draw = |s: &Option<Vec<(Option<f64>, Option<f64>, Option<f64>)>>, i: usize| s.as_ref().and_then(|s| s.get(i)).and_then(|t| t.2);
let draw = |s: &Option<Vec<SmiPower>>, i: usize| s.as_ref().and_then(|s| s.get(i)).and_then(|t| t.2);
let draws_w: Vec<Vec<f64>> = (0..n).map(|i| [draw(&before, i), draw(&after, i)].into_iter().flatten().collect()).collect();
let ceiling_w: Vec<Option<f64>> = (0..n).map(|i| ceiling_w(&nvml, &after, i)).collect();
// The verdict that speaks for the run is the most severe of its GPUs',