sysinfo/unix/linux/
cpu.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3#![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    /// This field is needed to prevent updating when not enough time passed since last update.
24    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 the last CPU usage update is too close (less than `MINIMUM_CPU_UPDATE_INTERVAL`),
59        // we don't want to update CPUs times.
60        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(); // we don't want the name again
125                            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            // This function is voluntarily made generic in case we want to generalize it.
153            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            // `get_cpu_frequency` is very slow, so better run it in parallel.
168            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/// Struct containing values to compute a CPU usage.
190#[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    /// Sets the given argument to the corresponding fields.
206    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        // `guest` is already accounted in `user`.
220        self.user = user.saturating_sub(guest);
221        // `guest_nice` is already accounted in `nice`.
222        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    /// Returns work time.
234    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    /// Returns total time.
243    pub fn total_time(&self) -> u64 {
244        self.work_time()
245            .saturating_add(self.idle)
246            .saturating_add(self.iowait)
247            // `steal`, `guest` and `guest_nice` are only used if we want to account the "guest"
248            // into the computation.
249            .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.; // to prevent the percentage to go above 100%
323        }
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    /// Returns the CPU frequency in MHz.
394    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                // On systems with only physical cores like raspberry, there is no "core id" or
456                // "physical id" fields. So if one of them is missing, we simply use the "CPU"
457                // info and count it as a physical core.
458                $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
499/// Obtain the implementer of this CPU core.
500///
501/// This has been obtained from util-linux's lscpu implementation, see
502/// https://github.com/util-linux/util-linux/blob/7076703b529d255600631306419cca1b48ab850a/sys-utils/lscpu-arm.c#L240
503///
504/// This list will have to be updated every time a new vendor appears, please keep it synchronized
505/// with util-linux and update the link above with the commit you have used.
506fn 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
530/// Obtain the part of this CPU core.
531///
532/// This has been obtained from util-linux's lscpu implementation, see
533/// https://github.com/util-linux/util-linux/blob/eb788e20b82d0e1001a30867c71c8bfb2bb86819/sys-utils/lscpu-arm.c#L25
534///
535/// This list will have to be updated every time a new core appears, please keep it synchronized
536/// with util-linux and update the link above with the commit you have used.
537fn get_arm_part(implementer: u32, part: u32) -> Option<&'static str> {
538    Some(match (implementer, part) {
539        // ARM
540        (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", // Originally A12
559        (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        // Broadcom
616        (0x42, 0x00f) => "Brahma-B15",
617        (0x42, 0x100) => "Brahma-B53",
618        (0x42, 0x516) => "ThunderX2",
619
620        // Cavium
621        (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        // DEC
628        (0x44, 0xa10) => "SA110",
629        (0x44, 0xa11) => "SA1100",
630
631        // Fujitsu
632        (0x46, 0x001) => "A64FX",
633
634        // HiSilicon
635        (0x48, 0xd01) => "Kunpeng-920", // aka tsv110
636
637        // NVIDIA
638        (0x4e, 0x000) => "Denver",
639        (0x4e, 0x003) => "Denver 2",
640        (0x4e, 0x004) => "Carmel",
641
642        // APM
643        (0x50, 0x000) => "X-Gene",
644
645        // Qualcomm
646        (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        // Samsung
663        (0x53, 0x001) => "exynos-m1",
664
665        // Marvell
666        (0x56, 0x131) => "Feroceon-88FR131",
667        (0x56, 0x581) => "PJ4/PJ4b",
668        (0x56, 0x584) => "PJ4B-MP",
669
670        // Apple
671        (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        // Faraday
685        (0x66, 0x526) => "FA526",
686        (0x66, 0x626) => "FA626",
687
688        // Intel
689        (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        // Phytium
712        (0x70, 0x660) => "FTC660",
713        (0x70, 0x661) => "FTC661",
714        (0x70, 0x662) => "FTC662",
715        (0x70, 0x663) => "FTC663",
716
717        _ => return None,
718    })
719}
720
721/// Returns the brand/vendor string for the first CPU (which should be the same for all CPUs).
722pub(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                // It's possible to "model name" even with an ARM CPU, so just in case we can't retrieve
773                // the brand from "CPU part", we will then use the value from "model name".
774                //
775                // Example from raspberry pi 3B+:
776                //
777                // ```
778                // model name      : ARMv7 Processor rev 4 (v7l)
779                // CPU implementer : 0x41
780                // CPU part        : 0xd03
781                // ```
782                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}