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]>
77 lines
3.2 KiB
Rust
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");
|
|
}
|