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    #[inline]
243    pub fn work_time(&self) -> u64 {
244        self.user.saturating_add(self.nice)
245    }
246
247    #[inline]
248    pub fn system_time(&self) -> u64 {
249        self.system
250            .saturating_add(self.irq)
251            .saturating_add(self.softirq)
252    }
253
254    #[inline]
255    pub fn idle_time(&self) -> u64 {
256        self.idle.saturating_add(self.iowait)
257    }
258
259    #[inline]
260    pub fn virtual_time(&self) -> u64 {
261        self.guest.saturating_add(self.guest_nice)
262    }
263
264    #[inline]
265    pub fn total_time(&self) -> u64 {
266        self.work_time()
267            .saturating_add(self.system_time())
268            .saturating_add(self.idle_time())
269            .saturating_add(self.virtual_time())
270            .saturating_add(self.steal)
271    }
272}
273
274#[derive(Default)]
275pub(crate) struct CpuUsage {
276    percent: f32,
277    old_values: CpuValues,
278    new_values: CpuValues,
279    total_time: u64,
280    old_total_time: u64,
281}
282
283impl CpuUsage {
284    pub(crate) fn new_with_values(
285        user: u64,
286        nice: u64,
287        system: u64,
288        idle: u64,
289        iowait: u64,
290        irq: u64,
291        softirq: u64,
292        steal: u64,
293        guest: u64,
294        guest_nice: u64,
295    ) -> Self {
296        let mut new_values = CpuValues::default();
297        new_values.set(
298            user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
299        );
300        Self {
301            old_values: CpuValues::default(),
302            new_values,
303            percent: 0f32,
304            total_time: 0,
305            old_total_time: 0,
306        }
307    }
308
309    pub(crate) fn set(
310        &mut self,
311        user: u64,
312        nice: u64,
313        system: u64,
314        idle: u64,
315        iowait: u64,
316        irq: u64,
317        softirq: u64,
318        steal: u64,
319        guest: u64,
320        guest_nice: u64,
321    ) {
322        macro_rules! min {
323            ($a:expr, $b:expr, $def:expr) => {
324                if $a > $b {
325                    ($a - $b) as f32
326                } else {
327                    $def
328                }
329            };
330        }
331
332        self.old_values = self.new_values;
333        self.new_values.set(
334            user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
335        );
336
337        self.total_time = self.new_values.total_time();
338        self.old_total_time = self.old_values.total_time();
339
340        let nice_period = self.new_values.nice.saturating_sub(self.old_values.nice);
341        let user_period = self.new_values.user.saturating_sub(self.old_values.user);
342        let steal_period = self.new_values.steal.saturating_sub(self.old_values.steal);
343        let guest_period = self
344            .new_values
345            .virtual_time()
346            .saturating_sub(self.old_values.virtual_time());
347        let system_period = self
348            .new_values
349            .system_time()
350            .saturating_sub(self.old_values.system_time());
351
352        let total = min!(self.total_time, self.old_total_time, 1.);
353        let nice = nice_period as f32 / total;
354        let user = user_period as f32 / total;
355        let system = system_period as f32 / total;
356        let irq = (steal_period + guest_period) as f32 / total;
357
358        self.percent = (nice + user + system + irq) * 100.;
359        if self.percent > 100. {
360            self.percent = 100.; // to prevent the percentage to go above 100%
361        }
362    }
363
364    pub(crate) fn usage(&self) -> f32 {
365        self.percent
366    }
367}
368
369pub(crate) struct CpuInner {
370    usage: CpuUsage,
371    pub(crate) name: String,
372    pub(crate) frequency: u64,
373    pub(crate) vendor_id: String,
374    pub(crate) brand: String,
375}
376
377impl CpuInner {
378    pub(crate) fn new_with_values(
379        name: &str,
380        user: u64,
381        nice: u64,
382        system: u64,
383        idle: u64,
384        iowait: u64,
385        irq: u64,
386        softirq: u64,
387        steal: u64,
388        guest: u64,
389        guest_nice: u64,
390        frequency: u64,
391        vendor_id: String,
392        brand: String,
393    ) -> Self {
394        Self {
395            usage: CpuUsage::new_with_values(
396                user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
397            ),
398            name: name.to_owned(),
399            frequency,
400            vendor_id,
401            brand,
402        }
403    }
404
405    pub(crate) fn set(
406        &mut self,
407        user: u64,
408        nice: u64,
409        system: u64,
410        idle: u64,
411        iowait: u64,
412        irq: u64,
413        softirq: u64,
414        steal: u64,
415        guest: u64,
416        guest_nice: u64,
417    ) {
418        self.usage.set(
419            user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
420        );
421    }
422
423    pub(crate) fn cpu_usage(&self) -> f32 {
424        self.usage.percent
425    }
426
427    pub(crate) fn name(&self) -> &str {
428        &self.name
429    }
430
431    /// Returns the CPU frequency in MHz.
432    pub(crate) fn frequency(&self) -> u64 {
433        self.frequency
434    }
435
436    pub(crate) fn vendor_id(&self) -> &str {
437        &self.vendor_id
438    }
439
440    pub(crate) fn brand(&self) -> &str {
441        &self.brand
442    }
443}
444
445pub(crate) fn get_cpu_frequency(cpu_core_index: usize) -> u64 {
446    let mut s = String::new();
447    if File::open(format!(
448        "/sys/devices/system/cpu/cpu{cpu_core_index}/cpufreq/scaling_cur_freq",
449    ))
450    .and_then(|mut f| f.read_to_string(&mut s))
451    .is_ok()
452    {
453        let freq_option = s.trim().split('\n').next();
454        if let Some(freq_string) = freq_option {
455            if let Ok(freq) = freq_string.parse::<u64>() {
456                return freq / 1000;
457            }
458        }
459    }
460    s.clear();
461    if File::open("/proc/cpuinfo")
462        .and_then(|mut f| f.read_to_string(&mut s))
463        .is_err()
464    {
465        return 0;
466    }
467    let find_cpu_mhz = s.split('\n').find(|line| {
468        cpuinfo_is_key(line, b"cpu MHz\t")
469            || cpuinfo_is_key(line, b"CPU MHz\t")
470            || cpuinfo_is_key(line, b"BogoMIPS")
471            || cpuinfo_is_key(line, b"clock\t")
472            || cpuinfo_is_key(line, b"bogomips per cpu")
473    });
474    find_cpu_mhz
475        .and_then(|line| line.split(':').next_back())
476        .and_then(|val| val.replace("MHz", "").trim().parse::<f64>().ok())
477        .map(|speed| speed as u64)
478        .unwrap_or_default()
479}
480
481#[allow(unused_assignments)]
482pub(crate) fn get_physical_core_count() -> Option<usize> {
483    let mut s = String::new();
484    if let Err(_e) = File::open("/proc/cpuinfo").and_then(|mut f| f.read_to_string(&mut s)) {
485        sysinfo_debug!("Cannot read `/proc/cpuinfo` file: {:?}", _e);
486        return None;
487    }
488
489    macro_rules! add_core {
490        ($core_ids_and_physical_ids:ident, $core_id:ident, $physical_id:ident, $cpu:ident) => {{
491            if !$core_id.is_empty() && !$physical_id.is_empty() {
492                $core_ids_and_physical_ids.insert(format!("{} {}", $core_id, $physical_id));
493            } else if !$cpu.is_empty() {
494                // On systems with only physical cores like raspberry, there is no "core id" or
495                // "physical id" fields. So if one of them is missing, we simply use the "CPU"
496                // info and count it as a physical core.
497                $core_ids_and_physical_ids.insert($cpu.to_owned());
498            }
499            $core_id = "";
500            $physical_id = "";
501            $cpu = "";
502        }};
503    }
504
505    let mut core_ids_and_physical_ids: HashSet<String> = HashSet::new();
506    let mut core_id = "";
507    let mut physical_id = "";
508    let mut cpu = "";
509
510    for line in s.lines() {
511        if line.is_empty() {
512            add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu);
513        } else if line.starts_with("processor") {
514            cpu = line
515                .splitn(2, ':')
516                .last()
517                .map(|x| x.trim())
518                .unwrap_or_default();
519        } else if line.starts_with("core id") {
520            core_id = line
521                .splitn(2, ':')
522                .last()
523                .map(|x| x.trim())
524                .unwrap_or_default();
525        } else if line.starts_with("physical id") {
526            physical_id = line
527                .splitn(2, ':')
528                .last()
529                .map(|x| x.trim())
530                .unwrap_or_default();
531        }
532    }
533    add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu);
534
535    Some(core_ids_and_physical_ids.len())
536}
537
538/// Obtain the implementer 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#L240
542///
543/// This list will have to be updated every time a new vendor appears, please keep it synchronized
544/// with util-linux and update the link above with the commit you have used.
545fn get_arm_implementer(implementer: u32) -> Option<&'static str> {
546    Some(match implementer {
547        0x41 => "ARM",
548        0x42 => "Broadcom",
549        0x43 => "Cavium",
550        0x44 => "DEC",
551        0x46 => "FUJITSU",
552        0x48 => "HiSilicon",
553        0x49 => "Infineon",
554        0x4d => "Motorola/Freescale",
555        0x4e => "NVIDIA",
556        0x50 => "APM",
557        0x51 => "Qualcomm",
558        0x53 => "Samsung",
559        0x56 => "Marvell",
560        0x61 => "Apple",
561        0x66 => "Faraday",
562        0x69 => "Intel",
563        0x70 => "Phytium",
564        0xc0 => "Ampere",
565        _ => return None,
566    })
567}
568
569/// Obtain the part of this CPU core.
570///
571/// This has been obtained from util-linux's lscpu implementation, see
572/// https://github.com/util-linux/util-linux/blob/eb788e20b82d0e1001a30867c71c8bfb2bb86819/sys-utils/lscpu-arm.c#L25
573///
574/// This list will have to be updated every time a new core appears, please keep it synchronized
575/// with util-linux and update the link above with the commit you have used.
576fn get_arm_part(implementer: u32, part: u32) -> Option<&'static str> {
577    Some(match (implementer, part) {
578        // ARM
579        (0x41, 0x810) => "ARM810",
580        (0x41, 0x920) => "ARM920",
581        (0x41, 0x922) => "ARM922",
582        (0x41, 0x926) => "ARM926",
583        (0x41, 0x940) => "ARM940",
584        (0x41, 0x946) => "ARM946",
585        (0x41, 0x966) => "ARM966",
586        (0x41, 0xa20) => "ARM1020",
587        (0x41, 0xa22) => "ARM1022",
588        (0x41, 0xa26) => "ARM1026",
589        (0x41, 0xb02) => "ARM11 MPCore",
590        (0x41, 0xb36) => "ARM1136",
591        (0x41, 0xb56) => "ARM1156",
592        (0x41, 0xb76) => "ARM1176",
593        (0x41, 0xc05) => "Cortex-A5",
594        (0x41, 0xc07) => "Cortex-A7",
595        (0x41, 0xc08) => "Cortex-A8",
596        (0x41, 0xc09) => "Cortex-A9",
597        (0x41, 0xc0d) => "Cortex-A17", // Originally A12
598        (0x41, 0xc0f) => "Cortex-A15",
599        (0x41, 0xc0e) => "Cortex-A17",
600        (0x41, 0xc14) => "Cortex-R4",
601        (0x41, 0xc15) => "Cortex-R5",
602        (0x41, 0xc17) => "Cortex-R7",
603        (0x41, 0xc18) => "Cortex-R8",
604        (0x41, 0xc20) => "Cortex-M0",
605        (0x41, 0xc21) => "Cortex-M1",
606        (0x41, 0xc23) => "Cortex-M3",
607        (0x41, 0xc24) => "Cortex-M4",
608        (0x41, 0xc27) => "Cortex-M7",
609        (0x41, 0xc60) => "Cortex-M0+",
610        (0x41, 0xd01) => "Cortex-A32",
611        (0x41, 0xd02) => "Cortex-A34",
612        (0x41, 0xd03) => "Cortex-A53",
613        (0x41, 0xd04) => "Cortex-A35",
614        (0x41, 0xd05) => "Cortex-A55",
615        (0x41, 0xd06) => "Cortex-A65",
616        (0x41, 0xd07) => "Cortex-A57",
617        (0x41, 0xd08) => "Cortex-A72",
618        (0x41, 0xd09) => "Cortex-A73",
619        (0x41, 0xd0a) => "Cortex-A75",
620        (0x41, 0xd0b) => "Cortex-A76",
621        (0x41, 0xd0c) => "Neoverse-N1",
622        (0x41, 0xd0d) => "Cortex-A77",
623        (0x41, 0xd0e) => "Cortex-A76AE",
624        (0x41, 0xd13) => "Cortex-R52",
625        (0x41, 0xd15) => "Cortex-R82",
626        (0x41, 0xd16) => "Cortex-R52+",
627        (0x41, 0xd20) => "Cortex-M23",
628        (0x41, 0xd21) => "Cortex-M33",
629        (0x41, 0xd22) => "Cortex-R55",
630        (0x41, 0xd23) => "Cortex-R85",
631        (0x41, 0xd40) => "Neoverse-V1",
632        (0x41, 0xd41) => "Cortex-A78",
633        (0x41, 0xd42) => "Cortex-A78AE",
634        (0x41, 0xd43) => "Cortex-A65AE",
635        (0x41, 0xd44) => "Cortex-X1",
636        (0x41, 0xd46) => "Cortex-A510",
637        (0x41, 0xd47) => "Cortex-A710",
638        (0x41, 0xd48) => "Cortex-X2",
639        (0x41, 0xd49) => "Neoverse-N2",
640        (0x41, 0xd4a) => "Neoverse-E1",
641        (0x41, 0xd4b) => "Cortex-A78C",
642        (0x41, 0xd4c) => "Cortex-X1C",
643        (0x41, 0xd4d) => "Cortex-A715",
644        (0x41, 0xd4e) => "Cortex-X3",
645        (0x41, 0xd4f) => "Neoverse-V2",
646        (0x41, 0xd80) => "Cortex-A520",
647        (0x41, 0xd81) => "Cortex-A720",
648        (0x41, 0xd82) => "Cortex-X4",
649        (0x41, 0xd84) => "Neoverse-V3",
650        (0x41, 0xd85) => "Cortex-X925",
651        (0x41, 0xd87) => "Cortex-A725",
652        (0x41, 0xd8e) => "Neoverse-N3",
653
654        // Broadcom
655        (0x42, 0x00f) => "Brahma-B15",
656        (0x42, 0x100) => "Brahma-B53",
657        (0x42, 0x516) => "ThunderX2",
658
659        // Cavium
660        (0x43, 0x0a0) => "ThunderX",
661        (0x43, 0x0a1) => "ThunderX-88XX",
662        (0x43, 0x0a2) => "ThunderX-81XX",
663        (0x43, 0x0a3) => "ThunderX-83XX",
664        (0x43, 0x0af) => "ThunderX2-99xx",
665
666        // DEC
667        (0x44, 0xa10) => "SA110",
668        (0x44, 0xa11) => "SA1100",
669
670        // Fujitsu
671        (0x46, 0x001) => "A64FX",
672
673        // HiSilicon
674        (0x48, 0xd01) => "Kunpeng-920", // aka tsv110
675
676        // NVIDIA
677        (0x4e, 0x000) => "Denver",
678        (0x4e, 0x003) => "Denver 2",
679        (0x4e, 0x004) => "Carmel",
680
681        // APM
682        (0x50, 0x000) => "X-Gene",
683
684        // Qualcomm
685        (0x51, 0x00f) => "Scorpion",
686        (0x51, 0x02d) => "Scorpion",
687        (0x51, 0x04d) => "Krait",
688        (0x51, 0x06f) => "Krait",
689        (0x51, 0x201) => "Kryo",
690        (0x51, 0x205) => "Kryo",
691        (0x51, 0x211) => "Kryo",
692        (0x51, 0x800) => "Falkor-V1/Kryo",
693        (0x51, 0x801) => "Kryo-V2",
694        (0x51, 0x802) => "Kryo-3XX-Gold",
695        (0x51, 0x803) => "Kryo-3XX-Silver",
696        (0x51, 0x804) => "Kryo-4XX-Gold",
697        (0x51, 0x805) => "Kryo-4XX-Silver",
698        (0x51, 0xc00) => "Falkor",
699        (0x51, 0xc01) => "Saphira",
700
701        // Samsung
702        (0x53, 0x001) => "exynos-m1",
703
704        // Marvell
705        (0x56, 0x131) => "Feroceon-88FR131",
706        (0x56, 0x581) => "PJ4/PJ4b",
707        (0x56, 0x584) => "PJ4B-MP",
708
709        // Apple
710        (0x61, 0x020) => "Icestorm-A14",
711        (0x61, 0x021) => "Firestorm-A14",
712        (0x61, 0x022) => "Icestorm-M1",
713        (0x61, 0x023) => "Firestorm-M1",
714        (0x61, 0x024) => "Icestorm-M1-Pro",
715        (0x61, 0x025) => "Firestorm-M1-Pro",
716        (0x61, 0x028) => "Icestorm-M1-Max",
717        (0x61, 0x029) => "Firestorm-M1-Max",
718        (0x61, 0x030) => "Blizzard-A15",
719        (0x61, 0x031) => "Avalanche-A15",
720        (0x61, 0x032) => "Blizzard-M2",
721        (0x61, 0x033) => "Avalanche-M2",
722
723        // Faraday
724        (0x66, 0x526) => "FA526",
725        (0x66, 0x626) => "FA626",
726
727        // Intel
728        (0x69, 0x200) => "i80200",
729        (0x69, 0x210) => "PXA250A",
730        (0x69, 0x212) => "PXA210A",
731        (0x69, 0x242) => "i80321-400",
732        (0x69, 0x243) => "i80321-600",
733        (0x69, 0x290) => "PXA250B/PXA26x",
734        (0x69, 0x292) => "PXA210B",
735        (0x69, 0x2c2) => "i80321-400-B0",
736        (0x69, 0x2c3) => "i80321-600-B0",
737        (0x69, 0x2d0) => "PXA250C/PXA255/PXA26x",
738        (0x69, 0x2d2) => "PXA210C",
739        (0x69, 0x411) => "PXA27x",
740        (0x69, 0x41c) => "IPX425-533",
741        (0x69, 0x41d) => "IPX425-400",
742        (0x69, 0x41f) => "IPX425-266",
743        (0x69, 0x682) => "PXA32x",
744        (0x69, 0x683) => "PXA930/PXA935",
745        (0x69, 0x688) => "PXA30x",
746        (0x69, 0x689) => "PXA31x",
747        (0x69, 0xb11) => "SA1110",
748        (0x69, 0xc12) => "IPX1200",
749
750        // Phytium
751        (0x70, 0x660) => "FTC660",
752        (0x70, 0x661) => "FTC661",
753        (0x70, 0x662) => "FTC662",
754        (0x70, 0x663) => "FTC663",
755
756        _ => return None,
757    })
758}
759
760/// Returns the brand/vendor string for the first CPU (which should be the same for all CPUs).
761pub(crate) fn get_vendor_id_and_brand() -> HashMap<usize, (String, String)> {
762    let mut s = String::new();
763    if File::open("/proc/cpuinfo")
764        .and_then(|mut f| f.read_to_string(&mut s))
765        .is_err()
766    {
767        return HashMap::new();
768    }
769    get_vendor_id_and_brand_inner(&s)
770}
771
772#[inline]
773fn cpuinfo_is_key(line: &str, key: &[u8]) -> bool {
774    let line = line.as_bytes();
775    line.len() > key.len() && line[..key.len()].eq_ignore_ascii_case(key)
776}
777
778fn get_vendor_id_and_brand_inner(data: &str) -> HashMap<usize, (String, String)> {
779    fn get_value(s: &str) -> String {
780        s.split(':')
781            .next_back()
782            .map(|x| x.trim().to_owned())
783            .unwrap_or_default()
784    }
785
786    fn get_hex_value(s: &str) -> u32 {
787        s.split(':')
788            .next_back()
789            .map(|x| x.trim())
790            .filter(|x| x.starts_with("0x"))
791            .map(|x| u32::from_str_radix(&x[2..], 16).unwrap())
792            .unwrap_or_default()
793    }
794
795    #[inline]
796    fn is_new_processor(line: &str) -> bool {
797        line.starts_with("processor\t")
798    }
799
800    #[derive(Default)]
801    struct CpuInfo {
802        index: usize,
803        vendor_id: Option<String>,
804        brand: Option<String>,
805        implementer: Option<u32>,
806        part: Option<u32>,
807    }
808
809    impl CpuInfo {
810        fn has_all_info(&self) -> bool {
811            (self.brand.is_some() && self.vendor_id.is_some())
812                || (self.implementer.is_some() && self.part.is_some())
813        }
814
815        fn convert(mut self) -> (usize, String, String) {
816            let (vendor_id, brand) = if let (Some(implementer), Some(part)) =
817                (self.implementer.take(), self.part.take())
818            {
819                let vendor_id = get_arm_implementer(implementer).map(String::from);
820                // It's possible to "model name" even with an ARM CPU, so just in case we can't retrieve
821                // the brand from "CPU part", we will then use the value from "model name".
822                //
823                // Example from raspberry pi 3B+:
824                //
825                // ```
826                // model name      : ARMv7 Processor rev 4 (v7l)
827                // CPU implementer : 0x41
828                // CPU part        : 0xd03
829                // ```
830                let brand = get_arm_part(implementer, part)
831                    .map(String::from)
832                    .or_else(|| self.brand.take());
833                (vendor_id, brand)
834            } else {
835                (self.vendor_id.take(), self.brand.take())
836            };
837            (
838                self.index,
839                vendor_id.unwrap_or_default(),
840                brand.unwrap_or_default(),
841            )
842        }
843    }
844
845    let mut cpus: HashMap<usize, (String, String)> = HashMap::new();
846    let mut lines = data.split('\n').peekable();
847    while let Some(line) = lines.next() {
848        if is_new_processor(line) {
849            let index = match line
850                .split(':')
851                .nth(1)
852                .and_then(|i| i.trim().parse::<usize>().ok())
853            {
854                Some(index) => index,
855                None => {
856                    sysinfo_debug!("Couldn't get processor ID from {line:?}, ignoring this core");
857                    continue;
858                }
859            };
860
861            let mut info = CpuInfo {
862                index,
863                ..Default::default()
864            };
865
866            #[allow(clippy::while_let_on_iterator)]
867            while let Some(line) = lines.peek() {
868                if cpuinfo_is_key(line, b"vendor_id\t") {
869                    info.vendor_id = Some(get_value(line));
870                } else if cpuinfo_is_key(line, b"model name\t") {
871                    info.brand = Some(get_value(line));
872                } else if cpuinfo_is_key(line, b"CPU implementer\t") {
873                    info.implementer = Some(get_hex_value(line));
874                } else if cpuinfo_is_key(line, b"CPU part\t") {
875                    info.part = Some(get_hex_value(line));
876                } else if info.has_all_info() || is_new_processor(line) {
877                    break;
878                }
879                lines.next();
880            }
881            let (index, vendor_id, brand) = info.convert();
882            cpus.insert(index, (vendor_id, brand));
883        }
884    }
885    cpus
886}
887
888#[cfg(test)]
889mod test {
890    use super::get_vendor_id_and_brand_inner;
891
892    // The iterator was skipping the `is_new_processor` check because we already moved past
893    // the line where `processor]\t` is located.
894    //
895    // Regression test for <https://github.com/GuillaumeGomez/sysinfo/issues/1527>.
896    #[test]
897    fn test_cpu_retrieval() {
898        const DATA: &str = r#"
899processor		: 1
900cpu model		: Loongson-3 V0.4  FPU V0.1
901model name		: Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
902CPU MHz			: 1800.00
903core			: 1
904
905processor		: 2
906cpu model		: Loongson-3 V0.4  FPU V0.1
907model name		: Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
908CPU MHz			: 1800.00
909package			: 0
910core			: 2
911
912processor		: 3
913cpu model		: Loongson-3 V0.4  FPU V0.1
914model name		: Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
915CPU MHz			: 1800.00
916core			: 3
917
918processor		: 4
919cpu model		: Loongson-3 V0.4  FPU V0.1
920model name		: Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
921CPU MHz			: 1800.00
922core			: 0
923
924processor		: 5
925cpu model		: Loongson-3 V0.4  FPU V0.1
926model name		: Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
927CPU MHz			: 1800.00
928core			: 1
929
930processor		: 6
931cpu model		: Loongson-3 V0.4  FPU V0.1
932model name		: Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
933CPU MHz			: 1800.00
934core			: 2
935
936processor		: 7
937cpu model		: Loongson-3 V0.4  FPU V0.1
938model name		: Loongson-3A R4 (Loongson-3B4000) @ 1800MHz
939CPU MHz			: 1800.00
940core			: 3"#;
941
942        let cpus = get_vendor_id_and_brand_inner(DATA);
943        assert_eq!(cpus.len(), 7);
944    }
945}