1use 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#[derive(Debug, Eq, PartialEq)]
16pub struct CpuInfo(String);
17
18impl CpuInfo {
19 pub fn read() -> Result<Self, std::io::Error> {
25 Self::read_from(DEFAULT_FILE)
26 }
27
28 pub fn read_from(filename: impl AsRef<Path>) -> Result<Self, std::io::Error> {
34 read_to_string(filename).map(Self)
35 }
36
37 #[must_use]
39 pub fn cpu(&self, index: usize) -> Option<Cpu<'_>> {
40 self.cpus().find(|cpu| cpu.processor() == Some(index))
41 }
42
43 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 pub fn iter(&self) -> impl Iterator<Item = Cpu<'_>> {
55 self.cpus()
56 }
57}
58
59impl Default for CpuInfo {
60 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#[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 #[must_use]
106 pub fn get(&self, key: &str) -> Option<&str> {
107 self.0.get(key).copied()
108 }
109
110 #[must_use]
112 pub fn processor(&self) -> Option<usize> {
113 self.get("processor").and_then(|s| s.parse().ok())
114 }
115
116 #[must_use]
118 pub fn vendor_id(&self) -> Option<&str> {
119 self.get("vendor_id")
120 }
121
122 #[must_use]
124 pub fn cpu_family(&self) -> Option<u8> {
125 self.get("cpu family").and_then(|s| s.parse().ok())
126 }
127
128 #[must_use]
130 pub fn model(&self) -> Option<usize> {
131 self.get("model").and_then(|s| s.parse().ok())
132 }
133
134 #[must_use]
136 pub fn model_name(&self) -> Option<&str> {
137 self.get("model name")
138 }
139
140 #[must_use]
142 pub fn stepping(&self) -> Option<usize> {
143 self.get("stepping").and_then(|s| s.parse().ok())
144 }
145
146 #[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 #[must_use]
155 pub fn cpu_mhz(&self) -> Option<f32> {
156 self.get("cpu MHz").and_then(|s| s.parse().ok())
157 }
158
159 #[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 #[must_use]
180 pub fn physical_id(&self) -> Option<usize> {
181 self.get("physical id").and_then(|s| s.parse().ok())
182 }
183
184 #[must_use]
186 pub fn siblings(&self) -> Option<usize> {
187 self.get("siblings").and_then(|s| s.parse().ok())
188 }
189
190 #[must_use]
192 pub fn core_id(&self) -> Option<usize> {
193 self.get("core id").and_then(|s| s.parse().ok())
194 }
195
196 #[must_use]
198 pub fn cpu_cores(&self) -> Option<usize> {
199 self.get("cpu cores").and_then(|s| s.parse().ok())
200 }
201
202 #[must_use]
204 pub fn apicid(&self) -> Option<usize> {
205 self.get("apicid").and_then(|s| s.parse().ok())
206 }
207
208 #[must_use]
210 pub fn initial_apicid(&self) -> Option<usize> {
211 self.get("initial apicid").and_then(|s| s.parse().ok())
212 }
213
214 #[must_use]
216 pub fn fpu(&self) -> Option<bool> {
217 self.get("fpu").map(|s| s == "yes")
218 }
219
220 #[must_use]
222 pub fn fpu_exception(&self) -> Option<bool> {
223 self.get("fpu_exception").map(|s| s == "yes")
224 }
225
226 #[must_use]
228 pub fn cpuid_level(&self) -> Option<usize> {
229 self.get("cpuid level").and_then(|s| s.parse().ok())
230 }
231
232 #[must_use]
234 pub fn wp(&self) -> Option<bool> {
235 self.get("wp").map(|s| s == "yes")
236 }
237
238 #[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 #[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 #[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 #[must_use]
261 pub fn bogomips(&self) -> Option<f32> {
262 self.get("bogomips").and_then(|s| s.parse().ok())
263 }
264
265 #[must_use]
267 pub fn clflush_size(&self) -> Option<usize> {
268 self.get("clflush size").and_then(|s| s.parse().ok())
269 }
270
271 #[must_use]
273 pub fn cache_alignment(&self) -> Option<usize> {
274 self.get("cache_alignment").and_then(|s| s.parse().ok())
275 }
276
277 #[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 #[must_use]
301 pub fn power_management(&self) -> Option<&str> {
302 self.get("power management")
303 }
304}