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.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(); // we don't want the name again
125                            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                                // A new CPU was added, so let's ignore it. If they want it into
140                                // the list, they need to use `refresh_cpu_list`.
141                                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            // This function is voluntarily made generic in case we want to generalize it.
162            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            // `get_cpu_frequency` is very slow, so better run it in parallel.
177            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/// Struct containing values to compute a CPU usage.
199#[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    /// Sets the given argument to the corresponding fields.
215    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        // `guest` is already accounted in `user`.
229        self.user = user.saturating_sub(guest);
230        // `guest_nice` is already accounted in `nice`.
231        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    /// Returns work time.
243    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    /// Returns total time.
252    pub fn total_time(&self) -> u64 {
253        self.work_time()
254            .saturating_add(self.idle)
255            .saturating_add(self.iowait)
256            // `steal`, `guest` and `guest_nice` are only used if we want to account the "guest"
257            // into the computation.
258            .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.; // to prevent the percentage to go above 100%
332        }
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    /// Returns the CPU frequency in MHz.
403    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                // On systems with only physical cores like raspberry, there is no "core id" or
466                // "physical id" fields. So if one of them is missing, we simply use the "CPU"
467                // info and count it as a physical core.
468                $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
509/// Obtain the implementer of this CPU core.
510///
511/// This has been obtained from util-linux's lscpu implementation, see
512/// https://github.com/util-linux/util-linux/blob/7076703b529d255600631306419cca1b48ab850a/sys-utils/lscpu-arm.c#L240
513///
514/// This list will have to be updated every time a new vendor appears, please keep it synchronized
515/// with util-linux and update the link above with the commit you have used.
516fn 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
540/// Obtain the part of this CPU core.
541///
542/// This has been obtained from util-linux's lscpu implementation, see
543/// https://github.com/util-linux/util-linux/blob/eb788e20b82d0e1001a30867c71c8bfb2bb86819/sys-utils/lscpu-arm.c#L25
544///
545/// This list will have to be updated every time a new core appears, please keep it synchronized
546/// with util-linux and update the link above with the commit you have used.
547fn get_arm_part(implementer: u32, part: u32) -> Option<&'static str> {
548    Some(match (implementer, part) {
549        // ARM
550        (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", // Originally A12
569        (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        // Broadcom
626        (0x42, 0x00f) => "Brahma-B15",
627        (0x42, 0x100) => "Brahma-B53",
628        (0x42, 0x516) => "ThunderX2",
629
630        // Cavium
631        (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        // DEC
638        (0x44, 0xa10) => "SA110",
639        (0x44, 0xa11) => "SA1100",
640
641        // Fujitsu
642        (0x46, 0x001) => "A64FX",
643
644        // HiSilicon
645        (0x48, 0xd01) => "Kunpeng-920", // aka tsv110
646
647        // NVIDIA
648        (0x4e, 0x000) => "Denver",
649        (0x4e, 0x003) => "Denver 2",
650        (0x4e, 0x004) => "Carmel",
651
652        // APM
653        (0x50, 0x000) => "X-Gene",
654
655        // Qualcomm
656        (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        // Samsung
673        (0x53, 0x001) => "exynos-m1",
674
675        // Marvell
676        (0x56, 0x131) => "Feroceon-88FR131",
677        (0x56, 0x581) => "PJ4/PJ4b",
678        (0x56, 0x584) => "PJ4B-MP",
679
680        // Apple
681        (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        // Faraday
695        (0x66, 0x526) => "FA526",
696        (0x66, 0x626) => "FA626",
697
698        // Intel
699        (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        // Phytium
722        (0x70, 0x660) => "FTC660",
723        (0x70, 0x661) => "FTC661",
724        (0x70, 0x662) => "FTC662",
725        (0x70, 0x663) => "FTC663",
726
727        _ => return None,
728    })
729}
730
731/// Returns the brand/vendor string for the first CPU (which should be the same for all CPUs).
732pub(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                // It's possible to "model name" even with an ARM CPU, so just in case we can't retrieve
792                // the brand from "CPU part", we will then use the value from "model name".
793                //
794                // Example from raspberry pi 3B+:
795                //
796                // ```
797                // model name      : ARMv7 Processor rev 4 (v7l)
798                // CPU implementer : 0x41
799                // CPU part        : 0xd03
800                // ```
801                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    // The iterator was skipping the `is_new_processor` check because we already moved past
864    // the line where `processor]\t` is located.
865    //
866    // Regression test for <https://github.com/GuillaumeGomez/sysinfo/issues/1527>.
867    #[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}