From b120f95f8ab67d88146a48fd0a58f8fd7a5d3961 Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Mon, 24 Nov 2025 20:25:08 +0100 Subject: [PATCH] Restore service discovery and disk usage calculation Fixes missing services and 0B disk usage issues by restoring: - Wildcard pattern matching for service filters (gitea*, redis*) - Service disk usage calculation from directories and WorkingDirectory - Proper Status::Inactive for inactive services Services now properly discovered and show actual disk usage. --- agent/Cargo.toml | 2 +- agent/src/collectors/systemd.rs | 126 +++++++++++++++++++++++++++++--- dashboard/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- 4 files changed, 120 insertions(+), 12 deletions(-) diff --git a/agent/Cargo.toml b/agent/Cargo.toml index 262d1ea..05945b3 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-agent" -version = "0.1.141" +version = "0.1.142" edition = "2021" [dependencies] diff --git a/agent/src/collectors/systemd.rs b/agent/src/collectors/systemd.rs index 0e4e25d..818bdbe 100644 --- a/agent/src/collectors/systemd.rs +++ b/agent/src/collectors/systemd.rs @@ -1,6 +1,6 @@ use anyhow::Result; use async_trait::async_trait; -use cm_dashboard_shared::{AgentData, ServiceData}; +use cm_dashboard_shared::{AgentData, ServiceData, Status}; use std::process::Command; use std::sync::RwLock; use std::time::Instant; @@ -66,11 +66,12 @@ impl SystemdCollector { // Populate AgentData with service information for service in services { agent_data.services.push(ServiceData { - name: service.name, - status: service.status, + name: service.name.clone(), + status: service.status.clone(), memory_mb: service.memory_mb, disk_gb: service.disk_gb, user_stopped: false, // TODO: Integrate with service tracker + service_status: self.calculate_service_status(&service.name, &service.status), }); } @@ -113,16 +114,20 @@ impl SystemdCollector { continue; } - // Filter services based on configuration - if self.config.service_name_filters.is_empty() || self.config.service_name_filters.contains(&service_name.to_string()) { + // Filter services based on configuration with wildcard support + if self.should_monitor_service(service_name) { // Get memory usage for this service let memory_mb = self.get_service_memory_usage(service_name).await.unwrap_or(0.0); + // Get disk usage for this service + let disk_gb = self.get_service_disk_usage(service_name).await.unwrap_or(0.0); + + let normalized_status = self.normalize_service_status(active_state, sub_state); let service_info = ServiceInfo { name: service_name.to_string(), - status: self.normalize_service_status(active_state, sub_state), + status: normalized_status, memory_mb, - disk_gb: 0.0, // Services typically don't have disk usage + disk_gb, }; services.push(service_info); @@ -133,6 +138,108 @@ impl SystemdCollector { Ok(services) } + /// Check if a service should be monitored based on configuration filters with wildcard support + fn should_monitor_service(&self, service_name: &str) -> bool { + // If no filters configured, monitor nothing (to prevent noise) + if self.config.service_name_filters.is_empty() { + return false; + } + + // Check if service matches any of the configured patterns + for pattern in &self.config.service_name_filters { + if self.matches_pattern(service_name, pattern) { + return true; + } + } + + false + } + + /// Check if service name matches pattern (supports wildcards like nginx*) + fn matches_pattern(&self, service_name: &str, pattern: &str) -> bool { + if pattern.ends_with('*') { + let prefix = &pattern[..pattern.len() - 1]; + service_name.starts_with(prefix) + } else { + service_name == pattern + } + } + + /// Get disk usage for a specific service + async fn get_service_disk_usage(&self, service_name: &str) -> Result { + // Check if this service has configured directory paths + if let Some(dirs) = self.config.service_directories.get(service_name) { + // Service has configured paths - use the first accessible one + for dir in dirs { + if let Some(size) = self.get_directory_size(dir) { + return Ok(size); + } + } + // If configured paths failed, return 0 + return Ok(0.0); + } + + // No configured path - try to get WorkingDirectory from systemctl + let output = Command::new("systemctl") + .args(&["show", &format!("{}.service", service_name), "--property=WorkingDirectory"]) + .output() + .map_err(|e| CollectorError::SystemRead { + path: format!("WorkingDirectory for {}", service_name), + error: e.to_string(), + })?; + + let output_str = String::from_utf8_lossy(&output.stdout); + for line in output_str.lines() { + if line.starts_with("WorkingDirectory=") && !line.contains("[not set]") { + let dir = line.strip_prefix("WorkingDirectory=").unwrap_or(""); + if !dir.is_empty() { + return Ok(self.get_directory_size(dir).unwrap_or(0.0)); + } + } + } + + Ok(0.0) + } + + /// Get size of a directory in GB + fn get_directory_size(&self, path: &str) -> Option { + let output = Command::new("du") + .args(&["-sb", path]) + .output() + .ok()?; + + if !output.status.success() { + return None; + } + + let output_str = String::from_utf8_lossy(&output.stdout); + let parts: Vec<&str> = output_str.split_whitespace().collect(); + if let Some(size_str) = parts.first() { + if let Ok(size_bytes) = size_str.parse::() { + return Some(size_bytes as f32 / (1024.0 * 1024.0 * 1024.0)); + } + } + + None + } + + /// Calculate service status, taking user-stopped services into account + fn calculate_service_status(&self, service_name: &str, active_status: &str) -> Status { + match active_status.to_lowercase().as_str() { + "active" => Status::Ok, + "inactive" | "dead" => { + debug!("Service '{}' is inactive - treating as Inactive status", service_name); + Status::Inactive + }, + "failed" | "error" => Status::Critical, + "activating" | "deactivating" | "reloading" | "starting" | "stopping" => { + debug!("Service '{}' is transitioning - treating as Pending", service_name); + Status::Pending + }, + _ => Status::Unknown, + } + } + /// Get memory usage for a specific service async fn get_service_memory_usage(&self, service_name: &str) -> Result { let output = Command::new("systemctl") @@ -206,11 +313,12 @@ impl Collector for SystemdCollector { debug!("Using cached systemd services data"); for service in cached_services { agent_data.services.push(ServiceData { - name: service.name, - status: service.status, + name: service.name.clone(), + status: service.status.clone(), memory_mb: service.memory_mb, disk_gb: service.disk_gb, user_stopped: false, // TODO: Integrate with service tracker + service_status: self.calculate_service_status(&service.name, &service.status), }); } Ok(()) diff --git a/dashboard/Cargo.toml b/dashboard/Cargo.toml index 52e552c..5a03071 100644 --- a/dashboard/Cargo.toml +++ b/dashboard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard" -version = "0.1.141" +version = "0.1.142" edition = "2021" [dependencies] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 1a279a5..d1b4a9b 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-shared" -version = "0.1.141" +version = "0.1.142" edition = "2021" [dependencies]