Implement comprehensive backup monitoring and fix timestamp issues
- Add BackupCollector for reading TOML status files with disk space metrics - Implement BackupWidget with disk usage display and service status details - Fix backup script disk space parsing by adding missing capture_output=True - Update backup widget to show actual disk usage instead of repository size - Fix timestamp parsing to use backup completion time instead of start time - Resolve timezone issues by using UTC timestamps in backup script - Add disk identification metrics (product name, serial number) to backup status - Enhance UI layout with proper backup monitoring integration
This commit is contained in:
@@ -173,152 +173,7 @@ impl CpuCollector {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Collect top CPU consuming process using ps command for accurate percentages
|
||||
async fn collect_top_cpu_process(&self) -> Result<Option<Metric>, CollectorError> {
|
||||
use std::process::Command;
|
||||
|
||||
// Use ps to get current CPU percentages, sorted by CPU usage
|
||||
let output = Command::new("ps")
|
||||
.arg("aux")
|
||||
.arg("--sort=-%cpu")
|
||||
.arg("--no-headers")
|
||||
.output()
|
||||
.map_err(|e| CollectorError::SystemRead {
|
||||
path: "ps command".to_string(),
|
||||
error: e.to_string(),
|
||||
})?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
// Parse lines and find the first non-ps process (to avoid catching our own ps command)
|
||||
for line in output_str.lines() {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() >= 11 {
|
||||
// ps aux format: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
|
||||
let pid = parts[1];
|
||||
let cpu_percent = parts[2];
|
||||
let full_command = parts[10..].join(" ");
|
||||
|
||||
// Skip ps processes to avoid catching our own ps command
|
||||
if full_command.contains("ps aux") || full_command.starts_with("ps ") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract just the command name (basename of executable)
|
||||
let command_name = if let Some(first_part) = parts.get(10) {
|
||||
// Get just the executable name, not the full path
|
||||
if let Some(basename) = first_part.split('/').last() {
|
||||
basename.to_string()
|
||||
} else {
|
||||
first_part.to_string()
|
||||
}
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
};
|
||||
|
||||
// Validate CPU percentage is reasonable (not over 100% per core)
|
||||
if let Ok(cpu_val) = cpu_percent.parse::<f32>() {
|
||||
if cpu_val > 1000.0 {
|
||||
// Skip obviously wrong values
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let process_info = format!("{} (PID {}) {}%", command_name, pid, cpu_percent);
|
||||
|
||||
return Ok(Some(Metric::new(
|
||||
"top_cpu_process".to_string(),
|
||||
MetricValue::String(process_info),
|
||||
Status::Ok,
|
||||
).with_description("Process consuming the most CPU".to_string())));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Metric::new(
|
||||
"top_cpu_process".to_string(),
|
||||
MetricValue::String("No processes found".to_string()),
|
||||
Status::Ok,
|
||||
).with_description("Process consuming the most CPU".to_string())))
|
||||
}
|
||||
|
||||
/// Collect top RAM consuming process using ps command for accurate memory usage
|
||||
async fn collect_top_ram_process(&self) -> Result<Option<Metric>, CollectorError> {
|
||||
use std::process::Command;
|
||||
|
||||
// Use ps to get current memory usage, sorted by memory
|
||||
let output = Command::new("ps")
|
||||
.arg("aux")
|
||||
.arg("--sort=-%mem")
|
||||
.arg("--no-headers")
|
||||
.output()
|
||||
.map_err(|e| CollectorError::SystemRead {
|
||||
path: "ps command".to_string(),
|
||||
error: e.to_string(),
|
||||
})?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
// Parse lines and find the first non-ps process (to avoid catching our own ps command)
|
||||
for line in output_str.lines() {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() >= 11 {
|
||||
// ps aux format: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
|
||||
let pid = parts[1];
|
||||
let mem_percent = parts[3];
|
||||
let rss_kb = parts[5]; // RSS in KB
|
||||
let full_command = parts[10..].join(" ");
|
||||
|
||||
// Skip ps processes to avoid catching our own ps command
|
||||
if full_command.contains("ps aux") || full_command.starts_with("ps ") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract just the command name (basename of executable)
|
||||
let command_name = if let Some(first_part) = parts.get(10) {
|
||||
// Get just the executable name, not the full path
|
||||
if let Some(basename) = first_part.split('/').last() {
|
||||
basename.to_string()
|
||||
} else {
|
||||
first_part.to_string()
|
||||
}
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
};
|
||||
|
||||
// Convert RSS from KB to MB
|
||||
if let Ok(rss_kb_val) = rss_kb.parse::<u64>() {
|
||||
let rss_mb = rss_kb_val as f32 / 1024.0;
|
||||
|
||||
// Skip processes with very little memory (likely temporary commands)
|
||||
if rss_mb < 1.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let process_info = format!("{} (PID {}) {:.1}MB", command_name, pid, rss_mb);
|
||||
|
||||
return Ok(Some(Metric::new(
|
||||
"top_ram_process".to_string(),
|
||||
MetricValue::String(process_info),
|
||||
Status::Ok,
|
||||
).with_description("Process consuming the most RAM".to_string())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Metric::new(
|
||||
"top_ram_process".to_string(),
|
||||
MetricValue::String("No processes found".to_string()),
|
||||
Status::Ok,
|
||||
).with_description("Process consuming the most RAM".to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -347,15 +202,6 @@ impl Collector for CpuCollector {
|
||||
metrics.push(freq_metric);
|
||||
}
|
||||
|
||||
// Collect top CPU process (optional)
|
||||
if let Some(top_cpu_metric) = self.collect_top_cpu_process().await? {
|
||||
metrics.push(top_cpu_metric);
|
||||
}
|
||||
|
||||
// Collect top RAM process (optional)
|
||||
if let Some(top_ram_metric) = self.collect_top_ram_process().await? {
|
||||
metrics.push(top_ram_metric);
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
debug!("CPU collection completed in {:?} with {} metrics", duration, metrics.len());
|
||||
|
||||
Reference in New Issue
Block a user