//! Process metrics collection. use crate::telemetry::metrics::{raw, Gauge, GaugeExt, Register}; use std::{future::Future, time::Duration}; use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System}; /// The interval at which to update process metrics. const TICK_INTERVAL: Duration = Duration::from_secs(10); /// Process metrics collector. pub struct Metrics { /// Resident set size in bytes. pub rss: Gauge, /// Virtual memory size in bytes. pub virtual_memory: Gauge, /// Process ID. pid: sysinfo::Pid, /// System information handle. system: System, } impl Metrics { /// Initialize process metrics and register them with the given registry. pub fn init(registry: &mut impl Register) -> Self { Self { pid: sysinfo::Pid::from_u32(std::process::id()), rss: registry.register( "process_rss", "Resident set size of the current process", raw::Gauge::default(), ), virtual_memory: registry.register( "process_virtual_memory", "Virtual memory size of the current process", raw::Gauge::default(), ), system: System::new(), } } /// Update all process metrics. fn update(&mut self) { // Refresh process information self.system.refresh_processes_specifics( ProcessesToUpdate::Some(&[self.pid]), false, ProcessRefreshKind::nothing().with_memory(), ); // If the process exists, update the metrics if let Some(process) = self.system.process(self.pid) { let _ = self.rss.try_set(process.memory()); let _ = self.virtual_memory.try_set(process.virtual_memory()); } } /// Update process metrics periodically. /// /// This function takes a sleep function as a parameter to allow different runtimes /// to provide their own implementation. pub async fn collect(mut self, sleep_fn: F) where F: Fn(Duration) -> Fut, Fut: Future, { loop { self.update(); sleep_fn(TICK_INTERVAL).await; } } } #[cfg(test)] #[cfg(not(target_os = "windows"))] mod tests { use super::*; #[test] fn test_process_metrics_init() { let mut registry = crate::telemetry::metrics::Registry::default(); let mut metrics = Metrics::init(&mut registry); // Update metrics metrics.update(); let rss = metrics.rss.get(); assert!(rss > 1024 * 1024); // 1MB let virt = metrics.virtual_memory.get(); assert!( virt >= rss, "Expected virtual memory ({virt}) to be >= RSS ({rss})" ); // Update metrics metrics.update(); let new_rss = metrics.rss.get(); assert!(new_rss > 1024 * 1024); // 1MB let new_virt = metrics.virtual_memory.get(); assert!( new_virt >= new_rss, "Expected virtual memory ({new_virt}) to be >= RSS ({new_rss})" ); // Because tests may be run in parallel, we can't assert anything about the value of the metrics. } }