use crate::memory_account::{MemoryAccountant, MemoryAccountingClass, MemoryAttribution};
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug)]
pub struct MemoryManager {
total_allocated: AtomicU64,
max_memory: u64,
accountant: MemoryAccountant,
}
impl MemoryManager {
pub fn new(max_memory: u64) -> Self {
Self {
total_allocated: AtomicU64::new(0),
max_memory,
accountant: MemoryAccountant::new(),
}
}
pub fn max_memory(&self) -> u64 {
self.max_memory
}
pub fn total_allocated(&self) -> u64 {
self.total_allocated.load(Ordering::Relaxed)
}
pub fn allocate(&self, amount: u64) {
self.allocate_with(
MemoryAttribution {
domain: "other",
class: MemoryAccountingClass::Other,
},
amount,
);
}
pub fn allocate_with(&self, attr: MemoryAttribution, amount: u64) {
self.total_allocated.fetch_add(amount, Ordering::Relaxed);
self.accountant.allocate(attr, amount);
}
pub fn deallocate(&self, amount: u64) {
self.deallocate_with(
MemoryAttribution {
domain: "other",
class: MemoryAccountingClass::Other,
},
amount,
);
}
pub fn deallocate_with(&self, attr: MemoryAttribution, amount: u64) {
self.total_allocated.fetch_sub(amount, Ordering::Relaxed);
self.accountant.deallocate(attr, amount);
}
pub fn is_under_limit(&self) -> bool {
self.total_allocated() <= self.max_memory
}
pub fn accountant(&self) -> &MemoryAccountant {
&self.accountant
}
pub fn effective_spill_threshold(&self) -> u64 {
self.max_memory.saturating_sub(self.total_allocated())
}
pub fn memory_pressure(&self) -> f64 {
if self.max_memory == 0 {
return 0.0;
}
(self.total_allocated() as f64 / self.max_memory as f64).clamp(0.0, 1.0)
}
pub fn is_under_memory_pressure(&self, ratio: f64) -> bool {
self.memory_pressure() >= ratio
}
}
impl Default for MemoryManager {
fn default() -> Self {
Self::new(u64::MAX)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::memory_account::{BUFFER_POOL, MemoryAccountingClass};
#[test]
fn test_allocate_with_is_accounted() {
let mm = MemoryManager::new(1024);
mm.allocate_with(BUFFER_POOL, 100);
assert_eq!(mm.total_allocated(), 100);
assert_eq!(mm.accountant().class_usage(MemoryAccountingClass::BufferPool), 100);
mm.deallocate_with(BUFFER_POOL, 100);
assert_eq!(mm.total_allocated(), 0);
}
#[test]
fn test_plain_allocate_uses_other_class() {
let mm = MemoryManager::new(1024);
mm.allocate(64);
assert_eq!(mm.accountant().class_usage(MemoryAccountingClass::Other), 64);
}
#[test]
fn test_effective_spill_threshold() {
let mm = MemoryManager::new(1000);
assert_eq!(mm.effective_spill_threshold(), 1000);
mm.allocate_with(BUFFER_POOL, 300);
assert_eq!(mm.effective_spill_threshold(), 700);
mm.allocate_with(BUFFER_POOL, 10_000);
assert_eq!(mm.effective_spill_threshold(), 0);
}
#[test]
fn test_memory_pressure() {
let mm = MemoryManager::new(100);
assert!((mm.memory_pressure() - 0.0).abs() < 1e-9);
assert!(!mm.is_under_memory_pressure(0.8));
mm.allocate_with(BUFFER_POOL, 90);
assert!((mm.memory_pressure() - 0.9).abs() < 1e-9);
assert!(mm.is_under_memory_pressure(0.8));
}
}