use super::ResourceUsage;
use std::time::{Duration, Instant};
const BYTES_PER_MB: f64 = 1024.0 * 1024.0;
pub struct SystemProbe {
system: sysinfo::System,
networks: sysinfo::Networks,
pid: Option<sysinfo::Pid>,
last_cpu_refresh: Option<Instant>,
last_network_refresh: Option<Instant>,
last_disk_refresh: Option<Instant>,
cpu_ready: bool,
}
impl std::fmt::Debug for SystemProbe {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("SystemProbe")
.field("cpu_ready", &self.cpu_ready)
.field("has_pid", &self.pid.is_some())
.finish()
}
}
impl Default for SystemProbe {
fn default() -> Self {
Self::new()
}
}
impl SystemProbe {
pub fn new() -> Self {
let mut system = sysinfo::System::new();
system.refresh_memory();
system.refresh_cpu_usage();
let networks = sysinfo::Networks::new_with_refreshed_list();
let pid = sysinfo::get_current_pid().ok();
let now = Instant::now();
let mut probe = Self {
system,
networks,
pid,
last_cpu_refresh: Some(now),
last_network_refresh: Some(now),
last_disk_refresh: None,
cpu_ready: false,
};
probe.refresh_process();
probe.last_disk_refresh = Some(now);
probe
}
fn refresh_process(&mut self) {
if let Some(pid) = self.pid {
self.system.refresh_processes_specifics(
sysinfo::ProcessesToUpdate::Some(&[pid]),
false,
sysinfo::ProcessRefreshKind::nothing()
.with_disk_usage()
.with_memory(),
);
}
}
pub fn sample(&mut self) -> ResourceUsage {
let now = Instant::now();
let mut usage = ResourceUsage {
timestamp: now,
..ResourceUsage::default()
};
self.system.refresh_memory();
usage.memory_usage_mb = (self.system.used_memory() / 1024 / 1024) as usize;
usage.total_memory_mb = (self.system.total_memory() / 1024 / 1024) as usize;
let cpu_elapsed = self
.last_cpu_refresh
.map(|last| now.saturating_duration_since(last))
.unwrap_or(Duration::ZERO);
if cpu_elapsed >= sysinfo::MINIMUM_CPU_UPDATE_INTERVAL {
self.system.refresh_cpu_usage();
self.last_cpu_refresh = Some(now);
self.cpu_ready = true;
}
if self.cpu_ready {
let percent = self.system.global_cpu_usage() as f64;
if percent.is_finite() {
usage.cpu_usage_percent = percent.clamp(0.0, 100.0);
usage.cpu_usage_percent_valid = true;
}
}
if let Some(last) = self.last_network_refresh {
let elapsed = now.saturating_duration_since(last).as_secs_f64();
self.networks.refresh(true);
self.last_network_refresh = Some(now);
if elapsed > 0.0 {
let bytes: u64 = self
.networks
.list()
.values()
.map(|interface| interface.received().saturating_add(interface.transmitted()))
.sum();
usage.network_io_mbps = Some(bytes as f64 / BYTES_PER_MB / elapsed);
}
}
if let Some(pid) = self.pid {
let elapsed = self
.last_disk_refresh
.map(|last| now.saturating_duration_since(last).as_secs_f64())
.unwrap_or(0.0);
self.refresh_process();
self.last_disk_refresh = Some(now);
if let Some(process) = self.system.process(pid) {
usage.process_memory_mb = Some((process.memory() / 1024 / 1024) as usize);
if elapsed > 0.0 {
let disk = process.disk_usage();
let bytes = disk.read_bytes.saturating_add(disk.written_bytes);
usage.disk_io_mbps = Some(bytes as f64 / BYTES_PER_MB / elapsed);
}
}
}
usage.active_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1);
usage
}
}