1#![allow(clippy::too_many_arguments)]
4
5use std::collections::{HashMap, HashSet};
6use std::fs::File;
7use std::io::{BufRead, BufReader, Read};
8use std::time::Instant;
9
10use crate::sys::utils::to_u64;
11use crate::{Cpu, CpuRefreshKind};
12
13macro_rules! to_str {
14 ($e:expr) => {
15 unsafe { std::str::from_utf8_unchecked($e) }
16 };
17}
18
19pub(crate) struct CpusWrapper {
20 pub(crate) global_cpu: CpuUsage,
21 pub(crate) cpus: Vec<Cpu>,
22 got_cpu_frequency: bool,
23 last_update: Option<Instant>,
25}
26
27impl CpusWrapper {
28 pub(crate) fn new() -> Self {
29 Self {
30 global_cpu: CpuUsage::default(),
31 cpus: Vec::with_capacity(4),
32 got_cpu_frequency: false,
33 last_update: None,
34 }
35 }
36
37 pub(crate) fn refresh_if_needed(
38 &mut self,
39 only_update_global_cpu: bool,
40 refresh_kind: CpuRefreshKind,
41 ) {
42 self.refresh(only_update_global_cpu, refresh_kind);
43 }
44
45 pub(crate) fn refresh(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) {
46 let need_cpu_usage_update = self
47 .last_update
48 .map(|last_update| last_update.elapsed() > crate::MINIMUM_CPU_UPDATE_INTERVAL)
49 .unwrap_or(true);
50
51 let first = self.cpus.is_empty();
52 let mut vendors_brands = if first {
53 get_vendor_id_and_brand()
54 } else {
55 HashMap::new()
56 };
57
58 if need_cpu_usage_update {
61 self.last_update = Some(Instant::now());
62 let f = match File::open("/proc/stat") {
63 Ok(f) => f,
64 Err(_e) => {
65 sysinfo_debug!("failed to retrieve CPU information: {:?}", _e);
66 return;
67 }
68 };
69 let buf = BufReader::new(f);
70
71 let mut i: usize = 0;
72 let mut it = buf.split(b'\n');
73
74 if first || refresh_kind.cpu_usage() {
75 if let Some(Ok(line)) = it.next() {
76 if line.len() < 4 || &line[..4] != b"cpu " {
77 return;
78 }
79 let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()).skip(1);
80 self.global_cpu.set(
81 parts.next().map(to_u64).unwrap_or(0),
82 parts.next().map(to_u64).unwrap_or(0),
83 parts.next().map(to_u64).unwrap_or(0),
84 parts.next().map(to_u64).unwrap_or(0),
85 parts.next().map(to_u64).unwrap_or(0),
86 parts.next().map(to_u64).unwrap_or(0),
87 parts.next().map(to_u64).unwrap_or(0),
88 parts.next().map(to_u64).unwrap_or(0),
89 parts.next().map(to_u64).unwrap_or(0),
90 parts.next().map(to_u64).unwrap_or(0),
91 );
92 }
93 if first || !only_update_global_cpu {
94 while let Some(Ok(line)) = it.next() {
95 if line.len() < 3 || &line[..3] != b"cpu" {
96 break;
97 }
98
99 let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty());
100 if first {
101 let (vendor_id, brand) = match vendors_brands.remove(&i) {
102 Some((vendor_id, brand)) => (vendor_id, brand),
103 None => (String::new(), String::new()),
104 };
105 self.cpus.push(Cpu {
106 inner: CpuInner::new_with_values(
107 to_str!(parts.next().unwrap_or(&[])),
108 parts.next().map(to_u64).unwrap_or(0),
109 parts.next().map(to_u64).unwrap_or(0),
110 parts.next().map(to_u64).unwrap_or(0),
111 parts.next().map(to_u64).unwrap_or(0),
112 parts.next().map(to_u64).unwrap_or(0),
113 parts.next().map(to_u64).unwrap_or(0),
114 parts.next().map(to_u64).unwrap_or(0),
115 parts.next().map(to_u64).unwrap_or(0),
116 parts.next().map(to_u64).unwrap_or(0),
117 parts.next().map(to_u64).unwrap_or(0),
118 0,
119 vendor_id,
120 brand,
121 ),
122 });
123 } else {
124 parts.next(); if let Some(cpu) = self.cpus.get_mut(i) {
126 cpu.inner.set(
127 parts.next().map(to_u64).unwrap_or(0),
128 parts.next().map(to_u64).unwrap_or(0),
129 parts.next().map(to_u64).unwrap_or(0),
130 parts.next().map(to_u64).unwrap_or(0),
131 parts.next().map(to_u64).unwrap_or(0),
132 parts.next().map(to_u64).unwrap_or(0),
133 parts.next().map(to_u64).unwrap_or(0),
134 parts.next().map(to_u64).unwrap_or(0),
135 parts.next().map(to_u64).unwrap_or(0),
136 parts.next().map(to_u64).unwrap_or(0),
137 );
138 } else {
139 sysinfo_debug!("ignoring new CPU added");
142 }
143 }
144
145 i += 1;
146 }
147 }
148 if i < self.cpus.len() {
149 sysinfo_debug!("{} CPU(s) seem to have been removed", self.cpus.len() - i);
150 }
151 }
152 }
153
154 if refresh_kind.frequency() {
155 #[cfg(feature = "multithread")]
156 use rayon::iter::{
157 IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator,
158 };
159
160 #[cfg(feature = "multithread")]
161 fn iter_mut<'a, T>(
163 val: &'a mut T,
164 ) -> <&'a mut T as rayon::iter::IntoParallelIterator>::Iter
165 where
166 &'a mut T: rayon::iter::IntoParallelIterator,
167 {
168 val.par_iter_mut()
169 }
170
171 #[cfg(not(feature = "multithread"))]
172 fn iter_mut(val: &mut [Cpu]) -> std::slice::IterMut<'_, Cpu> {
173 val.iter_mut()
174 }
175
176 iter_mut(&mut self.cpus)
178 .enumerate()
179 .for_each(|(pos, proc_)| proc_.inner.frequency = get_cpu_frequency(pos));
180
181 self.got_cpu_frequency = true;
182 }
183 }
184
185 pub(crate) fn get_global_raw_times(&self) -> (u64, u64) {
186 (self.global_cpu.total_time, self.global_cpu.old_total_time)
187 }
188
189 pub(crate) fn len(&self) -> usize {
190 self.cpus.len()
191 }
192
193 pub(crate) fn is_empty(&self) -> bool {
194 self.cpus.is_empty()
195 }
196}
197
198#[derive(Clone, Copy, Debug, Default)]
200pub(crate) struct CpuValues {
201 user: u64,
202 nice: u64,
203 system: u64,
204 idle: u64,
205 iowait: u64,
206 irq: u64,
207 softirq: u64,
208 steal: u64,
209 guest: u64,
210 guest_nice: u64,
211}
212
213impl CpuValues {
214 pub fn set(
216 &mut self,
217 user: u64,
218 nice: u64,
219 system: u64,
220 idle: u64,
221 iowait: u64,
222 irq: u64,
223 softirq: u64,
224 steal: u64,
225 guest: u64,
226 guest_nice: u64,
227 ) {
228 self.user = user.saturating_sub(guest);
230 self.nice = nice.saturating_sub(guest_nice);
232 self.system = system;
233 self.idle = idle;
234 self.iowait = iowait;
235 self.irq = irq;
236 self.softirq = softirq;
237 self.steal = steal;
238 self.guest = guest;
239 self.guest_nice = guest_nice;
240 }
241
242 pub fn work_time(&self) -> u64 {
244 self.user
245 .saturating_add(self.nice)
246 .saturating_add(self.system)
247 .saturating_add(self.irq)
248 .saturating_add(self.softirq)
249 }
250
251 pub fn total_time(&self) -> u64 {
253 self.work_time()
254 .saturating_add(self.idle)
255 .saturating_add(self.iowait)
256 .saturating_add(self.guest)
259 .saturating_add(self.guest_nice)
260 .saturating_add(self.steal)
261 }
262}
263
264#[derive(Default)]
265pub(crate) struct CpuUsage {
266 percent: f32,
267 old_values: CpuValues,
268 new_values: CpuValues,
269 total_time: u64,
270 old_total_time: u64,
271}
272
273impl CpuUsage {
274 pub(crate) fn new_with_values(
275 user: u64,
276 nice: u64,
277 system: u64,
278 idle: u64,
279 iowait: u64,
280 irq: u64,
281 softirq: u64,
282 steal: u64,
283 guest: u64,
284 guest_nice: u64,
285 ) -> Self {
286 let mut new_values = CpuValues::default();
287 new_values.set(
288 user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
289 );
290 Self {
291 old_values: CpuValues::default(),
292 new_values,
293 percent: 0f32,
294 total_time: 0,
295 old_total_time: 0,
296 }
297 }
298
299 pub(crate) fn set(
300 &mut self,
301 user: u64,
302 nice: u64,
303 system: u64,
304 idle: u64,
305 iowait: u64,
306 irq: u64,
307 softirq: u64,
308 steal: u64,
309 guest: u64,
310 guest_nice: u64,
311 ) {
312 macro_rules! min {
313 ($a:expr, $b:expr, $def:expr) => {
314 if $a > $b {
315 ($a - $b) as f32
316 } else {
317 $def
318 }
319 };
320 }
321 self.old_values = self.new_values;
322 self.new_values.set(
323 user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
324 );
325 self.total_time = self.new_values.total_time();
326 self.old_total_time = self.old_values.total_time();
327 self.percent = min!(self.new_values.work_time(), self.old_values.work_time(), 0.)
328 / min!(self.total_time, self.old_total_time, 1.)
329 * 100.;
330 if self.percent > 100. {
331 self.percent = 100.; }
333 }
334
335 pub(crate) fn usage(&self) -> f32 {
336 self.percent
337 }
338}
339
340pub(crate) struct CpuInner {
341 usage: CpuUsage,
342 pub(crate) name: String,
343 pub(crate) frequency: u64,
344 pub(crate) vendor_id: String,
345 pub(crate) brand: String,
346}
347
348impl CpuInner {
349 pub(crate) fn new_with_values(
350 name: &str,
351 user: u64,
352 nice: u64,
353 system: u64,
354 idle: u64,
355 iowait: u64,
356 irq: u64,
357 softirq: u64,
358 steal: u64,
359 guest: u64,
360 guest_nice: u64,
361 frequency: u64,
362 vendor_id: String,
363 brand: String,
364 ) -> Self {
365 Self {
366 usage: CpuUsage::new_with_values(
367 user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
368 ),
369 name: name.to_owned(),
370 frequency,
371 vendor_id,
372 brand,
373 }
374 }
375
376 pub(crate) fn set(
377 &mut self,
378 user: u64,
379 nice: u64,
380 system: u64,
381 idle: u64,
382 iowait: u64,
383 irq: u64,
384 softirq: u64,
385 steal: u64,
386 guest: u64,
387 guest_nice: u64,
388 ) {
389 self.usage.set(
390 user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
391 );
392 }
393
394 pub(crate) fn cpu_usage(&self) -> f32 {
395 self.usage.percent
396 }
397
398 pub(crate) fn name(&self) -> &str {
399 &self.name
400 }
401
402 pub(crate) fn frequency(&self) -> u64 {
404 self.frequency
405 }
406
407 pub(crate) fn vendor_id(&self) -> &str {
408 &self.vendor_id
409 }
410
411 pub(crate) fn brand(&self) -> &str {
412 &self.brand
413 }
414}
415
416pub(crate) fn get_cpu_frequency(cpu_core_index: usize) -> u64 {
417 let mut s = String::new();
418 if File::open(format!(
419 "/sys/devices/system/cpu/cpu{cpu_core_index}/cpufreq/scaling_cur_freq",
420 ))
421 .and_then(|mut f| f.read_to_string(&mut s))
422 .is_ok()
423 {
424 let freq_option = s.trim().split('\n').next();
425 if let Some(freq_string) = freq_option {
426 if let Ok(freq) = freq_string.parse::<u64>() {
427 return freq / 1000;
428 }
429 }
430 }
431 s.clear();
432 if File::open("/proc/cpuinfo")
433 .and_then(|mut f| f.read_to_string(&mut s))
434 .is_err()
435 {
436 return 0;
437 }
438 let find_cpu_mhz = s.split('\n').find(|line| {
439 cpuinfo_is_key(line, b"cpu MHz\t")
440 || cpuinfo_is_key(line, b"CPU MHz\t")
441 || cpuinfo_is_key(line, b"BogoMIPS")
442 || cpuinfo_is_key(line, b"clock\t")
443 || cpuinfo_is_key(line, b"bogomips per cpu")
444 });
445 find_cpu_mhz
446 .and_then(|line| line.split(':').next_back())
447 .and_then(|val| val.replace("MHz", "").trim().parse::<f64>().ok())
448 .map(|speed| speed as u64)
449 .unwrap_or_default()
450}
451
452#[allow(unused_assignments)]
453pub(crate) fn get_physical_core_count() -> Option<usize> {
454 let mut s = String::new();
455 if let Err(_e) = File::open("/proc/cpuinfo").and_then(|mut f| f.read_to_string(&mut s)) {
456 sysinfo_debug!("Cannot read `/proc/cpuinfo` file: {:?}", _e);
457 return None;
458 }
459
460 macro_rules! add_core {
461 ($core_ids_and_physical_ids:ident, $core_id:ident, $physical_id:ident, $cpu:ident) => {{
462 if !$core_id.is_empty() && !$physical_id.is_empty() {
463 $core_ids_and_physical_ids.insert(format!("{} {}", $core_id, $physical_id));
464 } else if !$cpu.is_empty() {
465 $core_ids_and_physical_ids.insert($cpu.to_owned());
469 }
470 $core_id = "";
471 $physical_id = "";
472 $cpu = "";
473 }};
474 }
475
476 let mut core_ids_and_physical_ids: HashSet<String> = HashSet::new();
477 let mut core_id = "";
478 let mut physical_id = "";
479 let mut cpu = "";
480
481 for line in s.lines() {
482 if line.is_empty() {
483 add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu);
484 } else if line.starts_with("processor") {
485 cpu = line
486 .splitn(2, ':')
487 .last()
488 .map(|x| x.trim())
489 .unwrap_or_default();
490 } else if line.starts_with("core id") {
491 core_id = line
492 .splitn(2, ':')
493 .last()
494 .map(|x| x.trim())
495 .unwrap_or_default();
496 } else if line.starts_with("physical id") {
497 physical_id = line
498 .splitn(2, ':')
499 .last()
500 .map(|x| x.trim())
501 .unwrap_or_default();
502 }
503 }
504 add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu);
505
506 Some(core_ids_and_physical_ids.len())
507}
508
509fn get_arm_implementer(implementer: u32) -> Option<&'static str> {
517 Some(match implementer {
518 0x41 => "ARM",
519 0x42 => "Broadcom",
520 0x43 => "Cavium",
521 0x44 => "DEC",
522 0x46 => "FUJITSU",
523 0x48 => "HiSilicon",
524 0x49 => "Infineon",
525 0x4d => "Motorola/Freescale",
526 0x4e => "NVIDIA",
527 0x50 => "APM",
528 0x51 => "Qualcomm",
529 0x53 => "Samsung",
530 0x56 => "Marvell",
531 0x61 => "Apple",
532 0x66 => "Faraday",
533 0x69 => "Intel",
534 0x70 => "Phytium",
535 0xc0 => "Ampere",
536 _ => return None,
537 })
538}
539
540fn get_arm_part(implementer: u32, part: u32) -> Option<&'static str> {
548 Some(match (implementer, part) {
549 (0x41, 0x810) => "ARM810",
551 (0x41, 0x920) => "ARM920",
552 (0x41, 0x922) => "ARM922",
553 (0x41, 0x926) => "ARM926",
554 (0x41, 0x940) => "ARM940",
555 (0x41, 0x946) => "ARM946",
556 (0x41, 0x966) => "ARM966",
557 (0x41, 0xa20) => "ARM1020",
558 (0x41, 0xa22) => "ARM1022",
559 (0x41, 0xa26) => "ARM1026",
560 (0x41, 0xb02) => "ARM11 MPCore",
561 (0x41, 0xb36) => "ARM1136",
562 (0x41, 0xb56) => "ARM1156",
563 (0x41, 0xb76) => "ARM1176",
564 (0x41, 0xc05) => "Cortex-A5",
565 (0x41, 0xc07) => "Cortex-A7",
566 (0x41, 0xc08) => "Cortex-A8",
567 (0x41, 0xc09) => "Cortex-A9",
568 (0x41, 0xc0d) => "Cortex-A17", (0x41, 0xc0f) => "Cortex-A15",
570 (0x41, 0xc0e) => "Cortex-A17",
571 (0x41, 0xc14) => "Cortex-R4",
572 (0x41, 0xc15) => "Cortex-R5",
573 (0x41, 0xc17) => "Cortex-R7",
574 (0x41, 0xc18) => "Cortex-R8",
575 (0x41, 0xc20) => "Cortex-M0",
576 (0x41, 0xc21) => "Cortex-M1",
577 (0x41, 0xc23) => "Cortex-M3",
578 (0x41, 0xc24) => "Cortex-M4",
579 (0x41, 0xc27) => "Cortex-M7",
580 (0x41, 0xc60) => "Cortex-M0+",
581 (0x41, 0xd01) => "Cortex-A32",
582 (0x41, 0xd02) => "Cortex-A34",
583 (0x41, 0xd03) => "Cortex-A53",
584 (0x41, 0xd04) => "Cortex-A35",
585 (0x41, 0xd05) => "Cortex-A55",
586 (0x41, 0xd06) => "Cortex-A65",
587 (0x41, 0xd07) => "Cortex-A57",
588 (0x41, 0xd08) => "Cortex-A72",
589 (0x41, 0xd09) => "Cortex-A73",
590 (0x41, 0xd0a) => "Cortex-A75",
591 (0x41, 0xd0b) => "Cortex-A76",
592 (0x41, 0xd0c) => "Neoverse-N1",
593 (0x41, 0xd0d) => "Cortex-A77",
594 (0x41, 0xd0e) => "Cortex-A76AE",
595 (0x41, 0xd13) => "Cortex-R52",
596 (0x41, 0xd15) => "Cortex-R82",
597 (0x41, 0xd16) => "Cortex-R52+",
598 (0x41, 0xd20) => "Cortex-M23",
599 (0x41, 0xd21) => "Cortex-M33",
600 (0x41, 0xd22) => "Cortex-R55",
601 (0x41, 0xd23) => "Cortex-R85",
602 (0x41, 0xd40) => "Neoverse-V1",
603 (0x41, 0xd41) => "Cortex-A78",
604 (0x41, 0xd42) => "Cortex-A78AE",
605 (0x41, 0xd43) => "Cortex-A65AE",
606 (0x41, 0xd44) => "Cortex-X1",
607 (0x41, 0xd46) => "Cortex-A510",
608 (0x41, 0xd47) => "Cortex-A710",
609 (0x41, 0xd48) => "Cortex-X2",
610 (0x41, 0xd49) => "Neoverse-N2",
611 (0x41, 0xd4a) => "Neoverse-E1",
612 (0x41, 0xd4b) => "Cortex-A78C",
613 (0x41, 0xd4c) => "Cortex-X1C",
614 (0x41, 0xd4d) => "Cortex-A715",
615 (0x41, 0xd4e) => "Cortex-X3",
616 (0x41, 0xd4f) => "Neoverse-V2",
617 (0x41, 0xd80) => "Cortex-A520",
618 (0x41, 0xd81) => "Cortex-A720",
619 (0x41, 0xd82) => "Cortex-X4",
620 (0x41, 0xd84) => "Neoverse-V3",
621 (0x41, 0xd85) => "Cortex-X925",
622 (0x41, 0xd87) => "Cortex-A725",
623 (0x41, 0xd8e) => "Neoverse-N3",
624
625 (0x42, 0x00f) => "Brahma-B15",
627 (0x42, 0x100) => "Brahma-B53",
628 (0x42, 0x516) => "ThunderX2",
629
630 (0x43, 0x0a0) => "ThunderX",
632 (0x43, 0x0a1) => "ThunderX-88XX",
633 (0x43, 0x0a2) => "ThunderX-81XX",
634 (0x43, 0x0a3) => "ThunderX-83XX",
635 (0x43, 0x0af) => "ThunderX2-99xx",
636
637 (0x44, 0xa10) => "SA110",
639 (0x44, 0xa11) => "SA1100",
640
641 (0x46, 0x001) => "A64FX",
643
644 (0x48, 0xd01) => "Kunpeng-920", (0x4e, 0x000) => "Denver",
649 (0x4e, 0x003) => "Denver 2",
650 (0x4e, 0x004) => "Carmel",
651
652 (0x50, 0x000) => "X-Gene",
654
655 (0x51, 0x00f) => "Scorpion",
657 (0x51, 0x02d) => "Scorpion",
658 (0x51, 0x04d) => "Krait",
659 (0x51, 0x06f) => "Krait",
660 (0x51, 0x201) => "Kryo",
661 (0x51, 0x205) => "Kryo",
662 (0x51, 0x211) => "Kryo",
663 (0x51, 0x800) => "Falkor-V1/Kryo",
664 (0x51, 0x801) => "Kryo-V2",
665 (0x51, 0x802) => "Kryo-3XX-Gold",
666 (0x51, 0x803) => "Kryo-3XX-Silver",
667 (0x51, 0x804) => "Kryo-4XX-Gold",
668 (0x51, 0x805) => "Kryo-4XX-Silver",
669 (0x51, 0xc00) => "Falkor",
670 (0x51, 0xc01) => "Saphira",
671
672 (0x53, 0x001) => "exynos-m1",
674
675 (0x56, 0x131) => "Feroceon-88FR131",
677 (0x56, 0x581) => "PJ4/PJ4b",
678 (0x56, 0x584) => "PJ4B-MP",
679
680 (0x61, 0x020) => "Icestorm-A14",
682 (0x61, 0x021) => "Firestorm-A14",
683 (0x61, 0x022) => "Icestorm-M1",
684 (0x61, 0x023) => "Firestorm-M1",
685 (0x61, 0x024) => "Icestorm-M1-Pro",
686 (0x61, 0x025) => "Firestorm-M1-Pro",
687 (0x61, 0x028) => "Icestorm-M1-Max",
688 (0x61, 0x029) => "Firestorm-M1-Max",
689 (0x61, 0x030) => "Blizzard-A15",
690 (0x61, 0x031) => "Avalanche-A15",
691 (0x61, 0x032) => "Blizzard-M2",
692 (0x61, 0x033) => "Avalanche-M2",
693
694 (0x66, 0x526) => "FA526",
696 (0x66, 0x626) => "FA626",
697
698 (0x69, 0x200) => "i80200",
700 (0x69, 0x210) => "PXA250A",
701 (0x69, 0x212) => "PXA210A",
702 (0x69, 0x242) => "i80321-400",
703 (0x69, 0x243) => "i80321-600",
704 (0x69, 0x290) => "PXA250B/PXA26x",
705 (0x69, 0x292) => "PXA210B",
706 (0x69, 0x2c2) => "i80321-400-B0",
707 (0x69, 0x2c3) => "i80321-600-B0",
708 (0x69, 0x2d0) => "PXA250C/PXA255/PXA26x",
709 (0x69, 0x2d2) => "PXA210C",
710 (0x69, 0x411) => "PXA27x",
711 (0x69, 0x41c) => "IPX425-533",
712 (0x69, 0x41d) => "IPX425-400",
713 (0x69, 0x41f) => "IPX425-266",
714 (0x69, 0x682) => "PXA32x",
715 (0x69, 0x683) => "PXA930/PXA935",
716 (0x69, 0x688) => "PXA30x",
717 (0x69, 0x689) => "PXA31x",
718 (0x69, 0xb11) => "SA1110",
719 (0x69, 0xc12) => "IPX1200",
720
721 (0x70, 0x660) => "FTC660",
723 (0x70, 0x661) => "FTC661",
724 (0x70, 0x662) => "FTC662",
725 (0x70, 0x663) => "FTC663",
726
727 _ => return None,
728 })
729}
730
731pub(crate) fn get_vendor_id_and_brand() -> HashMap<usize, (String, String)> {
733 let mut s = String::new();
734 if File::open("/proc/cpuinfo")
735 .and_then(|mut f| f.read_to_string(&mut s))
736 .is_err()
737 {
738 return HashMap::new();
739 }
740 get_vendor_id_and_brand_inner(&s)
741}
742
743#[inline]
744fn cpuinfo_is_key(line: &str, key: &[u8]) -> bool {
745 let line = line.as_bytes();
746 line.len() > key.len() && line[..key.len()].eq_ignore_ascii_case(key)
747}
748
749fn get_vendor_id_and_brand_inner(data: &str) -> HashMap<usize, (String, String)> {
750 fn get_value(s: &str) -> String {
751 s.split(':')
752 .next_back()
753 .map(|x| x.trim().to_owned())
754 .unwrap_or_default()
755 }
756
757 fn get_hex_value(s: &str) -> u32 {
758 s.split(':')
759 .next_back()
760 .map(|x| x.trim())
761 .filter(|x| x.starts_with("0x"))
762 .map(|x| u32::from_str_radix(&x[2..], 16).unwrap())
763 .unwrap_or_default()
764 }
765
766 #[inline]
767 fn is_new_processor(line: &str) -> bool {
768 line.starts_with("processor\t")
769 }
770
771 #[derive(Default)]
772 struct CpuInfo {
773 index: usize,
774 vendor_id: Option<String>,
775 brand: Option<String>,
776 implementer: Option<u32>,
777 part: Option<u32>,
778 }
779
780 impl CpuInfo {
781 fn has_all_info(&self) -> bool {
782 (self.brand.is_some() && self.vendor_id.is_some())
783 || (self.implementer.is_some() && self.part.is_some())
784 }
785
786 fn convert(mut self) -> (usize, String, String) {
787 let (vendor_id, brand) = if let (Some(implementer), Some(part)) =
788 (self.implementer.take(), self.part.take())
789 {
790 let vendor_id = get_arm_implementer(implementer).map(String::from);
791 let brand = get_arm_part(implementer, part)
802 .map(String::from)
803 .or_else(|| self.brand.take());
804 (vendor_id, brand)
805 } else {
806 (self.vendor_id.take(), self.brand.take())
807 };
808 (
809 self.index,
810 vendor_id.unwrap_or_default(),
811 brand.unwrap_or_default(),
812 )
813 }
814 }
815
816 let mut cpus: HashMap<usize, (String, String)> = HashMap::new();
817 let mut lines = data.split('\n').peekable();
818 while let Some(line) = lines.next() {
819 if is_new_processor(line) {
820 let index = match line
821 .split(':')
822 .nth(1)
823 .and_then(|i| i.trim().parse::<usize>().ok())
824 {
825 Some(index) => index,
826 None => {
827 sysinfo_debug!("Couldn't get processor ID from {line:?}, ignoring this core");
828 continue;
829 }
830 };
831
832 let mut info = CpuInfo {
833 index,
834 ..Default::default()
835 };
836
837 #[allow(clippy::while_let_on_iterator)]
838 while let Some(line) = lines.peek() {
839 if cpuinfo_is_key(line, b"vendor_id\t") {
840 info.vendor_id = Some(get_value(line));
841 } else if cpuinfo_is_key(line, b"model name\t") {
842 info.brand = Some(get_value(line));
843 } else if cpuinfo_is_key(line, b"CPU implementer\t") {
844 info.implementer = Some(get_hex_value(line));
845 } else if cpuinfo_is_key(line, b"CPU part\t") {
846 info.part = Some(get_hex_value(line));
847 } else if info.has_all_info() || is_new_processor(line) {
848 break;
849 }
850 lines.next();
851 }
852 let (index, vendor_id, brand) = info.convert();
853 cpus.insert(index, (vendor_id, brand));
854 }
855 }
856 cpus
857}
858
859#[cfg(test)]
860mod test {
861 use super::get_vendor_id_and_brand_inner;
862
863 #[test]
868 fn test_cpu_retrieval() {
869 const DATA: &str = r#"
870processor : 1
871cpu model : Loongson-3 V0.4 FPU V0.1
872model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
873CPU MHz : 1800.00
874core : 1
875
876processor : 2
877cpu model : Loongson-3 V0.4 FPU V0.1
878model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
879CPU MHz : 1800.00
880package : 0
881core : 2
882
883processor : 3
884cpu model : Loongson-3 V0.4 FPU V0.1
885model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
886CPU MHz : 1800.00
887core : 3
888
889processor : 4
890cpu model : Loongson-3 V0.4 FPU V0.1
891model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
892CPU MHz : 1800.00
893core : 0
894
895processor : 5
896cpu model : Loongson-3 V0.4 FPU V0.1
897model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
898CPU MHz : 1800.00
899core : 1
900
901processor : 6
902cpu model : Loongson-3 V0.4 FPU V0.1
903model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
904CPU MHz : 1800.00
905core : 2
906
907processor : 7
908cpu model : Loongson-3 V0.4 FPU V0.1
909model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
910CPU MHz : 1800.00
911core : 3"#;
912
913 let cpus = get_vendor_id_and_brand_inner(DATA);
914 assert_eq!(cpus.len(), 7);
915 }
916}