1use crate::tracker::MemoryTracker;
4use crate::types::{AllocationInfo, MemoryStats, TrackingResult, TypeMemoryUsage};
5use std::collections::HashMap;
6use std::fs::File;
7use std::io::Write;
8use std::path::Path;
9use svg::node::element::{Circle, Rectangle, Text as SvgText};
10use svg::Document;
11
12fn enhance_type_information(memory_by_type: &[TypeMemoryUsage]) -> Vec<EnhancedTypeInfo> {
14 memory_by_type
15 .iter()
16 .filter_map(|usage| {
17 if usage.type_name == "Unknown" {
19 return None;
20 }
21
22 let (simplified_name, category) = simplify_type_name(&usage.type_name);
24
25 Some(EnhancedTypeInfo {
26 simplified_name,
27 category,
28 total_size: usage.total_size,
29 allocation_count: usage.allocation_count,
30 })
31 })
32 .collect()
33}
34
35fn categorize_allocations(allocations: &[AllocationInfo]) -> Vec<AllocationCategory> {
37 let mut categories: HashMap<String, AllocationCategory> = HashMap::new();
38
39 for allocation in allocations {
40 if allocation.var_name.is_none()
42 || allocation.type_name.as_ref().is_none_or(|t| t == "Unknown")
43 {
44 continue;
45 }
46
47 let type_name = allocation.type_name.as_ref().unwrap();
48 let (_, category_name) = simplify_type_name(type_name);
49
50 let category =
51 categories
52 .entry(category_name.clone())
53 .or_insert_with(|| AllocationCategory {
54 name: category_name.clone(),
55 allocations: Vec::new(),
56 total_size: 0,
57 color: get_category_color(&category_name),
58 });
59
60 category.allocations.push(allocation.clone());
61 category.total_size += allocation.size;
62 }
63
64 let mut result: Vec<_> = categories.into_values().collect();
65 result.sort_by(|a, b| b.total_size.cmp(&a.total_size));
66 result
67}
68
69fn simplify_type_name(type_name: &str) -> (String, String) {
71 if type_name.starts_with("alloc::vec::Vec<") || type_name.starts_with("std::vec::Vec<") {
72 let inner = extract_generic_type(type_name, "Vec");
73 (format!("Vec<{inner}>"), "Collections".to_string())
74 } else if type_name.starts_with("alloc::string::String") || type_name == "String" {
75 ("String".to_string(), "Text".to_string())
76 } else if type_name.starts_with("alloc::boxed::Box<")
77 || type_name.starts_with("std::boxed::Box<")
78 {
79 let inner = extract_generic_type(type_name, "Box");
80 (format!("Box<{inner}>"), "Smart Pointers".to_string())
81 } else if type_name.starts_with("alloc::rc::Rc<") || type_name.starts_with("std::rc::Rc<") {
82 let inner = extract_generic_type(type_name, "Rc");
83 (format!("Rc<{inner}>"), "Reference Counted".to_string())
84 } else if type_name.starts_with("alloc::sync::Arc<") || type_name.starts_with("std::sync::Arc<")
85 {
86 let inner = extract_generic_type(type_name, "Arc");
87 (format!("Arc<{inner}>"), "Thread-Safe Shared".to_string())
88 } else if type_name.contains("HashMap") {
89 ("HashMap".to_string(), "Collections".to_string())
90 } else if type_name.contains("BTreeMap") {
91 ("BTreeMap".to_string(), "Collections".to_string())
92 } else if type_name.contains("VecDeque") {
93 ("VecDeque".to_string(), "Collections".to_string())
94 } else {
95 let simplified = type_name
97 .split("::")
98 .last()
99 .unwrap_or(type_name)
100 .to_string();
101 (simplified, "Other".to_string())
102 }
103}
104
105fn extract_generic_type(type_name: &str, container: &str) -> String {
107 if let Some(start) = type_name.find(&format!("{container}<")) {
108 let start = start + container.len() + 1;
109 if let Some(end) = type_name[start..].rfind('>') {
110 let inner = &type_name[start..start + end];
111 return inner.split("::").last().unwrap_or(inner).to_string();
113 }
114 }
115 "?".to_string()
116}
117
118fn get_category_color(category: &str) -> String {
120 match category {
121 "Collections" => "#3498db".to_string(), "Text" => "#2ecc71".to_string(), "Smart Pointers" => "#e74c3c".to_string(), "Reference Counted" => "#f39c12".to_string(), "Thread-Safe Shared" => "#9b59b6".to_string(), _ => "#95a5a6".to_string(), }
128}
129
130#[derive(Debug, Clone)]
131struct EnhancedTypeInfo {
132 simplified_name: String,
133 category: String,
134 total_size: usize,
135 allocation_count: usize,
136}
137
138#[derive(Debug, Clone)]
139struct AllocationCategory {
140 name: String,
141 allocations: Vec<AllocationInfo>,
142 total_size: usize,
143 color: String,
144}
145
146fn format_bytes(bytes: usize) -> String {
148 const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
149 let mut size = bytes as f64;
150 let mut unit_index = 0;
151
152 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
153 size /= 1024.0;
154 unit_index += 1;
155 }
156
157 if unit_index == 0 {
158 format!("{} {}", bytes, UNITS[unit_index])
159 } else {
160 format!("{:.1} {}", size, UNITS[unit_index])
161 }
162}
163
164pub fn export_enhanced_svg<P: AsRef<Path>>(tracker: &MemoryTracker, path: P) -> TrackingResult<()> {
166 let path = path.as_ref();
167
168 if let Some(parent) = path.parent() {
170 if !parent.exists() {
171 std::fs::create_dir_all(parent)?;
172 }
173 }
174
175 let active_allocations = tracker.get_active_allocations()?;
176 let memory_by_type = tracker.get_memory_by_type()?;
177 let stats = tracker.get_stats()?;
178
179 let enhanced_memory_by_type = enhance_type_information(&memory_by_type);
181 let categorized_allocations = categorize_allocations(&active_allocations);
182
183 let mut document = Document::new()
185 .set("viewBox", (0, 0, 1800, 2400))
186 .set("width", 1800)
187 .set("height", 2400)
188 .set(
189 "style",
190 "background-color: #f8f9fa; font-family: 'Segoe UI', Arial, sans-serif;",
191 );
192
193 document = add_css_styles(document)?;
195
196 document = add_enhanced_header(document, &stats)?;
198
199 document = add_performance_dashboard(document, &stats, &active_allocations)?;
201
202 document = add_memory_heatmap(document, &active_allocations)?;
204
205 if !enhanced_memory_by_type.is_empty() {
207 document = add_enhanced_type_chart(document, &enhanced_memory_by_type)?;
208 }
209 document = add_fragmentation_analysis(document, &active_allocations)?;
210
211 if !categorized_allocations.is_empty() {
213 document = add_categorized_allocations(document, &categorized_allocations)?;
214 }
215 document = add_callstack_analysis(document, &active_allocations)?;
216
217 document = add_memory_growth_trends(document, &active_allocations, &stats)?;
219
220 document = add_memory_timeline(document, &active_allocations, &stats)?;
222
223 document = add_interactive_legend(document)?;
225 document = add_comprehensive_summary(document, &stats, &active_allocations)?;
226
227 let mut file = File::create(path)?;
229 write!(file, "{document}")?;
230
231 Ok(())
232}
233
234fn add_enhanced_header(mut document: Document, stats: &MemoryStats) -> TrackingResult<Document> {
236 let title = SvgText::new("Rust Memory Usage Analysis")
238 .set("x", 600)
239 .set("y", 40)
240 .set("text-anchor", "middle")
241 .set("font-size", 24)
242 .set("font-weight", "bold")
243 .set("fill", "#2c3e50");
244
245 document = document.add(title);
246
247 let stats_bg = Rectangle::new()
249 .set("x", 50)
250 .set("y", 60)
251 .set("width", 1100)
252 .set("height", 80)
253 .set("fill", "#ecf0f1")
254 .set("stroke", "#bdc3c7")
255 .set("stroke-width", 1)
256 .set("rx", 5);
257
258 document = document.add(stats_bg);
259
260 let stats_text = [
262 format!("Active Allocations: {}", stats.active_allocations),
263 format!("Active Memory: {}", format_bytes(stats.active_memory)),
264 format!("Peak Memory: {}", format_bytes(stats.peak_memory)),
265 format!("Total Allocations: {}", stats.total_allocations),
266 ];
267
268 for (i, text) in stats_text.iter().enumerate() {
269 let x = 80 + (i * 270);
270 let stat_text = SvgText::new(text)
271 .set("x", x)
272 .set("y", 105)
273 .set("font-size", 14)
274 .set("font-weight", "600")
275 .set("fill", "#34495e");
276
277 document = document.add(stat_text);
278 }
279
280 Ok(document)
281}
282
283fn add_enhanced_type_chart(
285 mut document: Document,
286 types: &[EnhancedTypeInfo],
287) -> TrackingResult<Document> {
288 let chart_x = 50;
289 let chart_y = 670;
290 let chart_width = 850;
291 let chart_height = 300;
292
293 let bg = Rectangle::new()
295 .set("x", chart_x)
296 .set("y", chart_y)
297 .set("width", chart_width)
298 .set("height", chart_height)
299 .set("fill", "white")
300 .set("stroke", "#bdc3c7")
301 .set("stroke-width", 1)
302 .set("rx", 5);
303
304 document = document.add(bg);
305
306 let title = SvgText::new("Memory Usage by Type")
308 .set("x", chart_x + chart_width / 2)
309 .set("y", chart_y - 10)
310 .set("text-anchor", "middle")
311 .set("font-size", 16)
312 .set("font-weight", "bold")
313 .set("fill", "#2c3e50");
314
315 document = document.add(title);
316
317 if types.is_empty() {
318 let no_data = SvgText::new("No type information available")
319 .set("x", chart_x + chart_width / 2)
320 .set("y", chart_y + chart_height / 2)
321 .set("text-anchor", "middle")
322 .set("font-size", 14)
323 .set("fill", "#7f8c8d");
324
325 document = document.add(no_data);
326 return Ok(document);
327 }
328
329 let max_size = types.iter().map(|t| t.total_size).max().unwrap_or(1);
330 let bar_height = (chart_height - 40) / types.len().min(10);
331
332 for (i, type_info) in types.iter().take(10).enumerate() {
333 let y = chart_y + 20 + i * bar_height;
334 let bar_width =
335 ((type_info.total_size as f64 / max_size as f64) * (chart_width - 200) as f64) as i32;
336
337 let bar = Rectangle::new()
339 .set("x", chart_x + 150)
340 .set("y", y)
341 .set("width", bar_width)
342 .set("height", bar_height - 5)
343 .set("fill", get_category_color(&type_info.category))
344 .set("stroke", "#34495e")
345 .set("stroke-width", 1);
346
347 document = document.add(bar);
348
349 let name_text = SvgText::new(&type_info.simplified_name)
351 .set("x", chart_x + 10)
352 .set("y", y + bar_height / 2 + 4)
353 .set("font-size", 11)
354 .set("font-weight", "600")
355 .set("fill", "#2c3e50");
356
357 document = document.add(name_text);
358
359 let size_text = SvgText::new(format!(
361 "{} ({} allocs)",
362 format_bytes(type_info.total_size),
363 type_info.allocation_count
364 ))
365 .set("x", chart_x + 160)
366 .set("y", y + bar_height / 2 + 4)
367 .set("font-size", 10)
368 .set("fill", "white");
369
370 document = document.add(size_text);
371 }
372
373 Ok(document)
374}
375
376fn add_categorized_allocations(
378 mut document: Document,
379 categories: &[AllocationCategory],
380) -> TrackingResult<Document> {
381 let chart_x = 50;
382 let chart_y = 1020;
383 let chart_width = 850;
384 let chart_height = 300;
385
386 let bg = Rectangle::new()
388 .set("x", chart_x)
389 .set("y", chart_y)
390 .set("width", chart_width)
391 .set("height", chart_height)
392 .set("fill", "white")
393 .set("stroke", "#bdc3c7")
394 .set("stroke-width", 1)
395 .set("rx", 5);
396
397 document = document.add(bg);
398
399 let title = SvgText::new("Tracked Variables by Category")
401 .set("x", chart_x + chart_width / 2)
402 .set("y", chart_y - 10)
403 .set("text-anchor", "middle")
404 .set("font-size", 16)
405 .set("font-weight", "bold")
406 .set("fill", "#2c3e50");
407
408 document = document.add(title);
409
410 if categories.is_empty() {
411 let no_data = SvgText::new("No tracked variables found")
412 .set("x", chart_x + chart_width / 2)
413 .set("y", chart_y + chart_height / 2)
414 .set("text-anchor", "middle")
415 .set("font-size", 14)
416 .set("fill", "#7f8c8d");
417
418 document = document.add(no_data);
419 return Ok(document);
420 }
421
422 let max_size = categories.iter().map(|c| c.total_size).max().unwrap_or(1);
424 let bar_height = (chart_height - 60) / categories.len().min(8);
425
426 for (i, category) in categories.iter().take(8).enumerate() {
427 let y = chart_y + 30 + i * bar_height;
428 let bar_width =
429 ((category.total_size as f64 / max_size as f64) * (chart_width - 200) as f64) as i32;
430
431 let bar = Rectangle::new()
433 .set("x", chart_x + 150)
434 .set("y", y)
435 .set("width", bar_width)
436 .set("height", bar_height - 5)
437 .set("fill", category.color.as_str())
438 .set("stroke", "#34495e")
439 .set("stroke-width", 1);
440
441 document = document.add(bar);
442
443 let name_text = SvgText::new(&category.name)
445 .set("x", chart_x + 10)
446 .set("y", y + bar_height / 2 + 4)
447 .set("font-size", 12)
448 .set("font-weight", "600")
449 .set("fill", "#2c3e50");
450
451 document = document.add(name_text);
452
453 let size_text = SvgText::new(format!(
455 "{} ({} vars)",
456 format_bytes(category.total_size),
457 category.allocations.len()
458 ))
459 .set("x", chart_x + 160)
460 .set("y", y + bar_height / 2 + 4)
461 .set("font-size", 10)
462 .set("fill", "white");
463
464 document = document.add(size_text);
465 }
466
467 Ok(document)
468}
469
470fn add_memory_timeline(
472 mut document: Document,
473 allocations: &[AllocationInfo],
474 _stats: &MemoryStats,
475) -> TrackingResult<Document> {
476 let chart_x = 50;
477 let chart_y = 1720;
478 let chart_width = 1700;
479 let chart_height = 300;
480
481 let bg = Rectangle::new()
483 .set("x", chart_x)
484 .set("y", chart_y)
485 .set("width", chart_width)
486 .set("height", chart_height)
487 .set("fill", "white")
488 .set("stroke", "#bdc3c7")
489 .set("stroke-width", 1)
490 .set("rx", 5);
491
492 document = document.add(bg);
493
494 let title = SvgText::new("Variable Allocation Timeline")
496 .set("x", chart_x + chart_width / 2)
497 .set("y", chart_y - 10)
498 .set("text-anchor", "middle")
499 .set("font-size", 16)
500 .set("font-weight", "bold")
501 .set("fill", "#2c3e50");
502
503 document = document.add(title);
504
505 if allocations.is_empty() {
506 let no_data = SvgText::new("No allocation data available")
507 .set("x", chart_x + chart_width / 2)
508 .set("y", chart_y + chart_height / 2)
509 .set("text-anchor", "middle")
510 .set("font-size", 14)
511 .set("fill", "#7f8c8d");
512
513 document = document.add(no_data);
514 return Ok(document);
515 }
516
517 let mut tracked_allocs: Vec<_> = allocations
519 .iter()
520 .filter(|a| a.var_name.is_some())
521 .collect();
522 tracked_allocs.sort_by_key(|a| a.timestamp_alloc);
523
524 if tracked_allocs.is_empty() {
525 let no_data = SvgText::new("No tracked variables found")
526 .set("x", chart_x + chart_width / 2)
527 .set("y", chart_y + chart_height / 2)
528 .set("text-anchor", "middle")
529 .set("font-size", 14)
530 .set("fill", "#7f8c8d");
531
532 document = document.add(no_data);
533 return Ok(document);
534 }
535
536 let min_time = tracked_allocs
537 .first()
538 .map(|a| a.timestamp_alloc)
539 .unwrap_or(0);
540 let max_time = tracked_allocs
541 .last()
542 .map(|a| a.timestamp_alloc)
543 .unwrap_or(min_time + 1);
544 let _time_range = (max_time - min_time).max(1);
545
546 let label_width = 200; let timeline_width = chart_width - label_width - 40;
549 let max_items = 8; for (i, allocation) in tracked_allocs.iter().take(max_items).enumerate() {
553 let x = chart_x + 20 + (i * timeline_width / max_items.max(1));
555 let y = chart_y + 50 + (i * 25); let x = x.min(chart_x + timeline_width).max(chart_x + 20);
559
560 let color = if let Some(type_name) = &allocation.type_name {
562 let (_, category) = simplify_type_name(type_name);
563 get_category_color(&category)
564 } else {
565 "#95a5a6".to_string()
566 };
567
568 let point = Circle::new()
570 .set("cx", x)
571 .set("cy", y)
572 .set("r", 5)
573 .set("fill", color)
574 .set("stroke", "#2c3e50")
575 .set("stroke-width", 2);
576
577 document = document.add(point);
578
579 let label_start_x = chart_x + timeline_width + 20;
581 let line = svg::node::element::Line::new()
582 .set("x1", x + 5)
583 .set("y1", y)
584 .set("x2", label_start_x)
585 .set("y2", y)
586 .set("stroke", "#bdc3c7")
587 .set("stroke-width", 1)
588 .set("stroke-dasharray", "3,3");
589
590 document = document.add(line);
591
592 if let Some(var_name) = &allocation.var_name {
594 let label_text = format!("{} ({})", var_name, format_bytes(allocation.size));
595 let label = SvgText::new(label_text)
596 .set("x", label_start_x + 5)
597 .set("y", y + 4)
598 .set("font-size", 11)
599 .set("font-weight", "500")
600 .set("fill", "#2c3e50");
601
602 document = document.add(label);
603 }
604 }
605
606 let axis_y = chart_y + chart_height - 40;
608 let axis_line = svg::node::element::Line::new()
609 .set("x1", chart_x + 20)
610 .set("y1", axis_y)
611 .set("x2", chart_x + timeline_width)
612 .set("y2", axis_y)
613 .set("stroke", "#34495e")
614 .set("stroke-width", 2);
615
616 document = document.add(axis_line);
617
618 let start_label = SvgText::new("Timeline")
620 .set("x", chart_x + 20)
621 .set("y", axis_y + 20)
622 .set("font-size", 12)
623 .set("font-weight", "600")
624 .set("fill", "#7f8c8d");
625
626 document = document.add(start_label);
627
628 Ok(document)
629}
630
631fn add_fragmentation_analysis(
633 mut document: Document,
634 allocations: &[AllocationInfo],
635) -> TrackingResult<Document> {
636 let chart_x = 950;
637 let chart_y = 670;
638 let chart_width = 800;
639 let chart_height = 300;
640
641 let bg = Rectangle::new()
643 .set("x", chart_x)
644 .set("y", chart_y)
645 .set("width", chart_width)
646 .set("height", chart_height)
647 .set("fill", "white")
648 .set("stroke", "#f39c12")
649 .set("stroke-width", 2)
650 .set("rx", 10);
651
652 document = document.add(bg);
653
654 let title = SvgText::new("Memory Fragmentation Analysis")
656 .set("x", chart_x + chart_width / 2)
657 .set("y", chart_y - 10)
658 .set("text-anchor", "middle")
659 .set("font-size", 18)
660 .set("font-weight", "bold")
661 .set("fill", "#2c3e50");
662
663 document = document.add(title);
664
665 if allocations.is_empty() {
666 let no_data = SvgText::new("No allocation data available")
667 .set("x", chart_x + chart_width / 2)
668 .set("y", chart_y + chart_height / 2)
669 .set("text-anchor", "middle")
670 .set("font-size", 14)
671 .set("fill", "#7f8c8d");
672
673 document = document.add(no_data);
674 return Ok(document);
675 }
676
677 let size_buckets = [
679 (0, 64, "Tiny (0-64B)"),
680 (65, 256, "Small (65-256B)"),
681 (257, 1024, "Medium (257B-1KB)"),
682 (1025, 4096, "Large (1-4KB)"),
683 (4097, 16384, "XLarge (4-16KB)"),
684 (16385, usize::MAX, "Huge (>16KB)"),
685 ];
686
687 let mut bucket_counts = vec![0; size_buckets.len()];
688
689 for allocation in allocations {
690 for (i, &(min, max, _)) in size_buckets.iter().enumerate() {
691 if allocation.size >= min && allocation.size <= max {
692 bucket_counts[i] += 1;
693 break;
694 }
695 }
696 }
697
698 let max_count = bucket_counts.iter().max().copied().unwrap_or(1);
699 let bar_width = (chart_width - 100) / size_buckets.len();
700
701 for (i, (&(_, _, label), &count)) in size_buckets.iter().zip(bucket_counts.iter()).enumerate() {
703 let x = chart_x + 50 + i * bar_width;
704 let bar_height = if max_count > 0 {
705 (count as f64 / max_count as f64 * (chart_height - 80) as f64) as i32
706 } else {
707 0
708 };
709 let y = chart_y + chart_height - 40 - bar_height;
710
711 let color = match i {
713 0..=1 => "#27ae60", 2..=3 => "#f39c12", _ => "#e74c3c", };
717
718 let bar = Rectangle::new()
719 .set("x", x)
720 .set("y", y)
721 .set("width", bar_width - 5)
722 .set("height", bar_height)
723 .set("fill", color)
724 .set("stroke", "#2c3e50")
725 .set("stroke-width", 1);
726
727 document = document.add(bar);
728
729 let count_text = SvgText::new(count.to_string())
731 .set("x", x + bar_width / 2)
732 .set("y", y - 5)
733 .set("text-anchor", "middle")
734 .set("font-size", 12)
735 .set("font-weight", "bold")
736 .set("fill", "#2c3e50");
737
738 document = document.add(count_text);
739
740 let size_text = SvgText::new(label)
742 .set("x", x + bar_width / 2)
743 .set("y", chart_y + chart_height - 10)
744 .set("text-anchor", "middle")
745 .set("font-size", 10)
746 .set("fill", "#7f8c8d");
747
748 document = document.add(size_text);
749 }
750
751 Ok(document)
752}
753
754fn add_callstack_analysis(
756 mut document: Document,
757 allocations: &[AllocationInfo],
758) -> TrackingResult<Document> {
759 let chart_x = 950;
760 let chart_y = 1020;
761 let chart_width = 800;
762 let chart_height = 300;
763
764 let bg = Rectangle::new()
766 .set("x", chart_x)
767 .set("y", chart_y)
768 .set("width", chart_width)
769 .set("height", chart_height)
770 .set("fill", "white")
771 .set("stroke", "#9b59b6")
772 .set("stroke-width", 2)
773 .set("rx", 10);
774
775 document = document.add(bg);
776
777 let title = SvgText::new("Call Stack Analysis")
779 .set("x", chart_x + chart_width / 2)
780 .set("y", chart_y - 10)
781 .set("text-anchor", "middle")
782 .set("font-size", 18)
783 .set("font-weight", "bold")
784 .set("fill", "#2c3e50");
785
786 document = document.add(title);
787
788 let mut source_stats: HashMap<String, (usize, usize)> = HashMap::new();
790
791 for allocation in allocations {
792 let source = if let Some(type_name) = &allocation.type_name {
793 if type_name.len() > 30 {
794 format!("{}...", &type_name[..27])
795 } else {
796 type_name.clone()
797 }
798 } else {
799 "Unknown".to_string()
800 };
801
802 let entry = source_stats.entry(source).or_insert((0, 0));
803 entry.0 += 1;
804 entry.1 += allocation.size;
805 }
806
807 if source_stats.is_empty() {
808 let no_data = SvgText::new("No call stack data available")
809 .set("x", chart_x + chart_width / 2)
810 .set("y", chart_y + chart_height / 2)
811 .set("text-anchor", "middle")
812 .set("font-size", 14)
813 .set("fill", "#7f8c8d");
814
815 document = document.add(no_data);
816 return Ok(document);
817 }
818
819 let mut sorted_sources: Vec<_> = source_stats.iter().collect();
821 sorted_sources.sort_by(|a, b| b.1 .1.cmp(&a.1 .1));
822
823 let max_size = sorted_sources
824 .first()
825 .map(|(_, (_, size))| *size)
826 .unwrap_or(1);
827
828 for (i, (source, (count, total_size))) in sorted_sources.iter().take(10).enumerate() {
830 let y = chart_y + 40 + i * 25;
831 let node_size = ((*total_size as f64 / max_size as f64) * 15.0 + 5.0) as i32;
832
833 let colors = ["#e74c3c", "#f39c12", "#27ae60", "#3498db", "#9b59b6"];
835 let color = colors[i % colors.len()];
836
837 let node = Circle::new()
838 .set("cx", chart_x + 50)
839 .set("cy", y)
840 .set("r", node_size)
841 .set("fill", color)
842 .set("stroke", "#2c3e50")
843 .set("stroke-width", 2);
844
845 document = document.add(node);
846
847 let source_text = format!("{source} ({count} allocs, {total_size} bytes)");
849
850 let label = SvgText::new(source_text)
851 .set("x", chart_x + 80)
852 .set("y", y + 5)
853 .set("font-size", 11)
854 .set("font-weight", "500")
855 .set("fill", "#2c3e50");
856
857 document = document.add(label);
858 }
859
860 Ok(document)
861}
862
863fn add_memory_growth_trends(
865 mut document: Document,
866 allocations: &[AllocationInfo],
867 stats: &MemoryStats,
868) -> TrackingResult<Document> {
869 let chart_x = 50;
870 let chart_y = 1370;
871 let chart_width = 1700;
872 let chart_height = 300;
873
874 let bg = Rectangle::new()
876 .set("x", chart_x)
877 .set("y", chart_y)
878 .set("width", chart_width)
879 .set("height", chart_height)
880 .set("fill", "white")
881 .set("stroke", "#27ae60")
882 .set("stroke-width", 2)
883 .set("rx", 10);
884
885 document = document.add(bg);
886
887 let title = SvgText::new("Memory Growth Trends")
889 .set("x", chart_x + chart_width / 2)
890 .set("y", chart_y - 10)
891 .set("text-anchor", "middle")
892 .set("font-size", 18)
893 .set("font-weight", "bold")
894 .set("fill", "#2c3e50");
895
896 document = document.add(title);
897
898 if allocations.is_empty() {
899 let no_data = SvgText::new("No allocation data available")
900 .set("x", chart_x + chart_width / 2)
901 .set("y", chart_y + chart_height / 2)
902 .set("text-anchor", "middle")
903 .set("font-size", 14)
904 .set("fill", "#7f8c8d");
905
906 document = document.add(no_data);
907 return Ok(document);
908 }
909
910 let time_points = 10;
912 let point_width = (chart_width - 100) / time_points;
913
914 for i in 0..time_points {
915 let x = chart_x + 50 + i * point_width;
916 let simulated_memory = stats.active_memory / time_points * (i + 1);
917 let y = chart_y + chart_height
918 - 50
919 - ((simulated_memory as f64 / stats.peak_memory as f64) * (chart_height - 100) as f64)
920 as i32;
921
922 let point = Circle::new()
924 .set("cx", x)
925 .set("cy", y)
926 .set("r", 4)
927 .set("fill", "#27ae60")
928 .set("stroke", "#2c3e50")
929 .set("stroke-width", 1);
930
931 document = document.add(point);
932
933 if i > 0 {
935 let prev_x = chart_x + 50 + (i - 1) * point_width;
936 let prev_memory = stats.active_memory / time_points * i;
937 let prev_y = chart_y + chart_height
938 - 50
939 - ((prev_memory as f64 / stats.peak_memory as f64) * (chart_height - 100) as f64)
940 as i32;
941
942 let line = svg::node::element::Line::new()
943 .set("x1", prev_x)
944 .set("y1", prev_y)
945 .set("x2", x)
946 .set("y2", y)
947 .set("stroke", "#27ae60")
948 .set("stroke-width", 2);
949
950 document = document.add(line);
951 }
952 }
953
954 let peak_y = chart_y + 50;
956 let peak_line = svg::node::element::Line::new()
957 .set("x1", chart_x + 50)
958 .set("y1", peak_y)
959 .set("x2", chart_x + chart_width - 50)
960 .set("y2", peak_y)
961 .set("stroke", "#e74c3c")
962 .set("stroke-width", 2)
963 .set("stroke-dasharray", "10,5");
964
965 document = document.add(peak_line);
966
967 let peak_label = SvgText::new(format!("Peak: {} bytes", stats.peak_memory))
968 .set("x", chart_x + chart_width - 100)
969 .set("y", peak_y - 10)
970 .set("font-size", 12)
971 .set("font-weight", "bold")
972 .set("fill", "#e74c3c");
973
974 document = document.add(peak_label);
975
976 Ok(document)
977}
978
979fn add_interactive_legend(mut document: Document) -> TrackingResult<Document> {
981 let legend_x = 50;
982 let legend_y = 2070;
983 let legend_width = 850;
984 let legend_height = 250;
985
986 let bg = Rectangle::new()
988 .set("x", legend_x)
989 .set("y", legend_y)
990 .set("width", legend_width)
991 .set("height", legend_height)
992 .set("fill", "white")
993 .set("stroke", "#34495e")
994 .set("stroke-width", 2)
995 .set("rx", 10);
996
997 document = document.add(bg);
998
999 let title = SvgText::new("Interactive Legend & Guide")
1001 .set("x", legend_x + legend_width / 2)
1002 .set("y", legend_y - 10)
1003 .set("text-anchor", "middle")
1004 .set("font-size", 18)
1005 .set("font-weight", "bold")
1006 .set("fill", "#2c3e50");
1007
1008 document = document.add(title);
1009
1010 let legend_items = [
1012 ("#e74c3c", "High Memory Usage / Critical"),
1013 ("#f39c12", "Medium Usage / Warning"),
1014 ("#27ae60", "Low Usage / Good"),
1015 ("#3498db", "Performance Metrics"),
1016 ("#9b59b6", "Call Stack Data"),
1017 ("#34495e", "General Information"),
1018 ];
1019
1020 for (i, (color, description)) in legend_items.iter().enumerate() {
1021 let x = legend_x + 30 + (i % 3) * 220;
1022 let y = legend_y + 40 + (i / 3) * 40;
1023
1024 let swatch = Rectangle::new()
1026 .set("x", x)
1027 .set("y", y - 10)
1028 .set("width", 20)
1029 .set("height", 15)
1030 .set("fill", *color)
1031 .set("stroke", "#2c3e50")
1032 .set("stroke-width", 1);
1033
1034 document = document.add(swatch);
1035
1036 let desc_text = SvgText::new(*description)
1038 .set("x", x + 30)
1039 .set("y", y)
1040 .set("font-size", 12)
1041 .set("fill", "#2c3e50");
1042
1043 document = document.add(desc_text);
1044 }
1045
1046 Ok(document)
1047}
1048
1049fn add_comprehensive_summary(
1051 mut document: Document,
1052 stats: &MemoryStats,
1053 allocations: &[AllocationInfo],
1054) -> TrackingResult<Document> {
1055 let summary_x = 950;
1056 let summary_y = 2070;
1057 let summary_width = 800;
1058 let summary_height = 250;
1059
1060 let bg = Rectangle::new()
1062 .set("x", summary_x)
1063 .set("y", summary_y)
1064 .set("width", summary_width)
1065 .set("height", summary_height)
1066 .set("fill", "white")
1067 .set("stroke", "#2c3e50")
1068 .set("stroke-width", 2)
1069 .set("rx", 10);
1070
1071 document = document.add(bg);
1072
1073 let title = SvgText::new("Memory Analysis Summary")
1075 .set("x", summary_x + summary_width / 2)
1076 .set("y", summary_y - 10)
1077 .set("text-anchor", "middle")
1078 .set("font-size", 18)
1079 .set("font-weight", "bold")
1080 .set("fill", "#2c3e50");
1081
1082 document = document.add(title);
1083
1084 let tracked_vars = allocations.iter().filter(|a| a.var_name.is_some()).count();
1086 let avg_size = if !allocations.is_empty() {
1087 allocations.iter().map(|a| a.size).sum::<usize>() / allocations.len()
1088 } else {
1089 0
1090 };
1091
1092 let summary_items = [
1093 format!("Total Active Allocations: {}", stats.active_allocations),
1094 format!(
1095 "Tracked Variables: {} ({:.1}%)",
1096 tracked_vars,
1097 if stats.active_allocations > 0 {
1098 tracked_vars as f64 / stats.active_allocations as f64 * 100.0
1099 } else {
1100 0.0
1101 }
1102 ),
1103 format!("Average Allocation Size: {avg_size} bytes"),
1104 format!(
1105 "Memory Efficiency: {:.1}%",
1106 if stats.total_allocations > 0 {
1107 stats.total_deallocations as f64 / stats.total_allocations as f64 * 100.0
1108 } else {
1109 0.0
1110 }
1111 ),
1112 format!(
1113 "Peak vs Current: {} vs {} bytes",
1114 stats.peak_memory, stats.active_memory
1115 ),
1116 ];
1117
1118 for (i, item) in summary_items.iter().enumerate() {
1119 let summary_text = SvgText::new(item)
1120 .set("x", summary_x + 30)
1121 .set("y", summary_y + 40 + i * 25)
1122 .set("font-size", 13)
1123 .set("font-weight", "500")
1124 .set("fill", "#2c3e50");
1125
1126 document = document.add(summary_text);
1127 }
1128
1129 Ok(document)
1130}
1131
1132fn add_css_styles(mut document: Document) -> TrackingResult<Document> {
1134 let style = svg::node::element::Style::new(
1135 r#"
1136 .tooltip { opacity: 0; transition: opacity 0.3s; }
1137 .chart-element:hover .tooltip { opacity: 1; }
1138 .interactive-bar:hover { opacity: 0.8; cursor: pointer; }
1139 .legend-item:hover { background-color: #ecf0f1; }
1140 .heatmap-cell:hover { stroke-width: 2; }
1141 .trend-line { stroke-dasharray: 5,5; animation: dash 1s linear infinite; }
1142 @keyframes dash { to { stroke-dashoffset: -10; } }
1143 .performance-gauge { filter: drop-shadow(2px 2px 4px rgba(0,0,0,0.3)); }
1144 .callstack-node:hover { transform: scale(1.1); transform-origin: center; }
1145 "#,
1146 );
1147
1148 document = document.add(style);
1149 Ok(document)
1150}
1151
1152fn add_performance_dashboard(
1154 mut document: Document,
1155 stats: &MemoryStats,
1156 _allocations: &[AllocationInfo],
1157) -> TrackingResult<Document> {
1158 let dashboard_x = 50;
1159 let dashboard_y = 170;
1160 let dashboard_width = 1700;
1161 let dashboard_height = 200;
1162
1163 let bg = Rectangle::new()
1165 .set("x", dashboard_x)
1166 .set("y", dashboard_y)
1167 .set("width", dashboard_width)
1168 .set("height", dashboard_height)
1169 .set("fill", "white")
1170 .set("stroke", "#3498db")
1171 .set("stroke-width", 2)
1172 .set("rx", 10)
1173 .set("class", "performance-gauge");
1174
1175 document = document.add(bg);
1176
1177 let title = SvgText::new("Performance Dashboard")
1179 .set("x", dashboard_x + dashboard_width / 2)
1180 .set("y", dashboard_y - 10)
1181 .set("text-anchor", "middle")
1182 .set("font-size", 18)
1183 .set("font-weight", "bold")
1184 .set("fill", "#2c3e50");
1185
1186 document = document.add(title);
1187
1188 let efficiency = if stats.total_allocations > 0 {
1190 stats.total_deallocations as f64 / stats.total_allocations as f64 * 100.0
1191 } else {
1192 0.0
1193 };
1194
1195 let avg_allocation_size = if stats.total_allocations > 0 {
1196 stats.active_memory / stats.total_allocations
1197 } else {
1198 0
1199 };
1200
1201 let memory_utilization = if stats.peak_memory > 0 {
1202 stats.active_memory as f64 / stats.peak_memory as f64 * 100.0
1203 } else {
1204 0.0
1205 };
1206
1207 let gauges = [
1209 ("Memory Efficiency", efficiency, "%", "#e74c3c"),
1210 ("Avg Alloc Size", avg_allocation_size as f64, "B", "#f39c12"),
1211 ("Memory Utilization", memory_utilization, "%", "#27ae60"),
1212 (
1213 "Active Allocs",
1214 stats.active_allocations as f64,
1215 "",
1216 "#9b59b6",
1217 ),
1218 ];
1219
1220 for (i, (label, value, unit, color)) in gauges.iter().enumerate() {
1221 let gauge_x = dashboard_x + 50 + (i * 170);
1222 let gauge_y = dashboard_y + 50;
1223
1224 let bg_circle = Circle::new()
1226 .set("cx", gauge_x)
1227 .set("cy", gauge_y)
1228 .set("r", 40)
1229 .set("fill", "none")
1230 .set("stroke", "#ecf0f1")
1231 .set("stroke-width", 8);
1232
1233 document = document.add(bg_circle);
1234
1235 let normalized_value = if *unit == "%" {
1237 value.min(100.0) / 100.0
1238 } else {
1239 (value / 1000.0).min(1.0) };
1241
1242 let arc_length = normalized_value * 2.0 * std::f64::consts::PI * 40.0;
1243 let gauge_arc = Circle::new()
1244 .set("cx", gauge_x)
1245 .set("cy", gauge_y)
1246 .set("r", 40)
1247 .set("fill", "none")
1248 .set("stroke", *color)
1249 .set("stroke-width", 8)
1250 .set("stroke-dasharray", format!("{} {}", arc_length, 300.0))
1251 .set("transform", format!("rotate(-90 {gauge_x} {gauge_y})"));
1252
1253 document = document.add(gauge_arc);
1254
1255 let value_text = if *unit == "B" && *value > 1024.0 {
1257 format!("{:.1}K", value / 1024.0)
1258 } else {
1259 format!("{value:.1}{unit}")
1260 };
1261
1262 let text = SvgText::new(value_text)
1263 .set("x", gauge_x)
1264 .set("y", gauge_y + 5)
1265 .set("text-anchor", "middle")
1266 .set("font-size", 12)
1267 .set("font-weight", "bold")
1268 .set("fill", *color);
1269
1270 document = document.add(text);
1271
1272 let label_text = SvgText::new(*label)
1274 .set("x", gauge_x)
1275 .set("y", gauge_y + 60)
1276 .set("text-anchor", "middle")
1277 .set("font-size", 10)
1278 .set("fill", "#7f8c8d");
1279
1280 document = document.add(label_text);
1281 }
1282
1283 Ok(document)
1284}
1285
1286fn add_memory_heatmap(
1288 mut document: Document,
1289 allocations: &[AllocationInfo],
1290) -> TrackingResult<Document> {
1291 let heatmap_x = 50;
1292 let heatmap_y = 420;
1293 let heatmap_width = 1700;
1294 let heatmap_height = 200;
1295
1296 let bg = Rectangle::new()
1298 .set("x", heatmap_x)
1299 .set("y", heatmap_y)
1300 .set("width", heatmap_width)
1301 .set("height", heatmap_height)
1302 .set("fill", "white")
1303 .set("stroke", "#e74c3c")
1304 .set("stroke-width", 2)
1305 .set("rx", 10);
1306
1307 document = document.add(bg);
1308
1309 let title = SvgText::new("Memory Allocation Heatmap")
1311 .set("x", heatmap_x + heatmap_width / 2)
1312 .set("y", heatmap_y - 10)
1313 .set("text-anchor", "middle")
1314 .set("font-size", 18)
1315 .set("font-weight", "bold")
1316 .set("fill", "#2c3e50");
1317
1318 document = document.add(title);
1319
1320 if allocations.is_empty() {
1321 let no_data = SvgText::new("No allocation data available")
1322 .set("x", heatmap_x + heatmap_width / 2)
1323 .set("y", heatmap_y + heatmap_height / 2)
1324 .set("text-anchor", "middle")
1325 .set("font-size", 14)
1326 .set("fill", "#7f8c8d");
1327
1328 document = document.add(no_data);
1329 return Ok(document);
1330 }
1331
1332 let grid_cols = 20;
1334 let grid_rows = 8;
1335 let cell_width = (heatmap_width - 40) / grid_cols;
1336 let cell_height = (heatmap_height - 40) / grid_rows;
1337
1338 let mut density_grid = vec![vec![0; grid_cols]; grid_rows];
1340 let max_size = allocations.iter().map(|a| a.size).max().unwrap_or(1);
1341
1342 for allocation in allocations {
1343 let size_ratio = allocation.size as f64 / max_size as f64;
1345 let time_ratio = (allocation.timestamp_alloc % 1000) as f64 / 1000.0;
1346
1347 let col = ((size_ratio * (grid_cols - 1) as f64) as usize).min(grid_cols - 1);
1348 let row = ((time_ratio * (grid_rows - 1) as f64) as usize).min(grid_rows - 1);
1349
1350 density_grid[row][col] += 1;
1351 }
1352
1353 let max_density = density_grid
1355 .iter()
1356 .flat_map(|row| row.iter())
1357 .max()
1358 .copied()
1359 .unwrap_or(1);
1360
1361 for (row, row_data) in density_grid.iter().enumerate() {
1363 for (col, &density) in row_data.iter().enumerate() {
1364 let x = heatmap_x + 20 + col * cell_width;
1365 let y = heatmap_y + 20 + row * cell_height;
1366
1367 let intensity = if max_density > 0 {
1369 density as f64 / max_density as f64
1370 } else {
1371 0.0
1372 };
1373
1374 let color = if intensity == 0.0 {
1375 "#f8f9fa".to_string()
1376 } else {
1377 let red = (255.0 * intensity) as u8;
1379 let blue = (255.0 * (1.0 - intensity)) as u8;
1380 format!("rgb({red}, 100, {blue})")
1381 };
1382
1383 let cell = Rectangle::new()
1384 .set("x", x)
1385 .set("y", y)
1386 .set("width", cell_width - 1)
1387 .set("height", cell_height - 1)
1388 .set("fill", color)
1389 .set("stroke", "#bdc3c7")
1390 .set("stroke-width", 0.5)
1391 .set("class", "heatmap-cell");
1392
1393 document = document.add(cell);
1394
1395 if density > 0 {
1397 let density_text = SvgText::new(density.to_string())
1398 .set("x", x + cell_width / 2)
1399 .set("y", y + cell_height / 2 + 3)
1400 .set("text-anchor", "middle")
1401 .set("font-size", 8)
1402 .set("fill", if intensity > 0.5 { "white" } else { "black" });
1403
1404 document = document.add(density_text);
1405 }
1406 }
1407 }
1408
1409 let legend_y = heatmap_y + heatmap_height - 15;
1411 let legend_text = SvgText::new("Size →")
1412 .set("x", heatmap_x + 20)
1413 .set("y", legend_y)
1414 .set("font-size", 10)
1415 .set("fill", "#7f8c8d");
1416 document = document.add(legend_text);
1417
1418 let legend_text2 = SvgText::new("↑ Time")
1419 .set("x", heatmap_x + 10)
1420 .set("y", heatmap_y + 40)
1421 .set("font-size", 10)
1422 .set("fill", "#7f8c8d")
1423 .set(
1424 "transform",
1425 format!("rotate(-90 {} {})", heatmap_x + 10, heatmap_y + 40),
1426 );
1427 document = document.add(legend_text2);
1428
1429 Ok(document)
1430}