//! A joule-ui interaction metered by the **real Sandbox `PowerMeter`** — the //! reference Meter edge (SPEC §5) — rather than a stub. Proves the //! recognized-edge design composes end to end: a fired Action's energy //! record is sealed into the chain. //! //! What that record is depends on the host, and the output says which. Each //! interaction is metered as one `PowerMeter` span and sealed with that //! span's own figure and provenance (the `SpanMeter` in `support/`, the //! adapter `joule-ui-serve` uses). The handlers run on this process's own //! thread and spawn nothing, so the meter is `PowerMeter::detect_in_process()`. //! Where this process's own CPU energy counter is live (every Apple Silicon //! host checked, macOS 27.0.1) and advanced over the span, the figure is //! `HwShunt`: the energy the kernel attributes to this process's threads over //! the interaction. Where it is not, the meter is `detect()`'s: a live //! machine-wide counter (Intel/AMD RAPL; Apple Silicon IOReport only when its //! CPU rail is live) gives `HwShunt`, the machine-wide delta; with none, it is //! the meter's `Estimator` figure, wall-clock × TDP for the interaction's own //! span: an estimate, labelled as one, and never 0 µJ for work that ran. //! //! ```bash //! cargo run -p joule-ui-interaction --example real_meter --release //! ``` use joule_ui_interaction::{ContentId, Energy, InteractionLog, Provenance, run_interaction, verify_chain}; use sandbox_meter::PowerMeter; use std::time::{Duration, Instant}; #[path = "support/span_meter.rs"] mod span_meter; use span_meter::SpanMeter; /// What an interaction's figure is, read from its own record: a 0 for work /// that ran is no figure, and only a hardware record is called a reading. fn figure(e: &Energy) -> String { match (e.uj(), e.provenance) { (0, p) => format!("no figure (0 µJ [{}]: the meter gave no reading for this work)", p.label()), (uj, Provenance::HwShunt) => format!("{uj} µJ [HwShunt]"), (uj, p) => format!("{uj} µJ [{}, an estimate]", p.label()), } } fn main() { // The handlers below run on this thread and spawn nothing: in-process work. let pm = PowerMeter::detect_in_process(); let hardware = pm.is_hardware(); let hardware_in_process = pm.sources().contains(&"process-energy"); println!( "meter: {}", if hardware { format!("Sandbox PowerMeter — hardware ({})", pm.sources().join(", ")) } else { let why = match sandbox_meter::ioreport_status() { // Apple Silicon: say why the SoC counters were declined. sandbox_meter::IoReportStatus::Unavailable => String::new(), status => format!(" ({status})"), }; format!( "no hardware probe{why} — each interaction below is sealed with the estimator's \ wall-clock × TDP figure for its own span" ) } ); let meter = SpanMeter::new(pm); let surface = ContentId::of_bytes(b"
"); let signing = ed25519_dalek::SigningKey::from_bytes(&[5u8; 32]); let mut log = InteractionLog::new(signing); // Three real interactions: each handler does ~150ms of genuine CPU work // (the "computation behind the UI"), metered by whatever the meter can read. for n in 1..=3u64 { let it = run_interaction(&surface, "render", "did:key:z6MkDemoUser", &n.to_le_bytes(), &meter, None, || { let t = Instant::now(); let mut x = n; while t.elapsed() < Duration::from_millis(150) { for _ in 0..50_000 { x = x.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407); } } std::hint::black_box(x).to_le_bytes().to_vec() }); println!(" render #{n}: {} · {}", it.verdict.tag(), figure(&it.energy)); log.record(it); } let verified = verify_chain(log.entries()).expect("chain verifies"); // Label the total by the sealed records, not by the meter: "measured" // only when every record is a hardware reading that moved. let records: Vec<&Energy> = log.entries().iter().map(|e| &e.interaction.energy).collect(); let unmetered = records.iter().filter(|e| e.uj() == 0).count(); let label = if unmetered > 0 { format!( " — {unmetered} of {} interaction(s) have no figure (0 µJ for work that ran): not a measurement", records.len() ) } else if records.iter().all(|e| e.provenance == Provenance::HwShunt) { if hardware_in_process { " — hardware counter deltas (HwShunt), this process's CPU energy over each interaction".to_string() } else { " — hardware counter deltas (HwShunt), machine-wide over each interaction".to_string() } } else if records.iter().all(|e| e.provenance == Provenance::Estimator) { " — estimated (wall-clock × TDP over each interaction's span), not measured".to_string() } else { " — partly estimated, not measured".to_string() }; println!("\nsealed chain: {verified} links verified, {} µJ total{label}", log.total_uj()); }