use std::sync::Arc;
use std::time::{Duration, Instant};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceLimits {
pub max_memory_bytes: Option<u64>,
pub max_cpu_seconds: Option<u64>,
pub max_disk_bytes: Option<u64>,
pub max_open_files: Option<u32>,
pub max_duration: Option<Duration>,
pub max_pages: Option<usize>,
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
max_memory_bytes: Some(512 * 1024 * 1024), max_cpu_seconds: Some(3600), max_disk_bytes: Some(1024 * 1024 * 1024), max_open_files: Some(1024),
max_duration: Some(Duration::from_secs(3600)),
max_pages: Some(10000),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ResourceUsage {
pub memory_bytes: u64,
pub cpu_seconds: u64,
pub disk_bytes: u64,
pub open_files: u32,
pub elapsed: Duration,
pub pages_processed: usize,
}
pub struct ResourceMonitor {
limits: ResourceLimits,
usage: Arc<RwLock<ResourceUsage>>,
start_time: Instant,
}
impl ResourceMonitor {
#[must_use]
pub fn new(limits: ResourceLimits) -> Self {
Self {
limits,
usage: Arc::new(RwLock::new(ResourceUsage::default())),
start_time: Instant::now(),
}
}
#[must_use]
pub fn with_default_limits() -> Self {
Self::new(ResourceLimits::default())
}
pub fn update(&self, usage: ResourceUsage) {
*self.usage.write() = usage;
}
pub fn record_page(&self) {
self.usage.write().pages_processed += 1;
}
#[must_use]
pub fn current_usage(&self) -> ResourceUsage {
let mut usage = self.usage.read().clone();
usage.elapsed = self.start_time.elapsed();
usage
}
#[must_use]
pub fn is_over_limit(&self) -> bool {
let usage = self.current_usage();
if let Some(max_mem) = self.limits.max_memory_bytes {
if usage.memory_bytes > max_mem {
return true;
}
}
if let Some(max_cpu) = self.limits.max_cpu_seconds {
if usage.cpu_seconds > max_cpu {
return true;
}
}
if let Some(max_disk) = self.limits.max_disk_bytes {
if usage.disk_bytes > max_disk {
return true;
}
}
if let Some(max_files) = self.limits.max_open_files {
if usage.open_files > max_files {
return true;
}
}
if let Some(max_duration) = self.limits.max_duration {
if usage.elapsed > max_duration {
return true;
}
}
if let Some(max_pages) = self.limits.max_pages {
if usage.pages_processed > max_pages {
return true;
}
}
false
}
#[must_use]
pub fn exceeded_limits(&self) -> Vec<String> {
let usage = self.current_usage();
let mut exceeded = Vec::new();
if let Some(max_mem) = self.limits.max_memory_bytes {
if usage.memory_bytes > max_mem {
exceeded.push(format!(
"Memory: {} / {} bytes",
usage.memory_bytes, max_mem
));
}
}
if let Some(max_cpu) = self.limits.max_cpu_seconds {
if usage.cpu_seconds > max_cpu {
exceeded.push(format!("CPU: {} / {} seconds", usage.cpu_seconds, max_cpu));
}
}
if let Some(max_pages) = self.limits.max_pages {
if usage.pages_processed > max_pages {
exceeded.push(format!("Pages: {} / {}", usage.pages_processed, max_pages));
}
}
if let Some(max_duration) = self.limits.max_duration {
if usage.elapsed > max_duration {
exceeded.push(format!(
"Duration: {:?} / {:?}",
usage.elapsed, max_duration
));
}
}
exceeded
}
}
impl Default for ResourceMonitor {
fn default() -> Self {
Self::with_default_limits()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resource_monitor_default_limits() {
let monitor = ResourceMonitor::with_default_limits();
assert!(!monitor.is_over_limit());
}
#[test]
fn test_resource_monitor_page_limit() {
let limits = ResourceLimits {
max_pages: Some(2),
..Default::default()
};
let monitor = ResourceMonitor::new(limits);
monitor.record_page();
assert!(!monitor.is_over_limit());
monitor.record_page();
assert!(!monitor.is_over_limit());
monitor.record_page();
assert!(monitor.is_over_limit());
}
#[test]
fn test_resource_monitor_exceeded_limits() {
let limits = ResourceLimits {
max_pages: Some(1),
..Default::default()
};
let monitor = ResourceMonitor::new(limits);
monitor.record_page();
monitor.record_page();
let exceeded = monitor.exceeded_limits();
assert!(!exceeded.is_empty());
assert!(exceeded[0].contains("Pages"));
}
}