From 7c030b33d6c0ad3cac74ea5b9e97aac0d4523a58 Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Fri, 28 Nov 2025 23:45:46 +0100 Subject: [PATCH] Show top 3 C-states with usage percentages - Changed CpuData.cstate from String to Vec - Added CStateInfo struct with name and percent fields - Collector calculates percentage for each C-state based on accumulated time - Sorts and returns top 3 C-states by usage - Dashboard displays: "C10:79% C8:10% C6:8%" Provides better visibility into CPU idle state distribution. Bump version to v0.1.209 Co-Authored-By: Claude --- Cargo.lock | 6 ++-- agent/Cargo.toml | 2 +- agent/src/collectors/cpu.rs | 44 ++++++++++++++++++++---------- dashboard/Cargo.toml | 2 +- dashboard/src/ui/widgets/system.rs | 20 +++++++++----- shared/Cargo.toml | 2 +- shared/src/agent_data.rs | 11 ++++++-- 7 files changed, 58 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55d36f6..ed586cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,7 +279,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cm-dashboard" -version = "0.1.206" +version = "0.1.208" dependencies = [ "anyhow", "chrono", @@ -301,7 +301,7 @@ dependencies = [ [[package]] name = "cm-dashboard-agent" -version = "0.1.207" +version = "0.1.208" dependencies = [ "anyhow", "async-trait", @@ -324,7 +324,7 @@ dependencies = [ [[package]] name = "cm-dashboard-shared" -version = "0.1.207" +version = "0.1.208" dependencies = [ "chrono", "serde", diff --git a/agent/Cargo.toml b/agent/Cargo.toml index f7b9d3f..9eb1c25 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-agent" -version = "0.1.208" +version = "0.1.209" edition = "2021" [dependencies] diff --git a/agent/src/collectors/cpu.rs b/agent/src/collectors/cpu.rs index cb6bf22..2581281 100644 --- a/agent/src/collectors/cpu.rs +++ b/agent/src/collectors/cpu.rs @@ -119,30 +119,27 @@ impl CpuCollector { utils::parse_u64(content.trim()) } - /// Collect CPU C-state (idle depth) and populate AgentData + /// Collect CPU C-state (idle depth) and populate AgentData with top 3 C-states by usage async fn collect_cstate(&self, agent_data: &mut AgentData) -> Result<(), CollectorError> { // Read C-state usage from first CPU (representative of overall system) // C-states indicate CPU idle depth: C1=light sleep, C6=deep sleep, C10=deepest - let mut deepest_state = String::from("C0"); // Default to active - let mut max_time: u64 = 0; + let mut cstate_times: Vec<(String, u64)> = Vec::new(); + let mut total_time: u64 = 0; - // Check C-states from CPU0 + // Collect all C-state times from CPU0 for state_num in 0..=10 { let time_path = format!("/sys/devices/system/cpu/cpu0/cpuidle/state{}/time", state_num); let name_path = format!("/sys/devices/system/cpu/cpu0/cpuidle/state{}/name", state_num); if let Ok(time_str) = utils::read_proc_file(&time_path) { if let Ok(time) = utils::parse_u64(time_str.trim()) { - if time > max_time { - // This state has most accumulated time - if let Ok(name) = utils::read_proc_file(&name_path) { - let state_name = name.trim().to_string(); - // Skip POLL state (not real idle) - if state_name != "POLL" { - max_time = time; - deepest_state = state_name; - } + if let Ok(name) = utils::read_proc_file(&name_path) { + let state_name = name.trim().to_string(); + // Skip POLL state (not real idle) + if state_name != "POLL" && time > 0 { + cstate_times.push((state_name, time)); + total_time += time; } } } @@ -152,7 +149,26 @@ impl CpuCollector { } } - agent_data.system.cpu.cstate = deepest_state; + // Sort by time descending to get top 3 + cstate_times.sort_by(|a, b| b.1.cmp(&a.1)); + + // Calculate percentages for top 3 and populate AgentData + agent_data.system.cpu.cstates = cstate_times + .iter() + .take(3) + .map(|(name, time)| { + let percent = if total_time > 0 { + (*time as f32 / total_time as f32) * 100.0 + } else { + 0.0 + }; + cm_dashboard_shared::CStateInfo { + name: name.clone(), + percent, + } + }) + .collect(); + Ok(()) } } diff --git a/dashboard/Cargo.toml b/dashboard/Cargo.toml index d91e41d..3214ff1 100644 --- a/dashboard/Cargo.toml +++ b/dashboard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard" -version = "0.1.208" +version = "0.1.209" edition = "2021" [dependencies] diff --git a/dashboard/src/ui/widgets/system.rs b/dashboard/src/ui/widgets/system.rs index cd83417..9869712 100644 --- a/dashboard/src/ui/widgets/system.rs +++ b/dashboard/src/ui/widgets/system.rs @@ -26,7 +26,7 @@ pub struct SystemWidget { cpu_load_1min: Option, cpu_load_5min: Option, cpu_load_15min: Option, - cpu_cstate: Option, + cpu_cstates: Vec, cpu_status: Status, // Memory metrics @@ -102,7 +102,7 @@ impl SystemWidget { cpu_load_1min: None, cpu_load_5min: None, cpu_load_15min: None, - cpu_cstate: None, + cpu_cstates: Vec::new(), cpu_status: Status::Unknown, memory_usage_percent: None, memory_used_gb: None, @@ -137,12 +137,18 @@ impl SystemWidget { } } - /// Format CPU C-state (idle depth) + /// Format CPU C-states (idle depth) with percentages fn format_cpu_cstate(&self) -> String { - match &self.cpu_cstate { - Some(cstate) => cstate.clone(), - None => "—".to_string(), + if self.cpu_cstates.is_empty() { + return "—".to_string(); } + + // Format top 3 C-states with percentages: "C10:79% C8:10% C6:8%" + self.cpu_cstates + .iter() + .map(|cs| format!("{}:{:.0}%", cs.name, cs.percent)) + .collect::>() + .join(" ") } /// Format memory usage @@ -188,7 +194,7 @@ impl Widget for SystemWidget { self.cpu_load_1min = Some(cpu.load_1min); self.cpu_load_5min = Some(cpu.load_5min); self.cpu_load_15min = Some(cpu.load_15min); - self.cpu_cstate = Some(cpu.cstate.clone()); + self.cpu_cstates = cpu.cstates.clone(); self.cpu_status = Status::Ok; // Extract memory data directly diff --git a/shared/Cargo.toml b/shared/Cargo.toml index bdd07ec..a837197 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-shared" -version = "0.1.208" +version = "0.1.209" edition = "2021" [dependencies] diff --git a/shared/src/agent_data.rs b/shared/src/agent_data.rs index f930ede..c4e5160 100644 --- a/shared/src/agent_data.rs +++ b/shared/src/agent_data.rs @@ -40,13 +40,20 @@ pub struct NetworkInterfaceData { pub vlan_id: Option, } +/// CPU C-state usage information +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CStateInfo { + pub name: String, + pub percent: f32, +} + /// CPU monitoring data #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CpuData { pub load_1min: f32, pub load_5min: f32, pub load_15min: f32, - pub cstate: String, // Deepest C-state in use (C1, C6, C10, etc.) - indicates CPU idle depth + pub cstates: Vec, // C-state usage percentages (C1, C6, C10, etc.) - indicates CPU idle depth distribution pub temperature_celsius: Option, pub load_status: Status, pub temperature_status: Status, @@ -204,7 +211,7 @@ impl AgentData { load_1min: 0.0, load_5min: 0.0, load_15min: 0.0, - cstate: String::from("C0"), + cstates: Vec::new(), temperature_celsius: None, load_status: Status::Unknown, temperature_status: Status::Unknown,