Restore service discovery and disk usage calculation
Some checks failed
Build and Release / build-and-release (push) Failing after 1m2s

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.
This commit is contained in:
Christoffer Martinsson 2025-11-24 20:25:08 +01:00
parent 66ab7a492d
commit b120f95f8a
4 changed files with 120 additions and 12 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "cm-dashboard-agent" name = "cm-dashboard-agent"
version = "0.1.141" version = "0.1.142"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View File

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use cm_dashboard_shared::{AgentData, ServiceData}; use cm_dashboard_shared::{AgentData, ServiceData, Status};
use std::process::Command; use std::process::Command;
use std::sync::RwLock; use std::sync::RwLock;
use std::time::Instant; use std::time::Instant;
@ -66,11 +66,12 @@ impl SystemdCollector {
// Populate AgentData with service information // Populate AgentData with service information
for service in services { for service in services {
agent_data.services.push(ServiceData { agent_data.services.push(ServiceData {
name: service.name, name: service.name.clone(),
status: service.status, status: service.status.clone(),
memory_mb: service.memory_mb, memory_mb: service.memory_mb,
disk_gb: service.disk_gb, disk_gb: service.disk_gb,
user_stopped: false, // TODO: Integrate with service tracker 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; continue;
} }
// Filter services based on configuration // Filter services based on configuration with wildcard support
if self.config.service_name_filters.is_empty() || self.config.service_name_filters.contains(&service_name.to_string()) { if self.should_monitor_service(service_name) {
// Get memory usage for this service // Get memory usage for this service
let memory_mb = self.get_service_memory_usage(service_name).await.unwrap_or(0.0); 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 { let service_info = ServiceInfo {
name: service_name.to_string(), name: service_name.to_string(),
status: self.normalize_service_status(active_state, sub_state), status: normalized_status,
memory_mb, memory_mb,
disk_gb: 0.0, // Services typically don't have disk usage disk_gb,
}; };
services.push(service_info); services.push(service_info);
@ -133,6 +138,108 @@ impl SystemdCollector {
Ok(services) 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<f32, CollectorError> {
// 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<f32> {
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::<u64>() {
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 /// Get memory usage for a specific service
async fn get_service_memory_usage(&self, service_name: &str) -> Result<f32, CollectorError> { async fn get_service_memory_usage(&self, service_name: &str) -> Result<f32, CollectorError> {
let output = Command::new("systemctl") let output = Command::new("systemctl")
@ -206,11 +313,12 @@ impl Collector for SystemdCollector {
debug!("Using cached systemd services data"); debug!("Using cached systemd services data");
for service in cached_services { for service in cached_services {
agent_data.services.push(ServiceData { agent_data.services.push(ServiceData {
name: service.name, name: service.name.clone(),
status: service.status, status: service.status.clone(),
memory_mb: service.memory_mb, memory_mb: service.memory_mb,
disk_gb: service.disk_gb, disk_gb: service.disk_gb,
user_stopped: false, // TODO: Integrate with service tracker user_stopped: false, // TODO: Integrate with service tracker
service_status: self.calculate_service_status(&service.name, &service.status),
}); });
} }
Ok(()) Ok(())

View File

@ -1,6 +1,6 @@
[package] [package]
name = "cm-dashboard" name = "cm-dashboard"
version = "0.1.141" version = "0.1.142"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "cm-dashboard-shared" name = "cm-dashboard-shared"
version = "0.1.141" version = "0.1.142"
edition = "2021" edition = "2021"
[dependencies] [dependencies]