use windows_sys::Win32::System::Power::{CallNtPowerInformation, ProcessorInformation};
use corescout_core::error::{Error, Result};
use crate::topology::Topology;
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct ProcessorPowerInformation {
pub number: u32,
pub max_mhz: u32,
pub current_mhz: u32,
pub mhz_limit: u32,
pub max_idle_state: u32,
pub current_idle_state: u32,
}
pub fn read(cpu_count: usize) -> Result<Vec<ProcessorPowerInformation>> {
if cpu_count == 0 {
return Ok(Vec::new());
}
let mut buffer = vec![ProcessorPowerInformation::default(); cpu_count];
let bytes = std::mem::size_of_val(buffer.as_slice()) as u32;
let status = unsafe {
CallNtPowerInformation(
ProcessorInformation,
std::ptr::null(),
0,
buffer.as_mut_ptr().cast(),
bytes,
)
};
if status != 0 {
return Err(Error::syscall(
"CallNtPowerInformation(ProcessorInformation)",
status,
));
}
Ok(buffer)
}
pub fn fill(topology: &mut Topology) {
let Ok(records) = read(topology.logical_cpus.len()) else {
return;
};
for (cpu, record) in topology.logical_cpus.iter_mut().zip(records.iter()) {
if record.max_mhz > 0 {
cpu.frequency.max_khz = Some(record.max_mhz as u64 * 1000);
}
if record.current_mhz > 0 {
cpu.frequency.current_khz = Some(record.current_mhz as u64 * 1000);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn this_machine_reports_its_clocks() {
let topology = super::super::topology::discover().expect("discoverable");
let records = read(topology.logical_cpus.len()).expect("power information");
assert_eq!(records.len(), topology.logical_cpus.len());
assert!(
records.iter().any(|r| r.max_mhz > 0),
"no CPU reported a maximum frequency"
);
for record in &records {
if record.max_mhz > 0 {
assert!(
(100..100_000).contains(&record.max_mhz),
"implausible max {} MHz",
record.max_mhz
);
}
}
}
#[test]
fn filling_a_topology_populates_frequencies() {
let mut topology = super::super::topology::discover().expect("discoverable");
for cpu in &mut topology.logical_cpus {
cpu.frequency = Default::default();
}
fill(&mut topology);
assert!(
topology
.logical_cpus
.iter()
.any(|cpu| cpu.frequency.max_khz.is_some()),
"fill did not populate any maximum frequency"
);
}
#[test]
fn asking_for_no_cpus_is_not_an_error() {
assert!(read(0).expect("empty is fine").is_empty());
}
}