procfs_core/
cpuinfo.rs

1use crate::{expect, ProcResult};
2
3#[cfg(feature = "serde1")]
4use serde::{Deserialize, Serialize};
5use std::{collections::HashMap, io::BufRead};
6
7/// Represents the data from `/proc/cpuinfo`.
8///
9/// The `fields` field stores the fields that are common among all CPUs.  The `cpus` field stores
10/// CPU-specific info.
11///
12/// For common fields, there are methods that will return the data, converted to a more appropriate
13/// data type.  These methods will all return `None` if the field doesn't exist, or is in some
14/// unexpected format (in that case, you'll have to access the string data directly).
15#[derive(Debug, Clone)]
16#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
17pub struct CpuInfo {
18    /// This stores fields that are common among all CPUs
19    pub fields: HashMap<String, String>,
20    pub cpus: Vec<HashMap<String, String>>,
21}
22
23impl crate::FromBufRead for CpuInfo {
24    fn from_buf_read<R: BufRead>(r: R) -> ProcResult<Self> {
25        let mut list = Vec::new();
26        let mut map = Some(HashMap::new());
27
28        // the first line of a cpu block must start with "processor"
29        let mut found_first = false;
30
31        for line in r.lines().flatten() {
32            if !line.is_empty() {
33                let mut s = line.split(':');
34                let key = expect!(s.next());
35                if !found_first && key.trim() == "processor" {
36                    found_first = true;
37                }
38                if !found_first {
39                    continue;
40                }
41                if let Some(value) = s.next() {
42                    let key = key.trim().to_owned();
43                    let value = value.trim().to_owned();
44
45                    map.get_or_insert(HashMap::new()).insert(key, value);
46                }
47            } else if let Some(map) = map.take() {
48                list.push(map);
49                found_first = false;
50            }
51        }
52        if let Some(map) = map.take() {
53            list.push(map);
54        }
55
56        // find properties that are the same for all cpus
57        assert!(!list.is_empty());
58
59        let common_fields: Vec<String> = list[0]
60            .iter()
61            .filter_map(|(key, val)| {
62                if list.iter().all(|map| map.get(key).map_or(false, |v| v == val)) {
63                    Some(key.clone())
64                } else {
65                    None
66                }
67            })
68            .collect();
69
70        let mut common_map = HashMap::new();
71        for (k, v) in &list[0] {
72            if common_fields.contains(k) {
73                common_map.insert(k.clone(), v.clone());
74            }
75        }
76
77        for map in &mut list {
78            map.retain(|k, _| !common_fields.contains(k));
79        }
80
81        Ok(CpuInfo {
82            fields: common_map,
83            cpus: list,
84        })
85    }
86}
87
88impl CpuInfo {
89    /// Get the total number of cpu cores.
90    ///
91    /// This is the number of entries in the `/proc/cpuinfo` file.
92    pub fn num_cores(&self) -> usize {
93        self.cpus.len()
94    }
95
96    /// Get info for a specific cpu.
97    ///
98    /// This will merge the common fields with the cpu-specific fields.
99    ///
100    /// Returns None if the requested cpu index is not found.
101    pub fn get_info(&self, cpu_num: usize) -> Option<HashMap<&str, &str>> {
102        self.cpus.get(cpu_num).map(|info| {
103            self.fields
104                .iter()
105                .chain(info.iter())
106                .map(|(k, v)| (k.as_ref(), v.as_ref()))
107                .collect()
108        })
109    }
110
111    /// Get the content of a specific field associated to a CPU
112    ///
113    /// If the field is not found in the set of CPU-specific fields, then
114    /// it is returned from the set of common fields.
115    ///
116    /// Returns None if the requested cpu index is not found, or if the field
117    /// is not found.
118    pub fn get_field(&self, cpu_num: usize, field_name: &str) -> Option<&str> {
119        self.cpus.get(cpu_num).and_then(|cpu_fields| {
120            cpu_fields
121                .get(field_name)
122                .or_else(|| self.fields.get(field_name))
123                .map(|s| s.as_ref())
124        })
125    }
126
127    pub fn model_name(&self, cpu_num: usize) -> Option<&str> {
128        self.get_field(cpu_num, "model name")
129    }
130
131    pub fn vendor_id(&self, cpu_num: usize) -> Option<&str> {
132        self.get_field(cpu_num, "vendor_id")
133    }
134
135    /// May not be available on some older 2.6 kernels
136    pub fn physical_id(&self, cpu_num: usize) -> Option<u32> {
137        self.get_field(cpu_num, "physical id").and_then(|s| s.parse().ok())
138    }
139
140    pub fn flags(&self, cpu_num: usize) -> Option<Vec<&str>> {
141        self.get_field(cpu_num, "flags")
142            .map(|flags| flags.split_whitespace().collect())
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_cpuinfo_rpi() {
152        // My rpi system includes some stuff at the end of /proc/cpuinfo that we shouldn't parse
153        let data = r#"processor       : 0
154model name      : ARMv7 Processor rev 4 (v7l)
155BogoMIPS        : 38.40
156Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
157CPU implementer : 0x41
158CPU architecture: 7
159CPU variant     : 0x0
160CPU part        : 0xd03
161CPU revision    : 4
162
163processor       : 1
164model name      : ARMv7 Processor rev 4 (v7l)
165BogoMIPS        : 38.40
166Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
167CPU implementer : 0x41
168CPU architecture: 7
169CPU variant     : 0x0
170CPU part        : 0xd03
171CPU revision    : 4
172
173processor       : 2
174model name      : ARMv7 Processor rev 4 (v7l)
175BogoMIPS        : 38.40
176Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
177CPU implementer : 0x41
178CPU architecture: 7
179CPU variant     : 0x0
180CPU part        : 0xd03
181CPU revision    : 4
182
183processor       : 3
184model name      : ARMv7 Processor rev 4 (v7l)
185BogoMIPS        : 38.40
186Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
187CPU implementer : 0x41
188CPU architecture: 7
189CPU variant     : 0x0
190CPU part        : 0xd03
191CPU revision    : 4
192
193Hardware        : BCM2835
194Revision        : a020d3
195Serial          : 0000000012345678
196Model           : Raspberry Pi 3 Model B Plus Rev 1.3
197"#;
198
199        let r = std::io::Cursor::new(data.as_bytes());
200
201        use crate::FromRead;
202
203        let info = CpuInfo::from_read(r).unwrap();
204        assert_eq!(info.num_cores(), 4);
205        let info = info.get_info(0).unwrap();
206        assert!(info.get("model name").is_some());
207        assert!(info.get("BogoMIPS").is_some());
208        assert!(info.get("Features").is_some());
209        assert!(info.get("CPU implementer").is_some());
210        assert!(info.get("CPU architecture").is_some());
211        assert!(info.get("CPU variant").is_some());
212        assert!(info.get("CPU part").is_some());
213        assert!(info.get("CPU revision").is_some());
214    }
215}