use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
use thiserror::Error;
#[derive(Debug, Clone, Error)]
pub enum MemoryError {
#[error(
"Memory limit exceeded: requested {requested} bytes, available {available} bytes, limit {limit} bytes"
)]
LimitExceeded {
requested: usize,
available: usize,
limit: usize,
},
#[error("Allocation failed: {0}")]
AllocationFailed(String),
#[error(
"Deallocation underflow: tried to free {requested} bytes but only {tracked} bytes tracked"
)]
DeallocationUnderflow {
requested: usize,
tracked: usize,
},
#[error("Invalid memory limit: {0}")]
InvalidLimit(String),
}
#[derive(Debug, Clone, Default)]
pub struct MemoryBreakdown {
pub cache_memory: usize,
pub index_memory: usize,
pub model_memory: usize,
}
#[derive(Debug, Clone, Default)]
pub struct MemoryStats {
pub current_bytes: usize,
pub peak_bytes: usize,
pub limit_bytes: usize,
pub allocation_count: usize,
pub deallocation_count: usize,
pub breakdown: MemoryBreakdown,
}
impl MemoryStats {
#[must_use]
pub fn usage_percentage(&self) -> f64 {
if self.limit_bytes == 0 {
0.0
} else {
#[allow(clippy::cast_precision_loss)]
{
(self.current_bytes as f64 / self.limit_bytes as f64) * 100.0
}
}
}
#[must_use]
pub fn available_bytes(&self) -> usize {
if self.limit_bytes == 0 {
usize::MAX
} else {
self.limit_bytes.saturating_sub(self.current_bytes)
}
}
#[must_use]
pub fn is_within_limit(&self) -> bool {
self.limit_bytes == 0 || self.current_bytes <= self.limit_bytes
}
}
pub struct MemoryMonitor {
current_bytes: AtomicUsize,
peak_bytes: AtomicUsize,
limit_bytes: AtomicUsize,
allocation_count: AtomicUsize,
deallocation_count: AtomicUsize,
breakdown: RwLock<MemoryBreakdown>,
}
impl Default for MemoryMonitor {
fn default() -> Self {
Self::new()
}
}
impl MemoryMonitor {
#[must_use]
pub fn new() -> Self {
Self {
current_bytes: AtomicUsize::new(0),
peak_bytes: AtomicUsize::new(0),
limit_bytes: AtomicUsize::new(0),
allocation_count: AtomicUsize::new(0),
deallocation_count: AtomicUsize::new(0),
breakdown: RwLock::new(MemoryBreakdown::default()),
}
}
#[must_use]
pub fn with_limit(limit_bytes: usize) -> Self {
Self {
current_bytes: AtomicUsize::new(0),
peak_bytes: AtomicUsize::new(0),
limit_bytes: AtomicUsize::new(limit_bytes),
allocation_count: AtomicUsize::new(0),
deallocation_count: AtomicUsize::new(0),
breakdown: RwLock::new(MemoryBreakdown::default()),
}
}
#[must_use]
pub fn current_usage(&self) -> usize {
self.current_bytes.load(Ordering::Relaxed)
}
#[must_use]
pub fn peak_usage(&self) -> usize {
self.peak_bytes.load(Ordering::Relaxed)
}
#[must_use]
pub fn limit(&self) -> usize {
self.limit_bytes.load(Ordering::Relaxed)
}
pub fn set_limit(&self, bytes: usize) {
self.limit_bytes.store(bytes, Ordering::Relaxed);
}
pub fn check_limit(&self) -> Result<(), MemoryError> {
let current = self.current_bytes.load(Ordering::Relaxed);
let limit = self.limit_bytes.load(Ordering::Relaxed);
if limit > 0 && current > limit {
Err(MemoryError::LimitExceeded {
requested: 0,
available: 0,
limit,
})
} else {
Ok(())
}
}
pub fn check_allocation(&self, bytes: usize) -> Result<(), MemoryError> {
let current = self.current_bytes.load(Ordering::Relaxed);
let limit = self.limit_bytes.load(Ordering::Relaxed);
if limit > 0 && current.saturating_add(bytes) > limit {
Err(MemoryError::LimitExceeded {
requested: bytes,
available: limit.saturating_sub(current),
limit,
})
} else {
Ok(())
}
}
pub fn register_allocation(&self, bytes: usize) {
let new_current = self.current_bytes.fetch_add(bytes, Ordering::Relaxed) + bytes;
self.allocation_count.fetch_add(1, Ordering::Relaxed);
let mut current_peak = self.peak_bytes.load(Ordering::Relaxed);
while new_current > current_peak {
match self.peak_bytes.compare_exchange_weak(
current_peak,
new_current,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(x) => current_peak = x,
}
}
}
pub fn unregister_allocation(&self, bytes: usize) -> Result<(), MemoryError> {
let current = self.current_bytes.load(Ordering::Relaxed);
if bytes > current {
return Err(MemoryError::DeallocationUnderflow {
requested: bytes,
tracked: current,
});
}
self.current_bytes.fetch_sub(bytes, Ordering::Relaxed);
self.deallocation_count.fetch_add(1, Ordering::Relaxed);
Ok(())
}
pub fn update_breakdown(&self, component: MemoryComponent, bytes: usize) {
if let Ok(mut breakdown) = self.breakdown.write() {
match component {
MemoryComponent::Cache => breakdown.cache_memory = bytes,
MemoryComponent::Index => breakdown.index_memory = bytes,
MemoryComponent::Model => breakdown.model_memory = bytes,
}
}
}
#[must_use]
pub fn stats(&self) -> MemoryStats {
let breakdown = self.breakdown.read().map(|b| b.clone()).unwrap_or_default();
MemoryStats {
current_bytes: self.current_bytes.load(Ordering::Relaxed),
peak_bytes: self.peak_bytes.load(Ordering::Relaxed),
limit_bytes: self.limit_bytes.load(Ordering::Relaxed),
allocation_count: self.allocation_count.load(Ordering::Relaxed),
deallocation_count: self.deallocation_count.load(Ordering::Relaxed),
breakdown,
}
}
pub fn reset(&self) {
self.current_bytes.store(0, Ordering::Relaxed);
self.peak_bytes.store(0, Ordering::Relaxed);
self.allocation_count.store(0, Ordering::Relaxed);
self.deallocation_count.store(0, Ordering::Relaxed);
if let Ok(mut breakdown) = self.breakdown.write() {
*breakdown = MemoryBreakdown::default();
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemoryComponent {
Cache,
Index,
Model,
}
#[derive(Clone)]
pub struct MemoryBudget {
monitor: Arc<MemoryMonitor>,
}
impl Default for MemoryBudget {
fn default() -> Self {
Self::new()
}
}
impl MemoryBudget {
#[must_use]
pub fn new() -> Self {
Self {
monitor: Arc::new(MemoryMonitor::new()),
}
}
#[must_use]
pub fn with_limit(limit_bytes: usize) -> Self {
Self {
monitor: Arc::new(MemoryMonitor::with_limit(limit_bytes)),
}
}
#[must_use]
pub fn from_monitor(monitor: Arc<MemoryMonitor>) -> Self {
Self { monitor }
}
#[must_use]
pub fn monitor(&self) -> &MemoryMonitor {
&self.monitor
}
pub fn allocate(&self, size: usize) -> Result<MemoryGuard, MemoryError> {
self.monitor.check_allocation(size)?;
self.monitor.register_allocation(size);
Ok(MemoryGuard {
monitor: Arc::clone(&self.monitor),
size,
})
}
#[must_use]
pub fn try_allocate(&self, size: usize) -> Option<MemoryGuard> {
self.allocate(size).ok()
}
#[must_use]
pub fn stats(&self) -> MemoryStats {
self.monitor.stats()
}
#[must_use]
pub fn available(&self) -> usize {
let current = self.monitor.current_usage();
let limit = self.monitor.limit();
if limit == 0 {
usize::MAX
} else {
limit.saturating_sub(current)
}
}
pub fn set_limit(&self, bytes: usize) {
self.monitor.set_limit(bytes);
}
}
pub struct MemoryGuard {
monitor: Arc<MemoryMonitor>,
size: usize,
}
impl MemoryGuard {
#[must_use]
pub fn size(&self) -> usize {
self.size
}
pub fn resize(&mut self, new_size: usize) -> Result<(), MemoryError> {
if new_size > self.size {
let additional = new_size - self.size;
self.monitor.check_allocation(additional)?;
self.monitor.register_allocation(additional);
} else if new_size < self.size {
let freed = self.size - new_size;
let _ = self.monitor.unregister_allocation(freed);
}
self.size = new_size;
Ok(())
}
#[must_use]
pub fn leak(self) -> usize {
let size = self.size;
std::mem::forget(self);
size
}
}
impl Drop for MemoryGuard {
fn drop(&mut self) {
let _ = self.monitor.unregister_allocation(self.size);
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_memory_monitor_basic() {
let monitor = MemoryMonitor::new();
assert_eq!(monitor.current_usage(), 0);
assert_eq!(monitor.peak_usage(), 0);
monitor.register_allocation(1000);
assert_eq!(monitor.current_usage(), 1000);
assert_eq!(monitor.peak_usage(), 1000);
monitor.register_allocation(500);
assert_eq!(monitor.current_usage(), 1500);
assert_eq!(monitor.peak_usage(), 1500);
monitor.unregister_allocation(800).unwrap();
assert_eq!(monitor.current_usage(), 700);
assert_eq!(monitor.peak_usage(), 1500);
}
#[test]
fn test_memory_monitor_with_limit() {
let monitor = MemoryMonitor::with_limit(1000);
assert!(monitor.check_allocation(500).is_ok());
assert!(monitor.check_allocation(1000).is_ok());
assert!(monitor.check_allocation(1001).is_err());
monitor.register_allocation(600);
assert!(monitor.check_allocation(400).is_ok());
assert!(monitor.check_allocation(401).is_err());
}
#[test]
fn test_memory_monitor_set_limit() {
let monitor = MemoryMonitor::new();
assert_eq!(monitor.limit(), 0);
monitor.set_limit(2000);
assert_eq!(monitor.limit(), 2000);
monitor.register_allocation(1500);
assert!(monitor.check_limit().is_ok());
monitor.register_allocation(600);
assert!(monitor.check_limit().is_err());
}
#[test]
fn test_deallocation_underflow() {
let monitor = MemoryMonitor::new();
monitor.register_allocation(100);
let result = monitor.unregister_allocation(200);
assert!(matches!(
result,
Err(MemoryError::DeallocationUnderflow { .. })
));
}
#[test]
fn test_memory_stats() {
let monitor = MemoryMonitor::with_limit(10000);
monitor.register_allocation(1000);
monitor.register_allocation(2000);
monitor.unregister_allocation(500).unwrap();
let stats = monitor.stats();
assert_eq!(stats.current_bytes, 2500);
assert_eq!(stats.peak_bytes, 3000);
assert_eq!(stats.limit_bytes, 10000);
assert_eq!(stats.allocation_count, 2);
assert_eq!(stats.deallocation_count, 1);
assert_eq!(stats.available_bytes(), 7500);
assert!((stats.usage_percentage() - 25.0).abs() < 0.1);
}
#[test]
fn test_memory_breakdown() {
let monitor = MemoryMonitor::new();
monitor.update_breakdown(MemoryComponent::Cache, 1000);
monitor.update_breakdown(MemoryComponent::Index, 2000);
monitor.update_breakdown(MemoryComponent::Model, 5000);
let stats = monitor.stats();
assert_eq!(stats.breakdown.cache_memory, 1000);
assert_eq!(stats.breakdown.index_memory, 2000);
assert_eq!(stats.breakdown.model_memory, 5000);
}
#[test]
fn test_memory_budget_allocation() {
let budget = MemoryBudget::with_limit(1000);
let guard1 = budget.allocate(400).unwrap();
assert_eq!(guard1.size(), 400);
assert_eq!(budget.monitor().current_usage(), 400);
let guard2 = budget.allocate(400).unwrap();
assert_eq!(budget.monitor().current_usage(), 800);
let result = budget.allocate(300);
assert!(result.is_err());
drop(guard1);
assert_eq!(budget.monitor().current_usage(), 400);
let _guard3 = budget.allocate(500).unwrap();
assert_eq!(budget.monitor().current_usage(), 900);
drop(guard2);
}
#[test]
fn test_memory_guard_drop() {
let budget = MemoryBudget::with_limit(1000);
{
let _guard = budget.allocate(500).unwrap();
assert_eq!(budget.monitor().current_usage(), 500);
}
assert_eq!(budget.monitor().current_usage(), 0);
}
#[test]
fn test_memory_guard_resize() {
let budget = MemoryBudget::with_limit(1000);
let mut guard = budget.allocate(300).unwrap();
assert_eq!(guard.size(), 300);
assert_eq!(budget.monitor().current_usage(), 300);
guard.resize(500).unwrap();
assert_eq!(guard.size(), 500);
assert_eq!(budget.monitor().current_usage(), 500);
guard.resize(200).unwrap();
assert_eq!(guard.size(), 200);
assert_eq!(budget.monitor().current_usage(), 200);
let result = guard.resize(1100);
assert!(result.is_err());
}
#[test]
fn test_memory_guard_leak() {
let budget = MemoryBudget::with_limit(1000);
let guard = budget.allocate(500).unwrap();
let size = guard.leak();
assert_eq!(size, 500);
assert_eq!(budget.monitor().current_usage(), 500);
}
#[test]
fn test_try_allocate() {
let budget = MemoryBudget::with_limit(1000);
let guard1 = budget.try_allocate(800);
assert!(guard1.is_some());
let guard2 = budget.try_allocate(300);
assert!(guard2.is_none());
drop(guard1);
}
#[test]
fn test_unlimited_budget() {
let budget = MemoryBudget::new();
assert_eq!(budget.available(), usize::MAX);
let _guard1 = budget.allocate(1_000_000).unwrap();
let _guard2 = budget.allocate(1_000_000_000).unwrap();
let _guard3 = budget.allocate(1_000_000_000).unwrap();
}
#[test]
fn test_memory_monitor_reset() {
let monitor = MemoryMonitor::with_limit(10000);
monitor.register_allocation(5000);
monitor.update_breakdown(MemoryComponent::Cache, 1000);
let stats = monitor.stats();
assert_eq!(stats.current_bytes, 5000);
assert_eq!(stats.breakdown.cache_memory, 1000);
monitor.reset();
let stats = monitor.stats();
assert_eq!(stats.current_bytes, 0);
assert_eq!(stats.peak_bytes, 0);
assert_eq!(stats.allocation_count, 0);
assert_eq!(stats.breakdown.cache_memory, 0);
}
#[test]
fn test_concurrent_allocations() {
let budget = Arc::new(MemoryBudget::with_limit(100_000));
let mut handles = vec![];
for _ in 0..10 {
let b = Arc::clone(&budget);
handles.push(thread::spawn(move || {
for _ in 0..100 {
if let Ok(guard) = b.allocate(10) {
thread::yield_now();
drop(guard);
}
}
}));
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(budget.monitor().current_usage(), 0);
}
#[test]
fn test_memory_stats_within_limit() {
let stats = MemoryStats {
current_bytes: 500,
peak_bytes: 800,
limit_bytes: 1000,
allocation_count: 5,
deallocation_count: 2,
breakdown: MemoryBreakdown::default(),
};
assert!(stats.is_within_limit());
assert_eq!(stats.available_bytes(), 500);
let stats_exceeded = MemoryStats {
current_bytes: 1500,
limit_bytes: 1000,
..stats.clone()
};
assert!(!stats_exceeded.is_within_limit());
}
#[test]
fn test_memory_stats_unlimited() {
let stats = MemoryStats {
current_bytes: 1_000_000,
peak_bytes: 2_000_000,
limit_bytes: 0, allocation_count: 100,
deallocation_count: 50,
breakdown: MemoryBreakdown::default(),
};
assert!(stats.is_within_limit());
assert_eq!(stats.available_bytes(), usize::MAX);
assert_eq!(stats.usage_percentage(), 0.0);
}
#[test]
fn test_memory_error_display() {
let err = MemoryError::LimitExceeded {
requested: 1000,
available: 500,
limit: 2000,
};
let msg = err.to_string();
assert!(msg.contains("1000"));
assert!(msg.contains("500"));
assert!(msg.contains("2000"));
let err = MemoryError::DeallocationUnderflow {
requested: 1000,
tracked: 500,
};
let msg = err.to_string();
assert!(msg.contains("1000"));
assert!(msg.contains("500"));
let err = MemoryError::AllocationFailed("test error".to_string());
assert!(err.to_string().contains("test error"));
let err = MemoryError::InvalidLimit("negative value".to_string());
assert!(err.to_string().contains("negative value"));
}
#[test]
fn test_memory_budget_from_monitor() {
let monitor = Arc::new(MemoryMonitor::with_limit(5000));
monitor.register_allocation(1000);
let budget = MemoryBudget::from_monitor(monitor);
assert_eq!(budget.monitor().current_usage(), 1000);
assert_eq!(budget.monitor().limit(), 5000);
}
#[test]
fn test_peak_tracking_with_fluctuations() {
let monitor = MemoryMonitor::new();
monitor.register_allocation(1000);
assert_eq!(monitor.peak_usage(), 1000);
monitor.register_allocation(2000);
assert_eq!(monitor.peak_usage(), 3000);
monitor.unregister_allocation(2500).unwrap();
assert_eq!(monitor.current_usage(), 500);
assert_eq!(monitor.peak_usage(), 3000);
monitor.register_allocation(1000);
assert_eq!(monitor.peak_usage(), 3000);
monitor.register_allocation(2000);
assert_eq!(monitor.peak_usage(), 3500); }
}