1use anyhow::Result;
28use serde::{Deserialize, Serialize};
29use std::collections::{HashMap, VecDeque};
30use std::sync::{Arc, Mutex};
31use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
32use tokio::time::interval;
33use uuid::Uuid;
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct MemoryProfilingConfig {
38 pub enable_heap_tracking: bool,
40 pub enable_leak_detection: bool,
42 pub enable_pattern_analysis: bool,
44 pub enable_fragmentation_monitoring: bool,
46 pub enable_gc_pressure_analysis: bool,
48 pub sampling_interval_ms: u64,
50 pub max_allocation_records: usize,
52 pub large_allocation_threshold: usize,
54 pub pattern_analysis_window_secs: u64,
56 pub leak_detection_threshold_secs: u64,
58}
59
60impl Default for MemoryProfilingConfig {
61 fn default() -> Self {
62 Self {
63 enable_heap_tracking: true,
64 enable_leak_detection: true,
65 enable_pattern_analysis: true,
66 enable_fragmentation_monitoring: true,
67 enable_gc_pressure_analysis: true,
68 sampling_interval_ms: 100, max_allocation_records: 100000,
70 large_allocation_threshold: 1024 * 1024, pattern_analysis_window_secs: 60, leak_detection_threshold_secs: 300, }
74 }
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct AllocationRecord {
80 pub id: Uuid,
81 pub size: usize,
82 pub timestamp: SystemTime,
83 pub stack_trace: Vec<String>,
84 pub allocation_type: AllocationType,
85 pub freed: bool,
86 pub freed_at: Option<SystemTime>,
87 pub tags: Vec<String>, }
89
90#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
92pub enum AllocationType {
93 Tensor,
94 Buffer,
95 Weights,
96 Gradients,
97 Activations,
98 Cache,
99 Temporary,
100 Other(String),
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct MemorySnapshot {
106 pub timestamp: SystemTime,
107 pub total_heap_bytes: usize,
108 pub used_heap_bytes: usize,
109 pub free_heap_bytes: usize,
110 pub peak_heap_bytes: usize,
111 pub allocation_count: usize,
112 pub free_count: usize,
113 pub fragmentation_ratio: f64,
114 pub gc_pressure_score: f64,
115 pub allocations_by_type: HashMap<AllocationType, usize>,
116 pub allocations_by_size: HashMap<String, usize>, }
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct MemoryLeak {
122 pub allocation_id: Uuid,
123 pub size: usize,
124 pub age_seconds: f64,
125 pub allocation_type: AllocationType,
126 pub stack_trace: Vec<String>,
127 pub tags: Vec<String>,
128 pub severity: LeakSeverity,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133pub enum LeakSeverity {
134 Low, Medium, High, Critical, }
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct AllocationPattern {
143 pub pattern_type: PatternType,
144 pub description: String,
145 pub confidence: f64, pub impact_score: f64, pub recommendations: Vec<String>,
148 pub examples: Vec<AllocationRecord>,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153pub enum PatternType {
154 MemoryLeak, ChurningAllocations, FragmentationCausing, LargeAllocations, UnbalancedTypes, PeakUsageSpikes, }
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct FragmentationAnalysis {
165 pub fragmentation_ratio: f64,
166 pub largest_free_block: usize,
167 pub total_free_memory: usize,
168 pub free_block_count: usize,
169 pub average_free_block_size: f64,
170 pub fragmentation_severity: FragmentationSeverity,
171 pub recommendations: Vec<String>,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176pub enum FragmentationSeverity {
177 Low, Medium, High, Severe, }
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct GCPressureAnalysis {
186 pub pressure_score: f64, pub allocation_rate: f64, pub deallocation_rate: f64, pub churn_rate: f64, pub pressure_level: GCPressureLevel,
191 pub contributing_factors: Vec<String>,
192 pub recommendations: Vec<String>,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
197pub enum GCPressureLevel {
198 Low,
199 Medium,
200 High,
201 Critical,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct MemoryProfilingReport {
207 pub session_id: Uuid,
208 pub start_time: SystemTime,
209 pub end_time: SystemTime,
210 pub duration_secs: f64,
211 pub config: MemoryProfilingConfig,
212
213 pub peak_memory_mb: f64,
215 pub average_memory_mb: f64,
216 pub total_allocations: usize,
217 pub total_deallocations: usize,
218 pub net_allocations: i64,
219
220 pub memory_timeline: Vec<MemorySnapshot>,
222
223 pub potential_leaks: Vec<MemoryLeak>,
225 pub leak_summary: HashMap<AllocationType, usize>,
226
227 pub detected_patterns: Vec<AllocationPattern>,
229
230 pub fragmentation_analysis: FragmentationAnalysis,
232
233 pub gc_pressure_analysis: GCPressureAnalysis,
235
236 pub allocations_by_type: HashMap<AllocationType, AllocationTypeStats>,
238 pub allocations_by_size_bucket: HashMap<String, usize>,
239
240 pub profiling_overhead_ms: f64,
242 pub sampling_accuracy: f64,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct AllocationTypeStats {
248 pub total_allocations: usize,
249 pub total_deallocations: usize,
250 pub current_count: usize,
251 pub total_bytes_allocated: usize,
252 pub total_bytes_deallocated: usize,
253 pub current_bytes: usize,
254 pub peak_count: usize,
255 pub peak_bytes: usize,
256 pub average_allocation_size: f64,
257 pub largest_allocation: usize,
258}
259
260#[derive(Debug)]
262pub struct MemoryProfiler {
263 config: MemoryProfilingConfig,
264 session_id: Uuid,
265 start_time: Option<Instant>,
266 allocations: Arc<Mutex<HashMap<Uuid, AllocationRecord>>>,
267 memory_timeline: Arc<Mutex<VecDeque<MemorySnapshot>>>,
268 type_stats: Arc<Mutex<HashMap<AllocationType, AllocationTypeStats>>>,
269 running: Arc<Mutex<bool>>,
270 profiling_start_time: Option<Instant>,
271}
272
273impl MemoryProfiler {
274 pub fn new(config: MemoryProfilingConfig) -> Self {
276 Self {
277 config,
278 session_id: Uuid::new_v4(),
279 start_time: None,
280 allocations: Arc::new(Mutex::new(HashMap::new())),
281 memory_timeline: Arc::new(Mutex::new(VecDeque::new())),
282 type_stats: Arc::new(Mutex::new(HashMap::new())),
283 running: Arc::new(Mutex::new(false)),
284 profiling_start_time: None,
285 }
286 }
287
288 pub async fn start(&mut self) -> Result<()> {
290 let mut running = self.running.lock().unwrap();
291 if *running {
292 return Err(anyhow::anyhow!("Memory profiler is already running"));
293 }
294
295 *running = true;
296 self.start_time = Some(Instant::now());
297 self.profiling_start_time = Some(Instant::now());
298
299 if self.config.enable_heap_tracking {
301 self.start_sampling().await?;
302 }
303
304 tracing::info!("Memory profiler started for session {}", self.session_id);
305 Ok(())
306 }
307
308 pub async fn stop(&mut self) -> Result<MemoryProfilingReport> {
310 let mut running = self.running.lock().unwrap();
311 if !*running {
312 return Err(anyhow::anyhow!("Memory profiler is not running"));
313 }
314
315 *running = false;
316 let end_time = SystemTime::now();
317 let start_time = self.start_time.unwrap();
318 let duration =
319 end_time.duration_since(UNIX_EPOCH)?.as_secs_f64() - start_time.elapsed().as_secs_f64();
320
321 let profiling_overhead = if let Some(prof_start) = self.profiling_start_time {
323 prof_start.elapsed().as_millis() as f64 * 0.01 } else {
325 0.0
326 };
327
328 let report = self.generate_report(end_time, duration, profiling_overhead).await?;
329
330 tracing::info!("Memory profiler stopped for session {}", self.session_id);
331 Ok(report)
332 }
333
334 pub fn record_allocation(
336 &self,
337 size: usize,
338 allocation_type: AllocationType,
339 tags: Vec<String>,
340 ) -> Result<Uuid> {
341 let running = self.running.lock().unwrap();
342 if !*running {
343 return Err(anyhow::anyhow!("Memory profiler is not running"));
344 }
345
346 let allocation_id = Uuid::new_v4();
347 let record = AllocationRecord {
348 id: allocation_id,
349 size,
350 timestamp: SystemTime::now(),
351 stack_trace: self.capture_stack_trace(),
352 allocation_type: allocation_type.clone(),
353 freed: false,
354 freed_at: None,
355 tags,
356 };
357
358 let mut allocations = self.allocations.lock().unwrap();
360 allocations.insert(allocation_id, record);
361
362 self.update_type_stats(&allocation_type, size, true);
364
365 Ok(allocation_id)
366 }
367
368 pub fn record_deallocation(&self, allocation_id: Uuid) -> Result<()> {
370 let running = self.running.lock().unwrap();
371 if !*running {
372 return Ok(()); }
374
375 let mut allocations = self.allocations.lock().unwrap();
376 if let Some(record) = allocations.get_mut(&allocation_id) {
377 record.freed = true;
378 record.freed_at = Some(SystemTime::now());
379
380 self.update_type_stats(&record.allocation_type, record.size, false);
382 }
383
384 Ok(())
385 }
386
387 pub fn tag_allocation(&self, allocation_id: Uuid, tag: String) -> Result<()> {
389 let mut allocations = self.allocations.lock().unwrap();
390 if let Some(record) = allocations.get_mut(&allocation_id) {
391 record.tags.push(tag);
392 }
393 Ok(())
394 }
395
396 pub fn get_memory_snapshot(&self) -> Result<MemorySnapshot> {
398 let allocations = self.allocations.lock().unwrap();
399 let _type_stats = self.type_stats.lock().unwrap();
400
401 let mut total_heap = 0;
402 let mut used_heap = 0;
403 let mut allocation_count = 0;
404 let mut free_count = 0;
405 let mut allocations_by_type = HashMap::new();
406 let mut allocations_by_size = HashMap::new();
407
408 for record in allocations.values() {
409 total_heap += record.size;
410
411 if !record.freed {
412 used_heap += record.size;
413 allocation_count += 1;
414
415 *allocations_by_type.entry(record.allocation_type.clone()).or_insert(0) +=
416 record.size;
417
418 let size_bucket = self.get_size_bucket(record.size);
419 *allocations_by_size.entry(size_bucket).or_insert(0) += 1;
420 } else {
421 free_count += 1;
422 }
423 }
424
425 let free_heap = total_heap - used_heap;
426 let fragmentation_ratio =
427 if total_heap > 0 { free_heap as f64 / total_heap as f64 } else { 0.0 };
428
429 let gc_pressure_score = self.calculate_gc_pressure_score();
430
431 Ok(MemorySnapshot {
432 timestamp: SystemTime::now(),
433 total_heap_bytes: total_heap,
434 used_heap_bytes: used_heap,
435 free_heap_bytes: free_heap,
436 peak_heap_bytes: used_heap, allocation_count,
438 free_count,
439 fragmentation_ratio,
440 gc_pressure_score,
441 allocations_by_type,
442 allocations_by_size,
443 })
444 }
445
446 pub fn detect_leaks(&self) -> Result<Vec<MemoryLeak>> {
448 let allocations = self.allocations.lock().unwrap();
449 let now = SystemTime::now();
450 let threshold = Duration::from_secs(self.config.leak_detection_threshold_secs);
451 let mut leaks = Vec::new();
452
453 for record in allocations.values() {
454 if !record.freed {
455 let age = now.duration_since(record.timestamp)?;
456 if age > threshold {
457 let age_seconds = age.as_secs_f64();
458 let severity = self.classify_leak_severity(record.size, age_seconds);
459
460 leaks.push(MemoryLeak {
461 allocation_id: record.id,
462 size: record.size,
463 age_seconds,
464 allocation_type: record.allocation_type.clone(),
465 stack_trace: record.stack_trace.clone(),
466 tags: record.tags.clone(),
467 severity,
468 });
469 }
470 }
471 }
472
473 leaks.sort_by(|a, b| b.severity.cmp(&a.severity).then(b.size.cmp(&a.size)));
475
476 Ok(leaks)
477 }
478
479 pub fn analyze_patterns(&self) -> Result<Vec<AllocationPattern>> {
481 let mut patterns = Vec::new();
482
483 if let Ok(leak_pattern) = self.detect_leak_pattern() {
485 patterns.push(leak_pattern);
486 }
487
488 if let Ok(churn_pattern) = self.detect_churn_pattern() {
490 patterns.push(churn_pattern);
491 }
492
493 if let Ok(large_alloc_pattern) = self.detect_large_allocation_pattern() {
495 patterns.push(large_alloc_pattern);
496 }
497
498 if let Ok(frag_pattern) = self.detect_fragmentation_pattern() {
500 patterns.push(frag_pattern);
501 }
502
503 Ok(patterns)
504 }
505
506 pub fn analyze_fragmentation(&self) -> Result<FragmentationAnalysis> {
508 let snapshot = self.get_memory_snapshot()?;
509
510 let fragmentation_ratio = snapshot.fragmentation_ratio;
511 let severity = match fragmentation_ratio {
512 r if r < 0.1 => FragmentationSeverity::Low,
513 r if r < 0.3 => FragmentationSeverity::Medium,
514 r if r < 0.6 => FragmentationSeverity::High,
515 _ => FragmentationSeverity::Severe,
516 };
517
518 let recommendations = match severity {
519 FragmentationSeverity::Low => {
520 vec!["Memory fragmentation is low. Continue current practices.".to_string()]
521 },
522 FragmentationSeverity::Medium => vec![
523 "Consider pooling allocations of similar sizes.".to_string(),
524 "Monitor for increasing fragmentation trends.".to_string(),
525 ],
526 FragmentationSeverity::High => vec![
527 "Implement memory pooling for frequent allocations.".to_string(),
528 "Consider compaction strategies for long-running processes.".to_string(),
529 "Review allocation patterns for optimization opportunities.".to_string(),
530 ],
531 FragmentationSeverity::Severe => vec![
532 "Critical fragmentation detected. Immediate action required.".to_string(),
533 "Implement custom allocators with compaction.".to_string(),
534 "Consider restarting the process to reset memory layout.".to_string(),
535 "Review and optimize allocation strategies.".to_string(),
536 ],
537 };
538
539 Ok(FragmentationAnalysis {
540 fragmentation_ratio,
541 largest_free_block: snapshot.free_heap_bytes, total_free_memory: snapshot.free_heap_bytes,
543 free_block_count: snapshot.free_count,
544 average_free_block_size: if snapshot.free_count > 0 {
545 snapshot.free_heap_bytes as f64 / snapshot.free_count as f64
546 } else {
547 0.0
548 },
549 fragmentation_severity: severity,
550 recommendations,
551 })
552 }
553
554 pub fn analyze_gc_pressure(&self) -> Result<GCPressureAnalysis> {
556 let timeline = self.memory_timeline.lock().unwrap();
557
558 let pressure_score = self.calculate_gc_pressure_score();
559 let (allocation_rate, deallocation_rate) = self.calculate_allocation_rates(&timeline);
560 let churn_rate = allocation_rate.min(deallocation_rate);
561
562 let pressure_level = match pressure_score {
563 p if p < 0.25 => GCPressureLevel::Low,
564 p if p < 0.5 => GCPressureLevel::Medium,
565 p if p < 0.75 => GCPressureLevel::High,
566 _ => GCPressureLevel::Critical,
567 };
568
569 let mut contributing_factors = Vec::new();
570 let mut recommendations = Vec::new();
571
572 if allocation_rate > 1000.0 {
573 contributing_factors.push("High allocation rate".to_string());
574 recommendations.push("Consider object pooling or reuse strategies".to_string());
575 }
576
577 if churn_rate > 500.0 {
578 contributing_factors.push("High allocation churn".to_string());
579 recommendations.push("Reduce temporary object creation".to_string());
580 }
581
582 if pressure_level == GCPressureLevel::Critical {
583 recommendations
584 .push("Consider manual memory management for critical paths".to_string());
585 }
586
587 Ok(GCPressureAnalysis {
588 pressure_score,
589 allocation_rate,
590 deallocation_rate,
591 churn_rate,
592 pressure_level,
593 contributing_factors,
594 recommendations,
595 })
596 }
597
598 async fn start_sampling(&self) -> Result<()> {
601 let interval_duration = Duration::from_millis(self.config.sampling_interval_ms);
602 let mut interval = interval(interval_duration);
603 let _timeline = Arc::clone(&self.memory_timeline);
604 let running = Arc::clone(&self.running);
605
606 tokio::spawn(async move {
607 loop {
608 interval.tick().await;
609
610 let is_running = {
611 let running_guard = running.lock().unwrap();
612 *running_guard
613 };
614
615 if !is_running {
616 break;
617 }
618
619 }
622 });
623
624 Ok(())
625 }
626
627 pub async fn generate_report(
628 &self,
629 end_time: SystemTime,
630 duration_secs: f64,
631 profiling_overhead_ms: f64,
632 ) -> Result<MemoryProfilingReport> {
633 let allocations = self.allocations.lock().unwrap();
634 let timeline = self.memory_timeline.lock().unwrap();
635 let type_stats = self.type_stats.lock().unwrap();
636
637 let total_allocations = allocations.len();
638 let total_deallocations = allocations.values().filter(|r| r.freed).count();
639 let net_allocations = total_allocations as i64 - total_deallocations as i64;
640
641 let potential_leaks = self.detect_leaks()?;
642 let detected_patterns = self.analyze_patterns()?;
643 let fragmentation_analysis = self.analyze_fragmentation()?;
644 let gc_pressure_analysis = self.analyze_gc_pressure()?;
645
646 let peak_memory_mb = timeline
648 .iter()
649 .map(|s| s.peak_heap_bytes as f64 / 1024.0 / 1024.0)
650 .fold(0.0, f64::max);
651
652 let average_memory_mb = if !timeline.is_empty() {
653 timeline.iter().map(|s| s.used_heap_bytes as f64 / 1024.0 / 1024.0).sum::<f64>()
654 / timeline.len() as f64
655 } else {
656 0.0
657 };
658
659 let mut leak_summary = HashMap::new();
660 for leak in &potential_leaks {
661 *leak_summary.entry(leak.allocation_type.clone()).or_insert(0) += 1;
662 }
663
664 let mut allocations_by_size_bucket = HashMap::new();
666 for record in allocations.values() {
667 let bucket = self.get_size_bucket(record.size);
668 *allocations_by_size_bucket.entry(bucket).or_insert(0) += 1;
669 }
670
671 Ok(MemoryProfilingReport {
672 session_id: self.session_id,
673 start_time: UNIX_EPOCH
674 + Duration::from_secs_f64(
675 SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs_f64() - duration_secs,
676 ),
677 end_time,
678 duration_secs,
679 config: self.config.clone(),
680 peak_memory_mb,
681 average_memory_mb,
682 total_allocations,
683 total_deallocations,
684 net_allocations,
685 memory_timeline: timeline.iter().cloned().collect(),
686 potential_leaks,
687 leak_summary,
688 detected_patterns,
689 fragmentation_analysis,
690 gc_pressure_analysis,
691 allocations_by_type: type_stats.clone(),
692 allocations_by_size_bucket,
693 profiling_overhead_ms,
694 sampling_accuracy: 0.95, })
696 }
697
698 fn capture_stack_trace(&self) -> Vec<String> {
699 vec![
702 "function_a".to_string(),
703 "function_b".to_string(),
704 "main".to_string(),
705 ]
706 }
707
708 fn update_type_stats(
709 &self,
710 allocation_type: &AllocationType,
711 size: usize,
712 is_allocation: bool,
713 ) {
714 let mut type_stats = self.type_stats.lock().unwrap();
715 let stats = type_stats.entry(allocation_type.clone()).or_insert(AllocationTypeStats {
716 total_allocations: 0,
717 total_deallocations: 0,
718 current_count: 0,
719 total_bytes_allocated: 0,
720 total_bytes_deallocated: 0,
721 current_bytes: 0,
722 peak_count: 0,
723 peak_bytes: 0,
724 average_allocation_size: 0.0,
725 largest_allocation: 0,
726 });
727
728 if is_allocation {
729 stats.total_allocations += 1;
730 stats.current_count += 1;
731 stats.total_bytes_allocated += size;
732 stats.current_bytes += size;
733 stats.peak_count = stats.peak_count.max(stats.current_count);
734 stats.peak_bytes = stats.peak_bytes.max(stats.current_bytes);
735 stats.largest_allocation = stats.largest_allocation.max(size);
736 } else {
737 stats.total_deallocations += 1;
738 stats.current_count = stats.current_count.saturating_sub(1);
739 stats.total_bytes_deallocated += size;
740 stats.current_bytes = stats.current_bytes.saturating_sub(size);
741 }
742
743 stats.average_allocation_size = if stats.total_allocations > 0 {
744 stats.total_bytes_allocated as f64 / stats.total_allocations as f64
745 } else {
746 0.0
747 };
748 }
749
750 fn get_size_bucket(&self, size: usize) -> String {
751 match size {
752 0..=1024 => "0-1KB".to_string(),
753 1025..=10240 => "1-10KB".to_string(),
754 10241..=102400 => "10-100KB".to_string(),
755 102401..=1048576 => "100KB-1MB".to_string(),
756 1048577..=10485760 => "1-10MB".to_string(),
757 _ => ">10MB".to_string(),
758 }
759 }
760
761 fn classify_leak_severity(&self, size: usize, age_seconds: f64) -> LeakSeverity {
762 let large_size = size > self.config.large_allocation_threshold;
763 let old_age = age_seconds > 1800.0; let very_old_age = age_seconds > 3600.0; match (large_size, old_age, very_old_age) {
767 (true, _, true) => LeakSeverity::Critical,
768 (true, true, _) => LeakSeverity::High,
769 (true, false, _) => LeakSeverity::Medium,
770 (false, true, _) => LeakSeverity::Medium,
771 _ => LeakSeverity::Low,
772 }
773 }
774
775 fn calculate_gc_pressure_score(&self) -> f64 {
776 0.3 }
781
782 fn calculate_allocation_rates(&self, timeline: &VecDeque<MemorySnapshot>) -> (f64, f64) {
783 if timeline.len() < 2 {
784 return (0.0, 0.0);
785 }
786
787 let first = &timeline[0];
789 let last = &timeline[timeline.len() - 1];
790
791 let duration = last
792 .timestamp
793 .duration_since(first.timestamp)
794 .unwrap_or(Duration::from_secs(1))
795 .as_secs_f64();
796
797 let allocation_rate =
798 (last.allocation_count as f64 - first.allocation_count as f64) / duration;
799 let deallocation_rate = (last.free_count as f64 - first.free_count as f64) / duration;
800
801 (allocation_rate.max(0.0), deallocation_rate.max(0.0))
802 }
803
804 fn detect_leak_pattern(&self) -> Result<AllocationPattern> {
807 let leaks = self.detect_leaks()?;
808 let high_severity_leaks = leaks
809 .iter()
810 .filter(|l| l.severity == LeakSeverity::High || l.severity == LeakSeverity::Critical)
811 .count();
812
813 let confidence = if leaks.len() > 10 { 0.9 } else { 0.5 };
814 let impact_score = (high_severity_leaks as f64 / (leaks.len().max(1)) as f64).min(1.0);
815
816 Ok(AllocationPattern {
817 pattern_type: PatternType::MemoryLeak,
818 description: format!("Detected {} potential memory leaks", leaks.len()),
819 confidence,
820 impact_score,
821 recommendations: vec![
822 "Review long-lived allocations for proper cleanup".to_string(),
823 "Implement RAII patterns for automatic resource management".to_string(),
824 ],
825 examples: leaks
826 .into_iter()
827 .take(3)
828 .map(|leak| {
829 AllocationRecord {
831 id: leak.allocation_id,
832 size: leak.size,
833 timestamp: SystemTime::now(), stack_trace: leak.stack_trace,
835 allocation_type: leak.allocation_type,
836 freed: false,
837 freed_at: None,
838 tags: leak.tags,
839 }
840 })
841 .collect(),
842 })
843 }
844
845 fn detect_churn_pattern(&self) -> Result<AllocationPattern> {
846 let allocations = self.allocations.lock().unwrap();
848 let short_lived_count = allocations
849 .values()
850 .filter(|record| {
851 if let (Some(_freed_at), false) = (record.freed_at, record.freed) {
852 false } else if record.freed {
854 if let Some(freed_at) = record.freed_at {
855 freed_at.duration_since(record.timestamp).unwrap_or(Duration::from_secs(0))
856 < Duration::from_secs(1)
857 } else {
858 false
859 }
860 } else {
861 false
862 }
863 })
864 .count();
865
866 let total_count = allocations.len();
867 let churn_ratio = if total_count > 0 {
868 short_lived_count as f64 / total_count as f64
869 } else {
870 0.0
871 };
872
873 Ok(AllocationPattern {
874 pattern_type: PatternType::ChurningAllocations,
875 description: format!(
876 "High allocation churn detected: {:.1}% short-lived allocations",
877 churn_ratio * 100.0
878 ),
879 confidence: if churn_ratio > 0.5 { 0.8 } else { 0.4 },
880 impact_score: churn_ratio,
881 recommendations: vec![
882 "Consider object pooling for frequently allocated objects".to_string(),
883 "Reduce temporary object creation in hot paths".to_string(),
884 ],
885 examples: vec![], })
887 }
888
889 fn detect_large_allocation_pattern(&self) -> Result<AllocationPattern> {
890 let allocations = self.allocations.lock().unwrap();
891 let large_allocations: Vec<_> = allocations
892 .values()
893 .filter(|record| record.size > self.config.large_allocation_threshold)
894 .cloned()
895 .collect();
896
897 let impact_score = if !allocations.is_empty() {
898 large_allocations.len() as f64 / allocations.len() as f64
899 } else {
900 0.0
901 };
902
903 Ok(AllocationPattern {
904 pattern_type: PatternType::LargeAllocations,
905 description: format!(
906 "Found {} large allocations (>{}MB)",
907 large_allocations.len(),
908 self.config.large_allocation_threshold / 1024 / 1024
909 ),
910 confidence: if large_allocations.len() > 5 { 0.9 } else { 0.6 },
911 impact_score,
912 recommendations: vec![
913 "Review large allocations for optimization opportunities".to_string(),
914 "Consider streaming or chunked processing for large data".to_string(),
915 ],
916 examples: large_allocations.into_iter().take(3).collect(),
917 })
918 }
919
920 fn detect_fragmentation_pattern(&self) -> Result<AllocationPattern> {
921 let fragmentation = self.analyze_fragmentation()?;
922
923 Ok(AllocationPattern {
924 pattern_type: PatternType::FragmentationCausing,
925 description: format!(
926 "Memory fragmentation at {:.1}%",
927 fragmentation.fragmentation_ratio * 100.0
928 ),
929 confidence: 0.8,
930 impact_score: fragmentation.fragmentation_ratio,
931 recommendations: fragmentation.recommendations,
932 examples: vec![], })
934 }
935}
936
937impl PartialOrd for LeakSeverity {
938 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
939 Some(self.cmp(other))
940 }
941}
942
943impl Ord for LeakSeverity {
944 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
945 let self_val = match self {
946 LeakSeverity::Low => 0,
947 LeakSeverity::Medium => 1,
948 LeakSeverity::High => 2,
949 LeakSeverity::Critical => 3,
950 };
951 let other_val = match other {
952 LeakSeverity::Low => 0,
953 LeakSeverity::Medium => 1,
954 LeakSeverity::High => 2,
955 LeakSeverity::Critical => 3,
956 };
957 self_val.cmp(&other_val)
958 }
959}
960
961#[cfg(test)]
962mod tests {
963 use super::*;
964 use tokio;
965
966 #[tokio::test(flavor = "multi_thread")]
967 #[ignore] async fn test_memory_profiler_basic() -> Result<()> {
969 let config = MemoryProfilingConfig {
970 sampling_interval_ms: 1000, ..Default::default()
972 };
973 let mut profiler = MemoryProfiler::new(config);
974
975 let test_result = tokio::time::timeout(Duration::from_millis(500), async {
977 profiler.start().await?;
978
979 let alloc_id1 = profiler.record_allocation(
981 1024,
982 AllocationType::Tensor,
983 vec!["test".to_string()],
984 )?;
985
986 let _alloc_id2 = profiler.record_allocation(
987 2048,
988 AllocationType::Buffer,
989 vec!["test".to_string()],
990 )?;
991
992 profiler.record_deallocation(alloc_id1)?;
994
995 tokio::time::sleep(Duration::from_millis(1)).await;
997
998 let report = profiler.stop().await?;
999
1000 assert_eq!(report.total_allocations, 2);
1001 assert_eq!(report.total_deallocations, 1);
1002 assert_eq!(report.net_allocations, 1);
1003
1004 Ok::<(), anyhow::Error>(())
1005 })
1006 .await;
1007
1008 match test_result {
1009 Ok(result) => result,
1010 Err(_) => Err(anyhow::anyhow!("Test timed out after 500ms")),
1011 }
1012 }
1013
1014 #[tokio::test]
1015 async fn test_leak_detection() -> Result<()> {
1016 let config = MemoryProfilingConfig {
1017 leak_detection_threshold_secs: 1, ..Default::default()
1019 };
1020
1021 let mut profiler = MemoryProfiler::new(config);
1022 profiler.start().await?; profiler.record_allocation(1024, AllocationType::Tensor, vec!["leak_test".to_string()])?;
1026
1027 tokio::time::sleep(Duration::from_secs(2)).await;
1028
1029 let leaks = profiler.detect_leaks()?;
1030 assert!(!leaks.is_empty());
1031
1032 Ok(())
1033 }
1034
1035 #[test]
1036 fn test_size_buckets() {
1037 let config = MemoryProfilingConfig::default();
1038 let profiler = MemoryProfiler::new(config);
1039
1040 assert_eq!(profiler.get_size_bucket(512), "0-1KB");
1041 assert_eq!(profiler.get_size_bucket(5120), "1-10KB");
1042 assert_eq!(profiler.get_size_bucket(51200), "10-100KB");
1043 assert_eq!(profiler.get_size_bucket(512000), "100KB-1MB");
1044 assert_eq!(profiler.get_size_bucket(5120000), "1-10MB");
1045 assert_eq!(profiler.get_size_bucket(51200000), ">10MB");
1046 }
1047
1048 #[test]
1049 fn test_leak_severity_classification() {
1050 let config = MemoryProfilingConfig::default();
1051 let profiler = MemoryProfiler::new(config);
1052
1053 assert_eq!(
1055 profiler.classify_leak_severity(1024, 60.0),
1056 LeakSeverity::Low
1057 );
1058
1059 assert_eq!(
1061 profiler.classify_leak_severity(10485760, 3700.0),
1062 LeakSeverity::Critical
1063 );
1064
1065 assert_eq!(
1067 profiler.classify_leak_severity(524288, 1900.0),
1068 LeakSeverity::Medium
1069 );
1070 }
1071}