All checks were successful
Build and Release / build-and-release (push) Successful in 1m15s
Root cause: run_command_with_timeout() was calling cmd.spawn() without configuring stdout/stderr pipes. This caused command output to go to journald instead of being captured by wait_with_output(). The disk collector received empty output and failed silently. Solution: Configure stdout(Stdio::piped()) and stderr(Stdio::piped()) before spawning commands. This ensures wait_with_output() can properly capture command output. Fixes: Empty Storage section, lsblk output appearing in journald Bump version to v0.1.222
132 lines
4.3 KiB
Rust
132 lines
4.3 KiB
Rust
use async_trait::async_trait;
|
|
use cm_dashboard_shared::{AgentData};
|
|
use std::process::Output;
|
|
use std::time::Duration;
|
|
|
|
pub mod backup;
|
|
pub mod cpu;
|
|
pub mod disk;
|
|
pub mod error;
|
|
pub mod memory;
|
|
pub mod network;
|
|
pub mod nixos;
|
|
pub mod systemd;
|
|
|
|
pub use error::CollectorError;
|
|
|
|
/// Run a command with a timeout to prevent blocking
|
|
/// Properly kills the process if timeout is exceeded
|
|
pub async fn run_command_with_timeout(mut cmd: tokio::process::Command, timeout_secs: u64) -> std::io::Result<Output> {
|
|
use tokio::time::timeout;
|
|
use std::process::Stdio;
|
|
let timeout_duration = Duration::from_secs(timeout_secs);
|
|
|
|
// Configure stdio to capture output
|
|
cmd.stdout(Stdio::piped());
|
|
cmd.stderr(Stdio::piped());
|
|
|
|
let child = cmd.spawn()?;
|
|
let pid = child.id();
|
|
|
|
match timeout(timeout_duration, child.wait_with_output()).await {
|
|
Ok(result) => result,
|
|
Err(_) => {
|
|
// Timeout - force kill the process using system kill command
|
|
if let Some(process_id) = pid {
|
|
let _ = tokio::process::Command::new("kill")
|
|
.args(&["-9", &process_id.to_string()])
|
|
.output()
|
|
.await;
|
|
}
|
|
Err(std::io::Error::new(
|
|
std::io::ErrorKind::TimedOut,
|
|
format!("Command timed out after {} seconds", timeout_secs)
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// Base trait for all collectors with direct structured data output
|
|
#[async_trait]
|
|
pub trait Collector: Send + Sync {
|
|
/// Collect data and populate AgentData directly with status evaluation
|
|
async fn collect_structured(&self, agent_data: &mut AgentData) -> Result<(), CollectorError>;
|
|
}
|
|
|
|
/// CPU efficiency rules for all collectors
|
|
pub mod efficiency {
|
|
//! CRITICAL: All collectors must follow these efficiency rules to minimize system impact
|
|
//!
|
|
//! # FILE READING RULES
|
|
//! - Read entire files in single syscall when possible
|
|
//! - Use BufReader only for very large files (>4KB)
|
|
//! - Never read files character by character
|
|
//! - Cache file descriptors when safe (immutable paths)
|
|
//!
|
|
//! # PARSING RULES
|
|
//! - Use split() instead of regex for simple patterns
|
|
//! - Parse numbers with from_str() not complex parsing
|
|
//! - Avoid string allocations in hot paths
|
|
//! - Use str::trim() before parsing numbers
|
|
//!
|
|
//! # MEMORY ALLOCATION RULES
|
|
//! - Reuse Vec buffers when possible
|
|
//! - Pre-allocate collections with known sizes
|
|
//! - Use str slices instead of String when possible
|
|
//! - Avoid clone() in hot paths
|
|
//!
|
|
//! # SYSTEM CALL RULES
|
|
//! - Minimize syscalls - prefer single reads over multiple
|
|
//! - Use /proc filesystem efficiently
|
|
//! - Avoid spawning processes when /proc data available
|
|
//! - Cache static data (like CPU count)
|
|
//!
|
|
//! # ERROR HANDLING RULES
|
|
//! - Use Result<> but minimize allocation in error paths
|
|
//! - Log errors at debug level only to avoid I/O overhead
|
|
//! - Graceful degradation - missing metrics better than failing
|
|
//! - Never panic in collectors
|
|
//!
|
|
//! # CONCURRENCY RULES
|
|
//! - Collectors must be thread-safe but avoid locks
|
|
//! - Use atomic operations for simple counters
|
|
//! - Avoid shared mutable state between collections
|
|
//! - Each collection should be independent
|
|
}
|
|
|
|
/// Utility functions for efficient system data collection
|
|
pub mod utils {
|
|
use super::CollectorError;
|
|
use std::fs;
|
|
|
|
/// Read entire file content efficiently
|
|
pub fn read_proc_file(path: &str) -> Result<String, CollectorError> {
|
|
fs::read_to_string(path).map_err(|e| CollectorError::SystemRead {
|
|
path: path.to_string(),
|
|
error: e.to_string(),
|
|
})
|
|
}
|
|
|
|
/// Parse float from string slice efficiently
|
|
pub fn parse_f32(s: &str) -> Result<f32, CollectorError> {
|
|
s.trim()
|
|
.parse()
|
|
.map_err(|e: std::num::ParseFloatError| CollectorError::Parse {
|
|
value: s.to_string(),
|
|
error: e.to_string(),
|
|
})
|
|
}
|
|
|
|
/// Parse integer from string slice efficiently
|
|
pub fn parse_u64(s: &str) -> Result<u64, CollectorError> {
|
|
s.trim()
|
|
.parse()
|
|
.map_err(|e: std::num::ParseIntError| CollectorError::Parse {
|
|
value: s.to_string(),
|
|
error: e.to_string(),
|
|
})
|
|
}
|
|
|
|
}
|