From 546b13287a48434f360cb0d66683dc0dc04c3672 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 29 Oct 2026 20:27:40 -0400 Subject: [PATCH] proof-cascade: the raw-reader pin counts the subscriptions it hands over The pin compared the raw reader's listing of two subscriptions with the oracle's count of a third, its own, opened first. An all-channel subscription holds the channels registered when it opened, and a laptop registers more as it runs: on the M5 Max runner the oracle's held 12,862 and open()'s 13,014 a moment later, so the pin failed with the reader listing the same 13,014 on every read. The reader was right; the pin measured the wrong subscription. The oracle's reader is now a function of any live subscription (oracle_read), and the pin counts the probe's group subscription and the all-channel one with it: the reader must list as many as the oracle reads in the very subscription it was handed, every time. It also checks that the group subscription holds only the group, that the all-channel one holds the whole group, and that the group is the smaller. Co-Authored-By: Claude Opus 5.5 --- .../crates/proof-cascade/src/meter.rs | 139 ++++++++++++------ 1 file changed, 91 insertions(+), 48 deletions(-) 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 ); } } -- 2.54.0 (Apple Git-157)