This commit addresses several key issues identified during development: Major Changes: - Replace hardcoded top CPU/RAM process display with real system data - Add intelligent process monitoring to CpuCollector using ps command - Fix disk metrics permission issues in systemd collector - Optimize service collection to focus on status, memory, and disk only - Update dashboard widgets to display live process information Process Monitoring Implementation: - Added collect_top_cpu_process() and collect_top_ram_process() methods - Implemented ps-based monitoring with accurate CPU percentages - Added filtering to prevent self-monitoring artifacts (ps commands) - Enhanced error handling and validation for process data - Dashboard now shows realistic values like "claude (PID 2974) 11.0%" Service Collection Optimization: - Removed CPU monitoring from systemd collector for efficiency - Enhanced service directory permission error logging - Simplified services widget to show essential metrics only - Fixed service-to-directory mapping accuracy UI and Dashboard Improvements: - Reorganized dashboard layout with btop-inspired multi-panel design - Updated system panel to include real top CPU/RAM process display - Enhanced widget formatting and data presentation - Removed placeholder/hardcoded data throughout the interface Technical Details: - Updated agent/src/collectors/cpu.rs with process monitoring - Modified dashboard/src/ui/mod.rs for real-time process display - Enhanced systemd collector error handling and disk metrics - Updated CLAUDE.md documentation with implementation details
211 lines
7.5 KiB
Rust
211 lines
7.5 KiB
Rust
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<MemoryInfo, CollectorError> {
|
|
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<Metric> {
|
|
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<Vec<Metric>, 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<super::PerformanceMetrics> {
|
|
None // Performance tracking handled by cache system
|
|
}
|
|
} |