use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System};
#[derive(Debug, Clone)]
pub struct ProcessMetrics {
system: Arc<std::sync::Mutex<System>>,
pid: sysinfo::Pid,
start_time: f64,
}
impl ProcessMetrics {
#[must_use]
pub fn new(_namespace: &str) -> Self {
let pid = sysinfo::Pid::from_u32(std::process::id());
let system = System::new_with_specifics(
RefreshKind::nothing().with_processes(ProcessRefreshKind::everything()),
);
let start_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0.0, |d| d.as_secs_f64());
Self::register_metrics();
Self {
system: Arc::new(std::sync::Mutex::new(system)),
pid,
start_time,
}
}
fn register_metrics() {
metrics::describe_gauge!(
"process_cpu_seconds_total",
"Total user and system CPU time spent in seconds"
);
metrics::describe_gauge!(
"process_resident_memory_bytes",
"Resident memory size in bytes"
);
metrics::describe_gauge!(
"process_virtual_memory_bytes",
"Virtual memory size in bytes"
);
metrics::describe_gauge!("process_open_fds", "Number of open file descriptors");
metrics::describe_gauge!(
"process_start_time_seconds",
"Start time of the process since unix epoch in seconds"
);
}
pub fn update(&self) {
let mut system = self.system.lock().unwrap_or_else(|e| e.into_inner());
system.refresh_processes_specifics(
ProcessesToUpdate::Some(&[self.pid]),
true,
ProcessRefreshKind::everything(),
);
if let Some(process) = system.process(self.pid) {
let cpu_usage = f64::from(process.cpu_usage());
metrics::gauge!("process_cpu_seconds_total").set(cpu_usage);
let rss = process.memory();
let virtual_mem = process.virtual_memory();
metrics::gauge!("process_resident_memory_bytes").set(rss as f64);
metrics::gauge!("process_virtual_memory_bytes").set(virtual_mem as f64);
#[cfg(target_os = "linux")]
{
if let Ok(fds) = count_open_fds() {
metrics::gauge!("process_open_fds").set(fds as f64);
}
}
metrics::gauge!("process_start_time_seconds").set(self.start_time);
}
}
}
#[cfg(target_os = "linux")]
fn count_open_fds() -> std::io::Result<usize> {
let fd_dir = format!("/proc/{}/fd", std::process::id());
std::fs::read_dir(fd_dir).map(|entries| entries.count())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process_metrics_new() {
let pm = ProcessMetrics::new("test");
assert!(pm.start_time > 0.0);
}
#[test]
fn test_process_metrics_update() {
let pm = ProcessMetrics::new("test");
pm.update();
}
}