1use crate::error::{Result, TorshError};
8use std::alloc::{GlobalAlloc, Layout, System};
9use std::backtrace::Backtrace;
10use std::collections::{HashMap, VecDeque};
11use std::fmt;
12use std::sync::{Arc, Mutex};
13use std::time::{Duration, Instant};
14
15static MEMORY_DEBUGGER: std::sync::OnceLock<Arc<Mutex<MemoryDebugger>>> =
17 std::sync::OnceLock::new();
18
19#[derive(Debug)]
21pub struct AllocationInfo {
22 pub id: u64,
24 pub size: usize,
26 pub layout: Layout,
28 pub timestamp: Instant,
30 pub backtrace: Option<String>,
32 pub tag: Option<String>,
34 pub is_active: bool,
36 pub thread_id: std::thread::ThreadId,
38}
39
40impl Clone for AllocationInfo {
41 fn clone(&self) -> Self {
42 Self {
43 id: self.id,
44 size: self.size,
45 layout: self.layout,
46 timestamp: self.timestamp,
47 backtrace: self.backtrace.clone(),
48 tag: self.tag.clone(),
49 is_active: self.is_active,
50 thread_id: self.thread_id,
51 }
52 }
53}
54
55#[derive(Debug, Clone, Default)]
57pub struct MemoryStats {
58 pub total_allocated: usize,
60 pub peak_allocated: usize,
62 pub total_allocations: u64,
64 pub total_deallocations: u64,
66 pub active_allocations: u64,
68 pub average_allocation_size: f64,
70 pub lifetime_allocated: usize,
72 pub lifetime_deallocated: usize,
74}
75
76#[derive(Debug, Clone)]
78pub struct MemoryLeak {
79 pub allocation: AllocationInfo,
81 pub age: Duration,
83 pub leak_probability: f64,
85 pub confidence: f64,
87 pub risk_level: LeakRiskLevel,
89 pub suggested_actions: Vec<String>,
91}
92
93#[derive(Debug, Clone, PartialEq)]
95pub enum LeakRiskLevel {
96 Low,
98 Medium,
100 High,
102 Critical,
104}
105
106#[derive(Debug, Clone)]
108pub struct AllocationPattern {
109 pub size_class: String,
111 pub frequency: u64,
113 pub average_lifetime: Duration,
115 pub common_stacks: Vec<String>,
117}
118
119#[derive(Debug, Clone)]
121pub struct MemoryDebugConfig {
122 pub capture_backtraces: bool,
124 pub max_tracked_allocations: usize,
126 pub track_patterns: bool,
128 pub min_tracked_size: usize,
130 pub enable_leak_detection: bool,
132 pub leak_detection_frequency: u64,
134 pub leak_threshold: Duration,
136 pub enable_real_time_monitoring: bool,
138 pub monitoring_interval: Duration,
140 pub enable_auto_mitigation: bool,
142 pub memory_warning_threshold: usize,
144 pub memory_critical_threshold: usize,
146}
147
148#[derive(Debug, Clone)]
150pub struct RealTimeStats {
151 pub current_usage: usize,
153 pub usage_trend: f64,
155 pub recent_allocations: u64,
157 pub recent_deallocations: u64,
159 pub leak_detection_rate: f64,
161 pub system_pressure: SystemPressureLevel,
163}
164
165#[derive(Debug, Clone, PartialEq)]
167pub enum SystemPressureLevel {
168 Normal,
170 Low,
172 Medium,
174 High,
176 Critical,
178}
179
180#[derive(Debug, Clone)]
182pub struct LeakStats {
183 pub total_leaks: usize,
184 pub critical_leaks: usize,
185 pub high_leaks: usize,
186 pub medium_leaks: usize,
187 pub low_leaks: usize,
188 pub total_leaked_bytes: usize,
189 pub average_leak_age: u64,
190}
191
192impl Default for MemoryDebugConfig {
193 fn default() -> Self {
194 Self {
195 capture_backtraces: true,
196 max_tracked_allocations: 10000,
197 track_patterns: true,
198 min_tracked_size: 1024, enable_leak_detection: true,
200 leak_detection_frequency: 1000,
201 leak_threshold: Duration::from_secs(300), enable_real_time_monitoring: true,
203 monitoring_interval: Duration::from_secs(10),
204 enable_auto_mitigation: false,
205 memory_warning_threshold: 1024 * 1024 * 1024, memory_critical_threshold: 2 * 1024 * 1024 * 1024, }
208 }
209}
210
211#[derive(Debug)]
213pub struct MemoryDebugger {
214 config: MemoryDebugConfig,
216 allocations: HashMap<u64, AllocationInfo>,
218 stats: MemoryStats,
220 next_id: u64,
222 allocation_history: VecDeque<AllocationInfo>,
224 detected_leaks: Vec<MemoryLeak>,
226 last_leak_check: Instant,
228 realtime_stats: RealTimeStats,
230 last_monitoring: Instant,
232 previous_usage: usize,
234 allocations_since_monitoring: u64,
236 deallocations_since_monitoring: u64,
238}
239
240impl MemoryDebugger {
241 pub fn new() -> Self {
243 Self::with_config(MemoryDebugConfig::default())
244 }
245
246 pub fn with_config(config: MemoryDebugConfig) -> Self {
248 let now = Instant::now();
249 Self {
250 config,
251 allocations: HashMap::new(),
252 stats: MemoryStats::default(),
253 next_id: 1,
254 allocation_history: VecDeque::new(),
255 detected_leaks: Vec::new(),
256 last_leak_check: now,
257 realtime_stats: RealTimeStats {
258 current_usage: 0,
259 usage_trend: 0.0,
260 recent_allocations: 0,
261 recent_deallocations: 0,
262 leak_detection_rate: 0.0,
263 system_pressure: SystemPressureLevel::Normal,
264 },
265 last_monitoring: now,
266 previous_usage: 0,
267 allocations_since_monitoring: 0,
268 deallocations_since_monitoring: 0,
269 }
270 }
271
272 pub fn record_allocation(&mut self, size: usize, layout: Layout, tag: Option<String>) -> u64 {
274 if size < self.config.min_tracked_size {
275 return 0; }
277
278 let id = self.next_id;
279 self.next_id += 1;
280
281 let allocation = AllocationInfo {
282 id,
283 size,
284 layout,
285 timestamp: Instant::now(),
286 backtrace: if self.config.capture_backtraces {
287 Some(format!("{}", Backtrace::capture()))
288 } else {
289 None
290 },
291 tag,
292 is_active: true,
293 thread_id: std::thread::current().id(),
294 };
295
296 self.stats.total_allocated += size;
298 self.stats.peak_allocated = self.stats.peak_allocated.max(self.stats.total_allocated);
299 self.stats.total_allocations += 1;
300 self.stats.active_allocations += 1;
301 self.stats.lifetime_allocated += size;
302 self.stats.average_allocation_size =
303 self.stats.lifetime_allocated as f64 / self.stats.total_allocations as f64;
304
305 self.allocations_since_monitoring += 1;
307 self.realtime_stats.current_usage = self.stats.total_allocated;
308 self.update_realtime_monitoring();
309
310 self.allocations.insert(id, allocation.clone());
312
313 if self.config.track_patterns {
315 self.allocation_history.push_back(allocation);
316
317 while self.allocation_history.len() > self.config.max_tracked_allocations {
319 self.allocation_history.pop_front();
320 }
321 }
322
323 if self.config.enable_leak_detection
325 && self
326 .stats
327 .total_allocations
328 .is_multiple_of(self.config.leak_detection_frequency)
329 {
330 self.detect_leaks();
331 }
332
333 id
334 }
335
336 pub fn record_deallocation(&mut self, id: u64) {
338 if let Some(mut allocation) = self.allocations.remove(&id) {
339 allocation.is_active = false;
340
341 self.stats.total_allocated = self.stats.total_allocated.saturating_sub(allocation.size);
343 self.stats.total_deallocations += 1;
344 self.stats.active_allocations = self.stats.active_allocations.saturating_sub(1);
345 self.stats.lifetime_deallocated += allocation.size;
346
347 self.deallocations_since_monitoring += 1;
349 self.realtime_stats.current_usage = self.stats.total_allocated;
350 self.update_realtime_monitoring();
351 }
352 }
353
354 fn update_realtime_monitoring(&mut self) {
356 if !self.config.enable_real_time_monitoring {
357 return;
358 }
359
360 let now = Instant::now();
361 let time_elapsed = now.duration_since(self.last_monitoring);
362
363 if time_elapsed >= self.config.monitoring_interval {
364 let time_seconds = time_elapsed.as_secs_f64();
365 let usage_change = self.stats.total_allocated as i64 - self.previous_usage as i64;
366
367 self.realtime_stats.usage_trend = usage_change as f64 / time_seconds;
369
370 self.realtime_stats.recent_allocations = self.allocations_since_monitoring;
372 self.realtime_stats.recent_deallocations = self.deallocations_since_monitoring;
373
374 let total_recent_ops =
376 self.allocations_since_monitoring + self.deallocations_since_monitoring;
377 self.realtime_stats.leak_detection_rate = if total_recent_ops > 0 {
378 self.detected_leaks.len() as f64 / total_recent_ops as f64
379 } else {
380 0.0
381 };
382
383 self.realtime_stats.system_pressure = self.calculate_system_pressure();
385
386 self.previous_usage = self.stats.total_allocated;
388 self.allocations_since_monitoring = 0;
389 self.deallocations_since_monitoring = 0;
390 self.last_monitoring = now;
391 }
392 }
393
394 fn calculate_system_pressure(&self) -> SystemPressureLevel {
396 let current_usage = self.stats.total_allocated;
397 let warning_threshold = self.config.memory_warning_threshold;
398 let critical_threshold = self.config.memory_critical_threshold;
399
400 if current_usage >= critical_threshold {
401 SystemPressureLevel::Critical
402 } else if current_usage >= warning_threshold {
403 SystemPressureLevel::High
404 } else if current_usage >= warning_threshold * 3 / 4 {
405 SystemPressureLevel::Medium
406 } else if current_usage >= warning_threshold / 2 {
407 SystemPressureLevel::Low
408 } else {
409 SystemPressureLevel::Normal
410 }
411 }
412
413 pub fn stats(&self) -> MemoryStats {
415 self.stats.clone()
416 }
417
418 pub fn realtime_stats(&self) -> RealTimeStats {
420 self.realtime_stats.clone()
421 }
422
423 pub fn is_under_pressure(&self) -> bool {
425 matches!(
426 self.realtime_stats.system_pressure,
427 SystemPressureLevel::High | SystemPressureLevel::Critical
428 )
429 }
430
431 pub fn get_pressure_level(&self) -> SystemPressureLevel {
433 self.realtime_stats.system_pressure.clone()
434 }
435
436 pub fn force_leak_detection(&mut self) -> Vec<MemoryLeak> {
438 self.detect_leaks()
439 }
440
441 pub fn leak_stats(&self) -> LeakStats {
443 let total_leaks = self.detected_leaks.len();
444 let critical_leaks = self
445 .detected_leaks
446 .iter()
447 .filter(|l| l.risk_level == LeakRiskLevel::Critical)
448 .count();
449 let high_leaks = self
450 .detected_leaks
451 .iter()
452 .filter(|l| l.risk_level == LeakRiskLevel::High)
453 .count();
454 let medium_leaks = self
455 .detected_leaks
456 .iter()
457 .filter(|l| l.risk_level == LeakRiskLevel::Medium)
458 .count();
459 let low_leaks = self
460 .detected_leaks
461 .iter()
462 .filter(|l| l.risk_level == LeakRiskLevel::Low)
463 .count();
464
465 LeakStats {
466 total_leaks,
467 critical_leaks,
468 high_leaks,
469 medium_leaks,
470 low_leaks,
471 total_leaked_bytes: self.detected_leaks.iter().map(|l| l.allocation.size).sum(),
472 average_leak_age: if total_leaks > 0 {
473 self.detected_leaks
474 .iter()
475 .map(|l| l.age.as_secs())
476 .sum::<u64>()
477 / total_leaks as u64
478 } else {
479 0
480 },
481 }
482 }
483
484 pub fn detect_leaks(&mut self) -> Vec<MemoryLeak> {
486 let now = Instant::now();
487 let threshold = self.config.leak_threshold;
488 let mut new_leaks = Vec::new();
489
490 for allocation in self.allocations.values() {
491 if !allocation.is_active {
492 continue;
493 }
494
495 let age = now.duration_since(allocation.timestamp);
496 if age > threshold {
497 let age_factor = age.as_secs_f64() / threshold.as_secs_f64();
499 let size_factor = (allocation.size as f64).log2() / 20.0; let leak_probability = (age_factor * 0.7 + size_factor * 0.3).min(1.0);
501
502 if leak_probability > 0.5 {
503 let confidence = (age_factor * 0.8 + size_factor * 0.2).min(1.0);
505
506 let risk_level = if allocation.size > 1024 * 1024 && age.as_secs() > 3600 {
508 LeakRiskLevel::Critical
509 } else if allocation.size > 64 * 1024 || age.as_secs() > 1800 {
510 LeakRiskLevel::High
511 } else if allocation.size > 4 * 1024 || age.as_secs() > 600 {
512 LeakRiskLevel::Medium
513 } else {
514 LeakRiskLevel::Low
515 };
516
517 let mut suggested_actions = Vec::new();
519 if age.as_secs() > 1800 {
520 suggested_actions
521 .push("Consider reviewing allocation lifetime".to_string());
522 }
523 if allocation.size > 64 * 1024 {
524 suggested_actions.push("Review large allocation usage".to_string());
525 }
526 if allocation.backtrace.is_some() {
527 suggested_actions.push("Check allocation backtrace for source".to_string());
528 }
529 if suggested_actions.is_empty() {
530 suggested_actions
531 .push("Monitor allocation for continued growth".to_string());
532 }
533
534 new_leaks.push(MemoryLeak {
535 allocation: allocation.clone(),
536 age,
537 leak_probability,
538 confidence,
539 risk_level,
540 suggested_actions,
541 });
542 }
543 }
544 }
545
546 self.detected_leaks.extend(new_leaks.clone());
547 self.last_leak_check = now;
548 new_leaks
549 }
550
551 pub fn analyze_patterns(&self) -> Vec<AllocationPattern> {
553 if !self.config.track_patterns {
554 return Vec::new();
555 }
556
557 let mut size_patterns: HashMap<String, (u64, Duration, Vec<String>)> = HashMap::new();
558
559 for allocation in &self.allocation_history {
560 let size_class = Self::classify_size(allocation.size);
561 let lifetime = if allocation.is_active {
562 Instant::now().duration_since(allocation.timestamp)
563 } else {
564 Duration::from_secs(0) };
566
567 let stack_trace = allocation
568 .backtrace
569 .as_ref()
570 .cloned()
571 .unwrap_or_else(|| "No backtrace".to_string());
572
573 let entry = size_patterns
574 .entry(size_class)
575 .or_insert((0, Duration::ZERO, Vec::new()));
576 entry.0 += 1; entry.1 += lifetime; if !entry.2.contains(&stack_trace) && entry.2.len() < 5 {
579 entry.2.push(stack_trace); }
581 }
582
583 size_patterns
584 .into_iter()
585 .map(
586 |(size_class, (frequency, total_lifetime, stacks))| AllocationPattern {
587 size_class,
588 frequency,
589 average_lifetime: total_lifetime / frequency.max(1) as u32,
590 common_stacks: stacks,
591 },
592 )
593 .collect()
594 }
595
596 fn classify_size(size: usize) -> String {
598 match size {
599 0..=1024 => "small".to_string(),
600 1025..=65536 => "medium".to_string(),
601 65537..=1048576 => "large".to_string(),
602 _ => "huge".to_string(),
603 }
604 }
605
606 pub fn get_leaks(&self) -> &[MemoryLeak] {
608 &self.detected_leaks
609 }
610
611 pub fn clear(&mut self) {
613 self.allocations.clear();
614 self.allocation_history.clear();
615 self.detected_leaks.clear();
616 self.stats = MemoryStats::default();
617 self.next_id = 1;
618 }
619
620 pub fn generate_report(&self) -> MemoryReport {
622 MemoryReport {
623 stats: self.stats.clone(),
624 leaks: self.detected_leaks.clone(),
625 patterns: self.analyze_patterns(),
626 config: self.config.clone(),
627 timestamp: Instant::now(),
628 }
629 }
630}
631
632impl Default for MemoryDebugger {
633 fn default() -> Self {
634 Self::new()
635 }
636}
637
638#[derive(Debug, Clone)]
640pub struct MemoryReport {
641 pub stats: MemoryStats,
643 pub leaks: Vec<MemoryLeak>,
645 pub patterns: Vec<AllocationPattern>,
647 pub config: MemoryDebugConfig,
649 pub timestamp: Instant,
651}
652
653impl fmt::Display for MemoryReport {
654 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
655 writeln!(f, "=== Memory Debug Report ===")?;
656 writeln!(f, "Generated at: {:?}", self.timestamp)?;
657 writeln!(f)?;
658
659 writeln!(f, "Memory Statistics:")?;
660 writeln!(f, " Total allocated: {} bytes", self.stats.total_allocated)?;
661 writeln!(f, " Peak allocated: {} bytes", self.stats.peak_allocated)?;
662 writeln!(f, " Active allocations: {}", self.stats.active_allocations)?;
663 writeln!(f, " Total allocations: {}", self.stats.total_allocations)?;
664 writeln!(
665 f,
666 " Total deallocations: {}",
667 self.stats.total_deallocations
668 )?;
669 writeln!(
670 f,
671 " Average allocation size: {:.2} bytes",
672 self.stats.average_allocation_size
673 )?;
674 writeln!(f)?;
675
676 if !self.leaks.is_empty() {
677 writeln!(f, "Memory Leaks Detected ({}):", self.leaks.len())?;
678 for (i, leak) in self.leaks.iter().enumerate() {
679 writeln!(
680 f,
681 " {}. ID: {}, Size: {} bytes, Age: {:?}, Probability: {:.2}",
682 i + 1,
683 leak.allocation.id,
684 leak.allocation.size,
685 leak.age,
686 leak.leak_probability
687 )?;
688 }
689 writeln!(f)?;
690 }
691
692 if !self.patterns.is_empty() {
693 writeln!(f, "Allocation Patterns:")?;
694 for pattern in &self.patterns {
695 writeln!(
696 f,
697 " {}: {} allocations, avg lifetime: {:?}",
698 pattern.size_class, pattern.frequency, pattern.average_lifetime
699 )?;
700 }
701 }
702
703 Ok(())
704 }
705}
706
707pub fn init_memory_debugger() -> Result<()> {
709 let debugger = Arc::new(Mutex::new(MemoryDebugger::new()));
710 MEMORY_DEBUGGER
711 .set(debugger)
712 .map_err(|_| TorshError::ConfigError("Memory debugger already initialized".to_string()))?;
713 Ok(())
714}
715
716pub fn init_memory_debugger_with_config(config: MemoryDebugConfig) -> Result<()> {
717 let debugger = Arc::new(Mutex::new(MemoryDebugger::with_config(config)));
718 MEMORY_DEBUGGER
719 .set(debugger)
720 .map_err(|_| TorshError::ConfigError("Memory debugger already initialized".to_string()))?;
721 Ok(())
722}
723
724pub fn record_allocation(size: usize, layout: Layout, tag: Option<String>) -> u64 {
725 if let Some(debugger) = MEMORY_DEBUGGER.get() {
726 if let Ok(mut debugger) = debugger.lock() {
727 return debugger.record_allocation(size, layout, tag);
728 }
729 }
730 0
731}
732
733pub fn record_deallocation(id: u64) {
734 if let Some(debugger) = MEMORY_DEBUGGER.get() {
735 if let Ok(mut debugger) = debugger.lock() {
736 debugger.record_deallocation(id);
737 }
738 }
739}
740
741pub fn get_memory_stats() -> Option<MemoryStats> {
742 MEMORY_DEBUGGER.get()?.lock().ok().map(|d| d.stats())
743}
744
745pub fn detect_memory_leaks() -> Option<Vec<MemoryLeak>> {
746 MEMORY_DEBUGGER
747 .get()?
748 .lock()
749 .ok()
750 .map(|mut d| d.detect_leaks())
751}
752
753pub fn generate_memory_report() -> Option<MemoryReport> {
754 MEMORY_DEBUGGER
755 .get()?
756 .lock()
757 .ok()
758 .map(|d| d.generate_report())
759}
760
761pub fn get_realtime_stats() -> Option<RealTimeStats> {
762 MEMORY_DEBUGGER
763 .get()?
764 .lock()
765 .ok()
766 .map(|d| d.realtime_stats())
767}
768
769pub fn is_under_memory_pressure() -> bool {
770 MEMORY_DEBUGGER
771 .get()
772 .and_then(|d| d.lock().ok())
773 .map(|d| d.is_under_pressure())
774 .unwrap_or(false)
775}
776
777pub fn get_pressure_level() -> SystemPressureLevel {
778 MEMORY_DEBUGGER
779 .get()
780 .and_then(|d| d.lock().ok())
781 .map(|d| d.get_pressure_level())
782 .unwrap_or(SystemPressureLevel::Normal)
783}
784
785pub fn force_leak_detection() -> Option<Vec<MemoryLeak>> {
786 MEMORY_DEBUGGER
787 .get()?
788 .lock()
789 .ok()
790 .map(|mut d| d.force_leak_detection())
791}
792
793pub fn get_leak_stats() -> Option<LeakStats> {
794 MEMORY_DEBUGGER.get()?.lock().ok().map(|d| d.leak_stats())
795}
796
797pub struct DebuggingAllocator<A: GlobalAlloc> {
799 inner: A,
800}
801
802impl<A: GlobalAlloc> DebuggingAllocator<A> {
803 pub const fn new(inner: A) -> Self {
804 Self { inner }
805 }
806}
807
808unsafe impl<A: GlobalAlloc> GlobalAlloc for DebuggingAllocator<A> {
809 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
810 let ptr = self.inner.alloc(layout);
811 if !ptr.is_null() {
812 record_allocation(layout.size(), layout, None);
813 }
814 ptr
815 }
816
817 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
818 record_deallocation(0); self.inner.dealloc(ptr, layout);
822 }
823}
824
825pub type SystemDebuggingAllocator = DebuggingAllocator<System>;
827
828#[cfg(test)]
829mod tests {
830 use super::*;
831
832 #[test]
833 fn test_memory_debugger_basic() {
834 let mut debugger = MemoryDebugger::new();
835
836 let layout = Layout::from_size_align(1024, 8).expect("layout should be valid");
837 let id = debugger.record_allocation(1024, layout, Some("test".to_string()));
838
839 assert_eq!(debugger.stats().total_allocated, 1024);
840 assert_eq!(debugger.stats().active_allocations, 1);
841
842 debugger.record_deallocation(id);
843
844 assert_eq!(debugger.stats().total_allocated, 0);
845 assert_eq!(debugger.stats().active_allocations, 0);
846 }
847
848 #[test]
849 fn test_leak_detection() {
850 let config = MemoryDebugConfig {
851 leak_threshold: Duration::from_millis(1),
852 ..Default::default()
853 };
854
855 let mut debugger = MemoryDebugger::with_config(config);
856 let layout = Layout::from_size_align(1024, 8).expect("layout should be valid");
857
858 let _id = debugger.record_allocation(1024, layout, Some("potential_leak".to_string()));
859
860 std::thread::sleep(Duration::from_millis(2));
862
863 let leaks = debugger.detect_leaks();
864 assert!(!leaks.is_empty());
865 }
866
867 #[test]
868 fn test_pattern_analysis() {
869 let mut debugger = MemoryDebugger::new();
870 let layout_small = Layout::from_size_align(512, 8).expect("layout should be valid");
871 let layout_large = Layout::from_size_align(2048, 8).expect("layout should be valid");
872
873 for _ in 0..5 {
875 debugger.record_allocation(512, layout_small, Some("small".to_string()));
876 }
877
878 for _ in 0..3 {
880 debugger.record_allocation(2048, layout_large, Some("large".to_string()));
881 }
882
883 let patterns = debugger.analyze_patterns();
884 assert!(!patterns.is_empty());
885
886 let has_small = patterns.iter().any(|p| p.size_class == "small");
888 let has_medium = patterns.iter().any(|p| p.size_class == "medium");
889
890 assert!(has_small || has_medium); }
892
893 #[test]
894 fn test_enhanced_leak_detection() {
895 let config = MemoryDebugConfig {
896 leak_threshold: Duration::from_millis(1),
897 ..Default::default()
898 };
899
900 let mut debugger = MemoryDebugger::with_config(config);
901 let layout = Layout::from_size_align(1024 * 1024, 8).expect("layout should be valid"); let _id =
904 debugger.record_allocation(1024 * 1024, layout, Some("large_allocation".to_string()));
905
906 std::thread::sleep(Duration::from_millis(10));
908
909 let leaks = debugger.detect_leaks();
910 assert!(!leaks.is_empty());
911
912 let leak = &leaks[0];
913 assert!(leak.confidence > 0.0);
914 assert!(!leak.suggested_actions.is_empty());
915
916 assert!(matches!(
918 leak.risk_level,
919 LeakRiskLevel::High | LeakRiskLevel::Critical
920 ));
921 }
922
923 #[test]
924 fn test_realtime_monitoring() {
925 let config = MemoryDebugConfig {
926 monitoring_interval: Duration::from_millis(1),
927 enable_real_time_monitoring: true,
928 ..Default::default()
929 };
930
931 let mut debugger = MemoryDebugger::with_config(config);
932 let layout = Layout::from_size_align(1024, 8).expect("layout should be valid");
933
934 for _ in 0..5 {
936 debugger.record_allocation(1024, layout, None);
937 }
938
939 std::thread::sleep(Duration::from_millis(10));
941
942 debugger.record_allocation(1024, layout, None);
944
945 let stats = debugger.realtime_stats();
946 assert!(stats.current_usage > 0);
947 assert!(stats.recent_allocations > 0);
948 }
949
950 #[test]
951 fn test_pressure_level_calculation() {
952 let config = MemoryDebugConfig {
953 memory_warning_threshold: 1000,
954 memory_critical_threshold: 2000,
955 min_tracked_size: 1, monitoring_interval: Duration::from_millis(1), ..Default::default()
958 };
959
960 let mut debugger = MemoryDebugger::with_config(config);
961 let layout = Layout::from_size_align(1000, 8).expect("layout should be valid");
962
963 assert_eq!(
965 debugger.calculate_system_pressure(),
966 SystemPressureLevel::Normal
967 );
968
969 let _id1 = debugger.record_allocation(600, layout, None);
971 std::thread::sleep(Duration::from_millis(2)); assert_eq!(
973 debugger.calculate_system_pressure(),
974 SystemPressureLevel::Low
975 );
976
977 let _id2 = debugger.record_allocation(200, layout, None);
979 assert_eq!(
980 debugger.calculate_system_pressure(),
981 SystemPressureLevel::Medium
982 );
983
984 let _id3 = debugger.record_allocation(200, layout, None);
986 assert_eq!(
987 debugger.calculate_system_pressure(),
988 SystemPressureLevel::High
989 );
990
991 let _id4 = debugger.record_allocation(1000, layout, None);
993 assert_eq!(
994 debugger.calculate_system_pressure(),
995 SystemPressureLevel::Critical
996 );
997 }
998
999 #[test]
1000 fn test_leak_stats() {
1001 let config = MemoryDebugConfig {
1002 leak_threshold: Duration::from_millis(1),
1003 ..Default::default()
1004 };
1005
1006 let mut debugger = MemoryDebugger::with_config(config);
1007 let layout_small = Layout::from_size_align(1024, 8).expect("layout should be valid");
1008 let layout_large = Layout::from_size_align(1024 * 1024, 8).expect("layout should be valid");
1009
1010 let _id1 = debugger.record_allocation(1024, layout_small, Some("small_leak".to_string()));
1012 let _id2 =
1013 debugger.record_allocation(1024 * 1024, layout_large, Some("large_leak".to_string()));
1014
1015 std::thread::sleep(Duration::from_millis(50));
1017
1018 let leaks = debugger.detect_leaks();
1019 assert!(!leaks.is_empty(), "Expected leaks to be detected");
1020
1021 let stats = debugger.leak_stats();
1022
1023 assert!(stats.total_leaks > 0);
1024 assert!(stats.total_leaked_bytes > 0);
1025 }
1028
1029 #[test]
1030 fn test_global_api_functions() {
1031 let config = MemoryDebugConfig {
1033 enable_real_time_monitoring: true,
1034 monitoring_interval: Duration::from_millis(1),
1035 ..Default::default()
1036 };
1037
1038 let _ = init_memory_debugger_with_config(config);
1039
1040 let layout = Layout::from_size_align(1024, 8).expect("layout should be valid");
1042 let id = record_allocation(1024, layout, Some("test".to_string()));
1043
1044 let stats = get_memory_stats();
1045 assert!(stats.is_some());
1046
1047 let pressure_level = get_pressure_level();
1048 assert_eq!(pressure_level, SystemPressureLevel::Normal);
1049
1050 let is_under_pressure = is_under_memory_pressure();
1051 assert!(!is_under_pressure);
1052
1053 record_deallocation(id);
1054
1055 let report = generate_memory_report();
1056 assert!(report.is_some());
1057 }
1058}