use async_trait::async_trait; use cm_dashboard_shared::{Metric, MetricValue, Status, registry}; use std::time::Duration; use tracing::debug; use super::{Collector, CollectorError, utils}; use crate::config::MemoryConfig; /// Extremely efficient memory metrics collector /// /// EFFICIENCY OPTIMIZATIONS: /// - Single /proc/meminfo read for all memory metrics /// - Minimal string parsing with split operations /// - Pre-calculated KB to GB conversion /// - No regex or complex parsing /// - <0.1ms collection time target pub struct MemoryCollector { config: MemoryConfig, name: String, } /// Memory information parsed from /proc/meminfo #[derive(Debug, Default)] struct MemoryInfo { total_kb: u64, available_kb: u64, free_kb: u64, buffers_kb: u64, cached_kb: u64, swap_total_kb: u64, swap_free_kb: u64, } impl MemoryCollector { pub fn new(config: MemoryConfig) -> Self { Self { config, name: "memory".to_string(), } } /// Calculate memory usage status using configured thresholds fn calculate_usage_status(&self, usage_percent: f32) -> Status { if usage_percent >= self.config.usage_critical_percent { Status::Critical } else if usage_percent >= self.config.usage_warning_percent { Status::Warning } else { Status::Ok } } /// Parse /proc/meminfo efficiently /// Format: "MemTotal: 16384000 kB" async fn parse_meminfo(&self) -> Result { let content = utils::read_proc_file("/proc/meminfo")?; let mut info = MemoryInfo::default(); // Parse each line efficiently - only extract what we need for line in content.lines() { if let Some(colon_pos) = line.find(':') { let key = &line[..colon_pos]; let value_part = &line[colon_pos + 1..]; // Extract number from value part (format: " 12345 kB") if let Some(number_str) = value_part.split_whitespace().next() { if let Ok(value_kb) = utils::parse_u64(number_str) { match key { "MemTotal" => info.total_kb = value_kb, "MemAvailable" => info.available_kb = value_kb, "MemFree" => info.free_kb = value_kb, "Buffers" => info.buffers_kb = value_kb, "Cached" => info.cached_kb = value_kb, "SwapTotal" => info.swap_total_kb = value_kb, "SwapFree" => info.swap_free_kb = value_kb, _ => {} // Skip other fields for efficiency } } } } } // Validate that we got essential fields if info.total_kb == 0 { return Err(CollectorError::Parse { value: "MemTotal".to_string(), error: "MemTotal not found or zero in /proc/meminfo".to_string(), }); } // If MemAvailable is not available (older kernels), calculate it if info.available_kb == 0 { info.available_kb = info.free_kb + info.buffers_kb + info.cached_kb; } Ok(info) } /// Convert KB to GB efficiently (avoiding floating point in hot path) fn kb_to_gb(kb: u64) -> f32 { kb as f32 / 1_048_576.0 // 1024 * 1024 } /// Calculate memory metrics from parsed info fn calculate_metrics(&self, info: &MemoryInfo) -> Vec { let mut metrics = Vec::with_capacity(6); // Calculate derived values let used_kb = info.total_kb - info.available_kb; let usage_percent = (used_kb as f32 / info.total_kb as f32) * 100.0; let usage_status = self.calculate_usage_status(usage_percent); let swap_used_kb = info.swap_total_kb - info.swap_free_kb; // Convert to GB for metrics let total_gb = Self::kb_to_gb(info.total_kb); let used_gb = Self::kb_to_gb(used_kb); let available_gb = Self::kb_to_gb(info.available_kb); let swap_total_gb = Self::kb_to_gb(info.swap_total_kb); let swap_used_gb = Self::kb_to_gb(swap_used_kb); // Memory usage percentage (primary metric with status) metrics.push(Metric::new( registry::MEMORY_USAGE_PERCENT.to_string(), MetricValue::Float(usage_percent), usage_status, ).with_description("Memory usage percentage".to_string()) .with_unit("%".to_string())); // Total memory metrics.push(Metric::new( registry::MEMORY_TOTAL_GB.to_string(), MetricValue::Float(total_gb), Status::Ok, // Total memory doesn't have status ).with_description("Total system memory".to_string()) .with_unit("GB".to_string())); // Used memory metrics.push(Metric::new( registry::MEMORY_USED_GB.to_string(), MetricValue::Float(used_gb), Status::Ok, // Used memory absolute value doesn't have status ).with_description("Used system memory".to_string()) .with_unit("GB".to_string())); // Available memory metrics.push(Metric::new( registry::MEMORY_AVAILABLE_GB.to_string(), MetricValue::Float(available_gb), Status::Ok, // Available memory absolute value doesn't have status ).with_description("Available system memory".to_string()) .with_unit("GB".to_string())); // Swap metrics (only if swap exists) if info.swap_total_kb > 0 { metrics.push(Metric::new( registry::MEMORY_SWAP_TOTAL_GB.to_string(), MetricValue::Float(swap_total_gb), Status::Ok, ).with_description("Total swap space".to_string()) .with_unit("GB".to_string())); metrics.push(Metric::new( registry::MEMORY_SWAP_USED_GB.to_string(), MetricValue::Float(swap_used_gb), Status::Ok, ).with_description("Used swap space".to_string()) .with_unit("GB".to_string())); } metrics } } #[async_trait] impl Collector for MemoryCollector { fn name(&self) -> &str { &self.name } async fn collect(&self) -> Result, CollectorError> { debug!("Collecting memory metrics"); let start = std::time::Instant::now(); // Parse memory info from /proc/meminfo let info = self.parse_meminfo().await?; // Calculate all metrics from parsed info let metrics = self.calculate_metrics(&info); let duration = start.elapsed(); debug!("Memory collection completed in {:?} with {} metrics", duration, metrics.len()); // Efficiency check: warn if collection takes too long if duration.as_millis() > 1 { debug!("Memory collection took {}ms - consider optimization", duration.as_millis()); } // Store performance metrics // Performance tracking handled by cache system Ok(metrics) } fn get_performance_metrics(&self) -> Option { None // Performance tracking handled by cache system } }