//! The examples' `SpanMeter` (`examples/support/span_meter.rs`), driven over //! scripted spans through the real `run_interaction`: each interaction is //! sealed with its own span's figure and grade, and a span with no reading of //! the CPU is never sealed as a hardware reading of the interaction. #[path = "../examples/support/span_meter.rs"] #[allow(dead_code)] mod span_meter; use joule_ui_interaction::{ContentId, InteractionLog, Provenance, run_interaction, verify_chain}; use sandbox_meter::{DeviceEnergy, Measurement, Provenance as Grade}; use sandbox_schema::DeviceClass; use span_meter::{SpanMeter, Spans}; use std::collections::VecDeque; use std::time::Duration; /// A meter whose spans replay a script, one per close, and whose estimate is /// 20 W, the Sandbox estimator's default. struct Script(VecDeque); impl Spans for Script { type Open = (); fn open(&mut self) {} fn close(&mut self, _span: ()) -> Measurement { self.0.pop_front().expect("script exhausted") } fn estimate(&self, wall: Duration) -> Measurement { let uj = (wall.as_secs_f64() * 20e6) as u64; Measurement { devices: vec![record(DeviceClass::Cpu, Grade::Estimator, uj)], wall, source: "estimator" } } } fn record(class: DeviceClass, provenance: Grade, uj: u64) -> DeviceEnergy { DeviceEnergy { class, measured_uj: (provenance == Grade::HwShunt).then_some(uj), estimated_uj: uj, provenance } } /// An 8 ms span of these records. fn span_of(devices: Vec, source: &'static str) -> Measurement { Measurement { devices, wall: Duration::from_millis(8), source } } /// Seal `n` interactions over the scripted spans — the first interaction's /// own span, then for each later one the idle gap before it and its own span /// — through the real `run_interaction`, returning the log. fn seal(script: Vec, n: u8) -> InteractionLog { assert_eq!(script.len(), 2 * usize::from(n) - 1, "setup: one span for the first, two for each later one"); let meter = SpanMeter::new(Script(script.into())); let surface = ContentId::of_bytes(b"
"); let mut log = InteractionLog::new(ed25519_dalek::SigningKey::from_bytes(&[9u8; 32])); for i in 1..=n { log.record(run_interaction(&surface, "apply", "did:key:z6MkTestActor", &[i], &meter, None, || vec![i])); } log } /// A GPU-only span — NVML's reading, no CPU record, as on a Linux host whose /// only live counter is the GPU's — is not a reading of the interaction, /// which ran on the CPU. It is sealed as the meter's estimate for the span's /// 8 ms (160 000 µJ at 20 W), `Estimator`: not the GPU's 9 000 µJ as /// `HwShunt`, and not 0. The span that reads the CPU beside the GPU (RAPL and /// NVML) is a reading, sealed as one; and one that reads only the CPU is too. #[test] fn a_gpu_only_span_is_sealed_as_an_estimate_never_as_a_reading() { let gpu_only = span_of(vec![record(DeviceClass::Gpu, Grade::HwShunt, 9_000)], "nvml-total-energy"); assert!(gpu_only.fully_measured() && !gpu_only.reads_cpu(), "setup: a GPU reading with no CPU record"); let gap = || span_of(vec![record(DeviceClass::Cpu, Grade::Estimator, 999_999)], "estimator"); let both = span_of( vec![record(DeviceClass::Cpu, Grade::HwShunt, 1_500), record(DeviceClass::Gpu, Grade::HwShunt, 9_000)], "composite", ); let cpu = span_of(vec![record(DeviceClass::Cpu, Grade::HwShunt, 2_000)], "rapl-powercap"); let log = seal(vec![gpu_only, gap(), both, gap(), cpu], 3); assert_eq!(verify_chain(log.entries()), Ok(3)); let sealed: Vec<_> = log.entries().iter().map(|e| e.interaction.energy).collect(); let got: Vec<_> = sealed.iter().map(|e| (e.provenance, e.measured_uj, e.estimated_uj)).collect(); assert_eq!( got, [ (Provenance::Estimator, None, 160_000), (Provenance::HwShunt, Some(10_500), 10_500), (Provenance::HwShunt, Some(2_000), 2_000), ], "{sealed:?}" ); }