diff --git a/Cargo.lock b/Cargo.lock index f5ffb60..55d36f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,7 +301,7 @@ dependencies = [ [[package]] name = "cm-dashboard-agent" -version = "0.1.206" +version = "0.1.207" dependencies = [ "anyhow", "async-trait", @@ -324,7 +324,7 @@ dependencies = [ [[package]] name = "cm-dashboard-shared" -version = "0.1.206" +version = "0.1.207" dependencies = [ "chrono", "serde", diff --git a/agent/Cargo.toml b/agent/Cargo.toml index 1ed8b87..f7b9d3f 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-agent" -version = "0.1.207" +version = "0.1.208" edition = "2021" [dependencies] diff --git a/agent/src/collectors/cpu.rs b/agent/src/collectors/cpu.rs index 6c870de..cb6bf22 100644 --- a/agent/src/collectors/cpu.rs +++ b/agent/src/collectors/cpu.rs @@ -119,36 +119,40 @@ impl CpuCollector { utils::parse_u64(content.trim()) } - /// Collect CPU frequency and populate AgentData - async fn collect_frequency(&self, agent_data: &mut AgentData) -> Result<(), CollectorError> { - // Try scaling frequency first (more accurate for current frequency) - if let Ok(freq) = - utils::read_proc_file("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq") - { - if let Ok(freq_khz) = utils::parse_u64(freq.trim()) { - let freq_mhz = freq_khz as f32 / 1000.0; - agent_data.system.cpu.frequency_mhz = freq_mhz; - return Ok(()); - } - } + /// Collect CPU C-state (idle depth) and populate AgentData + 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 - // Fallback: parse /proc/cpuinfo for base frequency - if let Ok(content) = utils::read_proc_file("/proc/cpuinfo") { - for line in content.lines() { - if line.starts_with("cpu MHz") { - if let Some(freq_str) = line.split(':').nth(1) { - if let Ok(freq_mhz) = utils::parse_f32(freq_str) { - agent_data.system.cpu.frequency_mhz = freq_mhz; - return Ok(()); + let mut deepest_state = String::from("C0"); // Default to active + let mut max_time: u64 = 0; + + // Check C-states 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; + } } } - break; // Only need first CPU entry } + } else { + // No more states available + break; } } - debug!("CPU frequency not available"); - // Leave frequency as 0.0 if not available + agent_data.system.cpu.cstate = deepest_state; Ok(()) } } @@ -165,8 +169,8 @@ impl Collector for CpuCollector { // Collect temperature (optional) self.collect_temperature(agent_data).await?; - // Collect frequency (optional) - self.collect_frequency(agent_data).await?; + // Collect C-state (CPU idle depth) + self.collect_cstate(agent_data).await?; let duration = start.elapsed(); debug!("CPU collection completed in {:?}", duration); diff --git a/dashboard/Cargo.toml b/dashboard/Cargo.toml index db8e46c..d91e41d 100644 --- a/dashboard/Cargo.toml +++ b/dashboard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard" -version = "0.1.206" +version = "0.1.208" edition = "2021" [dependencies] diff --git a/dashboard/src/ui/widgets/system.rs b/dashboard/src/ui/widgets/system.rs index c702a5e..cd83417 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_frequency: Option, + cpu_cstate: Option, cpu_status: Status, // Memory metrics @@ -102,7 +102,7 @@ impl SystemWidget { cpu_load_1min: None, cpu_load_5min: None, cpu_load_15min: None, - cpu_frequency: None, + cpu_cstate: None, cpu_status: Status::Unknown, memory_usage_percent: None, memory_used_gb: None, @@ -137,11 +137,11 @@ impl SystemWidget { } } - /// Format CPU frequency - fn format_cpu_frequency(&self) -> String { - match self.cpu_frequency { - Some(freq) => format!("{:.0} MHz", freq), - None => "— MHz".to_string(), + /// Format CPU C-state (idle depth) + fn format_cpu_cstate(&self) -> String { + match &self.cpu_cstate { + Some(cstate) => cstate.clone(), + None => "—".to_string(), } } @@ -188,7 +188,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_frequency = Some(cpu.frequency_mhz); + self.cpu_cstate = Some(cpu.cstate.clone()); self.cpu_status = Status::Ok; // Extract memory data directly @@ -832,10 +832,10 @@ impl SystemWidget { ); lines.push(Line::from(cpu_spans)); - let freq_text = self.format_cpu_frequency(); + let cstate_text = self.format_cpu_cstate(); lines.push(Line::from(vec![ Span::styled(" └─ ", Typography::tree()), - Span::styled(format!("Freq: {}", freq_text), Typography::secondary()) + Span::styled(format!("C-state: {}", cstate_text), Typography::secondary()) ])); // RAM section diff --git a/shared/Cargo.toml b/shared/Cargo.toml index be4d563..bdd07ec 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-shared" -version = "0.1.207" +version = "0.1.208" edition = "2021" [dependencies] diff --git a/shared/src/agent_data.rs b/shared/src/agent_data.rs index 82c7d03..f930ede 100644 --- a/shared/src/agent_data.rs +++ b/shared/src/agent_data.rs @@ -46,7 +46,7 @@ pub struct CpuData { pub load_1min: f32, pub load_5min: f32, pub load_15min: f32, - pub frequency_mhz: f32, + pub cstate: String, // Deepest C-state in use (C1, C6, C10, etc.) - indicates CPU idle depth pub temperature_celsius: Option, pub load_status: Status, pub temperature_status: Status, @@ -204,7 +204,7 @@ impl AgentData { load_1min: 0.0, load_5min: 0.0, load_15min: 0.0, - frequency_mhz: 0.0, + cstate: String::from("C0"), temperature_celsius: None, load_status: Status::Unknown, temperature_status: Status::Unknown,