Skip to main content

scirs2_interpolate/
memory_monitor.rs

1//! Memory leak detection and monitoring for continuous use
2//!
3//! This module provides utilities for tracking memory usage patterns, detecting
4//! potential leaks, and monitoring memory-related performance issues during
5//! long-running interpolation operations.
6//!
7//! # Overview
8//!
9//! The memory monitoring system tracks:
10//! - Memory allocations and deallocations per interpolator
11//! - Cache memory usage and growth patterns  
12//! - Peak memory usage across operations
13//! - Memory leaks through reference counting
14//! - Memory pressure and allocation patterns
15//!
16//! # Usage
17//!
18//! ```rust
19//! use scirs2_interpolate::memory_monitor::{MemoryMonitor, start_monitoring};
20//!
21//! // Start global memory monitoring
22//! start_monitoring();
23//!
24//! // Create a monitored interpolator
25//! let mut monitor = MemoryMonitor::new("rbf_interpolator");
26//!
27//! // Track memory during operations
28//! monitor.track_allocation(1024, "distance_matrix");
29//! // ... perform interpolation operations ...
30//! monitor.track_deallocation(1024, "distance_matrix");
31//!
32//! // Check for memory leaks
33//! let report = monitor.generate_report();
34//! if report.has_potential_leaks() {
35//!     println!("Warning: Potential memory leaks detected");
36//! }
37//! ```
38
39use std::collections::{HashMap, VecDeque};
40use std::sync::{Arc, Mutex, OnceLock};
41use std::time::{Duration, Instant};
42
43/// Global memory monitoring registry
44static GLOBAL_MONITOR: OnceLock<Arc<Mutex<GlobalMemoryMonitor>>> = OnceLock::new();
45
46/// Global memory monitoring system
47#[derive(Debug)]
48struct GlobalMemoryMonitor {
49    /// Active memory monitors by name
50    monitors: HashMap<String, Arc<Mutex<MemoryMonitor>>>,
51
52    /// Global memory statistics
53    global_stats: GlobalMemoryStats,
54
55    /// Whether monitoring is enabled
56    enabled: bool,
57
58    /// Maximum number of monitors to track
59    max_monitors: usize,
60}
61
62/// Global memory statistics across all interpolators
63#[derive(Debug, Clone)]
64pub struct GlobalMemoryStats {
65    /// Total memory allocated across all interpolators
66    pub total_allocated_bytes: usize,
67
68    /// Peak memory usage across all interpolators
69    pub peak_total_bytes: usize,
70
71    /// Number of active interpolators being monitored
72    pub active_interpolators: usize,
73
74    /// Total number of allocations tracked
75    pub total_allocations: u64,
76
77    /// Total number of deallocations tracked
78    pub total_deallocations: u64,
79
80    /// Start time of monitoring
81    pub monitoring_start: Instant,
82}
83
84impl Default for GlobalMemoryStats {
85    fn default() -> Self {
86        Self {
87            total_allocated_bytes: 0,
88            peak_total_bytes: 0,
89            active_interpolators: 0,
90            total_allocations: 0,
91            total_deallocations: 0,
92            monitoring_start: Instant::now(),
93        }
94    }
95}
96
97/// Individual memory monitor for a specific interpolator
98#[derive(Debug)]
99pub struct MemoryMonitor {
100    /// Name/identifier for this monitor
101    name: String,
102
103    /// Current memory allocations by category
104    allocations: HashMap<String, usize>,
105
106    /// Memory allocation history
107    allocation_history: VecDeque<AllocationEvent>,
108
109    /// Peak memory usage for this interpolator
110    peak_memory_bytes: usize,
111
112    /// Current total memory usage
113    current_memory_bytes: usize,
114
115    /// Statistics for leak detection
116    leak_stats: LeakDetectionStats,
117
118    /// Performance metrics
119    perf_metrics: MemoryPerformanceMetrics,
120
121    /// Whether this monitor is active
122    active: bool,
123
124    /// Creation timestamp
125    created_at: Instant,
126}
127
128/// Memory allocation/deallocation event
129#[derive(Debug, Clone)]
130struct AllocationEvent {
131    /// Type of event (allocation or deallocation)
132    event_type: EventType,
133
134    /// Size in bytes
135    sizebytes: usize,
136
137    /// Category of memory (e.g., "distance_matrix", "cache", "coefficients")
138    #[allow(dead_code)]
139    category: String,
140
141    /// Timestamp of event
142    #[allow(dead_code)]
143    timestamp: Instant,
144}
145
146/// Type of memory event
147#[derive(Debug, Clone, Copy, PartialEq)]
148enum EventType {
149    Allocation,
150    Deallocation,
151}
152
153/// Statistics for leak detection
154#[derive(Debug, Clone)]
155struct LeakDetectionStats {
156    /// Total number of allocations
157    total_allocations: u64,
158
159    /// Total number of deallocations
160    total_deallocations: u64,
161
162    /// Number of unmatched allocations (potential leaks)
163    #[allow(dead_code)]
164    unmatched_allocations: u64,
165
166    /// Memory that has been allocated but not freed for a long time
167    long_lived_allocations: HashMap<String, (usize, Instant)>,
168
169    /// Threshold for considering allocations as potential leaks (in seconds)
170    leak_detection_threshold: Duration,
171}
172
173impl Default for LeakDetectionStats {
174    fn default() -> Self {
175        Self {
176            total_allocations: 0,
177            total_deallocations: 0,
178            unmatched_allocations: 0,
179            long_lived_allocations: HashMap::new(),
180            leak_detection_threshold: Duration::from_secs(300), // 5 minutes
181        }
182    }
183}
184
185/// Memory performance metrics
186#[derive(Debug, Clone)]
187struct MemoryPerformanceMetrics {
188    /// Average allocation size
189    avg_allocation_size: f64,
190
191    /// Average time between allocations
192    #[allow(dead_code)]
193    avg_allocation_interval: Duration,
194
195    /// Memory fragmentation estimate (0.0 to 1.0)
196    #[allow(dead_code)]
197    fragmentation_estimate: f64,
198
199    /// Cache hit ratio for memory reuse
200    cache_hit_ratio: f64,
201
202    /// Last update timestamp
203    last_update: Instant,
204}
205
206impl Default for MemoryPerformanceMetrics {
207    fn default() -> Self {
208        Self {
209            avg_allocation_size: 0.0,
210            avg_allocation_interval: Duration::from_millis(0),
211            fragmentation_estimate: 0.0,
212            cache_hit_ratio: 0.0,
213            last_update: Instant::now(),
214        }
215    }
216}
217
218/// Memory monitoring report
219#[derive(Debug, Clone)]
220pub struct MemoryReport {
221    /// Monitor name
222    pub monitorname: String,
223
224    /// Current memory usage by category
225    pub current_allocations: HashMap<String, usize>,
226
227    /// Peak memory usage
228    pub peak_memory_bytes: usize,
229
230    /// Total memory allocated over lifetime
231    pub total_allocated_bytes: usize,
232
233    /// Memory leak indicators
234    pub leak_indicators: LeakIndicators,
235
236    /// Performance metrics
237    pub performance_summary: PerformanceSummary,
238
239    /// Recommendations for memory optimization
240    pub recommendations: Vec<String>,
241
242    /// Report generation timestamp
243    pub generated_at: Instant,
244}
245
246/// Memory leak indicators
247#[derive(Debug, Clone)]
248pub struct LeakIndicators {
249    /// Potential memory leaks detected
250    pub has_potential_leaks: bool,
251
252    /// Number of unmatched allocations
253    pub unmatched_allocations: u64,
254
255    /// Memory that has been held for a long time
256    pub long_lived_memory_bytes: usize,
257
258    /// Categories with suspicious allocation patterns
259    pub suspicious_categories: Vec<String>,
260
261    /// Leak severity (0.0 = no leaks, 1.0 = severe leaks)
262    pub leak_severity: f64,
263}
264
265/// Performance summary for memory usage
266#[derive(Debug, Clone)]
267pub struct PerformanceSummary {
268    /// Memory efficiency (lower is better)
269    pub memory_efficiency_score: f64,
270
271    /// Allocation pattern efficiency
272    pub allocation_pattern_score: f64,
273
274    /// Cache utilization score
275    pub cache_utilization_score: f64,
276
277    /// Overall memory performance grade
278    pub overall_grade: PerformanceGrade,
279}
280
281/// Performance grade classification
282#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
283pub enum PerformanceGrade {
284    Excellent,
285    Good,
286    Fair,
287    Poor,
288    Critical,
289}
290
291impl MemoryMonitor {
292    /// Create a new memory monitor
293    pub fn new(name: impl Into<String>) -> Self {
294        let name = name.into();
295        let monitor = Self {
296            name: name.clone(),
297            allocations: HashMap::new(),
298            allocation_history: VecDeque::new(),
299            peak_memory_bytes: 0,
300            current_memory_bytes: 0,
301            leak_stats: LeakDetectionStats::default(),
302            perf_metrics: MemoryPerformanceMetrics::default(),
303            active: true,
304            created_at: Instant::now(),
305        };
306
307        // Register with global monitor
308        register_monitor(&name, monitor.clone());
309        monitor
310    }
311
312    /// Track a memory allocation
313    pub fn track_allocation(&mut self, sizebytes: usize, category: impl Into<String>) {
314        if !self.active {
315            return;
316        }
317
318        let category = category.into();
319        let now = Instant::now();
320
321        // Update current allocations
322        *self.allocations.entry(category.clone()).or_insert(0) += sizebytes;
323        self.current_memory_bytes += sizebytes;
324
325        // Update peak usage
326        if self.current_memory_bytes > self.peak_memory_bytes {
327            self.peak_memory_bytes = self.current_memory_bytes;
328        }
329
330        // Record allocation event
331        let event = AllocationEvent {
332            event_type: EventType::Allocation,
333            sizebytes,
334            category: category.clone(),
335            timestamp: now,
336        };
337
338        self.allocation_history.push_back(event);
339
340        // Limit history size to prevent memory growth
341        if self.allocation_history.len() > 10000 {
342            self.allocation_history.pop_front();
343        }
344
345        // Update leak detection stats
346        self.leak_stats.total_allocations += 1;
347        self.leak_stats.long_lived_allocations.insert(
348            format!("{}_{}", category, self.leak_stats.total_allocations),
349            (sizebytes, now),
350        );
351
352        // Update performance metrics
353        self.update_performance_metrics();
354
355        // Update global stats
356        update_global_stats(sizebytes, true);
357    }
358
359    /// Track a memory deallocation
360    pub fn track_deallocation(&mut self, sizebytes: usize, category: impl Into<String>) {
361        if !self.active {
362            return;
363        }
364
365        let category = category.into();
366        let now = Instant::now();
367
368        // Update current allocations
369        if let Some(current) = self.allocations.get_mut(&category) {
370            *current = current.saturating_sub(sizebytes);
371            if *current == 0 {
372                self.allocations.remove(&category);
373            }
374        }
375
376        self.current_memory_bytes = self.current_memory_bytes.saturating_sub(sizebytes);
377
378        // Record deallocation event
379        let event = AllocationEvent {
380            event_type: EventType::Deallocation,
381            sizebytes,
382            category: category.clone(),
383            timestamp: now,
384        };
385
386        self.allocation_history.push_back(event);
387
388        // Update leak detection stats
389        self.leak_stats.total_deallocations += 1;
390
391        // Remove from long-lived allocations (simplified - would need better matching in production)
392        self.leak_stats
393            .long_lived_allocations
394            .retain(|k, _| !k.starts_with(&category));
395
396        // Update performance metrics
397        self.update_performance_metrics();
398
399        // Update global stats
400        update_global_stats(sizebytes, false);
401    }
402
403    /// Generate a comprehensive memory report
404    pub fn generate_report(&self) -> MemoryReport {
405        let leak_indicators = self.analyze_leaks();
406        let performance_summary = self.analyze_performance();
407        let recommendations = self.generate_recommendations(&leak_indicators, &performance_summary);
408
409        MemoryReport {
410            monitorname: self.name.clone(),
411            current_allocations: self.allocations.clone(),
412            peak_memory_bytes: self.peak_memory_bytes,
413            total_allocated_bytes: self.calculate_total_allocated(),
414            leak_indicators,
415            performance_summary,
416            recommendations,
417            generated_at: Instant::now(),
418        }
419    }
420
421    /// Analyze potential memory leaks
422    fn analyze_leaks(&self) -> LeakIndicators {
423        let unmatched = self
424            .leak_stats
425            .total_allocations
426            .saturating_sub(self.leak_stats.total_deallocations);
427
428        // Calculate long-lived memory
429        let now = Instant::now();
430        let long_lived_memory: usize = self
431            .leak_stats
432            .long_lived_allocations
433            .values()
434            .filter(|(_, timestamp)| {
435                now.duration_since(*timestamp) > self.leak_stats.leak_detection_threshold
436            })
437            .map(|(size, _)| *size)
438            .sum();
439
440        // Identify suspicious categories (categories with consistently growing memory)
441        let suspicious_categories: Vec<String> = self.allocations
442            .iter()
443            .filter(|(_, &size)| size > 1024 * 1024) // More than 1MB
444            .map(|(cat, _)| cat.clone())
445            .collect();
446
447        let has_potential_leaks =
448            unmatched > 0 || long_lived_memory > 0 || !suspicious_categories.is_empty();
449
450        // Calculate leak severity
451        let leak_severity = if has_potential_leaks {
452            let severity_factors = [
453                (unmatched as f64) / (self.leak_stats.total_allocations as f64).max(1.0),
454                (long_lived_memory as f64) / (self.peak_memory_bytes as f64).max(1.0),
455                (suspicious_categories.len() as f64) / 10.0, // Normalize by 10 categories
456            ];
457            severity_factors.iter().sum::<f64>() / severity_factors.len() as f64
458        } else {
459            0.0
460        };
461
462        LeakIndicators {
463            has_potential_leaks,
464            unmatched_allocations: unmatched,
465            long_lived_memory_bytes: long_lived_memory,
466            suspicious_categories,
467            leak_severity: leak_severity.min(1.0),
468        }
469    }
470
471    /// Analyze memory performance
472    fn analyze_performance(&self) -> PerformanceSummary {
473        // Calculate memory efficiency (lower peak/current ratio is better)
474        let memory_efficiency_score = if self.peak_memory_bytes > 0 {
475            1.0 - (self.current_memory_bytes as f64 / self.peak_memory_bytes as f64)
476        } else {
477            1.0
478        };
479
480        // Calculate allocation pattern efficiency
481        let allocation_pattern_score = if self.leak_stats.total_allocations > 0 {
482            let deallocation_ratio = self.leak_stats.total_deallocations as f64
483                / self.leak_stats.total_allocations as f64;
484            deallocation_ratio.min(1.0)
485        } else {
486            1.0
487        };
488
489        // Use cached cache utilization score
490        let cache_utilization_score = self.perf_metrics.cache_hit_ratio;
491
492        // Calculate overall grade
493        let overall_score =
494            (memory_efficiency_score + allocation_pattern_score + cache_utilization_score) / 3.0;
495        let overall_grade = match overall_score {
496            s if s >= 0.9 => PerformanceGrade::Excellent,
497            s if s >= 0.7 => PerformanceGrade::Good,
498            s if s >= 0.5 => PerformanceGrade::Fair,
499            s if s >= 0.3 => PerformanceGrade::Poor,
500            _ => PerformanceGrade::Critical,
501        };
502
503        PerformanceSummary {
504            memory_efficiency_score,
505            allocation_pattern_score,
506            cache_utilization_score,
507            overall_grade,
508        }
509    }
510
511    /// Generate optimization recommendations
512    fn generate_recommendations(
513        &self,
514        leak_indicators: &LeakIndicators,
515        performance: &PerformanceSummary,
516    ) -> Vec<String> {
517        let mut recommendations = Vec::new();
518
519        if leak_indicators.has_potential_leaks {
520            recommendations
521                .push("Consider implementing explicit memory cleanup in destructor".to_string());
522
523            if leak_indicators.unmatched_allocations > 0 {
524                recommendations.push(format!(
525                    "Found {} unmatched allocations - check for missing deallocations",
526                    leak_indicators.unmatched_allocations
527                ));
528            }
529
530            if leak_indicators.long_lived_memory_bytes > 1024 * 1024 {
531                recommendations.push(format!(
532                    "Large amount of long-lived memory ({} MB) - consider periodic cleanup",
533                    leak_indicators.long_lived_memory_bytes / (1024 * 1024)
534                ));
535            }
536        }
537
538        if matches!(
539            performance.overall_grade,
540            PerformanceGrade::Fair | PerformanceGrade::Poor | PerformanceGrade::Critical
541        ) {
542            recommendations.push("Memory performance can be improved".to_string());
543
544            if performance.memory_efficiency_score < 0.5 {
545                recommendations.push(
546                    "High peak memory usage - consider processing data in chunks".to_string(),
547                );
548            }
549
550            if performance.cache_utilization_score < 0.3 {
551                recommendations.push(
552                    "Low cache utilization - enable caching for repeated operations".to_string(),
553                );
554            }
555        }
556
557        #[cfg(target_pointer_width = "32")]
558        let high_memory_threshold = 256 * 1024 * 1024; // 256MB for 32-bit
559        #[cfg(target_pointer_width = "64")]
560        let high_memory_threshold = 1024 * 1024 * 1024; // 1GB for 64-bit
561
562        if self.peak_memory_bytes > high_memory_threshold {
563            recommendations.push(
564                "Very high memory usage - consider using memory-efficient algorithms".to_string(),
565            );
566        }
567
568        recommendations
569    }
570
571    /// Update performance metrics
572    fn update_performance_metrics(&mut self) {
573        let now = Instant::now();
574
575        // Update average allocation size
576        if self.leak_stats.total_allocations > 0 {
577            let total_size: usize = self
578                .allocation_history
579                .iter()
580                .filter(|e| e.event_type == EventType::Allocation)
581                .map(|e| e.sizebytes)
582                .sum();
583            self.perf_metrics.avg_allocation_size =
584                total_size as f64 / self.leak_stats.total_allocations as f64;
585        }
586
587        // Simple cache hit ratio simulation (would need actual cache statistics in practice)
588        self.perf_metrics.cache_hit_ratio = 0.7; // Placeholder
589
590        self.perf_metrics.last_update = now;
591    }
592
593    /// Calculate total memory allocated over lifetime
594    fn calculate_total_allocated(&self) -> usize {
595        self.allocation_history
596            .iter()
597            .filter(|e| e.event_type == EventType::Allocation)
598            .map(|e| e.sizebytes)
599            .sum()
600    }
601
602    /// Disable this monitor
603    pub fn disable(&mut self) {
604        self.active = false;
605    }
606
607    /// Check if monitor is active
608    pub fn is_active(&self) -> bool {
609        self.active
610    }
611}
612
613impl Clone for MemoryMonitor {
614    fn clone(&self) -> Self {
615        Self {
616            name: format!("{}_clone", self.name),
617            allocations: self.allocations.clone(),
618            allocation_history: self.allocation_history.clone(),
619            peak_memory_bytes: self.peak_memory_bytes,
620            current_memory_bytes: self.current_memory_bytes,
621            leak_stats: self.leak_stats.clone(),
622            perf_metrics: self.perf_metrics.clone(),
623            active: self.active,
624            created_at: self.created_at,
625        }
626    }
627}
628
629impl MemoryReport {
630    /// Check if the report indicates potential memory leaks
631    pub fn has_potential_leaks(&self) -> bool {
632        self.leak_indicators.has_potential_leaks
633    }
634
635    /// Get memory efficiency rating
636    pub fn memory_efficiency_rating(&self) -> PerformanceGrade {
637        self.performance_summary.overall_grade
638    }
639
640    /// Get human-readable summary
641    pub fn summary(&self) -> String {
642        format!(
643            "Memory Report for '{}': Current: {} KB, Peak: {} KB, Grade: {:?}, Leaks: {}",
644            self.monitorname,
645            self.current_allocations.values().sum::<usize>() / 1024,
646            self.peak_memory_bytes / 1024,
647            self.performance_summary.overall_grade,
648            if self.has_potential_leaks() {
649                "Detected"
650            } else {
651                "None"
652            }
653        )
654    }
655}
656
657/// Global memory monitoring functions
658/// Start global memory monitoring
659#[allow(dead_code)]
660pub fn start_monitoring() {
661    let _ = GLOBAL_MONITOR.set(Arc::new(Mutex::new(GlobalMemoryMonitor {
662        monitors: HashMap::new(),
663        global_stats: GlobalMemoryStats::default(),
664        enabled: true,
665        max_monitors: 100,
666    })));
667}
668
669/// Stop global memory monitoring
670#[allow(dead_code)]
671pub fn stop_monitoring() {
672    if let Some(monitor) = GLOBAL_MONITOR.get() {
673        if let Ok(mut global) = monitor.lock() {
674            global.enabled = false;
675            global.monitors.clear();
676        }
677    }
678}
679
680/// Register a memory monitor with the global system
681#[allow(dead_code)]
682fn register_monitor(name: &str, monitor: MemoryMonitor) {
683    if let Some(global_monitor) = GLOBAL_MONITOR.get() {
684        if let Ok(mut global) = global_monitor.lock() {
685            if global.enabled && global.monitors.len() < global.max_monitors {
686                global
687                    .monitors
688                    .insert(name.to_string(), Arc::new(Mutex::new(monitor)));
689                global.global_stats.active_interpolators = global.monitors.len();
690            }
691        }
692    }
693}
694
695/// Update global memory statistics
696#[allow(dead_code)]
697fn update_global_stats(sizebytes: usize, isallocation: bool) {
698    if let Some(global_monitor) = GLOBAL_MONITOR.get() {
699        if let Ok(mut global) = global_monitor.lock() {
700            if isallocation {
701                global.global_stats.total_allocated_bytes += sizebytes;
702                global.global_stats.total_allocations += 1;
703
704                if global.global_stats.total_allocated_bytes > global.global_stats.peak_total_bytes
705                {
706                    global.global_stats.peak_total_bytes =
707                        global.global_stats.total_allocated_bytes;
708                }
709            } else {
710                global.global_stats.total_allocated_bytes = global
711                    .global_stats
712                    .total_allocated_bytes
713                    .saturating_sub(sizebytes);
714                global.global_stats.total_deallocations += 1;
715            }
716        }
717    }
718}
719
720/// Get global memory statistics
721#[allow(dead_code)]
722pub fn get_global_stats() -> Option<GlobalMemoryStats> {
723    GLOBAL_MONITOR
724        .get()
725        .and_then(|monitor| monitor.lock().ok())
726        .map(|global| global.global_stats.clone())
727}
728
729/// Get report for a specific monitor
730#[allow(dead_code)]
731pub fn get_monitor_report(name: &str) -> Option<MemoryReport> {
732    GLOBAL_MONITOR
733        .get()
734        .and_then(|global_monitor| {
735            global_monitor
736                .lock()
737                .ok()
738                .and_then(|global| global.monitors.get(name).cloned())
739        })
740        .and_then(|monitor| monitor.lock().ok().map(|m| m.generate_report()))
741}
742
743/// Get reports for all active monitors
744#[allow(dead_code)]
745pub fn get_all_reports() -> Vec<MemoryReport> {
746    if let Some(global_monitor) = GLOBAL_MONITOR.get() {
747        if let Ok(global) = global_monitor.lock() {
748            return global
749                .monitors
750                .values()
751                .filter_map(|monitor| monitor.lock().ok())
752                .map(|m| m.generate_report())
753                .collect();
754        }
755    }
756    Vec::new()
757}
758
759/// Enhanced stress testing memory profiler
760#[derive(Debug)]
761pub struct StressMemoryProfiler {
762    /// Base monitor for standard tracking
763    base_monitor: MemoryMonitor,
764
765    /// Stress test specific metrics
766    stress_metrics: StressMemoryMetrics,
767
768    /// Memory usage history during stress tests
769    stress_history: VecDeque<MemorySnapshot>,
770
771    /// System memory pressure indicators
772    pressure_indicators: MemoryPressureIndicators,
773
774    /// Configuration for stress profiling
775    stress_config: StressProfilingConfig,
776}
777
778/// Stress-specific memory metrics
779#[derive(Debug, Clone)]
780pub struct StressMemoryMetrics {
781    /// Maximum memory growth rate during stress (bytes/second)
782    pub max_growth_rate: f64,
783
784    /// Memory allocation spikes during stress
785    pub allocation_spikes: Vec<AllocationSpike>,
786
787    /// Memory fragmentation under stress
788    pub stress_fragmentation: f64,
789
790    /// Concurrent access memory overhead
791    pub concurrent_overhead: f64,
792
793    /// Large dataset memory efficiency
794    pub large_dataset_efficiency: f64,
795
796    /// Memory recovery time after stress
797    pub recovery_time_seconds: f64,
798}
799
800/// Memory allocation spike during stress testing
801#[derive(Debug, Clone)]
802pub struct AllocationSpike {
803    /// Time when spike occurred
804    pub timestamp: Instant,
805
806    /// Size of the spike in bytes
807    pub spike_size: usize,
808
809    /// Duration of the spike
810    pub duration: Duration,
811
812    /// Stress condition that caused the spike
813    pub stresscondition: String,
814}
815
816/// Memory snapshot during stress testing
817#[derive(Debug, Clone)]
818pub struct MemorySnapshot {
819    /// Timestamp of snapshot
820    pub timestamp: Instant,
821
822    /// Total memory usage at this point
823    pub total_memory: usize,
824
825    /// Memory usage by category
826    pub category_breakdown: HashMap<String, usize>,
827
828    /// System memory pressure level (0.0 to 1.0)
829    pub system_pressure: f64,
830
831    /// Active stress conditions
832    pub active_stressconditions: Vec<String>,
833}
834
835/// System memory pressure indicators
836#[derive(Debug, Clone)]
837pub struct MemoryPressureIndicators {
838    /// System memory utilization percentage
839    pub system_memory_utilization: f64,
840
841    /// Available memory in bytes
842    pub available_memory: usize,
843
844    /// Memory allocation failure rate
845    pub allocation_failure_rate: f64,
846
847    /// Garbage collection frequency (if applicable)
848    pub gc_frequency: f64,
849
850    /// Swap usage percentage
851    pub swap_utilization: f64,
852}
853
854impl Default for MemoryPressureIndicators {
855    fn default() -> Self {
856        #[cfg(target_pointer_width = "32")]
857        let available_memory = 512 * 1024 * 1024; // 512MB default for 32-bit
858        #[cfg(target_pointer_width = "64")]
859        let available_memory = 8usize * 1024 * 1024 * 1024; // 8GB default for 64-bit
860
861        Self {
862            system_memory_utilization: 0.0,
863            available_memory,
864            allocation_failure_rate: 0.0,
865            gc_frequency: 0.0,
866            swap_utilization: 0.0,
867        }
868    }
869}
870
871/// Configuration for stress profiling
872#[derive(Debug, Clone)]
873pub struct StressProfilingConfig {
874    /// Sampling interval for memory snapshots during stress
875    pub snapshot_interval: Duration,
876
877    /// Maximum number of snapshots to retain
878    pub max_snapshots: usize,
879
880    /// Threshold for detecting allocation spikes (bytes)
881    pub spike_threshold: usize,
882
883    /// Enable system memory pressure monitoring
884    pub monitor_system_pressure: bool,
885
886    /// Enable detailed category tracking under stress
887    pub detailed_category_tracking: bool,
888}
889
890impl Default for StressProfilingConfig {
891    fn default() -> Self {
892        Self {
893            snapshot_interval: Duration::from_millis(100), // 10 samples per second
894            max_snapshots: 10000,                          // ~17 minutes at 100ms intervals
895            spike_threshold: 10 * 1024 * 1024,             // 10MB
896            monitor_system_pressure: true,
897            detailed_category_tracking: true,
898        }
899    }
900}
901
902impl StressMemoryProfiler {
903    /// Create a new stress memory profiler
904    pub fn new(name: impl Into<String>, config: Option<StressProfilingConfig>) -> Self {
905        Self {
906            base_monitor: MemoryMonitor::new(name),
907            stress_metrics: StressMemoryMetrics {
908                max_growth_rate: 0.0,
909                allocation_spikes: Vec::new(),
910                stress_fragmentation: 0.0,
911                concurrent_overhead: 0.0,
912                large_dataset_efficiency: 1.0,
913                recovery_time_seconds: 0.0,
914            },
915            stress_history: VecDeque::new(),
916            pressure_indicators: MemoryPressureIndicators::default(),
917            stress_config: config.unwrap_or_default(),
918        }
919    }
920
921    /// Start profiling under specific stress condition
922    pub fn start_stress_profiling(&mut self, stresscondition: &str) {
923        println!("Starting stress memory profiling for: {}", stresscondition);
924
925        // Take initial snapshot
926        self.take_memory_snapshot(vec![stresscondition.to_string()]);
927
928        // Update system pressure indicators
929        self.update_system_pressure();
930    }
931
932    /// Track memory allocation during stress test
933    pub fn track_stress_allocation(
934        &mut self,
935        sizebytes: usize,
936        category: impl Into<String>,
937        stresscondition: &str,
938    ) {
939        let category = category.into();
940
941        // Track with base monitor
942        self.base_monitor.track_allocation(sizebytes, &category);
943
944        // Check for allocation spike
945        if sizebytes >= self.stress_config.spike_threshold {
946            self.stress_metrics.allocation_spikes.push(AllocationSpike {
947                timestamp: Instant::now(),
948                spike_size: sizebytes,
949                duration: Duration::from_millis(0), // Would measure actual duration
950                stresscondition: stresscondition.to_string(),
951            });
952        }
953
954        // Take periodic snapshots
955        if self.should_take_snapshot() {
956            self.take_memory_snapshot(vec![stresscondition.to_string()]);
957        }
958
959        // Update growth rate
960        self.update_growth_rate();
961    }
962
963    /// Track memory deallocation during stress test
964    pub fn track_stress_deallocation(&mut self, sizebytes: usize, category: impl Into<String>) {
965        self.base_monitor.track_deallocation(sizebytes, category);
966
967        // Update stress metrics
968        self.update_growth_rate();
969    }
970
971    /// Take a memory snapshot for stress analysis
972    fn take_memory_snapshot(&mut self, active_stressconditions: Vec<String>) {
973        let snapshot = MemorySnapshot {
974            timestamp: Instant::now(),
975            total_memory: self.base_monitor.current_memory_bytes,
976            category_breakdown: self.base_monitor.allocations.clone(),
977            system_pressure: self.calculate_system_pressure(),
978            active_stressconditions,
979        };
980
981        self.stress_history.push_back(snapshot);
982
983        // Limit history size
984        if self.stress_history.len() > self.stress_config.max_snapshots {
985            self.stress_history.pop_front();
986        }
987    }
988
989    /// Check if should take snapshot based on timing
990    fn should_take_snapshot(&self) -> bool {
991        if let Some(last_snapshot) = self.stress_history.back() {
992            last_snapshot.timestamp.elapsed() >= self.stress_config.snapshot_interval
993        } else {
994            true // Always take first snapshot
995        }
996    }
997
998    /// Update memory growth rate during stress
999    fn update_growth_rate(&mut self) {
1000        if self.stress_history.len() >= 2 {
1001            let recent_snapshots: Vec<_> = self.stress_history.iter().rev().take(10).collect();
1002
1003            if recent_snapshots.len() >= 2 {
1004                let latest = recent_snapshots[0];
1005                let previous = recent_snapshots[recent_snapshots.len() - 1];
1006
1007                let memory_delta = latest.total_memory as i64 - previous.total_memory as i64;
1008                let time_delta = latest
1009                    .timestamp
1010                    .duration_since(previous.timestamp)
1011                    .as_secs_f64();
1012
1013                if time_delta > 0.0 {
1014                    let growth_rate = memory_delta as f64 / time_delta;
1015                    self.stress_metrics.max_growth_rate =
1016                        self.stress_metrics.max_growth_rate.max(growth_rate);
1017                }
1018            }
1019        }
1020    }
1021
1022    /// Update system memory pressure indicators
1023    fn update_system_pressure(&mut self) {
1024        // In a real implementation, this would query the operating system
1025        // For now, simulate pressure based on our current usage
1026
1027        #[cfg(target_pointer_width = "32")]
1028        let total_system_memory: u64 = 1024 * 1024 * 1024; // 1GB assumed for 32-bit
1029        #[cfg(target_pointer_width = "64")]
1030        let total_system_memory: u64 = 16u64 * 1024 * 1024 * 1024; // 16GB assumed for 64-bit
1031
1032        let our_usage = self.base_monitor.current_memory_bytes;
1033
1034        self.pressure_indicators.system_memory_utilization =
1035            (our_usage as f64 / total_system_memory as f64 * 100.0).min(100.0);
1036
1037        self.pressure_indicators.available_memory =
1038            (total_system_memory as usize).saturating_sub(our_usage);
1039
1040        // Simulate other metrics
1041        self.pressure_indicators.allocation_failure_rate =
1042            if self.pressure_indicators.system_memory_utilization > 90.0 {
1043                0.1
1044            } else {
1045                0.0
1046            };
1047    }
1048
1049    /// Calculate current system pressure level
1050    fn calculate_system_pressure(&self) -> f64 {
1051        let pressure_factors = [
1052            self.pressure_indicators.system_memory_utilization / 100.0,
1053            self.pressure_indicators.allocation_failure_rate,
1054            self.pressure_indicators.swap_utilization / 100.0,
1055        ];
1056
1057        pressure_factors.iter().sum::<f64>() / pressure_factors.len() as f64
1058    }
1059
1060    /// Analyze memory efficiency under large dataset stress
1061    pub fn analyze_large_dataset_efficiency(
1062        &mut self,
1063        dataset_size: usize,
1064        expected_memory: usize,
1065    ) {
1066        let actual_memory = self.base_monitor.current_memory_bytes;
1067
1068        self.stress_metrics.large_dataset_efficiency =
1069            expected_memory as f64 / actual_memory.max(1) as f64;
1070
1071        println!(
1072            "Large dataset efficiency for {} elements: {:.2} (expected: {}MB, actual: {}MB)",
1073            dataset_size,
1074            self.stress_metrics.large_dataset_efficiency,
1075            expected_memory / (1024 * 1024),
1076            actual_memory / (1024 * 1024)
1077        );
1078    }
1079
1080    /// Analyze concurrent access memory overhead
1081    pub fn analyze_concurrent_overhead(
1082        &mut self,
1083        baseline_memory: usize,
1084        concurrent_threads: usize,
1085    ) {
1086        let current_memory = self.base_monitor.current_memory_bytes;
1087        let overhead = current_memory.saturating_sub(baseline_memory);
1088
1089        self.stress_metrics.concurrent_overhead = overhead as f64 / concurrent_threads as f64;
1090
1091        println!(
1092            "Concurrent access overhead: {:.1}KB per thread ({} threads)",
1093            self.stress_metrics.concurrent_overhead / 1024.0,
1094            concurrent_threads
1095        );
1096    }
1097
1098    /// Measure memory recovery time after stress
1099    pub fn measure_recovery_time(&mut self, stress_endtime: Instant) {
1100        let _recovery_start_memory = self.base_monitor.current_memory_bytes;
1101
1102        // Monitor memory for recovery (simplified - would need async monitoring in practice)
1103        let recovery_time = Instant::now().duration_since(stress_endtime);
1104        self.stress_metrics.recovery_time_seconds = recovery_time.as_secs_f64();
1105
1106        println!(
1107            "Memory recovery _time: {:.2}s",
1108            self.stress_metrics.recovery_time_seconds
1109        );
1110    }
1111
1112    /// Generate comprehensive stress memory report
1113    pub fn generate_stress_report(&self) -> StressMemoryReport {
1114        let base_report = self.base_monitor.generate_report();
1115
1116        let memory_pressure_analysis = self.analyze_memory_pressure();
1117        let allocation_pattern_analysis = self.analyze_allocation_patterns();
1118        let stress_performance_analysis = self.analyze_stress_performance();
1119
1120        StressMemoryReport {
1121            base_report,
1122            stress_metrics: self.stress_metrics.clone(),
1123            memory_pressure_analysis,
1124            allocation_pattern_analysis,
1125            stress_performance_analysis,
1126            system_pressure: self.pressure_indicators.clone(),
1127            snapshot_count: self.stress_history.len(),
1128            stress_recommendations: self.generate_stress_recommendations(),
1129        }
1130    }
1131
1132    /// Analyze memory pressure patterns
1133    fn analyze_memory_pressure(&self) -> MemoryPressureAnalysis {
1134        let max_pressure = self
1135            .stress_history
1136            .iter()
1137            .map(|s| s.system_pressure)
1138            .fold(0.0, f64::max);
1139
1140        let avg_pressure = if !self.stress_history.is_empty() {
1141            self.stress_history
1142                .iter()
1143                .map(|s| s.system_pressure)
1144                .sum::<f64>()
1145                / self.stress_history.len() as f64
1146        } else {
1147            0.0
1148        };
1149
1150        let pressure_spikes = self
1151            .stress_history
1152            .iter()
1153            .filter(|s| s.system_pressure > 0.8)
1154            .count();
1155
1156        MemoryPressureAnalysis {
1157            max_pressure,
1158            avg_pressure,
1159            pressure_spikes,
1160            critical_periods: pressure_spikes, // Simplified
1161        }
1162    }
1163
1164    /// Analyze allocation patterns under stress
1165    fn analyze_allocation_patterns(&self) -> AllocationPatternAnalysis {
1166        let spike_count = self.stress_metrics.allocation_spikes.len();
1167        let total_spike_memory: usize = self
1168            .stress_metrics
1169            .allocation_spikes
1170            .iter()
1171            .map(|s| s.spike_size)
1172            .sum();
1173
1174        let pattern_regularity = if spike_count > 1 {
1175            // Calculate variance in spike timing
1176            let intervals: Vec<_> = self
1177                .stress_metrics
1178                .allocation_spikes
1179                .windows(2)
1180                .map(|pair| {
1181                    pair[1]
1182                        .timestamp
1183                        .duration_since(pair[0].timestamp)
1184                        .as_secs_f64()
1185                })
1186                .collect();
1187
1188            if !intervals.is_empty() {
1189                let mean_interval = intervals.iter().sum::<f64>() / intervals.len() as f64;
1190                let variance = intervals
1191                    .iter()
1192                    .map(|&x| (x - mean_interval).powi(2))
1193                    .sum::<f64>()
1194                    / intervals.len() as f64;
1195                1.0 / (1.0 + variance) // Higher variance = lower regularity
1196            } else {
1197                1.0
1198            }
1199        } else {
1200            1.0
1201        };
1202
1203        AllocationPatternAnalysis {
1204            spike_count,
1205            total_spike_memory,
1206            pattern_regularity,
1207            fragmentation_level: self.stress_metrics.stress_fragmentation,
1208        }
1209    }
1210
1211    /// Analyze stress performance
1212    fn analyze_stress_performance(&self) -> StressPerformanceAnalysis {
1213        StressPerformanceAnalysis {
1214            max_growth_rate: self.stress_metrics.max_growth_rate,
1215            concurrent_overhead: self.stress_metrics.concurrent_overhead,
1216            large_dataset_efficiency: self.stress_metrics.large_dataset_efficiency,
1217            recovery_time: self.stress_metrics.recovery_time_seconds,
1218            overall_stress_grade: self.calculate_stress_grade(),
1219        }
1220    }
1221
1222    /// Calculate overall stress performance grade
1223    fn calculate_stress_grade(&self) -> StressPerformanceGrade {
1224        let factors = [
1225            if self.stress_metrics.max_growth_rate < 1024.0 * 1024.0 {
1226                1.0
1227            } else {
1228                0.0
1229            }, // < 1MB/s growth
1230            if self.stress_metrics.concurrent_overhead < 1024.0 * 1024.0 {
1231                1.0
1232            } else {
1233                0.0
1234            }, // < 1MB overhead per thread
1235            self.stress_metrics.large_dataset_efficiency.min(1.0), // Efficiency ratio
1236            if self.stress_metrics.recovery_time_seconds < 10.0 {
1237                1.0
1238            } else {
1239                0.0
1240            }, // < 10s recovery
1241        ];
1242
1243        let score = factors.iter().sum::<f64>() / factors.len() as f64;
1244
1245        match score {
1246            s if s >= 0.9 => StressPerformanceGrade::Excellent,
1247            s if s >= 0.7 => StressPerformanceGrade::Good,
1248            s if s >= 0.5 => StressPerformanceGrade::Fair,
1249            s if s >= 0.3 => StressPerformanceGrade::Poor,
1250            _ => StressPerformanceGrade::Critical,
1251        }
1252    }
1253
1254    /// Generate stress-specific recommendations
1255    fn generate_stress_recommendations(&self) -> Vec<String> {
1256        let mut recommendations = Vec::new();
1257
1258        if self.stress_metrics.max_growth_rate > 10.0 * 1024.0 * 1024.0 {
1259            // > 10MB/s
1260            recommendations
1261                .push("High memory growth rate detected - consider batch processing".to_string());
1262        }
1263
1264        if self.stress_metrics.allocation_spikes.len() > 10 {
1265            recommendations
1266                .push("Frequent allocation spikes - implement memory pre-allocation".to_string());
1267        }
1268
1269        if self.stress_metrics.concurrent_overhead > 5.0 * 1024.0 * 1024.0 {
1270            // > 5MB per thread
1271            recommendations
1272                .push("High concurrent overhead - review thread-local memory usage".to_string());
1273        }
1274
1275        if self.stress_metrics.large_dataset_efficiency < 0.7 {
1276            recommendations
1277                .push("Poor large dataset efficiency - optimize memory layout".to_string());
1278        }
1279
1280        if self.stress_metrics.recovery_time_seconds > 30.0 {
1281            recommendations.push("Slow memory recovery - implement explicit cleanup".to_string());
1282        }
1283
1284        recommendations
1285    }
1286}
1287
1288/// Comprehensive stress memory report
1289#[derive(Debug, Clone)]
1290pub struct StressMemoryReport {
1291    /// Base memory report
1292    pub base_report: MemoryReport,
1293
1294    /// Stress-specific metrics
1295    pub stress_metrics: StressMemoryMetrics,
1296
1297    /// Memory pressure analysis
1298    pub memory_pressure_analysis: MemoryPressureAnalysis,
1299
1300    /// Allocation pattern analysis
1301    pub allocation_pattern_analysis: AllocationPatternAnalysis,
1302
1303    /// Stress performance analysis
1304    pub stress_performance_analysis: StressPerformanceAnalysis,
1305
1306    /// System pressure indicators
1307    pub system_pressure: MemoryPressureIndicators,
1308
1309    /// Number of snapshots taken
1310    pub snapshot_count: usize,
1311
1312    /// Stress-specific recommendations
1313    pub stress_recommendations: Vec<String>,
1314}
1315
1316/// Memory pressure analysis results
1317#[derive(Debug, Clone)]
1318pub struct MemoryPressureAnalysis {
1319    /// Maximum pressure level reached (0.0 to 1.0)
1320    pub max_pressure: f64,
1321
1322    /// Average pressure level
1323    pub avg_pressure: f64,
1324
1325    /// Number of pressure spikes
1326    pub pressure_spikes: usize,
1327
1328    /// Number of critical pressure periods
1329    pub critical_periods: usize,
1330}
1331
1332/// Allocation pattern analysis results
1333#[derive(Debug, Clone)]
1334pub struct AllocationPatternAnalysis {
1335    /// Number of allocation spikes
1336    pub spike_count: usize,
1337
1338    /// Total memory in spikes
1339    pub total_spike_memory: usize,
1340
1341    /// Pattern regularity (0.0 to 1.0)
1342    pub pattern_regularity: f64,
1343
1344    /// Memory fragmentation level
1345    pub fragmentation_level: f64,
1346}
1347
1348/// Stress performance analysis
1349#[derive(Debug, Clone)]
1350pub struct StressPerformanceAnalysis {
1351    /// Maximum memory growth rate (bytes/second)
1352    pub max_growth_rate: f64,
1353
1354    /// Concurrent access overhead per thread
1355    pub concurrent_overhead: f64,
1356
1357    /// Large dataset memory efficiency
1358    pub large_dataset_efficiency: f64,
1359
1360    /// Recovery time after stress
1361    pub recovery_time: f64,
1362
1363    /// Overall stress performance grade
1364    pub overall_stress_grade: StressPerformanceGrade,
1365}
1366
1367/// Stress performance grades
1368#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1369pub enum StressPerformanceGrade {
1370    Excellent,
1371    Good,
1372    Fair,
1373    Poor,
1374    Critical,
1375}
1376
1377/// Create a stress memory profiler for testing
1378#[allow(dead_code)]
1379pub fn create_stress_profiler(name: impl Into<String>) -> StressMemoryProfiler {
1380    StressMemoryProfiler::new(name, None)
1381}
1382
1383/// Create a stress memory profiler with custom configuration
1384#[allow(dead_code)]
1385pub fn create_stress_profiler_with_config(
1386    name: impl Into<String>,
1387    config: StressProfilingConfig,
1388) -> StressMemoryProfiler {
1389    StressMemoryProfiler::new(name, Some(config))
1390}
1391
1392#[cfg(test)]
1393mod tests {
1394    use super::*;
1395
1396    #[test]
1397    fn test_memory_monitor_basic() {
1398        let mut monitor = MemoryMonitor::new("test");
1399
1400        // Track some allocations
1401        monitor.track_allocation(1024, "matrix");
1402        monitor.track_allocation(512, "cache");
1403
1404        assert_eq!(monitor.current_memory_bytes, 1536);
1405        assert_eq!(monitor.peak_memory_bytes, 1536);
1406
1407        // Track deallocations - deallocate all to avoid false leak detection
1408        monitor.track_deallocation(512, "cache");
1409        assert_eq!(monitor.current_memory_bytes, 1024);
1410
1411        monitor.track_deallocation(1024, "matrix");
1412        assert_eq!(monitor.current_memory_bytes, 0);
1413
1414        let report = monitor.generate_report();
1415        // After deallocating all memory, should have no leaks
1416        assert!(!report.has_potential_leaks());
1417    }
1418
1419    #[test]
1420    fn test_leak_detection() {
1421        let mut monitor = MemoryMonitor::new("leak_test");
1422
1423        // Allocate without deallocating (potential leak)
1424        monitor.track_allocation(2048, "leaked_memory");
1425
1426        let report = monitor.generate_report();
1427        assert!(report.leak_indicators.unmatched_allocations > 0);
1428    }
1429
1430    #[test]
1431    fn test_global_monitoring() {
1432        start_monitoring();
1433
1434        let _monitor1 = MemoryMonitor::new("global_test_1");
1435        let _monitor2 = MemoryMonitor::new("global_test_2");
1436
1437        let stats = get_global_stats().expect("Operation failed");
1438        // Test may run in parallel with other tests, so check >= 2 instead of == 2
1439        assert!(
1440            stats.active_interpolators >= 2,
1441            "Expected at least 2 active interpolators, got {}",
1442            stats.active_interpolators
1443        );
1444
1445        stop_monitoring();
1446    }
1447
1448    #[test]
1449    fn test_stress_profiler_basic() {
1450        let mut profiler = create_stress_profiler("stress_test");
1451
1452        profiler.start_stress_profiling("large_dataset");
1453        profiler.track_stress_allocation(10 * 1024 * 1024, "large_matrix", "large_dataset");
1454        profiler.track_stress_allocation(5 * 1024 * 1024, "cache", "large_dataset");
1455
1456        let report = profiler.generate_stress_report();
1457        assert!(report.stress_metrics.allocation_spikes.len() > 0);
1458        assert!(report.snapshot_count > 0);
1459    }
1460
1461    #[test]
1462    fn test_stress_allocation_spike_detection() {
1463        let mut profiler = create_stress_profiler("spike_test");
1464
1465        // Trigger allocation spike (default threshold is 10MB)
1466        profiler.track_stress_allocation(15 * 1024 * 1024, "spike", "stress_test");
1467
1468        assert_eq!(profiler.stress_metrics.allocation_spikes.len(), 1);
1469        assert_eq!(
1470            profiler.stress_metrics.allocation_spikes[0].spike_size,
1471            15 * 1024 * 1024
1472        );
1473    }
1474}