Skip to main content

trustformers_debug/
memory_profiler.rs

1//! Advanced memory profiling for TrustformeRS models.
2//!
3//! This module provides comprehensive memory profiling capabilities including:
4//! - Heap allocation tracking
5//! - Memory leak detection
6//! - Peak memory analysis
7//! - Allocation patterns
8//! - GC pressure analysis
9//! - Memory fragmentation monitoring
10//!
11//! # Example
12//!
13//! ```no_run
14//! use trustformers_debug::{MemoryProfiler, MemoryProfilingConfig};
15//!
16//! # async fn run() -> anyhow::Result<()> {
17//! let config = MemoryProfilingConfig::default();
18//! let mut profiler = MemoryProfiler::new(config);
19//!
20//! profiler.start().await?;
21//! // ... run model training/inference ...
22//! let report = profiler.stop().await?;
23//!
24//! println!("Peak memory usage: {} MB", report.peak_memory_mb);
25//! println!("Memory leaks detected: {}", report.potential_leaks.len());
26//! # Ok(())
27//! # }
28//! ```
29
30use anyhow::Result;
31use serde::{Deserialize, Serialize};
32use std::collections::{HashMap, VecDeque};
33use std::sync::{Arc, Mutex};
34use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
35use tokio::time::interval;
36use uuid::Uuid;
37
38/// Configuration for memory profiling
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct MemoryProfilingConfig {
41    /// Enable heap allocation tracking
42    pub enable_heap_tracking: bool,
43    /// Enable leak detection
44    pub enable_leak_detection: bool,
45    /// Enable allocation pattern analysis
46    pub enable_pattern_analysis: bool,
47    /// Enable memory fragmentation monitoring
48    pub enable_fragmentation_monitoring: bool,
49    /// Enable GC pressure analysis
50    pub enable_gc_pressure_analysis: bool,
51    /// Sampling interval for memory measurements (milliseconds)
52    pub sampling_interval_ms: u64,
53    /// Maximum number of allocation records to keep
54    pub max_allocation_records: usize,
55    /// Threshold for considering an allocation "large" (bytes)
56    pub large_allocation_threshold: usize,
57    /// Window size for detecting allocation patterns (seconds)
58    pub pattern_analysis_window_secs: u64,
59    /// Threshold for leak detection (allocations alive for this duration)
60    pub leak_detection_threshold_secs: u64,
61}
62
63impl Default for MemoryProfilingConfig {
64    fn default() -> Self {
65        Self {
66            enable_heap_tracking: true,
67            enable_leak_detection: true,
68            enable_pattern_analysis: true,
69            enable_fragmentation_monitoring: true,
70            enable_gc_pressure_analysis: true,
71            sampling_interval_ms: 100, // 100ms sampling
72            max_allocation_records: 100000,
73            large_allocation_threshold: 1024 * 1024, // 1MB
74            pattern_analysis_window_secs: 60,        // 1 minute window
75            leak_detection_threshold_secs: 300,      // 5 minutes
76        }
77    }
78}
79
80/// Allocation record for tracking individual allocations
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct AllocationRecord {
83    pub id: Uuid,
84    pub size: usize,
85    pub timestamp: SystemTime,
86    pub stack_trace: Vec<String>,
87    pub allocation_type: AllocationType,
88    pub freed: bool,
89    pub freed_at: Option<SystemTime>,
90    pub tags: Vec<String>, // For categorizing allocations
91}
92
93/// Type of allocation
94#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
95pub enum AllocationType {
96    Tensor,
97    Buffer,
98    Weights,
99    Gradients,
100    Activations,
101    Cache,
102    Temporary,
103    Other(String),
104}
105
106/// Memory usage snapshot at a point in time
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct MemorySnapshot {
109    pub timestamp: SystemTime,
110    pub total_heap_bytes: usize,
111    pub used_heap_bytes: usize,
112    pub free_heap_bytes: usize,
113    pub peak_heap_bytes: usize,
114    pub allocation_count: usize,
115    pub free_count: usize,
116    pub fragmentation_ratio: f64,
117    pub gc_pressure_score: f64,
118    pub allocations_by_type: HashMap<AllocationType, usize>,
119    pub allocations_by_size: HashMap<String, usize>, // Size buckets
120}
121
122/// Memory leak information
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct MemoryLeak {
125    pub allocation_id: Uuid,
126    pub size: usize,
127    pub age_seconds: f64,
128    pub allocation_type: AllocationType,
129    pub stack_trace: Vec<String>,
130    pub tags: Vec<String>,
131    pub severity: LeakSeverity,
132}
133
134/// Severity of memory leak
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
136pub enum LeakSeverity {
137    Low,      // Small allocations, short-lived
138    Medium,   // Moderate size or moderately old
139    High,     // Large allocations or very old
140    Critical, // Very large or extremely old
141}
142
143/// Allocation pattern detected by analysis
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct AllocationPattern {
146    pub pattern_type: PatternType,
147    pub description: String,
148    pub confidence: f64,   // 0.0 to 1.0
149    pub impact_score: f64, // 0.0 to 1.0 (higher = more concerning)
150    pub recommendations: Vec<String>,
151    pub examples: Vec<AllocationRecord>,
152}
153
154/// Type of allocation pattern
155#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
156pub enum PatternType {
157    MemoryLeak,           // Consistent growth without deallocation
158    ChurningAllocations,  // Rapid alloc/free cycles
159    FragmentationCausing, // Allocations that cause fragmentation
160    LargeAllocations,     // Unexpectedly large allocations
161    UnbalancedTypes,      // Disproportionate allocation types
162    PeakUsageSpikes,      // Sudden memory usage spikes
163}
164
165/// Memory fragmentation analysis
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct FragmentationAnalysis {
168    pub fragmentation_ratio: f64,
169    pub largest_free_block: usize,
170    pub total_free_memory: usize,
171    pub free_block_count: usize,
172    pub average_free_block_size: f64,
173    pub fragmentation_severity: FragmentationSeverity,
174    pub recommendations: Vec<String>,
175}
176
177/// Fragmentation severity levels
178#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
179pub enum FragmentationSeverity {
180    Low,    // < 10% fragmentation
181    Medium, // 10-30% fragmentation
182    High,   // 30-60% fragmentation
183    Severe, // > 60% fragmentation
184}
185
186/// Garbage collection pressure analysis
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct GCPressureAnalysis {
189    pub pressure_score: f64,    // 0.0 to 1.0
190    pub allocation_rate: f64,   // allocations per second
191    pub deallocation_rate: f64, // deallocations per second
192    pub churn_rate: f64,        // alloc/dealloc cycles per second
193    pub pressure_level: GCPressureLevel,
194    pub contributing_factors: Vec<String>,
195    pub recommendations: Vec<String>,
196}
197
198/// GC pressure levels
199#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
200pub enum GCPressureLevel {
201    Low,
202    Medium,
203    High,
204    Critical,
205}
206
207/// Comprehensive memory profiling report
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct MemoryProfilingReport {
210    pub session_id: Uuid,
211    pub start_time: SystemTime,
212    pub end_time: SystemTime,
213    pub duration_secs: f64,
214    pub config: MemoryProfilingConfig,
215
216    // Summary statistics
217    pub peak_memory_mb: f64,
218    pub average_memory_mb: f64,
219    pub total_allocations: usize,
220    pub total_deallocations: usize,
221    pub net_allocations: i64,
222
223    // Memory timeline
224    pub memory_timeline: Vec<MemorySnapshot>,
225
226    // Leak detection
227    pub potential_leaks: Vec<MemoryLeak>,
228    pub leak_summary: HashMap<AllocationType, usize>,
229
230    // Pattern analysis
231    pub detected_patterns: Vec<AllocationPattern>,
232
233    // Fragmentation analysis
234    pub fragmentation_analysis: FragmentationAnalysis,
235
236    // GC pressure analysis
237    pub gc_pressure_analysis: GCPressureAnalysis,
238
239    // Allocation statistics
240    pub allocations_by_type: HashMap<AllocationType, AllocationTypeStats>,
241    pub allocations_by_size_bucket: HashMap<String, usize>,
242
243    // Performance metrics
244    pub profiling_overhead_ms: f64,
245    pub sampling_accuracy: f64,
246}
247
248/// Statistics for each allocation type
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct AllocationTypeStats {
251    pub total_allocations: usize,
252    pub total_deallocations: usize,
253    pub current_count: usize,
254    pub total_bytes_allocated: usize,
255    pub total_bytes_deallocated: usize,
256    pub current_bytes: usize,
257    pub peak_count: usize,
258    pub peak_bytes: usize,
259    pub average_allocation_size: f64,
260    pub largest_allocation: usize,
261}
262
263/// Memory profiler implementation
264#[derive(Debug)]
265pub struct MemoryProfiler {
266    config: MemoryProfilingConfig,
267    session_id: Uuid,
268    start_time: Option<Instant>,
269    allocations: Arc<Mutex<HashMap<Uuid, AllocationRecord>>>,
270    memory_timeline: Arc<Mutex<VecDeque<MemorySnapshot>>>,
271    type_stats: Arc<Mutex<HashMap<AllocationType, AllocationTypeStats>>>,
272    running: Arc<Mutex<bool>>,
273    profiling_start_time: Option<Instant>,
274}
275
276impl MemoryProfiler {
277    /// Create a new memory profiler
278    pub fn new(config: MemoryProfilingConfig) -> Self {
279        Self {
280            config,
281            session_id: Uuid::new_v4(),
282            start_time: None,
283            allocations: Arc::new(Mutex::new(HashMap::new())),
284            memory_timeline: Arc::new(Mutex::new(VecDeque::new())),
285            type_stats: Arc::new(Mutex::new(HashMap::new())),
286            running: Arc::new(Mutex::new(false)),
287            profiling_start_time: None,
288        }
289    }
290
291    /// Start memory profiling
292    pub async fn start(&mut self) -> Result<()> {
293        let mut running = self.running.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
294        if *running {
295            return Err(anyhow::anyhow!("Memory profiler is already running"));
296        }
297
298        *running = true;
299        self.start_time = Some(Instant::now());
300        self.profiling_start_time = Some(Instant::now());
301
302        // Start periodic sampling
303        if self.config.enable_heap_tracking {
304            self.start_sampling().await?;
305        }
306
307        tracing::info!("Memory profiler started for session {}", self.session_id);
308        Ok(())
309    }
310
311    /// Stop memory profiling and generate report
312    pub async fn stop(&mut self) -> Result<MemoryProfilingReport> {
313        {
314            let mut running = self.running.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
315            if !*running {
316                return Err(anyhow::anyhow!("Memory profiler is not running"));
317            }
318            *running = false;
319        }
320        // Guard is dropped here so background sampling task can check the flag and exit
321
322        let end_time = SystemTime::now();
323        let start_time = self
324            .start_time
325            .ok_or_else(|| anyhow::anyhow!("start_time should be set when profiler is running"))?;
326        let duration =
327            end_time.duration_since(UNIX_EPOCH)?.as_secs_f64() - start_time.elapsed().as_secs_f64();
328
329        // Calculate profiling overhead
330        let profiling_overhead = if let Some(prof_start) = self.profiling_start_time {
331            prof_start.elapsed().as_millis() as f64 * 0.01 // Estimated 1% overhead
332        } else {
333            0.0
334        };
335
336        let report = self.generate_report(end_time, duration, profiling_overhead).await?;
337
338        tracing::info!("Memory profiler stopped for session {}", self.session_id);
339        Ok(report)
340    }
341
342    /// Record an allocation
343    pub fn record_allocation(
344        &self,
345        size: usize,
346        allocation_type: AllocationType,
347        tags: Vec<String>,
348    ) -> Result<Uuid> {
349        let running = self.running.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
350        if !*running {
351            return Err(anyhow::anyhow!("Memory profiler is not running"));
352        }
353
354        let allocation_id = Uuid::new_v4();
355        let record = AllocationRecord {
356            id: allocation_id,
357            size,
358            timestamp: SystemTime::now(),
359            stack_trace: self.capture_stack_trace(),
360            allocation_type: allocation_type.clone(),
361            freed: false,
362            freed_at: None,
363            tags,
364        };
365
366        // Store allocation record
367        let mut allocations =
368            self.allocations.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
369        allocations.insert(allocation_id, record);
370
371        // Update type statistics
372        self.update_type_stats(&allocation_type, size, true);
373
374        Ok(allocation_id)
375    }
376
377    /// Record a deallocation
378    pub fn record_deallocation(&self, allocation_id: Uuid) -> Result<()> {
379        let running = self.running.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
380        if !*running {
381            return Ok(()); // Silently ignore if not running
382        }
383
384        let mut allocations =
385            self.allocations.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
386        if let Some(record) = allocations.get_mut(&allocation_id) {
387            record.freed = true;
388            record.freed_at = Some(SystemTime::now());
389
390            // Update type statistics
391            self.update_type_stats(&record.allocation_type, record.size, false);
392        }
393
394        Ok(())
395    }
396
397    /// Tag an existing allocation
398    pub fn tag_allocation(&self, allocation_id: Uuid, tag: String) -> Result<()> {
399        let mut allocations =
400            self.allocations.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
401        if let Some(record) = allocations.get_mut(&allocation_id) {
402            record.tags.push(tag);
403        }
404        Ok(())
405    }
406
407    /// Get current memory usage snapshot
408    pub fn get_memory_snapshot(&self) -> Result<MemorySnapshot> {
409        let allocations = self.allocations.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
410        let _type_stats = self.type_stats.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
411
412        let mut total_heap = 0;
413        let mut used_heap = 0;
414        let mut allocation_count = 0;
415        let mut free_count = 0;
416        let mut allocations_by_type = HashMap::new();
417        let mut allocations_by_size = HashMap::new();
418
419        for record in allocations.values() {
420            total_heap += record.size;
421
422            if !record.freed {
423                used_heap += record.size;
424                allocation_count += 1;
425
426                *allocations_by_type.entry(record.allocation_type.clone()).or_insert(0) +=
427                    record.size;
428
429                let size_bucket = self.get_size_bucket(record.size);
430                *allocations_by_size.entry(size_bucket).or_insert(0) += 1;
431            } else {
432                free_count += 1;
433            }
434        }
435
436        let free_heap = total_heap - used_heap;
437        let fragmentation_ratio =
438            if total_heap > 0 { free_heap as f64 / total_heap as f64 } else { 0.0 };
439
440        let gc_pressure_score = self.calculate_gc_pressure_score();
441
442        Ok(MemorySnapshot {
443            timestamp: SystemTime::now(),
444            total_heap_bytes: total_heap,
445            used_heap_bytes: used_heap,
446            free_heap_bytes: free_heap,
447            peak_heap_bytes: used_heap, // Simplified for now
448            allocation_count,
449            free_count,
450            fragmentation_ratio,
451            gc_pressure_score,
452            allocations_by_type,
453            allocations_by_size,
454        })
455    }
456
457    /// Detect memory leaks
458    pub fn detect_leaks(&self) -> Result<Vec<MemoryLeak>> {
459        let allocations = self.allocations.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
460        let now = SystemTime::now();
461        let threshold = Duration::from_secs(self.config.leak_detection_threshold_secs);
462        let mut leaks = Vec::new();
463
464        for record in allocations.values() {
465            if !record.freed {
466                let age = now.duration_since(record.timestamp)?;
467                if age > threshold {
468                    let age_seconds = age.as_secs_f64();
469                    let severity = self.classify_leak_severity(record.size, age_seconds);
470
471                    leaks.push(MemoryLeak {
472                        allocation_id: record.id,
473                        size: record.size,
474                        age_seconds,
475                        allocation_type: record.allocation_type.clone(),
476                        stack_trace: record.stack_trace.clone(),
477                        tags: record.tags.clone(),
478                        severity,
479                    });
480                }
481            }
482        }
483
484        // Sort by severity and size
485        leaks.sort_by(|a, b| b.severity.cmp(&a.severity).then(b.size.cmp(&a.size)));
486
487        Ok(leaks)
488    }
489
490    /// Analyze allocation patterns
491    pub fn analyze_patterns(&self) -> Result<Vec<AllocationPattern>> {
492        let mut patterns = Vec::new();
493
494        // Detect memory leak patterns
495        if let Ok(leak_pattern) = self.detect_leak_pattern() {
496            patterns.push(leak_pattern);
497        }
498
499        // Detect churning allocation patterns
500        if let Ok(churn_pattern) = self.detect_churn_pattern() {
501            patterns.push(churn_pattern);
502        }
503
504        // Detect large allocation patterns
505        if let Ok(large_alloc_pattern) = self.detect_large_allocation_pattern() {
506            patterns.push(large_alloc_pattern);
507        }
508
509        // Detect fragmentation-causing patterns
510        if let Ok(frag_pattern) = self.detect_fragmentation_pattern() {
511            patterns.push(frag_pattern);
512        }
513
514        Ok(patterns)
515    }
516
517    /// Analyze memory fragmentation
518    pub fn analyze_fragmentation(&self) -> Result<FragmentationAnalysis> {
519        let snapshot = self.get_memory_snapshot()?;
520
521        let fragmentation_ratio = snapshot.fragmentation_ratio;
522        let severity = match fragmentation_ratio {
523            r if r < 0.1 => FragmentationSeverity::Low,
524            r if r < 0.3 => FragmentationSeverity::Medium,
525            r if r < 0.6 => FragmentationSeverity::High,
526            _ => FragmentationSeverity::Severe,
527        };
528
529        let recommendations = match severity {
530            FragmentationSeverity::Low => {
531                vec!["Memory fragmentation is low. Continue current practices.".to_string()]
532            },
533            FragmentationSeverity::Medium => vec![
534                "Consider pooling allocations of similar sizes.".to_string(),
535                "Monitor for increasing fragmentation trends.".to_string(),
536            ],
537            FragmentationSeverity::High => vec![
538                "Implement memory pooling for frequent allocations.".to_string(),
539                "Consider compaction strategies for long-running processes.".to_string(),
540                "Review allocation patterns for optimization opportunities.".to_string(),
541            ],
542            FragmentationSeverity::Severe => vec![
543                "Critical fragmentation detected. Immediate action required.".to_string(),
544                "Implement custom allocators with compaction.".to_string(),
545                "Consider restarting the process to reset memory layout.".to_string(),
546                "Review and optimize allocation strategies.".to_string(),
547            ],
548        };
549
550        Ok(FragmentationAnalysis {
551            fragmentation_ratio,
552            largest_free_block: snapshot.free_heap_bytes, // Simplified
553            total_free_memory: snapshot.free_heap_bytes,
554            free_block_count: snapshot.free_count,
555            average_free_block_size: if snapshot.free_count > 0 {
556                snapshot.free_heap_bytes as f64 / snapshot.free_count as f64
557            } else {
558                0.0
559            },
560            fragmentation_severity: severity,
561            recommendations,
562        })
563    }
564
565    /// Analyze GC pressure
566    pub fn analyze_gc_pressure(&self) -> Result<GCPressureAnalysis> {
567        let timeline = self.memory_timeline.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
568
569        let pressure_score = self.calculate_gc_pressure_score();
570        let (allocation_rate, deallocation_rate) = self.calculate_allocation_rates(&timeline);
571        let churn_rate = allocation_rate.min(deallocation_rate);
572
573        let pressure_level = match pressure_score {
574            p if p < 0.25 => GCPressureLevel::Low,
575            p if p < 0.5 => GCPressureLevel::Medium,
576            p if p < 0.75 => GCPressureLevel::High,
577            _ => GCPressureLevel::Critical,
578        };
579
580        let mut contributing_factors = Vec::new();
581        let mut recommendations = Vec::new();
582
583        if allocation_rate > 1000.0 {
584            contributing_factors.push("High allocation rate".to_string());
585            recommendations.push("Consider object pooling or reuse strategies".to_string());
586        }
587
588        if churn_rate > 500.0 {
589            contributing_factors.push("High allocation churn".to_string());
590            recommendations.push("Reduce temporary object creation".to_string());
591        }
592
593        if pressure_level == GCPressureLevel::Critical {
594            recommendations
595                .push("Consider manual memory management for critical paths".to_string());
596        }
597
598        Ok(GCPressureAnalysis {
599            pressure_score,
600            allocation_rate,
601            deallocation_rate,
602            churn_rate,
603            pressure_level,
604            contributing_factors,
605            recommendations,
606        })
607    }
608
609    // Private helper methods
610
611    async fn start_sampling(&self) -> Result<()> {
612        let interval_duration = Duration::from_millis(self.config.sampling_interval_ms);
613        let mut interval = interval(interval_duration);
614        let _timeline = Arc::clone(&self.memory_timeline);
615        let running = Arc::clone(&self.running);
616
617        tokio::spawn(async move {
618            loop {
619                interval.tick().await;
620
621                let is_running = {
622                    let running_guard =
623                        running.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
624                    *running_guard
625                };
626
627                if !is_running {
628                    break;
629                }
630
631                // This would normally sample actual memory usage
632                // For now, we'll use a placeholder implementation
633            }
634        });
635
636        Ok(())
637    }
638
639    pub async fn generate_report(
640        &self,
641        end_time: SystemTime,
642        duration_secs: f64,
643        profiling_overhead_ms: f64,
644    ) -> Result<MemoryProfilingReport> {
645        // Extract data from locked mutexes first, then drop guards before calling
646        // analysis methods that also need to acquire these locks.
647        let (
648            total_allocations,
649            total_deallocations,
650            net_allocations,
651            peak_memory_mb,
652            average_memory_mb,
653            allocations_by_size_bucket,
654            timeline_snapshot,
655            type_stats_snapshot,
656        ) = {
657            let allocations =
658                self.allocations.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
659            let timeline =
660                self.memory_timeline.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
661            let type_stats =
662                self.type_stats.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
663
664            let total_allocs = allocations.len();
665            let total_deallocs = allocations.values().filter(|r| r.freed).count();
666            let net_allocs = total_allocs as i64 - total_deallocs as i64;
667
668            // Calculate summary statistics
669            let peak_mem = timeline
670                .iter()
671                .map(|s| s.peak_heap_bytes as f64 / 1024.0 / 1024.0)
672                .fold(0.0, f64::max);
673
674            let avg_mem = if !timeline.is_empty() {
675                timeline.iter().map(|s| s.used_heap_bytes as f64 / 1024.0 / 1024.0).sum::<f64>()
676                    / timeline.len() as f64
677            } else {
678                0.0
679            };
680
681            // Create size buckets
682            let mut size_buckets = HashMap::new();
683            for record in allocations.values() {
684                let bucket = self.get_size_bucket(record.size);
685                *size_buckets.entry(bucket).or_insert(0) += 1;
686            }
687
688            let timeline_snap: Vec<_> = timeline.iter().cloned().collect();
689            let type_stats_snap = type_stats.clone();
690
691            (
692                total_allocs,
693                total_deallocs,
694                net_allocs,
695                peak_mem,
696                avg_mem,
697                size_buckets,
698                timeline_snap,
699                type_stats_snap,
700            )
701        };
702        // Guards are dropped here -- analysis methods can now safely acquire locks
703
704        let potential_leaks = self.detect_leaks()?;
705        let detected_patterns = self.analyze_patterns()?;
706        let fragmentation_analysis = self.analyze_fragmentation()?;
707        let gc_pressure_analysis = self.analyze_gc_pressure()?;
708
709        let mut leak_summary = HashMap::new();
710        for leak in &potential_leaks {
711            *leak_summary.entry(leak.allocation_type.clone()).or_insert(0) += 1;
712        }
713
714        Ok(MemoryProfilingReport {
715            session_id: self.session_id,
716            start_time: UNIX_EPOCH
717                + Duration::from_secs_f64(
718                    SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs_f64() - duration_secs,
719                ),
720            end_time,
721            duration_secs,
722            config: self.config.clone(),
723            peak_memory_mb,
724            average_memory_mb,
725            total_allocations,
726            total_deallocations,
727            net_allocations,
728            memory_timeline: timeline_snapshot,
729            potential_leaks,
730            leak_summary,
731            detected_patterns,
732            fragmentation_analysis,
733            gc_pressure_analysis,
734            allocations_by_type: type_stats_snapshot,
735            allocations_by_size_bucket,
736            profiling_overhead_ms,
737            sampling_accuracy: 0.95, // Placeholder
738        })
739    }
740
741    fn capture_stack_trace(&self) -> Vec<String> {
742        // Placeholder implementation - in a real implementation,
743        // this would capture the actual call stack
744        vec![
745            "function_a".to_string(),
746            "function_b".to_string(),
747            "main".to_string(),
748        ]
749    }
750
751    fn update_type_stats(
752        &self,
753        allocation_type: &AllocationType,
754        size: usize,
755        is_allocation: bool,
756    ) {
757        let mut type_stats =
758            self.type_stats.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
759        let stats = type_stats.entry(allocation_type.clone()).or_insert(AllocationTypeStats {
760            total_allocations: 0,
761            total_deallocations: 0,
762            current_count: 0,
763            total_bytes_allocated: 0,
764            total_bytes_deallocated: 0,
765            current_bytes: 0,
766            peak_count: 0,
767            peak_bytes: 0,
768            average_allocation_size: 0.0,
769            largest_allocation: 0,
770        });
771
772        if is_allocation {
773            stats.total_allocations += 1;
774            stats.current_count += 1;
775            stats.total_bytes_allocated += size;
776            stats.current_bytes += size;
777            stats.peak_count = stats.peak_count.max(stats.current_count);
778            stats.peak_bytes = stats.peak_bytes.max(stats.current_bytes);
779            stats.largest_allocation = stats.largest_allocation.max(size);
780        } else {
781            stats.total_deallocations += 1;
782            stats.current_count = stats.current_count.saturating_sub(1);
783            stats.total_bytes_deallocated += size;
784            stats.current_bytes = stats.current_bytes.saturating_sub(size);
785        }
786
787        stats.average_allocation_size = if stats.total_allocations > 0 {
788            stats.total_bytes_allocated as f64 / stats.total_allocations as f64
789        } else {
790            0.0
791        };
792    }
793
794    fn get_size_bucket(&self, size: usize) -> String {
795        match size {
796            0..=1024 => "0-1KB".to_string(),
797            1025..=10240 => "1-10KB".to_string(),
798            10241..=102400 => "10-100KB".to_string(),
799            102401..=1048576 => "100KB-1MB".to_string(),
800            1048577..=10485760 => "1-10MB".to_string(),
801            _ => ">10MB".to_string(),
802        }
803    }
804
805    fn classify_leak_severity(&self, size: usize, age_seconds: f64) -> LeakSeverity {
806        let large_size = size > self.config.large_allocation_threshold;
807        let old_age = age_seconds > 1800.0; // 30 minutes
808        let very_old_age = age_seconds > 3600.0; // 1 hour
809
810        match (large_size, old_age, very_old_age) {
811            (true, _, true) => LeakSeverity::Critical,
812            (true, true, _) => LeakSeverity::High,
813            (true, false, _) => LeakSeverity::Medium,
814            (false, true, _) => LeakSeverity::Medium,
815            _ => LeakSeverity::Low,
816        }
817    }
818
819    fn calculate_gc_pressure_score(&self) -> f64 {
820        // Simplified GC pressure calculation
821        // In a real implementation, this would consider allocation patterns,
822        // heap growth rate, and other factors
823        0.3 // Placeholder value
824    }
825
826    fn calculate_allocation_rates(&self, timeline: &VecDeque<MemorySnapshot>) -> (f64, f64) {
827        if timeline.len() < 2 {
828            return (0.0, 0.0);
829        }
830
831        // Simplified rate calculation
832        let first = &timeline[0];
833        let last = &timeline[timeline.len() - 1];
834
835        let duration = last
836            .timestamp
837            .duration_since(first.timestamp)
838            .unwrap_or(Duration::from_secs(1))
839            .as_secs_f64();
840
841        let allocation_rate =
842            (last.allocation_count as f64 - first.allocation_count as f64) / duration;
843        let deallocation_rate = (last.free_count as f64 - first.free_count as f64) / duration;
844
845        (allocation_rate.max(0.0), deallocation_rate.max(0.0))
846    }
847
848    // Pattern detection methods
849
850    fn detect_leak_pattern(&self) -> Result<AllocationPattern> {
851        let leaks = self.detect_leaks()?;
852        let high_severity_leaks = leaks
853            .iter()
854            .filter(|l| l.severity == LeakSeverity::High || l.severity == LeakSeverity::Critical)
855            .count();
856
857        let confidence = if leaks.len() > 10 { 0.9 } else { 0.5 };
858        let impact_score = (high_severity_leaks as f64 / (leaks.len().max(1)) as f64).min(1.0);
859
860        Ok(AllocationPattern {
861            pattern_type: PatternType::MemoryLeak,
862            description: format!("Detected {} potential memory leaks", leaks.len()),
863            confidence,
864            impact_score,
865            recommendations: vec![
866                "Review long-lived allocations for proper cleanup".to_string(),
867                "Implement RAII patterns for automatic resource management".to_string(),
868            ],
869            examples: leaks
870                .into_iter()
871                .take(3)
872                .map(|leak| {
873                    // Convert leak to allocation record for example
874                    AllocationRecord {
875                        id: leak.allocation_id,
876                        size: leak.size,
877                        timestamp: SystemTime::now(), // Placeholder
878                        stack_trace: leak.stack_trace,
879                        allocation_type: leak.allocation_type,
880                        freed: false,
881                        freed_at: None,
882                        tags: leak.tags,
883                    }
884                })
885                .collect(),
886        })
887    }
888
889    fn detect_churn_pattern(&self) -> Result<AllocationPattern> {
890        // Simplified churn detection
891        let allocations = self.allocations.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
892        let short_lived_count = allocations
893            .values()
894            .filter(|record| {
895                if let (Some(_freed_at), false) = (record.freed_at, record.freed) {
896                    false // Contradiction, skip
897                } else if record.freed {
898                    if let Some(freed_at) = record.freed_at {
899                        freed_at.duration_since(record.timestamp).unwrap_or(Duration::from_secs(0))
900                            < Duration::from_secs(1)
901                    } else {
902                        false
903                    }
904                } else {
905                    false
906                }
907            })
908            .count();
909
910        let total_count = allocations.len();
911        let churn_ratio = if total_count > 0 {
912            short_lived_count as f64 / total_count as f64
913        } else {
914            0.0
915        };
916
917        Ok(AllocationPattern {
918            pattern_type: PatternType::ChurningAllocations,
919            description: format!(
920                "High allocation churn detected: {:.1}% short-lived allocations",
921                churn_ratio * 100.0
922            ),
923            confidence: if churn_ratio > 0.5 { 0.8 } else { 0.4 },
924            impact_score: churn_ratio,
925            recommendations: vec![
926                "Consider object pooling for frequently allocated objects".to_string(),
927                "Reduce temporary object creation in hot paths".to_string(),
928            ],
929            examples: vec![], // Simplified for now
930        })
931    }
932
933    fn detect_large_allocation_pattern(&self) -> Result<AllocationPattern> {
934        let allocations = self.allocations.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
935        let large_allocations: Vec<_> = allocations
936            .values()
937            .filter(|record| record.size > self.config.large_allocation_threshold)
938            .cloned()
939            .collect();
940
941        let impact_score = if !allocations.is_empty() {
942            large_allocations.len() as f64 / allocations.len() as f64
943        } else {
944            0.0
945        };
946
947        Ok(AllocationPattern {
948            pattern_type: PatternType::LargeAllocations,
949            description: format!(
950                "Found {} large allocations (>{}MB)",
951                large_allocations.len(),
952                self.config.large_allocation_threshold / 1024 / 1024
953            ),
954            confidence: if large_allocations.len() > 5 { 0.9 } else { 0.6 },
955            impact_score,
956            recommendations: vec![
957                "Review large allocations for optimization opportunities".to_string(),
958                "Consider streaming or chunked processing for large data".to_string(),
959            ],
960            examples: large_allocations.into_iter().take(3).collect(),
961        })
962    }
963
964    fn detect_fragmentation_pattern(&self) -> Result<AllocationPattern> {
965        let fragmentation = self.analyze_fragmentation()?;
966
967        Ok(AllocationPattern {
968            pattern_type: PatternType::FragmentationCausing,
969            description: format!(
970                "Memory fragmentation at {:.1}%",
971                fragmentation.fragmentation_ratio * 100.0
972            ),
973            confidence: 0.8,
974            impact_score: fragmentation.fragmentation_ratio,
975            recommendations: fragmentation.recommendations,
976            examples: vec![], // Simplified for now
977        })
978    }
979}
980
981impl PartialOrd for LeakSeverity {
982    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
983        Some(self.cmp(other))
984    }
985}
986
987impl Ord for LeakSeverity {
988    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
989        let self_val = match self {
990            LeakSeverity::Low => 0,
991            LeakSeverity::Medium => 1,
992            LeakSeverity::High => 2,
993            LeakSeverity::Critical => 3,
994        };
995        let other_val = match other {
996            LeakSeverity::Low => 0,
997            LeakSeverity::Medium => 1,
998            LeakSeverity::High => 2,
999            LeakSeverity::Critical => 3,
1000        };
1001        self_val.cmp(&other_val)
1002    }
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007    use super::*;
1008    use tokio;
1009
1010    #[tokio::test(flavor = "multi_thread")]
1011    #[ignore] // FIXME: This test has implementation issues causing slow execution
1012    async fn test_memory_profiler_basic() -> Result<()> {
1013        let config = MemoryProfilingConfig {
1014            sampling_interval_ms: 1000, // Slower sampling for faster tests
1015            ..Default::default()
1016        };
1017        let mut profiler = MemoryProfiler::new(config);
1018
1019        // Wrap in timeout to prevent hanging
1020        let test_result = tokio::time::timeout(Duration::from_millis(500), async {
1021            profiler.start().await?;
1022
1023            // Record some allocations
1024            let alloc_id1 = profiler.record_allocation(
1025                1024,
1026                AllocationType::Tensor,
1027                vec!["test".to_string()],
1028            )?;
1029
1030            let _alloc_id2 = profiler.record_allocation(
1031                2048,
1032                AllocationType::Buffer,
1033                vec!["test".to_string()],
1034            )?;
1035
1036            // Free one allocation
1037            profiler.record_deallocation(alloc_id1)?;
1038
1039            // Give background tasks a moment to process
1040            tokio::time::sleep(Duration::from_millis(1)).await;
1041
1042            let report = profiler.stop().await?;
1043
1044            assert_eq!(report.total_allocations, 2);
1045            assert_eq!(report.total_deallocations, 1);
1046            assert_eq!(report.net_allocations, 1);
1047
1048            Ok::<(), anyhow::Error>(())
1049        })
1050        .await;
1051
1052        match test_result {
1053            Ok(result) => result,
1054            Err(_) => Err(anyhow::anyhow!("Test timed out after 500ms")),
1055        }
1056    }
1057
1058    #[tokio::test]
1059    async fn test_leak_detection() -> Result<()> {
1060        let config = MemoryProfilingConfig {
1061            leak_detection_threshold_secs: 1, // 1 second for testing
1062            ..Default::default()
1063        };
1064
1065        let mut profiler = MemoryProfiler::new(config);
1066        profiler.start().await?; // Start the profiler
1067
1068        // Record allocation and wait
1069        profiler.record_allocation(1024, AllocationType::Tensor, vec!["leak_test".to_string()])?;
1070
1071        tokio::time::sleep(Duration::from_secs(2)).await;
1072
1073        let leaks = profiler.detect_leaks()?;
1074        assert!(!leaks.is_empty());
1075
1076        Ok(())
1077    }
1078
1079    #[test]
1080    fn test_size_buckets() {
1081        let config = MemoryProfilingConfig::default();
1082        let profiler = MemoryProfiler::new(config);
1083
1084        assert_eq!(profiler.get_size_bucket(512), "0-1KB");
1085        assert_eq!(profiler.get_size_bucket(5120), "1-10KB");
1086        assert_eq!(profiler.get_size_bucket(51200), "10-100KB");
1087        assert_eq!(profiler.get_size_bucket(512000), "100KB-1MB");
1088        assert_eq!(profiler.get_size_bucket(5120000), "1-10MB");
1089        assert_eq!(profiler.get_size_bucket(51200000), ">10MB");
1090    }
1091
1092    #[test]
1093    fn test_leak_severity_classification() {
1094        let config = MemoryProfilingConfig::default();
1095        let profiler = MemoryProfiler::new(config);
1096
1097        // Small, new allocation
1098        assert_eq!(
1099            profiler.classify_leak_severity(1024, 60.0),
1100            LeakSeverity::Low
1101        );
1102
1103        // Large, old allocation
1104        assert_eq!(
1105            profiler.classify_leak_severity(10485760, 3700.0),
1106            LeakSeverity::Critical
1107        );
1108
1109        // Medium size, medium age
1110        assert_eq!(
1111            profiler.classify_leak_severity(524288, 1900.0),
1112            LeakSeverity::Medium
1113        );
1114    }
1115}