1use crate::memory_account::{MemoryAccountant, MemoryAccountingClass, MemoryAttribution};
6use std::sync::atomic::{AtomicU64, Ordering};
7
8#[derive(Debug)]
15pub struct MemoryManager {
16 total_allocated: AtomicU64,
17 max_memory: u64,
18 accountant: MemoryAccountant,
19}
20
21impl MemoryManager {
22 pub fn new(max_memory: u64) -> Self {
23 Self {
24 total_allocated: AtomicU64::new(0),
25 max_memory,
26 accountant: MemoryAccountant::new(),
27 }
28 }
29
30 pub fn max_memory(&self) -> u64 {
31 self.max_memory
32 }
33
34 pub fn total_allocated(&self) -> u64 {
35 self.total_allocated.load(Ordering::Relaxed)
36 }
37
38 pub fn allocate(&self, amount: u64) {
43 self.allocate_with(
44 MemoryAttribution {
45 domain: "other",
46 class: MemoryAccountingClass::Other,
47 },
48 amount,
49 );
50 }
51
52 pub fn allocate_with(&self, attr: MemoryAttribution, amount: u64) {
54 self.total_allocated.fetch_add(amount, Ordering::Relaxed);
55 self.accountant.allocate(attr, amount);
56 }
57
58 pub fn deallocate(&self, amount: u64) {
61 self.deallocate_with(
62 MemoryAttribution {
63 domain: "other",
64 class: MemoryAccountingClass::Other,
65 },
66 amount,
67 );
68 }
69
70 pub fn deallocate_with(&self, attr: MemoryAttribution, amount: u64) {
72 self.total_allocated.fetch_sub(amount, Ordering::Relaxed);
73 self.accountant.deallocate(attr, amount);
74 }
75
76 pub fn is_under_limit(&self) -> bool {
77 self.total_allocated() <= self.max_memory
78 }
79
80 pub fn accountant(&self) -> &MemoryAccountant {
82 &self.accountant
83 }
84
85 pub fn effective_spill_threshold(&self) -> u64 {
94 self.max_memory.saturating_sub(self.total_allocated())
95 }
96
97 pub fn memory_pressure(&self) -> f64 {
99 if self.max_memory == 0 {
100 return 0.0;
101 }
102 (self.total_allocated() as f64 / self.max_memory as f64).clamp(0.0, 1.0)
103 }
104
105 pub fn is_under_memory_pressure(&self, ratio: f64) -> bool {
107 self.memory_pressure() >= ratio
108 }
109}
110
111impl Default for MemoryManager {
112 fn default() -> Self {
113 Self::new(u64::MAX)
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::memory_account::{BUFFER_POOL, MemoryAccountingClass};
122
123 #[test]
124 fn test_allocate_with_is_accounted() {
125 let mm = MemoryManager::new(1024);
126 mm.allocate_with(BUFFER_POOL, 100);
127 assert_eq!(mm.total_allocated(), 100);
128 assert_eq!(mm.accountant().class_usage(MemoryAccountingClass::BufferPool), 100);
129 mm.deallocate_with(BUFFER_POOL, 100);
130 assert_eq!(mm.total_allocated(), 0);
131 }
132
133 #[test]
134 fn test_plain_allocate_uses_other_class() {
135 let mm = MemoryManager::new(1024);
136 mm.allocate(64);
137 assert_eq!(mm.accountant().class_usage(MemoryAccountingClass::Other), 64);
138 }
139
140 #[test]
141 fn test_effective_spill_threshold() {
142 let mm = MemoryManager::new(1000);
143 assert_eq!(mm.effective_spill_threshold(), 1000);
144 mm.allocate_with(BUFFER_POOL, 300);
145 assert_eq!(mm.effective_spill_threshold(), 700);
146 mm.allocate_with(BUFFER_POOL, 10_000);
148 assert_eq!(mm.effective_spill_threshold(), 0);
149 }
150
151 #[test]
152 fn test_memory_pressure() {
153 let mm = MemoryManager::new(100);
154 assert!((mm.memory_pressure() - 0.0).abs() < 1e-9);
155 assert!(!mm.is_under_memory_pressure(0.8));
156 mm.allocate_with(BUFFER_POOL, 90);
157 assert!((mm.memory_pressure() - 0.9).abs() < 1e-9);
158 assert!(mm.is_under_memory_pressure(0.8));
159 }
160}