1use 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#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
59#[derive(Debug, Clone)]
60pub struct LeakDetectionConfig {
61 pub enabled: bool,
63 pub growth_threshold_bytes: u64,
65 pub detection_window: Duration,
67 pub samplingrate: f64,
69 pub collect_call_stacks: bool,
71 pub max_tracked_allocations: usize,
73 pub enable_external_profilers: bool,
75 pub profiler_tools: Vec<ProfilerTool>,
77 pub enable_periodic_checks: bool,
79 pub check_interval: Duration,
81 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, detection_window: Duration::from_secs(300), samplingrate: 0.01, collect_call_stacks: false, 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), production_mode: true,
99 }
100 }
101}
102
103impl LeakDetectionConfig {
104 pub fn with_threshold_mb(mut self, mb: u64) -> Self {
106 self.growth_threshold_bytes = mb * 1024 * 1024;
107 self
108 }
109
110 pub fn with_samplingrate(mut self, rate: f64) -> Self {
112 self.samplingrate = rate.clamp(0.0, 1.0);
113 self
114 }
115
116 pub fn with_call_stacks(mut self, enable: bool) -> Self {
118 self.collect_call_stacks = enable;
119 self
120 }
121
122 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134pub enum ProfilerTool {
135 Valgrind,
137 AddressSanitizer,
139 Heaptrack,
141 Massif,
143 Jemalloc,
145 Custom(String),
147}
148
149impl ProfilerTool {
150 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#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
165#[derive(Debug, Clone)]
166pub struct MemoryCheckpoint {
167 pub id: Uuid,
169 pub name: String,
171 pub timestamp: chrono::DateTime<chrono::Utc>,
173 pub memory_usage: MemoryUsage,
175 pub active_allocations: u64,
177 pub call_stack: Option<CallStack>,
179 pub thread_id: u64,
181 pub process_id: u32,
183}
184
185#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
187#[derive(Debug, Clone)]
188pub struct MemoryUsage {
189 pub rss_bytes: u64,
191 pub virtual_bytes: u64,
193 pub heap_bytes: Option<u64>,
195 pub stack_bytes: Option<u64>,
197 pub mappings_count: Option<u64>,
199 pub peak_bytes: Option<u64>,
201}
202
203#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
205#[derive(Debug, Clone)]
206pub struct CallStack {
207 pub frames: Vec<StackFrame>,
209 pub max_depth: usize,
211 pub truncated: bool,
213}
214
215#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
217#[derive(Debug, Clone)]
218pub struct StackFrame {
219 pub function: Option<String>,
221 pub file: Option<String>,
223 pub line: Option<u32>,
225 pub address: u64,
227 pub module: Option<String>,
229}
230
231#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
233#[derive(Debug, Clone)]
234pub struct LeakReport {
235 pub checkpoint: MemoryCheckpoint,
237 pub current_usage: MemoryUsage,
239 pub memory_growth: i64,
241 pub leaks: Vec<MemoryLeak>,
243 pub summary: LeakSummary,
245 pub recommendations: Vec<String>,
247 pub detection_time: chrono::DateTime<chrono::Utc>,
249}
250
251impl LeakReport {
252 pub fn has_leaks(&self) -> bool {
254 !self.leaks.is_empty()
255 }
256
257 pub fn total_leaked_bytes(&self) -> u64 {
259 self.leaks.iter().map(|leak| leak.size_bytes).sum()
260 }
261
262 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#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
278#[derive(Debug, Clone)]
279pub struct MemoryLeak {
280 pub id: Uuid,
282 pub size_bytes: u64,
284 pub allocation_stack: Option<CallStack>,
286 pub leak_type: LeakType,
288 pub confidence: f64,
290 pub first_detected: chrono::DateTime<chrono::Utc>,
292 pub last_seen: chrono::DateTime<chrono::Utc>,
294 pub allocation_count: u64,
296}
297
298#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
300#[derive(Debug, Clone, Copy, PartialEq, Eq)]
301pub enum LeakType {
302 Definite,
304 Indirect,
306 Possible,
308 Reachable,
310 GrowthPattern,
312}
313
314impl LeakType {
315 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#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
329#[derive(Debug, Clone)]
330pub struct LeakSummary {
331 pub total_leaks: usize,
333 pub total_leaked_bytes: u64,
335 pub leaks_by_type: HashMap<String, usize>,
337 pub max_severity: u8,
339 pub average_confidence: f64,
341 pub growth_rate: f64,
343}
344
345#[derive(Debug, Clone)]
347struct AllocationInfo {
348 size: u64,
350 timestamp: chrono::DateTime<chrono::Utc>,
352 call_stack: Option<CallStack>,
354 #[allow(dead_code)]
356 thread_id: u64,
357 #[allow(dead_code)]
359 id: u64,
360}
361
362pub struct LeakDetector {
364 config: LeakDetectionConfig,
366 allocations: Arc<RwLock<HashMap<u64, AllocationInfo>>>,
368 address_to_id: Arc<RwLock<HashMap<u64, u64>>>,
370 checkpoints: Arc<Mutex<HashMap<Uuid, MemoryCheckpoint>>>,
372 reports: Arc<Mutex<Vec<LeakReport>>>,
374 monitoring_active: Arc<Mutex<bool>>,
376 allocation_counter: Arc<Mutex<u64>>,
378 profiler_integrations: Vec<Box<dyn ProfilerIntegration + Send + Sync>>,
380}
381
382impl LeakDetector {
383 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 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(()); }
414
415 *monitoring = true;
416
417 Ok(())
420 }
421
422 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 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 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 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 leaks.extend(self.analyze_allocation_patterns(checkpoint)?);
488
489 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 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 pub fn track_allocation(&self, size: u64, address: u64) -> Result<(), CoreError> {
518 if !self.config.enabled {
519 return Ok(());
520 }
521
522 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 if allocations.len() >= self.config.max_tracked_allocations {
561 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 }
574 }
575
576 allocations.insert(allocation_id, allocation_info);
577
578 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 pub fn track_deallocation(&self, address: u64) -> Result<(), CoreError> {
591 if !self.config.enabled {
592 return Ok(());
593 }
594
595 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 fn get_current_memory_usage(&self) -> Result<MemoryUsage, CoreError> {
619 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 fn get_rss_memory(&self) -> Result<u64, CoreError> {
633 #[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); }
643 }
644 }
645 }
646 }
647 }
648
649 Ok(64 * 1024 * 1024) }
652
653 fn get_virtual_memory(&self) -> Result<u64, CoreError> {
655 #[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); }
665 }
666 }
667 }
668 }
669 }
670
671 Ok(256 * 1024 * 1024) }
674
675 fn get_heap_memory(&self) -> Result<u64, CoreError> {
677 Ok(32 * 1024 * 1024) }
680
681 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 fn capture_call_stack(&self) -> Result<CallStack, CoreError> {
693 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 fn get_thread_id(&self) -> u64 {
709 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 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 let now = chrono::Utc::now();
733 let old_threshold = now - Duration::from_secs(3600); for (_id, allocation) in allocations.iter() {
736 if allocation.timestamp < old_threshold && allocation.size > 1024 * 1024 {
737 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 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 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; 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 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) .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 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 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
881pub trait ProfilerIntegration {
883 fn check_leaks(&self) -> Result<Vec<MemoryLeak>, CoreError>;
885
886 fn name(&self) -> &str;
888
889 fn is_available(&self) -> bool;
891}
892
893pub 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 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 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
937pub struct LeakCheckGuard<'a> {
939 detector: &'a LeakDetector,
940 checkpoint: MemoryCheckpoint,
941 check_on_drop: bool,
942}
943
944impl<'a> LeakCheckGuard<'a> {
945 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 pub fn disable_auto_check(mut self) -> Self {
957 self.check_on_drop = false;
958 self
959 }
960
961 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#[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
992static GLOBAL_DETECTOR: std::sync::OnceLock<Arc<Mutex<LeakDetector>>> = std::sync::OnceLock::new();
994
995#[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 detector
1067 .track_allocation(1024 * 1024, 0x12345678)
1068 .expect("Operation failed");
1069 } }
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}