use crate::cpu::info::Frequency;
use crate::cpu::uarch::detect_uarch;
use crate::cpu::{CpuError, CpuInfo, CpuidWrapper, Vendor, Version};
pub fn detect_cpu() -> Result<CpuInfo, CpuError> {
let cpuid = CpuidWrapper::new();
let basic_info = cpuid
.get_basic_info()
.map_err(|e| CpuError::InfoRead(format!("Failed to get basic CPU info: {e}")))?;
let cpu_vendor = match basic_info.vendor_string.as_str() {
"GenuineIntel" => Vendor::Intel,
"AuthenticAMD" | "HygonGenuine" => Vendor::AMD,
_ => Vendor::Unknown,
};
#[allow(clippy::cast_possible_truncation)]
let version = Version {
family: if basic_info.family == 0xF {
((u16::from(basic_info.extended_family) << 4) + u16::from(basic_info.family)) as u8
} else {
basic_info.family
},
model: if basic_info.family == 0xF || basic_info.family == 0x6 {
((u16::from(basic_info.extended_model) << 4) + u16::from(basic_info.model)) as u8
} else {
basic_info.model
},
stepping: basic_info.stepping,
};
let features =
crate::cpu::detect_features().map_err(|e| CpuError::InfoRead(format!("Failed to detect CPU features: {e}")))?;
let logical_cores = u32::try_from(num_cpus::get()).unwrap_or(0);
let physical_cores = u32::try_from(num_cpus::get_physical()).unwrap_or(0);
let frequency = detect_frequency_for_info();
let cache_sizes = detect_cache_sizes(&cpuid);
let microarch = detect_uarch(&cpu_vendor, version.family, version.model);
let hypervisor = cpuid.detect_hypervisor();
let peak_flops = crate::cpu::perf::calculate_peak_flops(physical_cores, frequency.max, frequency.base, features);
Ok(CpuInfo {
vendor: cpu_vendor,
brand_string: basic_info.brand_string,
version,
physical_cores,
logical_cores,
frequency,
cache_sizes,
features,
microarch,
hypervisor,
peak_flops,
p_cores: None,
e_cores: None,
})
}
fn detect_frequency_for_info() -> Frequency {
#[cfg(feature = "frequency")]
{
match crate::cpu::frequency::detect_frequency() {
Ok(f) => Frequency {
base: f.base,
current: f.current,
max: f.max,
},
Err(_) => Frequency::default(),
}
}
#[cfg(not(feature = "frequency"))]
{
Frequency::default()
}
}
fn detect_cache_sizes(cpuid: &CpuidWrapper) -> [Option<u32>; 4] {
let mut cache_sizes = [None; 4];
if let Ok(topology) = cpuid.get_cache_topology() {
for cache in topology.caches.iter().flatten() {
let index = match (cache.level, cache.cache_type) {
(1, crate::cpu::CacheType::Instruction) => Some(0),
(1, crate::cpu::CacheType::Data) => Some(1),
(2, _) => Some(2),
(3, _) => Some(3),
_ => None,
};
if let Some(idx) = index {
cache_sizes[idx] = Some(cache.size_kb);
}
}
}
cache_sizes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg_attr(not(target_arch = "x86_64"), ignore)]
fn test_detect_cpu() {
let info = detect_cpu().unwrap();
assert!(!info.brand_string.is_empty());
assert!(info.logical_cores > 0);
assert!(info.physical_cores > 0);
println!("Detected x86_64 CPU: {info:?}");
println!("Cache sizes: {:?}", info.cache_sizes);
println!("Microarch: {:?}", info.microarch);
println!("Hypervisor: {:?}", info.hypervisor);
println!("Peak GFLOP/s: {:?}", info.peak_flops);
}
#[test]
#[cfg_attr(not(target_arch = "x86_64"), ignore)]
fn test_frequency_populated() {
let info = detect_cpu().unwrap();
#[cfg(any(target_os = "linux", target_os = "macos"))]
assert!(
info.frequency.base.is_some() || info.frequency.max.is_some() || info.frequency.current.is_some(),
"No frequency data detected — frequency feature may be disabled or unavailable"
);
}
}