1use serde::{Deserialize, Serialize};
40use std::collections::HashMap;
41use std::time::{Duration, Instant};
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct AllocationEvent {
46 pub timestamp: u64,
48 pub size: usize,
50 pub operation: String,
52 pub event_type: EventType,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
58pub enum EventType {
59 Allocation,
61 Deallocation,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct ScopeStats {
68 pub name: String,
70 pub count: usize,
72 pub total_duration: Duration,
74 pub min_duration: Duration,
76 pub max_duration: Duration,
78 pub peak_memory: usize,
80 pub total_allocated: usize,
82 pub total_deallocated: usize,
84}
85
86impl ScopeStats {
87 fn new(name: String) -> Self {
89 Self {
90 name,
91 count: 0,
92 total_duration: Duration::ZERO,
93 min_duration: Duration::MAX,
94 max_duration: Duration::ZERO,
95 peak_memory: 0,
96 total_allocated: 0,
97 total_deallocated: 0,
98 }
99 }
100
101 fn update(&mut self, duration: Duration, allocated: usize, deallocated: usize) {
103 self.count += 1;
104 self.total_duration += duration;
105 self.min_duration = self.min_duration.min(duration);
106 self.max_duration = self.max_duration.max(duration);
107 self.total_allocated += allocated;
108 self.total_deallocated += deallocated;
109
110 let current_usage = self.total_allocated.saturating_sub(self.total_deallocated);
111 self.peak_memory = self.peak_memory.max(current_usage);
112 }
113
114 pub fn avg_duration(&self) -> Duration {
116 if self.count > 0 {
117 self.total_duration / self.count as u32
118 } else {
119 Duration::ZERO
120 }
121 }
122
123 pub fn net_memory(&self) -> isize {
125 self.total_allocated as isize - self.total_deallocated as isize
126 }
127}
128
129#[derive(Debug)]
131pub struct MemoryProfiler {
132 start_time: Instant,
134 events: Vec<AllocationEvent>,
136 scopes: HashMap<String, ScopeStats>,
138 current_memory: usize,
140 peak_memory: usize,
142 active_scopes: Vec<(String, Instant, usize)>,
144}
145
146impl MemoryProfiler {
147 pub fn new() -> Self {
149 Self {
150 start_time: Instant::now(),
151 events: Vec::new(),
152 scopes: HashMap::new(),
153 current_memory: 0,
154 peak_memory: 0,
155 active_scopes: Vec::new(),
156 }
157 }
158
159 fn elapsed(&self) -> u64 {
161 self.start_time.elapsed().as_micros() as u64
162 }
163
164 pub fn record_allocation(&mut self, operation: &str, size: usize) {
166 let event = AllocationEvent {
167 timestamp: self.elapsed(),
168 size,
169 operation: operation.to_string(),
170 event_type: EventType::Allocation,
171 };
172
173 self.current_memory += size;
174 self.peak_memory = self.peak_memory.max(self.current_memory);
175 self.events.push(event);
176 }
177
178 pub fn record_deallocation(&mut self, operation: &str, size: usize) {
180 let event = AllocationEvent {
181 timestamp: self.elapsed(),
182 size,
183 operation: operation.to_string(),
184 event_type: EventType::Deallocation,
185 };
186
187 self.current_memory = self.current_memory.saturating_sub(size);
188 self.events.push(event);
189 }
190
191 pub fn start_scope(&mut self, name: &str) {
193 let start_time = Instant::now();
194 let start_memory = self.current_memory;
195 self.active_scopes
196 .push((name.to_string(), start_time, start_memory));
197 }
198
199 pub fn end_scope(&mut self, name: &str) {
201 if let Some(pos) = self.active_scopes.iter().rposition(|(n, _, _)| n == name) {
202 let (scope_name, start_time, start_memory) = self.active_scopes.remove(pos);
203 let duration = start_time.elapsed();
204 let end_memory = self.current_memory;
205
206 let allocated = end_memory.saturating_sub(start_memory);
207 let deallocated = start_memory.saturating_sub(end_memory);
208
209 self.scopes
210 .entry(scope_name.clone())
211 .or_insert_with(|| ScopeStats::new(scope_name))
212 .update(duration, allocated, deallocated);
213 }
214 }
215
216 pub fn current_memory(&self) -> usize {
218 self.current_memory
219 }
220
221 pub fn peak_memory(&self) -> usize {
223 self.peak_memory
224 }
225
226 pub fn total_allocations(&self) -> usize {
228 self.events
229 .iter()
230 .filter(|e| e.event_type == EventType::Allocation)
231 .count()
232 }
233
234 pub fn total_deallocations(&self) -> usize {
236 self.events
237 .iter()
238 .filter(|e| e.event_type == EventType::Deallocation)
239 .count()
240 }
241
242 pub fn total_bytes_allocated(&self) -> usize {
244 self.events
245 .iter()
246 .filter(|e| e.event_type == EventType::Allocation)
247 .map(|e| e.size)
248 .sum()
249 }
250
251 pub fn total_bytes_deallocated(&self) -> usize {
253 self.events
254 .iter()
255 .filter(|e| e.event_type == EventType::Deallocation)
256 .map(|e| e.size)
257 .sum()
258 }
259
260 pub fn check_leaks(&self) -> Vec<String> {
262 let mut leaks = Vec::new();
263
264 for (name, stats) in &self.scopes {
265 let net = stats.net_memory();
266 if net > 1024 {
267 leaks.push(format!("{}: {} bytes potentially leaked", name, net));
269 }
270 }
271
272 if self.current_memory > 1024 && self.total_deallocations() < self.total_allocations() {
273 leaks.push(format!(
274 "Global: {} bytes not deallocated ({} allocs, {} deallocs)",
275 self.current_memory,
276 self.total_allocations(),
277 self.total_deallocations()
278 ));
279 }
280
281 leaks
282 }
283
284 pub fn scope_stats(&self, name: &str) -> Option<&ScopeStats> {
286 self.scopes.get(name)
287 }
288
289 pub fn all_scopes(&self) -> &HashMap<String, ScopeStats> {
291 &self.scopes
292 }
293
294 pub fn events(&self) -> &[AllocationEvent] {
296 &self.events
297 }
298
299 pub fn report(&self) -> String {
301 let mut report = String::new();
302 report.push_str("=== Memory Profiling Report ===\n\n");
303
304 report.push_str("Overall Statistics:\n");
306 report.push_str(&format!(
307 " Peak memory: {} bytes ({:.2} MB)\n",
308 self.peak_memory,
309 self.peak_memory as f64 / 1024.0 / 1024.0
310 ));
311 report.push_str(&format!(
312 " Current memory: {} bytes ({:.2} MB)\n",
313 self.current_memory,
314 self.current_memory as f64 / 1024.0 / 1024.0
315 ));
316 report.push_str(&format!(
317 " Total allocations: {}\n",
318 self.total_allocations()
319 ));
320 report.push_str(&format!(
321 " Total deallocations: {}\n",
322 self.total_deallocations()
323 ));
324 report.push_str(&format!(
325 " Total allocated: {} bytes ({:.2} MB)\n",
326 self.total_bytes_allocated(),
327 self.total_bytes_allocated() as f64 / 1024.0 / 1024.0
328 ));
329 report.push_str(&format!(
330 " Total deallocated: {} bytes ({:.2} MB)\n\n",
331 self.total_bytes_deallocated(),
332 self.total_bytes_deallocated() as f64 / 1024.0 / 1024.0
333 ));
334
335 if !self.scopes.is_empty() {
337 report.push_str("Scope Statistics:\n");
338 let mut scopes: Vec<_> = self.scopes.iter().collect();
339 scopes.sort_by_key(|(_, stats)| std::cmp::Reverse(stats.peak_memory));
340
341 for (name, stats) in scopes {
342 report.push_str(&format!("\n {}:\n", name));
343 report.push_str(&format!(" Count: {}\n", stats.count));
344 report.push_str(&format!(" Avg duration: {:?}\n", stats.avg_duration()));
345 report.push_str(&format!(" Min duration: {:?}\n", stats.min_duration));
346 report.push_str(&format!(" Max duration: {:?}\n", stats.max_duration));
347 report.push_str(&format!(" Peak memory: {} bytes\n", stats.peak_memory));
348 report.push_str(&format!(
349 " Total allocated: {} bytes\n",
350 stats.total_allocated
351 ));
352 report.push_str(&format!(
353 " Total deallocated: {} bytes\n",
354 stats.total_deallocated
355 ));
356 report.push_str(&format!(" Net memory: {} bytes\n", stats.net_memory()));
357 }
358 report.push('\n');
359 }
360
361 let leaks = self.check_leaks();
363 if !leaks.is_empty() {
364 report.push_str("Potential Memory Leaks:\n");
365 for leak in leaks {
366 report.push_str(&format!(" - {}\n", leak));
367 }
368 } else {
369 report.push_str("No memory leaks detected.\n");
370 }
371
372 report
373 }
374
375 pub fn clear(&mut self) {
377 self.events.clear();
378 self.scopes.clear();
379 self.current_memory = 0;
380 self.peak_memory = 0;
381 self.active_scopes.clear();
382 self.start_time = Instant::now();
383 }
384}
385
386impl Default for MemoryProfiler {
387 fn default() -> Self {
388 Self::new()
389 }
390}
391
392pub struct ProfileScope<'a> {
394 profiler: &'a mut MemoryProfiler,
395 name: String,
396}
397
398impl<'a> ProfileScope<'a> {
399 pub fn new(profiler: &'a mut MemoryProfiler, name: &str) -> Self {
401 profiler.start_scope(name);
402 Self {
403 profiler,
404 name: name.to_string(),
405 }
406 }
407}
408
409impl<'a> Drop for ProfileScope<'a> {
410 fn drop(&mut self) {
411 self.profiler.end_scope(&self.name);
412 }
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize)]
417pub struct MemorySnapshot {
418 pub timestamp: u64,
420 pub memory_used: usize,
422 pub operation: Option<String>,
424}
425
426#[derive(Debug)]
428pub struct TimelineAnalyzer {
429 snapshots: Vec<MemorySnapshot>,
431 start_time: Instant,
433}
434
435impl TimelineAnalyzer {
436 pub fn new() -> Self {
438 Self {
439 snapshots: Vec::new(),
440 start_time: Instant::now(),
441 }
442 }
443
444 pub fn snapshot(&mut self, memory_used: usize, operation: Option<&str>) {
446 let timestamp = self.start_time.elapsed().as_micros() as u64;
447 self.snapshots.push(MemorySnapshot {
448 timestamp,
449 memory_used,
450 operation: operation.map(String::from),
451 });
452 }
453
454 pub fn snapshots(&self) -> &[MemorySnapshot] {
456 &self.snapshots
457 }
458
459 pub fn peak_memory(&self) -> usize {
461 self.snapshots
462 .iter()
463 .map(|s| s.memory_used)
464 .max()
465 .unwrap_or(0)
466 }
467
468 pub fn avg_memory(&self) -> usize {
470 if self.snapshots.is_empty() {
471 0
472 } else {
473 self.snapshots.iter().map(|s| s.memory_used).sum::<usize>() / self.snapshots.len()
474 }
475 }
476
477 pub fn top_operations(&self, n: usize) -> Vec<(String, usize)> {
479 let mut op_memory: HashMap<String, usize> = HashMap::new();
480
481 for snapshot in &self.snapshots {
482 if let Some(ref op) = snapshot.operation {
483 let entry = op_memory.entry(op.clone()).or_insert(0);
484 *entry = (*entry).max(snapshot.memory_used);
485 }
486 }
487
488 let mut sorted: Vec<_> = op_memory.into_iter().collect();
489 sorted.sort_by_key(|(_, mem)| std::cmp::Reverse(*mem));
490 sorted.truncate(n);
491 sorted
492 }
493
494 pub fn to_csv(&self) -> String {
496 let mut csv = String::from("timestamp_us,memory_bytes,operation\n");
497 for snapshot in &self.snapshots {
498 csv.push_str(&format!(
499 "{},{},{}\n",
500 snapshot.timestamp,
501 snapshot.memory_used,
502 snapshot.operation.as_deref().unwrap_or("")
503 ));
504 }
505 csv
506 }
507
508 pub fn clear(&mut self) {
510 self.snapshots.clear();
511 self.start_time = Instant::now();
512 }
513}
514
515impl Default for TimelineAnalyzer {
516 fn default() -> Self {
517 Self::new()
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 fn test_memory_profiler_creation() {
527 let profiler = MemoryProfiler::new();
528 assert_eq!(profiler.current_memory(), 0);
529 assert_eq!(profiler.peak_memory(), 0);
530 }
531
532 #[test]
533 fn test_record_allocation() {
534 let mut profiler = MemoryProfiler::new();
535 profiler.record_allocation("test", 1024);
536 assert_eq!(profiler.current_memory(), 1024);
537 assert_eq!(profiler.peak_memory(), 1024);
538 assert_eq!(profiler.total_allocations(), 1);
539 }
540
541 #[test]
542 fn test_record_deallocation() {
543 let mut profiler = MemoryProfiler::new();
544 profiler.record_allocation("test", 2048);
545 profiler.record_deallocation("test", 1024);
546 assert_eq!(profiler.current_memory(), 1024);
547 assert_eq!(profiler.peak_memory(), 2048);
548 }
549
550 #[test]
551 fn test_scope_tracking() {
552 let mut profiler = MemoryProfiler::new();
553
554 profiler.start_scope("operation1");
555 profiler.record_allocation("op1", 512);
556 std::thread::sleep(Duration::from_millis(10));
557 profiler.end_scope("operation1");
558
559 let stats = profiler.scope_stats("operation1").unwrap();
560 assert_eq!(stats.count, 1);
561 assert!(stats.total_duration >= Duration::from_millis(10));
562 assert_eq!(stats.total_allocated, 512);
563 }
564
565 #[test]
566 fn test_profile_scope_raii() {
567 let mut profiler = MemoryProfiler::new();
568
569 {
570 let _scope = ProfileScope::new(&mut profiler, "scoped_op");
571 std::thread::sleep(Duration::from_millis(5));
572 }
573
574 let stats = profiler.scope_stats("scoped_op").unwrap();
575 assert_eq!(stats.count, 1);
576 assert!(stats.total_duration >= Duration::from_millis(5));
577 }
578
579 #[test]
580 fn test_nested_scopes() {
581 let mut profiler = MemoryProfiler::new();
582
583 profiler.start_scope("outer");
584 profiler.start_scope("inner");
585 profiler.end_scope("inner");
586 profiler.end_scope("outer");
587
588 assert!(profiler.scope_stats("outer").is_some());
589 assert!(profiler.scope_stats("inner").is_some());
590 }
591
592 #[test]
593 fn test_leak_detection() {
594 let mut profiler = MemoryProfiler::new();
595
596 profiler.start_scope("leaky_op");
597 profiler.record_allocation("leaky_op", 2048);
598 profiler.end_scope("leaky_op");
600
601 let leaks = profiler.check_leaks();
602 assert!(!leaks.is_empty());
603 }
604
605 #[test]
606 fn test_report_generation() {
607 let mut profiler = MemoryProfiler::new();
608
609 profiler.start_scope("test_op");
610 profiler.record_allocation("test_op", 1024);
611 profiler.record_deallocation("test_op", 1024);
612 profiler.end_scope("test_op");
613
614 let report = profiler.report();
615 assert!(report.contains("Memory Profiling Report"));
616 assert!(report.contains("test_op"));
617 }
618
619 #[test]
620 fn test_clear() {
621 let mut profiler = MemoryProfiler::new();
622 profiler.record_allocation("test", 1024);
623 profiler.clear();
624
625 assert_eq!(profiler.current_memory(), 0);
626 assert_eq!(profiler.peak_memory(), 0);
627 assert_eq!(profiler.total_allocations(), 0);
628 }
629
630 #[test]
631 fn test_timeline_analyzer() {
632 let mut analyzer = TimelineAnalyzer::new();
633
634 analyzer.snapshot(1024, Some("op1"));
635 analyzer.snapshot(2048, Some("op2"));
636 analyzer.snapshot(512, Some("op1"));
637
638 assert_eq!(analyzer.snapshots().len(), 3);
639 assert_eq!(analyzer.peak_memory(), 2048);
640 assert_eq!(analyzer.avg_memory(), (1024 + 2048 + 512) / 3);
641 }
642
643 #[test]
644 fn test_top_operations() {
645 let mut analyzer = TimelineAnalyzer::new();
646
647 analyzer.snapshot(1024, Some("op1"));
648 analyzer.snapshot(2048, Some("op2"));
649 analyzer.snapshot(1536, Some("op1"));
650 analyzer.snapshot(512, Some("op3"));
651
652 let top = analyzer.top_operations(2);
653 assert_eq!(top.len(), 2);
654 assert_eq!(top[0].0, "op2");
655 assert_eq!(top[0].1, 2048);
656 }
657
658 #[test]
659 fn test_csv_export() {
660 let mut analyzer = TimelineAnalyzer::new();
661 analyzer.snapshot(1024, Some("test"));
662
663 let csv = analyzer.to_csv();
664 assert!(csv.contains("timestamp_us,memory_bytes,operation"));
665 assert!(csv.contains("1024"));
666 assert!(csv.contains("test"));
667 }
668
669 #[test]
670 fn test_scope_stats_avg_duration() {
671 let mut stats = ScopeStats::new("test".to_string());
672 stats.update(Duration::from_millis(10), 0, 0);
673 stats.update(Duration::from_millis(20), 0, 0);
674 stats.update(Duration::from_millis(30), 0, 0);
675
676 let avg = stats.avg_duration();
677 assert_eq!(avg, Duration::from_millis(20));
678 }
679
680 #[test]
681 fn test_scope_stats_peak_memory() {
682 let mut stats = ScopeStats::new("test".to_string());
683 stats.update(Duration::from_millis(1), 1024, 0);
684 stats.update(Duration::from_millis(1), 2048, 512);
685 stats.update(Duration::from_millis(1), 512, 1024);
686
687 assert!(stats.peak_memory > 0);
689 }
690}