diff --git a/proof/proof-rs/crates/proof-cascade/src/meter.rs b/proof/proof-rs/crates/proof-cascade/src/meter.rs index a3052fc20..8ea8349c7 100644 --- a/proof/proof-rs/crates/proof-cascade/src/meter.rs +++ b/proof/proof-rs/crates/proof-cascade/src/meter.rs @@ -3800,47 +3800,59 @@ mod macos_ioreport { /// cumulative values of the CPU and GPU energy channels, in channel /// order. fn read(&self) -> Option { - unsafe { - let smp = IOReportCreateSamples(self.sub, self.ch, std::ptr::null()); - if smp.is_null() { - return None; - } - let key = CFStringCreateWithCString( - std::ptr::null_mut(), - c"IOReportChannels".as_ptr() as *const _, - K_CFSTRING_ENCODING_UTF8, - ); - let mut read = OracleRead { group: Group { energy_model: 0, all: 0 }, cpu: Vec::new(), gpu: Vec::new() }; - if !key.is_null() { - let arr = CFDictionaryGetValue(smp, key as *const _) as CFArrayRef; - CFRelease(key as CFTypeRef); - if !arr.is_null() { - for i in 0..CFArrayGetCount(arr) { - let it = CFArrayGetValueAtIndex(arr, i); - if it.is_null() { - continue; - } - read.group.all += 1; - let group = cfstring_to_string(IOReportChannelGetGroup(it)).unwrap_or_default(); - let name = cfstring_to_string(IOReportChannelGetChannelName(it)).unwrap_or_default(); - let em = group.eq_ignore_ascii_case("Energy Model"); - read.group.energy_model += usize::from(em); - let rail = |group: String, name: String| OracleRail { - group, - name, - value: IOReportSimpleGetIntegerValue(it, 0) as i64, - }; - if name.ends_with("CPU Energy") { - read.cpu.push(rail(group, name)); - } else if em && name == "GPU Energy" { - read.gpu.push(rail(group, name)); - } + // SAFETY: the subscription is live until drop. + unsafe { oracle_read(self.sub, self.ch) } + } + } + + /// The oracle's read of any live subscription, with its own sampling + /// and parsing (not `energy_model_channels`): what `OracleSub::read` + /// reads of its own, and what the raw-reader pin counts of the + /// subscriptions it hands the reader. + /// + /// # Safety + /// `sub` and `ch` must be a live subscription and its channels. + unsafe fn oracle_read(sub: IOReportSubscriptionRef, ch: CFMutableDictionaryRef) -> Option { + unsafe { + let smp = IOReportCreateSamples(sub, ch, std::ptr::null()); + if smp.is_null() { + return None; + } + let key = CFStringCreateWithCString( + std::ptr::null_mut(), + c"IOReportChannels".as_ptr() as *const _, + K_CFSTRING_ENCODING_UTF8, + ); + let mut read = OracleRead { group: Group { energy_model: 0, all: 0 }, cpu: Vec::new(), gpu: Vec::new() }; + if !key.is_null() { + let arr = CFDictionaryGetValue(smp, key as *const _) as CFArrayRef; + CFRelease(key as CFTypeRef); + if !arr.is_null() { + for i in 0..CFArrayGetCount(arr) { + let it = CFArrayGetValueAtIndex(arr, i); + if it.is_null() { + continue; + } + read.group.all += 1; + let group = cfstring_to_string(IOReportChannelGetGroup(it)).unwrap_or_default(); + let name = cfstring_to_string(IOReportChannelGetChannelName(it)).unwrap_or_default(); + let em = group.eq_ignore_ascii_case("Energy Model"); + read.group.energy_model += usize::from(em); + let rail = |group: String, name: String| OracleRail { + group, + name, + value: IOReportSimpleGetIntegerValue(it, 0) as i64, + }; + if name.ends_with("CPU Energy") { + read.cpu.push(rail(group, name)); + } else if em && name == "GPU Energy" { + read.gpu.push(rail(group, name)); } } } - CFRelease(smp); - Some(read) } + CFRelease(smp); + Some(read) } } @@ -4445,10 +4457,17 @@ mod macos_ioreport { /// The raw reader reads the subscription it is handed, every time. /// Handed the probe's own Energy Model subscription and an all-channel /// one (`open()`) in turn, it lists the group's channels, then every - /// channel, then the group's again — as many as the oracle counts in - /// each. A reader that hands back an earlier read (stale for the - /// process) fails here whether or not any channel moves, which the GPU - /// freshness pin cannot promise on a host whose GPU is idle. + /// channel, then the group's again — as many as the oracle's own reader + /// counts in each. A reader that hands back an earlier read (stale for + /// the process) fails here whether or not any channel moves, which the + /// GPU freshness pin cannot promise on a host whose GPU is idle. + /// + /// The oracle counts the two subscriptions the reader is handed, not + /// one of its own: an all-channel subscription holds the channels + /// registered when it opened, and a laptop registers more as it runs + /// (12,862 when the oracle's opened, 13,014 a moment later for + /// `open()`'s, on an M5 Max), so a third subscription is no measure of + /// these two. #[test] fn the_raw_reader_reads_the_subscription_it_is_given_every_time() { let Some(oracle) = OracleSub::open() else { @@ -4456,10 +4475,17 @@ mod macos_ioreport { assert_ne!(ioreport_status(), IoReportStatus::Live, "Live with no IOReport"); return; }; - let group = oracle.read().expect("the oracle's subscription yields a sample").group; + let own = oracle.read().expect("the oracle's subscription yields a sample").group; let probe = EnergyModelSub::open().expect("the oracle reads IOReport; the probe's group subscription does not open"); // SAFETY: open returns owned, non-null refs, released below. let (sub, ch) = unsafe { open() }.expect("the probe's group opens, a subscription to every channel does not"); + // SAFETY: both subscriptions are live until released below. + let (want_g, want_a) = unsafe { + ( + oracle_read(probe.sub, probe.ch).expect("the oracle reads the group subscription").group, + oracle_read(sub, ch).expect("the oracle reads the all-channel subscription").group, + ) + }; let mut listed = Vec::new(); for _ in 0..3 { // SAFETY: both subscriptions are live until released below. @@ -4473,17 +4499,34 @@ mod macos_ioreport { CFRelease(ch as CFTypeRef); } eprintln!( - "ioreport raw reader: (group, every channel) listed {listed:?}; the oracle counts ({}, {})", - group.energy_model, group.all + "ioreport raw reader: (group, every channel) listed {listed:?}; the oracle counts ({}, {}) in the \ + subscriptions handed over, ({}, {}) in its own", + want_g.all, want_a.all, own.energy_model, own.all + ); + assert_eq!( + want_g.energy_model, want_g.all, + "the probe's subscription holds {} channels, {} of them the Energy Model group's", + want_g.all, want_g.energy_model + ); + assert_eq!( + want_a.energy_model, want_g.all, + "the all-channel subscription holds {} Energy Model channels, the probe's {}", + want_a.energy_model, want_g.all + ); + assert!( + want_g.all < want_a.all, + "the group's {} channels are not fewer than every channel's {}", + want_g.all, + want_a.all ); for (i, (g, a)) in listed.iter().enumerate() { assert_eq!( (*g, *a), - (group.energy_model, group.all), + (want_g.all, want_a.all), "read {i}: the raw reader listed {g} channels for the group and {a} for every channel; the oracle \ - counts {} and {}: it did not read the subscription it was handed", - group.energy_model, - group.all + counts {} and {} in those subscriptions: it did not read the subscription it was handed", + want_g.all, + want_a.all ); } }