//! 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, 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(¤t) } } } } 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 { 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 { 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 } }