Skip to main content

cpufetch_rs/cpu/
info.rs

1//! CPU information structures and traits.
2//!
3//! This module provides the core data structures for representing CPU information
4//! across different architectures. It aims to provide a unified interface for
5//! accessing CPU details regardless of the underlying hardware.
6
7use crate::cpu::uarch::Microarch;
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11/// Error types for CPU information gathering
12#[derive(Debug, thiserror::Error)]
13pub enum CpuError {
14    #[error("Failed to detect CPU vendor: {0}")]
15    VendorDetection(String),
16    #[error("Failed to read CPU information: {0}")]
17    InfoRead(String),
18    #[error("Unsupported CPU architecture")]
19    UnsupportedArch,
20}
21
22/// CPU vendor identification
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub enum Vendor {
25    Intel,
26    AMD,
27    ARM,
28    Apple,
29    Unknown,
30}
31
32impl fmt::Display for Vendor {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            Vendor::Intel => write!(f, "Intel"),
36            Vendor::AMD => write!(f, "AMD"),
37            Vendor::ARM => write!(f, "ARM"),
38            Vendor::Apple => write!(f, "Apple"),
39            Vendor::Unknown => write!(f, "Unknown"),
40        }
41    }
42}
43
44/// CPU frequency information in MHz
45#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
46pub struct Frequency {
47    /// Base/nominal frequency
48    pub base: Option<f64>,
49    /// Maximum turbo frequency
50    pub max: Option<f64>,
51    /// Current operating frequency
52    pub current: Option<f64>,
53}
54
55impl fmt::Display for Frequency {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        let base = self
58            .base
59            .map_or_else(|| "Unknown".to_string(), |v| format!("{v:.2} MHz"));
60        let current = self
61            .current
62            .map_or_else(|| "Unknown".to_string(), |v| format!("{v:.2} MHz"));
63        let max = self
64            .max
65            .map_or_else(|| "Unknown".to_string(), |v| format!("{v:.2} MHz"));
66
67        write!(f, "Base: {base}, Current: {current}, Max: {max}")
68    }
69}
70
71/// Represents version information for a CPU
72#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
73pub struct Version {
74    /// CPU family identifier
75    pub family: u8,
76    /// CPU model identifier
77    pub model: u8,
78    /// CPU stepping identifier
79    pub stepping: u8,
80}
81
82/// Core CPU information structure
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct CpuInfo {
85    /// CPU vendor identification
86    pub vendor: Vendor,
87    /// Marketing name of the CPU
88    pub brand_string: String,
89    /// Version information (family/model/stepping)
90    pub version: Version,
91    /// Number of physical CPU cores
92    pub physical_cores: u32,
93    /// Number of logical CPU threads
94    pub logical_cores: u32,
95    /// Frequency information
96    pub frequency: Frequency,
97    /// Cache sizes in KB (L1i, L1d, L2, L3)
98    pub cache_sizes: [Option<u32>; 4],
99    /// CPU features
100    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
101    pub features: crate::cpu::X86Features,
102    #[cfg(target_arch = "aarch64")]
103    pub features: crate::cpu::ArmFeatures,
104    /// Detected CPU microarchitecture (if recognised)
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub microarch: Option<Microarch>,
107    /// Hypervisor name if running inside a virtual machine
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub hypervisor: Option<String>,
110    /// Theoretical peak double-precision performance in GFLOP/s
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub peak_flops: Option<f64>,
113    /// Performance core count (for hybrid architectures like Apple Silicon, Alder Lake)
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub p_cores: Option<u32>,
116    /// Efficiency core count (for hybrid architectures)
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub e_cores: Option<u32>,
119}
120
121impl CpuInfo {
122    /// Creates a new `CpuInfo` instance by detecting the current CPU
123    ///
124    /// # Errors
125    ///
126    /// Returns `CpuError` if CPU detection fails.
127    pub fn new() -> Result<Self, CpuError> {
128        #[cfg(target_arch = "x86_64")]
129        {
130            crate::arch::x86_64::detect_cpu()
131        }
132        #[cfg(target_arch = "aarch64")]
133        {
134            crate::arch::aarch64::detect_cpu()
135        }
136        #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
137        {
138            Err(CpuError::UnsupportedArch)
139        }
140    }
141
142    /// Returns a reference to a statically detected CPU info
143    ///
144    /// This is useful when you want to avoid the overhead of detecting
145    /// the CPU multiple times during program execution.
146    #[must_use]
147    pub fn get() -> &'static Self {
148        static CPU_INFO: std::sync::LazyLock<CpuInfo> =
149            std::sync::LazyLock::new(|| CpuInfo::new().expect("Failed to detect CPU information"));
150        &CPU_INFO
151    }
152}
153
154impl Default for CpuInfo {
155    fn default() -> Self {
156        Self {
157            vendor: Vendor::Unknown,
158            brand_string: String::new(),
159            version: Version::default(),
160            physical_cores: 0,
161            logical_cores: 0,
162            frequency: Frequency::default(),
163            cache_sizes: [None; 4],
164            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
165            features: crate::cpu::X86Features::empty(),
166            #[cfg(target_arch = "aarch64")]
167            features: crate::cpu::ArmFeatures::empty(),
168            microarch: None,
169            hypervisor: None,
170            peak_flops: None,
171            p_cores: None,
172            e_cores: None,
173        }
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn test_vendor_display() {
183        assert_eq!(Vendor::Intel.to_string(), "Intel");
184        assert_eq!(Vendor::AMD.to_string(), "AMD");
185        assert_eq!(Vendor::ARM.to_string(), "ARM");
186        assert_eq!(Vendor::Unknown.to_string(), "Unknown");
187    }
188
189    #[test]
190    fn test_cpu_info_default() {
191        let info = CpuInfo::default();
192        assert_eq!(info.vendor, Vendor::Unknown);
193        assert_eq!(info.physical_cores, 0);
194        assert_eq!(info.logical_cores, 0);
195        assert_eq!(info.cache_sizes, [None; 4]);
196    }
197}