1use anyhow::Result;
36use serde::{Deserialize, Serialize};
37use std::fs::read_to_string;
38
39use crate::traits::{ToJson, ToPlainText, print_opt_val};
40use crate::utils::Size;
41
42#[derive(Debug, Serialize, Clone)]
44pub struct Processors {
45 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#[derive(Debug, Serialize, Default, Clone)]
70pub struct CPU {
71 pub processor: Option<usize>,
73
74 pub vendor_id: Option<String>,
79
80 pub cpu_family: Option<u32>,
82
83 pub model: Option<u32>,
85
86 pub model_name: Option<String>,
88
89 pub stepping: Option<u32>,
91
92 pub microcode: Option<String>,
94
95 pub cpu_mhz: Option<f32>,
97
98 pub cache_size: Option<Size>,
100
101 pub physical_id: Option<u32>,
103
104 pub siblings: Option<u32>,
106
107 pub core_id: Option<u32>,
109
110 pub cpu_cores: Option<u32>,
112
113 pub apicid: Option<u32>,
115
116 pub initial_apicid: Option<u32>,
118
119 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 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 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 {
160 Some(proc) => format!("\nProcessor #{proc}\n"),
161 None => format!("\nProcessor #unknown\n"),
162 };
163 s += "\tArchitecture: x86_64\n";
164 s += &print_opt_val("Vendor ID", &self.vendor_id);
165 s += &print_opt_val("CPU Family", &self.cpu_family);
166 s += &print_opt_val("CPU Model ID", &self.model);
167 s += &print_opt_val("CPU Model Name", &self.model_name);
168 s += &print_opt_val("Stepping", &self.stepping);
169 s += &print_opt_val("Microcode", &self.microcode);
170 s += &print_opt_val("Current frequency", &self.cpu_mhz);
171 s += &print_opt_val("L3 Cache Size", &self.cache_size);
172 s += &print_opt_val("Physical ID of CPU Core", &self.physical_id);
173 s += &print_opt_val("Siblings", &self.siblings);
174 s += &print_opt_val("Core ID", &self.core_id);
175 s += &print_opt_val("CPU cores", &self.cpu_cores);
176 s += &print_opt_val("APIC ID", &self.apicid);
177 s += &print_opt_val("Initial APIC ID", &self.initial_apicid);
178 s += &print_opt_val("FPU", &self.fpu);
179 s += &print_opt_val("FPU Exception", &self.fpu_exception);
180 s += &print_opt_val("CPUID Level", &self.cpuid_level);
181 s += &print_opt_val("WP", &self.wp);
182 s += &print_opt_val("Bogo MIPS", &self.bogomips);
183 s += &print_opt_val("Clflush Size", &self.clflush_size);
184 s += &print_opt_val("Cache alignment", &self.cache_alignment);
185 s += &print_opt_val("Address sizes", &self.address_sizes);
186 s += &print_opt_val("Power management", &self.power_management);
187
188 s
189 }
190}
191
192#[cfg(target_arch = "aarch64")]
193impl ToPlainText for CPU {
194 fn to_plain(&self) -> String {
195 let mut s = match self.processor {
196 Some(proc) => format!("Processor #{proc}\n"),
197 None => format!("Processor #unknown\n"),
198 };
199 s += &print_opt_val("CPU Implementer", &self.cpu_implementer);
200 s += &print_opt_val("CPU Architecture", &self.cpu_architecture);
201 s += &print_opt_val("CPU Variant", &self.cpu_variant);
202 s += &print_opt_val("CPU Part", &self.cpu_part);
203 s += &print_opt_val("CPU Revision", &self.cpu_revision);
204
205 s
206 }
207}
208
209fn read_info() -> Result<Vec<CPU>> {
210 let blocks = read_to_string("/proc/cpuinfo")?;
211 let blocks = blocks
212 .split("\n\n") .collect::<Vec<_>>();
214 let mut processors = Vec::with_capacity(blocks.len());
215
216 for block in blocks {
217 if block.trim().is_empty() {
218 continue;
219 }
220 let mut cpu = CPU::default();
221 for line in block.lines() {
222 parse_cpuinfo(&mut cpu, line);
223 }
224 processors.push(cpu);
225 }
226 Ok(processors)
227}
228
229fn get_parts(s: &str) -> impl Iterator<Item = &str> {
230 s.splitn(2, ':').map(|item| item.trim())
231}
232
233#[cfg(target_arch = "x86_64")]
234fn parse_cpuinfo(cpu: &mut CPU, parts: &str) {
235 let mut parts = get_parts(parts);
236 match (parts.next(), parts.next()) {
237 (Some(key), Some(val)) => match key {
238 "processor" => cpu.processor = val.parse().ok(),
239 "vendor_id" => cpu.vendor_id = Some(val.to_string()),
240 "cpu family" => cpu.cpu_family = val.parse().ok(),
241 "model" => cpu.model = val.parse().ok(),
242 "model name" => cpu.model_name = Some(val.to_string()),
243 "stepping" => cpu.stepping = val.parse().ok(),
244 "microcode" => cpu.microcode = Some(val.to_string()),
245 "cpu MHz" => cpu.cpu_mhz = val.parse().ok(),
246 "cache size" => cpu.cache_size = Size::try_from(val).ok(),
247 "physical id" => cpu.physical_id = val.parse().ok(),
248 "siblings" => cpu.siblings = val.parse().ok(),
249 "core id" => cpu.core_id = val.parse().ok(),
250 "cpu cores" => cpu.cpu_cores = val.parse().ok(),
251 "apicid" => cpu.apicid = val.parse().ok(),
252 "initial apicid" => cpu.initial_apicid = val.parse().ok(),
253 "fpu" => cpu.fpu = Some(get_bool(val)),
254 "fpu_exception" => cpu.fpu_exception = Some(get_bool(val)),
255 "cpuid level" => cpu.cpuid_level = val.parse().ok(),
256 "wp" => cpu.wp = Some(get_bool(val)),
257 "flags" | "Features" => {
258 cpu.flags = Some(val.split_whitespace().map(String::from).collect())
259 }
260 "bugs" => cpu.bugs = Some(val.split_whitespace().map(String::from).collect()),
261 "bogomips" | "BogoMIPS" => cpu.bogomips = val.parse().ok(),
262 "clflush size" => cpu.clflush_size = val.parse().ok(),
263 "cache_alignment" => cpu.cache_alignment = val.parse().ok(),
264 "address sizes" => cpu.address_sizes = Some(val.to_string()),
265 "power management" => cpu.power_management = Some(val.to_string()),
266 _ => {} },
268 _ => {}
269 }
270}
271
272#[cfg(target_arch = "aarch64")]
273fn parse_cpuinfo(cpu: &mut CPU, parts: &str) {
274 let mut parts = get_parts(parts);
275 match (parts.next(), parts.next()) {
276 (Some(key), Some(val)) => match key {
277 "CPU implementer" => cpu.cpu_implementer = Some(val.to_string()),
279 "CPU architecture" => cpu.cpu_architecture = val.parse().ok(),
280 "CPU variant" => cpu.cpu_variant = Some(val.to_string()),
281 "CPU part" => cpu.cpu_part = Some(val.to_string()),
282 "CPU revision" => cpu.cpu_revision = val.parse().ok(),
283 _ => {} },
285 _ => {}
286 }
287}
288
289fn get_bool(s: &str) -> bool {
290 match s {
291 "yes" | "ok" => true,
292 _ => false,
293 }
294}
295
296#[derive(Debug, Deserialize, Serialize, Clone, Default)]
298pub struct Stat {
299 pub cpu: Option<CpuUsage>,
300 pub cpus: Vec<CpuUsage>,
301 pub interrupts: Option<u64>,
302 pub context_switches: Option<u64>,
303 pub boot_time: Option<u64>,
304 pub processes_created: Option<u64>,
305 pub processes_running: Option<u64>,
306 pub processes_blocked: Option<u64>,
307 pub softirq: Option<SoftIrq>,
308}
309
310impl Stat {
311 pub fn new() -> Result<Self> {
312 parse_proc_stat()
313 }
314}
315
316#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default)]
317pub struct CpuUsage {
318 pub user: Option<u64>,
319 pub nice: Option<u64>,
320 pub system: Option<u64>,
321 pub idle: Option<u64>,
322 pub iowait: Option<u64>,
323 pub irq: Option<u64>,
324 pub softirq: Option<u64>,
325 pub steal: Option<u64>,
326 pub guest: Option<u64>,
327 pub guest_nice: Option<u64>,
328}
329
330impl CpuUsage {
331 pub fn total_time(&self) -> u64 {
332 self.user.unwrap_or(0)
333 + self.nice.unwrap_or(0)
334 + self.system.unwrap_or(0)
335 + self.idle.unwrap_or(0)
336 + self.iowait.unwrap_or(0)
337 + self.irq.unwrap_or(0)
338 + self.softirq.unwrap_or(0)
339 + self.steal.unwrap_or(0)
340 }
341
342 pub fn active_time(&self) -> u64 {
343 self.total_time() - self.idle.unwrap_or(0) - self.iowait.unwrap_or(0)
344 }
345
346 pub fn usage_percentage(&self, prev: Option<Self>) -> f32 {
347 if prev.is_none() {
348 return 0.0;
349 }
350 let prev = prev.unwrap();
351
352 let total_diff = self.total_time() - prev.total_time();
353 let active_diff = self.active_time() - prev.active_time();
354
355 if total_diff > 0 {
356 (active_diff as f32 / total_diff as f32) * 100.0
357 } else {
358 0.0
359 }
360 }
361}
362
363impl From<&str> for CpuUsage {
364 fn from(value: &str) -> Self {
365 let parts = value.split_whitespace().collect::<Vec<&str>>();
366 if parts.is_empty() || parts.len() < 11 {
367 return Self::default();
368 }
369
370 Self {
371 user: parts[1].parse().ok(),
372 nice: parts[2].parse().ok(),
373 system: parts[3].parse().ok(),
374 idle: parts[4].parse().ok(),
375 iowait: parts[5].parse().ok(),
376 irq: parts[6].parse().ok(),
377 softirq: parts[7].parse().ok(),
378 steal: parts[8].parse().ok(),
379 guest: parts[9].parse().ok(),
380 guest_nice: parts[10].parse().ok(),
381 }
382 }
383}
384
385#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default)]
386pub struct SoftIrq {
387 pub total: Option<u64>,
388 pub hi: Option<u64>,
389 pub timer: Option<u64>,
390 pub net_tx: Option<u64>,
391 pub net_rx: Option<u64>,
392 pub block: Option<u64>,
393 pub irq_poll: Option<u64>,
394 pub tasklet: Option<u64>,
395 pub shed: Option<u64>,
396 pub hrtimer: Option<u64>,
397 pub rcu: Option<u64>,
398}
399
400impl From<&str> for SoftIrq {
401 fn from(value: &str) -> Self {
402 let parts = value.split_whitespace().collect::<Vec<&str>>();
403 if parts.is_empty() || parts.len() < 12 {
404 return Self::default();
405 }
406
407 Self {
408 total: parts[1].parse().ok(),
409 hi: parts[2].parse().ok(),
410 timer: parts[3].parse().ok(),
411 net_tx: parts[4].parse().ok(),
412 net_rx: parts[5].parse().ok(),
413 block: parts[6].parse().ok(),
414 irq_poll: parts[7].parse().ok(),
415 tasklet: parts[8].parse().ok(),
416 shed: parts[9].parse().ok(),
417 hrtimer: parts[10].parse().ok(),
418 rcu: parts[11].parse().ok(),
419 }
420 }
421}
422
423fn parse_proc_stat() -> Result<Stat> {
424 let content = read_to_string("/proc/stat")?;
425 let mut stat = Stat::default();
426
427 for line in content.lines() {
428 let parts = line.split_whitespace().collect::<Vec<&str>>();
429 if parts.is_empty() {
430 continue;
431 }
432 match parts[0] {
433 "cpu" => {
434 if parts.len() >= 11 {
435 stat.cpu = Some(CpuUsage::from(line));
436 }
437 }
438 key if key.starts_with("cpu")
439 && key[3..]
440 .chars()
441 .next()
442 .map(|c| c.is_ascii_digit())
443 .unwrap_or(false) =>
444 {
445 if parts.len() >= 11 {
446 stat.cpus.push(CpuUsage::from(line));
447 }
448 }
449 "intr" => {
450 if parts.len() >= 2 {
451 stat.interrupts = parts[1].parse().ok();
452 }
453 }
454 "ctxt" => {
455 if parts.len() >= 2 {
456 stat.context_switches = parts[1].parse().ok();
457 }
458 }
459 "btime" => {
460 if parts.len() >= 2 {
461 stat.boot_time = parts[1].parse().ok();
462 }
463 }
464 "processes" => {
465 if parts.len() >= 2 {
466 stat.processes_created = parts[1].parse().ok();
467 }
468 }
469 "procs_running" => {
470 if parts.len() >= 2 {
471 stat.processes_running = parts[1].parse().ok();
472 }
473 }
474 "procs_blocked" => {
475 if parts.len() >= 2 {
476 stat.processes_blocked = parts[1].parse().ok();
477 }
478 }
479 "softirq" => {
480 if parts.len() >= 12 {
481 stat.softirq = Some(SoftIrq::from(line));
482 }
483 }
484 _ => {}
485 }
486 }
487 Ok(stat)
488}