use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum MemoryPressure {
Low,
Moderate,
High,
Critical,
}
impl MemoryPressure {
pub fn from_utilization(utilization: f32) -> Self {
if utilization < 0.5 {
Self::Low
} else if utilization < 0.75 {
Self::Moderate
} else if utilization < 0.9 {
Self::High
} else {
Self::Critical
}
}
pub fn quality_factor(self) -> f32 {
match self {
Self::Low => 1.0,
Self::Moderate => 0.9,
Self::High => 0.7,
Self::Critical => 0.5,
}
}
pub fn should_block_new(self) -> bool {
matches!(self, Self::Critical)
}
pub fn should_pause_background(self) -> bool {
matches!(self, Self::High | Self::Critical)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MemoryStats {
pub total_allocations: u64,
pub current_allocations: u64,
pub total_allocated: u64,
pub total_deallocated: u64,
pub peak_usage: u64,
pub critical_pressure_count: u64,
}
pub struct GpuMemoryTracker {
capacity: u64,
used: AtomicU64,
peak: AtomicU64,
total_allocations: AtomicU64,
current_allocations: AtomicU64,
total_allocated: AtomicU64,
total_deallocated: AtomicU64,
critical_events: AtomicU64,
}
impl GpuMemoryTracker {
pub fn new(capacity: u64) -> Self {
Self {
capacity,
used: AtomicU64::new(0),
peak: AtomicU64::new(0),
total_allocations: AtomicU64::new(0),
current_allocations: AtomicU64::new(0),
total_allocated: AtomicU64::new(0),
total_deallocated: AtomicU64::new(0),
critical_events: AtomicU64::new(0),
}
}
pub fn capacity(&self) -> u64 {
self.capacity
}
pub fn used(&self) -> u64 {
self.used.load(Ordering::Relaxed)
}
pub fn available(&self) -> u64 {
self.capacity.saturating_sub(self.used())
}
pub fn utilization(&self) -> f32 {
if self.capacity == 0 {
return 1.0;
}
self.used() as f32 / self.capacity as f32
}
pub fn pressure(&self) -> f32 {
self.utilization()
}
pub fn pressure_level(&self) -> MemoryPressure {
MemoryPressure::from_utilization(self.utilization())
}
pub fn try_allocate(&self, bytes: u64) -> bool {
let mut current = self.used.load(Ordering::Relaxed);
loop {
let new = current + bytes;
if new > self.capacity {
return false;
}
match self
.used
.compare_exchange_weak(current, new, Ordering::AcqRel, Ordering::Relaxed)
{
Ok(_) => {
self.total_allocations.fetch_add(1, Ordering::Relaxed);
self.current_allocations.fetch_add(1, Ordering::Relaxed);
self.total_allocated.fetch_add(bytes, Ordering::Relaxed);
let mut peak = self.peak.load(Ordering::Relaxed);
while new > peak {
match self.peak.compare_exchange_weak(
peak,
new,
Ordering::AcqRel,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(p) => peak = p,
}
}
if MemoryPressure::from_utilization(new as f32 / self.capacity as f32)
== MemoryPressure::Critical
{
self.critical_events.fetch_add(1, Ordering::Relaxed);
}
return true;
},
Err(c) => current = c,
}
}
}
pub fn allocate(&self, bytes: u64) {
self.used.fetch_add(bytes, Ordering::Relaxed);
self.total_allocations.fetch_add(1, Ordering::Relaxed);
self.current_allocations.fetch_add(1, Ordering::Relaxed);
self.total_allocated.fetch_add(bytes, Ordering::Relaxed);
let new = self.used.load(Ordering::Relaxed);
let mut peak = self.peak.load(Ordering::Relaxed);
while new > peak {
match self
.peak
.compare_exchange_weak(peak, new, Ordering::AcqRel, Ordering::Relaxed)
{
Ok(_) => break,
Err(p) => peak = p,
}
}
}
pub fn deallocate(&self, bytes: u64) {
self.used
.fetch_sub(bytes.min(self.used()), Ordering::Relaxed);
self.current_allocations.fetch_sub(1, Ordering::Relaxed);
self.total_deallocated.fetch_add(bytes, Ordering::Relaxed);
}
pub fn stats(&self) -> MemoryStats {
MemoryStats {
total_allocations: self.total_allocations.load(Ordering::Relaxed),
current_allocations: self.current_allocations.load(Ordering::Relaxed),
total_allocated: self.total_allocated.load(Ordering::Relaxed),
total_deallocated: self.total_deallocated.load(Ordering::Relaxed),
peak_usage: self.peak.load(Ordering::Relaxed),
critical_pressure_count: self.critical_events.load(Ordering::Relaxed),
}
}
#[cfg(test)]
pub fn reset(&self) {
self.used.store(0, Ordering::Relaxed);
self.peak.store(0, Ordering::Relaxed);
self.total_allocations.store(0, Ordering::Relaxed);
self.current_allocations.store(0, Ordering::Relaxed);
self.total_allocated.store(0, Ordering::Relaxed);
self.total_deallocated.store(0, Ordering::Relaxed);
self.critical_events.store(0, Ordering::Relaxed);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pressure_levels() {
assert_eq!(MemoryPressure::from_utilization(0.3), MemoryPressure::Low);
assert_eq!(
MemoryPressure::from_utilization(0.6),
MemoryPressure::Moderate
);
assert_eq!(MemoryPressure::from_utilization(0.8), MemoryPressure::High);
assert_eq!(
MemoryPressure::from_utilization(0.95),
MemoryPressure::Critical
);
}
#[test]
fn test_tracker_allocation() {
let tracker = GpuMemoryTracker::new(1000);
assert!(tracker.try_allocate(500));
assert_eq!(tracker.used(), 500);
assert_eq!(tracker.available(), 500);
assert!(tracker.try_allocate(400));
assert_eq!(tracker.used(), 900);
assert!(!tracker.try_allocate(200));
assert_eq!(tracker.used(), 900);
}
#[test]
fn test_tracker_deallocation() {
let tracker = GpuMemoryTracker::new(1000);
tracker.allocate(500);
tracker.deallocate(300);
assert_eq!(tracker.used(), 200);
assert_eq!(tracker.available(), 800);
}
#[test]
fn test_utilization() {
let tracker = GpuMemoryTracker::new(1000);
tracker.allocate(750);
assert!((tracker.utilization() - 0.75).abs() < 0.001);
assert_eq!(tracker.pressure_level(), MemoryPressure::High);
}
#[test]
fn test_peak_tracking() {
let tracker = GpuMemoryTracker::new(1000);
tracker.allocate(800);
tracker.deallocate(500);
tracker.allocate(200);
let stats = tracker.stats();
assert_eq!(stats.peak_usage, 800);
}
}