Skip to main content

turbo_cdn/
memory_tracker.rs

1//! Memory usage tracking and metrics
2//!
3//! This module provides comprehensive memory usage tracking, including heap allocations,
4//! memory pressure detection, and adaptive behavior based on memory usage patterns.
5
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::sync::Arc;
8use std::time::{Duration, Instant};
9use tracing::{debug, info, warn};
10
11/// Memory usage statistics
12#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
13pub struct MemoryUsage {
14    /// Current heap allocated bytes
15    pub heap_allocated: u64,
16    /// Current heap deallocated bytes
17    pub heap_deallocated: u64,
18    /// Peak memory usage during session
19    pub peak_memory: u64,
20    /// Current memory usage (allocated - deallocated)
21    pub current_memory: u64,
22    /// Number of allocations
23    pub allocation_count: u64,
24    /// Number of deallocations
25    pub deallocation_count: u64,
26    /// Average allocation size
27    pub avg_allocation_size: f64,
28}
29
30impl Default for MemoryUsage {
31    fn default() -> Self {
32        Self {
33            heap_allocated: 0,
34            heap_deallocated: 0,
35            peak_memory: 0,
36            current_memory: 0,
37            allocation_count: 0,
38            deallocation_count: 0,
39            avg_allocation_size: 0.0,
40        }
41    }
42}
43
44impl MemoryUsage {
45    /// Calculate memory efficiency ratio (deallocated / allocated)
46    pub fn efficiency_ratio(&self) -> f64 {
47        if self.heap_allocated > 0 {
48            self.heap_deallocated as f64 / self.heap_allocated as f64
49        } else {
50            0.0
51        }
52    }
53
54    /// Check if memory usage is considered high
55    pub fn is_high_usage(&self, threshold_mb: u64) -> bool {
56        self.current_memory > threshold_mb * 1024 * 1024
57    }
58
59    /// Get memory usage in MB
60    pub fn current_memory_mb(&self) -> f64 {
61        self.current_memory as f64 / (1024.0 * 1024.0)
62    }
63
64    /// Get peak memory usage in MB
65    pub fn peak_memory_mb(&self) -> f64 {
66        self.peak_memory as f64 / (1024.0 * 1024.0)
67    }
68}
69
70/// Memory pressure levels for adaptive behavior
71#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
72pub enum MemoryPressure {
73    /// Low memory usage - normal operation
74    Low,
75    /// Moderate memory usage - start optimizing
76    Moderate,
77    /// High memory usage - aggressive optimization
78    High,
79    /// Critical memory usage - emergency measures
80    Critical,
81}
82
83impl MemoryPressure {
84    /// Determine memory pressure level based on current usage
85    pub fn from_usage(current_mb: f64, peak_mb: f64) -> Self {
86        let usage_ratio = if peak_mb > 0.0 {
87            current_mb / peak_mb
88        } else {
89            0.0
90        };
91
92        match current_mb {
93            mb if mb > 1000.0 => Self::Critical,  // > 1GB
94            mb if mb > 500.0 => Self::High,       // > 500MB
95            mb if mb > 200.0 => Self::Moderate,   // > 200MB
96            _ if usage_ratio > 0.8 => Self::High, // High ratio to peak
97            _ => Self::Low,
98        }
99    }
100
101    /// Get recommended action for this pressure level
102    pub fn recommended_action(&self) -> &'static str {
103        match self {
104            Self::Low => "Normal operation",
105            Self::Moderate => "Start cache cleanup and reduce buffer sizes",
106            Self::High => "Aggressive cache cleanup and limit concurrent operations",
107            Self::Critical => "Emergency cleanup and pause non-essential operations",
108        }
109    }
110}
111
112/// Memory tracker for monitoring heap allocations and providing adaptive behavior
113#[derive(Debug)]
114pub struct MemoryTracker {
115    /// Total bytes allocated
116    allocated: AtomicU64,
117    /// Total bytes deallocated
118    deallocated: AtomicU64,
119    /// Peak memory usage
120    peak_memory: AtomicU64,
121    /// Number of allocations
122    allocation_count: AtomicU64,
123    /// Number of deallocations
124    deallocation_count: AtomicU64,
125    /// Total allocation size for average calculation
126    total_allocation_size: AtomicU64,
127    /// Last memory check timestamp
128    last_check: std::sync::Mutex<Instant>,
129    /// Memory pressure thresholds in MB
130    moderate_threshold: u64,
131    high_threshold: u64,
132    critical_threshold: u64,
133}
134
135impl Default for MemoryTracker {
136    fn default() -> Self {
137        Self::new()
138    }
139}
140
141impl MemoryTracker {
142    /// Create a new memory tracker with default thresholds
143    pub fn new() -> Self {
144        Self::with_thresholds(200, 500, 1000) // 200MB, 500MB, 1GB
145    }
146
147    /// Create a new memory tracker with custom thresholds (in MB)
148    pub fn with_thresholds(moderate: u64, high: u64, critical: u64) -> Self {
149        Self {
150            allocated: AtomicU64::new(0),
151            deallocated: AtomicU64::new(0),
152            peak_memory: AtomicU64::new(0),
153            allocation_count: AtomicU64::new(0),
154            deallocation_count: AtomicU64::new(0),
155            total_allocation_size: AtomicU64::new(0),
156            last_check: std::sync::Mutex::new(Instant::now()),
157            moderate_threshold: moderate,
158            high_threshold: high,
159            critical_threshold: critical,
160        }
161    }
162
163    /// Record a memory allocation
164    pub fn record_allocation(&self, size: usize) {
165        let size_u64 = size as u64;
166
167        self.allocated.fetch_add(size_u64, Ordering::Relaxed);
168        self.allocation_count.fetch_add(1, Ordering::Relaxed);
169        self.total_allocation_size
170            .fetch_add(size_u64, Ordering::Relaxed);
171
172        // Update peak memory if necessary
173        let current = self.current_memory();
174        let mut peak = self.peak_memory.load(Ordering::Relaxed);
175        while current > peak {
176            match self.peak_memory.compare_exchange_weak(
177                peak,
178                current,
179                Ordering::Relaxed,
180                Ordering::Relaxed,
181            ) {
182                Ok(_) => break,
183                Err(new_peak) => peak = new_peak,
184            }
185        }
186
187        // Log large allocations
188        if size > 10 * 1024 * 1024 {
189            // > 10MB
190            debug!(
191                "Large allocation: {:.2} MB",
192                size as f64 / (1024.0 * 1024.0)
193            );
194        }
195    }
196
197    /// Record a memory deallocation
198    pub fn record_deallocation(&self, size: usize) {
199        let size_u64 = size as u64;
200
201        self.deallocated.fetch_add(size_u64, Ordering::Relaxed);
202        self.deallocation_count.fetch_add(1, Ordering::Relaxed);
203    }
204
205    /// Get current memory usage statistics
206    pub fn get_usage(&self) -> MemoryUsage {
207        let allocated = self.allocated.load(Ordering::Relaxed);
208        let deallocated = self.deallocated.load(Ordering::Relaxed);
209        let allocation_count = self.allocation_count.load(Ordering::Relaxed);
210        let total_allocation_size = self.total_allocation_size.load(Ordering::Relaxed);
211
212        MemoryUsage {
213            heap_allocated: allocated,
214            heap_deallocated: deallocated,
215            peak_memory: self.peak_memory.load(Ordering::Relaxed),
216            current_memory: allocated.saturating_sub(deallocated),
217            allocation_count,
218            deallocation_count: self.deallocation_count.load(Ordering::Relaxed),
219            avg_allocation_size: if allocation_count > 0 {
220                total_allocation_size as f64 / allocation_count as f64
221            } else {
222                0.0
223            },
224        }
225    }
226
227    /// Get current memory usage in bytes
228    pub fn current_memory(&self) -> u64 {
229        let allocated = self.allocated.load(Ordering::Relaxed);
230        let deallocated = self.deallocated.load(Ordering::Relaxed);
231        allocated.saturating_sub(deallocated)
232    }
233
234    /// Get current memory pressure level
235    pub fn memory_pressure(&self) -> MemoryPressure {
236        let usage = self.get_usage();
237        let current_mb = usage.current_memory_mb();
238
239        // Use thresholds to determine pressure level
240        match current_mb as u64 {
241            mb if mb >= self.critical_threshold => MemoryPressure::Critical,
242            mb if mb >= self.high_threshold => MemoryPressure::High,
243            mb if mb >= self.moderate_threshold => MemoryPressure::Moderate,
244            _ => MemoryPressure::Low,
245        }
246    }
247
248    /// Check if memory pressure has changed and log if necessary
249    pub fn check_memory_pressure(&self) -> MemoryPressure {
250        let pressure = self.memory_pressure();
251        let usage = self.get_usage();
252
253        // Only check periodically to avoid spam
254        if let Ok(mut last_check) = self.last_check.lock() {
255            if last_check.elapsed() > Duration::from_secs(30) {
256                match pressure {
257                    MemoryPressure::Critical => {
258                        warn!(
259                            "Critical memory pressure: {:.2} MB current, {:.2} MB peak - {}",
260                            usage.current_memory_mb(),
261                            usage.peak_memory_mb(),
262                            pressure.recommended_action()
263                        );
264                    }
265                    MemoryPressure::High => {
266                        warn!(
267                            "High memory pressure: {:.2} MB current, {:.2} MB peak - {}",
268                            usage.current_memory_mb(),
269                            usage.peak_memory_mb(),
270                            pressure.recommended_action()
271                        );
272                    }
273                    MemoryPressure::Moderate => {
274                        info!(
275                            "Moderate memory usage: {:.2} MB current, {:.2} MB peak",
276                            usage.current_memory_mb(),
277                            usage.peak_memory_mb()
278                        );
279                    }
280                    MemoryPressure::Low => {
281                        debug!(
282                            "Low memory usage: {:.2} MB current, {:.2} MB peak",
283                            usage.current_memory_mb(),
284                            usage.peak_memory_mb()
285                        );
286                    }
287                }
288                *last_check = Instant::now();
289            }
290        }
291
292        pressure
293    }
294
295    /// Reset all statistics
296    pub fn reset(&self) {
297        self.allocated.store(0, Ordering::Relaxed);
298        self.deallocated.store(0, Ordering::Relaxed);
299        self.peak_memory.store(0, Ordering::Relaxed);
300        self.allocation_count.store(0, Ordering::Relaxed);
301        self.deallocation_count.store(0, Ordering::Relaxed);
302        self.total_allocation_size.store(0, Ordering::Relaxed);
303        if let Ok(mut last_check) = self.last_check.lock() {
304            *last_check = Instant::now();
305        }
306    }
307
308    /// Get memory efficiency metrics
309    pub fn efficiency_metrics(&self) -> MemoryEfficiencyMetrics {
310        let usage = self.get_usage();
311
312        MemoryEfficiencyMetrics {
313            efficiency_ratio: usage.efficiency_ratio(),
314            fragmentation_ratio: self.calculate_fragmentation_ratio(),
315            allocation_rate: self.calculate_allocation_rate(),
316            pressure_level: self.memory_pressure(),
317            recommendations: self.get_recommendations(),
318        }
319    }
320
321    /// Calculate fragmentation ratio (estimate)
322    fn calculate_fragmentation_ratio(&self) -> f64 {
323        let allocation_count = self.allocation_count.load(Ordering::Relaxed);
324        let deallocation_count = self.deallocation_count.load(Ordering::Relaxed);
325
326        if allocation_count > 0 {
327            let active_allocations = allocation_count.saturating_sub(deallocation_count);
328            active_allocations as f64 / allocation_count as f64
329        } else {
330            0.0
331        }
332    }
333
334    /// Calculate allocation rate (allocations per second, estimated)
335    fn calculate_allocation_rate(&self) -> f64 {
336        if let Ok(last_check) = self.last_check.lock() {
337            let elapsed = last_check.elapsed().as_secs_f64();
338
339            if elapsed > 0.0 {
340                let allocation_count = self.allocation_count.load(Ordering::Relaxed);
341                allocation_count as f64 / elapsed
342            } else {
343                0.0
344            }
345        } else {
346            0.0 // Return 0 if lock acquisition fails
347        }
348    }
349
350    /// Get optimization recommendations based on current state
351    fn get_recommendations(&self) -> Vec<String> {
352        let mut recommendations = Vec::new();
353        let usage = self.get_usage();
354        let pressure = self.memory_pressure();
355
356        match pressure {
357            MemoryPressure::Critical => {
358                recommendations.push("Immediately reduce concurrent operations".to_string());
359                recommendations.push("Clear all non-essential caches".to_string());
360                recommendations.push("Consider reducing buffer sizes".to_string());
361            }
362            MemoryPressure::High => {
363                recommendations.push("Reduce concurrent chunk downloads".to_string());
364                recommendations.push("Enable aggressive cache cleanup".to_string());
365                recommendations.push("Consider smaller buffer sizes".to_string());
366            }
367            MemoryPressure::Moderate => {
368                recommendations.push("Enable periodic cache cleanup".to_string());
369                recommendations.push("Monitor allocation patterns".to_string());
370            }
371            MemoryPressure::Low => {
372                if usage.efficiency_ratio() < 0.8 {
373                    recommendations.push("Consider optimizing memory reuse".to_string());
374                }
375            }
376        }
377
378        if usage.avg_allocation_size > 1024.0 * 1024.0 {
379            // > 1MB average
380            recommendations.push(
381                "Large average allocation size detected - consider buffer pooling".to_string(),
382            );
383        }
384
385        recommendations
386    }
387}
388
389/// Memory efficiency metrics for performance analysis
390#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
391pub struct MemoryEfficiencyMetrics {
392    /// Ratio of deallocated to allocated memory
393    pub efficiency_ratio: f64,
394    /// Estimated fragmentation ratio
395    pub fragmentation_ratio: f64,
396    /// Allocation rate (allocations per second)
397    pub allocation_rate: f64,
398    /// Current memory pressure level
399    pub pressure_level: MemoryPressure,
400    /// Optimization recommendations
401    pub recommendations: Vec<String>,
402}
403
404/// Global memory tracker instance
405static GLOBAL_MEMORY_TRACKER: once_cell::sync::Lazy<Arc<MemoryTracker>> =
406    once_cell::sync::Lazy::new(|| Arc::new(MemoryTracker::new()));
407
408/// Get the global memory tracker instance
409pub fn global_memory_tracker() -> Arc<MemoryTracker> {
410    GLOBAL_MEMORY_TRACKER.clone()
411}
412
413/// Record a global memory allocation
414pub fn record_allocation(size: usize) {
415    GLOBAL_MEMORY_TRACKER.record_allocation(size);
416}
417
418/// Record a global memory deallocation
419pub fn record_deallocation(size: usize) {
420    GLOBAL_MEMORY_TRACKER.record_deallocation(size);
421}
422
423/// Get global memory usage statistics
424pub fn global_memory_usage() -> MemoryUsage {
425    GLOBAL_MEMORY_TRACKER.get_usage()
426}
427
428/// Get global memory pressure level
429pub fn global_memory_pressure() -> MemoryPressure {
430    GLOBAL_MEMORY_TRACKER.memory_pressure()
431}
432
433/// Check global memory pressure and log if necessary
434pub fn check_global_memory_pressure() -> MemoryPressure {
435    GLOBAL_MEMORY_TRACKER.check_memory_pressure()
436}
437
438/// Custom allocator wrapper that tracks memory usage
439///
440/// This allocator wraps mimalloc and automatically records all allocations
441/// and deallocations to the global memory tracker for comprehensive monitoring.
442#[derive(Debug)]
443pub struct TrackingAllocator;
444
445unsafe impl std::alloc::GlobalAlloc for TrackingAllocator {
446    unsafe fn alloc(&self, layout: std::alloc::Layout) -> *mut u8 {
447        let ptr = mimalloc::MiMalloc.alloc(layout);
448        if !ptr.is_null() {
449            record_allocation(layout.size());
450        }
451        ptr
452    }
453
454    unsafe fn dealloc(&self, ptr: *mut u8, layout: std::alloc::Layout) {
455        record_deallocation(layout.size());
456        mimalloc::MiMalloc.dealloc(ptr, layout);
457    }
458
459    unsafe fn alloc_zeroed(&self, layout: std::alloc::Layout) -> *mut u8 {
460        let ptr = mimalloc::MiMalloc.alloc_zeroed(layout);
461        if !ptr.is_null() {
462            record_allocation(layout.size());
463        }
464        ptr
465    }
466
467    unsafe fn realloc(&self, ptr: *mut u8, layout: std::alloc::Layout, new_size: usize) -> *mut u8 {
468        let new_ptr = mimalloc::MiMalloc.realloc(ptr, layout, new_size);
469        if !new_ptr.is_null() {
470            // Record deallocation of old size and allocation of new size
471            record_deallocation(layout.size());
472            record_allocation(new_size);
473        }
474        new_ptr
475    }
476}
477
478/// Enable memory tracking by using the TrackingAllocator as the global allocator
479///
480/// Add this to your main.rs or lib.rs:
481/// ```rust
482/// #[global_allocator]
483/// static GLOBAL: turbo_cdn::memory_tracker::TrackingAllocator = turbo_cdn::memory_tracker::TrackingAllocator;
484/// ```
485pub static TRACKING_ALLOCATOR: TrackingAllocator = TrackingAllocator;
486
487#[cfg(test)]
488mod tests {
489    use super::*;
490
491    #[test]
492    fn test_memory_tracker_creation() {
493        let tracker = MemoryTracker::new();
494        let usage = tracker.get_usage();
495
496        assert_eq!(usage.heap_allocated, 0);
497        assert_eq!(usage.heap_deallocated, 0);
498        assert_eq!(usage.current_memory, 0);
499        assert_eq!(usage.allocation_count, 0);
500    }
501
502    #[test]
503    fn test_memory_allocation_tracking() {
504        let tracker = MemoryTracker::new();
505
506        tracker.record_allocation(1024);
507        tracker.record_allocation(2048);
508
509        let usage = tracker.get_usage();
510        assert_eq!(usage.heap_allocated, 3072);
511        assert_eq!(usage.allocation_count, 2);
512        assert_eq!(usage.current_memory, 3072);
513        assert_eq!(usage.avg_allocation_size, 1536.0);
514    }
515
516    #[test]
517    fn test_memory_deallocation_tracking() {
518        let tracker = MemoryTracker::new();
519
520        tracker.record_allocation(2048);
521        tracker.record_deallocation(1024);
522
523        let usage = tracker.get_usage();
524        assert_eq!(usage.heap_allocated, 2048);
525        assert_eq!(usage.heap_deallocated, 1024);
526        assert_eq!(usage.current_memory, 1024);
527        assert_eq!(usage.deallocation_count, 1);
528    }
529
530    #[test]
531    fn test_peak_memory_tracking() {
532        let tracker = MemoryTracker::new();
533
534        tracker.record_allocation(1024);
535        tracker.record_allocation(2048);
536        tracker.record_deallocation(1024);
537
538        let usage = tracker.get_usage();
539        assert_eq!(usage.peak_memory, 3072);
540        assert_eq!(usage.current_memory, 2048);
541    }
542
543    #[test]
544    fn test_memory_pressure_levels() {
545        assert_eq!(MemoryPressure::from_usage(50.0, 100.0), MemoryPressure::Low);
546        assert_eq!(
547            MemoryPressure::from_usage(250.0, 300.0),
548            MemoryPressure::Moderate
549        );
550        assert_eq!(
551            MemoryPressure::from_usage(600.0, 700.0),
552            MemoryPressure::High
553        );
554        assert_eq!(
555            MemoryPressure::from_usage(1200.0, 1300.0),
556            MemoryPressure::Critical
557        );
558    }
559
560    #[test]
561    fn test_memory_efficiency_ratio() {
562        let usage = MemoryUsage {
563            heap_allocated: 1000,
564            heap_deallocated: 800,
565            ..Default::default()
566        };
567
568        assert_eq!(usage.efficiency_ratio(), 0.8);
569    }
570
571    #[test]
572    fn test_global_memory_tracker() {
573        let tracker1 = global_memory_tracker();
574        let tracker2 = global_memory_tracker();
575
576        // Should be the same instance
577        assert!(Arc::ptr_eq(&tracker1, &tracker2));
578    }
579
580    #[test]
581    fn test_memory_usage_mb_conversion() {
582        let usage = MemoryUsage {
583            current_memory: 1024 * 1024,  // 1MB
584            peak_memory: 2 * 1024 * 1024, // 2MB
585            ..Default::default()
586        };
587
588        assert_eq!(usage.current_memory_mb(), 1.0);
589        assert_eq!(usage.peak_memory_mb(), 2.0);
590    }
591
592    #[test]
593    fn test_memory_tracker_reset() {
594        let tracker = MemoryTracker::new();
595
596        tracker.record_allocation(1024);
597        tracker.record_deallocation(512);
598
599        let usage_before = tracker.get_usage();
600        assert!(usage_before.heap_allocated > 0);
601
602        tracker.reset();
603
604        let usage_after = tracker.get_usage();
605        assert_eq!(usage_after.heap_allocated, 0);
606        assert_eq!(usage_after.heap_deallocated, 0);
607        assert_eq!(usage_after.allocation_count, 0);
608    }
609}