proc_cpuinfo/
lib.rs

1//! Parse /proc/cpuinfo on Linux systems.
2
3use std::collections::{HashMap, HashSet};
4use std::convert::Infallible;
5use std::fs::read_to_string;
6use std::path::Path;
7use std::str::FromStr;
8
9const DEFAULT_FILE: &str = "/proc/cpuinfo";
10const KIB: usize = 1024;
11const MIB: usize = 1024 * KIB;
12const GIB: usize = 1024 * MIB;
13
14/// Information about /proc/cpuinfo.
15#[derive(Debug, Eq, PartialEq)]
16pub struct CpuInfo(String);
17
18impl CpuInfo {
19    /// Read CPU information from `/proc/cpuinfo`.
20    ///
21    /// # Errors
22    ///
23    /// Returns an [`std::io::Error`] if the file could not be read
24    pub fn read() -> Result<Self, std::io::Error> {
25        Self::read_from(DEFAULT_FILE)
26    }
27
28    /// Read CPU information from the given file.
29    ///
30    /// # Errors
31    ///
32    /// Returns an [`std::io::Error`] if the file could not be read
33    pub fn read_from(filename: impl AsRef<Path>) -> Result<Self, std::io::Error> {
34        read_to_string(filename).map(Self)
35    }
36
37    /// Return the CPU at the given index if it exists.
38    #[must_use]
39    pub fn cpu(&self, index: usize) -> Option<Cpu<'_>> {
40        self.cpus().find(|cpu| cpu.processor() == Some(index))
41    }
42
43    /// Return an iterator over all available CPUs.
44    pub fn cpus(&self) -> impl Iterator<Item = Cpu<'_>> {
45        self.0
46            .split("\n\n")
47            .filter(|text| !text.is_empty())
48            .map(Cpu::from_str)
49    }
50
51    /// Return an iterator over all available CPUs.
52    ///
53    /// This is the same as calling `Self::cpus()`.
54    pub fn iter(&self) -> impl Iterator<Item = Cpu<'_>> {
55        self.cpus()
56    }
57}
58
59impl Default for CpuInfo {
60    /// Read the CPU info from `/proc/cpuinfo`.
61    ///
62    /// # Panics
63    ///
64    /// This method panics, if the file could not be read.
65    fn default() -> Self {
66        Self::read().unwrap_or_else(|_| panic!("Could not read from {DEFAULT_FILE}"))
67    }
68}
69
70impl From<&str> for CpuInfo {
71    fn from(s: &str) -> Self {
72        Self::from(s.to_string())
73    }
74}
75
76impl From<String> for CpuInfo {
77    fn from(text: String) -> Self {
78        Self(text)
79    }
80}
81
82impl FromStr for CpuInfo {
83    type Err = Infallible;
84
85    fn from_str(s: &str) -> Result<Self, Self::Err> {
86        Ok(Self::from(s))
87    }
88}
89
90/// Information about a single CPU.
91#[derive(Debug, Eq, PartialEq)]
92pub struct Cpu<'cpu_info>(HashMap<&'cpu_info str, &'cpu_info str>);
93
94impl<'cpu_info> Cpu<'cpu_info> {
95    fn from_str(s: &'cpu_info str) -> Self {
96        Self(
97            s.lines()
98                .filter_map(|line| line.split_once(':'))
99                .map(|(key, value)| (key.trim(), value.trim()))
100                .collect(),
101        )
102    }
103
104    /// Return the raw value stored under the given key if available.
105    #[must_use]
106    pub fn get(&self, key: &str) -> Option<&str> {
107        self.0.get(key).copied()
108    }
109
110    /// Return the processor ID if available.
111    #[must_use]
112    pub fn processor(&self) -> Option<usize> {
113        self.get("processor").and_then(|s| s.parse().ok())
114    }
115
116    /// Return the vendor ID if available.
117    #[must_use]
118    pub fn vendor_id(&self) -> Option<&str> {
119        self.get("vendor_id")
120    }
121
122    /// Return the CPU family if available.
123    #[must_use]
124    pub fn cpu_family(&self) -> Option<u8> {
125        self.get("cpu family").and_then(|s| s.parse().ok())
126    }
127
128    /// Return the CPU model if available.
129    #[must_use]
130    pub fn model(&self) -> Option<usize> {
131        self.get("model").and_then(|s| s.parse().ok())
132    }
133
134    /// Return the model name if available.
135    #[must_use]
136    pub fn model_name(&self) -> Option<&str> {
137        self.get("model name")
138    }
139
140    /// Return the stepping size if available.
141    #[must_use]
142    pub fn stepping(&self) -> Option<usize> {
143        self.get("stepping").and_then(|s| s.parse().ok())
144    }
145
146    /// Return the microcode version if available.
147    #[must_use]
148    pub fn microcode(&self) -> Option<usize> {
149        self.get("microcode")
150            .and_then(|s| usize::from_str_radix(s.trim_start_matches("0x"), 16).ok())
151    }
152
153    /// Return the CPU frequency in Megahertz if available.
154    #[must_use]
155    pub fn cpu_mhz(&self) -> Option<f32> {
156        self.get("cpu MHz").and_then(|s| s.parse().ok())
157    }
158
159    /// Return the cache size in bytes if available.
160    #[must_use]
161    pub fn cache_size(&self) -> Option<usize> {
162        self.get("cache size")
163            .and_then(|s| match s.split_once(' ') {
164                Some((value, unit)) => {
165                    let value: usize = value.parse().ok()?;
166                    match unit {
167                        "B" => Some(value),
168                        "KB" => Some(value * KIB),
169                        "MB" => Some(value * MIB),
170                        "GB" => Some(value * GIB),
171                        _ => None,
172                    }
173                }
174                None => s.parse().ok(),
175            })
176    }
177
178    /// Return the physical ID if available.
179    #[must_use]
180    pub fn physical_id(&self) -> Option<usize> {
181        self.get("physical id").and_then(|s| s.parse().ok())
182    }
183
184    /// Return the amount of siblings if available.
185    #[must_use]
186    pub fn siblings(&self) -> Option<usize> {
187        self.get("siblings").and_then(|s| s.parse().ok())
188    }
189
190    /// Return the core ID if available.
191    #[must_use]
192    pub fn core_id(&self) -> Option<usize> {
193        self.get("core id").and_then(|s| s.parse().ok())
194    }
195
196    /// Return the amount of CPU cores if available.
197    #[must_use]
198    pub fn cpu_cores(&self) -> Option<usize> {
199        self.get("cpu cores").and_then(|s| s.parse().ok())
200    }
201
202    /// Return the APIC ID if available.
203    #[must_use]
204    pub fn apicid(&self) -> Option<usize> {
205        self.get("apicid").and_then(|s| s.parse().ok())
206    }
207
208    /// Return the initial APIC ID if available.
209    #[must_use]
210    pub fn initial_apicid(&self) -> Option<usize> {
211        self.get("initial apicid").and_then(|s| s.parse().ok())
212    }
213
214    /// Return the FPU flag if available.
215    #[must_use]
216    pub fn fpu(&self) -> Option<bool> {
217        self.get("fpu").map(|s| s == "yes")
218    }
219
220    /// Return the FPU exception flag if available.
221    #[must_use]
222    pub fn fpu_exception(&self) -> Option<bool> {
223        self.get("fpu_exception").map(|s| s == "yes")
224    }
225
226    /// Return the CPU ID level if available.
227    #[must_use]
228    pub fn cpuid_level(&self) -> Option<usize> {
229        self.get("cpuid level").and_then(|s| s.parse().ok())
230    }
231
232    /// Return the WP flag if available.
233    #[must_use]
234    pub fn wp(&self) -> Option<bool> {
235        self.get("wp").map(|s| s == "yes")
236    }
237
238    /// Return the available flags.
239    #[must_use]
240    pub fn flags(&self) -> HashSet<&str> {
241        self.get("flags")
242            .map_or_else(HashSet::default, |s| s.split(' ').collect())
243    }
244
245    /// Return the available VMX flags.
246    #[must_use]
247    pub fn vmx_flags(&self) -> HashSet<&str> {
248        self.get("vmx flags")
249            .map_or_else(HashSet::default, |s| s.split(' ').collect())
250    }
251
252    /// Return the known bugs.
253    #[must_use]
254    pub fn bugs(&self) -> HashSet<&str> {
255        self.get("bugs")
256            .map_or_else(HashSet::default, |s| s.split(' ').collect())
257    }
258
259    /// Return the bogo MIPS if available.
260    #[must_use]
261    pub fn bogomips(&self) -> Option<f32> {
262        self.get("bogomips").and_then(|s| s.parse().ok())
263    }
264
265    /// Return the CL flush size if available.
266    #[must_use]
267    pub fn clflush_size(&self) -> Option<usize> {
268        self.get("clflush size").and_then(|s| s.parse().ok())
269    }
270
271    /// Return the cache alignment if available.
272    #[must_use]
273    pub fn cache_alignment(&self) -> Option<usize> {
274        self.get("cache_alignment").and_then(|s| s.parse().ok())
275    }
276
277    /// Return the address sizes if available.
278    ///
279    /// # Returns
280    ///
281    /// Returns a tuple of `(<pysical>, <virtual>)` sizes if available, else `None`.
282    #[must_use]
283    pub fn address_sizes(&self) -> Option<(usize, usize)> {
284        self.get("address sizes")
285            .and_then(|s| s.split_once(','))
286            .map(|(lhs, rhs)| {
287                (
288                    lhs.trim().trim_end_matches(" bits physical"),
289                    rhs.trim().trim_end_matches(" bits virtual"),
290                )
291            })
292            .and_then(|(phy, vir)| {
293                phy.parse()
294                    .ok()
295                    .and_then(|phy| vir.parse().ok().map(|vir| (phy, vir)))
296            })
297    }
298
299    /// Returns the power management if available.
300    #[must_use]
301    pub fn power_management(&self) -> Option<&str> {
302        self.get("power management")
303    }
304}