From 5bc250a738dc49c2cd6436055c9ed6fbe8269987 Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Mon, 1 Dec 2025 19:15:57 +0100 Subject: [PATCH] Add static CPU model and core count to CPU collector - Collect CPU model name and core count from /proc/cpuinfo - Only collect once at startup (check if fields already set) - Display below C-state row in dashboard CPU section - Move CPU info collection from NixOS collector to CPU collector --- agent/Cargo.toml | 2 +- agent/src/collectors/cpu.rs | 33 ++++++++++++++++++++++++++++++ dashboard/Cargo.toml | 2 +- dashboard/src/ui/widgets/system.rs | 28 ++++++++++++++++++++++++- shared/Cargo.toml | 2 +- shared/src/agent_data.rs | 7 +++++++ 6 files changed, 70 insertions(+), 4 deletions(-) diff --git a/agent/Cargo.toml b/agent/Cargo.toml index c19c539..e9588cd 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-agent" -version = "0.1.239" +version = "0.1.240" edition = "2021" [dependencies] diff --git a/agent/src/collectors/cpu.rs b/agent/src/collectors/cpu.rs index ce6fdfb..7165b18 100644 --- a/agent/src/collectors/cpu.rs +++ b/agent/src/collectors/cpu.rs @@ -119,6 +119,34 @@ impl CpuCollector { utils::parse_u64(content.trim()) } + /// Collect static CPU information from /proc/cpuinfo (only once at startup) + async fn collect_cpu_info(&self, agent_data: &mut AgentData) -> Result<(), CollectorError> { + let content = utils::read_proc_file("/proc/cpuinfo")?; + + let mut model_name: Option = None; + let mut core_count: u32 = 0; + + for line in content.lines() { + if line.starts_with("model name") { + if let Some(colon_pos) = line.find(':') { + let name = line[colon_pos + 1..].trim().to_string(); + if model_name.is_none() { + model_name = Some(name); + } + } + } else if line.starts_with("processor") { + core_count += 1; + } + } + + agent_data.system.cpu.model_name = model_name; + if core_count > 0 { + agent_data.system.cpu.core_count = Some(core_count); + } + + Ok(()) + } + /// 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) @@ -192,6 +220,11 @@ impl Collector for CpuCollector { debug!("Collecting CPU metrics"); let start = std::time::Instant::now(); + // Collect static CPU info (only once at startup) + if agent_data.system.cpu.model_name.is_none() || agent_data.system.cpu.core_count.is_none() { + self.collect_cpu_info(agent_data).await?; + } + // Collect load averages (always available) self.collect_load_averages(agent_data).await?; diff --git a/dashboard/Cargo.toml b/dashboard/Cargo.toml index f74a0bd..919442b 100644 --- a/dashboard/Cargo.toml +++ b/dashboard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard" -version = "0.1.239" +version = "0.1.240" edition = "2021" [dependencies] diff --git a/dashboard/src/ui/widgets/system.rs b/dashboard/src/ui/widgets/system.rs index 87acbfc..8ed54d8 100644 --- a/dashboard/src/ui/widgets/system.rs +++ b/dashboard/src/ui/widgets/system.rs @@ -27,6 +27,8 @@ pub struct SystemWidget { cpu_load_5min: Option, cpu_load_15min: Option, cpu_cstates: Vec, + cpu_model_name: Option, + cpu_core_count: Option, cpu_status: Status, // Memory metrics @@ -97,6 +99,8 @@ impl SystemWidget { cpu_load_5min: None, cpu_load_15min: None, cpu_cstates: Vec::new(), + cpu_model_name: None, + cpu_core_count: None, cpu_status: Status::Unknown, memory_usage_percent: None, memory_used_gb: None, @@ -184,6 +188,8 @@ impl Widget for SystemWidget { self.cpu_load_5min = Some(cpu.load_5min); self.cpu_load_15min = Some(cpu.load_15min); self.cpu_cstates = cpu.cstates.clone(); + self.cpu_model_name = cpu.model_name.clone(); + self.cpu_core_count = cpu.core_count; self.cpu_status = Status::Ok; // Extract memory data directly @@ -830,11 +836,31 @@ impl SystemWidget { lines.push(Line::from(cpu_spans)); let cstate_text = self.format_cpu_cstate(); + let has_cpu_info = self.cpu_model_name.is_some() || self.cpu_core_count.is_some(); + let cstate_tree = if has_cpu_info { " ├─ " } else { " └─ " }; lines.push(Line::from(vec![ - Span::styled(" └─ ", Typography::tree()), + Span::styled(cstate_tree, Typography::tree()), Span::styled(format!("C-state: {}", cstate_text), Typography::secondary()) ])); + // CPU model and core count (if available) + if let (Some(model), Some(cores)) = (&self.cpu_model_name, self.cpu_core_count) { + lines.push(Line::from(vec![ + Span::styled(" └─ ", Typography::tree()), + Span::styled(format!("{} ({} cores)", model, cores), Typography::secondary()) + ])); + } else if let Some(model) = &self.cpu_model_name { + lines.push(Line::from(vec![ + Span::styled(" └─ ", Typography::tree()), + Span::styled(model.clone(), Typography::secondary()) + ])); + } else if let Some(cores) = self.cpu_core_count { + lines.push(Line::from(vec![ + Span::styled(" └─ ", Typography::tree()), + Span::styled(format!("{} cores", cores), Typography::secondary()) + ])); + } + // RAM section lines.push(Line::from(vec![ Span::styled("RAM:", Typography::widget_title()) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 11a0f73..466e1b0 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-shared" -version = "0.1.239" +version = "0.1.240" edition = "2021" [dependencies] diff --git a/shared/src/agent_data.rs b/shared/src/agent_data.rs index 295b4dc..370930d 100644 --- a/shared/src/agent_data.rs +++ b/shared/src/agent_data.rs @@ -57,6 +57,11 @@ pub struct CpuData { pub temperature_celsius: Option, pub load_status: Status, pub temperature_status: Status, + // Static CPU information (collected once at startup) + #[serde(skip_serializing_if = "Option::is_none")] + pub model_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub core_count: Option, } /// Memory monitoring data @@ -219,6 +224,8 @@ impl AgentData { temperature_celsius: None, load_status: Status::Unknown, temperature_status: Status::Unknown, + model_name: None, + core_count: None, }, memory: MemoryData { usage_percent: 0.0,