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[..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[..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(); self.cpus[i].inner.set(
126 parts.next().map(to_u64).unwrap_or(0),
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 );
137 }
138
139 i += 1;
140 }
141 }
142 }
143 }
144
145 if refresh_kind.frequency() {
146 #[cfg(feature = "multithread")]
147 use rayon::iter::{
148 IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator,
149 };
150
151 #[cfg(feature = "multithread")]
152 fn iter_mut<'a, T>(
154 val: &'a mut T,
155 ) -> <&'a mut T as rayon::iter::IntoParallelIterator>::Iter
156 where
157 &'a mut T: rayon::iter::IntoParallelIterator,
158 {
159 val.par_iter_mut()
160 }
161
162 #[cfg(not(feature = "multithread"))]
163 fn iter_mut<'a>(val: &'a mut Vec<Cpu>) -> std::slice::IterMut<'a, Cpu> {
164 val.iter_mut()
165 }
166
167 iter_mut(&mut self.cpus)
169 .enumerate()
170 .for_each(|(pos, proc_)| proc_.inner.frequency = get_cpu_frequency(pos));
171
172 self.got_cpu_frequency = true;
173 }
174 }
175
176 pub(crate) fn get_global_raw_times(&self) -> (u64, u64) {
177 (self.global_cpu.total_time, self.global_cpu.old_total_time)
178 }
179
180 pub(crate) fn len(&self) -> usize {
181 self.cpus.len()
182 }
183
184 pub(crate) fn is_empty(&self) -> bool {
185 self.cpus.is_empty()
186 }
187}
188
189#[derive(Clone, Copy, Debug, Default)]
191pub(crate) struct CpuValues {
192 user: u64,
193 nice: u64,
194 system: u64,
195 idle: u64,
196 iowait: u64,
197 irq: u64,
198 softirq: u64,
199 steal: u64,
200 guest: u64,
201 guest_nice: u64,
202}
203
204impl CpuValues {
205 pub fn set(
207 &mut self,
208 user: u64,
209 nice: u64,
210 system: u64,
211 idle: u64,
212 iowait: u64,
213 irq: u64,
214 softirq: u64,
215 steal: u64,
216 guest: u64,
217 guest_nice: u64,
218 ) {
219 self.user = user.saturating_sub(guest);
221 self.nice = nice.saturating_sub(guest_nice);
223 self.system = system;
224 self.idle = idle;
225 self.iowait = iowait;
226 self.irq = irq;
227 self.softirq = softirq;
228 self.steal = steal;
229 self.guest = guest;
230 self.guest_nice = guest_nice;
231 }
232
233 pub fn work_time(&self) -> u64 {
235 self.user
236 .saturating_add(self.nice)
237 .saturating_add(self.system)
238 .saturating_add(self.irq)
239 .saturating_add(self.softirq)
240 }
241
242 pub fn total_time(&self) -> u64 {
244 self.work_time()
245 .saturating_add(self.idle)
246 .saturating_add(self.iowait)
247 .saturating_add(self.guest)
250 .saturating_add(self.guest_nice)
251 .saturating_add(self.steal)
252 }
253}
254
255#[derive(Default)]
256pub(crate) struct CpuUsage {
257 percent: f32,
258 old_values: CpuValues,
259 new_values: CpuValues,
260 total_time: u64,
261 old_total_time: u64,
262}
263
264impl CpuUsage {
265 pub(crate) fn new_with_values(
266 user: u64,
267 nice: u64,
268 system: u64,
269 idle: u64,
270 iowait: u64,
271 irq: u64,
272 softirq: u64,
273 steal: u64,
274 guest: u64,
275 guest_nice: u64,
276 ) -> Self {
277 let mut new_values = CpuValues::default();
278 new_values.set(
279 user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
280 );
281 Self {
282 old_values: CpuValues::default(),
283 new_values,
284 percent: 0f32,
285 total_time: 0,
286 old_total_time: 0,
287 }
288 }
289
290 pub(crate) fn set(
291 &mut self,
292 user: u64,
293 nice: u64,
294 system: u64,
295 idle: u64,
296 iowait: u64,
297 irq: u64,
298 softirq: u64,
299 steal: u64,
300 guest: u64,
301 guest_nice: u64,
302 ) {
303 macro_rules! min {
304 ($a:expr, $b:expr, $def:expr) => {
305 if $a > $b {
306 ($a - $b) as f32
307 } else {
308 $def
309 }
310 };
311 }
312 self.old_values = self.new_values;
313 self.new_values.set(
314 user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
315 );
316 self.total_time = self.new_values.total_time();
317 self.old_total_time = self.old_values.total_time();
318 self.percent = min!(self.new_values.work_time(), self.old_values.work_time(), 0.)
319 / min!(self.total_time, self.old_total_time, 1.)
320 * 100.;
321 if self.percent > 100. {
322 self.percent = 100.; }
324 }
325
326 pub(crate) fn usage(&self) -> f32 {
327 self.percent
328 }
329}
330
331pub(crate) struct CpuInner {
332 usage: CpuUsage,
333 pub(crate) name: String,
334 pub(crate) frequency: u64,
335 pub(crate) vendor_id: String,
336 pub(crate) brand: String,
337}
338
339impl CpuInner {
340 pub(crate) fn new_with_values(
341 name: &str,
342 user: u64,
343 nice: u64,
344 system: u64,
345 idle: u64,
346 iowait: u64,
347 irq: u64,
348 softirq: u64,
349 steal: u64,
350 guest: u64,
351 guest_nice: u64,
352 frequency: u64,
353 vendor_id: String,
354 brand: String,
355 ) -> Self {
356 Self {
357 usage: CpuUsage::new_with_values(
358 user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
359 ),
360 name: name.to_owned(),
361 frequency,
362 vendor_id,
363 brand,
364 }
365 }
366
367 pub(crate) fn set(
368 &mut self,
369 user: u64,
370 nice: u64,
371 system: u64,
372 idle: u64,
373 iowait: u64,
374 irq: u64,
375 softirq: u64,
376 steal: u64,
377 guest: u64,
378 guest_nice: u64,
379 ) {
380 self.usage.set(
381 user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
382 );
383 }
384
385 pub(crate) fn cpu_usage(&self) -> f32 {
386 self.usage.percent
387 }
388
389 pub(crate) fn name(&self) -> &str {
390 &self.name
391 }
392
393 pub(crate) fn frequency(&self) -> u64 {
395 self.frequency
396 }
397
398 pub(crate) fn vendor_id(&self) -> &str {
399 &self.vendor_id
400 }
401
402 pub(crate) fn brand(&self) -> &str {
403 &self.brand
404 }
405}
406
407pub(crate) fn get_cpu_frequency(cpu_core_index: usize) -> u64 {
408 let mut s = String::new();
409 if File::open(format!(
410 "/sys/devices/system/cpu/cpu{cpu_core_index}/cpufreq/scaling_cur_freq",
411 ))
412 .and_then(|mut f| f.read_to_string(&mut s))
413 .is_ok()
414 {
415 let freq_option = s.trim().split('\n').next();
416 if let Some(freq_string) = freq_option {
417 if let Ok(freq) = freq_string.parse::<u64>() {
418 return freq / 1000;
419 }
420 }
421 }
422 s.clear();
423 if File::open("/proc/cpuinfo")
424 .and_then(|mut f| f.read_to_string(&mut s))
425 .is_err()
426 {
427 return 0;
428 }
429 let find_cpu_mhz = s.split('\n').find(|line| {
430 line.starts_with("cpu MHz\t")
431 || line.starts_with("BogoMIPS")
432 || line.starts_with("clock\t")
433 || line.starts_with("bogomips per cpu")
434 });
435 find_cpu_mhz
436 .and_then(|line| line.split(':').last())
437 .and_then(|val| val.replace("MHz", "").trim().parse::<f64>().ok())
438 .map(|speed| speed as u64)
439 .unwrap_or_default()
440}
441
442#[allow(unused_assignments)]
443pub(crate) fn get_physical_core_count() -> Option<usize> {
444 let mut s = String::new();
445 if let Err(_e) = File::open("/proc/cpuinfo").and_then(|mut f| f.read_to_string(&mut s)) {
446 sysinfo_debug!("Cannot read `/proc/cpuinfo` file: {:?}", _e);
447 return None;
448 }
449
450 macro_rules! add_core {
451 ($core_ids_and_physical_ids:ident, $core_id:ident, $physical_id:ident, $cpu:ident) => {{
452 if !$core_id.is_empty() && !$physical_id.is_empty() {
453 $core_ids_and_physical_ids.insert(format!("{} {}", $core_id, $physical_id));
454 } else if !$cpu.is_empty() {
455 $core_ids_and_physical_ids.insert($cpu.to_owned());
459 }
460 $core_id = "";
461 $physical_id = "";
462 $cpu = "";
463 }};
464 }
465
466 let mut core_ids_and_physical_ids: HashSet<String> = HashSet::new();
467 let mut core_id = "";
468 let mut physical_id = "";
469 let mut cpu = "";
470
471 for line in s.lines() {
472 if line.is_empty() {
473 add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu);
474 } else if line.starts_with("processor") {
475 cpu = line
476 .splitn(2, ':')
477 .last()
478 .map(|x| x.trim())
479 .unwrap_or_default();
480 } else if line.starts_with("core id") {
481 core_id = line
482 .splitn(2, ':')
483 .last()
484 .map(|x| x.trim())
485 .unwrap_or_default();
486 } else if line.starts_with("physical id") {
487 physical_id = line
488 .splitn(2, ':')
489 .last()
490 .map(|x| x.trim())
491 .unwrap_or_default();
492 }
493 }
494 add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu);
495
496 Some(core_ids_and_physical_ids.len())
497}
498
499fn get_arm_implementer(implementer: u32) -> Option<&'static str> {
507 Some(match implementer {
508 0x41 => "ARM",
509 0x42 => "Broadcom",
510 0x43 => "Cavium",
511 0x44 => "DEC",
512 0x46 => "FUJITSU",
513 0x48 => "HiSilicon",
514 0x49 => "Infineon",
515 0x4d => "Motorola/Freescale",
516 0x4e => "NVIDIA",
517 0x50 => "APM",
518 0x51 => "Qualcomm",
519 0x53 => "Samsung",
520 0x56 => "Marvell",
521 0x61 => "Apple",
522 0x66 => "Faraday",
523 0x69 => "Intel",
524 0x70 => "Phytium",
525 0xc0 => "Ampere",
526 _ => return None,
527 })
528}
529
530fn get_arm_part(implementer: u32, part: u32) -> Option<&'static str> {
538 Some(match (implementer, part) {
539 (0x41, 0x810) => "ARM810",
541 (0x41, 0x920) => "ARM920",
542 (0x41, 0x922) => "ARM922",
543 (0x41, 0x926) => "ARM926",
544 (0x41, 0x940) => "ARM940",
545 (0x41, 0x946) => "ARM946",
546 (0x41, 0x966) => "ARM966",
547 (0x41, 0xa20) => "ARM1020",
548 (0x41, 0xa22) => "ARM1022",
549 (0x41, 0xa26) => "ARM1026",
550 (0x41, 0xb02) => "ARM11 MPCore",
551 (0x41, 0xb36) => "ARM1136",
552 (0x41, 0xb56) => "ARM1156",
553 (0x41, 0xb76) => "ARM1176",
554 (0x41, 0xc05) => "Cortex-A5",
555 (0x41, 0xc07) => "Cortex-A7",
556 (0x41, 0xc08) => "Cortex-A8",
557 (0x41, 0xc09) => "Cortex-A9",
558 (0x41, 0xc0d) => "Cortex-A17", (0x41, 0xc0f) => "Cortex-A15",
560 (0x41, 0xc0e) => "Cortex-A17",
561 (0x41, 0xc14) => "Cortex-R4",
562 (0x41, 0xc15) => "Cortex-R5",
563 (0x41, 0xc17) => "Cortex-R7",
564 (0x41, 0xc18) => "Cortex-R8",
565 (0x41, 0xc20) => "Cortex-M0",
566 (0x41, 0xc21) => "Cortex-M1",
567 (0x41, 0xc23) => "Cortex-M3",
568 (0x41, 0xc24) => "Cortex-M4",
569 (0x41, 0xc27) => "Cortex-M7",
570 (0x41, 0xc60) => "Cortex-M0+",
571 (0x41, 0xd01) => "Cortex-A32",
572 (0x41, 0xd02) => "Cortex-A34",
573 (0x41, 0xd03) => "Cortex-A53",
574 (0x41, 0xd04) => "Cortex-A35",
575 (0x41, 0xd05) => "Cortex-A55",
576 (0x41, 0xd06) => "Cortex-A65",
577 (0x41, 0xd07) => "Cortex-A57",
578 (0x41, 0xd08) => "Cortex-A72",
579 (0x41, 0xd09) => "Cortex-A73",
580 (0x41, 0xd0a) => "Cortex-A75",
581 (0x41, 0xd0b) => "Cortex-A76",
582 (0x41, 0xd0c) => "Neoverse-N1",
583 (0x41, 0xd0d) => "Cortex-A77",
584 (0x41, 0xd0e) => "Cortex-A76AE",
585 (0x41, 0xd13) => "Cortex-R52",
586 (0x41, 0xd15) => "Cortex-R82",
587 (0x41, 0xd16) => "Cortex-R52+",
588 (0x41, 0xd20) => "Cortex-M23",
589 (0x41, 0xd21) => "Cortex-M33",
590 (0x41, 0xd22) => "Cortex-R55",
591 (0x41, 0xd23) => "Cortex-R85",
592 (0x41, 0xd40) => "Neoverse-V1",
593 (0x41, 0xd41) => "Cortex-A78",
594 (0x41, 0xd42) => "Cortex-A78AE",
595 (0x41, 0xd43) => "Cortex-A65AE",
596 (0x41, 0xd44) => "Cortex-X1",
597 (0x41, 0xd46) => "Cortex-A510",
598 (0x41, 0xd47) => "Cortex-A710",
599 (0x41, 0xd48) => "Cortex-X2",
600 (0x41, 0xd49) => "Neoverse-N2",
601 (0x41, 0xd4a) => "Neoverse-E1",
602 (0x41, 0xd4b) => "Cortex-A78C",
603 (0x41, 0xd4c) => "Cortex-X1C",
604 (0x41, 0xd4d) => "Cortex-A715",
605 (0x41, 0xd4e) => "Cortex-X3",
606 (0x41, 0xd4f) => "Neoverse-V2",
607 (0x41, 0xd80) => "Cortex-A520",
608 (0x41, 0xd81) => "Cortex-A720",
609 (0x41, 0xd82) => "Cortex-X4",
610 (0x41, 0xd84) => "Neoverse-V3",
611 (0x41, 0xd85) => "Cortex-X925",
612 (0x41, 0xd87) => "Cortex-A725",
613 (0x41, 0xd8e) => "Neoverse-N3",
614
615 (0x42, 0x00f) => "Brahma-B15",
617 (0x42, 0x100) => "Brahma-B53",
618 (0x42, 0x516) => "ThunderX2",
619
620 (0x43, 0x0a0) => "ThunderX",
622 (0x43, 0x0a1) => "ThunderX-88XX",
623 (0x43, 0x0a2) => "ThunderX-81XX",
624 (0x43, 0x0a3) => "ThunderX-83XX",
625 (0x43, 0x0af) => "ThunderX2-99xx",
626
627 (0x44, 0xa10) => "SA110",
629 (0x44, 0xa11) => "SA1100",
630
631 (0x46, 0x001) => "A64FX",
633
634 (0x48, 0xd01) => "Kunpeng-920", (0x4e, 0x000) => "Denver",
639 (0x4e, 0x003) => "Denver 2",
640 (0x4e, 0x004) => "Carmel",
641
642 (0x50, 0x000) => "X-Gene",
644
645 (0x51, 0x00f) => "Scorpion",
647 (0x51, 0x02d) => "Scorpion",
648 (0x51, 0x04d) => "Krait",
649 (0x51, 0x06f) => "Krait",
650 (0x51, 0x201) => "Kryo",
651 (0x51, 0x205) => "Kryo",
652 (0x51, 0x211) => "Kryo",
653 (0x51, 0x800) => "Falkor-V1/Kryo",
654 (0x51, 0x801) => "Kryo-V2",
655 (0x51, 0x802) => "Kryo-3XX-Gold",
656 (0x51, 0x803) => "Kryo-3XX-Silver",
657 (0x51, 0x804) => "Kryo-4XX-Gold",
658 (0x51, 0x805) => "Kryo-4XX-Silver",
659 (0x51, 0xc00) => "Falkor",
660 (0x51, 0xc01) => "Saphira",
661
662 (0x53, 0x001) => "exynos-m1",
664
665 (0x56, 0x131) => "Feroceon-88FR131",
667 (0x56, 0x581) => "PJ4/PJ4b",
668 (0x56, 0x584) => "PJ4B-MP",
669
670 (0x61, 0x020) => "Icestorm-A14",
672 (0x61, 0x021) => "Firestorm-A14",
673 (0x61, 0x022) => "Icestorm-M1",
674 (0x61, 0x023) => "Firestorm-M1",
675 (0x61, 0x024) => "Icestorm-M1-Pro",
676 (0x61, 0x025) => "Firestorm-M1-Pro",
677 (0x61, 0x028) => "Icestorm-M1-Max",
678 (0x61, 0x029) => "Firestorm-M1-Max",
679 (0x61, 0x030) => "Blizzard-A15",
680 (0x61, 0x031) => "Avalanche-A15",
681 (0x61, 0x032) => "Blizzard-M2",
682 (0x61, 0x033) => "Avalanche-M2",
683
684 (0x66, 0x526) => "FA526",
686 (0x66, 0x626) => "FA626",
687
688 (0x69, 0x200) => "i80200",
690 (0x69, 0x210) => "PXA250A",
691 (0x69, 0x212) => "PXA210A",
692 (0x69, 0x242) => "i80321-400",
693 (0x69, 0x243) => "i80321-600",
694 (0x69, 0x290) => "PXA250B/PXA26x",
695 (0x69, 0x292) => "PXA210B",
696 (0x69, 0x2c2) => "i80321-400-B0",
697 (0x69, 0x2c3) => "i80321-600-B0",
698 (0x69, 0x2d0) => "PXA250C/PXA255/PXA26x",
699 (0x69, 0x2d2) => "PXA210C",
700 (0x69, 0x411) => "PXA27x",
701 (0x69, 0x41c) => "IPX425-533",
702 (0x69, 0x41d) => "IPX425-400",
703 (0x69, 0x41f) => "IPX425-266",
704 (0x69, 0x682) => "PXA32x",
705 (0x69, 0x683) => "PXA930/PXA935",
706 (0x69, 0x688) => "PXA30x",
707 (0x69, 0x689) => "PXA31x",
708 (0x69, 0xb11) => "SA1110",
709 (0x69, 0xc12) => "IPX1200",
710
711 (0x70, 0x660) => "FTC660",
713 (0x70, 0x661) => "FTC661",
714 (0x70, 0x662) => "FTC662",
715 (0x70, 0x663) => "FTC663",
716
717 _ => return None,
718 })
719}
720
721pub(crate) fn get_vendor_id_and_brand() -> HashMap<usize, (String, String)> {
723 let mut s = String::new();
724 if File::open("/proc/cpuinfo")
725 .and_then(|mut f| f.read_to_string(&mut s))
726 .is_err()
727 {
728 return HashMap::new();
729 }
730
731 fn get_value(s: &str) -> String {
732 s.split(':')
733 .last()
734 .map(|x| x.trim().to_owned())
735 .unwrap_or_default()
736 }
737
738 fn get_hex_value(s: &str) -> u32 {
739 s.split(':')
740 .last()
741 .map(|x| x.trim())
742 .filter(|x| x.starts_with("0x"))
743 .map(|x| u32::from_str_radix(&x[2..], 16).unwrap())
744 .unwrap_or_default()
745 }
746
747 #[inline]
748 fn is_new_processor(line: &str) -> bool {
749 line.starts_with("processor\t")
750 }
751
752 #[derive(Default)]
753 struct CpuInfo {
754 index: usize,
755 vendor_id: Option<String>,
756 brand: Option<String>,
757 implementer: Option<u32>,
758 part: Option<u32>,
759 }
760
761 impl CpuInfo {
762 fn has_all_info(&self) -> bool {
763 (self.brand.is_some() && self.vendor_id.is_some())
764 || (self.implementer.is_some() && self.part.is_some())
765 }
766
767 fn convert(mut self) -> (usize, String, String) {
768 let (vendor_id, brand) = if let (Some(implementer), Some(part)) =
769 (self.implementer.take(), self.part.take())
770 {
771 let vendor_id = get_arm_implementer(implementer).map(String::from);
772 let brand = get_arm_part(implementer, part)
783 .map(String::from)
784 .or_else(|| self.brand.take());
785 (vendor_id, brand)
786 } else {
787 (self.vendor_id.take(), self.brand.take())
788 };
789 (
790 self.index,
791 vendor_id.unwrap_or_default(),
792 brand.unwrap_or_default(),
793 )
794 }
795 }
796
797 let mut cpus: HashMap<usize, (String, String)> = HashMap::new();
798 let mut lines = s.split('\n');
799 while let Some(line) = lines.next() {
800 if is_new_processor(line) {
801 let index = match line
802 .split(':')
803 .nth(1)
804 .and_then(|i| i.trim().parse::<usize>().ok())
805 {
806 Some(index) => index,
807 None => {
808 sysinfo_debug!("Couldn't get processor ID from {line:?}, ignoring this core");
809 continue;
810 }
811 };
812
813 let mut info = CpuInfo {
814 index,
815 ..Default::default()
816 };
817
818 #[allow(clippy::while_let_on_iterator)]
819 while let Some(line) = lines.next() {
820 if line.starts_with("vendor_id\t") {
821 info.vendor_id = Some(get_value(line));
822 } else if line.starts_with("model name\t") {
823 info.brand = Some(get_value(line));
824 } else if line.starts_with("CPU implementer\t") {
825 info.implementer = Some(get_hex_value(line));
826 } else if line.starts_with("CPU part\t") {
827 info.part = Some(get_hex_value(line));
828 } else if info.has_all_info() || is_new_processor(line) {
829 break;
830 }
831 }
832 let (index, vendor_id, brand) = info.convert();
833 cpus.insert(index, (vendor_id, brand));
834 }
835 }
836 cpus
837}