//! Per-CPU plugin. use crate::cpu_percent::cpu_percent; use crate::fields; use glances_core::plugin::{Field, Plugin, PluginModel, UpdateContext}; use glances_core::{Args, Config}; use serde_json::Value; pub struct PerCpuPlugin { pub model: PluginModel, pub max_cpu_display: usize, } impl PerCpuPlugin { pub fn new(config: &Config, args: &Args) -> Self { let fields = fields! { "cpu_number" => Field::new("CPU number"), "total" => Field::new("Sum of CPU percentages (except idle) for current CPU number.").unit("percent"), "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"), "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"), "iowait" => Field::new("*(Linux)*: percent time spent by the CPU waiting for I/O operations to complete.").unit("percent"), "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"), "irq" => Field::new("*(Linux and BSD)*: percent time spent servicing/handling hardware/software interrupts. Time servicing interrupts (hardware + software).").unit("percent"), "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"), "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"), "guest" => Field::new("*(Linux)*: percent of time spent running a virtual CPU for guest operating systems under the control of the Linux kernel.").unit("percent"), "guest_nice" => Field::new("*(Linux)*: percent of time spent running a niced guest (virtual CPU).").unit("percent"), "softirq" => Field::new("*(Linux)*: percent of time spent handling software interrupts.").unit("percent"), "dpc" => Field::new("*(Windows)*: percent of time spent handling deferred procedure calls.").unit("percent"), "interrupt" => Field::new("*(Windows)*: percent of time spent handling software interrupts.").unit("percent"), }; let mut model = PluginModel::new( "percpu", fields, Vec::new(), Value::Array(Vec::new()), config, args.time.unwrap_or(2.0), args.disable_history, args.disable_config_exec, ); model.set_item_key("cpu_number"); let max_cpu_display = config.get_int_value("percpu", "max_cpu_display", 4).max(1) as usize; Self { model, max_cpu_display } } } impl Plugin for PerCpuPlugin { fn name(&self) -> &str { "percpu" } fn model(&self) -> &PluginModel { &self.model } fn model_mut(&mut self) -> &mut PluginModel { &mut self.model } fn get_key(&self) -> Option<&'static str> { Some("cpu_number") } fn collect(&mut self, _ctx: &mut UpdateContext) -> anyhow::Result { let mut state = cpu_percent().lock().unwrap(); Ok(Value::Array(state.get_percpu().to_vec())) } fn render(&mut self, ctx: &glances_core::RenderContext) -> Vec { let mut ret = Vec::new(); let items: Vec = self.model.stats.as_array().cloned().unwrap_or_default(); if items.is_empty() || !ctx.runtime.percpu { return ret; } let items = &items; let quicklook_disabled = !ctx.runtime.is_plugin_enabled("quicklook"); let mut header = vec!["user", "system", "iowait", "idle", "irq", "nice", "steal", "guest"]; if quicklook_disabled { ret.push(self.model.curse_add_line(format!("{:5}", "CPU"), glances_core::Decoration::Title, false, false, false)); header.insert(0, "total"); } for stat in &header { ret.push(self.model.curse_add_line(format!("{stat:>7}"), glances_core::Decoration::Default, false, false, false)); } // Sort by total and keep the top max_cpu_display cores. let mut sorted: Vec = items.clone(); if sorted.len() > self.max_cpu_display { sorted.sort_by(|a, b| { b.get("total") .and_then(Value::as_f64) .unwrap_or(0.0) .partial_cmp(&a.get("total").and_then(Value::as_f64).unwrap_or(0.0)) .unwrap_or(std::cmp::Ordering::Equal) }); } let displayed: Vec = sorted.iter().take(self.max_cpu_display).cloned().collect(); for cpu in &displayed { ret.push(self.model.curse_new_line()); if quicklook_disabled { let cpu_id = cpu.get("cpu_number").and_then(Value::as_i64).unwrap_or(0); let msg = if cpu_id < 10 { format!("CPU{cpu_id:1} ") } else { format!("{cpu_id:4} ") }; ret.push(self.model.curse_add_line(msg, glances_core::Decoration::Default, false, false, false)); } for stat in &header { let value = cpu.get(*stat).and_then(Value::as_f64); let decoration = match value { Some(value) => self.model.get_alert(value, 0.0, 100.0, true, false, Some(stat), None, false), None => glances_core::Decoration::Default, }; let msg = match value { Some(value) => format!("{value:6.1}%"), None => format!("{:>6}%", "?"), }; ret.push(self.model.curse_add_line(msg, decoration, false, false, false)); } } if items.len() > self.max_cpu_display { ret.push(self.model.curse_new_line()); if quicklook_disabled { ret.push(self.model.curse_add_line("CPU* ", glances_core::Decoration::Default, false, false, false)); } for stat in &header { let values: Vec = sorted .iter() .skip(self.max_cpu_display) .filter_map(|cpu| cpu.get(*stat).and_then(Value::as_f64)) .collect(); let mean = if values.is_empty() { 0.0 } else { values.iter().sum::() / values.len() as f64 }; let decoration = self.model.get_alert(mean, 0.0, 100.0, true, false, Some(stat), None, false); ret.push(self.model.curse_add_line(format!("{mean:6.1}%"), decoration, false, false, false)); } } ret } }