ferrix_lib/
cpu.rs

1/* cpu.rs
2 *
3 * Copyright 2025 Michail Krasnov <mskrasnov07@ya.ru>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21//! Get information about installed CPUs
22//!
23//! Reads information from `/proc/cpuinfo` file
24//!
25//! ## Example
26//! ```
27//! use ferrix_lib::cpu::Processors;
28//! use ferrix_lib::traits::ToJson;
29//!
30//! let proc = Processors::new().unwrap();
31//! let json = proc.to_json().unwrap();
32//! dbg!(json);
33//! ```
34
35use anyhow::Result;
36use serde::Serialize;
37use std::fs::read_to_string;
38
39use crate::traits::{ToJson, ToPlainText, print_opt_val};
40use crate::utils::Size;
41
42/// A structure containing data from the `/proc/cpuinfo` file
43#[derive(Debug, Serialize, Clone)]
44pub struct Processors {
45    /// Information about all core/thread
46    pub entries: Vec<CPU>,
47}
48
49impl Processors {
50    pub fn new() -> Result<Self> {
51        Ok(Self {
52            entries: read_info()?,
53        })
54    }
55}
56
57impl ToJson for Processors {}
58impl ToPlainText for Processors {
59    fn to_plain(&self) -> String {
60        let mut s = format!("Information about processors");
61        for proc in &self.entries {
62            s += &proc.to_plain();
63        }
64        s
65    }
66}
67
68/// A structure with data about each processor core/thread
69#[derive(Debug, Serialize, Default, Clone)]
70pub struct CPU {
71    /// Entry number (index)
72    pub processor: Option<usize>,
73
74    /************************ NOTE ***********************
75     *   Parameters for x86 and x86_64 architectures     *
76     *****************************************************/
77    /// Vendor name
78    pub vendor_id: Option<String>,
79
80    /// CPU Family ID
81    pub cpu_family: Option<u32>,
82
83    /// Model ID
84    pub model: Option<u32>,
85
86    /// Model name
87    pub model_name: Option<String>,
88
89    /// Stepping
90    pub stepping: Option<u32>,
91
92    /// Microcode number (representation as a `String`!)
93    pub microcode: Option<String>,
94
95    /// CPU core/thread *current* frequency
96    pub cpu_mhz: Option<f32>,
97
98    /// L3 cache size
99    pub cache_size: Option<Size>,
100
101    /// Physical ID of CPU core/thread
102    pub physical_id: Option<u32>,
103
104    /// Siblings
105    pub siblings: Option<u32>,
106
107    /// Core ID
108    pub core_id: Option<u32>,
109
110    /// CPU cores count
111    pub cpu_cores: Option<u32>,
112
113    /// APIC ID
114    pub apicid: Option<u32>,
115
116    /// Initial APIC ID
117    pub initial_apicid: Option<u32>,
118
119    /// Is FPU exists?
120    pub fpu: Option<bool>,
121
122    pub fpu_exception: Option<bool>,
123    pub cpuid_level: Option<u32>,
124    pub wp: Option<bool>,
125    pub flags: Option<Vec<String>>,
126    pub bugs: Option<Vec<String>>,
127    pub bogomips: Option<f64>,
128    pub clflush_size: Option<u32>,
129    pub cache_alignment: Option<u32>,
130    pub address_sizes: Option<String>,
131    pub power_management: Option<String>,
132
133    /************************ NOTE ***********************
134     *    Parameters for AArch64 (ARMv8) architecture    *
135     *****************************************************/
136    pub cpu_implementer: Option<String>,
137    pub cpu_architecture: Option<u8>,
138    pub cpu_variant: Option<String>,
139    pub cpu_part: Option<String>,
140    pub cpu_revision: Option<u32>,
141
142    /************************ NOTE ***********************
143     *   Parameters for ppc64le (PowerPC) architecture   *
144     *****************************************************/
145    pub cpu: Option<String>,
146    pub clock: Option<f32>,
147    pub revision: Option<String>,
148    pub timebase: Option<usize>,
149    pub platform: Option<String>,
150    pub machine: Option<String>,
151    pub model_ppc: Option<String>,
152}
153
154impl ToJson for CPU {}
155
156#[cfg(target_arch = "x86_64")]
157impl ToPlainText for CPU {
158    fn to_plain(&self) -> String {
159        let mut s = match self.processor { Some(proc) => format!("\nProcessor #{proc}\n"),
160            None => format!("\nProcessor #unknown\n"),
161        };
162        s += "\tArchitecture: x86_64\n";
163        s += &print_opt_val("Vendor ID", &self.vendor_id);
164        s += &print_opt_val("CPU Family", &self.cpu_family);
165        s += &print_opt_val("CPU Model ID", &self.model);
166        s += &print_opt_val("CPU Model Name", &self.model_name);
167        s += &print_opt_val("Stepping", &self.stepping);
168        s += &print_opt_val("Microcode", &self.microcode);
169        s += &print_opt_val("Current frequency", &self.cpu_mhz);
170        s += &print_opt_val("L3 Cache Size", &self.cache_size);
171        s += &print_opt_val("Physical ID of CPU Core", &self.physical_id);
172        s += &print_opt_val("Siblings", &self.siblings);
173        s += &print_opt_val("Core ID", &self.core_id);
174        s += &print_opt_val("CPU cores", &self.cpu_cores);
175        s += &print_opt_val("APIC ID", &self.apicid);
176        s += &print_opt_val("Initial APIC ID", &self.initial_apicid);
177        s += &print_opt_val("FPU", &self.fpu);
178        s += &print_opt_val("FPU Exception", &self.fpu_exception);
179        s += &print_opt_val("CPUID Level", &self.cpuid_level);
180        s += &print_opt_val("WP", &self.wp);
181        s += &print_opt_val("Bogo MIPS", &self.bogomips);
182        s += &print_opt_val("Clflush Size", &self.clflush_size);
183        s += &print_opt_val("Cache alignment", &self.cache_alignment);
184        s += &print_opt_val("Address sizes", &self.address_sizes);
185        s += &print_opt_val("Power management", &self.power_management);
186
187        s
188    }
189}
190
191#[cfg(target_arch = "aarch64")]
192impl ToPlainText for CPU {
193    fn convert(&self) -> String {
194        let mut s = match self.processor {
195            Some(proc) => format!("Processor #{proc}\n"),
196            None => format!("Processor #unknown\n"),
197        };
198        s += &print_opt_val("CPU Implementer", &self.cpu_implementer);
199        s += &print_opt_val("CPU Architecture", &self.cpu_architecture);
200        s += &print_opt_val("CPU Variant", &self.cpu_variant);
201        s += &print_opt_val("CPU Part", &self.cpu_part);
202        s += &print_opt_val("CPU Revision", &self.cpu_revision);
203
204        s
205    }
206}
207
208fn read_info() -> Result<Vec<CPU>> {
209    let blocks = read_to_string("/proc/cpuinfo")?;
210    let blocks = blocks
211        .split("\n\n") // split by CPU blocks
212        .collect::<Vec<_>>();
213    let mut processors = Vec::with_capacity(blocks.len());
214
215    for block in blocks {
216        if block.trim().is_empty() {
217            continue;
218        }
219        let mut cpu = CPU::default();
220        for line in block.lines() {
221            parse_cpuinfo(&mut cpu, line);
222        }
223        processors.push(cpu);
224    }
225    Ok(processors)
226}
227
228fn get_parts(s: &str) -> impl Iterator<Item = &str> {
229    s.splitn(2, ':').map(|item| item.trim())
230}
231
232#[cfg(target_arch = "x86_64")]
233fn parse_cpuinfo(cpu: &mut CPU, parts: &str) {
234    let mut parts = get_parts(parts);
235    match (parts.next(), parts.next()) {
236        (Some(key), Some(val)) => match key {
237            "processor" => cpu.processor = val.parse().ok(),
238            "vendor_id" => cpu.vendor_id = Some(val.to_string()),
239            "cpu family" => cpu.cpu_family = val.parse().ok(),
240            "model" => cpu.model = val.parse().ok(),
241            "model name" => cpu.model_name = Some(val.to_string()),
242            "stepping" => cpu.stepping = val.parse().ok(),
243            "microcode" => cpu.microcode = Some(val.to_string()),
244            "cpu MHz" => cpu.cpu_mhz = val.parse().ok(),
245            "cache size" => cpu.cache_size = Size::try_from(val).ok(),
246            "physical id" => cpu.physical_id = val.parse().ok(),
247            "siblings" => cpu.siblings = val.parse().ok(),
248            "core id" => cpu.core_id = val.parse().ok(),
249            "cpu cores" => cpu.cpu_cores = val.parse().ok(),
250            "apicid" => cpu.apicid = val.parse().ok(),
251            "initial apicid" => cpu.initial_apicid = val.parse().ok(),
252            "fpu" => cpu.fpu = Some(get_bool(val)),
253            "fpu_exception" => cpu.fpu_exception = Some(get_bool(val)),
254            "cpuid level" => cpu.cpuid_level = val.parse().ok(),
255            "wp" => cpu.wp = Some(get_bool(val)),
256            "flags" | "Features" => {
257                cpu.flags = Some(val.split_whitespace().map(String::from).collect())
258            }
259            "bugs" => cpu.bugs = Some(val.split_whitespace().map(String::from).collect()),
260            "bogomips" | "BogoMIPS" => cpu.bogomips = val.parse().ok(),
261            "clflush size" => cpu.clflush_size = val.parse().ok(),
262            "cache_alignment" => cpu.cache_alignment = val.parse().ok(),
263            "address sizes" => cpu.address_sizes = Some(val.to_string()),
264            "power management" => cpu.power_management = Some(val.to_string()),
265            _ => {} // ignore unknown entry
266        },
267        _ => {}
268    }
269}
270
271#[cfg(target_arch = "aarch64")]
272fn parse_cpuinfo(cpu: &mut CPU, parts: &str) {
273    let mut parts = get_parts(parts);
274    match (parts.next(), parts.next()) {
275        (Some(key), Some(val)) => match key {
276            // ARM
277            "CPU implementer" => cpu.cpu_implementer = Some(val.to_string()),
278            "CPU architecture" => cpu.cpu_architecture = val.parse().ok(),
279            "CPU variant" => cpu.cpu_variant = Some(val.to_string()),
280            "CPU part" => cpu.cpu_part = Some(val.to_string()),
281            "CPU revision" => cpu.cpu_revision = val.parse().ok(),
282            _ => {} // ignore unknown entry
283        },
284        _ => {}
285    }
286}
287
288fn get_bool(s: &str) -> bool {
289    match s {
290        "yes" | "ok" => true,
291        _ => false,
292    }
293}