mprober_lib/cpu/
cpu_info.rs

1use std::{
2    collections::BTreeSet,
3    hash::{Hash, Hasher},
4    io::ErrorKind,
5    str::from_utf8_unchecked,
6};
7
8use crate::scanner_rust::{generic_array::typenum::U1024, ScannerAscii, ScannerError};
9
10#[allow(clippy::upper_case_acronyms)]
11#[derive(Default, Debug, Clone)]
12pub struct CPU {
13    pub physical_id: usize,
14    pub model_name:  String,
15    pub cpus_mhz:    Vec<f64>,
16    pub siblings:    usize,
17    pub cpu_cores:   usize,
18}
19
20impl Hash for CPU {
21    #[inline]
22    fn hash<H: Hasher>(&self, state: &mut H) {
23        self.physical_id.hash(state)
24    }
25}
26
27impl PartialEq for CPU {
28    #[inline]
29    fn eq(&self, other: &CPU) -> bool {
30        self.physical_id.eq(&other.physical_id)
31    }
32}
33
34/// Get CPU information by reading the `/proc/cpuinfo` file.
35///
36/// ```rust
37/// use mprober_lib::cpu;
38///
39/// let cpus = cpu::get_cpus().unwrap();
40///
41/// println!("{cpus:#?}");
42/// ```
43pub fn get_cpus() -> Result<Vec<CPU>, ScannerError> {
44    const USEFUL_ITEMS: [&[u8]; 5] =
45        [b"model name", b"cpu MHz", b"physical id", b"siblings", b"cpu cores"];
46    const MODEL_NAME_INDEX: usize = 0;
47    const CPU_MHZ_INDEX: usize = 1;
48    const PHYSICAL_ID_INDEX: usize = 2;
49    const SIBLINGS_INDEX: usize = 3;
50    const CPU_CORES: usize = 4;
51
52    let mut sc: ScannerAscii<_, U1024> = ScannerAscii::scan_path2("/proc/cpuinfo")?;
53
54    let mut cpus = Vec::with_capacity(1);
55    let mut physical_ids: BTreeSet<usize> = BTreeSet::new();
56
57    let mut physical_id = 0;
58    let mut model_name = String::new();
59    let mut cpus_mhz = Vec::with_capacity(1);
60    let mut siblings = 0;
61    let mut cpu_cores = 0;
62
63    'outer: loop {
64        'item: for (i, &item) in USEFUL_ITEMS.iter().enumerate() {
65            let item_len = item.len();
66
67            loop {
68                match sc.next_line_raw()? {
69                    Some(line) => {
70                        if line.starts_with(item) {
71                            let colon_index = line[item_len..]
72                                .iter()
73                                .copied()
74                                .position(|b| b == b':')
75                                .ok_or(ErrorKind::InvalidData)?;
76
77                            let value = unsafe {
78                                from_utf8_unchecked(&line[(item_len + colon_index + 1)..])
79                            }
80                            .trim();
81
82                            match i {
83                                MODEL_NAME_INDEX => {
84                                    if model_name.is_empty() {
85                                        model_name.push_str(value);
86                                    }
87                                },
88                                CPU_MHZ_INDEX => {
89                                    cpus_mhz.push(value.parse()?);
90                                },
91                                PHYSICAL_ID_INDEX => {
92                                    physical_id = value.parse()?;
93
94                                    if physical_ids.contains(&physical_id) {
95                                        break 'item;
96                                    }
97                                },
98                                SIBLINGS_INDEX => {
99                                    siblings = value.parse()?;
100                                },
101                                CPU_CORES => {
102                                    cpu_cores = value.parse()?;
103
104                                    break 'item;
105                                },
106                                _ => unreachable!(),
107                            }
108
109                            break;
110                        }
111                    },
112                    None => {
113                        if i == MODEL_NAME_INDEX {
114                            break 'outer;
115                        } else {
116                            return Err(ErrorKind::UnexpectedEof.into());
117                        }
118                    },
119                }
120            }
121        }
122
123        if siblings == cpus_mhz.len() {
124            let cpu = CPU {
125                physical_id,
126                model_name,
127                cpus_mhz,
128                siblings,
129                cpu_cores,
130            };
131
132            cpus.push(cpu);
133            physical_ids.insert(physical_id);
134
135            physical_id = 0;
136            model_name = String::new();
137            cpus_mhz = Vec::with_capacity(1);
138            siblings = 0;
139            cpu_cores = 0;
140        }
141
142        loop {
143            let line_length = sc.drop_next_line()?;
144
145            match line_length {
146                Some(line_length) => {
147                    if line_length == 0 {
148                        break;
149                    }
150                },
151                None => {
152                    break 'outer;
153                },
154            }
155        }
156    }
157
158    Ok(cpus)
159}