diff --git a/agent/src/collectors/service.rs b/agent/src/collectors/service.rs index d26d8f8..dd7e368 100644 --- a/agent/src/collectors/service.rs +++ b/agent/src/collectors/service.rs @@ -1361,7 +1361,7 @@ impl Collector for ServiceCollector { // Determine status and description based on latency and health let (site_status, site_description) = match (latency, is_healthy) { (Some(_ms), true) => (ServiceStatus::Running, None), - (Some(_ms), false) => (ServiceStatus::Stopped, Some(vec!["error".to_string()])), + (Some(_ms), false) => (ServiceStatus::Stopped, None), // Show error status but no description (None, _) => (ServiceStatus::Stopped, Some(vec!["unreachable".to_string()])), }; diff --git a/agent/src/collectors/system.rs b/agent/src/collectors/system.rs index 243e2a8..9984ba3 100644 --- a/agent/src/collectors/system.rs +++ b/agent/src/collectors/system.rs @@ -301,6 +301,10 @@ impl Collector for SystemCollector { // Get logged-in users (optional) let logged_in_users = self.get_logged_in_users().await; + + // Get top processes + let top_cpu_process = self.get_top_cpu_process().await; + let top_ram_process = self.get_top_ram_process().await; let mut system_metrics = json!({ "summary": { @@ -332,6 +336,14 @@ impl Collector for SystemCollector { system_metrics["summary"]["logged_in_users"] = json!(users); } + if let Some(cpu_proc) = top_cpu_process { + system_metrics["summary"]["top_cpu_process"] = json!(cpu_proc); + } + + if let Some(ram_proc) = top_ram_process { + system_metrics["summary"]["top_ram_process"] = json!(ram_proc); + } + debug!("System metrics collected: CPU load {:.2}, Memory {:.1}%", cpu_load_5, memory_usage_percent); @@ -340,4 +352,58 @@ impl Collector for SystemCollector { data: system_metrics, }) } + + async fn get_top_cpu_process(&self) -> Option { + // Get top CPU process using ps command + let output = Command::new("/run/current-system/sw/bin/ps") + .args(["aux", "--sort=-pcpu"]) + .output() + .await + .ok()?; + + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + // Skip header line and get first process + for line in stdout.lines().skip(1) { + let fields: Vec<&str> = line.split_whitespace().collect(); + if fields.len() >= 11 { + let cpu_percent = fields[2]; + let command = fields[10]; + // Skip kernel threads (in brackets) and low CPU processes + if !command.starts_with('[') && cpu_percent.parse::().unwrap_or(0.0) > 0.1 { + return Some(format!("{} {:.1}%", command, cpu_percent.parse::().unwrap_or(0.0))); + } + } + } + } + + None + } + + async fn get_top_ram_process(&self) -> Option { + // Get top RAM process using ps command + let output = Command::new("/run/current-system/sw/bin/ps") + .args(["aux", "--sort=-rss"]) + .output() + .await + .ok()?; + + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + // Skip header line and get first process + for line in stdout.lines().skip(1) { + let fields: Vec<&str> = line.split_whitespace().collect(); + if fields.len() >= 11 { + let mem_percent = fields[3]; + let command = fields[10]; + // Skip kernel threads (in brackets) and low memory processes + if !command.starts_with('[') && mem_percent.parse::().unwrap_or(0.0) > 0.1 { + return Some(format!("{} {:.1}%", command, mem_percent.parse::().unwrap_or(0.0))); + } + } + } + } + + None + } } \ No newline at end of file diff --git a/dashboard/src/data/metrics.rs b/dashboard/src/data/metrics.rs index 0df01eb..9ae9630 100644 --- a/dashboard/src/data/metrics.rs +++ b/dashboard/src/data/metrics.rs @@ -60,6 +60,10 @@ pub struct SystemSummary { pub cpu_cstate: Option>, #[serde(default)] pub logged_in_users: Option>, + #[serde(default)] + pub top_cpu_process: Option, + #[serde(default)] + pub top_ram_process: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/dashboard/src/ui/services.rs b/dashboard/src/ui/services.rs index a134f7a..4d163d6 100644 --- a/dashboard/src/ui/services.rs +++ b/dashboard/src/ui/services.rs @@ -109,10 +109,10 @@ fn render_metrics( // Add latency information for nginx sites if available let service_name_with_latency = if let Some(parent) = &svc.sub_service { if parent == "nginx" { - match (&svc.latency_ms, &svc.description) { - (Some(latency), _) => format!("{} {:.0}ms", svc.name, latency), - (None, Some(desc)) if !desc.is_empty() => format!("{} {}", svc.name, desc[0]), - _ => svc.name.clone(), + match &svc.latency_ms { + Some(latency) if *latency >= 5000.0 => format!("{} unreachable", svc.name), // Timeout (5s+) + Some(latency) => format!("{} {:.0}ms", svc.name, latency), + None => format!("{} unreachable", svc.name), // Connection failed } } else { svc.name.clone() diff --git a/dashboard/src/ui/system.rs b/dashboard/src/ui/system.rs index 3d84837..f71d04c 100644 --- a/dashboard/src/ui/system.rs +++ b/dashboard/src/ui/system.rs @@ -90,6 +90,16 @@ fn render_metrics( description_lines.push(user_line); } } + + // Add top CPU process + if let Some(cpu_proc) = &summary.top_cpu_process { + description_lines.push(format!("Top CPU: {}", cpu_proc)); + } + + // Add top RAM process + if let Some(ram_proc) = &summary.top_ram_process { + description_lines.push(format!("Top RAM: {}", ram_proc)); + } system_dataset.add_row( overall_status.clone(),