use std::num::NonZeroU16;
use raw_cpuid::{CpuId as RawCpuId, TopologyType};
#[cfg(target_os = "windows")]
use windows::Win32::{Foundation::*, System::SystemInformation::*};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Vendor {
Intel,
Amd,
Unknown,
}
pub struct CpuInfo {
vendor: Vendor,
family: u32,
model: u32,
stepping: u32,
core_count: Option<NonZeroU16>,
}
impl CpuInfo {
pub fn new() -> Option<Self> {
let cpuid = RawCpuId::new();
let vendor = match cpuid.get_vendor_info() {
Some(info) => match info.as_str() {
"GenuineIntel" => Vendor::Intel,
"AuthenticAMD" => Vendor::Amd,
_ => Vendor::Unknown,
},
None => Vendor::Unknown,
};
let (family, model, stepping) = match cpuid.get_feature_info() {
Some(info) => (
info.family_id() as u32,
info.model_id() as u32,
info.stepping_id() as u32,
),
None => (0, 0, 0),
};
let core_count = cpuid
.get_extended_topology_info()
.and_then(|mut info| info.find(|level| level.level_type() == TopologyType::Core))
.and_then(|level| NonZeroU16::new(level.processors()));
Some(CpuInfo {
vendor,
family,
model,
stepping,
core_count,
})
}
pub fn get_vendor(&self) -> Vendor {
self.vendor
}
pub fn get_family(&self) -> u32 {
self.family
}
pub fn get_model(&self) -> u32 {
self.model
}
pub fn get_stepping(&self) -> u32 {
self.stepping
}
pub fn get_core_count(&self) -> Option<NonZeroU16> {
self.core_count
}
pub fn has_temperature_sensor(&self) -> bool {
let cpuid = RawCpuId::new();
if cpuid.get_feature_info().is_some() {
if let Some(thermal_info) = cpuid.get_thermal_power_info() {
return thermal_info.has_dts(); }
}
false
}
pub fn get_name(&self) -> String {
let cpuid = RawCpuId::new();
if let Some(info) = cpuid.get_processor_brand_string() {
info.as_str().to_string()
} else {
"Unknown CPU".to_string()
}
}
pub fn get_core_cpu_mapping() -> anyhow::Result<Vec<Vec<usize>>> {
#[cfg(target_os = "windows")]
{
Self::get_core_cpu_mapping_windows()
}
#[cfg(target_os = "linux")]
{
let cpu_info = CpuInfo::new().ok_or_else(|| anyhow::anyhow!("Failed to get CPU info"))?;
cpu_info.get_core_cpu_mapping_linux()
}
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
{
anyhow::bail!("not implementated");
}
}
#[cfg(target_os = "windows")]
fn get_core_cpu_mapping_windows() -> anyhow::Result<Vec<Vec<usize>>> {
use std::mem;
let mut mappings = Vec::new();
let mut buffer_length: u32 = 0;
let result =
unsafe { GetLogicalProcessorInformationEx(RelationAll, None, &mut buffer_length as _) };
match result {
Ok(()) => return Ok(mappings),
Err(e) => {
if unsafe { GetLastError() } != ERROR_INSUFFICIENT_BUFFER {
anyhow::bail!(
"GetLogicalProcessorInformationEx failed (0x{:X}): {}",
e.code().0,
e.message()
)
}
}
}
let mut buffer: Vec<u8> = vec![0; buffer_length as usize];
let result = unsafe {
GetLogicalProcessorInformationEx(
RelationAll,
Some(buffer.as_mut_ptr() as _),
&mut buffer_length as _,
)
};
if let Err(e) = result {
anyhow::bail!(
"GetLogicalProcessorInformationEx failed (0x{:X}): {}",
e.code().0,
e.message()
)
}
let mut offset: usize = 0;
while offset < buffer.len() {
use anyhow::Context;
let size = mem::size_of::<SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>();
let cur_buffer = buffer
.get(offset..offset + size)
.context("slice info error")?;
let info: SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX =
unsafe { std::ptr::read_unaligned(cur_buffer.as_ptr() as _) };
if info.Relationship == RelationProcessorCore {
let mut logical_processors = Vec::new();
let processor_info = unsafe { &info.Anonymous.Processor };
let group_mask_offset = offset
+ mem::offset_of!(
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX,
Anonymous.Processor.GroupMask
);
let group_mask_size = processor_info.GroupCount as usize
* mem::size_of_val(&processor_info.GroupMask[0]);
let mask_buffer = buffer
.get(group_mask_offset..group_mask_offset + group_mask_size)
.context("slice group mask error")?;
for i in 0..processor_info.GroupCount as usize {
let group_mask = unsafe {
mask_buffer
.as_ptr()
.cast::<GROUP_AFFINITY>()
.add(i)
.read_unaligned()
};
let mut mask = group_mask.Mask;
while mask != 0 {
let bit = mask.trailing_zeros();
logical_processors.push(i * 64 + bit as usize);
mask &= mask - 1;
}
}
mappings.push(logical_processors);
}
offset += info.Size as usize;
}
Ok(mappings)
}
#[cfg(target_os = "linux")]
fn get_core_cpu_mapping_linux(&self) -> anyhow::Result<Vec<Vec<usize>>> {
use std::collections::BTreeMap;
use std::fs;
use anyhow::Context as _;
const CORE_ID_PATTERN: &str = "/sys/devices/system/cpu/cpu[0-9]*/topology/core_id";
let mut groups: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
for entry in glob::glob(CORE_ID_PATTERN).context("Failed to read glob pattern")? {
if let Ok(path) = entry {
let cpu_id = path
.ancestors()
.nth(2)
.and_then(|p| p.file_name())
.and_then(|s| s.to_str())
.and_then(|s| s.trim_start_matches("cpu").parse::<usize>().ok());
let core_id = fs::read_to_string(&path)
.ok()
.and_then(|s| s.trim().parse::<usize>().ok());
if let (Some(cpu), Some(core)) = (cpu_id, core_id) {
groups.entry(core).or_default().push(cpu);
}
}
}
Ok(groups.into_values().collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_core_cpu_mapping() {
let res = CpuInfo::get_core_cpu_mapping().unwrap();
println!("{:?}", res);
}
}