3 changed files with 53 additions and 10 deletions
|
|
@ -18,7 +18,9 @@ hw-meter = ["dep:jouleclaw-energy"]
|
|||
# Convenience: enable the platform backend most likely to be present.
|
||||
hw-meter-rapl = ["hw-meter", "jouleclaw-energy/rapl"]
|
||||
hw-meter-nvml = ["hw-meter", "jouleclaw-energy/nvml"]
|
||||
hw-meter-apple = ["hw-meter", "jouleclaw-energy/ioreport"]
|
||||
# Apple: IOReport where its CPU rail is live, else the Mac's whole-machine power
|
||||
# from the SMC (ModelBased; spans shorter than its 2 s floor are estimates).
|
||||
hw-meter-apple = ["hw-meter", "jouleclaw-energy/ioreport", "jouleclaw-energy/smc"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@
|
|||
//! * [`ManualMeter`] — caller-driven, for tests and replaying recorded spans.
|
||||
//!
|
||||
//! With the `hw-meter` feature, [`HwMeter`] bridges the family's meter (RAPL /
|
||||
//! NVML / Apple Silicon IOReport, else the family's estimator), so a span carries
|
||||
//! the provenance of whatever actually metered it: `HwShunt` from a hardware
|
||||
//! counter that is live for the domain it reads (Apple IOReport included, where
|
||||
//! its CPU energy rail advances under load), `ModelBased` from a vendor energy
|
||||
//! model, `Estimator` where the host exposes no live counter — as on Apple Silicon
|
||||
//! whose IOReport CPU rail is frozen (macOS 27.0.1).
|
||||
//! NVML / Apple Silicon IOReport / a Mac's SMC system power, else the family's
|
||||
//! estimator), so a span carries the provenance of whatever actually metered it:
|
||||
//! `HwShunt` from a hardware counter that is live for the domain it reads (Apple
|
||||
//! IOReport included, where its CPU energy rail advances under load),
|
||||
//! `ModelBased` from a vendor energy model — with `hw-meter-apple`, the SMC's
|
||||
//! whole-machine power integral on a Mac whose IOReport CPU rail is frozen (macOS
|
||||
//! 27 on an M5 Max and an M3 Ultra) — and `Estimator` where the host exposes
|
||||
//! neither.
|
||||
//!
|
||||
//! Also with `hw-meter`, [`ProcessMeter`] meters an engine that runs in this
|
||||
//! process (a GGUF decode, in process) by this process's own CPU energy and its
|
||||
|
|
@ -276,8 +278,11 @@ mod hw {
|
|||
/// estimator) into the Mesh [`Meter`] interface. The active backend's
|
||||
/// provenance is mapped through unchanged, so honesty is preserved across the
|
||||
/// bridge. On Apple Silicon whose IOReport CPU energy rail does not advance
|
||||
/// (macOS 27.0.1 on an M5 Max and an M3 Ultra), the active backend is the load
|
||||
/// estimator and spans say `Estimator`.
|
||||
/// (macOS 27 on an M5 Max and an M3 Ultra), the active backend is, with
|
||||
/// `hw-meter-apple`, the SMC's whole-machine power integral: a span at least
|
||||
/// its 2 s minimum window long says `ModelBased`, a shorter one `Estimator`.
|
||||
/// Without the SMC (or a Mac that does not publish it) the backend is the load
|
||||
/// estimator, and spans say `Estimator`.
|
||||
///
|
||||
/// If the backend fails a read, the bridge takes this crate's no-hardware path
|
||||
/// from then on: an [`EstimatorMeter`], continuing from the last good reading,
|
||||
|
|
@ -684,6 +689,39 @@ mod hw {
|
|||
}
|
||||
}
|
||||
|
||||
/// On this Mac, the bridge over the family's meter: where IOReport's CPU
|
||||
/// rail is frozen and the SMC publishes `PSTR`, the SMC's whole-machine
|
||||
/// integral, whose 2 s floor makes a 20 ms span an estimate and a span
|
||||
/// past it a modeled figure of the whole machine. Where IOReport is live,
|
||||
/// a hardware reading.
|
||||
#[cfg(all(feature = "hw-meter-apple", target_os = "macos"))]
|
||||
#[test]
|
||||
fn on_a_mac_the_smc_models_spans_past_its_floor_and_shorter_ones_are_estimates() {
|
||||
let m = HwMeter::detect();
|
||||
let short = span(&m);
|
||||
println!("mesh-meter on this Mac: meter={} floor={:?} 20 ms span {short:?}", m.name(), m.min_window());
|
||||
match m.name() {
|
||||
"apple-ioreport" => assert_eq!(short.provenance, Provenance::HwShunt),
|
||||
"apple-smc" => {
|
||||
assert_eq!(m.min_window(), Duration::from_secs(2));
|
||||
assert_eq!((short.provenance, short.measured_uj), (Provenance::Estimator, None), "{short:?}");
|
||||
assert!(short.estimated_uj > 0);
|
||||
let s = Session::start(&m);
|
||||
std::thread::sleep(Duration::from_millis(2_200));
|
||||
let long = s.stop();
|
||||
println!("mesh-meter on this Mac: 2.2 s span {long:?}");
|
||||
assert_eq!((long.provenance, long.measured_uj), (Provenance::ModelBased, None), "{long:?}");
|
||||
// A running Mac draws at least ~1 W: 2.2 s is over 2 J.
|
||||
assert!(long.estimated_uj > 2_000_000, "{long:?}");
|
||||
assert!(long.check_provenance().is_ok());
|
||||
}
|
||||
other => {
|
||||
assert_eq!(other, EstimatorMeter::default().name(), "an unexpected Apple backend");
|
||||
assert_eq!(short.provenance, Provenance::Estimator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A backend that steps `STEP` µJ per read from `BASE`, over `domain`, at
|
||||
/// `grade` — a live NVML counter is `(Gpu, HwShunt)`.
|
||||
struct Stepping {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@
|
|||
//! - `--features server,hw-meter` (or `hw-meter-rapl`, `-nvml`, `-apple`):
|
||||
//! `mesh_meter::HwMeter::detect`, the machine's counter, a reading only
|
||||
//! where a live one covers the whole request (a RAPL package on Linux) and
|
||||
//! `mesh_meter::EstimatorMeter` otherwise. A machine counter reads
|
||||
//! `mesh_meter::EstimatorMeter` otherwise. With `-apple` on a Mac whose
|
||||
//! IOReport CPU rail is frozen it is the SMC's whole-machine power: a modeled
|
||||
//! figure (`ModelBased`) for a serve at least its 2 s floor long, an estimate
|
||||
//! for a shorter one, as this engine's are. A machine counter reads
|
||||
//! everything the host does over the span, not this request alone.
|
||||
//! - `--features server` alone: `mesh_meter::EstimatorMeter`, wall time × an
|
||||
//! assumed draw, `Estimator`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue