From 47a7d5ae626675c153b876e6455467a2e1159ccb Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Mon, 20 Oct 2025 11:06:49 +0200 Subject: [PATCH] Simplify service disk usage detection - remove all estimation fallbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace complex multi-strategy detection with single deterministic method - Remove estimate_service_disk_usage and all fallback strategies - Use simple get_service_disk_usage method with clear logic: * Defined path exists → use only that path * Defined path fails → return None (shows as '-') * No defined path → use systemctl WorkingDirectory * No estimates or guessing ever Fixes misleading 5MB estimates when defined paths fail due to permissions. --- agent/src/collectors/systemd.rs | 243 ++------------------------------ 1 file changed, 14 insertions(+), 229 deletions(-) diff --git a/agent/src/collectors/systemd.rs b/agent/src/collectors/systemd.rs index a7b2fdd..f395568 100644 --- a/agent/src/collectors/systemd.rs +++ b/agent/src/collectors/systemd.rs @@ -322,43 +322,22 @@ impl SystemdCollector { } } - /// Get service disk usage with comprehensive detection strategies - fn get_comprehensive_service_disk_usage(&self, service: &str) -> Option { - // Strategy 1: Try service-specific directories first - if let Some(size) = self.get_service_disk_usage_basic(service) { - return Some(size); - } - - // Strategy 2: Check service binary and configuration directories - if let Some(size) = self.get_service_binary_disk_usage(service) { - return Some(size); - } - - // Strategy 3: Check service logs and runtime data - if let Some(size) = self.get_service_logs_disk_usage(service) { - return Some(size); - } - - // Strategy 4: Use process memory maps to find file usage - if let Some(size) = self.get_process_file_usage(service) { - return Some(size); - } - - // Strategy 5: Last resort - estimate based on service type - self.estimate_service_disk_usage(service) - } - - /// Basic service disk usage detection (existing logic) - fn get_service_disk_usage_basic(&self, service: &str) -> Option { - // Check defined service paths FIRST (highest priority) - let service_dirs = self.get_service_directories(service); - for dir in service_dirs { - if let Some(size) = self.get_directory_size(dir) { - return Some(size); + /// Get service disk usage - simple and deterministic + fn get_service_disk_usage(&self, service: &str) -> Option { + // 1. Check if service has defined directories + let defined_dirs = self.get_service_directories(service); + if !defined_dirs.is_empty() { + // Service has defined paths - use ONLY those + for dir in defined_dirs { + if let Some(size) = self.get_directory_size(dir) { + return Some(size); + } } + // If defined path failed, return None (shows as "-") + return None; } - // Only if no defined path, try systemctl WorkingDirectory + // 2. No defined path - use systemctl WorkingDirectory let output = Command::new("systemctl") .arg("show") .arg(format!("{}.service", service)) @@ -409,205 +388,11 @@ impl SystemdCollector { } } - /// Check service binary and configuration directories - fn get_service_binary_disk_usage(&self, service: &str) -> Option { - let mut total_size = 0u64; - let mut found_any = false; - // Check common binary locations - let binary_paths = [ - format!("/usr/bin/{}", service), - format!("/usr/sbin/{}", service), - format!("/usr/local/bin/{}", service), - format!("/opt/{}/bin/{}", service, service), - ]; - for binary_path in &binary_paths { - if let Ok(metadata) = std::fs::metadata(binary_path) { - total_size += metadata.len(); - found_any = true; - } - } - // Check configuration directories - let config_dirs = [ - format!("/etc/{}", service), - format!("/usr/share/{}", service), - format!("/var/lib/{}", service), - format!("/opt/{}", service), - ]; - for config_dir in &config_dirs { - if let Some(size_gb) = self.get_directory_size(config_dir) { - total_size += (size_gb * 1024.0 * 1024.0 * 1024.0) as u64; - found_any = true; - } - } - if found_any { - let size_gb = total_size as f32 / (1024.0 * 1024.0 * 1024.0); - Some(size_gb.max(0.001)) // Minimum 1MB for visibility - } else { - None - } - } - - /// Check service logs and runtime data - fn get_service_logs_disk_usage(&self, service: &str) -> Option { - let mut total_size = 0u64; - let mut found_any = false; - - // Check systemd journal logs for this service - let output = Command::new("journalctl") - .arg("-u") - .arg(format!("{}.service", service)) - .arg("--disk-usage") - .output() - .ok(); - - if let Some(output) = output { - if output.status.success() { - let output_str = String::from_utf8_lossy(&output.stdout); - // Extract size from "Archived and active journals take up X on disk." - if let Some(size_part) = output_str.split("take up ").nth(1) { - if let Some(size_str) = size_part.split(" on disk").next() { - // Parse sizes like "1.2M", "45.6K", "2.1G" - if let Some(size_bytes) = self.parse_size_string(size_str) { - total_size += size_bytes; - found_any = true; - } - } - } - } - } - - // Check common log directories - let log_dirs = [ - format!("/var/log/{}", service), - format!("/var/log/{}.log", service), - "/var/log/syslog".to_string(), - "/var/log/messages".to_string(), - ]; - - for log_path in &log_dirs { - if let Ok(metadata) = std::fs::metadata(log_path) { - total_size += metadata.len(); - found_any = true; - } - } - - if found_any { - let size_gb = total_size as f32 / (1024.0 * 1024.0 * 1024.0); - Some(size_gb.max(0.001)) - } else { - None - } - } - - /// Parse size strings like "1.2M", "45.6K", "2.1G" to bytes - fn parse_size_string(&self, size_str: &str) -> Option { - let size_str = size_str.trim(); - if size_str.is_empty() { - return None; - } - - let (number_part, unit) = if size_str.ends_with('K') { - (size_str.trim_end_matches('K'), 1024u64) - } else if size_str.ends_with('M') { - (size_str.trim_end_matches('M'), 1024 * 1024) - } else if size_str.ends_with('G') { - (size_str.trim_end_matches('G'), 1024 * 1024 * 1024) - } else { - (size_str, 1) - }; - - if let Ok(number) = number_part.parse::() { - Some((number * unit as f64) as u64) - } else { - None - } - } - - /// Use process information to find file usage - fn get_process_file_usage(&self, service: &str) -> Option { - // Get main PID - let output = Command::new("systemctl") - .arg("show") - .arg(format!("{}.service", service)) - .arg("--property=MainPID") - .output() - .ok()?; - - let output_str = String::from_utf8(output.stdout).ok()?; - for line in output_str.lines() { - if line.starts_with("MainPID=") { - let pid_str = line.trim_start_matches("MainPID="); - if let Ok(pid) = pid_str.parse::() { - if pid > 0 { - return self.get_process_open_files_size(pid); - } - } - } - } - None - } - - /// Get size of files opened by a process - fn get_process_open_files_size(&self, pid: u32) -> Option { - let mut total_size = 0u64; - let mut found_any = false; - - // Check /proc/PID/fd/ for open file descriptors - let fd_dir = format!("/proc/{}/fd", pid); - if let Ok(entries) = std::fs::read_dir(&fd_dir) { - for entry in entries.flatten() { - if let Ok(link) = std::fs::read_link(entry.path()) { - if let Some(path_str) = link.to_str() { - // Skip special files, focus on regular files - if !path_str.starts_with("/dev/") - && !path_str.starts_with("/proc/") - && !path_str.starts_with("[") - { - if let Ok(metadata) = std::fs::metadata(&link) { - total_size += metadata.len(); - found_any = true; - } - } - } - } - } - } - - if found_any { - let size_gb = total_size as f32 / (1024.0 * 1024.0 * 1024.0); - Some(size_gb.max(0.001)) - } else { - None - } - } - - /// Estimate disk usage based on service type and memory usage - fn estimate_service_disk_usage(&self, service: &str) -> Option { - // Get memory usage to help estimate disk usage - let memory_mb = self.get_service_memory(service).unwrap_or(0.0); - - let estimated_gb = match service { - // Database services typically have significant disk usage - s if s.contains("mysql") || s.contains("postgres") || s.contains("redis") => { - (memory_mb / 100.0).max(0.1) // Estimate based on memory - } - // Web services and applications - s if s.contains("nginx") || s.contains("apache") => 0.05, // ~50MB for configs/logs - s if s.contains("gitea") => (memory_mb / 50.0).max(0.5), // Code repositories - s if s.contains("docker") => 1.0, // Docker has significant overhead - // System services - s if s.contains("ssh") || s.contains("postfix") => 0.01, // ~10MB for configs/logs - // Default small footprint - _ => 0.005, // ~5MB minimum - }; - - Some(estimated_gb) - } } #[async_trait] @@ -660,7 +445,7 @@ impl Collector for SystemdCollector { } // Service disk usage (comprehensive detection) - if let Some(disk_gb) = self.get_comprehensive_service_disk_usage(service) { + if let Some(disk_gb) = self.get_service_disk_usage(service) { metrics.push(Metric { name: format!("service_{}_disk_gb", service), value: MetricValue::Float(disk_gb),