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: Cpu,
21    pub(crate) cpus: Vec<Cpu>,
22    /// Field set to `false` in `update_cpus` and to `true` in `refresh_processes_specifics`.
23    ///
24    /// The reason behind this is to avoid calling the `update_cpus` more than necessary.
25    /// For example when running `refresh_all` or `refresh_specifics`.
26    need_cpus_update: bool,
27    got_cpu_frequency: bool,
28    /// This field is needed to prevent updating when not enough time passed since last update.
29    last_update: Option<Instant>,
30}
31
32impl CpusWrapper {
33    pub(crate) fn new() -> Self {
34        Self {
35            global_cpu: Cpu {
36                inner: CpuInner::new_with_values(
37                    "",
38                    0,
39                    0,
40                    0,
41                    0,
42                    0,
43                    0,
44                    0,
45                    0,
46                    0,
47                    0,
48                    0,
49                    String::new(),
50                    String::new(),
51                ),
52            },
53            cpus: Vec::with_capacity(4),
54            need_cpus_update: true,
55            got_cpu_frequency: false,
56            last_update: None,
57        }
58    }
59
60    pub(crate) fn refresh_if_needed(
61        &mut self,
62        only_update_global_cpu: bool,
63        refresh_kind: CpuRefreshKind,
64    ) {
65        if self.need_cpus_update {
66            self.refresh(only_update_global_cpu, refresh_kind);
67        }
68    }
69
70    #[allow(clippy::assigning_clones)]
71    pub(crate) fn refresh(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) {
72        let need_cpu_usage_update = self
73            .last_update
74            .map(|last_update| last_update.elapsed() > crate::MINIMUM_CPU_UPDATE_INTERVAL)
75            .unwrap_or(true);
76
77        let first = self.cpus.is_empty();
78        let mut vendors_brands = if first {
79            get_vendor_id_and_brand()
80        } else {
81            HashMap::new()
82        };
83
84        // If the last CPU usage update is too close (less than `MINIMUM_CPU_UPDATE_INTERVAL`),
85        // we don't want to update CPUs times.
86        if need_cpu_usage_update {
87            self.last_update = Some(Instant::now());
88            let f = match File::open("/proc/stat") {
89                Ok(f) => f,
90                Err(_e) => {
91                    sysinfo_debug!("failed to retrieve CPU information: {:?}", _e);
92                    return;
93                }
94            };
95            let buf = BufReader::new(f);
96
97            self.need_cpus_update = false;
98            let mut i: usize = 0;
99            let mut it = buf.split(b'\n');
100
101            if first || refresh_kind.cpu_usage() {
102                if let Some(Ok(line)) = it.next() {
103                    if &line[..4] != b"cpu " {
104                        return;
105                    }
106                    let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty());
107                    if first {
108                        self.global_cpu.inner.name =
109                            to_str!(parts.next().unwrap_or(&[])).to_owned();
110                    } else {
111                        parts.next();
112                    }
113                    self.global_cpu.inner.set(
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                        parts.next().map(to_u64).unwrap_or(0),
119                        parts.next().map(to_u64).unwrap_or(0),
120                        parts.next().map(to_u64).unwrap_or(0),
121                        parts.next().map(to_u64).unwrap_or(0),
122                        parts.next().map(to_u64).unwrap_or(0),
123                        parts.next().map(to_u64).unwrap_or(0),
124                    );
125                }
126                if first || !only_update_global_cpu {
127                    while let Some(Ok(line)) = it.next() {
128                        if &line[..3] != b"cpu" {
129                            break;
130                        }
131
132                        let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty());
133                        if first {
134                            let (vendor_id, brand) = match vendors_brands.remove(&i) {
135                                Some((vendor_id, brand)) => (vendor_id, brand),
136                                None => (String::new(), String::new()),
137                            };
138                            self.cpus.push(Cpu {
139                                inner: CpuInner::new_with_values(
140                                    to_str!(parts.next().unwrap_or(&[])),
141                                    parts.next().map(to_u64).unwrap_or(0),
142                                    parts.next().map(to_u64).unwrap_or(0),
143                                    parts.next().map(to_u64).unwrap_or(0),
144                                    parts.next().map(to_u64).unwrap_or(0),
145                                    parts.next().map(to_u64).unwrap_or(0),
146                                    parts.next().map(to_u64).unwrap_or(0),
147                                    parts.next().map(to_u64).unwrap_or(0),
148                                    parts.next().map(to_u64).unwrap_or(0),
149                                    parts.next().map(to_u64).unwrap_or(0),
150                                    parts.next().map(to_u64).unwrap_or(0),
151                                    0,
152                                    vendor_id,
153                                    brand,
154                                ),
155                            });
156                        } else {
157                            parts.next(); // we don't want the name again
158                            self.cpus[i].inner.set(
159                                parts.next().map(to_u64).unwrap_or(0),
160                                parts.next().map(to_u64).unwrap_or(0),
161                                parts.next().map(to_u64).unwrap_or(0),
162                                parts.next().map(to_u64).unwrap_or(0),
163                                parts.next().map(to_u64).unwrap_or(0),
164                                parts.next().map(to_u64).unwrap_or(0),
165                                parts.next().map(to_u64).unwrap_or(0),
166                                parts.next().map(to_u64).unwrap_or(0),
167                                parts.next().map(to_u64).unwrap_or(0),
168                                parts.next().map(to_u64).unwrap_or(0),
169                            );
170                        }
171
172                        i += 1;
173                    }
174                }
175            }
176        }
177
178        if refresh_kind.frequency() {
179            #[cfg(feature = "multithread")]
180            use rayon::iter::{
181                IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator,
182            };
183
184            #[cfg(feature = "multithread")]
185            // This function is voluntarily made generic in case we want to generalize it.
186            fn iter_mut<'a, T>(
187                val: &'a mut T,
188            ) -> <&'a mut T as rayon::iter::IntoParallelIterator>::Iter
189            where
190                &'a mut T: rayon::iter::IntoParallelIterator,
191            {
192                val.par_iter_mut()
193            }
194
195            #[cfg(not(feature = "multithread"))]
196            fn iter_mut<'a>(val: &'a mut Vec<Cpu>) -> std::slice::IterMut<'a, Cpu> {
197                val.iter_mut()
198            }
199
200            // `get_cpu_frequency` is very slow, so better run it in parallel.
201            iter_mut(&mut self.cpus)
202                .enumerate()
203                .for_each(|(pos, proc_)| proc_.inner.frequency = get_cpu_frequency(pos));
204
205            self.got_cpu_frequency = true;
206        }
207    }
208
209    pub(crate) fn get_global_raw_times(&self) -> (u64, u64) {
210        (
211            self.global_cpu.inner.total_time,
212            self.global_cpu.inner.old_total_time,
213        )
214    }
215
216    pub(crate) fn len(&self) -> usize {
217        self.cpus.len()
218    }
219
220    pub(crate) fn is_empty(&self) -> bool {
221        self.cpus.is_empty()
222    }
223
224    pub(crate) fn set_need_cpus_update(&mut self) {
225        self.need_cpus_update = true;
226    }
227}
228
229/// Struct containing values to compute a CPU usage.
230#[derive(Clone, Copy, Debug)]
231pub(crate) struct CpuValues {
232    user: u64,
233    nice: u64,
234    system: u64,
235    idle: u64,
236    iowait: u64,
237    irq: u64,
238    softirq: u64,
239    steal: u64,
240    guest: u64,
241    guest_nice: u64,
242}
243
244impl CpuValues {
245    /// Creates a new instance of `CpuValues` with everything set to `0`.
246    pub fn new() -> CpuValues {
247        CpuValues {
248            user: 0,
249            nice: 0,
250            system: 0,
251            idle: 0,
252            iowait: 0,
253            irq: 0,
254            softirq: 0,
255            steal: 0,
256            guest: 0,
257            guest_nice: 0,
258        }
259    }
260
261    /// Sets the given argument to the corresponding fields.
262    pub fn set(
263        &mut self,
264        user: u64,
265        nice: u64,
266        system: u64,
267        idle: u64,
268        iowait: u64,
269        irq: u64,
270        softirq: u64,
271        steal: u64,
272        guest: u64,
273        guest_nice: u64,
274    ) {
275        // `guest` is already accounted in `user`.
276        self.user = user.saturating_sub(guest);
277        // `guest_nice` is already accounted in `nice`.
278        self.nice = nice.saturating_sub(guest_nice);
279        self.system = system;
280        self.idle = idle;
281        self.iowait = iowait;
282        self.irq = irq;
283        self.softirq = softirq;
284        self.steal = steal;
285        self.guest = guest;
286        self.guest_nice = guest_nice;
287    }
288
289    /// Returns work time.
290    pub fn work_time(&self) -> u64 {
291        self.user
292            .saturating_add(self.nice)
293            .saturating_add(self.system)
294            .saturating_add(self.irq)
295            .saturating_add(self.softirq)
296    }
297
298    /// Returns total time.
299    pub fn total_time(&self) -> u64 {
300        self.work_time()
301            .saturating_add(self.idle)
302            .saturating_add(self.iowait)
303            // `steal`, `guest` and `guest_nice` are only used if we want to account the "guest"
304            // into the computation.
305            .saturating_add(self.guest)
306            .saturating_add(self.guest_nice)
307            .saturating_add(self.steal)
308    }
309}
310
311pub(crate) struct CpuInner {
312    old_values: CpuValues,
313    new_values: CpuValues,
314    pub(crate) name: String,
315    cpu_usage: f32,
316    total_time: u64,
317    old_total_time: u64,
318    pub(crate) frequency: u64,
319    pub(crate) vendor_id: String,
320    pub(crate) brand: String,
321}
322
323impl CpuInner {
324    pub(crate) fn new_with_values(
325        name: &str,
326        user: u64,
327        nice: u64,
328        system: u64,
329        idle: u64,
330        iowait: u64,
331        irq: u64,
332        softirq: u64,
333        steal: u64,
334        guest: u64,
335        guest_nice: u64,
336        frequency: u64,
337        vendor_id: String,
338        brand: String,
339    ) -> Self {
340        let mut new_values = CpuValues::new();
341        new_values.set(
342            user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
343        );
344        Self {
345            name: name.to_owned(),
346            old_values: CpuValues::new(),
347            new_values,
348            cpu_usage: 0f32,
349            total_time: 0,
350            old_total_time: 0,
351            frequency,
352            vendor_id,
353            brand,
354        }
355    }
356
357    pub(crate) fn set(
358        &mut self,
359        user: u64,
360        nice: u64,
361        system: u64,
362        idle: u64,
363        iowait: u64,
364        irq: u64,
365        softirq: u64,
366        steal: u64,
367        guest: u64,
368        guest_nice: u64,
369    ) {
370        macro_rules! min {
371            ($a:expr, $b:expr, $def:expr) => {
372                if $a > $b {
373                    ($a - $b) as f32
374                } else {
375                    $def
376                }
377            };
378        }
379        self.old_values = self.new_values;
380        self.new_values.set(
381            user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
382        );
383        self.total_time = self.new_values.total_time();
384        self.old_total_time = self.old_values.total_time();
385        self.cpu_usage = min!(self.new_values.work_time(), self.old_values.work_time(), 0.)
386            / min!(self.total_time, self.old_total_time, 1.)
387            * 100.;
388        if self.cpu_usage > 100. {
389            self.cpu_usage = 100.; // to prevent the percentage to go above 100%
390        }
391    }
392
393    pub(crate) fn cpu_usage(&self) -> f32 {
394        self.cpu_usage
395    }
396
397    pub(crate) fn name(&self) -> &str {
398        &self.name
399    }
400
401    /// Returns the CPU frequency in MHz.
402    pub(crate) fn frequency(&self) -> u64 {
403        self.frequency
404    }
405
406    pub(crate) fn vendor_id(&self) -> &str {
407        &self.vendor_id
408    }
409
410    pub(crate) fn brand(&self) -> &str {
411        &self.brand
412    }
413}
414
415pub(crate) fn get_cpu_frequency(cpu_core_index: usize) -> u64 {
416    let mut s = String::new();
417    if File::open(format!(
418        "/sys/devices/system/cpu/cpu{cpu_core_index}/cpufreq/scaling_cur_freq",
419    ))
420    .and_then(|mut f| f.read_to_string(&mut s))
421    .is_ok()
422    {
423        let freq_option = s.trim().split('\n').next();
424        if let Some(freq_string) = freq_option {
425            if let Ok(freq) = freq_string.parse::<u64>() {
426                return freq / 1000;
427            }
428        }
429    }
430    s.clear();
431    if File::open("/proc/cpuinfo")
432        .and_then(|mut f| f.read_to_string(&mut s))
433        .is_err()
434    {
435        return 0;
436    }
437    let find_cpu_mhz = s.split('\n').find(|line| {
438        line.starts_with("cpu MHz\t")
439            || line.starts_with("BogoMIPS")
440            || line.starts_with("clock\t")
441            || line.starts_with("bogomips per cpu")
442    });
443    find_cpu_mhz
444        .and_then(|line| line.split(':').last())
445        .and_then(|val| val.replace("MHz", "").trim().parse::<f64>().ok())
446        .map(|speed| speed as u64)
447        .unwrap_or_default()
448}
449
450#[allow(unused_assignments)]
451pub(crate) fn get_physical_core_count() -> Option<usize> {
452    let mut s = String::new();
453    if let Err(_e) = File::open("/proc/cpuinfo").and_then(|mut f| f.read_to_string(&mut s)) {
454        sysinfo_debug!("Cannot read `/proc/cpuinfo` file: {:?}", _e);
455        return None;
456    }
457
458    macro_rules! add_core {
459        ($core_ids_and_physical_ids:ident, $core_id:ident, $physical_id:ident, $cpu:ident) => {{
460            if !$core_id.is_empty() && !$physical_id.is_empty() {
461                $core_ids_and_physical_ids.insert(format!("{} {}", $core_id, $physical_id));
462            } else if !$cpu.is_empty() {
463                // On systems with only physical cores like raspberry, there is no "core id" or
464                // "physical id" fields. So if one of them is missing, we simply use the "CPU"
465                // info and count it as a physical core.
466                $core_ids_and_physical_ids.insert($cpu.to_owned());
467            }
468            $core_id = "";
469            $physical_id = "";
470            $cpu = "";
471        }};
472    }
473
474    let mut core_ids_and_physical_ids: HashSet<String> = HashSet::new();
475    let mut core_id = "";
476    let mut physical_id = "";
477    let mut cpu = "";
478
479    for line in s.lines() {
480        if line.is_empty() {
481            add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu);
482        } else if line.starts_with("processor") {
483            cpu = line
484                .splitn(2, ':')
485                .last()
486                .map(|x| x.trim())
487                .unwrap_or_default();
488        } else if line.starts_with("core id") {
489            core_id = line
490                .splitn(2, ':')
491                .last()
492                .map(|x| x.trim())
493                .unwrap_or_default();
494        } else if line.starts_with("physical id") {
495            physical_id = line
496                .splitn(2, ':')
497                .last()
498                .map(|x| x.trim())
499                .unwrap_or_default();
500        }
501    }
502    add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu);
503
504    Some(core_ids_and_physical_ids.len())
505}
506
507/// Obtain the implementer of this CPU core.
508///
509/// This has been obtained from util-linux's lscpu implementation, see
510/// https://github.com/util-linux/util-linux/blob/7076703b529d255600631306419cca1b48ab850a/sys-utils/lscpu-arm.c#L240
511///
512/// This list will have to be updated every time a new vendor appears, please keep it synchronized
513/// with util-linux and update the link above with the commit you have used.
514fn get_arm_implementer(implementer: u32) -> Option<&'static str> {
515    Some(match implementer {
516        0x41 => "ARM",
517        0x42 => "Broadcom",
518        0x43 => "Cavium",
519        0x44 => "DEC",
520        0x46 => "FUJITSU",
521        0x48 => "HiSilicon",
522        0x49 => "Infineon",
523        0x4d => "Motorola/Freescale",
524        0x4e => "NVIDIA",
525        0x50 => "APM",
526        0x51 => "Qualcomm",
527        0x53 => "Samsung",
528        0x56 => "Marvell",
529        0x61 => "Apple",
530        0x66 => "Faraday",
531        0x69 => "Intel",
532        0x70 => "Phytium",
533        0xc0 => "Ampere",
534        _ => return None,
535    })
536}
537
538/// Obtain the part of this CPU core.
539///
540/// This has been obtained from util-linux's lscpu implementation, see
541/// https://github.com/util-linux/util-linux/blob/7076703b529d255600631306419cca1b48ab850a/sys-utils/lscpu-arm.c#L34
542///
543/// This list will have to be updated every time a new core appears, please keep it synchronized
544/// with util-linux and update the link above with the commit you have used.
545fn get_arm_part(implementer: u32, part: u32) -> Option<&'static str> {
546    Some(match (implementer, part) {
547        // ARM
548        (0x41, 0x810) => "ARM810",
549        (0x41, 0x920) => "ARM920",
550        (0x41, 0x922) => "ARM922",
551        (0x41, 0x926) => "ARM926",
552        (0x41, 0x940) => "ARM940",
553        (0x41, 0x946) => "ARM946",
554        (0x41, 0x966) => "ARM966",
555        (0x41, 0xa20) => "ARM1020",
556        (0x41, 0xa22) => "ARM1022",
557        (0x41, 0xa26) => "ARM1026",
558        (0x41, 0xb02) => "ARM11 MPCore",
559        (0x41, 0xb36) => "ARM1136",
560        (0x41, 0xb56) => "ARM1156",
561        (0x41, 0xb76) => "ARM1176",
562        (0x41, 0xc05) => "Cortex-A5",
563        (0x41, 0xc07) => "Cortex-A7",
564        (0x41, 0xc08) => "Cortex-A8",
565        (0x41, 0xc09) => "Cortex-A9",
566        (0x41, 0xc0d) => "Cortex-A17", // Originally A12
567        (0x41, 0xc0f) => "Cortex-A15",
568        (0x41, 0xc0e) => "Cortex-A17",
569        (0x41, 0xc14) => "Cortex-R4",
570        (0x41, 0xc15) => "Cortex-R5",
571        (0x41, 0xc17) => "Cortex-R7",
572        (0x41, 0xc18) => "Cortex-R8",
573        (0x41, 0xc20) => "Cortex-M0",
574        (0x41, 0xc21) => "Cortex-M1",
575        (0x41, 0xc23) => "Cortex-M3",
576        (0x41, 0xc24) => "Cortex-M4",
577        (0x41, 0xc27) => "Cortex-M7",
578        (0x41, 0xc60) => "Cortex-M0+",
579        (0x41, 0xd01) => "Cortex-A32",
580        (0x41, 0xd02) => "Cortex-A34",
581        (0x41, 0xd03) => "Cortex-A53",
582        (0x41, 0xd04) => "Cortex-A35",
583        (0x41, 0xd05) => "Cortex-A55",
584        (0x41, 0xd06) => "Cortex-A65",
585        (0x41, 0xd07) => "Cortex-A57",
586        (0x41, 0xd08) => "Cortex-A72",
587        (0x41, 0xd09) => "Cortex-A73",
588        (0x41, 0xd0a) => "Cortex-A75",
589        (0x41, 0xd0b) => "Cortex-A76",
590        (0x41, 0xd0c) => "Neoverse-N1",
591        (0x41, 0xd0d) => "Cortex-A77",
592        (0x41, 0xd0e) => "Cortex-A76AE",
593        (0x41, 0xd13) => "Cortex-R52",
594        (0x41, 0xd20) => "Cortex-M23",
595        (0x41, 0xd21) => "Cortex-M33",
596        (0x41, 0xd40) => "Neoverse-V1",
597        (0x41, 0xd41) => "Cortex-A78",
598        (0x41, 0xd42) => "Cortex-A78AE",
599        (0x41, 0xd43) => "Cortex-A65AE",
600        (0x41, 0xd44) => "Cortex-X1",
601        (0x41, 0xd46) => "Cortex-A510",
602        (0x41, 0xd47) => "Cortex-A710",
603        (0x41, 0xd48) => "Cortex-X2",
604        (0x41, 0xd49) => "Neoverse-N2",
605        (0x41, 0xd4a) => "Neoverse-E1",
606        (0x41, 0xd4b) => "Cortex-A78C",
607        (0x41, 0xd4c) => "Cortex-X1C",
608        (0x41, 0xd4d) => "Cortex-A715",
609        (0x41, 0xd4e) => "Cortex-X3",
610
611        // Broadcom
612        (0x42, 0x00f) => "Brahma-B15",
613        (0x42, 0x100) => "Brahma-B53",
614        (0x42, 0x516) => "ThunderX2",
615
616        // Cavium
617        (0x43, 0x0a0) => "ThunderX",
618        (0x43, 0x0a1) => "ThunderX-88XX",
619        (0x43, 0x0a2) => "ThunderX-81XX",
620        (0x43, 0x0a3) => "ThunderX-83XX",
621        (0x43, 0x0af) => "ThunderX2-99xx",
622
623        // DEC
624        (0x44, 0xa10) => "SA110",
625        (0x44, 0xa11) => "SA1100",
626
627        // Fujitsu
628        (0x46, 0x001) => "A64FX",
629
630        // HiSilicon
631        (0x48, 0xd01) => "Kunpeng-920", // aka tsv110
632
633        // NVIDIA
634        (0x4e, 0x000) => "Denver",
635        (0x4e, 0x003) => "Denver 2",
636        (0x4e, 0x004) => "Carmel",
637
638        // APM
639        (0x50, 0x000) => "X-Gene",
640
641        // Qualcomm
642        (0x51, 0x00f) => "Scorpion",
643        (0x51, 0x02d) => "Scorpion",
644        (0x51, 0x04d) => "Krait",
645        (0x51, 0x06f) => "Krait",
646        (0x51, 0x201) => "Kryo",
647        (0x51, 0x205) => "Kryo",
648        (0x51, 0x211) => "Kryo",
649        (0x51, 0x800) => "Falkor-V1/Kryo",
650        (0x51, 0x801) => "Kryo-V2",
651        (0x51, 0x802) => "Kryo-3XX-Gold",
652        (0x51, 0x803) => "Kryo-3XX-Silver",
653        (0x51, 0x804) => "Kryo-4XX-Gold",
654        (0x51, 0x805) => "Kryo-4XX-Silver",
655        (0x51, 0xc00) => "Falkor",
656        (0x51, 0xc01) => "Saphira",
657
658        // Samsung
659        (0x53, 0x001) => "exynos-m1",
660
661        // Marvell
662        (0x56, 0x131) => "Feroceon-88FR131",
663        (0x56, 0x581) => "PJ4/PJ4b",
664        (0x56, 0x584) => "PJ4B-MP",
665
666        // Apple
667        (0x61, 0x020) => "Icestorm-A14",
668        (0x61, 0x021) => "Firestorm-A14",
669        (0x61, 0x022) => "Icestorm-M1",
670        (0x61, 0x023) => "Firestorm-M1",
671        (0x61, 0x024) => "Icestorm-M1-Pro",
672        (0x61, 0x025) => "Firestorm-M1-Pro",
673        (0x61, 0x028) => "Icestorm-M1-Max",
674        (0x61, 0x029) => "Firestorm-M1-Max",
675        (0x61, 0x030) => "Blizzard-A15",
676        (0x61, 0x031) => "Avalanche-A15",
677        (0x61, 0x032) => "Blizzard-M2",
678        (0x61, 0x033) => "Avalanche-M2",
679
680        // Faraday
681        (0x66, 0x526) => "FA526",
682        (0x66, 0x626) => "FA626",
683
684        // Intel
685        (0x69, 0x200) => "i80200",
686        (0x69, 0x210) => "PXA250A",
687        (0x69, 0x212) => "PXA210A",
688        (0x69, 0x242) => "i80321-400",
689        (0x69, 0x243) => "i80321-600",
690        (0x69, 0x290) => "PXA250B/PXA26x",
691        (0x69, 0x292) => "PXA210B",
692        (0x69, 0x2c2) => "i80321-400-B0",
693        (0x69, 0x2c3) => "i80321-600-B0",
694        (0x69, 0x2d0) => "PXA250C/PXA255/PXA26x",
695        (0x69, 0x2d2) => "PXA210C",
696        (0x69, 0x411) => "PXA27x",
697        (0x69, 0x41c) => "IPX425-533",
698        (0x69, 0x41d) => "IPX425-400",
699        (0x69, 0x41f) => "IPX425-266",
700        (0x69, 0x682) => "PXA32x",
701        (0x69, 0x683) => "PXA930/PXA935",
702        (0x69, 0x688) => "PXA30x",
703        (0x69, 0x689) => "PXA31x",
704        (0x69, 0xb11) => "SA1110",
705        (0x69, 0xc12) => "IPX1200",
706
707        // Phytium
708        (0x70, 0x660) => "FTC660",
709        (0x70, 0x661) => "FTC661",
710        (0x70, 0x662) => "FTC662",
711        (0x70, 0x663) => "FTC663",
712
713        _ => return None,
714    })
715}
716
717/// Returns the brand/vendor string for the first CPU (which should be the same for all CPUs).
718pub(crate) fn get_vendor_id_and_brand() -> HashMap<usize, (String, String)> {
719    let mut s = String::new();
720    if File::open("/proc/cpuinfo")
721        .and_then(|mut f| f.read_to_string(&mut s))
722        .is_err()
723    {
724        return HashMap::new();
725    }
726
727    fn get_value(s: &str) -> String {
728        s.split(':')
729            .last()
730            .map(|x| x.trim().to_owned())
731            .unwrap_or_default()
732    }
733
734    fn get_hex_value(s: &str) -> u32 {
735        s.split(':')
736            .last()
737            .map(|x| x.trim())
738            .filter(|x| x.starts_with("0x"))
739            .map(|x| u32::from_str_radix(&x[2..], 16).unwrap())
740            .unwrap_or_default()
741    }
742
743    #[inline]
744    fn is_new_processor(line: &str) -> bool {
745        line.starts_with("processor\t")
746    }
747
748    #[derive(Default)]
749    struct CpuInfo {
750        index: usize,
751        vendor_id: Option<String>,
752        brand: Option<String>,
753        implementer: Option<u32>,
754        part: Option<u32>,
755    }
756
757    impl CpuInfo {
758        fn has_all_info(&self) -> bool {
759            (self.brand.is_some() && self.vendor_id.is_some())
760                || (self.implementer.is_some() && self.part.is_some())
761        }
762
763        fn convert(mut self) -> (usize, String, String) {
764            let (vendor_id, brand) = if let (Some(implementer), Some(part)) =
765                (self.implementer.take(), self.part.take())
766            {
767                let vendor_id = get_arm_implementer(implementer).map(String::from);
768                // It's possible to "model name" even with an ARM CPU, so just in case we can't retrieve
769                // the brand from "CPU part", we will then use the value from "model name".
770                //
771                // Example from raspberry pi 3B+:
772                //
773                // ```
774                // model name      : ARMv7 Processor rev 4 (v7l)
775                // CPU implementer : 0x41
776                // CPU part        : 0xd03
777                // ```
778                let brand = get_arm_part(implementer, part)
779                    .map(String::from)
780                    .or_else(|| self.brand.take());
781                (vendor_id, brand)
782            } else {
783                (self.vendor_id.take(), self.brand.take())
784            };
785            (
786                self.index,
787                vendor_id.unwrap_or_default(),
788                brand.unwrap_or_default(),
789            )
790        }
791    }
792
793    let mut cpus: HashMap<usize, (String, String)> = HashMap::new();
794    let mut lines = s.split('\n');
795    while let Some(line) = lines.next() {
796        if is_new_processor(line) {
797            let index = match line
798                .split(':')
799                .nth(1)
800                .and_then(|i| i.trim().parse::<usize>().ok())
801            {
802                Some(index) => index,
803                None => {
804                    sysinfo_debug!("Couldn't get processor ID from {line:?}, ignoring this core");
805                    continue;
806                }
807            };
808
809            let mut info = CpuInfo {
810                index,
811                ..Default::default()
812            };
813
814            #[allow(clippy::while_let_on_iterator)]
815            while let Some(line) = lines.next() {
816                if line.starts_with("vendor_id\t") {
817                    info.vendor_id = Some(get_value(line));
818                } else if line.starts_with("model name\t") {
819                    info.brand = Some(get_value(line));
820                } else if line.starts_with("CPU implementer\t") {
821                    info.implementer = Some(get_hex_value(line));
822                } else if line.starts_with("CPU part\t") {
823                    info.part = Some(get_hex_value(line));
824                } else if info.has_all_info() || is_new_processor(line) {
825                    break;
826                }
827            }
828            let (index, vendor_id, brand) = info.convert();
829            cpus.insert(index, (vendor_id, brand));
830        }
831    }
832    cpus
833}