//! Host metrics sampler for the admin dashboard: CPU load, RAM, free disk at //! the models dir, and the gateway's own RSS. One background task refreshes a //! snapshot every 2 s; the API endpoint only reads it. use serde::Serialize; use std::sync::{Arc, Mutex}; #[derive(Clone, Default, Serialize)] pub struct Snapshot { pub ts: u64, /// Whole-machine CPU usage 0..100 (average over all cores). pub cpu_percent: f32, pub cores: usize, pub load1: f64, pub ram_total: u64, pub ram_used: u64, /// Disk that holds models_dir. pub disk_total: u64, pub disk_free: u64, /// Resident memory of the gateway process itself. pub process_rss: u64, } pub struct SysMon { snap: Mutex, } impl SysMon { pub fn snapshot(&self) -> Snapshot { self.snap.lock().unwrap().clone() } } fn now() -> u64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() .as_secs() } /// Start the sampler. `models_dir` picks which disk's free space is reported. pub fn spawn(models_dir: String) -> Arc { let mon = Arc::new(SysMon { snap: Mutex::new(Snapshot::default()), }); let ret = mon.clone(); tokio::task::spawn_blocking(move || { use sysinfo::{Disks, ProcessesToUpdate, System}; let mut sys = System::new(); let pid = sysinfo::Pid::from_u32(std::process::id()); let models_abs = std::fs::canonicalize(&models_dir) .unwrap_or_else(|_| std::path::PathBuf::from(&models_dir)); loop { sys.refresh_cpu_usage(); sys.refresh_memory(); let _ = sys.refresh_processes(ProcessesToUpdate::Some(&[pid]), true); let disks = Disks::new_with_refreshed_list(); // the disk whose mount point is the longest prefix of models_dir let mut best: Option<(usize, u64, u64)> = None; for d in disks.list() { let mp = d.mount_point(); if models_abs.starts_with(mp) { let len = mp.as_os_str().len(); if best.map(|(l, _, _)| len > l).unwrap_or(true) { best = Some((len, d.total_space(), d.available_space())); } } } let (disk_total, disk_free) = best.map(|(_, t, f)| (t, f)).unwrap_or((0, 0)); let snap = Snapshot { ts: now(), cpu_percent: sys.global_cpu_usage(), cores: sys.cpus().len(), load1: System::load_average().one, ram_total: sys.total_memory(), ram_used: sys.used_memory(), disk_total, disk_free, process_rss: sys.process(pid).map(|p| p.memory()).unwrap_or(0), }; *mon.snap.lock().unwrap() = snap; std::thread::sleep(std::time::Duration::from_secs(2)); } }); ret }