open-standards/jcp/jcp-rs/crates/jcp-energy/tests/rapl_zone_public.rs
Transaction Science a4415ceab6 jcp-energy: the RAPL zone predicate and the tree reader are public
A crate that decides whether a powercap zone qualifies for the RAPL rung kept a
copy of is_rapl_package_name, and the copy fell behind when the predicate came to
accept package-<n>-die-<m>. The predicate is now public, and so is Rapl::open_in,
which reads the powercap tree at the root it is given and nothing else. Each doc
says what it answers. A test calls both through the crate's public path, on a
fixture tree with package-0-die-0 and package-0-die-1 zones beside a core subzone
and a psys zone: the reader sums the two die zones and only them. Nothing else
changes.

Changelog: none
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 08:16:58 -05:00

77 lines
3.2 KiB
Rust

//! The powercap zone predicate and the tree reader through the crate's public
//! path, as a crate that decides whether a zone qualifies for the RAPL rung
//! calls them: `is_rapl_package_name` on each zone's `name`, and
//! `Rapl::open_in` on a whole fixture tree, which holds a multi-die package's
//! `package-0-die-1` zone beside its `package-0-die-0`, a `core` subzone and
//! a `psys` zone. Files only, so it runs on every platform.
use std::path::{Path, PathBuf};
use jcp_energy::{is_rapl_package_name, Meter, Rapl};
use jcp_schema::Provenance;
/// A powercap tree under the temporary directory, removed when dropped.
struct Tree(PathBuf);
impl Tree {
/// Zones as (directory, `name`, `energy_uj`), each wrapping at the same
/// range.
fn new(zones: &[(&str, &str, &str)]) -> Self {
static N: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
let n = N.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let root = std::env::temp_dir().join(format!("jcp-energy-rapl-zone-public-{}-{n}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
for (dir, name, energy) in zones {
let zone = root.join(dir);
std::fs::create_dir_all(&zone).expect("create a fixture zone");
std::fs::write(zone.join("name"), format!("{name}\n")).unwrap();
std::fs::write(zone.join("max_energy_range_uj"), "262143328850\n").unwrap();
std::fs::write(zone.join("energy_uj"), format!("{energy}\n")).unwrap();
}
Tree(root)
}
fn write(&self, dir: &str, energy: &str) {
std::fs::write(self.0.join(dir).join("energy_uj"), format!("{energy}\n")).unwrap();
}
fn root(&self) -> &Path {
&self.0
}
}
impl Drop for Tree {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
#[test]
fn a_die_zone_qualifies_through_the_public_predicate_and_reader() {
for name in ["package-0", "package-0-die-0", "package-0-die-1\n", "package-12-die-3"] {
assert!(is_rapl_package_name(name), "{name:?} is a package zone");
}
for name in ["psys", "core", "dram", "uncore", "package-0-die-", "package-0-core-0"] {
assert!(!is_rapl_package_name(name), "{name:?} is not a package zone");
}
let tree = Tree::new(&[
("intel-rapl:0", "package-0-die-0", "1000"),
("intel-rapl:1", "package-0-die-1", "2000"),
("intel-rapl:0:0", "core", "700"),
("intel-rapl:2", "psys", "90000"),
]);
let rapl = Rapl::open_in(tree.root()).expect("a tree with two die zones opens");
// The reader sums the two die zones and nothing else: their rise, not the
// core subzone's or the platform zone's.
let (_, e) = Meter::Rapl(rapl).measure(|| {
tree.write("intel-rapl:0", "4000");
tree.write("intel-rapl:1", "7000");
tree.write("intel-rapl:0:0", "2700");
tree.write("intel-rapl:2", "190000");
});
assert_eq!((e.provenance, e.measured_uj), (Provenance::HwShunt, Some(8_000)), "not both dies' rise: {e:?}");
let psys_only = Tree::new(&[("intel-rapl:0", "psys", "90000")]);
assert!(Rapl::open_in(psys_only.root()).is_none(), "a tree with no package zone opened");
}