use crate::exec::error::ExecutionError;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[allow(dead_code)]
#[derive(Clone)]
pub struct MemoryBudget {
limit: usize,
allocated: Arc<AtomicUsize>,
peak: Arc<AtomicUsize>,
}
impl std::fmt::Debug for MemoryBudget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MemoryBudget")
.field("limit", &self.limit)
.field("allocated", &self.allocated.load(Ordering::SeqCst))
.field("peak", &self.peak.load(Ordering::SeqCst))
.finish()
}
}
impl MemoryBudget {
#[allow(dead_code)] pub fn new(limit: usize) -> Self {
Self {
limit,
allocated: Arc::new(AtomicUsize::new(0)),
peak: Arc::new(AtomicUsize::new(0)),
}
}
#[allow(dead_code)] pub fn unlimited() -> Self {
Self::new(usize::MAX)
}
#[allow(dead_code)] pub fn allocate(&self, bytes: usize) -> Result<(), ExecutionError> {
let current = self.allocated.fetch_add(bytes, Ordering::SeqCst);
let new_total = current + bytes;
self.peak.fetch_max(new_total, Ordering::SeqCst);
if new_total > self.limit {
self.allocated.fetch_sub(bytes, Ordering::SeqCst);
return Err(ExecutionError::MemoryLimitExceeded {
limit: self.limit,
requested: new_total,
});
}
Ok(())
}
#[allow(dead_code)] pub fn try_allocate(&self, bytes: usize) -> bool {
self.allocate(bytes).is_ok()
}
#[allow(dead_code)] pub fn release(&self, bytes: usize) {
self.allocated.fetch_sub(bytes, Ordering::SeqCst);
}
#[allow(dead_code)] pub fn allocated(&self) -> usize {
self.allocated.load(Ordering::SeqCst)
}
#[allow(dead_code)] pub fn peak(&self) -> usize {
self.peak.load(Ordering::SeqCst)
}
#[allow(dead_code)] pub fn limit(&self) -> usize {
self.limit
}
#[allow(dead_code)] pub fn available(&self) -> usize {
self.limit.saturating_sub(self.allocated())
}
#[allow(dead_code)] pub fn usage_ratio(&self) -> f64 {
if self.limit == 0 {
return 0.0;
}
self.allocated() as f64 / self.limit as f64
}
#[allow(dead_code)] pub fn is_approaching_limit(&self, threshold: f64) -> bool {
self.usage_ratio() > threshold
}
#[allow(dead_code)] pub fn reset(&self) {
self.allocated.store(0, Ordering::SeqCst);
self.peak.store(0, Ordering::SeqCst);
}
#[allow(dead_code)] pub fn stats(&self) -> MemoryStats {
MemoryStats {
limit: self.limit,
allocated: self.allocated(),
peak: self.peak(),
available: self.available(),
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MemoryStats {
pub limit: usize,
pub allocated: usize,
pub peak: usize,
pub available: usize,
}
impl MemoryStats {
#[allow(dead_code)] pub fn format_human_readable(&self) -> String {
format!(
"Memory: {}/{} ({:.1}%), Peak: {}",
Self::format_bytes(self.allocated),
Self::format_bytes(self.limit),
(self.allocated as f64 / self.limit as f64) * 100.0,
Self::format_bytes(self.peak)
)
}
#[allow(dead_code)] fn format_bytes(bytes: usize) -> String {
const KB: usize = 1024;
const MB: usize = KB * 1024;
const GB: usize = MB * 1024;
if bytes >= GB {
format!("{:.2}GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2}MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2}KB", bytes as f64 / KB as f64)
} else {
format!("{}B", bytes)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_budget_basic() {
let budget = MemoryBudget::new(1000);
assert!(budget.allocate(100).is_ok());
assert_eq!(budget.allocated(), 100);
assert!(budget.allocate(200).is_ok());
assert_eq!(budget.allocated(), 300);
budget.release(100);
assert_eq!(budget.allocated(), 200);
}
#[test]
fn test_memory_budget_limit_exceeded() {
let budget = MemoryBudget::new(1000);
assert!(budget.allocate(900).is_ok());
let result = budget.allocate(200);
assert!(result.is_err());
assert!(matches!(
result,
Err(ExecutionError::MemoryLimitExceeded { .. })
));
assert_eq!(budget.allocated(), 900);
}
#[test]
fn test_memory_budget_usage_ratio() {
let budget = MemoryBudget::new(1000);
assert_eq!(budget.usage_ratio(), 0.0);
budget.allocate(500).unwrap();
assert_eq!(budget.usage_ratio(), 0.5);
budget.allocate(250).unwrap();
assert_eq!(budget.usage_ratio(), 0.75);
}
#[test]
fn test_memory_budget_peak_tracking() {
let budget = MemoryBudget::new(1000);
budget.allocate(100).unwrap();
assert_eq!(budget.peak(), 100);
budget.allocate(200).unwrap();
assert_eq!(budget.peak(), 300);
budget.release(150);
assert_eq!(budget.allocated(), 150);
assert_eq!(budget.peak(), 300); }
#[test]
fn test_memory_budget_approaching_limit() {
let budget = MemoryBudget::new(1000);
budget.allocate(500).unwrap();
assert!(!budget.is_approaching_limit(0.9));
budget.allocate(450).unwrap();
assert!(budget.is_approaching_limit(0.9)); }
#[test]
fn test_memory_budget_reset() {
let budget = MemoryBudget::new(1000);
budget.allocate(500).unwrap();
assert_eq!(budget.allocated(), 500);
assert_eq!(budget.peak(), 500);
budget.reset();
assert_eq!(budget.allocated(), 0);
assert_eq!(budget.peak(), 0);
}
#[test]
fn test_memory_budget_unlimited() {
let budget = MemoryBudget::unlimited();
assert!(budget.allocate(1_000_000_000).is_ok());
assert!(budget.allocate(1_000_000_000).is_ok());
}
#[test]
fn test_memory_stats_format() {
let budget = MemoryBudget::new(100 * 1024 * 1024); budget.allocate(50 * 1024 * 1024).unwrap();
let stats = budget.stats();
let formatted = stats.format_human_readable();
assert!(formatted.contains("50.00MB"));
assert!(formatted.contains("100.00MB"));
assert!(formatted.contains("50.0%"));
}
#[test]
fn test_try_allocate() {
let budget = MemoryBudget::new(1000);
assert!(budget.try_allocate(500));
assert_eq!(budget.allocated(), 500);
assert!(!budget.try_allocate(600)); assert_eq!(budget.allocated(), 500); }
#[test]
fn test_available_memory() {
let budget = MemoryBudget::new(1000);
assert_eq!(budget.available(), 1000);
budget.allocate(300).unwrap();
assert_eq!(budget.available(), 700);
budget.allocate(500).unwrap();
assert_eq!(budget.available(), 200);
}
}