1use crate::tracker::MemoryTracker;
4use crate::types::{AllocationInfo, MemoryStats, TrackingResult, TypeMemoryUsage};
5use crate::utils::{format_bytes, get_category_color, simplify_type_name};
6
7fn calculate_allocation_percentiles(allocations: &[AllocationInfo]) -> (usize, usize) {
10 if allocations.is_empty() {
11 return (0, 0);
12 }
13
14 let mut sizes: Vec<usize> = allocations.iter().map(|a| a.size).collect();
16 sizes.sort_unstable();
17
18 let len = sizes.len();
19
20 let median = if len % 2 == 0 {
22 (sizes[len / 2 - 1] + sizes[len / 2]) / 2
23 } else {
24 sizes[len / 2]
25 };
26
27 let p95_index = ((len as f64) * 0.95) as usize;
29 let p95 = if p95_index >= len {
30 sizes[len - 1]
31 } else {
32 sizes[p95_index]
33 };
34
35 (median, p95)
36}
37use std::collections::HashMap;
38use std::fs::File;
39use std::io::Write;
40use std::path::Path;
41use svg::node::element::{Circle, Rectangle, Text as SvgText};
42use svg::Document;
43
44pub fn enhance_type_information(
46 memory_by_type: &[TypeMemoryUsage],
47 allocations: &[AllocationInfo],
48) -> Vec<EnhancedTypeInfo> {
49 let mut enhanced_types = Vec::new();
50 let mut inner_type_stats: std::collections::HashMap<String, (usize, usize, Vec<String>)> =
51 std::collections::HashMap::new();
52
53 for usage in memory_by_type {
54 if usage.type_name == "Unknown" {
56 continue;
57 }
58
59 let (simplified_name, category, subcategory) =
61 analyze_type_with_detailed_subcategory(&usage.type_name);
62
63 let variable_names: Vec<String> = allocations
65 .iter()
66 .filter_map(|alloc| {
67 if let (Some(var_name), Some(type_name)) = (&alloc.var_name, &alloc.type_name) {
68 let (alloc_simplified, _, _) =
69 analyze_type_with_detailed_subcategory(type_name);
70 if alloc_simplified == simplified_name {
71 Some(var_name.clone())
72 } else {
73 None
74 }
75 } else {
76 None
77 }
78 })
79 .take(5) .collect();
81
82 enhanced_types.push(EnhancedTypeInfo {
84 simplified_name: simplified_name.clone(),
85 category: category.clone(),
86 subcategory: subcategory.clone(),
87 total_size: usage.total_size,
88 allocation_count: usage.allocation_count,
89 variable_names,
90 });
91
92 extract_and_accumulate_inner_types_enhanced(
94 &usage.type_name,
95 usage.total_size,
96 usage.allocation_count,
97 &mut inner_type_stats,
98 );
99 }
100
101 for (inner_type, (total_size, allocation_count, var_names)) in inner_type_stats {
103 let (simplified_name, category, subcategory) =
104 analyze_type_with_detailed_subcategory(&inner_type);
105 enhanced_types.push(EnhancedTypeInfo {
108 simplified_name,
109 category,
110 subcategory,
111 total_size,
112 allocation_count,
113 variable_names: var_names.into_iter().take(5).collect(),
114 });
115 }
116
117 enhanced_types
124}
125
126fn analyze_type_with_detailed_subcategory(type_name: &str) -> (String, String, String) {
128 let clean_type = type_name.trim();
129
130 if clean_type.is_empty() || clean_type == "Unknown" {
132 return (
133 "Unknown Type".to_string(),
134 "Unknown".to_string(),
135 "Other".to_string(),
136 );
137 }
138
139 if clean_type.contains("Vec<") || clean_type.contains("vec::Vec") {
141 let inner = extract_generic_inner_type(clean_type, "Vec");
142 return (
143 format!("Vec<{}>", inner),
144 "Collections".to_string(),
145 "Vec<T>".to_string(),
146 );
147 }
148
149 if clean_type.contains("HashMap") || clean_type.contains("hash_map") {
150 return (
151 "HashMap<K,V>".to_string(),
152 "Collections".to_string(),
153 "HashMap<K,V>".to_string(),
154 );
155 }
156
157 if clean_type.contains("HashSet") || clean_type.contains("hash_set") {
158 return (
159 "HashSet<T>".to_string(),
160 "Collections".to_string(),
161 "HashSet<T>".to_string(),
162 );
163 }
164
165 if clean_type.contains("BTreeMap") || clean_type.contains("btree_map") {
166 return (
167 "BTreeMap<K,V>".to_string(),
168 "Collections".to_string(),
169 "BTreeMap<K,V>".to_string(),
170 );
171 }
172
173 if clean_type.contains("BTreeSet") || clean_type.contains("btree_set") {
174 return (
175 "BTreeSet<T>".to_string(),
176 "Collections".to_string(),
177 "BTreeSet<T>".to_string(),
178 );
179 }
180
181 if clean_type.contains("VecDeque") || clean_type.contains("vec_deque") {
182 return (
183 "VecDeque<T>".to_string(),
184 "Collections".to_string(),
185 "VecDeque<T>".to_string(),
186 );
187 }
188
189 if clean_type.contains("LinkedList") {
190 return (
191 "LinkedList<T>".to_string(),
192 "Collections".to_string(),
193 "LinkedList<T>".to_string(),
194 );
195 }
196
197 if clean_type.contains("String") || clean_type.contains("string::String") {
199 return (
200 "String".to_string(),
201 "Basic Types".to_string(),
202 "Strings".to_string(),
203 );
204 }
205
206 if clean_type.contains("&str") || clean_type == "str" {
207 return (
208 "&str".to_string(),
209 "Basic Types".to_string(),
210 "Strings".to_string(),
211 );
212 }
213
214 if clean_type == "i32" || clean_type.ends_with("::i32") {
216 return (
217 "i32".to_string(),
218 "Basic Types".to_string(),
219 "Integers".to_string(),
220 );
221 }
222 if clean_type == "i64" || clean_type.ends_with("::i64") {
223 return (
224 "i64".to_string(),
225 "Basic Types".to_string(),
226 "Integers".to_string(),
227 );
228 }
229 if clean_type == "u32" || clean_type.ends_with("::u32") {
230 return (
231 "u32".to_string(),
232 "Basic Types".to_string(),
233 "Integers".to_string(),
234 );
235 }
236 if clean_type == "u64" || clean_type.ends_with("::u64") {
237 return (
238 "u64".to_string(),
239 "Basic Types".to_string(),
240 "Integers".to_string(),
241 );
242 }
243 if clean_type == "usize" || clean_type.ends_with("::usize") {
244 return (
245 "usize".to_string(),
246 "Basic Types".to_string(),
247 "Integers".to_string(),
248 );
249 }
250 if clean_type == "isize" || clean_type.ends_with("::isize") {
251 return (
252 "isize".to_string(),
253 "Basic Types".to_string(),
254 "Integers".to_string(),
255 );
256 }
257 if clean_type == "i8" || clean_type.ends_with("::i8") {
258 return (
259 "i8".to_string(),
260 "Basic Types".to_string(),
261 "Integers".to_string(),
262 );
263 }
264 if clean_type == "u8" || clean_type.ends_with("::u8") {
265 return (
266 "u8".to_string(),
267 "Basic Types".to_string(),
268 "Integers".to_string(),
269 );
270 }
271 if clean_type == "i16" || clean_type.ends_with("::i16") {
272 return (
273 "i16".to_string(),
274 "Basic Types".to_string(),
275 "Integers".to_string(),
276 );
277 }
278 if clean_type == "u16" || clean_type.ends_with("::u16") {
279 return (
280 "u16".to_string(),
281 "Basic Types".to_string(),
282 "Integers".to_string(),
283 );
284 }
285
286 if clean_type == "f32" || clean_type.ends_with("::f32") {
288 return (
289 "f32".to_string(),
290 "Basic Types".to_string(),
291 "Floats".to_string(),
292 );
293 }
294 if clean_type == "f64" || clean_type.ends_with("::f64") {
295 return (
296 "f64".to_string(),
297 "Basic Types".to_string(),
298 "Floats".to_string(),
299 );
300 }
301
302 if clean_type == "bool" || clean_type.ends_with("::bool") {
304 return (
305 "bool".to_string(),
306 "Basic Types".to_string(),
307 "Booleans".to_string(),
308 );
309 }
310 if clean_type == "char" || clean_type.ends_with("::char") {
311 return (
312 "char".to_string(),
313 "Basic Types".to_string(),
314 "Characters".to_string(),
315 );
316 }
317
318 if clean_type.contains("Box<") {
320 let inner = extract_generic_inner_type(clean_type, "Box");
321 return (
322 format!("Box<{}>", inner),
323 "Smart Pointers".to_string(),
324 "Box<T>".to_string(),
325 );
326 }
327
328 if clean_type.contains("Rc<") {
329 let inner = extract_generic_inner_type(clean_type, "Rc");
330 return (
331 format!("Rc<{}>", inner),
332 "Smart Pointers".to_string(),
333 "Rc<T>".to_string(),
334 );
335 }
336
337 if clean_type.contains("Arc<") {
338 let inner = extract_generic_inner_type(clean_type, "Arc");
339 return (
340 format!("Arc<{}>", inner),
341 "Smart Pointers".to_string(),
342 "Arc<T>".to_string(),
343 );
344 }
345
346 let (simplified_name, category) = simplify_type_name(clean_type);
348 (simplified_name, category.clone(), "Other".to_string())
349}
350
351fn extract_generic_inner_type(type_name: &str, container: &str) -> String {
353 if let Some(start) = type_name.find(&format!("{}<", container)) {
354 let start = start + container.len() + 1;
355 if let Some(end) = type_name[start..].rfind('>') {
356 let inner = &type_name[start..start + end];
357 return inner.split("::").last().unwrap_or(inner).to_string();
358 }
359 }
360 "?".to_string()
361}
362
363fn extract_and_accumulate_inner_types_enhanced(
365 type_name: &str,
366 size: usize,
367 count: usize,
368 stats: &mut std::collections::HashMap<String, (usize, usize, Vec<String>)>,
369) {
370 let inner_types = extract_inner_primitive_types_enhanced(type_name);
372
373 for inner_type in inner_types {
379 let entry = stats
380 .entry(inner_type.clone())
381 .or_insert((0, 0, Vec::new()));
382 entry.0 += size / 4; entry.1 += count;
384 entry.2.push(format!("from {}", type_name));
385 }
387}
388
389fn extract_inner_primitive_types_enhanced(type_name: &str) -> Vec<String> {
391 let mut inner_types = Vec::new();
392
393 let primitives = [
395 "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize",
396 "f32", "f64", "bool", "char",
397 ];
398
399 for primitive in &primitives {
400 if type_name.contains(primitive) {
401 if type_name.contains(&format!("{}>", primitive))
403 || type_name.contains(&format!("{},", primitive))
404 || type_name.contains(&format!(" {}", primitive))
405 || type_name.ends_with(primitive)
406 {
407 inner_types.push(primitive.to_string());
408 }
409 }
410 }
411
412 inner_types
413}
414
415pub fn categorize_allocations(allocations: &[AllocationInfo]) -> Vec<AllocationCategory> {
417 let mut categories: HashMap<String, AllocationCategory> = HashMap::new();
418
419 for allocation in allocations {
420 if allocation.var_name.is_none()
422 || allocation.type_name.as_ref().is_none_or(|t| t == "Unknown")
423 {
424 continue;
425 }
426
427 let type_name = allocation.type_name.as_ref().unwrap();
428 let (_, category_name) = simplify_type_name(type_name);
429
430 let category =
431 categories
432 .entry(category_name.clone())
433 .or_insert_with(|| AllocationCategory {
434 name: category_name.clone(),
435 allocations: Vec::new(),
436 total_size: 0,
437 color: get_category_color(&category_name),
438 });
439
440 category.allocations.push(allocation.clone());
441 category.total_size += allocation.size;
442 }
443
444 let mut result: Vec<_> = categories.into_values().collect();
445 result.sort_by(|a, b| b.total_size.cmp(&a.total_size));
446 result
447}
448
449pub fn categorize_enhanced_allocations(
452 enhanced_types: &[EnhancedTypeInfo],
453) -> Vec<AllocationCategory> {
454 let mut categories: HashMap<String, AllocationCategory> = HashMap::new();
455
456 for enhanced_type in enhanced_types {
457 if enhanced_type.simplified_name == "Unknown" {
459 continue;
460 }
461
462 let category_name = &enhanced_type.category;
463
464 let category =
465 categories
466 .entry(category_name.clone())
467 .or_insert_with(|| AllocationCategory {
468 name: category_name.clone(),
469 allocations: Vec::new(),
470 total_size: 0,
471 color: get_category_color(category_name),
472 });
473
474 let mut synthetic_allocation = AllocationInfo::new(0, enhanced_type.total_size);
476 synthetic_allocation.var_name = Some(enhanced_type.variable_names.join(", "));
477 synthetic_allocation.type_name = Some(enhanced_type.simplified_name.clone());
478
479 category.allocations.push(synthetic_allocation);
480 category.total_size += enhanced_type.total_size;
481 }
482
483 let mut result: Vec<_> = categories.into_values().collect();
484 result.sort_by(|a, b| b.total_size.cmp(&a.total_size));
485 result
486}
487
488#[derive(Debug, Clone)]
490pub struct EnhancedTypeInfo {
491 pub simplified_name: String,
493 pub category: String,
495 pub subcategory: String,
497 pub total_size: usize,
499 pub allocation_count: usize,
501 pub variable_names: Vec<String>,
503}
504
505#[derive(Debug, Clone)]
507pub struct AllocationCategory {
508 pub name: String,
510 pub allocations: Vec<AllocationInfo>,
512 pub total_size: usize,
514 pub color: String,
516}
517
518pub fn export_enhanced_svg<P: AsRef<Path>>(tracker: &MemoryTracker, path: P) -> TrackingResult<()> {
520 let path = path.as_ref();
521
522 if let Some(parent) = path.parent() {
524 if !parent.exists() {
525 std::fs::create_dir_all(parent)?;
526 }
527 }
528
529 let active_allocations = tracker.get_active_allocations()?;
530 let memory_by_type = tracker.get_memory_by_type()?;
531 let stats = tracker.get_stats()?;
532
533 let enhanced_memory_by_type = enhance_type_information(&memory_by_type, &active_allocations);
535 let _categorized_allocations = categorize_allocations(&active_allocations);
536
537 let mut document = Document::new()
539 .set("viewBox", (0, 0, 1800, 400))
540 .set("width", 1800)
541 .set("height", 400)
542 .set(
543 "style",
544 "background: linear-gradient(135deg, #2C3E50 0%, #34495E 100%); font-family: 'Segoe UI', Arial, sans-serif;",
545 );
546
547 let title = SvgText::new("Top 3 Memory Analysis - Compact View")
551 .set("x", 700)
552 .set("y", 30)
553 .set("text-anchor", "middle")
554 .set("font-size", 24)
555 .set("font-weight", "bold")
556 .set("fill", "#FFFFFF");
557 document = document.add(title);
558
559 if !enhanced_memory_by_type.is_empty() {
561 document = add_compact_type_chart(document, &enhanced_memory_by_type)?;
562 }
563
564 document = add_compact_summary(document, &stats, &active_allocations)?;
566
567 let mut file = File::create(path)?;
569 write!(file, "{document}")?;
570
571 Ok(())
572}
573
574fn add_compact_type_chart(
576 mut document: Document,
577 types: &[EnhancedTypeInfo],
578) -> TrackingResult<Document> {
579 let chart_x = 100;
580 let chart_y = 60;
581 let chart_width = 1200;
582
583 if types.is_empty() {
584 let no_data = SvgText::new("No tracked variables found")
585 .set("x", chart_x + chart_width / 2)
586 .set("y", 200)
587 .set("text-anchor", "middle")
588 .set("font-size", 16)
589 .set("fill", "#E74C3C");
590 document = document.add(no_data);
591 return Ok(document);
592 }
593
594 let max_size = types.iter().map(|t| t.total_size).max().unwrap_or(1);
595
596 for (i, type_info) in types.iter().take(3).enumerate() {
598 let y = chart_y + (i as i32) * 80;
599
600 let bg_bar = Rectangle::new()
602 .set("x", chart_x)
603 .set("y", y)
604 .set("width", 600)
605 .set("height", 30)
606 .set("fill", "#34495E")
607 .set("stroke", "#ECF0F1")
608 .set("stroke-width", 1)
609 .set("rx", 6);
610 document = document.add(bg_bar);
611
612 let bar_width = ((type_info.total_size as f64 / max_size as f64) * 600.0) as i32;
614 let color = get_category_color(&type_info.category);
615 let progress_bar = Rectangle::new()
616 .set("x", chart_x)
617 .set("y", y)
618 .set("width", bar_width)
619 .set("height", 30)
620 .set("fill", color)
621 .set("rx", 6);
622 document = document.add(progress_bar);
623
624 let content_text = format!(
626 "{} ({} vars) | Total: {}",
627 type_info.simplified_name,
628 type_info.allocation_count,
629 format_bytes(type_info.total_size)
630 );
631
632 let content_label = SvgText::new(content_text)
633 .set("x", chart_x + 10)
634 .set("y", y + 20)
635 .set("font-size", 12)
636 .set("font-weight", "600")
637 .set("fill", "#FFFFFF")
638 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
639 document = document.add(content_label);
640
641 let percentage = (type_info.total_size as f64 / max_size as f64 * 100.0) as i32;
643 let percent_label = SvgText::new(format!("{percentage}%"))
644 .set("x", chart_x + 720)
645 .set("y", y + 20)
646 .set("font-size", 14)
647 .set("font-weight", "bold")
648 .set("fill", "#ECF0F1");
649 document = document.add(percent_label);
650
651 let var_names_text = if type_info.variable_names.is_empty() {
653 "no tracked vars".to_string()
654 } else {
655 format!("Variables: {}", type_info.variable_names.join(", "))
656 };
657
658 let vars_label = SvgText::new(var_names_text)
659 .set("x", chart_x + 10)
660 .set("y", y + 45)
661 .set("font-size", 9)
662 .set("fill", "#94A3B8")
663 .set("font-style", "italic");
664 document = document.add(vars_label);
665 }
666
667 Ok(document)
668}
669
670fn add_compact_summary(
672 mut document: Document,
673 stats: &MemoryStats,
674 allocations: &[AllocationInfo],
675) -> TrackingResult<Document> {
676 let tracked_vars = allocations.iter().filter(|a| a.var_name.is_some()).count();
677
678 let summary_text = format!(
679 "Showing TOP 3 memory-consuming types | Total tracked: {} variables | Active memory: {}",
680 tracked_vars,
681 format_bytes(stats.active_memory)
682 );
683
684 let summary = SvgText::new(summary_text)
685 .set("x", 700)
686 .set("y", 370)
687 .set("text-anchor", "middle")
688 .set("font-size", 14)
689 .set("font-weight", "bold")
690 .set("fill", "#ECF0F1");
691 document = document.add(summary);
692
693 Ok(document)
694}
695
696pub fn add_enhanced_header(
698 mut document: Document,
699 stats: &MemoryStats,
700 allocations: &[AllocationInfo],
701) -> TrackingResult<Document> {
702 let title = SvgText::new("Rust Memory Usage Analysis")
704 .set("x", 900)
705 .set("y", 40)
706 .set("text-anchor", "middle")
707 .set("font-size", 28)
708 .set("font-weight", "300")
709 .set("fill", "#2c3e50")
710 .set("style", "letter-spacing: 1px;");
711
712 document = document.add(title);
713
714 let active_memory = stats.active_memory;
716 let peak_memory = stats.peak_memory;
717 let active_allocations = stats.active_allocations;
718
719 let memory_reclamation_rate = if stats.total_allocated > 0 {
720 (stats.total_deallocated as f64 / stats.total_allocated as f64) * 100.0
721 } else {
722 0.0
723 };
724
725 let allocator_efficiency = if stats.peak_memory > 0 {
726 (stats.active_memory as f64 / stats.peak_memory as f64) * 100.0
727 } else {
728 0.0
729 };
730
731 let (median_alloc_size, p95_alloc_size) = calculate_allocation_percentiles(allocations);
732
733 let memory_fragmentation = if stats.peak_memory > 0 {
734 ((stats.peak_memory - stats.active_memory) as f64 / stats.peak_memory as f64) * 100.0
735 } else {
736 0.0
737 };
738
739 let metrics = [
741 (
742 "Active Memory",
743 format_bytes(active_memory),
744 (active_memory as f64 / peak_memory.max(1) as f64 * 100.0).min(100.0),
745 "#3498db",
746 ),
747 ("Peak Memory", format_bytes(peak_memory), 100.0, "#e74c3c"),
748 (
749 "Active Allocs",
750 format!("{}", active_allocations),
751 (active_allocations as f64 / 1000.0 * 100.0).min(100.0),
752 "#2ecc71",
753 ),
754 (
755 "Reclamation",
756 format!("{:.1}%", memory_reclamation_rate),
757 memory_reclamation_rate,
758 "#f39c12",
759 ),
760 (
761 "Efficiency",
762 format!("{:.1}%", allocator_efficiency),
763 allocator_efficiency,
764 "#9b59b6",
765 ),
766 (
767 "Median Size",
768 format_bytes(median_alloc_size),
769 (median_alloc_size as f64 / 1024.0 * 100.0).min(100.0),
770 "#1abc9c",
771 ),
772 (
773 "P95 Size",
774 format_bytes(p95_alloc_size),
775 (p95_alloc_size as f64 / 4096.0 * 100.0).min(100.0),
776 "#e67e22",
777 ),
778 (
779 "Fragmentation",
780 format!("{:.1}%", memory_fragmentation),
781 memory_fragmentation,
782 "#95a5a6",
783 ),
784 ];
785
786 let card_width = 200;
788 let card_height = 120;
789 let start_x = 50;
790 let start_y = 130;
791 let spacing_x = 220;
792
793 let header = SvgText::new("KEY PERFORMANCE METRICS")
795 .set("x", 900)
796 .set("y", start_y - 20)
797 .set("text-anchor", "middle")
798 .set("font-size", 11)
799 .set("font-weight", "600")
800 .set("fill", "#7f8c8d")
801 .set("style", "letter-spacing: 2px;");
802 document = document.add(header);
803
804 for (i, (title, value, percentage, color)) in metrics.iter().enumerate() {
806 let x = start_x + i * spacing_x;
807 let y = start_y;
808
809 let card_bg = Rectangle::new()
811 .set("x", x)
812 .set("y", y)
813 .set("width", card_width)
814 .set("height", card_height)
815 .set("fill", "#ffffff")
816 .set("stroke", "none")
817 .set("rx", 12)
818 .set("style", "filter: drop-shadow(0 4px 12px rgba(0,0,0,0.15));");
819
820 if i == 0 {
822 }
824
825 document = document.add(card_bg);
826
827 let ring_center_x = x + 40;
829 let ring_center_y = y + 60;
830 let ring_radius = 25;
831
832 let ring_bg = Circle::new()
833 .set("cx", ring_center_x)
834 .set("cy", ring_center_y)
835 .set("r", ring_radius)
836 .set("fill", "none")
837 .set("stroke", "#ecf0f1")
838 .set("stroke-width", 6);
839 document = document.add(ring_bg);
840
841 let circumference = 2.0 * std::f64::consts::PI * ring_radius as f64;
843 let progress_offset = circumference * (1.0 - percentage / 100.0);
844
845 let progress_ring = Circle::new()
846 .set("cx", ring_center_x)
847 .set("cy", ring_center_y)
848 .set("r", ring_radius)
849 .set("fill", "none")
850 .set("stroke", *color)
851 .set("stroke-width", 6)
852 .set("stroke-linecap", "round")
853 .set(
854 "stroke-dasharray",
855 format!("{} {}", circumference, circumference),
856 )
857 .set("stroke-dashoffset", progress_offset)
858 .set(
859 "transform",
860 format!("rotate(-90 {} {})", ring_center_x, ring_center_y),
861 )
862 .set("style", "transition: stroke-dashoffset 0.5s ease;");
863 document = document.add(progress_ring);
864
865 let percent_text = SvgText::new(format!("{:.0}%", percentage))
867 .set("x", ring_center_x)
868 .set("y", ring_center_y + 4)
869 .set("text-anchor", "middle")
870 .set("font-size", 12)
871 .set("font-weight", "bold")
872 .set("fill", *color);
873 document = document.add(percent_text);
874
875 let title_text = SvgText::new(*title)
877 .set("x", x + 90)
878 .set("y", y + 35)
879 .set("font-size", 12)
880 .set("font-weight", "600")
881 .set("fill", "#2c3e50");
882 document = document.add(title_text);
883
884 let value_text = SvgText::new(value)
886 .set("x", x + 90)
887 .set("y", y + 55)
888 .set("font-size", 16)
889 .set("font-weight", "bold")
890 .set("fill", "#2c3e50");
891 document = document.add(value_text);
892
893 let status_color = if *percentage >= 80.0 {
895 "#e74c3c" } else if *percentage >= 50.0 {
897 "#f39c12" } else {
899 "#27ae60" };
901
902 let status_dot = Circle::new()
903 .set("cx", x + 90)
904 .set("cy", y + 75)
905 .set("r", 4)
906 .set("fill", status_color);
907 document = document.add(status_dot);
908
909 let status_text = if *percentage >= 80.0 {
911 "HIGH"
912 } else if *percentage >= 50.0 {
913 "MEDIUM"
914 } else {
915 "OPTIMAL"
916 };
917
918 let status_label = SvgText::new(status_text)
919 .set("x", x + 105)
920 .set("y", y + 79)
921 .set("font-size", 9)
922 .set("font-weight", "600")
923 .set("fill", status_color);
924 document = document.add(status_label);
925 }
926
927 Ok(document)
928}
929
930pub fn add_enhanced_type_chart(
932 mut document: Document,
933 types: &[EnhancedTypeInfo],
934) -> TrackingResult<Document> {
935 let chart_x = 50;
937 let chart_y = 720; let chart_width = 850;
939 let chart_height = 300; let title = SvgText::new("Memory Usage by Type - Treemap Visualization")
943 .set("x", chart_x + chart_width / 2)
944 .set("y", chart_y - 10)
945 .set("text-anchor", "middle")
946 .set("font-size", 16)
947 .set("font-weight", "bold")
948 .set("fill", "#2c3e50");
949
950 document = document.add(title);
951
952 let styles = svg::node::element::Style::new(
954 r#"
955 .integrated-treemap-rect {
956 transition: all 0.3s ease;
957 cursor: pointer;
958 stroke: #ffffff;
959 stroke-width: 2;
960 }
961 .integrated-treemap-rect:hover {
962 stroke: #2c3e50;
963 stroke-width: 3;
964 filter: brightness(1.1);
965 }
966 .integrated-treemap-label {
967 fill: #ffffff;
968 font-weight: 700;
969 text-anchor: middle;
970 dominant-baseline: middle;
971 pointer-events: none;
972 text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
973 }
974 .integrated-treemap-percentage {
975 fill: #f8f9fa;
976 font-weight: 600;
977 text-anchor: middle;
978 dominant-baseline: middle;
979 pointer-events: none;
980 text-shadow: 1px 1px 2px rgba(0,0,0,0.6);
981 }
982 "#,
983 );
984 document = document.add(styles);
985
986 let treemap_area = IntegratedTreemapArea {
988 x: chart_x as f64,
989 y: chart_y as f64,
990 width: chart_width as f64,
991 height: chart_height as f64,
992 };
993
994 document = render_real_data_treemap(document, treemap_area, types)?;
996
997 Ok(document)
1000}
1001
1002#[derive(Debug, Clone)]
1004struct IntegratedTreemapArea {
1005 x: f64,
1006 y: f64,
1007 width: f64,
1008 height: f64,
1009}
1010
1011fn render_real_data_treemap(
1015 mut document: Document,
1016 area: IntegratedTreemapArea,
1017 types: &[EnhancedTypeInfo],
1018) -> TrackingResult<Document> {
1019 if types.is_empty() {
1020 let no_data_rect = Rectangle::new()
1021 .set("x", area.x)
1022 .set("y", area.y)
1023 .set("width", area.width)
1024 .set("height", area.height)
1025 .set("fill", "#f8f9fa")
1026 .set("stroke", "#dee2e6")
1027 .set("stroke-width", 2)
1028 .set("rx", 10);
1029 document = document.add(no_data_rect);
1030
1031 let no_data_text = SvgText::new("No Memory Type Data Available")
1032 .set("x", area.x + area.width / 2.0)
1033 .set("y", area.y + area.height / 2.0)
1034 .set("text-anchor", "middle")
1035 .set("font-size", 16)
1036 .set("font-weight", "bold")
1037 .set("fill", "#6c757d");
1038 document = document.add(no_data_text);
1039
1040 return Ok(document);
1041 }
1042
1043 let total_memory: usize = types.iter().map(|t| t.total_size).sum();
1045 if total_memory == 0 {
1046 return Ok(document);
1047 }
1048
1049 let mut collections_types = Vec::new();
1051 let mut basic_types = Vec::new();
1052 let mut smart_pointers_types = Vec::new();
1053 let mut other_types = Vec::new();
1054
1055 for type_info in types {
1056 match type_info.category.as_str() {
1057 "Collections" => collections_types.push(type_info),
1058 "Basic Types" => basic_types.push(type_info),
1059 "Strings" => basic_types.push(type_info), "Smart Pointers" => smart_pointers_types.push(type_info),
1061 _ => other_types.push(type_info),
1062 }
1063 }
1064
1065 let _collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1067 let _basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1068 let _smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1069 let _other_total: usize = other_types.iter().map(|t| t.total_size).sum();
1070
1071 let layout_strategy = analyze_data_distribution(
1073 &collections_types,
1074 &basic_types,
1075 &smart_pointers_types,
1076 &other_types,
1077 total_memory,
1078 );
1079
1080 let treemap_data = build_adaptive_treemap_data(
1082 collections_types,
1083 basic_types,
1084 smart_pointers_types,
1085 other_types,
1086 total_memory,
1087 &layout_strategy,
1088 );
1089
1090 document = render_adaptive_treemap(document, area, &treemap_data, &layout_strategy)?;
1092
1093 Ok(document)
1094}
1095
1096#[derive(Debug, Clone)]
1098struct TreemapNode {
1099 name: String,
1100 size: usize,
1101 percentage: f64,
1102 color: String,
1103 children: Vec<TreemapNode>,
1104}
1105
1106#[derive(Debug, Clone)]
1108enum TreemapLayoutStrategy {
1109 FullLayout,
1111 DominantCategoryLayout { dominant_category: String },
1113 MinimalLayout,
1115 CollectionsOnlyLayout,
1117 BasicTypesOnlyLayout,
1119}
1120
1121fn analyze_data_distribution(
1123 collections_types: &[&EnhancedTypeInfo],
1124 basic_types: &[&EnhancedTypeInfo],
1125 smart_pointers_types: &[&EnhancedTypeInfo],
1126 _other_types: &[&EnhancedTypeInfo],
1127 total_memory: usize,
1128) -> TreemapLayoutStrategy {
1129 let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1130 let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1131 let smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1132
1133 let collections_percentage = if total_memory > 0 {
1134 (collections_total as f64 / total_memory as f64) * 100.0
1135 } else {
1136 0.0
1137 };
1138 let basic_types_percentage = if total_memory > 0 {
1139 (basic_types_total as f64 / total_memory as f64) * 100.0
1140 } else {
1141 0.0
1142 };
1143 let smart_pointers_percentage = if total_memory > 0 {
1144 (smart_pointers_total as f64 / total_memory as f64) * 100.0
1145 } else {
1146 0.0
1147 };
1148
1149 let active_categories = [
1151 (collections_total > 0, "Collections"),
1152 (basic_types_total > 0, "Basic Types"),
1153 (smart_pointers_total > 0, "Smart Pointers"),
1154 ]
1155 .iter()
1156 .filter(|(active, _)| *active)
1157 .count();
1158
1159 match active_categories {
1160 0 => TreemapLayoutStrategy::MinimalLayout,
1161 1 => {
1162 if collections_percentage > 80.0 {
1164 TreemapLayoutStrategy::CollectionsOnlyLayout
1165 } else if basic_types_percentage > 80.0 {
1166 TreemapLayoutStrategy::BasicTypesOnlyLayout
1167 } else {
1168 TreemapLayoutStrategy::DominantCategoryLayout {
1169 dominant_category: if collections_total > basic_types_total
1170 && collections_total > smart_pointers_total
1171 {
1172 "Collections".to_string()
1173 } else if basic_types_total > smart_pointers_total {
1174 "Basic Types".to_string()
1175 } else {
1176 "Smart Pointers".to_string()
1177 },
1178 }
1179 }
1180 }
1181 2 => {
1182 if collections_percentage > 70.0
1184 || basic_types_percentage > 70.0
1185 || smart_pointers_percentage > 70.0
1186 {
1187 TreemapLayoutStrategy::DominantCategoryLayout {
1188 dominant_category: if collections_total > basic_types_total
1189 && collections_total > smart_pointers_total
1190 {
1191 "Collections".to_string()
1192 } else if basic_types_total > smart_pointers_total {
1193 "Basic Types".to_string()
1194 } else {
1195 "Smart Pointers".to_string()
1196 },
1197 }
1198 } else {
1199 TreemapLayoutStrategy::FullLayout
1200 }
1201 }
1202 _ => TreemapLayoutStrategy::FullLayout, }
1204}
1205
1206fn build_adaptive_treemap_data(
1208 collections_types: Vec<&EnhancedTypeInfo>,
1209 basic_types: Vec<&EnhancedTypeInfo>,
1210 smart_pointers_types: Vec<&EnhancedTypeInfo>,
1211 _other_types: Vec<&EnhancedTypeInfo>,
1212 total_memory: usize,
1213 strategy: &TreemapLayoutStrategy,
1214) -> Vec<TreemapNode> {
1215 let mut treemap_nodes = Vec::new();
1216
1217 match strategy {
1218 TreemapLayoutStrategy::MinimalLayout => {
1219 treemap_nodes.push(TreemapNode {
1221 name: "No Significant Memory Usage".to_string(),
1222 size: total_memory.max(1),
1223 percentage: 100.0,
1224 color: "#95a5a6".to_string(),
1225 children: Vec::new(),
1226 });
1227 }
1228 TreemapLayoutStrategy::CollectionsOnlyLayout => {
1229 let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1231 if collections_total > 0 {
1232 treemap_nodes.push(build_collections_node(&collections_types, total_memory));
1233 }
1234 }
1235 TreemapLayoutStrategy::BasicTypesOnlyLayout => {
1236 let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1238 if basic_types_total > 0 {
1239 treemap_nodes.push(build_basic_types_node(&basic_types, total_memory));
1240 }
1241 }
1242 TreemapLayoutStrategy::DominantCategoryLayout { dominant_category } => {
1243 match dominant_category.as_str() {
1245 "Collections" => {
1246 let collections_total: usize =
1247 collections_types.iter().map(|t| t.total_size).sum();
1248 if collections_total > 0 {
1249 treemap_nodes
1250 .push(build_collections_node(&collections_types, total_memory));
1251 }
1252 add_simple_categories(
1254 &mut treemap_nodes,
1255 &basic_types,
1256 &smart_pointers_types,
1257 total_memory,
1258 );
1259 }
1260 "Basic Types" => {
1261 let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1262 if basic_types_total > 0 {
1263 treemap_nodes.push(build_basic_types_node(&basic_types, total_memory));
1264 }
1265 add_simple_categories_alt(
1267 &mut treemap_nodes,
1268 &collections_types,
1269 &smart_pointers_types,
1270 total_memory,
1271 );
1272 }
1273 _ => {
1274 build_full_layout(
1276 &mut treemap_nodes,
1277 &collections_types,
1278 &basic_types,
1279 &smart_pointers_types,
1280 total_memory,
1281 );
1282 }
1283 }
1284 }
1285 TreemapLayoutStrategy::FullLayout => {
1286 build_full_layout(
1288 &mut treemap_nodes,
1289 &collections_types,
1290 &basic_types,
1291 &smart_pointers_types,
1292 total_memory,
1293 );
1294 }
1295 }
1296
1297 treemap_nodes
1298}
1299
1300fn build_collections_node(
1302 collections_types: &[&EnhancedTypeInfo],
1303 total_memory: usize,
1304) -> TreemapNode {
1305 let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1306
1307 if collections_total == 0 {
1312 return TreemapNode {
1313 name: "Collections".to_string(),
1314 size: 1,
1315 percentage: 0.0,
1316 color: "#ecf0f1".to_string(),
1317 children: Vec::new(),
1318 };
1319 }
1320
1321 let mut subcategory_groups: std::collections::HashMap<String, Vec<&EnhancedTypeInfo>> =
1323 std::collections::HashMap::new();
1324
1325 for collection_type in collections_types {
1326 subcategory_groups
1327 .entry(collection_type.subcategory.clone())
1328 .or_insert_with(Vec::new)
1329 .push(collection_type);
1330 }
1331
1332 let mut collections_children = Vec::new();
1333
1334 let subcategory_colors = [
1336 ("Vec<T>", "#e74c3c"),
1337 ("HashMap<K,V>", "#3498db"),
1338 ("HashSet<T>", "#9b59b6"),
1339 ("BTreeMap<K,V>", "#2ecc71"),
1340 ("BTreeSet<T>", "#27ae60"),
1341 ("VecDeque<T>", "#f39c12"),
1342 ("LinkedList<T>", "#e67e22"),
1343 ("Other", "#95a5a6"),
1344 ]
1345 .iter()
1346 .cloned()
1347 .collect::<std::collections::HashMap<&str, &str>>();
1348
1349 for (subcategory, types) in subcategory_groups {
1350 let category_total: usize = types.iter().map(|t| t.total_size).sum();
1351
1352 if category_total > 0 {
1360 let relative_percentage = (category_total as f64 / collections_total as f64) * 100.0;
1361 let color = subcategory_colors
1362 .get(subcategory.as_str())
1363 .unwrap_or(&"#95a5a6")
1364 .to_string();
1365
1366 collections_children.push(TreemapNode {
1367 name: format!("{} ({:.1}%)", subcategory, relative_percentage),
1368 size: category_total,
1369 percentage: (category_total as f64 / total_memory as f64) * 100.0,
1370 color,
1371 children: Vec::new(),
1372 });
1373 }
1374 }
1375
1376 collections_children.sort_by(|a, b| b.size.cmp(&a.size));
1378
1379 TreemapNode {
1380 name: format!(
1381 "Collections ({:.1}%)",
1382 (collections_total as f64 / total_memory as f64) * 100.0
1383 ),
1384 size: collections_total,
1385 percentage: (collections_total as f64 / total_memory as f64) * 100.0,
1386 color: "#ecf0f1".to_string(),
1387 children: collections_children,
1388 }
1389}
1390
1391fn build_basic_types_node(basic_types: &[&EnhancedTypeInfo], total_memory: usize) -> TreemapNode {
1393 let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1394
1395 if basic_types_total == 0 {
1402 return TreemapNode {
1403 name: "Basic Types".to_string(),
1404 size: 1,
1405 percentage: 0.0,
1406 color: "#ecf0f1".to_string(),
1407 children: Vec::new(),
1408 };
1409 }
1410
1411 let mut subcategory_groups: std::collections::HashMap<String, Vec<&EnhancedTypeInfo>> =
1413 std::collections::HashMap::new();
1414
1415 for basic_type in basic_types {
1416 subcategory_groups
1417 .entry(basic_type.subcategory.clone())
1418 .or_insert_with(Vec::new)
1419 .push(basic_type);
1420 }
1421
1422 let mut basic_types_children = Vec::new();
1423
1424 let subcategory_colors = [
1426 ("Strings", "#2ecc71"),
1427 ("Integers", "#3498db"),
1428 ("Floats", "#e74c3c"),
1429 ("Booleans", "#f39c12"),
1430 ("Characters", "#9b59b6"),
1431 ("Arrays", "#1abc9c"),
1432 ("References", "#e67e22"),
1433 ("Other", "#95a5a6"),
1434 ]
1435 .iter()
1436 .cloned()
1437 .collect::<std::collections::HashMap<&str, &str>>();
1438
1439 for (subcategory, types) in subcategory_groups {
1440 let category_total: usize = types.iter().map(|t| t.total_size).sum();
1441
1442 if category_total > 0 {
1450 let relative_percentage = (category_total as f64 / basic_types_total as f64) * 100.0;
1451 let color = subcategory_colors
1452 .get(subcategory.as_str())
1453 .unwrap_or(&"#95a5a6")
1454 .to_string();
1455
1456 basic_types_children.push(TreemapNode {
1457 name: format!("{} ({:.1}%)", subcategory, relative_percentage),
1458 size: category_total,
1459 percentage: (category_total as f64 / total_memory as f64) * 100.0,
1460 color,
1461 children: Vec::new(),
1462 });
1463 }
1464 }
1465
1466 basic_types_children.sort_by(|a, b| b.size.cmp(&a.size));
1468
1469 TreemapNode {
1476 name: format!(
1477 "Basic Types ({:.1}%)",
1478 (basic_types_total as f64 / total_memory as f64) * 100.0
1479 ),
1480 size: basic_types_total,
1481 percentage: (basic_types_total as f64 / total_memory as f64) * 100.0,
1482 color: "#ecf0f1".to_string(),
1483 children: basic_types_children,
1484 }
1485}
1486
1487fn add_simple_categories(
1489 treemap_nodes: &mut Vec<TreemapNode>,
1490 basic_types: &[&EnhancedTypeInfo],
1491 smart_pointers_types: &[&EnhancedTypeInfo],
1492 total_memory: usize,
1493) {
1494 let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1495 if basic_types_total > 0 {
1496 treemap_nodes.push(TreemapNode {
1497 name: "Basic Types".to_string(),
1498 size: basic_types_total,
1499 percentage: (basic_types_total as f64 / total_memory as f64) * 100.0,
1500 color: "#f39c12".to_string(),
1501 children: Vec::new(),
1502 });
1503 }
1504
1505 let smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1506 if smart_pointers_total > 0 {
1507 treemap_nodes.push(TreemapNode {
1508 name: "Smart Pointers".to_string(),
1509 size: smart_pointers_total,
1510 percentage: (smart_pointers_total as f64 / total_memory as f64) * 100.0,
1511 color: "#9b59b6".to_string(),
1512 children: Vec::new(),
1513 });
1514 }
1515}
1516
1517fn add_simple_categories_alt(
1519 treemap_nodes: &mut Vec<TreemapNode>,
1520 collections_types: &[&EnhancedTypeInfo],
1521 smart_pointers_types: &[&EnhancedTypeInfo],
1522 total_memory: usize,
1523) {
1524 let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1525 if collections_total > 0 {
1526 treemap_nodes.push(TreemapNode {
1527 name: "Collections".to_string(),
1528 size: collections_total,
1529 percentage: (collections_total as f64 / total_memory as f64) * 100.0,
1530 color: "#3498db".to_string(),
1531 children: Vec::new(),
1532 });
1533 }
1534
1535 let smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1536 if smart_pointers_total > 0 {
1537 treemap_nodes.push(TreemapNode {
1538 name: "Smart Pointers".to_string(),
1539 size: smart_pointers_total,
1540 percentage: (smart_pointers_total as f64 / total_memory as f64) * 100.0,
1541 color: "#9b59b6".to_string(),
1542 children: Vec::new(),
1543 });
1544 }
1545}
1546
1547fn build_full_layout(
1549 treemap_nodes: &mut Vec<TreemapNode>,
1550 collections_types: &[&EnhancedTypeInfo],
1551 basic_types: &[&EnhancedTypeInfo],
1552 smart_pointers_types: &[&EnhancedTypeInfo],
1553 total_memory: usize,
1554) {
1555 let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1556 if collections_total > 0 {
1557 treemap_nodes.push(build_collections_node(collections_types, total_memory));
1558 }
1559
1560 let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1561 if basic_types_total > 0 {
1562 treemap_nodes.push(build_basic_types_node(basic_types, total_memory));
1563 }
1564
1565 let smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1566 if smart_pointers_total > 0 {
1567 treemap_nodes.push(TreemapNode {
1568 name: "Smart Pointers".to_string(),
1569 size: smart_pointers_total,
1570 percentage: (smart_pointers_total as f64 / total_memory as f64) * 100.0,
1571 color: "#9b59b6".to_string(),
1572 children: Vec::new(),
1573 });
1574 }
1575}
1576
1577fn render_adaptive_treemap(
1579 document: Document,
1580 area: IntegratedTreemapArea,
1581 treemap_data: &[TreemapNode],
1582 _strategy: &TreemapLayoutStrategy,
1583) -> TrackingResult<Document> {
1584 render_squarified_treemap(document, area, treemap_data)
1586}
1587
1588fn render_squarified_treemap(
1590 mut document: Document,
1591 area: IntegratedTreemapArea,
1592 nodes: &[TreemapNode],
1593) -> TrackingResult<Document> {
1594 if nodes.is_empty() {
1595 return render_empty_treemap(document, area);
1596 }
1597
1598 let container_bg = Rectangle::new()
1600 .set("class", "integrated-treemap-container")
1601 .set("x", area.x)
1602 .set("y", area.y)
1603 .set("width", area.width)
1604 .set("height", area.height)
1605 .set("fill", "#ecf0f1")
1606 .set("stroke", "#bdc3c7")
1607 .set("stroke-width", 2)
1608 .set("rx", 20);
1609 document = document.add(container_bg);
1610
1611 let total_size: usize = nodes.iter().map(|n| n.size).sum();
1613 if total_size == 0 {
1614 return render_empty_treemap(document, area);
1615 }
1616
1617 let padding = 15.0;
1619 let band_spacing = 10.0;
1620 let available_width = area.width - (padding * 2.0);
1621 let available_height =
1622 area.height - (padding * 2.0) - (band_spacing * (nodes.len() - 1) as f64);
1623
1624 let mut current_y = area.y + padding;
1625
1626 let mut sorted_nodes = nodes.to_vec();
1628 sorted_nodes.sort_by(|a, b| b.size.cmp(&a.size));
1629
1630 for node in &sorted_nodes {
1631 let band_height = (node.size as f64 / total_size as f64) * available_height;
1633
1634 if !node.children.is_empty() {
1636 document = render_smart_horizontal_band(
1637 document,
1638 area.x + padding,
1639 current_y,
1640 available_width,
1641 band_height,
1642 node,
1643 )?;
1644 } else {
1645 document = render_simple_horizontal_band(
1646 document,
1647 area.x + padding,
1648 current_y,
1649 available_width,
1650 band_height,
1651 node,
1652 )?;
1653 }
1654
1655 current_y += band_height + band_spacing;
1656 }
1657
1658 Ok(document)
1661}
1662
1663fn render_empty_treemap(
1665 mut document: Document,
1666 area: IntegratedTreemapArea,
1667) -> TrackingResult<Document> {
1668 let container_bg = Rectangle::new()
1669 .set("class", "integrated-treemap-container")
1670 .set("x", area.x)
1671 .set("y", area.y)
1672 .set("width", area.width)
1673 .set("height", area.height)
1674 .set("fill", "#f8f9fa")
1675 .set("stroke", "#dee2e6")
1676 .set("stroke-width", 2)
1677 .set("rx", 20);
1678 document = document.add(container_bg);
1679
1680 let no_data_text = SvgText::new("No Memory Type Data Available")
1681 .set("x", area.x + area.width / 2.0)
1682 .set("y", area.y + area.height / 2.0)
1683 .set("text-anchor", "middle")
1684 .set("font-size", 16)
1685 .set("font-weight", "bold")
1686 .set("fill", "#6c757d");
1687 document = document.add(no_data_text);
1688
1689 Ok(document)
1690}
1691
1692fn render_smart_horizontal_band(
1694 mut document: Document,
1695 x: f64,
1696 y: f64,
1697 width: f64,
1698 height: f64,
1699 node: &TreemapNode,
1700) -> TrackingResult<Document> {
1701 let band_bg = Rectangle::new()
1703 .set("class", "integrated-treemap-rect")
1704 .set("x", x)
1705 .set("y", y)
1706 .set("width", width)
1707 .set("height", height)
1708 .set("fill", node.color.as_str())
1709 .set("rx", 18);
1710 document = document.add(band_bg);
1711
1712 let title_y = y + 20.0;
1714 let title = SvgText::new(format!("{} - {:.0}%", node.name, node.percentage))
1715 .set("class", "integrated-treemap-label")
1716 .set("x", x + width / 2.0)
1717 .set("y", title_y)
1718 .set("font-size", 16);
1719 document = document.add(title);
1720
1721 let children_y = title_y + 10.0;
1723 let children_height = height - 30.0;
1724 let children_padding = 10.0;
1725 let available_children_width = width - (children_padding * 2.0);
1726
1727 let total_children_size: usize = node.children.iter().map(|c| c.size).sum();
1729 if total_children_size == 0 {
1730 return Ok(document);
1731 }
1732
1733 let mut current_x = x + children_padding;
1734
1735 for child in &node.children {
1736 let child_width =
1738 (child.size as f64 / total_children_size as f64) * available_children_width;
1739
1740 let child_rect = Rectangle::new()
1742 .set("class", "integrated-treemap-rect")
1743 .set("x", current_x)
1744 .set("y", children_y)
1745 .set("width", child_width - 5.0) .set("height", children_height - 10.0)
1747 .set("fill", child.color.as_str())
1748 .set("rx", 18);
1749 document = document.add(child_rect);
1750
1751 let (child_name, child_relative_percentage) = extract_name_and_percentage(&child.name);
1753
1754 let child_label = SvgText::new(child_name)
1755 .set("class", "integrated-treemap-label")
1756 .set("x", current_x + child_width / 2.0)
1757 .set("y", children_y + children_height / 2.0 - 5.0)
1758 .set("font-size", calculate_font_size(child_width))
1759 .set("font-weight", "bold");
1760 document = document.add(child_label);
1761
1762 let percentage_text = if let Some(pct) = child_relative_percentage {
1764 format!("({})", pct)
1765 } else {
1766 format!(
1767 "({:.0}%)",
1768 (child.size as f64 / total_children_size as f64) * 100.0
1769 )
1770 };
1771
1772 let child_percentage = SvgText::new(percentage_text)
1773 .set("class", "integrated-treemap-percentage")
1774 .set("x", current_x + child_width / 2.0)
1775 .set("y", children_y + children_height / 2.0 + 15.0)
1776 .set("font-size", calculate_font_size(child_width) - 2);
1777 document = document.add(child_percentage);
1778
1779 current_x += child_width;
1780 }
1781
1782 Ok(document)
1783}
1784
1785fn render_simple_horizontal_band(
1787 mut document: Document,
1788 x: f64,
1789 y: f64,
1790 width: f64,
1791 height: f64,
1792 node: &TreemapNode,
1793) -> TrackingResult<Document> {
1794 let band_rect = Rectangle::new()
1796 .set("class", "integrated-treemap-rect")
1797 .set("x", x)
1798 .set("y", y)
1799 .set("width", width)
1800 .set("height", height)
1801 .set("fill", node.color.as_str())
1802 .set("rx", 18);
1803 document = document.add(band_rect);
1804
1805 let label = SvgText::new(&node.name)
1807 .set("class", "integrated-treemap-label")
1808 .set("x", x + width / 2.0)
1809 .set("y", y + height / 2.0 - 5.0)
1810 .set("font-size", 16)
1811 .set("font-weight", "bold");
1812 document = document.add(label);
1813
1814 let percentage_label = SvgText::new(format!("{:.0}%", node.percentage))
1816 .set("class", "integrated-treemap-percentage")
1817 .set("x", x + width / 2.0)
1818 .set("y", y + height / 2.0 + 15.0)
1819 .set("font-size", 12);
1820 document = document.add(percentage_label);
1821
1822 Ok(document)
1823}
1824
1825fn extract_name_and_percentage(formatted_name: &str) -> (&str, Option<&str>) {
1827 if let Some(open_paren) = formatted_name.find(" (") {
1828 if let Some(close_paren) = formatted_name.find(')') {
1829 let name = &formatted_name[..open_paren];
1830 let percentage = &formatted_name[open_paren + 2..close_paren];
1831 return (name, Some(percentage));
1832 }
1833 }
1834 (formatted_name, None)
1835}
1836
1837fn calculate_font_size(width: f64) -> i32 {
1839 if width > 200.0 {
1840 15
1841 } else if width > 100.0 {
1842 13
1843 } else if width > 50.0 {
1844 11
1845 } else {
1846 9
1847 }
1848}
1849
1850pub fn add_categorized_allocations(
1852 mut document: Document,
1853 categories: &[AllocationCategory],
1854) -> TrackingResult<Document> {
1855 let chart_x = 50;
1856 let chart_y = 1080; let chart_width = 850;
1858 let chart_height = 280; let bg = Rectangle::new()
1862 .set("x", chart_x)
1863 .set("y", chart_y)
1864 .set("width", chart_width)
1865 .set("height", chart_height)
1866 .set("fill", "white")
1867 .set("stroke", "#bdc3c7")
1868 .set("stroke-width", 2)
1869 .set("rx", 20); document = document.add(bg);
1872
1873 let title = SvgText::new("Tracked Variables by Category")
1875 .set("x", chart_x + chart_width / 2)
1876 .set("y", chart_y - 10)
1877 .set("text-anchor", "middle")
1878 .set("font-size", 16)
1879 .set("font-weight", "bold")
1880 .set("fill", "#2c3e50");
1881
1882 document = document.add(title);
1883
1884 if categories.is_empty() {
1885 let no_data = SvgText::new("No tracked variables found")
1886 .set("x", chart_x + chart_width / 2)
1887 .set("y", chart_y + chart_height / 2)
1888 .set("text-anchor", "middle")
1889 .set("font-size", 14)
1890 .set("fill", "#7f8c8d");
1891
1892 document = document.add(no_data);
1893 return Ok(document);
1894 }
1895
1896 let max_size = categories.iter().map(|c| c.total_size).max().unwrap_or(1);
1898 let bar_height = (chart_height - 60) / categories.len().min(8);
1899
1900 for (i, category) in categories.iter().take(8).enumerate() {
1901 let y = chart_y + 30 + i * bar_height;
1902 let bar_width =
1903 ((category.total_size as f64 / max_size as f64) * (chart_width - 200) as f64) as i32;
1904
1905 let bar = Rectangle::new()
1907 .set("x", chart_x + 150)
1908 .set("y", y)
1909 .set("width", bar_width)
1910 .set("height", bar_height - 5)
1911 .set("fill", category.color.as_str())
1912 .set("stroke", "#34495e")
1913 .set("stroke-width", 1)
1914 .set("rx", 12); document = document.add(bar);
1917
1918 let name_text = SvgText::new(&category.name)
1920 .set("x", chart_x + 10)
1921 .set("y", y + bar_height / 2 + 4)
1922 .set("font-size", 12)
1923 .set("font-weight", "600")
1924 .set("fill", "#2c3e50");
1925
1926 document = document.add(name_text);
1927
1928 let var_names: Vec<String> = category
1930 .allocations
1931 .iter()
1932 .filter_map(|a| {
1933 if let Some(var_name) = &a.var_name {
1934 let type_name = a.type_name.as_deref().unwrap_or("Unknown");
1935 let (simplified_type, _) = simplify_type_name(type_name);
1936 let short_var = if var_name.len() > 12 {
1938 format!("{}...", &var_name[..9])
1939 } else {
1940 var_name.clone()
1941 };
1942 Some(format!("{short_var}({simplified_type})"))
1943 } else {
1944 None
1945 }
1946 })
1947 .take(3) .collect();
1949
1950 let mut display_text = if var_names.is_empty() {
1951 format!(
1952 "{} ({} vars)",
1953 format_bytes(category.total_size),
1954 category.allocations.len()
1955 )
1956 } else {
1957 format!(
1958 "{} - Vars: {}",
1959 format_bytes(category.total_size),
1960 var_names.join(", ")
1961 )
1962 };
1963
1964 let max_text_width = chart_width - 180; let estimated_char_width = 7; let max_chars = (max_text_width / estimated_char_width) as usize;
1968
1969 if display_text.len() > max_chars {
1970 display_text = format!("{}...", &display_text[..max_chars.saturating_sub(3)]);
1971 }
1972
1973 let size_text = SvgText::new(display_text)
1974 .set("x", chart_x + 160)
1975 .set("y", y + bar_height / 2 + 4)
1976 .set("font-size", 11) .set("font-weight", "bold")
1978 .set("fill", "#FFFFFF")
1979 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
1980
1981 document = document.add(size_text);
1982 }
1983
1984 Ok(document)
1985}
1986
1987pub fn add_memory_timeline(
1989 mut document: Document,
1990 allocations: &[AllocationInfo],
1991 _stats: &MemoryStats,
1992) -> TrackingResult<Document> {
1993 let chart_x = 50;
1994 let chart_y = 1780;
1995 let chart_width = 1700;
1996 let chart_height = 300;
1997
1998 let bg = Rectangle::new()
2000 .set("x", chart_x)
2001 .set("y", chart_y)
2002 .set("width", chart_width)
2003 .set("height", chart_height)
2004 .set("fill", "white")
2005 .set("stroke", "#bdc3c7")
2006 .set("stroke-width", 1)
2007 .set("rx", 5);
2008
2009 document = document.add(bg);
2010
2011 let title = SvgText::new("Variable Allocation Timeline")
2013 .set("x", chart_x + chart_width / 2)
2014 .set("y", chart_y - 10)
2015 .set("text-anchor", "middle")
2016 .set("font-size", 16)
2017 .set("font-weight", "bold")
2018 .set("fill", "#2c3e50");
2019
2020 document = document.add(title);
2021
2022 if allocations.is_empty() {
2023 let no_data = SvgText::new("No allocation data available")
2024 .set("x", chart_x + chart_width / 2)
2025 .set("y", chart_y + chart_height / 2)
2026 .set("text-anchor", "middle")
2027 .set("font-size", 14)
2028 .set("fill", "#7f8c8d");
2029
2030 document = document.add(no_data);
2031 return Ok(document);
2032 }
2033
2034 let mut tracked_allocs: Vec<_> = allocations
2036 .iter()
2037 .filter(|a| a.var_name.is_some())
2038 .collect();
2039 tracked_allocs.sort_by_key(|a| a.timestamp_alloc);
2040
2041 if tracked_allocs.is_empty() {
2042 let no_data = SvgText::new("No tracked variables found")
2043 .set("x", chart_x + chart_width / 2)
2044 .set("y", chart_y + chart_height / 2)
2045 .set("text-anchor", "middle")
2046 .set("font-size", 14)
2047 .set("fill", "#7f8c8d");
2048
2049 document = document.add(no_data);
2050 return Ok(document);
2051 }
2052
2053 let min_time = tracked_allocs
2054 .first()
2055 .map(|a| a.timestamp_alloc)
2056 .unwrap_or(0);
2057 let max_time = tracked_allocs
2058 .last()
2059 .map(|a| a.timestamp_alloc)
2060 .unwrap_or(min_time + 1);
2061 let _time_range = (max_time - min_time).max(1);
2062
2063 let label_width = 400; let timeline_width = chart_width - label_width - 60; let max_items = 8; for (i, allocation) in tracked_allocs.iter().take(max_items).enumerate() {
2070 let x = chart_x + 20 + (i * timeline_width / max_items.max(1));
2072 let y = chart_y + 50 + (i * 25); let x = x.min(chart_x + timeline_width).max(chart_x + 20);
2076
2077 let color = if let Some(type_name) = &allocation.type_name {
2079 let (_, category) = simplify_type_name(type_name);
2080 get_category_color(&category)
2081 } else {
2082 "#95a5a6".to_string()
2083 };
2084
2085 let point = Circle::new()
2087 .set("cx", x)
2088 .set("cy", y)
2089 .set("r", 5)
2090 .set("fill", color)
2091 .set("stroke", "#2c3e50")
2092 .set("stroke-width", 2);
2093
2094 document = document.add(point);
2095
2096 let label_start_x = chart_x + timeline_width + 20;
2098 let line = svg::node::element::Line::new()
2099 .set("x1", x + 5)
2100 .set("y1", y)
2101 .set("x2", label_start_x)
2102 .set("y2", y)
2103 .set("stroke", "#bdc3c7")
2104 .set("stroke-width", 1)
2105 .set("stroke-dasharray", "3,3");
2106
2107 document = document.add(line);
2108
2109 if let Some(var_name) = &allocation.var_name {
2111 let type_name = allocation.type_name.as_deref().unwrap_or("Unknown");
2112 let (simplified_type, _) = simplify_type_name(type_name);
2113 let mut label_text = format!(
2114 "{}({}) memory: {}",
2115 var_name,
2116 simplified_type,
2117 format_bytes(allocation.size)
2118 );
2119
2120 if label_text.len() > 45 {
2122 label_text = format!("{}...", &label_text[..42]);
2123 }
2124
2125 let label = SvgText::new(label_text)
2126 .set("x", label_start_x + 5)
2127 .set("y", y + 4)
2128 .set("font-size", 10) .set("font-weight", "500")
2130 .set("fill", "#2c3e50");
2131
2132 document = document.add(label);
2133 }
2134 }
2135
2136 let axis_y = chart_y + chart_height - 40;
2138 let axis_line = svg::node::element::Line::new()
2139 .set("x1", chart_x + 20)
2140 .set("y1", axis_y)
2141 .set("x2", chart_x + timeline_width)
2142 .set("y2", axis_y)
2143 .set("stroke", "#34495e")
2144 .set("stroke-width", 2);
2145
2146 document = document.add(axis_line);
2147
2148 let start_label = SvgText::new("Timeline")
2150 .set("x", chart_x + 20)
2151 .set("y", axis_y + 20)
2152 .set("font-size", 12)
2153 .set("font-weight", "600")
2154 .set("fill", "#7f8c8d");
2155
2156 document = document.add(start_label);
2157
2158 Ok(document)
2159}
2160
2161pub fn add_fragmentation_analysis(
2163 mut document: Document,
2164 allocations: &[AllocationInfo],
2165) -> TrackingResult<Document> {
2166 let chart_x = 950;
2167 let chart_y = 710; let chart_width = 750; let chart_height = 300;
2170
2171 let bg = Rectangle::new()
2173 .set("x", chart_x)
2174 .set("y", chart_y)
2175 .set("width", chart_width)
2176 .set("height", chart_height)
2177 .set("fill", "white")
2178 .set("stroke", "#f39c12")
2179 .set("stroke-width", 2)
2180 .set("rx", 10);
2181
2182 document = document.add(bg);
2183
2184 let title = SvgText::new("Memory Fragmentation Analysis")
2186 .set("x", chart_x + chart_width / 2)
2187 .set("y", chart_y - 10)
2188 .set("text-anchor", "middle")
2189 .set("font-size", 18)
2190 .set("font-weight", "bold")
2191 .set("fill", "#2c3e50");
2192
2193 document = document.add(title);
2194
2195 if allocations.is_empty() {
2196 let no_data = SvgText::new("No allocation data available")
2197 .set("x", chart_x + chart_width / 2)
2198 .set("y", chart_y + chart_height / 2)
2199 .set("text-anchor", "middle")
2200 .set("font-size", 14)
2201 .set("fill", "#7f8c8d");
2202
2203 document = document.add(no_data);
2204 return Ok(document);
2205 }
2206
2207 let size_buckets = [
2209 (0, 64, "Tiny (0-64B)"),
2210 (65, 256, "Small (65-256B)"),
2211 (257, 1024, "Medium (257B-1KB)"),
2212 (1025, 4096, "Large (1-4KB)"),
2213 (4097, 16384, "XLarge (4-16KB)"),
2214 (16385, usize::MAX, "Huge (>16KB)"),
2215 ];
2216
2217 let mut bucket_counts = vec![0; size_buckets.len()];
2218
2219 for allocation in allocations {
2220 for (i, &(min, max, _)) in size_buckets.iter().enumerate() {
2221 if allocation.size >= min && allocation.size <= max {
2222 bucket_counts[i] += 1;
2223 break;
2224 }
2225 }
2226 }
2227
2228 let max_count = bucket_counts.iter().max().copied().unwrap_or(1);
2229 let bar_width = (chart_width - 100) / size_buckets.len();
2230
2231 for (i, (&(_, _, label), &count)) in size_buckets.iter().zip(bucket_counts.iter()).enumerate() {
2233 let x = chart_x + 50 + i * bar_width;
2234 let bar_height = if max_count > 0 {
2235 (count as f64 / max_count as f64 * (chart_height - 80) as f64) as i32
2236 } else {
2237 0
2238 };
2239 let y = chart_y + chart_height - 40 - bar_height;
2240
2241 let color = match i {
2243 0..=1 => "#27ae60", 2..=3 => "#f39c12", _ => "#e74c3c", };
2247
2248 let bar = Rectangle::new()
2249 .set("x", x)
2250 .set("y", y)
2251 .set("width", bar_width - 5)
2252 .set("height", bar_height)
2253 .set("fill", color)
2254 .set("stroke", "#2c3e50")
2255 .set("stroke-width", 1);
2256
2257 document = document.add(bar);
2258
2259 let count_text = SvgText::new(count.to_string())
2261 .set("x", x + bar_width / 2)
2262 .set("y", y - 5)
2263 .set("text-anchor", "middle")
2264 .set("font-size", 12)
2265 .set("font-weight", "bold")
2266 .set("fill", "#2c3e50");
2267
2268 document = document.add(count_text);
2269
2270 let size_text = SvgText::new(label)
2272 .set("x", x + bar_width / 2)
2273 .set("y", chart_y + chart_height - 10)
2274 .set("text-anchor", "middle")
2275 .set("font-size", 10)
2276 .set("fill", "#7f8c8d");
2277
2278 document = document.add(size_text);
2279 }
2280
2281 Ok(document)
2282}
2283
2284pub fn add_callstack_analysis(
2286 mut document: Document,
2287 allocations: &[AllocationInfo],
2288) -> TrackingResult<Document> {
2289 let chart_x = 950;
2290 let chart_y = 1060; let chart_width = 750; let chart_height = 300;
2293
2294 let bg = Rectangle::new()
2296 .set("x", chart_x)
2297 .set("y", chart_y)
2298 .set("width", chart_width)
2299 .set("height", chart_height)
2300 .set("fill", "white")
2301 .set("stroke", "#9b59b6")
2302 .set("stroke-width", 2)
2303 .set("rx", 10);
2304
2305 document = document.add(bg);
2306
2307 let title = SvgText::new("Call Stack Analysis")
2309 .set("x", chart_x + chart_width / 2)
2310 .set("y", chart_y - 10)
2311 .set("text-anchor", "middle")
2312 .set("font-size", 18)
2313 .set("font-weight", "bold")
2314 .set("fill", "#2c3e50");
2315
2316 document = document.add(title);
2317
2318 let mut source_stats: HashMap<String, (usize, usize)> = HashMap::new();
2320
2321 for allocation in allocations {
2322 let source_key = if let Some(var_name) = &allocation.var_name {
2324 if let Some(type_name) = &allocation.type_name {
2326 let (simplified_type, _) = simplify_type_name(type_name);
2327 format!(
2328 "{}({}) memory: {}",
2329 var_name,
2330 simplified_type,
2331 format_bytes(allocation.size)
2332 )
2333 } else {
2334 format!(
2335 "{}(Unknown Type) memory: {}",
2336 var_name,
2337 format_bytes(allocation.size)
2338 )
2339 }
2340 } else if let Some(type_name) = &allocation.type_name {
2341 let (simplified_type, _) = simplify_type_name(type_name);
2343
2344 if type_name.contains("std::") || type_name.contains("alloc::") {
2346 format!("System/Runtime {simplified_type} (untracked)")
2347 } else if simplified_type.contains("Vec") {
2348 "Internal Vec allocations (untracked)".to_string()
2349 } else if simplified_type.contains("String") {
2350 "Internal String allocations (untracked)".to_string()
2351 } else if simplified_type.contains("HashMap") {
2352 "Internal HashMap allocations (untracked)".to_string()
2353 } else {
2354 format!("Internal {simplified_type} allocations (untracked)")
2355 }
2356 } else {
2357 "System/Runtime allocations (no type info)".to_string()
2359 };
2360
2361 let entry = source_stats.entry(source_key).or_insert((0, 0));
2362 entry.0 += 1;
2363 entry.1 += allocation.size;
2364 }
2365
2366 if source_stats.is_empty() {
2367 let no_data = SvgText::new("No call stack data available")
2368 .set("x", chart_x + chart_width / 2)
2369 .set("y", chart_y + chart_height / 2)
2370 .set("text-anchor", "middle")
2371 .set("font-size", 14)
2372 .set("fill", "#7f8c8d");
2373
2374 document = document.add(no_data);
2375 return Ok(document);
2376 }
2377
2378 let mut sorted_sources: Vec<_> = source_stats.iter().collect();
2380 sorted_sources.sort_by(|a, b| b.1 .1.cmp(&a.1 .1));
2381
2382 let max_size = sorted_sources
2383 .first()
2384 .map(|(_, (_, size))| *size)
2385 .unwrap_or(1);
2386
2387 for (i, (source, (count, total_size))) in sorted_sources.iter().take(10).enumerate() {
2389 let y = chart_y + 40 + i * 25;
2390 let node_size = ((*total_size as f64 / max_size as f64) * 15.0 + 5.0) as i32;
2391
2392 let colors = ["#e74c3c", "#f39c12", "#27ae60", "#3498db", "#9b59b6"];
2394 let color = colors[i % colors.len()];
2395
2396 let node = Circle::new()
2397 .set("cx", chart_x + 50)
2398 .set("cy", y)
2399 .set("r", node_size)
2400 .set("fill", color)
2401 .set("stroke", "#2c3e50")
2402 .set("stroke-width", 2);
2403
2404 document = document.add(node);
2405
2406 let source_text = format!("{source} ({count} allocs, {total_size} bytes)");
2408
2409 let label = SvgText::new(source_text)
2410 .set("x", chart_x + 80)
2411 .set("y", y + 5)
2412 .set("font-size", 11)
2413 .set("font-weight", "500")
2414 .set("fill", "#2c3e50");
2415
2416 document = document.add(label);
2417 }
2418
2419 Ok(document)
2420}
2421
2422pub fn add_memory_growth_trends(
2424 mut document: Document,
2425 allocations: &[AllocationInfo],
2426 stats: &MemoryStats,
2427) -> TrackingResult<Document> {
2428 let chart_x = 50;
2429 let chart_y = 1430;
2430 let chart_width = 1700;
2431 let chart_height = 300;
2432
2433 let bg = Rectangle::new()
2435 .set("x", chart_x)
2436 .set("y", chart_y)
2437 .set("width", chart_width)
2438 .set("height", chart_height)
2439 .set("fill", "white")
2440 .set("stroke", "#27ae60")
2441 .set("stroke-width", 2)
2442 .set("rx", 10);
2443
2444 document = document.add(bg);
2445
2446 let title = SvgText::new("Memory Growth Trends")
2448 .set("x", chart_x + chart_width / 2)
2449 .set("y", chart_y - 10)
2450 .set("text-anchor", "middle")
2451 .set("font-size", 18)
2452 .set("font-weight", "bold")
2453 .set("fill", "#2c3e50");
2454
2455 document = document.add(title);
2456
2457 if allocations.is_empty() {
2458 let no_data = SvgText::new("No allocation data available")
2459 .set("x", chart_x + chart_width / 2)
2460 .set("y", chart_y + chart_height / 2)
2461 .set("text-anchor", "middle")
2462 .set("font-size", 14)
2463 .set("fill", "#7f8c8d");
2464
2465 document = document.add(no_data);
2466 return Ok(document);
2467 }
2468
2469 let time_points = 10;
2471 let point_width = (chart_width - 100) / time_points;
2472
2473 for i in 0..time_points {
2474 let x = chart_x + 50 + i * point_width;
2475 let simulated_memory = stats.active_memory / time_points * (i + 1);
2476 let y = chart_y + chart_height
2477 - 50
2478 - ((simulated_memory as f64 / stats.peak_memory as f64) * (chart_height - 100) as f64)
2479 as i32;
2480
2481 let point = Circle::new()
2483 .set("cx", x)
2484 .set("cy", y)
2485 .set("r", 4)
2486 .set("fill", "#27ae60")
2487 .set("stroke", "#2c3e50")
2488 .set("stroke-width", 1);
2489
2490 document = document.add(point);
2491
2492 if i > 0 {
2494 let prev_x = chart_x + 50 + (i - 1) * point_width;
2495 let prev_memory = stats.active_memory / time_points * i;
2496 let prev_y = chart_y + chart_height
2497 - 50
2498 - ((prev_memory as f64 / stats.peak_memory as f64) * (chart_height - 100) as f64)
2499 as i32;
2500
2501 let line = svg::node::element::Line::new()
2502 .set("x1", prev_x)
2503 .set("y1", prev_y)
2504 .set("x2", x)
2505 .set("y2", y)
2506 .set("stroke", "#27ae60")
2507 .set("stroke-width", 2);
2508
2509 document = document.add(line);
2510 }
2511 }
2512
2513 let peak_y = chart_y + 50;
2515 let peak_line = svg::node::element::Line::new()
2516 .set("x1", chart_x + 50)
2517 .set("y1", peak_y)
2518 .set("x2", chart_x + chart_width - 50)
2519 .set("y2", peak_y)
2520 .set("stroke", "#e74c3c")
2521 .set("stroke-width", 2)
2522 .set("stroke-dasharray", "10,5");
2523
2524 document = document.add(peak_line);
2525
2526 let peak_label = SvgText::new(format!("Peak: {} bytes", stats.peak_memory))
2527 .set("x", chart_x + chart_width - 100)
2528 .set("y", peak_y - 10)
2529 .set("font-size", 12)
2530 .set("font-weight", "bold")
2531 .set("fill", "#e74c3c");
2532
2533 document = document.add(peak_label);
2534
2535 Ok(document)
2536}
2537
2538pub fn add_interactive_legend(mut document: Document) -> TrackingResult<Document> {
2540 let legend_x = 50;
2541 let legend_y = 2130;
2542 let legend_width = 850;
2543 let legend_height = 250;
2544
2545 let bg = Rectangle::new()
2547 .set("x", legend_x)
2548 .set("y", legend_y)
2549 .set("width", legend_width)
2550 .set("height", legend_height)
2551 .set("fill", "white")
2552 .set("stroke", "#34495e")
2553 .set("stroke-width", 2)
2554 .set("rx", 10);
2555
2556 document = document.add(bg);
2557
2558 let title = SvgText::new("Interactive Legend & Guide")
2560 .set("x", legend_x + legend_width / 2)
2561 .set("y", legend_y - 10)
2562 .set("text-anchor", "middle")
2563 .set("font-size", 18)
2564 .set("font-weight", "bold")
2565 .set("fill", "#2c3e50");
2566
2567 document = document.add(title);
2568
2569 let legend_items = [
2571 ("#e74c3c", "High Memory Usage / Critical"),
2572 ("#f39c12", "Medium Usage / Warning"),
2573 ("#27ae60", "Low Usage / Good"),
2574 ("#3498db", "Performance Metrics"),
2575 ("#9b59b6", "Call Stack Data"),
2576 ("#34495e", "General Information"),
2577 ];
2578
2579 for (i, (color, description)) in legend_items.iter().enumerate() {
2580 let x = legend_x + 30 + (i % 3) * 220;
2581 let y = legend_y + 40 + (i / 3) * 40;
2582
2583 let swatch = Rectangle::new()
2585 .set("x", x)
2586 .set("y", y - 10)
2587 .set("width", 20)
2588 .set("height", 15)
2589 .set("fill", *color)
2590 .set("stroke", "#2c3e50")
2591 .set("stroke-width", 1);
2592
2593 document = document.add(swatch);
2594
2595 let desc_text = SvgText::new(*description)
2597 .set("x", x + 30)
2598 .set("y", y)
2599 .set("font-size", 12)
2600 .set("fill", "#2c3e50");
2601
2602 document = document.add(desc_text);
2603 }
2604
2605 Ok(document)
2606}
2607
2608pub fn add_comprehensive_summary(
2610 mut document: Document,
2611 stats: &MemoryStats,
2612 allocations: &[AllocationInfo],
2613) -> TrackingResult<Document> {
2614 let summary_x = 950;
2615 let summary_y = 2130;
2616 let summary_width = 800;
2617 let summary_height = 250;
2618
2619 let bg = Rectangle::new()
2621 .set("x", summary_x)
2622 .set("y", summary_y)
2623 .set("width", summary_width)
2624 .set("height", summary_height)
2625 .set("fill", "white")
2626 .set("stroke", "#2c3e50")
2627 .set("stroke-width", 2)
2628 .set("rx", 10);
2629
2630 document = document.add(bg);
2631
2632 let title = SvgText::new("Memory Analysis Summary")
2634 .set("x", summary_x + summary_width / 2)
2635 .set("y", summary_y - 10)
2636 .set("text-anchor", "middle")
2637 .set("font-size", 18)
2638 .set("font-weight", "bold")
2639 .set("fill", "#2c3e50");
2640
2641 document = document.add(title);
2642
2643 let tracked_vars = allocations.iter().filter(|a| a.var_name.is_some()).count();
2645 let avg_size = if !allocations.is_empty() {
2646 allocations.iter().map(|a| a.size).sum::<usize>() / allocations.len()
2647 } else {
2648 0
2649 };
2650
2651 let summary_items = [
2652 format!("Total Active Allocations: {}", stats.active_allocations),
2653 format!(
2654 "Tracked Variables: {} ({:.1}%)",
2655 tracked_vars,
2656 if stats.active_allocations > 0 {
2657 tracked_vars as f64 / stats.active_allocations as f64 * 100.0
2658 } else {
2659 0.0
2660 }
2661 ),
2662 format!("Average Allocation Size: {avg_size} bytes"),
2663 format!(
2664 "Memory Efficiency: {:.1}%",
2665 if stats.total_allocations > 0 {
2666 stats.total_deallocations as f64 / stats.total_allocations as f64 * 100.0
2667 } else {
2668 0.0
2669 }
2670 ),
2671 format!(
2672 "Peak vs Current: {} vs {} bytes",
2673 stats.peak_memory, stats.active_memory
2674 ),
2675 ];
2676
2677 for (i, item) in summary_items.iter().enumerate() {
2678 let summary_text = SvgText::new(item)
2679 .set("x", summary_x + 30)
2680 .set("y", summary_y + 40 + i * 25)
2681 .set("font-size", 13)
2682 .set("font-weight", "500")
2683 .set("fill", "#2c3e50");
2684
2685 document = document.add(summary_text);
2686 }
2687
2688 Ok(document)
2689}
2690
2691pub fn add_enhanced_timeline_dashboard(
2695 mut document: Document,
2696 _stats: &MemoryStats,
2697 allocations: &[AllocationInfo],
2698) -> TrackingResult<Document> {
2699 let chart_x = 50;
2700 let chart_y = 300; let chart_width = 1700;
2702 let chart_height = 350; let bg = Rectangle::new()
2706 .set("x", chart_x)
2707 .set("y", chart_y)
2708 .set("width", chart_width)
2709 .set("height", chart_height)
2710 .set("fill", "white")
2711 .set("stroke", "#bdc3c7")
2712 .set("stroke-width", 1)
2713 .set("rx", 8);
2714
2715 document = document.add(bg);
2716
2717 let title = SvgText::new("Memory Allocation Timeline")
2719 .set("x", chart_x + chart_width / 2)
2720 .set("y", chart_y - 15)
2721 .set("text-anchor", "middle")
2722 .set("font-size", 16)
2723 .set("font-weight", "bold")
2724 .set("fill", "#2c3e50");
2725
2726 document = document.add(title);
2727
2728 let tracked_vars: Vec<&AllocationInfo> = allocations
2730 .iter()
2731 .filter(|a| a.var_name.is_some())
2732 .collect();
2733
2734 if tracked_vars.is_empty() {
2735 let no_data = SvgText::new("No tracked variables found for timeline visualization")
2736 .set("x", chart_x + chart_width / 2)
2737 .set("y", chart_y + chart_height / 2)
2738 .set("text-anchor", "middle")
2739 .set("font-size", 14)
2740 .set("fill", "#7f8c8d");
2741 document = document.add(no_data);
2742 return Ok(document);
2743 }
2744
2745 let mut scope_groups: std::collections::HashMap<String, Vec<&AllocationInfo>> =
2747 std::collections::HashMap::new();
2748 for var in &tracked_vars {
2749 let scope_name = extract_scope_name(var.var_name.as_ref().unwrap());
2750 scope_groups
2751 .entry(scope_name)
2752 .or_insert_with(Vec::new)
2753 .push(*var);
2754 }
2755
2756 let mut sorted_scopes: Vec<_> = scope_groups.into_iter().collect();
2758 sorted_scopes.sort_by(|a, b| a.0.cmp(&b.0));
2759
2760 let max_time = tracked_vars
2762 .iter()
2763 .map(|a| a.timestamp_alloc)
2764 .max()
2765 .unwrap_or(1000) as f64;
2766 let min_time = tracked_vars
2767 .iter()
2768 .map(|a| a.timestamp_alloc)
2769 .min()
2770 .unwrap_or(0) as f64;
2771 let time_range = (max_time - min_time).max(1.0);
2772
2773 let plot_x = chart_x + 200; let plot_y = chart_y + 50;
2776 let plot_width = chart_width - 350; let plot_height = chart_height - 100;
2778
2779 let row_height = (plot_height as f64 / sorted_scopes.len().max(1) as f64)
2780 .min(40.0)
2781 .max(25.0); let time_axis = svg::node::element::Line::new()
2785 .set("x1", plot_x)
2786 .set("y1", plot_y + plot_height)
2787 .set("x2", plot_x + plot_width)
2788 .set("y2", plot_y + plot_height)
2789 .set("stroke", "#34495e")
2790 .set("stroke-width", 2);
2791 document = document.add(time_axis);
2792
2793 let time_units = ["0ms", "0.25ms", "0.5ms", "0.75ms", "1ms"];
2795 for i in 0..=4 {
2796 let x = plot_x + (i * plot_width / 4);
2797
2798 let tick = svg::node::element::Line::new()
2799 .set("x1", x)
2800 .set("y1", plot_y + plot_height)
2801 .set("x2", x)
2802 .set("y2", plot_y + plot_height + 5)
2803 .set("stroke", "#34495e")
2804 .set("stroke-width", 1);
2805 document = document.add(tick);
2806
2807 let time_label = time_units[i];
2809 let label = SvgText::new(time_label)
2810 .set("x", x)
2811 .set("y", plot_y + plot_height + 18)
2812 .set("text-anchor", "middle")
2813 .set("font-size", 10)
2814 .set("font-weight", "500")
2815 .set("fill", "#2c3e50");
2816 document = document.add(label);
2817 }
2818
2819 let x_label = SvgText::new("Execution Time (milliseconds)")
2821 .set("x", plot_x + plot_width / 2)
2822 .set("y", plot_y + plot_height + 40)
2823 .set("text-anchor", "middle")
2824 .set("font-size", 12)
2825 .set("font-weight", "bold")
2826 .set("fill", "#2c3e50");
2827 document = document.add(x_label);
2828
2829 let scale_markers = [
2833 (16, "16B"),
2834 (256, "256B"),
2835 (1024, "1KB"),
2836 (4096, "4KB"),
2837 (16384, "16KB"),
2838 ];
2839
2840 for (size, label) in scale_markers.iter() {
2841 {
2843 let log_size = (*size as f64).ln();
2844 let log_min = 1.0_f64.ln();
2845 let log_max = 16384.0_f64.ln();
2846 let log_range = log_max - log_min;
2847
2848 if log_range > 0.0 {
2849 let y_pos = plot_y + plot_height
2850 - ((log_size - log_min) / log_range * plot_height as f64) as i32;
2851
2852 let marker_line = svg::node::element::Line::new()
2854 .set("x1", plot_x - 5)
2855 .set("y1", y_pos)
2856 .set("x2", plot_x + 5)
2857 .set("y2", y_pos)
2858 .set("stroke", "#7f8c8d")
2859 .set("stroke-width", 1);
2860 document = document.add(marker_line);
2861
2862 let marker_label = SvgText::new(*label)
2864 .set("x", plot_x - 15)
2865 .set("y", y_pos + 3)
2866 .set("text-anchor", "end")
2867 .set("font-size", 10)
2868 .set("fill", "#7f8c8d");
2869 document = document.add(marker_label);
2870 }
2871 }
2872 }
2873
2874 let time_markers = 5;
2876 for i in 0..=time_markers {
2877 let time_point = min_time + (time_range * i as f64 / time_markers as f64);
2878 let x_pos = plot_x + (i * plot_width / time_markers);
2879
2880 let time_marker_line = svg::node::element::Line::new()
2882 .set("x1", x_pos)
2883 .set("y1", plot_y + plot_height - 5)
2884 .set("x2", x_pos)
2885 .set("y2", plot_y + plot_height + 5)
2886 .set("stroke", "#7f8c8d")
2887 .set("stroke-width", 1);
2888 document = document.add(time_marker_line);
2889
2890 let formatted_time = if time_point < 1000.0 {
2892 format!("{:.1}ms", time_point)
2893 } else if time_point < 60000.0 {
2894 format!("{:.2}s", time_point / 1000.0)
2895 } else {
2896 format!("{:.1}m", time_point / 60000.0)
2897 };
2898
2899 let time_label = SvgText::new(formatted_time)
2900 .set("x", x_pos)
2901 .set("y", plot_y + plot_height + 20)
2902 .set("text-anchor", "middle")
2903 .set("font-size", 10)
2904 .set("font-weight", "500")
2905 .set("fill", "#2c3e50");
2906 document = document.add(time_label);
2907 }
2908
2909 let categorized = categorize_allocations(allocations);
2911 let mut category_colors: HashMap<String, String> = HashMap::new();
2912 for category in &categorized {
2913 category_colors.insert(category.name.clone(), category.color.clone());
2914 }
2915
2916 let mut sizes: Vec<usize> = allocations
2918 .iter()
2919 .map(|a| a.size)
2920 .filter(|&s| s > 0)
2921 .collect();
2922 sizes.sort_unstable();
2923 let _p95_threshold = if !sizes.is_empty() {
2924 let p95_index = (sizes.len() as f64 * 0.95) as usize;
2925 sizes[p95_index.min(sizes.len() - 1)]
2926 } else {
2927 0
2928 };
2929
2930 for (scope_index, (scope_name, scope_vars)) in sorted_scopes.iter().enumerate() {
2932 let scope_y = plot_y + 10 + (scope_index as f64 * row_height) as i32; let scope_colors = [
2936 "#3498db", "#e74c3c", "#2ecc71", "#f39c12", "#9b59b6", "#1abc9c",
2937 ];
2938 let scope_color = scope_colors[scope_index % scope_colors.len()];
2939
2940 let scope_bg = Rectangle::new()
2942 .set("x", plot_x)
2943 .set("y", scope_y)
2944 .set("width", plot_width)
2945 .set("height", (row_height * 0.8) as i32)
2946 .set("fill", scope_color)
2947 .set("opacity", 0.2)
2948 .set("rx", 5);
2949 document = document.add(scope_bg);
2950
2951 let scope_label = SvgText::new(format!("Scope: {}", scope_name))
2953 .set("x", plot_x - 15)
2954 .set("y", scope_y + (row_height * 0.4) as i32)
2955 .set("text-anchor", "end")
2956 .set("font-size", 11)
2957 .set("font-weight", "bold")
2958 .set("fill", scope_color);
2959 document = document.add(scope_label);
2960
2961 for (var_index, alloc) in scope_vars.iter().enumerate() {
2963 let var_name = alloc.var_name.as_ref().unwrap();
2964
2965 let start_x = plot_x as i32
2967 + ((alloc.timestamp_alloc as f64 - min_time) / time_range * plot_width as f64)
2968 as i32;
2969 let var_y = scope_y + 5 + (var_index as f64 * 8.0) as i32; let (_, category) = if let Some(type_name) = &alloc.type_name {
2973 simplify_type_name(type_name)
2974 } else {
2975 (
2976 "Unknown".to_string(),
2977 "Runtime/System Allocation".to_string(),
2978 )
2979 };
2980 let var_color = get_category_color(&category);
2981
2982 let max_size = scope_vars.iter().map(|a| a.size).max().unwrap_or(1024) as f64;
2984 let min_size = scope_vars
2985 .iter()
2986 .map(|a| a.size)
2987 .filter(|&s| s > 0)
2988 .min()
2989 .unwrap_or(1) as f64;
2990 let size_ratio = if max_size > min_size {
2991 ((alloc.size as f64).ln() - min_size.ln()) / (max_size.ln() - min_size.ln())
2992 } else {
2993 0.5
2994 };
2995 let bar_width = ((plot_width as f64 * 0.4 * size_ratio) + 30.0) as i32;
2996
2997 let plot_x_i32 = plot_x as i32;
2999 let plot_width_i32 = plot_width as i32;
3000 let var_bar = Rectangle::new()
3001 .set("x", start_x.max(plot_x_i32))
3002 .set("y", var_y)
3003 .set(
3004 "width",
3005 bar_width.min(plot_x_i32 + plot_width_i32 - start_x.max(plot_x_i32)),
3006 )
3007 .set("height", 6)
3008 .set("fill", var_color)
3009 .set("stroke", "#ffffff")
3010 .set("stroke-width", 1)
3011 .set("rx", 2)
3012 .set("opacity", 0.9);
3013 document = document.add(var_bar);
3014
3015 let var_info = format!("{}: {}", var_name, format_bytes(alloc.size));
3017 let var_label = SvgText::new(if var_info.len() > 25 {
3018 format!("{}...", &var_info[..22])
3019 } else {
3020 var_info
3021 })
3022 .set("x", start_x.max(plot_x_i32) + 5)
3023 .set("y", var_y + 4)
3024 .set("font-size", 7)
3025 .set("font-weight", "500")
3026 .set("fill", "#2c3e50");
3027 document = document.add(var_label);
3028 }
3029 }
3030
3031 let legend_x = chart_x + 20;
3033 let legend_y = chart_y + 20;
3034 let legend_width = 200; let legend_height = 120; let legend_bg = Rectangle::new()
3039 .set("x", legend_x - 10)
3040 .set("y", legend_y - 15)
3041 .set("width", legend_width)
3042 .set("height", legend_height)
3043 .set("fill", "rgba(255,255,255,0.0)") .set("stroke", "none") .set("stroke-width", 0)
3046 .set("rx", 5);
3047 document = document.add(legend_bg);
3048
3049 let legend_title = SvgText::new("Type Categories")
3050 .set("x", legend_x)
3051 .set("y", legend_y)
3052 .set("font-size", 10) .set("font-weight", "bold")
3054 .set("fill", "#2c3e50");
3055 document = document.add(legend_title);
3056
3057 let unknown_count = allocations
3059 .iter()
3060 .filter(|a| {
3061 if let Some(type_name) = &a.type_name {
3062 let (_, category) = simplify_type_name(type_name);
3063 category == "Unknown"
3064 } else {
3065 true }
3067 })
3068 .count();
3069
3070 let unknown_legend_y = legend_y + 15;
3071
3072 let unknown_color_square = Rectangle::new()
3074 .set("x", legend_x)
3075 .set("y", unknown_legend_y - 6)
3076 .set("width", 8)
3077 .set("height", 8)
3078 .set("fill", "#95a5a6");
3079 document = document.add(unknown_color_square);
3080
3081 let unknown_label = if unknown_count > 0 {
3083 format!("System ({} allocs)", unknown_count)
3084 } else {
3085 "No System Allocs".to_string()
3086 };
3087
3088 let unknown_text = SvgText::new(unknown_label)
3089 .set("x", legend_x + 12)
3090 .set("y", unknown_legend_y - 1)
3091 .set("font-size", 8)
3092 .set("fill", "#2c3e50");
3093 document = document.add(unknown_text);
3094
3095 for (i, category) in categorized.iter().take(4).enumerate() {
3096 let legend_item_y = legend_y + 30 + (i as i32) * 15; let color_square = Rectangle::new()
3101 .set("x", legend_x)
3102 .set("y", legend_item_y - 6)
3103 .set("width", 8)
3104 .set("height", 8)
3105 .set("fill", category.color.as_str());
3106 document = document.add(color_square);
3107
3108 let category_name = if category.name.len() > 15 {
3110 format!("{}...", &category.name[..12])
3111 } else {
3112 category.name.clone()
3113 };
3114
3115 let category_text = SvgText::new(category_name)
3116 .set("x", legend_x + 12)
3117 .set("y", legend_item_y - 1)
3118 .set("font-size", 8) .set("fill", "#2c3e50");
3120 document = document.add(category_text);
3121 }
3122
3123 Ok(document)
3124}
3125
3126pub fn add_memory_heatmap(
3128 document: Document,
3129 _allocations: &[AllocationInfo],
3130) -> TrackingResult<Document> {
3131 Ok(document)
3134}
3135
3136fn extract_scope_name(var_name: &str) -> String {
3138 if var_name.contains("global") {
3140 "Global Scope".to_string()
3141 } else if var_name.contains("static") {
3142 "Static Scope".to_string()
3143 } else if var_name.contains("boxed") {
3144 "Boxed Allocations".to_string()
3145 } else if var_name.contains("shared") || var_name.contains("arc") || var_name.contains("rc") {
3146 "Shared References".to_string()
3147 } else if var_name.contains("node") {
3148 "Graph Nodes".to_string()
3149 } else if var_name.contains("mutable") {
3150 "Mutable Data".to_string()
3151 } else if var_name.contains("_") {
3152 let prefix = var_name.split('_').next().unwrap_or("Unknown");
3154 format!("{} Scope", prefix.to_ascii_uppercase())
3155 } else {
3156 if var_name.len() > 6 {
3158 format!("{} Scope", &var_name[..6].to_ascii_uppercase())
3159 } else {
3160 "Main Scope".to_string()
3161 }
3162 }
3163}