use anyhow::Result;
use sysinfo::{Pid as SysinfoPid, Process, ProcessesToUpdate, System};
use tracing::warn;
#[derive(Debug, Clone)]
pub struct ResourceLimits {
pub memory_mb: Option<u64>,
pub cpu_percent: Option<f32>,
}
pub struct ResourceMonitor {
system: System,
pid: SysinfoPid,
limits: ResourceLimits,
memory_warned: bool,
cpu_warned: bool,
}
#[derive(Debug)]
pub enum ResourceStatus {
Ok,
Exceeded {
resource: &'static str,
current: u64,
limit: u64,
},
ProcessNotFound,
}
impl ResourceMonitor {
pub fn new(pid: u32, limits: ResourceLimits) -> Self {
Self {
system: System::new(),
pid: SysinfoPid::from_u32(pid),
limits,
memory_warned: false,
cpu_warned: false,
}
}
pub fn check_resources(&mut self) -> Result<ResourceStatus> {
self.system
.refresh_processes(ProcessesToUpdate::Some(&[self.pid]), false);
if let Some(process) = self.system.process(self.pid) {
if let Some(limit_mb) = self.limits.memory_mb {
let memory_mb = self.get_memory_usage_mb(process);
if memory_mb > limit_mb {
return Ok(ResourceStatus::Exceeded {
resource: "memory",
current: memory_mb,
limit: limit_mb,
});
} else if memory_mb > (limit_mb * 80 / 100) {
if !self.memory_warned {
warn!(
"Memory usage approaching limit: {} MB / {} MB ({:.1}%)",
memory_mb,
limit_mb,
(memory_mb as f64 / limit_mb as f64) * 100.0
);
self.memory_warned = true;
}
} else {
self.memory_warned = false;
}
}
if let Some(limit_percent) = self.limits.cpu_percent {
let cpu_usage = process.cpu_usage();
if cpu_usage > limit_percent {
return Ok(ResourceStatus::Exceeded {
resource: "cpu",
current: cpu_usage as u64,
limit: limit_percent as u64,
});
} else if cpu_usage > (limit_percent * 0.8) {
if !self.cpu_warned {
warn!("CPU usage approaching limit: {:.1}% / {:.1}%", cpu_usage, limit_percent);
self.cpu_warned = true;
}
} else {
self.cpu_warned = false;
}
}
Ok(ResourceStatus::Ok)
} else {
Ok(ResourceStatus::ProcessNotFound)
}
}
fn get_memory_usage_mb(&self, process: &Process) -> u64 {
process.memory() / 1024 / 1024
}
}
impl ResourceLimits {
pub fn has_limits(&self) -> bool {
self.memory_mb.is_some() || self.cpu_percent.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resource_limits_none() {
let limits = ResourceLimits {
memory_mb: None,
cpu_percent: None,
};
assert!(!limits.has_limits());
}
#[test]
fn test_resource_limits_with_memory() {
let limits = ResourceLimits {
memory_mb: Some(512),
cpu_percent: None,
};
assert!(limits.has_limits());
}
#[test]
fn test_resource_limits_with_cpu() {
let limits = ResourceLimits {
memory_mb: None,
cpu_percent: Some(50.0),
};
assert!(limits.has_limits());
}
}