scirs2_core/memory/
leak_detection.rs

1//! # Memory Leak Detection System
2//!
3//! Production-grade memory leak detection and monitoring system for `SciRS2` Core
4//! providing comprehensive memory profiling, leak testing, and integration with
5//! memory profiling tools for enterprise deployments.
6//!
7//! ## Features
8//!
9//! - Real-time memory leak detection with configurable thresholds
10//! - Integration with valgrind, AddressSanitizer, and other profiling tools
11//! - Automatic leak testing for continuous integration
12//! - Memory pattern analysis and anomaly detection
13//! - Production-safe monitoring with minimal overhead
14//! - Detailed leak reports with call stack information
15//! - Integration with existing memory metrics system
16//! - Support for custom allocation tracking
17//!
18//! ## Example
19//!
20//! ```rust
21//! use scirs2_core::memory::leak_detection::{LeakDetector, LeakDetectionConfig};
22//!
23//! // Configure leak detection
24//! let config = LeakDetectionConfig::default()
25//!     .with_threshold_mb(100)
26//!     .with_samplingrate(0.1);
27//!
28//! let mut detector = LeakDetector::new(config)?;
29//!
30//! // Start monitoring a function
31//! let checkpoint = detector.create_checkpoint("matrix_operations")?;
32//!
33//! // Perform operations that might leak memory
34//! fn performmatrix_calculations() {
35//!     // Example computation that could potentially leak memory
36//!     let matrix: Vec<Vec<f64>> = (0..100).map(|i| vec![i as f64; 100]).collect();
37//! }
38//! performmatrix_calculations();
39//!
40//! // Check for leaks
41//! let report = detector.check_leaks(&checkpoint)?;
42//! if report.has_leaks() {
43//!     println!("Memory leaks detected: {}", report.summary());
44//! }
45//! # Ok::<(), Box<dyn std::error::Error>>(())
46//! ```
47
48use crate::error::CoreError;
49use std::collections::HashMap;
50use std::sync::{Arc, Mutex, RwLock};
51use std::time::Duration;
52use uuid::Uuid;
53
54#[cfg(feature = "serialization")]
55use serde::{Deserialize, Serialize};
56
57/// Memory leak detection configuration
58#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
59#[derive(Debug, Clone)]
60pub struct LeakDetectionConfig {
61    /// Enable real-time leak detection
62    pub enabled: bool,
63    /// Memory growth threshold in bytes
64    pub growth_threshold_bytes: u64,
65    /// Time window for leak detection
66    pub detection_window: Duration,
67    /// Sampling rate for profiling (0.0 to 1.0)
68    pub samplingrate: f64,
69    /// Enable call stack collection
70    pub collect_call_stacks: bool,
71    /// Maximum number of tracked allocations
72    pub max_tracked_allocations: usize,
73    /// Enable integration with external profilers
74    pub enable_external_profilers: bool,
75    /// Profiler tools to use
76    pub profiler_tools: Vec<ProfilerTool>,
77    /// Enable periodic leak checks
78    pub enable_periodic_checks: bool,
79    /// Periodic check interval
80    pub check_interval: Duration,
81    /// Enable production monitoring mode
82    pub production_mode: bool,
83}
84
85impl Default for LeakDetectionConfig {
86    fn default() -> Self {
87        Self {
88            enabled: true,
89            growth_threshold_bytes: 100 * 1024 * 1024, // 100MB
90            detection_window: Duration::from_secs(300), // 5 minutes
91            samplingrate: 0.01,                        // 1% sampling in production
92            collect_call_stacks: false,                // Expensive in production
93            max_tracked_allocations: 10000,
94            enable_external_profilers: false,
95            profiler_tools: Vec::new(),
96            enable_periodic_checks: true,
97            check_interval: Duration::from_secs(60), // 1 minute
98            production_mode: true,
99        }
100    }
101}
102
103impl LeakDetectionConfig {
104    /// Set memory threshold in megabytes
105    pub fn with_threshold_mb(mut self, mb: u64) -> Self {
106        self.growth_threshold_bytes = mb * 1024 * 1024;
107        self
108    }
109
110    /// Set sampling rate
111    pub fn with_samplingrate(mut self, rate: f64) -> Self {
112        self.samplingrate = rate.clamp(0.0, 1.0);
113        self
114    }
115
116    /// Enable call stack collection
117    pub fn with_call_stacks(mut self, enable: bool) -> Self {
118        self.collect_call_stacks = enable;
119        self
120    }
121
122    /// Enable development mode (more detailed tracking)
123    pub fn development_mode(mut self) -> Self {
124        self.production_mode = false;
125        self.samplingrate = 1.0;
126        self.collect_call_stacks = true;
127        self.max_tracked_allocations = 100000;
128        self
129    }
130}
131
132/// External profiler tools
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134pub enum ProfilerTool {
135    /// Valgrind memcheck
136    Valgrind,
137    /// AddressSanitizer
138    AddressSanitizer,
139    /// Heaptrack
140    Heaptrack,
141    /// Massif (part of Valgrind)
142    Massif,
143    /// jemalloc profiling
144    Jemalloc,
145    /// Custom profiler
146    Custom(String),
147}
148
149impl ProfilerTool {
150    /// Get the tool name
151    pub fn name(&self) -> &str {
152        match self {
153            ProfilerTool::Valgrind => "valgrind",
154            ProfilerTool::AddressSanitizer => "asan",
155            ProfilerTool::Heaptrack => "heaptrack",
156            ProfilerTool::Massif => "massif",
157            ProfilerTool::Jemalloc => "jemalloc",
158            ProfilerTool::Custom(name) => name,
159        }
160    }
161}
162
163/// Memory checkpoint for leak detection
164#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
165#[derive(Debug, Clone)]
166pub struct MemoryCheckpoint {
167    /// Unique checkpoint identifier
168    pub id: Uuid,
169    /// Checkpoint name/label
170    pub name: String,
171    /// Timestamp when checkpoint was created
172    pub timestamp: chrono::DateTime<chrono::Utc>,
173    /// Memory usage at checkpoint creation
174    pub memory_usage: MemoryUsage,
175    /// Active allocations at checkpoint
176    pub active_allocations: u64,
177    /// Call stack when checkpoint was created
178    pub call_stack: Option<CallStack>,
179    /// Thread ID
180    pub thread_id: u64,
181    /// Process ID
182    pub process_id: u32,
183}
184
185/// Memory usage snapshot
186#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
187#[derive(Debug, Clone)]
188pub struct MemoryUsage {
189    /// Resident set size (RSS) in bytes
190    pub rss_bytes: u64,
191    /// Virtual memory size in bytes
192    pub virtual_bytes: u64,
193    /// Heap size in bytes
194    pub heap_bytes: Option<u64>,
195    /// Stack size in bytes
196    pub stack_bytes: Option<u64>,
197    /// Number of memory mappings
198    pub mappings_count: Option<u64>,
199    /// Peak memory usage since start
200    pub peak_bytes: Option<u64>,
201}
202
203/// Call stack information
204#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
205#[derive(Debug, Clone)]
206pub struct CallStack {
207    /// Stack frames
208    pub frames: Vec<StackFrame>,
209    /// Maximum depth captured
210    pub max_depth: usize,
211    /// Whether stack was truncated
212    pub truncated: bool,
213}
214
215/// Individual stack frame
216#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
217#[derive(Debug, Clone)]
218pub struct StackFrame {
219    /// Function name
220    pub function: Option<String>,
221    /// File name
222    pub file: Option<String>,
223    /// Line number
224    pub line: Option<u32>,
225    /// Memory address
226    pub address: u64,
227    /// Module/library name
228    pub module: Option<String>,
229}
230
231/// Memory leak report
232#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
233#[derive(Debug, Clone)]
234pub struct LeakReport {
235    /// Checkpoint information
236    pub checkpoint: MemoryCheckpoint,
237    /// Current memory usage
238    pub current_usage: MemoryUsage,
239    /// Memory growth since checkpoint
240    pub memory_growth: i64,
241    /// Detected leaks
242    pub leaks: Vec<MemoryLeak>,
243    /// Analysis summary
244    pub summary: LeakSummary,
245    /// Recommendations
246    pub recommendations: Vec<String>,
247    /// Detection timestamp
248    pub detection_time: chrono::DateTime<chrono::Utc>,
249}
250
251impl LeakReport {
252    /// Check if any leaks were detected
253    pub fn has_leaks(&self) -> bool {
254        !self.leaks.is_empty()
255    }
256
257    /// Get total leaked bytes
258    pub fn total_leaked_bytes(&self) -> u64 {
259        self.leaks.iter().map(|leak| leak.size_bytes).sum()
260    }
261
262    /// Get summary string
263    pub fn summary(&self) -> String {
264        if self.has_leaks() {
265            format!(
266                "{} leaks detected totaling {} bytes",
267                self.leaks.len(),
268                self.total_leaked_bytes()
269            )
270        } else {
271            "No memory leaks detected".to_string()
272        }
273    }
274}
275
276/// Individual memory leak
277#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
278#[derive(Debug, Clone)]
279pub struct MemoryLeak {
280    /// Leak identifier
281    pub id: Uuid,
282    /// Size of the leak in bytes
283    pub size_bytes: u64,
284    /// Allocation site call stack
285    pub allocation_stack: Option<CallStack>,
286    /// Leak type
287    pub leak_type: LeakType,
288    /// Confidence level
289    pub confidence: f64,
290    /// First detected timestamp
291    pub first_detected: chrono::DateTime<chrono::Utc>,
292    /// Last seen timestamp
293    pub last_seen: chrono::DateTime<chrono::Utc>,
294    /// Allocation count
295    pub allocation_count: u64,
296}
297
298/// Types of memory leaks
299#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
300#[derive(Debug, Clone, Copy, PartialEq, Eq)]
301pub enum LeakType {
302    /// Definite leak - memory definitely lost
303    Definite,
304    /// Indirect leak - leak caused by other leaks
305    Indirect,
306    /// Possible leak - might be intentionally retained
307    Possible,
308    /// Reachable leak - still reachable but suspicious
309    Reachable,
310    /// Growth pattern - suspicious growth pattern
311    GrowthPattern,
312}
313
314impl LeakType {
315    /// Get the severity score (0-10)
316    pub fn severity(&self) -> u8 {
317        match self {
318            LeakType::Definite => 10,
319            LeakType::Indirect => 8,
320            LeakType::Possible => 5,
321            LeakType::Reachable => 3,
322            LeakType::GrowthPattern => 7,
323        }
324    }
325}
326
327/// Leak detection summary
328#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
329#[derive(Debug, Clone)]
330pub struct LeakSummary {
331    /// Total number of leaks
332    pub total_leaks: usize,
333    /// Total leaked bytes
334    pub total_leaked_bytes: u64,
335    /// Leaks by type
336    pub leaks_by_type: HashMap<String, usize>,
337    /// Highest severity leak
338    pub max_severity: u8,
339    /// Average confidence
340    pub average_confidence: f64,
341    /// Memory growth rate (bytes per second)
342    pub growth_rate: f64,
343}
344
345/// Allocation tracking information
346#[derive(Debug, Clone)]
347struct AllocationInfo {
348    /// Allocation size
349    size: u64,
350    /// Allocation timestamp
351    timestamp: chrono::DateTime<chrono::Utc>,
352    /// Call stack at allocation
353    call_stack: Option<CallStack>,
354    /// Thread ID
355    #[allow(dead_code)]
356    thread_id: u64,
357    /// Allocation ID
358    #[allow(dead_code)]
359    id: u64,
360}
361
362/// Memory leak detector implementation
363pub struct LeakDetector {
364    /// Configuration
365    config: LeakDetectionConfig,
366    /// Tracked allocations
367    allocations: Arc<RwLock<HashMap<u64, AllocationInfo>>>,
368    /// Address to allocation ID mapping
369    address_to_id: Arc<RwLock<HashMap<u64, u64>>>,
370    /// Checkpoints
371    checkpoints: Arc<Mutex<HashMap<Uuid, MemoryCheckpoint>>>,
372    /// Detection results
373    reports: Arc<Mutex<Vec<LeakReport>>>,
374    /// Background monitoring
375    monitoring_active: Arc<Mutex<bool>>,
376    /// Allocation counter
377    allocation_counter: Arc<Mutex<u64>>,
378    /// External profiler integrations
379    profiler_integrations: Vec<Box<dyn ProfilerIntegration + Send + Sync>>,
380}
381
382impl LeakDetector {
383    /// Create a new leak detector
384    pub fn new(config: LeakDetectionConfig) -> Result<Self, CoreError> {
385        let detector = Self {
386            config,
387            allocations: Arc::new(RwLock::new(HashMap::new())),
388            address_to_id: Arc::new(RwLock::new(HashMap::new())),
389            checkpoints: Arc::new(Mutex::new(HashMap::new())),
390            reports: Arc::new(Mutex::new(Vec::new())),
391            monitoring_active: Arc::new(Mutex::new(false)),
392            allocation_counter: Arc::new(Mutex::new(0)),
393            profiler_integrations: Vec::new(),
394        };
395
396        Ok(detector)
397    }
398
399    /// Start background monitoring
400    pub fn startmonitoring(&self) -> Result<(), CoreError> {
401        if !self.config.enabled || !self.config.enable_periodic_checks {
402            return Ok(());
403        }
404
405        let mut monitoring = self.monitoring_active.lock().map_err(|_| {
406            CoreError::ComputationError(crate::error::ErrorContext::new(
407                "Failed to acquire monitoring lock".to_string(),
408            ))
409        })?;
410
411        if *monitoring {
412            return Ok(()); // Already monitoring
413        }
414
415        *monitoring = true;
416
417        // In a real implementation, you'd spawn a background thread here
418        // For now, we'll just mark monitoring as active
419        Ok(())
420    }
421
422    /// Stop background monitoring
423    pub fn stopmonitoring(&self) -> Result<(), CoreError> {
424        let mut monitoring = self.monitoring_active.lock().map_err(|_| {
425            CoreError::ComputationError(crate::error::ErrorContext::new(
426                "Failed to acquire monitoring lock".to_string(),
427            ))
428        })?;
429        *monitoring = false;
430        Ok(())
431    }
432
433    /// Create a memory checkpoint
434    pub fn create_checkpoint(&self, name: &str) -> Result<MemoryCheckpoint, CoreError> {
435        let memory_usage = self.get_current_memory_usage()?;
436        let call_stack = if self.config.collect_call_stacks {
437            Some(self.capture_call_stack()?)
438        } else {
439            None
440        };
441
442        let checkpoint = MemoryCheckpoint {
443            id: Uuid::new_v4(),
444            name: name.to_string(),
445            timestamp: chrono::Utc::now(),
446            memory_usage,
447            active_allocations: self.get_active_allocation_count()?,
448            call_stack,
449            thread_id: self.get_thread_id(),
450            process_id: std::process::id(),
451        };
452
453        let mut checkpoints = self.checkpoints.lock().map_err(|_| {
454            CoreError::ComputationError(crate::error::ErrorContext::new(
455                "Failed to acquire checkpoints lock".to_string(),
456            ))
457        })?;
458        checkpoints.insert(checkpoint.id, checkpoint.clone());
459
460        Ok(checkpoint)
461    }
462
463    /// Check for memory leaks since a checkpoint
464    pub fn check_leaks(&self, checkpoint: &MemoryCheckpoint) -> Result<LeakReport, CoreError> {
465        let current_usage = self.get_current_memory_usage()?;
466        let memory_growth =
467            current_usage.rss_bytes as i64 - checkpoint.memory_usage.rss_bytes as i64;
468
469        let mut leaks = Vec::new();
470
471        // Check for threshold violations
472        if memory_growth > self.config.growth_threshold_bytes as i64 {
473            let leak = MemoryLeak {
474                id: Uuid::new_v4(),
475                size_bytes: memory_growth as u64,
476                allocation_stack: checkpoint.call_stack.clone(),
477                leak_type: LeakType::GrowthPattern,
478                confidence: 0.8,
479                first_detected: checkpoint.timestamp,
480                last_seen: chrono::Utc::now(),
481                allocation_count: 1,
482            };
483            leaks.push(leak);
484        }
485
486        // Analyze allocation patterns
487        leaks.extend(self.analyze_allocation_patterns(checkpoint)?);
488
489        // Run external profiler checks
490        leaks.extend(self.run_external_profiler_checks()?);
491
492        let summary = self.create_leak_summary(&leaks, memory_growth);
493        let recommendations = self.generate_recommendations(&leaks, memory_growth);
494
495        let report = LeakReport {
496            checkpoint: checkpoint.clone(),
497            current_usage,
498            memory_growth,
499            leaks,
500            summary,
501            recommendations,
502            detection_time: chrono::Utc::now(),
503        };
504
505        // Store report
506        let mut reports = self.reports.lock().map_err(|_| {
507            CoreError::ComputationError(crate::error::ErrorContext::new(
508                "Failed to acquire reports lock".to_string(),
509            ))
510        })?;
511        reports.push(report.clone());
512
513        Ok(report)
514    }
515
516    /// Track a memory allocation
517    pub fn track_allocation(&self, size: u64, address: u64) -> Result<(), CoreError> {
518        if !self.config.enabled {
519            return Ok(());
520        }
521
522        // Apply sampling
523        if self.config.samplingrate < 1.0 {
524            use rand::Rng;
525            let mut rng = rand::rng();
526            if rng.random::<f64>() > self.config.samplingrate {
527                return Ok(());
528            }
529        }
530
531        let mut counter = self.allocation_counter.lock().map_err(|_| {
532            CoreError::ComputationError(crate::error::ErrorContext::new(
533                "Failed to acquire allocation counter".to_string(),
534            ))
535        })?;
536        *counter += 1;
537        let allocation_id = *counter;
538
539        let call_stack = if self.config.collect_call_stacks {
540            Some(self.capture_call_stack()?)
541        } else {
542            None
543        };
544
545        let allocation_info = AllocationInfo {
546            size,
547            timestamp: chrono::Utc::now(),
548            call_stack,
549            thread_id: self.get_thread_id(),
550            id: allocation_id,
551        };
552
553        let mut allocations = self.allocations.write().map_err(|_| {
554            CoreError::ComputationError(crate::error::ErrorContext::new(
555                "Failed to acquire allocations lock".to_string(),
556            ))
557        })?;
558
559        // Prevent memory usage from growing too much
560        if allocations.len() >= self.config.max_tracked_allocations {
561            // Remove oldest allocation
562            if let Some((oldest_id, oldest_info)) = allocations
563                .iter()
564                .min_by_key(|(_, info)| info.timestamp)
565                .map(|(id, info)| (*id, info.clone()))
566            {
567                allocations.remove(&oldest_id);
568
569                // Also remove from address_to_id mapping
570                // Note: We don't have the address stored in AllocationInfo, so we'd need to
571                // iterate through address_to_id to find it. In a real implementation,
572                // AllocationInfo should store the address.
573            }
574        }
575
576        allocations.insert(allocation_id, allocation_info);
577
578        // Store address to ID mapping
579        let mut address_to_id = self.address_to_id.write().map_err(|_| {
580            CoreError::ComputationError(crate::error::ErrorContext::new(
581                "Failed to acquire address_to_id lock".to_string(),
582            ))
583        })?;
584        address_to_id.insert(address, allocation_id);
585
586        Ok(())
587    }
588
589    /// Track memory deallocation
590    pub fn track_deallocation(&self, address: u64) -> Result<(), CoreError> {
591        if !self.config.enabled {
592            return Ok(());
593        }
594
595        // Look up the allocation ID for this address
596        let allocation_id = {
597            let mut address_to_id = self.address_to_id.write().map_err(|_| {
598                CoreError::ComputationError(crate::error::ErrorContext::new(
599                    "Failed to acquire address_to_id lock".to_string(),
600                ))
601            })?;
602            address_to_id.remove(&address)
603        };
604
605        if let Some(id) = allocation_id {
606            let mut allocations = self.allocations.write().map_err(|_| {
607                CoreError::ComputationError(crate::error::ErrorContext::new(
608                    "Failed to acquire allocations lock".to_string(),
609                ))
610            })?;
611            allocations.remove(&id);
612        }
613
614        Ok(())
615    }
616
617    /// Get current memory usage
618    fn get_current_memory_usage(&self) -> Result<MemoryUsage, CoreError> {
619        // In a real implementation, this would query actual system memory usage
620        // For now, we'll return mock data
621        Ok(MemoryUsage {
622            rss_bytes: self.get_rss_memory()?,
623            virtual_bytes: self.get_virtual_memory()?,
624            heap_bytes: Some(self.get_heap_memory()?),
625            stack_bytes: None,
626            mappings_count: None,
627            peak_bytes: None,
628        })
629    }
630
631    /// Get resident set size (RSS) memory
632    fn get_rss_memory(&self) -> Result<u64, CoreError> {
633        // Simplified implementation - in production, read from /proc/self/status or similar
634        #[cfg(target_os = "linux")]
635        {
636            if let Ok(status) = std::fs::read_to_string("/proc/self/status") {
637                for line in status.lines() {
638                    if line.starts_with("VmRSS:") {
639                        if let Some(kb_str) = line.split_whitespace().nth(1) {
640                            if let Ok(kb) = kb_str.parse::<u64>() {
641                                return Ok(kb * 1024); // Convert KB to bytes
642                            }
643                        }
644                    }
645                }
646            }
647        }
648
649        // Fallback to mock data
650        Ok(64 * 1024 * 1024) // 64MB
651    }
652
653    /// Get virtual memory size
654    fn get_virtual_memory(&self) -> Result<u64, CoreError> {
655        // Simplified implementation
656        #[cfg(target_os = "linux")]
657        {
658            if let Ok(status) = std::fs::read_to_string("/proc/self/status") {
659                for line in status.lines() {
660                    if line.starts_with("VmSize:") {
661                        if let Some(kb_str) = line.split_whitespace().nth(1) {
662                            if let Ok(kb) = kb_str.parse::<u64>() {
663                                return Ok(kb * 1024); // Convert KB to bytes
664                            }
665                        }
666                    }
667                }
668            }
669        }
670
671        // Fallback
672        Ok(256 * 1024 * 1024) // 256MB
673    }
674
675    /// Get heap memory usage
676    fn get_heap_memory(&self) -> Result<u64, CoreError> {
677        // This would integrate with malloc stats or jemalloc
678        Ok(32 * 1024 * 1024) // 32MB mock
679    }
680
681    /// Get active allocation count
682    fn get_active_allocation_count(&self) -> Result<u64, CoreError> {
683        let allocations = self.allocations.read().map_err(|_| {
684            CoreError::ComputationError(crate::error::ErrorContext::new(
685                "Failed to acquire allocations lock".to_string(),
686            ))
687        })?;
688        Ok(allocations.len() as u64)
689    }
690
691    /// Capture current call stack
692    fn capture_call_stack(&self) -> Result<CallStack, CoreError> {
693        // In a real implementation, this would use backtrace crate or similar
694        Ok(CallStack {
695            frames: vec![StackFrame {
696                function: Some("capture_call_stack".to_string()),
697                file: Some("leak_detection.rs".to_string()),
698                line: Some(line!()),
699                address: 0x12345678,
700                module: Some("scirs2_core".to_string()),
701            }],
702            max_depth: 50,
703            truncated: false,
704        })
705    }
706
707    /// Get current thread ID
708    fn get_thread_id(&self) -> u64 {
709        // Simplified implementation
710        use std::thread;
711        format!("{:?}", thread::current().id())
712            .chars()
713            .filter_map(|c| c.to_digit(10))
714            .map(|d| d as u64)
715            .fold(0, |acc, d| acc * 10 + d)
716    }
717
718    /// Analyze allocation patterns for leaks
719    fn analyze_allocation_patterns(
720        &self,
721        _checkpoint: &MemoryCheckpoint,
722    ) -> Result<Vec<MemoryLeak>, CoreError> {
723        let mut leaks = Vec::new();
724
725        let allocations = self.allocations.read().map_err(|_| {
726            CoreError::ComputationError(crate::error::ErrorContext::new(
727                "Failed to acquire allocations lock".to_string(),
728            ))
729        })?;
730
731        // Look for suspicious patterns
732        let now = chrono::Utc::now();
733        let old_threshold = now - Duration::from_secs(3600); // 1 hour
734
735        for (_id, allocation) in allocations.iter() {
736            if allocation.timestamp < old_threshold && allocation.size > 1024 * 1024 {
737                // Large allocation that's been around for a while
738                leaks.push(MemoryLeak {
739                    id: Uuid::new_v4(),
740                    size_bytes: allocation.size,
741                    allocation_stack: allocation.call_stack.clone(),
742                    leak_type: LeakType::Possible,
743                    confidence: 0.6,
744                    first_detected: allocation.timestamp,
745                    last_seen: now,
746                    allocation_count: 1,
747                });
748            }
749        }
750
751        Ok(leaks)
752    }
753
754    /// Run external profiler checks
755    fn run_external_profiler_checks(&self) -> Result<Vec<MemoryLeak>, CoreError> {
756        let mut leaks = Vec::new();
757
758        for integration in &self.profiler_integrations {
759            leaks.extend(integration.check_leaks()?);
760        }
761
762        Ok(leaks)
763    }
764
765    /// Create leak summary
766    fn create_leak_summary(&self, leaks: &[MemoryLeak], memorygrowth: i64) -> LeakSummary {
767        let total_leaks = leaks.len();
768        let total_leaked_bytes = leaks.iter().map(|leak| leak.size_bytes).sum();
769
770        let mut leaks_by_type = HashMap::new();
771        for leak in leaks {
772            let type_name = format!("{0:?}", leak.leak_type);
773            *leaks_by_type.entry(type_name).or_insert(0) += 1;
774        }
775
776        let max_severity = leaks
777            .iter()
778            .map(|leak| leak.leak_type.severity())
779            .max()
780            .unwrap_or(0);
781
782        let average_confidence = if total_leaks > 0 {
783            leaks.iter().map(|leak| leak.confidence).sum::<f64>() / total_leaks as f64
784        } else {
785            0.0
786        };
787
788        let growth_rate = memorygrowth as f64 / 60.0; // bytes per second (assuming 1 minute window)
789
790        LeakSummary {
791            total_leaks,
792            total_leaked_bytes,
793            leaks_by_type,
794            max_severity,
795            average_confidence,
796            growth_rate,
797        }
798    }
799
800    /// Generate recommendations based on detected leaks
801    fn generate_recommendations(&self, leaks: &[MemoryLeak], memorygrowth: i64) -> Vec<String> {
802        let mut recommendations = Vec::new();
803
804        if leaks.is_empty() && memorygrowth < 1024 * 1024 {
805            recommendations.push("No significant memory issues detected".to_string());
806            return recommendations;
807        }
808
809        if memorygrowth > self.config.growth_threshold_bytes as i64 {
810            recommendations.push(format!(
811                "Memory growth of {memorygrowth} bytes exceeds threshold, investigate allocation patterns"
812            ));
813        }
814
815        let definite_leaks: Vec<_> = leaks
816            .iter()
817            .filter(|leak| leak.leak_type == LeakType::Definite)
818            .collect();
819
820        if !definite_leaks.is_empty() {
821            recommendations.push(format!(
822                "{} definite leaks detected - fix these immediately",
823                definite_leaks.len()
824            ));
825        }
826
827        let large_leaks: Vec<_> = leaks.iter()
828            .filter(|leak| leak.size_bytes > 10 * 1024 * 1024) // > 10MB
829            .collect();
830
831        if !large_leaks.is_empty() {
832            recommendations.push(format!(
833                "{} large leaks (>10MB) detected - prioritize fixing these",
834                large_leaks.len()
835            ));
836        }
837
838        if leaks.iter().any(|leak| leak.confidence > 0.8) {
839            recommendations.push("High confidence leaks detected - likely real issues".to_string());
840        }
841
842        recommendations.push(
843            "Consider running with valgrind or AddressSanitizer for detailed analysis".to_string(),
844        );
845
846        recommendations
847    }
848
849    /// Get all reports
850    pub fn get_reports(&self) -> Result<Vec<LeakReport>, CoreError> {
851        let reports = self.reports.lock().map_err(|_| {
852            CoreError::ComputationError(crate::error::ErrorContext::new(
853                "Failed to acquire reports lock".to_string(),
854            ))
855        })?;
856        Ok(reports.clone())
857    }
858
859    /// Clear old reports
860    pub fn clear_old_reports(&self, maxage: Duration) -> Result<usize, CoreError> {
861        let mut reports = self.reports.lock().map_err(|_| {
862            CoreError::ComputationError(crate::error::ErrorContext::new(
863                "Failed to acquire reports lock".to_string(),
864            ))
865        })?;
866
867        let cutoff = chrono::Utc::now()
868            - chrono::Duration::from_std(maxage).map_err(|e| {
869                CoreError::ComputationError(crate::error::ErrorContext::new(format!(
870                    "Invalid duration: {e}"
871                )))
872            })?;
873
874        let initial_len = reports.len();
875        reports.retain(|report| report.detection_time > cutoff);
876
877        Ok(initial_len - reports.len())
878    }
879}
880
881/// Trait for external profiler integrations
882pub trait ProfilerIntegration {
883    /// Check for leaks using the external profiler
884    fn check_leaks(&self) -> Result<Vec<MemoryLeak>, CoreError>;
885
886    /// Get profiler name
887    fn name(&self) -> &str;
888
889    /// Check if profiler is available
890    fn is_available(&self) -> bool;
891}
892
893/// Valgrind integration
894pub struct ValgrindIntegration {
895    enabled: bool,
896}
897
898impl ValgrindIntegration {
899    pub fn new() -> Self {
900        Self {
901            enabled: Self::check_valgrind_available(),
902        }
903    }
904
905    fn check_valgrind_available() -> bool {
906        // Check if running under valgrind
907        std::env::var("VALGRIND_OPTS").is_ok() || std::env::var("RUNNING_ON_VALGRIND").is_ok()
908    }
909}
910
911impl ProfilerIntegration for ValgrindIntegration {
912    fn check_leaks(&self) -> Result<Vec<MemoryLeak>, CoreError> {
913        if !self.enabled {
914            return Ok(Vec::new());
915        }
916
917        // In a real implementation, this would parse valgrind output
918        // For now, return empty
919        Ok(Vec::new())
920    }
921
922    fn name(&self) -> &str {
923        "valgrind"
924    }
925
926    fn is_available(&self) -> bool {
927        self.enabled
928    }
929}
930
931impl Default for ValgrindIntegration {
932    fn default() -> Self {
933        Self::new()
934    }
935}
936
937/// RAII guard for automatic leak checking
938pub struct LeakCheckGuard<'a> {
939    detector: &'a LeakDetector,
940    checkpoint: MemoryCheckpoint,
941    check_on_drop: bool,
942}
943
944impl<'a> LeakCheckGuard<'a> {
945    /// Create a new leak check guard
946    pub fn new(detector: &'a LeakDetector, name: &str) -> Result<Self, CoreError> {
947        let checkpoint = detector.create_checkpoint(name)?;
948        Ok(Self {
949            detector,
950            checkpoint,
951            check_on_drop: true,
952        })
953    }
954
955    /// Disable automatic check on drop
956    pub fn disable_auto_check(mut self) -> Self {
957        self.check_on_drop = false;
958        self
959    }
960
961    /// Manually check for leaks
962    pub fn check_leaks(&self) -> Result<LeakReport, CoreError> {
963        self.detector.check_leaks(&self.checkpoint)
964    }
965}
966
967impl Drop for LeakCheckGuard<'_> {
968    fn drop(&mut self) {
969        if self.check_on_drop {
970            if let Ok(report) = self.detector.check_leaks(&self.checkpoint) {
971                if report.has_leaks() {
972                    eprintln!(
973                        "Memory leaks detected in {}: {}",
974                        self.checkpoint.name,
975                        report.summary()
976                    );
977                }
978            }
979        }
980    }
981}
982
983/// Convenience macro for leak checking
984#[macro_export]
985macro_rules! check_leaks {
986    ($detector:expr, $name:expr, $block:block) => {{
987        let guard = $crate::memory::leak_detection::LeakCheckGuard::new($detector, $name)?;
988        $block
989    }};
990}
991
992/// Global leak detector instance
993static GLOBAL_DETECTOR: std::sync::OnceLock<Arc<Mutex<LeakDetector>>> = std::sync::OnceLock::new();
994
995/// Get the global leak detector
996#[allow(dead_code)]
997pub fn global_leak_detector() -> Arc<Mutex<LeakDetector>> {
998    GLOBAL_DETECTOR
999        .get_or_init(|| {
1000            let config = LeakDetectionConfig::default();
1001            Arc::new(Mutex::new(
1002                LeakDetector::new(config).expect("Failed to create global leak detector"),
1003            ))
1004        })
1005        .clone()
1006}
1007
1008#[cfg(test)]
1009mod tests {
1010    use super::*;
1011
1012    #[test]
1013    fn test_leak_detector_creation() {
1014        let config = LeakDetectionConfig::default();
1015        let detector = LeakDetector::new(config).expect("Operation failed");
1016
1017        assert!(!*detector.monitoring_active.lock().expect("Operation failed"));
1018    }
1019
1020    #[test]
1021    fn test_checkpoint_creation() {
1022        let config = LeakDetectionConfig::default();
1023        let detector = LeakDetector::new(config).expect("Operation failed");
1024
1025        let checkpoint = detector
1026            .create_checkpoint("test")
1027            .expect("Operation failed");
1028        assert_eq!(checkpoint.name, "test");
1029        assert!(checkpoint.memory_usage.rss_bytes > 0);
1030    }
1031
1032    #[test]
1033    fn test_allocation_tracking() {
1034        let config = LeakDetectionConfig::default().development_mode();
1035        let detector = LeakDetector::new(config).expect("Operation failed");
1036
1037        detector
1038            .track_allocation(1024, 0x12345678)
1039            .expect("Operation failed");
1040        detector
1041            .track_allocation(2048, 0x87654321)
1042            .expect("Operation failed");
1043
1044        let count = detector
1045            .get_active_allocation_count()
1046            .expect("Operation failed");
1047        assert_eq!(count, 2);
1048
1049        detector
1050            .track_deallocation(0x12345678)
1051            .expect("Operation failed");
1052        let count = detector
1053            .get_active_allocation_count()
1054            .expect("Operation failed");
1055        assert_eq!(count, 1);
1056    }
1057
1058    #[test]
1059    fn test_leak_check_guard() {
1060        let config = LeakDetectionConfig::default();
1061        let detector = LeakDetector::new(config).expect("Operation failed");
1062
1063        {
1064            let guard = LeakCheckGuard::new(&detector, "test_guard").expect("Operation failed");
1065            // Simulate some work that might leak memory
1066            detector
1067                .track_allocation(1024 * 1024, 0x12345678)
1068                .expect("Operation failed");
1069        } // Guard drops here and checks for leaks
1070    }
1071
1072    #[test]
1073    fn test_leak_types() {
1074        assert_eq!(LeakType::Definite.severity(), 10);
1075        assert_eq!(LeakType::Possible.severity(), 5);
1076        assert!(LeakType::Definite.severity() > LeakType::Possible.severity());
1077    }
1078
1079    #[test]
1080    fn test_config_builder() {
1081        let config = LeakDetectionConfig::default()
1082            .with_threshold_mb(50)
1083            .with_samplingrate(0.5)
1084            .with_call_stacks(true);
1085
1086        assert_eq!(config.growth_threshold_bytes, 50 * 1024 * 1024);
1087        assert_eq!(config.samplingrate, 0.5);
1088        assert!(config.collect_call_stacks);
1089    }
1090
1091    #[test]
1092    fn test_profiler_tools() {
1093        assert_eq!(ProfilerTool::Valgrind.name(), "valgrind");
1094        assert_eq!(ProfilerTool::AddressSanitizer.name(), "asan");
1095        assert_eq!(ProfilerTool::Custom("custom".to_string()).name(), "custom");
1096    }
1097
1098    #[test]
1099    fn test_memory_usage() {
1100        let usage = MemoryUsage {
1101            rss_bytes: 64 * 1024 * 1024,
1102            virtual_bytes: 256 * 1024 * 1024,
1103            heap_bytes: Some(32 * 1024 * 1024),
1104            stack_bytes: None,
1105            mappings_count: None,
1106            peak_bytes: None,
1107        };
1108
1109        assert_eq!(usage.rss_bytes, 64 * 1024 * 1024);
1110        assert!(usage.heap_bytes.is_some());
1111    }
1112}