1use crate::{expect, ProcResult};
2
3#[cfg(feature = "serde1")]
4use serde::{Deserialize, Serialize};
5use std::{collections::HashMap, io::BufRead};
6
7#[derive(Debug, Clone)]
16#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
17pub struct CpuInfo {
18 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 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 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 pub fn num_cores(&self) -> usize {
93 self.cpus.len()
94 }
95
96 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 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 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 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}