meters: clippy-clean in every Linux configuration, and a CI gate for it #650
11 changed files with 87 additions and 31 deletions
6
.github/workflows/joule-code-conformance.yml
vendored
6
.github/workflows/joule-code-conformance.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
30
.github/workflows/standards-build.yml
vendored
30
.github/workflows/standards-build.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue