Files
glances-rs/crates/glances-plugins/src/plugins/cpu.rs
T
glances-rs 8385900ac1 M2: terminal UI (ratatui)
- layout: header, top menu (quicklook, cpu, mem, swap, load), left sidebar,
  process list and alert footer
- decorations/colors, bars, per-plugin render implementations
- hotkeys: quit, help, modes, sorting, cursor, nice, kill, filters, toggles
- TestBackend layout test
2026-10-09 23:35:23 +00:00

216 lines
10 KiB
Rust

//! CPU plugin.
use crate::collectors::{CpuTimes, read_cpu_times};
use crate::cpu_percent::cpu_percent;
use crate::fields;
use glances_core::plugin::{Field, HistoryItem, Plugin, PluginModel, UpdateContext};
use glances_core::{Args, Config};
use serde_json::{Value, json};
pub struct CpuPlugin {
pub model: PluginModel,
previous_times: Option<CpuTimes>,
pub nb_log_core: usize,
}
impl CpuPlugin {
pub fn new(config: &Config, args: &Args) -> Self {
let fields = fields! {
"total" => Field::new("Sum of all CPU percentages (except idle).").unit("percent").log().mmm(),
"system" => Field::new("Percent time spent in kernel space. System CPU time is the time spent running code in the Operating System kernel.").unit("percent").log(),
"user" => Field::new("CPU percent time spent in user space. User CPU time is the time spent on the processor running your program's code (or code in libraries).").unit("percent").log(),
"iowait" => Field::new("*(Linux)*: percent time spent by the CPU waiting for I/O operations to complete.").unit("percent").log(),
"dpc" => Field::new("*(Windows)*: time spent servicing deferred procedure calls (DPCs)").unit("percent").log(),
"idle" => Field::new("percent of CPU used by any program. Every program or task that runs on a computer system occupies a certain amount of processing time on the CPU. If the CPU has completed all tasks it is idle.").unit("percent").optional(),
"irq" => Field::new("*(Linux and BSD)*: percent time spent servicing/handling hardware/software interrupts. Time servicing interrupts (hardware + software).").unit("percent").optional(),
"nice" => Field::new("*(Unix)*: percent time occupied by user level processes with a positive nice value. The time the CPU has spent running users' processes that are *niced*.").unit("percent").optional(),
"steal" => Field::new("*(Linux)*: percentage of time a virtual CPU waits for a real CPU while the hypervisor is servicing another virtual processor.").unit("percent").alert().optional(),
"guest" => Field::new("*(Linux)*: time spent running a virtual CPU for guest operating systems under the control of the Linux kernel.").unit("percent").optional(),
"ctx_switches" => Field::new("number of context switches (voluntary + involuntary) per second. A context switch is a procedure that a computer's CPU (central processing unit) follows to change from one task to another while ensuring that the tasks do not conflict.").unit("number").rate().min_symbol("K").short_name("ctx_sw").optional(),
"interrupts" => Field::new("number of interrupts per second.").unit("number").rate().min_symbol("K").short_name("inter").optional(),
"soft_interrupts" => Field::new("number of software interrupts per second. Always set to 0 on Windows and SunOS.").unit("number").rate().min_symbol("K").short_name("sw_int").optional(),
"syscalls" => Field::new("number of system calls per second. Always 0 on Linux OS.").unit("number").rate().min_symbol("K").short_name("sys_call").optional(),
"cpucore" => Field::new("Total number of CPU core.").unit("number"),
"time_since_update" => Field::new("Number of seconds since last update.").unit("seconds"),
};
let items_history = vec![
HistoryItem::new("user", "User CPU usage"),
HistoryItem::new("system", "System CPU usage"),
];
let model = PluginModel::new(
"cpu",
fields,
items_history,
json!({}),
config,
args.time.unwrap_or(2.0),
args.disable_history,
args.disable_config_exec,
);
let nb_log_core = crate::collectors::logical_core_count();
Self { model, previous_times: None, nb_log_core }
}
fn times_percent(&mut self) -> CpuTimes {
let current = read_cpu_times().unwrap_or_default();
match self.previous_times {
Some(previous) => {
self.previous_times = Some(current);
current.times_percent(&previous)
}
None => {
let second = read_cpu_times().unwrap_or(current);
self.previous_times = Some(second);
second.times_percent(&current)
}
}
}
}
impl Plugin for CpuPlugin {
fn name(&self) -> &str {
"cpu"
}
fn model(&self) -> &PluginModel {
&self.model
}
fn model_mut(&mut self) -> &mut PluginModel {
&mut self.model
}
fn collect(&mut self, _ctx: &mut UpdateContext) -> anyhow::Result<Value> {
let percent = self.times_percent();
let total = {
let mut state = cpu_percent().lock().unwrap();
state.get_cpu()
};
let mut stats = serde_json::Map::new();
stats.insert("total".to_string(), json!(total));
stats.insert("user".to_string(), json!(percent.user));
stats.insert("nice".to_string(), json!(percent.nice));
stats.insert("system".to_string(), json!(percent.system));
stats.insert("idle".to_string(), json!(percent.idle));
stats.insert("iowait".to_string(), json!(percent.iowait));
stats.insert("irq".to_string(), json!(percent.irq));
stats.insert("steal".to_string(), json!(percent.steal));
stats.insert("guest".to_string(), json!(percent.guest));
// Additional CPU stats from /proc/stat.
if let Ok(content) = std::fs::read_to_string("/proc/stat") {
let mut ctx_switches = 0u64;
let mut interrupts = 0u64;
let mut soft_interrupts = 0u64;
for line in content.lines() {
if let Some(rest) = line.strip_prefix("ctxt ") {
ctx_switches = rest.split_whitespace().next().and_then(|v| v.parse().ok()).unwrap_or(0);
} else if let Some(rest) = line.strip_prefix("intr ") {
interrupts = rest.split_whitespace().next().and_then(|v| v.parse().ok()).unwrap_or(0);
} else if let Some(rest) = line.strip_prefix("softirq ") {
soft_interrupts = rest.split_whitespace().next().and_then(|v| v.parse().ok()).unwrap_or(0);
}
}
stats.insert("ctx_switches".to_string(), json!(ctx_switches));
stats.insert("interrupts".to_string(), json!(interrupts));
stats.insert("soft_interrupts".to_string(), json!(soft_interrupts));
}
stats.insert("syscalls".to_string(), json!(0));
stats.insert("cpucore".to_string(), json!(self.nb_log_core));
Ok(Value::Object(stats))
}
fn update_views(&mut self) {
let key = self.get_key();
self.model.update_views(key);
if self.model.stats.get("time_since_update").and_then(Value::as_f64) != Some(0.0) {
if let Some(ctx_switches) = self.model.stats.get("ctx_switches").and_then(Value::as_f64) {
let maximum = 100.0 * self.nb_log_core as f64;
let decoration =
self.model.get_alert(ctx_switches, 0.0, maximum, true, false, Some("ctx_switches"), None, false);
self.model.set_view_decoration(None, "ctx_switches", decoration);
}
}
}
fn display_curse(&self) -> bool {
true
}
fn render(&mut self, ctx: &glances_core::RenderContext) -> Vec<glances_core::StatLine> {
let mut ret = Vec::new();
let s = &self.model.stats;
if !s.is_object() || ctx.runtime.percpu {
return ret;
}
let idle_tag = s.get("user").is_none();
// Line 1: total + idle + ctx_switches.
ret.push(self.model.curse_add_line(format!("{:8}", "CPU"), glances_core::Decoration::Title, false, false, false));
if let Some(total) = s.get("total").and_then(Value::as_f64) {
ret.push(self.model.curse_add_line(
format!("{total:>4.1}%"),
self.model.view_decoration(None, "total"),
false,
false,
false,
));
}
if s.get("idle").is_some() && !idle_tag {
let optional = self.model.view_bool(None, "idle", "optional");
ret.push(self.model.curse_add_line(" idle ", glances_core::Decoration::Default, optional, false, false));
if let Some(idle) = s.get("idle").and_then(Value::as_f64) {
ret.push(self.model.curse_add_line(
format!("{idle:>4.1}%"),
self.model.view_decoration(None, "idle"),
optional,
false,
false,
));
}
}
ret.extend(self.model.curse_add_stat("ctx_switches", Some(15), " ", "", ""));
// Line 2.
ret.push(self.model.curse_new_line());
if !idle_tag {
ret.extend(self.model.curse_add_stat("user", Some(15), "", "", ""));
} else {
ret.extend(self.model.curse_add_stat("idle", Some(15), "", "", ""));
}
ret.extend(self.model.curse_add_stat("irq", Some(14), " ", "", ""));
ret.extend(self.model.curse_add_stat("interrupts", Some(15), " ", "", ""));
// Line 3.
ret.push(self.model.curse_new_line());
if !idle_tag {
ret.extend(self.model.curse_add_stat("system", Some(15), "", "", ""));
} else {
ret.extend(self.model.curse_add_stat("core", Some(15), "", "", ""));
}
ret.extend(self.model.curse_add_stat("nice", Some(14), " ", "", ""));
if s.get("soft_interrupts").is_some() {
ret.extend(self.model.curse_add_stat("soft_interrupts", Some(15), " ", "", ""));
} else {
ret.extend(self.model.curse_add_stat("ctx_switches", Some(15), " ", "", ""));
}
// Line 4.
ret.push(self.model.curse_new_line());
if s.get("iowait").is_some() {
ret.extend(self.model.curse_add_stat("iowait", Some(15), "", "", ""));
} else {
ret.extend(self.model.curse_add_stat("dpc", Some(15), "", "", ""));
}
ret.extend(self.model.curse_add_stat("steal", Some(14), " ", "", ""));
if s.get("guest").is_some() {
ret.extend(self.model.curse_add_stat("guest", Some(14), " ", "", ""));
} else {
ret.extend(self.model.curse_add_stat("syscalls", Some(15), " ", "", ""));
}
ret
}
}