1use std::collections::HashMap;
39use std::sync::{Arc, RwLock};
40use std::time::{Duration, Instant};
41
42use crate::errors::TaleError;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum MemoryPressure {
47 Low,
49 Moderate,
51 High,
53 Critical,
55}
56
57impl MemoryPressure {
58 pub fn chunk_size_factor(&self) -> f64 {
60 match self {
61 MemoryPressure::Low => 1.0, MemoryPressure::Moderate => 0.8, MemoryPressure::High => 0.5, MemoryPressure::Critical => 0.25, }
66 }
67
68 pub fn requires_optimization(&self) -> bool {
70 matches!(self, MemoryPressure::High | MemoryPressure::Critical)
71 }
72}
73
74#[derive(Debug)]
76pub struct MemoryAllocation {
77 size: usize,
78 reader_id: String,
79 allocated_at: Instant,
80 budget: Arc<RwLock<MemoryBudgetInner>>,
81}
82
83impl MemoryAllocation {
84 fn new(size: usize, reader_id: String, budget: Arc<RwLock<MemoryBudgetInner>>) -> Self {
85 Self {
86 size,
87 reader_id,
88 allocated_at: Instant::now(),
89 budget,
90 }
91 }
92
93 pub fn size(&self) -> usize {
95 self.size
96 }
97
98 pub fn age(&self) -> Duration {
100 self.allocated_at.elapsed()
101 }
102
103 pub fn deallocate(self) {
105 drop(self); }
107}
108
109impl Drop for MemoryAllocation {
110 fn drop(&mut self) {
111 if let Ok(mut budget) = self.budget.write() {
112 budget.deallocate(self.size, &self.reader_id);
113 }
114 }
115}
116
117#[derive(Debug, Clone, Default)]
119pub struct ReaderMemoryStats {
120 pub current_allocation: usize,
122 pub peak_allocation: usize,
124 pub allocation_count: usize,
126 pub allocation_failures: usize,
128 pub total_allocations: usize,
130}
131
132#[derive(Debug)]
134struct MemoryBudgetInner {
135 total_limit: usize,
137 current_usage: usize,
139 peak_usage: usize,
141 reader_stats: HashMap<String, ReaderMemoryStats>,
143 last_system_check: Instant,
145 system_memory_available: usize,
146}
147
148impl MemoryBudgetInner {
149 fn new(total_limit: usize) -> Result<Self, TaleError> {
150 let system_memory = get_system_memory_available()?;
151
152 Ok(Self {
153 total_limit,
154 current_usage: 0,
155 peak_usage: 0,
156 reader_stats: HashMap::new(),
157 last_system_check: Instant::now(),
158 system_memory_available: system_memory,
159 })
160 }
161
162 fn try_allocate(&mut self, size: usize, reader_id: &str) -> Result<bool, TaleError> {
163 if self.last_system_check.elapsed() > Duration::from_secs(1) {
165 self.system_memory_available = get_system_memory_available()?;
166 self.last_system_check = Instant::now();
167 }
168
169 let new_usage = self.current_usage + size;
171 if new_usage > self.total_limit {
172 let stats = self.reader_stats.entry(reader_id.to_string()).or_default();
174 stats.allocation_failures += 1;
175 return Ok(false);
176 }
177
178 let system_safety_margin = self.system_memory_available / 4; if size > system_safety_margin {
181 let stats = self.reader_stats.entry(reader_id.to_string()).or_default();
182 stats.allocation_failures += 1;
183 return Ok(false);
184 }
185
186 self.current_usage = new_usage;
188 self.peak_usage = self.peak_usage.max(new_usage);
189
190 let stats = self.reader_stats.entry(reader_id.to_string()).or_default();
192 stats.current_allocation += size;
193 stats.peak_allocation = stats.peak_allocation.max(stats.current_allocation);
194 stats.allocation_count += 1;
195 stats.total_allocations += 1;
196
197 Ok(true)
198 }
199
200 fn deallocate(&mut self, size: usize, reader_id: &str) {
201 self.current_usage = self.current_usage.saturating_sub(size);
202
203 if let Some(stats) = self.reader_stats.get_mut(reader_id) {
204 stats.current_allocation = stats.current_allocation.saturating_sub(size);
205 stats.allocation_count = stats.allocation_count.saturating_sub(1);
206 }
207 }
208
209 fn current_pressure(&self) -> MemoryPressure {
210 use crate::defaults::SystemDefaults;
211 let usage_ratio = self.current_usage as f64 / self.total_limit as f64;
212
213 match usage_ratio {
214 r if r < SystemDefaults::MEMORY_PRESSURE_LOW_THRESHOLD => MemoryPressure::Low,
215 r if r < SystemDefaults::MEMORY_PRESSURE_MODERATE_THRESHOLD => MemoryPressure::Moderate,
216 r if r < SystemDefaults::MEMORY_PRESSURE_HIGH_THRESHOLD => MemoryPressure::High,
217 _ => MemoryPressure::Critical,
218 }
219 }
220}
221
222#[derive(Debug, Clone)]
224pub struct MemoryBudget {
225 inner: Arc<RwLock<MemoryBudgetInner>>,
226}
227
228impl MemoryBudget {
229 pub fn new(total_limit: usize) -> Result<Self, TaleError> {
231 let inner = Arc::new(RwLock::new(MemoryBudgetInner::new(total_limit)?));
232 Ok(Self { inner })
233 }
234
235 pub fn from_system_memory(percentage: f64) -> Result<Self, TaleError> {
237 let system_memory = get_system_memory_available()?;
238 let limit = (system_memory as f64 * percentage / 100.0) as usize;
239 Self::new(limit)
240 }
241
242 pub fn try_allocate(&self, size: usize, reader_id: &str) -> Result<Option<MemoryAllocation>, TaleError> {
244 let mut inner = self
245 .inner
246 .write()
247 .map_err(|_| TaleError::MemoryError("Failed to acquire budget lock for allocation".to_string()))?;
248
249 if inner.try_allocate(size, reader_id)? {
250 let allocation = MemoryAllocation::new(size, reader_id.to_string(), self.inner.clone());
251 Ok(Some(allocation))
252 } else {
253 Ok(None)
254 }
255 }
256
257 pub fn current_pressure(&self) -> Result<MemoryPressure, TaleError> {
259 let inner = self
260 .inner
261 .read()
262 .map_err(|_| TaleError::MemoryError("Failed to acquire budget lock for pressure check".to_string()))?;
263 Ok(inner.current_pressure())
264 }
265
266 pub fn usage_stats(&self) -> Result<MemoryBudgetStats, TaleError> {
268 let inner = self
269 .inner
270 .read()
271 .map_err(|_| TaleError::MemoryError("Failed to acquire budget lock for stats".to_string()))?;
272
273 Ok(MemoryBudgetStats {
274 total_limit: inner.total_limit,
275 current_usage: inner.current_usage,
276 peak_usage: inner.peak_usage,
277 pressure: inner.current_pressure(),
278 reader_count: inner.reader_stats.len(),
279 system_memory_available: inner.system_memory_available,
280 })
281 }
282
283 pub fn reader_stats(&self, reader_id: &str) -> Result<Option<ReaderMemoryStats>, TaleError> {
285 let inner = self
286 .inner
287 .read()
288 .map_err(|_| TaleError::MemoryError("Failed to acquire budget lock for reader stats".to_string()))?;
289 Ok(inner.reader_stats.get(reader_id).cloned())
290 }
291
292 pub fn recommended_chunk_size(&self, base_size: usize) -> Result<usize, TaleError> {
294 let pressure = self.current_pressure()?;
295 let factor = pressure.chunk_size_factor();
296 Ok((base_size as f64 * factor) as usize)
297 }
298
299 pub fn requires_emergency_measures(&self) -> Result<bool, TaleError> {
301 let pressure = self.current_pressure()?;
302 Ok(matches!(pressure, MemoryPressure::Critical))
303 }
304}
305
306#[derive(Debug, Clone)]
308pub struct MemoryBudgetStats {
309 pub total_limit: usize,
311 pub current_usage: usize,
313 pub peak_usage: usize,
315 pub pressure: MemoryPressure,
317 pub reader_count: usize,
319 pub system_memory_available: usize,
321}
322
323impl MemoryBudgetStats {
324 pub fn usage_percentage(&self) -> f64 {
326 if self.total_limit > 0 {
327 (self.current_usage as f64 / self.total_limit as f64) * 100.0
328 } else {
329 0.0
330 }
331 }
332
333 pub fn available_memory(&self) -> usize {
335 self.total_limit.saturating_sub(self.current_usage)
336 }
337
338 pub fn print_report(&self) {
340 println!("Memory Budget Report:");
341 println!("====================");
342 println!("Total Limit: {} MB", self.total_limit / (1024 * 1024));
343 println!(
344 "Current Usage: {} MB ({:.1}%)",
345 self.current_usage / (1024 * 1024),
346 self.usage_percentage()
347 );
348 println!("Peak Usage: {} MB", self.peak_usage / (1024 * 1024));
349 println!("Available: {} MB", self.available_memory() / (1024 * 1024));
350 println!("Pressure Level: {:?}", self.pressure);
351 println!("Active Readers: {}", self.reader_count);
352 println!("System Memory: {} MB", self.system_memory_available / (1024 * 1024));
353 }
354}
355
356fn get_system_memory_available() -> Result<usize, TaleError> {
358 if let Some(stats) = memory_stats::memory_stats() {
360 Ok(stats.physical_mem)
363 } else {
364 Ok(1024 * 1024 * 1024)
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn _memory_pressure_levels_work() {
375 assert_eq!(MemoryPressure::Low.chunk_size_factor(), 1.0);
376 assert_eq!(MemoryPressure::Moderate.chunk_size_factor(), 0.8);
377 assert_eq!(MemoryPressure::High.chunk_size_factor(), 0.5);
378 assert_eq!(MemoryPressure::Critical.chunk_size_factor(), 0.25);
379 }
380
381 #[test]
382 fn memory_budget_allocation_works() -> Result<(), TaleError> {
383 let budget = MemoryBudget::new(1000)?; let alloc1 = budget.try_allocate(500, "reader1")?;
387 assert!(alloc1.is_some());
388
389 let alloc2 = budget.try_allocate(400, "reader2")?;
391 assert!(alloc2.is_some());
392
393 let alloc3 = budget.try_allocate(200, "reader3")?;
395 assert!(alloc3.is_none());
396
397 drop(alloc1);
399 let alloc4 = budget.try_allocate(300, "reader4")?;
400 assert!(alloc4.is_some());
401
402 Ok(())
403 }
404
405 #[test]
406 fn memory_pressure_calculation_works() -> Result<(), TaleError> {
407 let budget = MemoryBudget::new(1000)?;
408
409 let _alloc1 = budget.try_allocate(500, "reader1")?;
411 assert_eq!(budget.current_pressure()?, MemoryPressure::Low);
412
413 let _alloc2 = budget.try_allocate(150, "reader2")?;
415 assert_eq!(budget.current_pressure()?, MemoryPressure::Moderate);
416
417 let _alloc3 = budget.try_allocate(200, "reader3")?;
419 assert_eq!(budget.current_pressure()?, MemoryPressure::High);
420
421 let _alloc4 = budget.try_allocate(100, "reader4")?;
423 assert_eq!(budget.current_pressure()?, MemoryPressure::Critical);
424
425 Ok(())
426 }
427
428 #[test]
429 fn can_recommend_chunk_size() -> Result<(), TaleError> {
430 let budget = MemoryBudget::new(1000)?;
431
432 assert_eq!(budget.recommended_chunk_size(1000)?, 1000);
434
435 let _alloc = budget.try_allocate(700, "reader1")?;
437 assert_eq!(budget.recommended_chunk_size(1000)?, 800); Ok(())
440 }
441
442 #[test]
443 fn allocation_automatic_cleanup_works() -> Result<(), TaleError> {
444 let budget = MemoryBudget::new(1000)?;
445
446 {
447 let _alloc1 = budget.try_allocate(500, "reader1")?;
448 let _alloc2 = budget.try_allocate(400, "reader2")?;
449 let stats = budget.usage_stats()?;
450 assert_eq!(stats.current_usage, 900);
451 } let stats = budget.usage_stats()?;
455 assert_eq!(stats.current_usage, 0);
456
457 Ok(())
458 }
459}