use crate::cpu::uarch::Microarch;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, thiserror::Error)]
pub enum CpuError {
#[error("Failed to detect CPU vendor: {0}")]
VendorDetection(String),
#[error("Failed to read CPU information: {0}")]
InfoRead(String),
#[error("Unsupported CPU architecture")]
UnsupportedArch,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Vendor {
Intel,
AMD,
ARM,
Apple,
Unknown,
}
impl fmt::Display for Vendor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Vendor::Intel => write!(f, "Intel"),
Vendor::AMD => write!(f, "AMD"),
Vendor::ARM => write!(f, "ARM"),
Vendor::Apple => write!(f, "Apple"),
Vendor::Unknown => write!(f, "Unknown"),
}
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Frequency {
pub base: Option<f64>,
pub max: Option<f64>,
pub current: Option<f64>,
}
impl fmt::Display for Frequency {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let base = self
.base
.map_or_else(|| "Unknown".to_string(), |v| format!("{v:.2} MHz"));
let current = self
.current
.map_or_else(|| "Unknown".to_string(), |v| format!("{v:.2} MHz"));
let max = self
.max
.map_or_else(|| "Unknown".to_string(), |v| format!("{v:.2} MHz"));
write!(f, "Base: {base}, Current: {current}, Max: {max}")
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct Version {
pub family: u8,
pub model: u8,
pub stepping: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CpuInfo {
pub vendor: Vendor,
pub brand_string: String,
pub version: Version,
pub physical_cores: u32,
pub logical_cores: u32,
pub frequency: Frequency,
pub cache_sizes: [Option<u32>; 4],
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub features: crate::cpu::X86Features,
#[cfg(target_arch = "aarch64")]
pub features: crate::cpu::ArmFeatures,
#[serde(skip_serializing_if = "Option::is_none")]
pub microarch: Option<Microarch>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hypervisor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub peak_flops: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub p_cores: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub e_cores: Option<u32>,
}
impl CpuInfo {
pub fn new() -> Result<Self, CpuError> {
#[cfg(target_arch = "x86_64")]
{
crate::arch::x86_64::detect_cpu()
}
#[cfg(target_arch = "aarch64")]
{
crate::arch::aarch64::detect_cpu()
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
Err(CpuError::UnsupportedArch)
}
}
#[must_use]
pub fn get() -> &'static Self {
static CPU_INFO: std::sync::LazyLock<CpuInfo> =
std::sync::LazyLock::new(|| CpuInfo::new().expect("Failed to detect CPU information"));
&CPU_INFO
}
}
impl Default for CpuInfo {
fn default() -> Self {
Self {
vendor: Vendor::Unknown,
brand_string: String::new(),
version: Version::default(),
physical_cores: 0,
logical_cores: 0,
frequency: Frequency::default(),
cache_sizes: [None; 4],
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
features: crate::cpu::X86Features::empty(),
#[cfg(target_arch = "aarch64")]
features: crate::cpu::ArmFeatures::empty(),
microarch: None,
hypervisor: None,
peak_flops: None,
p_cores: None,
e_cores: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vendor_display() {
assert_eq!(Vendor::Intel.to_string(), "Intel");
assert_eq!(Vendor::AMD.to_string(), "AMD");
assert_eq!(Vendor::ARM.to_string(), "ARM");
assert_eq!(Vendor::Unknown.to_string(), "Unknown");
}
#[test]
fn test_cpu_info_default() {
let info = CpuInfo::default();
assert_eq!(info.vendor, Vendor::Unknown);
assert_eq!(info.physical_cores, 0);
assert_eq!(info.logical_cores, 0);
assert_eq!(info.cache_sizes, [None; 4]);
}
}