M1: core engine, config, plugin model and core plugins

- Cargo workspace (core, plugins, tui, cli) with drop-in compatible CLI
- Python-compatible config file handling, limits, views, rates, MMM, history
- process collection via sysinfo + procfs, filters, thresholds, events, actions
- 20 core plugins (cpu, percpu, mem, memswap, load, fs, network, diskio, ...)
- stdout, stdout-csv, stdout-json, --issue, --modules-list output modes
- differential test harness against the Python implementation
This commit is contained in:
glances-rs
2026-10-09 23:25:35 +00:00
commit 975385da5d
61 changed files with 12049 additions and 0 deletions
+141
View File
@@ -0,0 +1,141 @@
//! 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
}
}