use crate::cpu::ArchInfo;
use crate::report::CpuReport;
use std::process::Command;
type CacheSize = Option<(u32, u32)>;
pub struct MacOSCpuInfo {
model: String,
vendor: String,
architecture: String,
physical_cores: u32,
logical_cores: u32,
base_mhz: Option<f32>,
l1_size: Option<(u32, u32)>,
l2_size: Option<(u32, u32)>,
l3_size: Option<(u32, u32)>,
flags: String,
arch_info: ArchInfo,
}
impl MacOSCpuInfo {
pub fn new() -> Result<Self, String> {
let model = Self::get_sysctl_string("machdep.cpu.brand_string")?;
let architecture = Self::get_architecture()?;
let vendor = if model.to_lowercase().contains("intel") {
"Intel".to_string()
} else if model.to_lowercase().contains("amd") {
"AMD".to_string()
} else if model.to_lowercase().contains("apple") {
"Apple".to_string()
} else {
"Unknown".to_string()
};
let physical_cores = Self::get_sysctl_u32("machdep.cpu.core_count")
.unwrap_or_else(|_| Self::get_sysctl_u32("machdep.cpu.cores_per_package").unwrap_or(0));
let logical_cores = Self::get_sysctl_u32("machdep.cpu.thread_count").unwrap_or_else(|_| {
Self::get_sysctl_u32("machdep.cpu.logical_per_package").unwrap_or(physical_cores)
});
let base_mhz = Self::get_sysctl_string("machdep.cpu.max_basic")
.ok()
.and_then(|s| s.parse::<f32>().ok());
let (l1_size, l2_size, l3_size) = Self::get_cache_info();
let flags = Self::get_cpu_flags();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
let arch_info = crate::cpu::x86::detect();
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
let arch_info = ArchInfo::default();
Ok(Self {
model,
vendor,
architecture,
physical_cores,
logical_cores,
base_mhz,
l1_size,
l2_size,
l3_size,
flags,
arch_info,
})
}
pub fn to_report(&self) -> CpuReport {
let info = &self.arch_info;
CpuReport {
model: self.model.clone(),
vendor: info.vendor.clone().unwrap_or_else(|| self.vendor.clone()),
uarch: info.uarch.clone(),
soc: None,
technology_nm: info.process_nm,
architecture: self.architecture.clone(),
physical_cores: self.physical_cores,
logical_cores: self.logical_cores,
max_ghz: None,
base_ghz: self.base_mhz.map(|mhz| mhz / 1000.0),
l1i_kb: None,
l1d_kb: self
.l1_size
.map(|(size, count)| (size, size * count.max(1))),
l2_kb: self
.l2_size
.map(|(size, count)| (size, size * count.max(1))),
l3_kb: self
.l3_size
.map(|(size, count)| (size, size * count.max(1))),
flags: if !info.features.is_empty() {
info.features.clone()
} else {
self.flags
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect()
},
}
}
fn get_cache_info() -> (CacheSize, CacheSize, CacheSize) {
let cache_sizes = Self::get_sysctl_string("hw.cachesize").unwrap_or_default();
let cache_config = Self::get_sysctl_string("hw.cacheconfig").unwrap_or_default();
let size_parts: Vec<&str> = cache_sizes.split_whitespace().collect();
let config_parts: Vec<&str> = cache_config.split_whitespace().collect();
let l1_size = if size_parts.len() >= 2 && config_parts.len() >= 2 {
let size_bytes = size_parts[1].parse::<u32>().unwrap_or(0);
let count = config_parts[1].parse::<u32>().unwrap_or(0);
if size_bytes > 0 && count > 0 {
Some((size_bytes / 1024, count)) } else {
None
}
} else {
None
};
let l2_size = if size_parts.len() >= 3 && config_parts.len() >= 3 {
let size_bytes = size_parts[2].parse::<u32>().unwrap_or(0);
let count = config_parts[2].parse::<u32>().unwrap_or(0);
if size_bytes > 0 && count > 0 {
Some((size_bytes / 1024, count)) } else {
None
}
} else {
None
};
let mut l3_size = if size_parts.len() >= 4 && config_parts.len() >= 4 {
let size_bytes = size_parts[3].parse::<u32>().unwrap_or(0);
let count = config_parts[3].parse::<u32>().unwrap_or(0);
if size_bytes > 0 && count > 0 {
Some((size_bytes / 1024, count)) } else {
None
}
} else {
None
};
if l3_size.is_none() {
let perf0_l2 = Self::get_sysctl_u32("hw.perflevel0.l2cachesize").ok();
let perf1_l2 = Self::get_sysctl_u32("hw.perflevel1.l2cachesize").ok();
if let (Some(p0_l2), Some(p1_l2)) = (perf0_l2, perf1_l2) {
if p0_l2 != p1_l2 {
let larger_cache = std::cmp::max(p0_l2, p1_l2);
l3_size = Some((larger_cache / 1024, 1)); }
}
}
(l1_size, l2_size, l3_size)
}
fn get_sysctl_string(key: &str) -> Result<String, String> {
let output = Command::new("sysctl")
.arg("-n")
.arg(key)
.output()
.map_err(|e| format!("Failed to execute sysctl: {}", e))?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
} else {
Err(format!("sysctl command failed for key: {}", key))
}
}
fn get_sysctl_u32(key: &str) -> Result<u32, String> {
let value_str = Self::get_sysctl_string(key)?;
value_str
.parse::<u32>()
.map_err(|e| format!("Failed to parse '{}' as u32: {}", value_str, e))
}
fn get_architecture() -> Result<String, String> {
let output = Command::new("uname")
.arg("-m")
.output()
.map_err(|e| format!("Failed to execute uname: {}", e))?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
} else {
Err("uname command failed".to_string())
}
}
fn get_cpu_flags() -> String {
let output = Command::new("sysctl").arg("hw.optional.arm.").output();
match output {
Ok(result) if result.status.success() => {
let output_str = String::from_utf8_lossy(&result.stdout);
let mut enabled_flags = Vec::new();
for line in output_str.lines() {
if let Some((key, value)) = line.split_once(": ") {
if value.trim() == "1" {
if let Some(flag_name) = key.strip_prefix("hw.optional.arm.") {
enabled_flags.push(flag_name.to_string());
}
}
}
}
enabled_flags.join(",")
}
_ => String::new(), }
}
}