use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System};
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct SystemStats {
pub cpu_usage: f32,
pub peak_cpu_usage: f32,
pub memory_bytes: u64,
pub peak_memory_bytes: u64,
}
pub struct SystemMonitor {
system: System,
pid: sysinfo::Pid,
peak_cpu: f32,
peak_memory: u64,
}
impl SystemMonitor {
fn refresh_kind() -> ProcessRefreshKind {
ProcessRefreshKind::nothing()
.with_cpu()
.with_memory()
.without_tasks()
}
#[must_use]
pub fn new() -> Self {
let mut system =
System::new_with_specifics(RefreshKind::nothing().with_processes(Self::refresh_kind()));
let pid = sysinfo::get_current_pid().expect("Failed to get current PID");
system.refresh_processes_specifics(
ProcessesToUpdate::Some(&[pid]),
true,
Self::refresh_kind(),
);
Self {
system,
pid,
peak_cpu: 0.0,
peak_memory: 0,
}
}
#[must_use]
pub fn update(&mut self) -> SystemStats {
self.system.refresh_processes_specifics(
ProcessesToUpdate::Some(&[self.pid]),
true,
Self::refresh_kind(),
);
if let Some(process) = self.system.process(self.pid) {
let cpu = process.cpu_usage();
let memory = process.memory();
if cpu > self.peak_cpu {
self.peak_cpu = cpu;
}
if memory > self.peak_memory {
self.peak_memory = memory;
}
SystemStats {
cpu_usage: cpu,
peak_cpu_usage: self.peak_cpu,
memory_bytes: memory,
peak_memory_bytes: self.peak_memory,
}
} else {
SystemStats::default()
}
}
#[must_use]
pub fn current(&self) -> SystemStats {
self.system
.process(self.pid)
.map_or_else(SystemStats::default, |process| SystemStats {
cpu_usage: process.cpu_usage(),
peak_cpu_usage: self.peak_cpu,
memory_bytes: process.memory(),
peak_memory_bytes: self.peak_memory,
})
}
}
impl Default for SystemMonitor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)] mod tests {
use super::*;
#[test]
fn test_refresh_kind_only_enables_needed_process_fields() {
let kind = SystemMonitor::refresh_kind();
assert!(kind.cpu());
assert!(kind.memory());
assert!(!kind.tasks());
assert!(!kind.disk_usage());
}
#[test]
fn test_system_monitor_creation() {
let monitor = SystemMonitor::new();
let stats = monitor.current();
assert!(stats.memory_bytes > 0);
assert!(stats.cpu_usage >= 0.0);
}
#[test]
fn test_system_monitor_update() {
let mut monitor = SystemMonitor::new();
let stats1 = monitor.update();
assert!(stats1.memory_bytes > 0);
let stats2 = monitor.update();
assert!(stats2.memory_bytes > 0);
assert!(stats2.cpu_usage >= 0.0);
assert!(stats2.peak_memory_bytes >= stats1.memory_bytes);
assert!(stats2.peak_memory_bytes >= stats2.memory_bytes);
assert!(stats2.peak_cpu_usage >= stats2.cpu_usage);
}
#[test]
fn test_current_without_update() {
let monitor = SystemMonitor::new();
let stats1 = monitor.current();
let stats2 = monitor.current();
assert_eq!(stats1.memory_bytes, stats2.memory_bytes);
assert_eq!(stats1.cpu_usage, stats2.cpu_usage);
}
#[test]
fn test_system_stats_default() {
let stats = SystemStats::default();
assert_eq!(stats.cpu_usage, 0.0);
assert_eq!(stats.peak_cpu_usage, 0.0);
assert_eq!(stats.memory_bytes, 0);
assert_eq!(stats.peak_memory_bytes, 0);
}
}