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::RngExt;
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> {
624 Ok(MemoryUsage {
625 rss_bytes: self.get_rss_memory()?,
626 virtual_bytes: self.get_virtual_memory()?,
627 heap_bytes: self.get_heap_memory(),
628 stack_bytes: None,
629 mappings_count: None,
630 peak_bytes: None,
631 })
632 }
633
634 fn get_rss_memory(&self) -> Result<u64, CoreError> {
641 #[cfg(target_os = "linux")]
642 {
643 if let Ok(status) = std::fs::read_to_string("/proc/self/status") {
644 for line in status.lines() {
645 if line.starts_with("VmRSS:") {
646 if let Some(kb_str) = line.split_whitespace().nth(1) {
647 if let Ok(kb) = kb_str.parse::<u64>() {
648 return Ok(kb * 1024); }
650 }
651 }
652 }
653 }
654 }
655
656 #[cfg(feature = "sysinfo")]
657 {
658 use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, System};
659 let pid = Pid::from_u32(std::process::id());
660 let mut system = System::new();
661 system.refresh_processes_specifics(
662 ProcessesToUpdate::Some(&[pid]),
663 true,
664 ProcessRefreshKind::nothing().with_memory(),
665 );
666 if let Some(process) = system.process(pid) {
667 return Ok(process.memory());
668 }
669 }
670
671 Err(CoreError::ComputationError(crate::error::ErrorContext::new(
672 "Resident memory size is unavailable on this platform (enable the `sysinfo` feature for non-Linux support)".to_string(),
673 )))
674 }
675
676 fn get_virtual_memory(&self) -> Result<u64, CoreError> {
682 #[cfg(target_os = "linux")]
683 {
684 if let Ok(status) = std::fs::read_to_string("/proc/self/status") {
685 for line in status.lines() {
686 if line.starts_with("VmSize:") {
687 if let Some(kb_str) = line.split_whitespace().nth(1) {
688 if let Ok(kb) = kb_str.parse::<u64>() {
689 return Ok(kb * 1024); }
691 }
692 }
693 }
694 }
695 }
696
697 #[cfg(feature = "sysinfo")]
698 {
699 use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, System};
700 let pid = Pid::from_u32(std::process::id());
701 let mut system = System::new();
702 system.refresh_processes_specifics(
703 ProcessesToUpdate::Some(&[pid]),
704 true,
705 ProcessRefreshKind::nothing().with_memory(),
706 );
707 if let Some(process) = system.process(pid) {
708 return Ok(process.virtual_memory());
709 }
710 }
711
712 Err(CoreError::ComputationError(crate::error::ErrorContext::new(
713 "Virtual memory size is unavailable on this platform (enable the `sysinfo` feature for non-Linux support)".to_string(),
714 )))
715 }
716
717 fn get_heap_memory(&self) -> Option<u64> {
724 None
728 }
729
730 fn get_active_allocation_count(&self) -> Result<u64, CoreError> {
732 let allocations = self.allocations.read().map_err(|_| {
733 CoreError::ComputationError(crate::error::ErrorContext::new(
734 "Failed to acquire allocations lock".to_string(),
735 ))
736 })?;
737 Ok(allocations.len() as u64)
738 }
739
740 fn capture_call_stack(&self) -> Result<CallStack, CoreError> {
753 use std::backtrace::{Backtrace, BacktraceStatus};
754
755 let backtrace = Backtrace::capture();
756
757 if backtrace.status() != BacktraceStatus::Captured {
758 return Ok(CallStack {
760 frames: Vec::new(),
761 max_depth: 0,
762 truncated: false,
763 });
764 }
765
766 let rendered = backtrace.to_string();
767 let mut frames = Vec::new();
768 for line in rendered.lines() {
769 let trimmed = line.trim();
770 if trimmed.is_empty() {
771 continue;
772 }
773 frames.push(StackFrame {
774 function: Some(trimmed.to_string()),
775 file: None,
776 line: None,
777 address: 0,
780 module: None,
781 });
782 }
783
784 let max_depth = frames.len();
785 Ok(CallStack {
786 frames,
787 max_depth,
788 truncated: false,
789 })
790 }
791
792 fn get_thread_id(&self) -> u64 {
794 use std::thread;
796 format!("{:?}", thread::current().id())
797 .chars()
798 .filter_map(|c| c.to_digit(10))
799 .map(|d| d as u64)
800 .fold(0, |acc, d| acc * 10 + d)
801 }
802
803 fn analyze_allocation_patterns(
805 &self,
806 _checkpoint: &MemoryCheckpoint,
807 ) -> Result<Vec<MemoryLeak>, CoreError> {
808 let mut leaks = Vec::new();
809
810 let allocations = self.allocations.read().map_err(|_| {
811 CoreError::ComputationError(crate::error::ErrorContext::new(
812 "Failed to acquire allocations lock".to_string(),
813 ))
814 })?;
815
816 let now = chrono::Utc::now();
818 let old_threshold = now - Duration::from_secs(3600); for (_id, allocation) in allocations.iter() {
821 if allocation.timestamp < old_threshold && allocation.size > 1024 * 1024 {
822 leaks.push(MemoryLeak {
824 id: Uuid::new_v4(),
825 size_bytes: allocation.size,
826 allocation_stack: allocation.call_stack.clone(),
827 leak_type: LeakType::Possible,
828 confidence: 0.6,
829 first_detected: allocation.timestamp,
830 last_seen: now,
831 allocation_count: 1,
832 });
833 }
834 }
835
836 Ok(leaks)
837 }
838
839 fn run_external_profiler_checks(&self) -> Result<Vec<MemoryLeak>, CoreError> {
841 let mut leaks = Vec::new();
842
843 for integration in &self.profiler_integrations {
844 leaks.extend(integration.check_leaks()?);
845 }
846
847 Ok(leaks)
848 }
849
850 fn create_leak_summary(&self, leaks: &[MemoryLeak], memorygrowth: i64) -> LeakSummary {
852 let total_leaks = leaks.len();
853 let total_leaked_bytes = leaks.iter().map(|leak| leak.size_bytes).sum();
854
855 let mut leaks_by_type = HashMap::new();
856 for leak in leaks {
857 let type_name = format!("{0:?}", leak.leak_type);
858 *leaks_by_type.entry(type_name).or_insert(0) += 1;
859 }
860
861 let max_severity = leaks
862 .iter()
863 .map(|leak| leak.leak_type.severity())
864 .max()
865 .unwrap_or(0);
866
867 let average_confidence = if total_leaks > 0 {
868 leaks.iter().map(|leak| leak.confidence).sum::<f64>() / total_leaks as f64
869 } else {
870 0.0
871 };
872
873 let growth_rate = memorygrowth as f64 / 60.0; LeakSummary {
876 total_leaks,
877 total_leaked_bytes,
878 leaks_by_type,
879 max_severity,
880 average_confidence,
881 growth_rate,
882 }
883 }
884
885 fn generate_recommendations(&self, leaks: &[MemoryLeak], memorygrowth: i64) -> Vec<String> {
887 let mut recommendations = Vec::new();
888
889 if leaks.is_empty() && memorygrowth < 1024 * 1024 {
890 recommendations.push("No significant memory issues detected".to_string());
891 return recommendations;
892 }
893
894 if memorygrowth > self.config.growth_threshold_bytes as i64 {
895 recommendations.push(format!(
896 "Memory growth of {memorygrowth} bytes exceeds threshold, investigate allocation patterns"
897 ));
898 }
899
900 let definite_leaks: Vec<_> = leaks
901 .iter()
902 .filter(|leak| leak.leak_type == LeakType::Definite)
903 .collect();
904
905 if !definite_leaks.is_empty() {
906 recommendations.push(format!(
907 "{} definite leaks detected - fix these immediately",
908 definite_leaks.len()
909 ));
910 }
911
912 let large_leaks: Vec<_> = leaks.iter()
913 .filter(|leak| leak.size_bytes > 10 * 1024 * 1024) .collect();
915
916 if !large_leaks.is_empty() {
917 recommendations.push(format!(
918 "{} large leaks (>10MB) detected - prioritize fixing these",
919 large_leaks.len()
920 ));
921 }
922
923 if leaks.iter().any(|leak| leak.confidence > 0.8) {
924 recommendations.push("High confidence leaks detected - likely real issues".to_string());
925 }
926
927 recommendations.push(
928 "Consider running with valgrind or AddressSanitizer for detailed analysis".to_string(),
929 );
930
931 recommendations
932 }
933
934 pub fn get_reports(&self) -> Result<Vec<LeakReport>, CoreError> {
936 let reports = self.reports.lock().map_err(|_| {
937 CoreError::ComputationError(crate::error::ErrorContext::new(
938 "Failed to acquire reports lock".to_string(),
939 ))
940 })?;
941 Ok(reports.clone())
942 }
943
944 pub fn clear_old_reports(&self, maxage: Duration) -> Result<usize, CoreError> {
946 let mut reports = self.reports.lock().map_err(|_| {
947 CoreError::ComputationError(crate::error::ErrorContext::new(
948 "Failed to acquire reports lock".to_string(),
949 ))
950 })?;
951
952 let cutoff = chrono::Utc::now()
953 - chrono::Duration::from_std(maxage).map_err(|e| {
954 CoreError::ComputationError(crate::error::ErrorContext::new(format!(
955 "Invalid duration: {e}"
956 )))
957 })?;
958
959 let initial_len = reports.len();
960 reports.retain(|report| report.detection_time > cutoff);
961
962 Ok(initial_len - reports.len())
963 }
964}
965
966pub trait ProfilerIntegration {
968 fn check_leaks(&self) -> Result<Vec<MemoryLeak>, CoreError>;
970
971 fn name(&self) -> &str;
973
974 fn is_available(&self) -> bool;
976}
977
978pub struct ValgrindIntegration {
980 enabled: bool,
981}
982
983impl ValgrindIntegration {
984 pub fn new() -> Self {
985 Self {
986 enabled: Self::check_valgrind_available(),
987 }
988 }
989
990 fn check_valgrind_available() -> bool {
991 std::env::var("VALGRIND_OPTS").is_ok() || std::env::var("RUNNING_ON_VALGRIND").is_ok()
993 }
994}
995
996impl ProfilerIntegration for ValgrindIntegration {
997 fn check_leaks(&self) -> Result<Vec<MemoryLeak>, CoreError> {
998 if !self.enabled {
999 return Ok(Vec::new());
1001 }
1002
1003 Err(CoreError::ComputationError(
1009 crate::error::ErrorContext::new(
1010 "Valgrind leak parsing is not implemented; cannot report leak results".to_string(),
1011 ),
1012 ))
1013 }
1014
1015 fn name(&self) -> &str {
1016 "valgrind"
1017 }
1018
1019 fn is_available(&self) -> bool {
1020 self.enabled
1021 }
1022}
1023
1024impl Default for ValgrindIntegration {
1025 fn default() -> Self {
1026 Self::new()
1027 }
1028}
1029
1030pub struct LeakCheckGuard<'a> {
1032 detector: &'a LeakDetector,
1033 checkpoint: MemoryCheckpoint,
1034 check_on_drop: bool,
1035}
1036
1037impl<'a> LeakCheckGuard<'a> {
1038 pub fn new(detector: &'a LeakDetector, name: &str) -> Result<Self, CoreError> {
1040 let checkpoint = detector.create_checkpoint(name)?;
1041 Ok(Self {
1042 detector,
1043 checkpoint,
1044 check_on_drop: true,
1045 })
1046 }
1047
1048 pub fn disable_auto_check(mut self) -> Self {
1050 self.check_on_drop = false;
1051 self
1052 }
1053
1054 pub fn check_leaks(&self) -> Result<LeakReport, CoreError> {
1056 self.detector.check_leaks(&self.checkpoint)
1057 }
1058}
1059
1060impl Drop for LeakCheckGuard<'_> {
1061 fn drop(&mut self) {
1062 if self.check_on_drop {
1063 if let Ok(report) = self.detector.check_leaks(&self.checkpoint) {
1064 if report.has_leaks() {
1065 eprintln!(
1066 "Memory leaks detected in {}: {}",
1067 self.checkpoint.name,
1068 report.summary()
1069 );
1070 }
1071 }
1072 }
1073 }
1074}
1075
1076#[macro_export]
1078macro_rules! check_leaks {
1079 ($detector:expr, $name:expr, $block:block) => {{
1080 let guard = $crate::memory::leak_detection::LeakCheckGuard::new($detector, $name)?;
1081 $block
1082 }};
1083}
1084
1085static GLOBAL_DETECTOR: std::sync::OnceLock<Arc<Mutex<LeakDetector>>> = std::sync::OnceLock::new();
1087
1088#[allow(dead_code)]
1090pub fn global_leak_detector() -> Arc<Mutex<LeakDetector>> {
1091 GLOBAL_DETECTOR
1092 .get_or_init(|| {
1093 let config = LeakDetectionConfig::default();
1094 Arc::new(Mutex::new(
1095 LeakDetector::new(config).expect("Failed to create global leak detector"),
1096 ))
1097 })
1098 .clone()
1099}
1100
1101#[cfg(test)]
1102mod tests {
1103 use super::*;
1104
1105 #[test]
1106 fn test_leak_detector_creation() {
1107 let config = LeakDetectionConfig::default();
1108 let detector = LeakDetector::new(config).expect("Operation failed");
1109
1110 assert!(!*detector.monitoring_active.lock().expect("Operation failed"));
1111 }
1112
1113 #[cfg(any(target_os = "linux", feature = "sysinfo"))]
1115 #[test]
1116 fn test_checkpoint_creation() {
1117 let config = LeakDetectionConfig::default();
1118 let detector = LeakDetector::new(config).expect("Operation failed");
1119
1120 let checkpoint = detector
1121 .create_checkpoint("test")
1122 .expect("Operation failed");
1123 assert_eq!(checkpoint.name, "test");
1124 assert!(checkpoint.memory_usage.rss_bytes > 0);
1125 }
1126
1127 #[test]
1128 fn test_allocation_tracking() {
1129 let config = LeakDetectionConfig::default().development_mode();
1130 let detector = LeakDetector::new(config).expect("Operation failed");
1131
1132 detector
1133 .track_allocation(1024, 0x12345678)
1134 .expect("Operation failed");
1135 detector
1136 .track_allocation(2048, 0x87654321)
1137 .expect("Operation failed");
1138
1139 let count = detector
1140 .get_active_allocation_count()
1141 .expect("Operation failed");
1142 assert_eq!(count, 2);
1143
1144 detector
1145 .track_deallocation(0x12345678)
1146 .expect("Operation failed");
1147 let count = detector
1148 .get_active_allocation_count()
1149 .expect("Operation failed");
1150 assert_eq!(count, 1);
1151 }
1152
1153 #[cfg(any(target_os = "linux", feature = "sysinfo"))]
1155 #[test]
1156 fn test_leak_check_guard() {
1157 let config = LeakDetectionConfig::default();
1158 let detector = LeakDetector::new(config).expect("Operation failed");
1159
1160 {
1161 let guard = LeakCheckGuard::new(&detector, "test_guard").expect("Operation failed");
1162 detector
1164 .track_allocation(1024 * 1024, 0x12345678)
1165 .expect("Operation failed");
1166 } }
1168
1169 #[test]
1170 fn test_leak_types() {
1171 assert_eq!(LeakType::Definite.severity(), 10);
1172 assert_eq!(LeakType::Possible.severity(), 5);
1173 assert!(LeakType::Definite.severity() > LeakType::Possible.severity());
1174 }
1175
1176 #[test]
1177 fn test_config_builder() {
1178 let config = LeakDetectionConfig::default()
1179 .with_threshold_mb(50)
1180 .with_samplingrate(0.5)
1181 .with_call_stacks(true);
1182
1183 assert_eq!(config.growth_threshold_bytes, 50 * 1024 * 1024);
1184 assert_eq!(config.samplingrate, 0.5);
1185 assert!(config.collect_call_stacks);
1186 }
1187
1188 #[test]
1189 fn test_profiler_tools() {
1190 assert_eq!(ProfilerTool::Valgrind.name(), "valgrind");
1191 assert_eq!(ProfilerTool::AddressSanitizer.name(), "asan");
1192 assert_eq!(ProfilerTool::Custom("custom".to_string()).name(), "custom");
1193 }
1194
1195 #[test]
1196 fn test_memory_usage() {
1197 let usage = MemoryUsage {
1198 rss_bytes: 64 * 1024 * 1024,
1199 virtual_bytes: 256 * 1024 * 1024,
1200 heap_bytes: Some(32 * 1024 * 1024),
1201 stack_bytes: None,
1202 mappings_count: None,
1203 peak_bytes: None,
1204 };
1205
1206 assert_eq!(usage.rss_bytes, 64 * 1024 * 1024);
1207 assert!(usage.heap_bytes.is_some());
1208 }
1209}