//! 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"); }