use crate::EnumerationAnomaly;
use crate::observation::Source;
use crate::records::{Record as RawRecord, RecordWalk};
use std::io;
use crate::records::SIZING_ATTEMPTS;
use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
use windows_sys::Win32::System::SystemInformation::{
CACHE_RELATIONSHIP, CacheData, CacheInstruction, CacheTrace, CacheUnified, GROUP_AFFINITY,
GROUP_RELATIONSHIP, GetLogicalProcessorInformationEx, LOGICAL_PROCESSOR_RELATIONSHIP,
NUMA_NODE_RELATIONSHIP, PROCESSOR_GROUP_INFO, PROCESSOR_RELATIONSHIP, RelationAll,
RelationCache, RelationGroup, RelationNumaNode, RelationNumaNodeEx, RelationProcessorCore,
RelationProcessorDie, RelationProcessorModule, RelationProcessorPackage,
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX,
};
#[derive(Clone, Copy, Debug)]
pub(crate) struct GroupAffinity {
pub(crate) group: u16,
pub(crate) mask: usize,
}
#[derive(Clone, Debug)]
pub(crate) struct ProcessorBody {
pub(crate) flags: u8,
pub(crate) efficiency_class: u8,
pub(crate) group_masks: Vec<GroupAffinity>,
}
#[derive(Clone, Debug)]
pub(crate) struct CacheBody {
pub(crate) level: u8,
pub(crate) associativity: u8,
pub(crate) line_size: u16,
pub(crate) cache_size: u32,
pub(crate) cache_type: i32,
pub(crate) group_masks: Vec<GroupAffinity>,
}
#[derive(Clone, Debug)]
pub(crate) struct NumaNodeBody {
pub(crate) node_number: u32,
pub(crate) group_masks: Vec<GroupAffinity>,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct GroupInfo {
pub(crate) maximum_processor_count: u8,
pub(crate) active_processor_count: u8,
pub(crate) active_processor_mask: usize,
}
#[derive(Clone, Debug)]
pub(crate) struct GroupBody {
pub(crate) group_info: Vec<GroupInfo>,
}
#[derive(Clone, Debug)]
pub(crate) enum Record {
ProcessorCore(ProcessorBody),
ProcessorPackage(ProcessorBody),
ProcessorDie(ProcessorBody),
ProcessorModule(ProcessorBody),
Cache(CacheBody),
NumaNode(NumaNodeBody),
Group(GroupBody),
#[allow(dead_code)]
Unknown(LOGICAL_PROCESSOR_RELATIONSHIP),
}
pub(crate) fn enumerate() -> io::Result<(Vec<Record>, Vec<EnumerationAnomaly>)> {
let mut grew = None;
for _ in 0..SIZING_ATTEMPTS {
let mut length: u32 = 0;
let probe = unsafe {
GetLogicalProcessorInformationEx(RelationAll, std::ptr::null_mut(), &raw mut length)
};
if probe != 0 {
return Ok((Vec::new(), Vec::new()));
}
let error = io::Error::last_os_error();
if error.raw_os_error() != Some(ERROR_INSUFFICIENT_BUFFER as i32) {
return Err(error);
}
let mut storage = vec![0_u64; length.div_ceil(8) as usize];
let buffer = storage.as_mut_ptr().cast::<u8>();
let mut actual_length = length;
let ok = unsafe {
GetLogicalProcessorInformationEx(RelationAll, buffer.cast(), &raw mut actual_length)
};
if ok != 0 {
return Ok(unsafe { decode(buffer.cast_const(), actual_length) });
}
let error = io::Error::last_os_error();
if error.raw_os_error() != Some(ERROR_INSUFFICIENT_BUFFER as i32) {
return Err(error);
}
grew = Some(error);
}
Err(grew.expect("SIZING_ATTEMPTS is a non-zero constant, so the loop ran at least once"))
}
const RELATIONSHIP_OFFSET: usize =
core::mem::offset_of!(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, Relationship);
const SIZE_OFFSET: usize = core::mem::offset_of!(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, Size);
const UNION_OFFSET: usize =
core::mem::offset_of!(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, Anonymous);
unsafe fn read_group_affinities(
record: RawRecord,
at: usize,
count: u16,
) -> (Vec<GroupAffinity>, bool) {
let (raw, complete) = unsafe { record.read_array::<GROUP_AFFINITY>(at, usize::from(count)) };
let affinities = raw
.into_iter()
.map(|entry| GroupAffinity {
group: entry.Group,
mask: entry.Mask,
})
.collect();
(affinities, complete)
}
unsafe fn read_legacy_group_affinities(
record: RawRecord,
at: usize,
group_count: u16,
) -> (Vec<GroupAffinity>, bool) {
let count = group_count.max(1);
unsafe { read_group_affinities(record, at, count) }
}
unsafe fn decode(buffer: *const u8, length: u32) -> (Vec<Record>, Vec<EnumerationAnomaly>) {
let mut walk = unsafe {
RecordWalk::new(
buffer,
length,
SIZE_OFFSET,
UNION_OFFSET,
Source::RelationshipWalk,
)
};
let mut records = Vec::new();
let mut anomalies = Vec::new();
for raw in &mut walk {
let Some(relationship) =
(unsafe { raw.read::<LOGICAL_PROCESSOR_RELATIONSHIP>(RELATIONSHIP_OFFSET) })
else {
continue;
};
let (record, truncated) = unsafe { decode_body(relationship, raw) };
if let Some(anomaly) = truncated {
anomalies.push(anomaly);
}
if let Some(record) = record {
records.push(record);
}
}
anomalies.extend(walk.anomaly());
(records, anomalies)
}
unsafe fn decode_body(
relationship: LOGICAL_PROCESSOR_RELATIONSHIP,
record: RawRecord,
) -> (Option<Record>, Option<EnumerationAnomaly>) {
#[allow(non_upper_case_globals)]
match relationship {
RelationProcessorCore
| RelationProcessorPackage
| RelationProcessorDie
| RelationProcessorModule => {
let (body, anomaly) = unsafe { read_processor_body(record) };
let wrap = match relationship {
RelationProcessorCore => Record::ProcessorCore,
RelationProcessorPackage => Record::ProcessorPackage,
RelationProcessorDie => Record::ProcessorDie,
_ => Record::ProcessorModule,
};
(body.map(wrap), anomaly)
}
RelationCache => {
let (body, anomaly) = unsafe { read_cache_body(record) };
(body.map(Record::Cache), anomaly)
}
RelationNumaNode | RelationNumaNodeEx => {
let (body, anomaly) = unsafe { read_numa_body(record) };
(body.map(Record::NumaNode), anomaly)
}
RelationGroup => {
let (body, anomaly) = unsafe { read_group_body(record) };
(body.map(Record::Group), anomaly)
}
other => (Some(Record::Unknown(other)), None),
}
}
macro_rules! body {
($ty:ty, $field:ident) => {
UNION_OFFSET + core::mem::offset_of!($ty, $field)
};
}
unsafe fn read_processor_body(
record: RawRecord,
) -> (Option<ProcessorBody>, Option<EnumerationAnomaly>) {
let Some((flags, efficiency_class, group_count)) = (unsafe {
(|| {
Some((
record.read::<u8>(body!(PROCESSOR_RELATIONSHIP, Flags))?,
record.read::<u8>(body!(PROCESSOR_RELATIONSHIP, EfficiencyClass))?,
record.read::<u16>(body!(PROCESSOR_RELATIONSHIP, GroupCount))?,
))
})()
}) else {
return (
None,
body_too_short(record, body!(PROCESSOR_RELATIONSHIP, GroupMask)),
);
};
let (group_masks, complete) = unsafe {
read_group_affinities(
record,
body!(PROCESSOR_RELATIONSHIP, GroupMask),
group_count,
)
};
let anomaly = truncation(record, complete, group_count, group_masks.len());
(
Some(ProcessorBody {
flags,
efficiency_class,
group_masks,
}),
anomaly,
)
}
fn body_too_short(record: RawRecord, minimum: usize) -> Option<EnumerationAnomaly> {
Some(EnumerationAnomaly::undersized(
Source::RelationshipWalk,
record.offset(),
record.size(),
minimum,
))
}
fn truncation(
record: RawRecord,
complete: bool,
declared: u16,
decoded: usize,
) -> Option<EnumerationAnomaly> {
(!complete).then(|| {
EnumerationAnomaly::truncated_array(
Source::RelationshipWalk,
record.offset(),
usize::from(declared),
decoded,
)
})
}
unsafe fn read_cache_body(record: RawRecord) -> (Option<CacheBody>, Option<EnumerationAnomaly>) {
let Some((level, associativity, line_size, cache_size, cache_type, group_count)) = (unsafe {
(|| {
Some((
record.read::<u8>(body!(CACHE_RELATIONSHIP, Level))?,
record.read::<u8>(body!(CACHE_RELATIONSHIP, Associativity))?,
record.read::<u16>(body!(CACHE_RELATIONSHIP, LineSize))?,
record.read::<u32>(body!(CACHE_RELATIONSHIP, CacheSize))?,
record.read::<i32>(body!(CACHE_RELATIONSHIP, Type))?,
record.read::<u16>(body!(CACHE_RELATIONSHIP, GroupCount))?,
))
})()
}) else {
return (
None,
body_too_short(record, body!(CACHE_RELATIONSHIP, Anonymous)),
);
};
let (group_masks, complete) = unsafe {
read_legacy_group_affinities(record, body!(CACHE_RELATIONSHIP, Anonymous), group_count)
};
let anomaly = truncation(record, complete, group_count.max(1), group_masks.len());
(
Some(CacheBody {
level,
associativity,
line_size,
cache_size,
cache_type,
group_masks,
}),
anomaly,
)
}
unsafe fn read_numa_body(record: RawRecord) -> (Option<NumaNodeBody>, Option<EnumerationAnomaly>) {
let Some((node_number, group_count)) = (unsafe {
(|| {
Some((
record.read::<u32>(body!(NUMA_NODE_RELATIONSHIP, NodeNumber))?,
record.read::<u16>(body!(NUMA_NODE_RELATIONSHIP, GroupCount))?,
))
})()
}) else {
return (
None,
body_too_short(record, body!(NUMA_NODE_RELATIONSHIP, Anonymous)),
);
};
let (group_masks, complete) = unsafe {
read_legacy_group_affinities(
record,
body!(NUMA_NODE_RELATIONSHIP, Anonymous),
group_count,
)
};
let anomaly = truncation(record, complete, group_count.max(1), group_masks.len());
(
Some(NumaNodeBody {
node_number,
group_masks,
}),
anomaly,
)
}
unsafe fn read_group_body(record: RawRecord) -> (Option<GroupBody>, Option<EnumerationAnomaly>) {
let Some(active_group_count) =
(unsafe { record.read::<u16>(body!(GROUP_RELATIONSHIP, ActiveGroupCount)) })
else {
return (
None,
body_too_short(record, body!(GROUP_RELATIONSHIP, GroupInfo)),
);
};
let (raw, complete) = unsafe {
record.read_array::<PROCESSOR_GROUP_INFO>(
body!(GROUP_RELATIONSHIP, GroupInfo),
usize::from(active_group_count),
)
};
let group_info: Vec<_> = raw
.into_iter()
.map(|entry| GroupInfo {
maximum_processor_count: entry.MaximumProcessorCount,
active_processor_count: entry.ActiveProcessorCount,
active_processor_mask: entry.ActiveProcessorMask,
})
.collect();
let anomaly = truncation(record, complete, active_group_count, group_info.len());
(Some(GroupBody { group_info }), anomaly)
}
#[allow(non_upper_case_globals)]
pub(crate) fn cache_kind_from_raw(value: i32) -> crate::CacheKind {
match value {
CacheUnified => crate::CacheKind::Unified,
CacheInstruction => crate::CacheKind::Instruction,
CacheData => crate::CacheKind::Data,
CacheTrace => crate::CacheKind::Trace,
other => crate::CacheKind::Other(other),
}
}
#[cfg(test)]
mod tests;