1use crate::tracker::MemoryTracker;
5use crate::types::{AllocationInfo, MemoryStats, TrackingError, TrackingResult};
6use crate::utils::{format_bytes, get_simple_type, get_type_color, get_type_gradient_colors};
7use std::collections::HashMap;
8use std::fs::File;
9use std::path::Path;
10use svg::node::element::{Circle, Group, Line, Rectangle, Style, Text as SvgText};
11use svg::Document;
12
13pub fn export_memory_analysis<P: AsRef<Path>>(
15 tracker: &MemoryTracker,
16 path: P,
17) -> TrackingResult<()> {
18 let path = path.as_ref();
19 tracing::info!("Exporting memory analysis to: {}", path.display());
20
21 if let Some(parent) = path.parent() {
22 if !parent.exists() {
23 std::fs::create_dir_all(parent)?;
24 }
25 }
26
27 let active_allocations = tracker.get_active_allocations()?;
28 let stats = tracker.get_stats()?;
29
30 let document = create_memory_analysis_svg(&active_allocations, &stats, tracker)?;
31
32 let mut file = File::create(path)?;
33 svg::write(&mut file, &document)
34 .map_err(|e| TrackingError::SerializationError(format!("Failed to write SVG: {e}")))?;
35
36 tracing::info!("Successfully exported memory analysis SVG");
37 Ok(())
38}
39
40pub fn export_lifecycle_timeline<P: AsRef<Path>>(
42 tracker: &MemoryTracker,
43 path: P,
44) -> TrackingResult<()> {
45 let path = path.as_ref();
46 tracing::info!("Exporting lifecycle timeline to: {}", path.display());
47
48 if let Some(parent) = path.parent() {
49 if !parent.exists() {
50 std::fs::create_dir_all(parent)?;
51 }
52 }
53
54 let active_allocations = tracker.get_active_allocations()?;
55 let stats = tracker.get_stats()?;
56
57 let document = create_lifecycle_timeline_svg(&active_allocations, &stats)?;
58
59 let mut file = File::create(path)?;
60 svg::write(&mut file, &document)
61 .map_err(|e| TrackingError::SerializationError(format!("Failed to write SVG: {e}")))?;
62
63 tracing::info!("Successfully exported lifecycle timeline SVG");
64 Ok(())
65}
66
67fn create_memory_analysis_svg(
69 allocations: &[AllocationInfo],
70 stats: &MemoryStats,
71 tracker: &MemoryTracker,
72) -> TrackingResult<Document> {
73 let width = 1800;
75 let height = 2400; let mut document = Document::new()
78 .set("viewBox", (0, 0, width, height))
79 .set("width", width)
80 .set("height", height)
81 .set("style", "background: linear-gradient(135deg, #ecf0f1 0%, #bdc3c7 100%); font-family: 'Segoe UI', Arial, sans-serif;");
82
83 document = crate::export_enhanced::add_enhanced_header(document, stats, allocations)?;
85
86 document =
88 crate::export_enhanced::add_enhanced_timeline_dashboard(document, stats, allocations)?;
89
90 document = crate::export_enhanced::add_memory_heatmap(document, allocations)?;
92
93 let memory_by_type_data = tracker.get_memory_by_type().unwrap_or_default();
96 let memory_by_type =
97 crate::export_enhanced::enhance_type_information(&memory_by_type_data, allocations);
98 document = crate::export_enhanced::add_enhanced_type_chart(document, &memory_by_type)?;
99
100 document = crate::export_enhanced::add_fragmentation_analysis(document, allocations)?;
102
103 let categorized = crate::export_enhanced::categorize_enhanced_allocations(&memory_by_type);
106 document = crate::export_enhanced::add_categorized_allocations(document, &categorized)?;
107
108 document = crate::export_enhanced::add_callstack_analysis(document, allocations)?;
110
111 document = crate::export_enhanced::add_memory_growth_trends(document, allocations, stats)?;
113
114 document = crate::export_enhanced::add_memory_timeline(document, allocations, stats)?;
116
117 document = crate::export_enhanced::add_interactive_legend(document)?;
119
120 document = crate::export_enhanced::add_comprehensive_summary(document, stats, allocations)?;
122
123 Ok(document)
124}
125
126fn create_lifecycle_timeline_svg(
128 allocations: &[AllocationInfo],
129 stats: &MemoryStats,
130) -> TrackingResult<Document> {
131 let width = 1600;
132 let height = 1200;
133
134 let mut document = Document::new()
135 .set("viewBox", (0, 0, width, height))
136 .set("width", width)
137 .set("height", height)
138 .set("style", "background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); font-family: 'Inter', 'Segoe UI', sans-serif;");
139
140 let styles = Style::new(
142 r#"
143 .timeline-bar { transition: all 0.3s ease; cursor: pointer; }
144 .timeline-bar:hover { stroke: #FFFFFF; stroke-width: 3; filter: drop-shadow(0 0 12px rgba(255,255,255,0.8)); }
145 .variable-label { fill: #FFFFFF; font-size: 13px; font-weight: 600; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
146 .memory-label { fill: #E2E8F0; font-size: 11px; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
147 .section-title { fill: #FFFFFF; font-size: 20px; font-weight: 700; text-shadow: 2px 2px 4px rgba(0,0,0,0.5); }
148 .section-bg { fill: rgba(255,255,255,0.1); stroke: rgba(255,255,255,0.2); stroke-width: 1; rx: 12; }
149 "#,
150 );
151 document = document.add(styles);
152
153 let title = SvgText::new("Scope Matrix & Lifecycle Visualization")
155 .set("x", width / 2)
156 .set("y", 40)
157 .set("text-anchor", "middle")
158 .set("font-size", 32)
159 .set("font-weight", "bold")
160 .set("fill", "#FFFFFF")
161 .set("style", "text-shadow: 3px 3px 6px rgba(0,0,0,0.5);");
162 document = document.add(title);
163
164 document = add_prominent_progress_bar_legend(document, width);
166
167 let tracked_vars: Vec<_> = allocations
168 .iter()
169 .filter(|a| a.var_name.is_some())
170 .collect();
171
172 if tracked_vars.is_empty() {
189 let no_data = SvgText::new(format!(
190 "No tracked variables found (checked {} allocations)",
191 allocations.len()
192 ))
193 .set("x", width / 2)
194 .set("y", height / 2)
195 .set("text-anchor", "middle")
196 .set("font-size", 18)
197 .set("fill", "#FFFFFF");
198 document = document.add(no_data);
199 return Ok(document);
200 }
201
202 document = add_matrix_layout_section(document, &tracked_vars, 50, 130)?;
204
205 document = add_memory_section(document, &tracked_vars, stats, 550, width - 100)?;
207
208 document = add_relationships_section(document, &tracked_vars, 900, width - 100)?;
210
211 Ok(document)
212}
213
214fn add_memory_section(
216 mut document: Document,
217 tracked_vars: &[&AllocationInfo],
218 _stats: &MemoryStats,
219 start_y: i32,
220 section_height: i32,
221) -> TrackingResult<Document> {
222 let section_bg = Rectangle::new()
224 .set("x", 50)
225 .set("y", start_y - 20)
226 .set("width", 1500)
227 .set("height", section_height)
228 .set("class", "section-bg");
229 document = document.add(section_bg);
230
231 let section_title = SvgText::new("Top 3 Memory Analysis")
233 .set("x", 70)
234 .set("y", start_y + 10)
235 .set("class", "section-title");
236 document = document.add(section_title);
237
238 let mut type_stats: HashMap<String, (usize, usize, Vec<String>)> = HashMap::new();
240 for allocation in tracked_vars {
241 if let Some(var_name) = &allocation.var_name {
242 let type_name = allocation.type_name.as_deref().unwrap_or("Unknown");
243 let simple_type = get_simple_type(type_name);
244
245 let entry = type_stats.entry(simple_type).or_insert((0, 0, Vec::new()));
246 entry.0 += 1;
247 entry.1 += allocation.size;
248 entry
249 .2
250 .push(format!("{}({})", var_name, format_bytes(allocation.size)));
251 }
252 }
253
254 let mut sorted_types: Vec<_> = type_stats.into_iter().collect();
256 sorted_types.sort_by(|a, b| b.1 .1.cmp(&a.1 .1)); sorted_types.truncate(3); let chart_x = 100;
261 let chart_y = start_y + 50;
262 let max_size = sorted_types
263 .iter()
264 .map(|(_, (_, size, _))| *size)
265 .max()
266 .unwrap_or(1);
267
268 for (i, (type_name, (count, total_size, vars))) in sorted_types.iter().enumerate() {
269 let y = chart_y + (i as i32) * 40;
270 let bar_width = ((*total_size as f64 / max_size as f64) * 400.0) as i32;
271
272 let color = get_type_color(type_name);
273 let memory_bar = Rectangle::new()
274 .set("x", chart_x)
275 .set("y", y)
276 .set("width", bar_width)
277 .set("height", 25)
278 .set("fill", color)
279 .set("rx", 4);
280 document = document.add(memory_bar);
281
282 let type_label = SvgText::new(format!("{type_name} ({count} vars)"))
284 .set("x", chart_x + 10)
285 .set("y", y + 17)
286 .set("font-size", 12)
287 .set("font-weight", "bold")
288 .set("fill", "#FFFFFF")
289 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
290 document = document.add(type_label);
291
292 let vars_text = vars.join(" | ");
294 let vars_label = SvgText::new(if vars_text.len() > 80 {
295 format!("{}...", &vars_text[..77])
296 } else {
297 vars_text
298 })
299 .set("x", chart_x + bar_width + 15)
300 .set("y", y + 17)
301 .set("font-size", 11)
302 .set("font-weight", "600")
303 .set("fill", "#FFFFFF")
304 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.6)");
305 document = document.add(vars_label);
306 }
307
308 Ok(document)
310}
311
312fn add_relationships_section(
314 mut document: Document,
315 tracked_vars: &[&AllocationInfo],
316 start_y: i32,
317 section_height: i32,
318) -> TrackingResult<Document> {
319 let section_bg = Rectangle::new()
321 .set("x", 50)
322 .set("y", start_y - 20)
323 .set("width", 1500)
324 .set("height", section_height)
325 .set("class", "section-bg");
326 document = document.add(section_bg);
327
328 let section_title = SvgText::new("Variable Relationships - Ownership & Borrowing")
330 .set("x", 70)
331 .set("y", start_y + 10)
332 .set("class", "section-title");
333 document = document.add(section_title);
334
335 let mut scope_groups: HashMap<String, Vec<&AllocationInfo>> = HashMap::new();
337 for var in tracked_vars {
338 let scope = identify_precise_scope(var);
339 scope_groups.entry(scope).or_default().push(*var);
340 }
341
342 let mut scope_positions = HashMap::new();
344 let start_x = 100;
345 let group_spacing_x = 400;
346 let group_spacing_y = 200;
347
348 for (i, (scope_name, _vars)) in scope_groups.iter().enumerate() {
349 let group_x = start_x + (i % 3) as i32 * group_spacing_x;
350 let group_y = start_y + 50 + (i / 3) as i32 * group_spacing_y;
351
352 let group_bg = Rectangle::new()
354 .set("x", group_x - 20)
355 .set("y", group_y - 20)
356 .set("width", 300)
357 .set("height", 150)
358 .set("fill", get_scope_background_color(scope_name))
359 .set("stroke", get_scope_border_color(scope_name))
360 .set("stroke-width", 2)
361 .set(
362 "stroke-dasharray",
363 if scope_name == "Global" {
364 "none"
365 } else {
366 "5,3"
367 },
368 )
369 .set("rx", 8)
370 .set("opacity", "0.3");
371 document = document.add(group_bg);
372
373 let scope_label = SvgText::new(format!("Scope: {}", scope_name))
375 .set("x", group_x - 10)
376 .set("y", group_y - 5)
377 .set("font-size", 12)
378 .set("font-weight", "bold")
379 .set("fill", "#FFFFFF");
380 document = document.add(scope_label);
381
382 scope_positions.insert(scope_name.clone(), (group_x, group_y));
383 }
384
385 for (scope_name, vars) in &scope_groups {
389 if let Some((group_x, group_y)) = scope_positions.get(scope_name) {
390 for (i, allocation) in vars.iter().take(4).enumerate() {
391 let node_x = group_x + (i % 2) as i32 * 120 + 40;
393 let node_y = group_y + (i / 2) as i32 * 60 + 30;
394
395 document = draw_variable_node(document, allocation, node_x, node_y)?;
396 }
397 }
398 }
399
400 document = add_relationship_legend(document, start_y + section_height - 100)?;
402
403 Ok(document)
404}
405
406fn draw_variable_node(
408 mut document: Document,
409 allocation: &AllocationInfo,
410 x: i32,
411 y: i32,
412) -> TrackingResult<Document> {
413 let var_name = allocation.var_name.as_ref().unwrap();
414 let type_name = allocation.type_name.as_deref().unwrap_or("Unknown");
415 let simple_type = get_simple_type(type_name);
416 let color = get_type_color(&simple_type);
417
418 let node = Circle::new()
420 .set("cx", x)
421 .set("cy", y)
422 .set("r", 20)
423 .set("fill", color)
424 .set("stroke", "#FFFFFF")
425 .set("stroke-width", 2)
426 .set(
427 "title",
428 format!(
429 "{}: {} ({})",
430 var_name,
431 simple_type,
432 format_bytes(allocation.size)
433 ),
434 );
435 document = document.add(node);
436
437 let display_name = if var_name.len() > 8 {
439 format!("{}...", &var_name[..6])
440 } else {
441 var_name.clone()
442 };
443
444 let name_label = SvgText::new(display_name)
445 .set("x", x)
446 .set("y", y + 3)
447 .set("text-anchor", "middle")
448 .set("font-size", 9)
449 .set("font-weight", "bold")
450 .set("fill", "#FFFFFF");
451 document = document.add(name_label);
452
453 let info_text = format!("{} | {}", simple_type, format_bytes(allocation.size));
455 let info_label = SvgText::new(info_text)
456 .set("x", x)
457 .set("y", y + 35)
458 .set("text-anchor", "middle")
459 .set("font-size", 7)
460 .set("fill", "#E2E8F0");
461 document = document.add(info_label);
462
463 Ok(document)
464}
465
466fn add_relationship_legend(mut document: Document, start_y: i32) -> TrackingResult<Document> {
468 let legend_items = [
469 ("Ownership Transfer", "#E74C3C", "solid", "4"),
470 ("Mutable Borrow", "#3498DB", "solid", "3"),
471 ("Immutable Borrow", "#27AE60", "solid", "2"),
472 ("Clone", "#95A5A6", "solid", "2"),
473 ("Shared Pointer", "#9B59B6", "8,4", "3"),
474 ("Indirect Reference", "#F39C12", "4,2", "1"),
475 ];
476
477 for (i, (label, color, dash, width)) in legend_items.iter().enumerate() {
478 let x = 100 + (i % 3) as i32 * 200;
479 let y = start_y + (i / 3) as i32 * 25;
480
481 let legend_line = Line::new()
483 .set("x1", x)
484 .set("y1", y)
485 .set("x2", x + 30)
486 .set("y2", y)
487 .set("stroke", *color)
488 .set("stroke-width", *width)
489 .set("stroke-dasharray", *dash);
490 document = document.add(legend_line);
491
492 let legend_label = SvgText::new(*label)
494 .set("x", x + 35)
495 .set("y", y + 4)
496 .set("font-size", 10)
497 .set("fill", "#FFFFFF");
498 document = document.add(legend_label);
499 }
500
501 Ok(document)
502}
503
504fn get_scope_background_color(scope_name: &str) -> &'static str {
506 match scope_name {
507 "Global" => "rgba(52, 73, 94, 0.2)",
508 _ => "rgba(52, 152, 219, 0.2)",
509 }
510}
511
512fn get_scope_border_color(scope_name: &str) -> &'static str {
514 match scope_name {
515 "Global" => "#34495E",
516 _ => "#3498DB",
517 }
518}
519
520fn calculate_dynamic_matrix_size(var_count: usize) -> (i32, i32) {
522 let standard_width = 350; let standard_height = 280; let card_height = 40; let header_height = 80; let standard_vars = 5;
527
528 if var_count <= standard_vars {
529 let actual_content_height = header_height + (var_count as i32 * card_height) + 40; let width_reduction = ((standard_vars - var_count) * 15) as i32; let actual_width = standard_width - width_reduction;
533
534 (actual_width.max(250), actual_content_height.max(150)) } else {
536 (standard_width, standard_height)
538 }
539}
540
541fn calculate_scope_lifetime(scope_name: &str, vars: &[&AllocationInfo]) -> u64 {
543 if vars.is_empty() {
544 return 0;
545 }
546
547 if scope_name == "Global" {
548 estimate_program_runtime()
550 } else {
551 let start = vars.iter().map(|v| v.timestamp_alloc).min().unwrap_or(0);
553 let end = vars.iter().map(|v| v.timestamp_alloc).max().unwrap_or(0);
554 let span = (end - start) as u64;
555 if span == 0 {
556 100 } else {
559 span
560 }
561 }
562}
563
564fn estimate_program_runtime() -> u64 {
566 2000 }
570
571fn add_prominent_progress_bar_legend(mut document: Document, svg_width: i32) -> Document {
573 let legend_bg = Rectangle::new()
575 .set("x", 50)
576 .set("y", 60)
577 .set("width", svg_width - 100)
578 .set("height", 35)
579 .set("fill", "rgba(252, 211, 77, 0.15)")
580 .set("stroke", "#FCD34D")
581 .set("stroke-width", 2)
582 .set("rx", 8)
583 .set("ry", 8);
584 document = document.add(legend_bg);
585
586 let example_bg = Rectangle::new()
588 .set("x", 70)
589 .set("y", 70)
590 .set("width", 60)
591 .set("height", 8)
592 .set("fill", "rgba(255, 255, 255, 0.2)")
593 .set("rx", 4);
594 document = document.add(example_bg);
595
596 let example_fill = Rectangle::new()
597 .set("x", 70)
598 .set("y", 70)
599 .set("width", 40)
600 .set("height", 8)
601 .set("fill", "#4CAF50")
602 .set("rx", 4);
603 document = document.add(example_fill);
604
605 let legend_text =
607 SvgText::new("📊 Progress Bars show: Variable Size / Largest Variable in Same Scope")
608 .set("x", 150)
609 .set("y", 78)
610 .set("font-size", 14)
611 .set("font-weight", "bold")
612 .set("fill", "#FCD34D")
613 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
614 document = document.add(legend_text);
615
616 let size_example = SvgText::new("Example: 2.4KB / 5.6KB")
618 .set("x", 150)
619 .set("y", 88)
620 .set("font-size", 10)
621 .set("fill", "#E2E8F0")
622 .set("font-style", "italic");
623 document = document.add(size_example);
624
625 document
626}
627
628fn prioritize_scopes_for_display<'a>(
630 scope_groups: &'a HashMap<String, Vec<&'a AllocationInfo>>,
631) -> Vec<(String, Vec<&'a AllocationInfo>)> {
632 let mut scopes_with_priority: Vec<_> = scope_groups
633 .iter()
634 .map(|(name, vars)| {
635 let priority = calculate_scope_priority(name, vars);
636 let total_memory: usize = vars.iter().map(|v| v.size).sum();
637 (name.clone(), vars.clone(), priority, total_memory)
638 })
639 .collect();
640
641 scopes_with_priority.sort_by(|a, b| b.2.cmp(&a.2).then(b.3.cmp(&a.3)));
643
644 scopes_with_priority
645 .into_iter()
646 .map(|(name, vars, _, _)| (name, vars))
647 .collect()
648}
649
650fn calculate_scope_priority(scope_name: &str, vars: &[&AllocationInfo]) -> u8 {
652 let name_lower = scope_name.to_lowercase();
653
654 if name_lower == "global"
656 || name_lower == "main"
657 || name_lower.contains("error")
658 || name_lower.contains("panic")
659 {
660 return 100;
661 }
662
663 if name_lower.contains("process")
665 || name_lower.contains("parse")
666 || name_lower.contains("compute")
667 || name_lower.contains("algorithm")
668 || name_lower.contains("core")
669 || name_lower.contains("engine")
670 {
671 return 80;
672 }
673
674 if name_lower.contains("util")
676 || name_lower.contains("helper")
677 || name_lower.contains("format")
678 || name_lower.contains("convert")
679 {
680 return 60;
681 }
682
683 if name_lower.contains("test")
685 || name_lower.contains("debug")
686 || name_lower.contains("macro")
687 || name_lower.contains("generated")
688 {
689 return 40;
690 }
691
692 let total_memory: usize = vars.iter().map(|v| v.size).sum();
694 let var_count = vars.len();
695
696 if total_memory > 1024 || var_count > 3 {
697 70 } else if total_memory > 256 || var_count > 1 {
699 50 } else {
701 30 }
703}
704
705fn export_scope_analysis_json(
707 all_scopes: &HashMap<String, Vec<&AllocationInfo>>,
708 displayed_scopes: &[(String, Vec<&AllocationInfo>)],
709) -> TrackingResult<()> {
710 use serde_json::{Map, Value};
711
712 let mut analysis = Map::new();
713
714 let mut project_analysis = Map::new();
716 project_analysis.insert(
717 "total_scopes".to_string(),
718 Value::Number((all_scopes.len() as u64).into()),
719 );
720 project_analysis.insert(
721 "displayed_in_svg".to_string(),
722 Value::Number((displayed_scopes.len() as u64).into()),
723 );
724 project_analysis.insert(
725 "exported_to_json".to_string(),
726 Value::Number(((all_scopes.len() - displayed_scopes.len()) as u64).into()),
727 );
728 project_analysis.insert(
729 "layout_strategy".to_string(),
730 Value::String("hierarchical_priority".to_string()),
731 );
732 project_analysis.insert(
733 "generation_timestamp".to_string(),
734 Value::String(
735 std::time::SystemTime::now()
736 .duration_since(std::time::UNIX_EPOCH)
737 .unwrap()
738 .as_secs()
739 .to_string(),
740 ),
741 );
742 analysis.insert(
743 "project_analysis".to_string(),
744 Value::Object(project_analysis),
745 );
746
747 let mut all_scopes_data = Vec::new();
749 for (scope_name, vars) in all_scopes {
750 let total_memory: usize = vars.iter().map(|v| v.size).sum();
751 let is_displayed = displayed_scopes.iter().any(|(name, _)| name == scope_name);
752
753 let mut scope_data = Map::new();
754 scope_data.insert("scope_name".to_string(), Value::String(scope_name.clone()));
755 scope_data.insert(
756 "total_memory".to_string(),
757 Value::Number((total_memory as u64).into()),
758 );
759 scope_data.insert(
760 "variable_count".to_string(),
761 Value::Number((vars.len() as u64).into()),
762 );
763 scope_data.insert(
764 "display_status".to_string(),
765 Value::String(if is_displayed {
766 "shown_in_svg".to_string()
767 } else {
768 "json_only".to_string()
769 }),
770 );
771 scope_data.insert(
772 "priority".to_string(),
773 Value::Number((calculate_scope_priority(scope_name, vars) as u64).into()),
774 );
775
776 let mut variables = Vec::new();
778 for var in vars {
779 if let Some(var_name) = &var.var_name {
780 let mut var_data = Map::new();
781 var_data.insert("name".to_string(), Value::String(var_name.clone()));
782 var_data.insert(
783 "type".to_string(),
784 Value::String(var.type_name.as_deref().unwrap_or("Unknown").to_string()),
785 );
786 var_data.insert(
787 "size_bytes".to_string(),
788 Value::Number((var.size as u64).into()),
789 );
790 var_data.insert(
791 "timestamp".to_string(),
792 Value::Number((var.timestamp_alloc as u64).into()),
793 );
794 variables.push(Value::Object(var_data));
795 }
796 }
797 scope_data.insert("variables".to_string(), Value::Array(variables));
798
799 all_scopes_data.push(Value::Object(scope_data));
800 }
801 analysis.insert("all_scopes".to_string(), Value::Array(all_scopes_data));
802
803 let json_content = serde_json::to_string_pretty(&Value::Object(analysis)).map_err(|e| {
805 TrackingError::SerializationError(format!("JSON serialization failed: {}", e))
806 })?;
807
808 std::fs::write("scope_analysis.json", json_content).map_err(|e| TrackingError::IoError(e))?;
809
810 tracing::info!("Exported complete scope analysis to scope_analysis.json");
811 Ok(())
812}
813
814fn get_duration_color(ratio: f64, is_global: bool) -> String {
819 if is_global {
820 return "#0A2540".to_string();
822 }
823
824 if ratio <= 0.01 {
829 "#F8FAFC".to_string()
831 } else {
832 let base_r = 248; let base_g = 250; let base_b = 252; let target_r = 30; let target_g = 64; let target_b = 175; let smooth_ratio = ratio.powf(0.7); let r = (base_r as f64 + (target_r as f64 - base_r as f64) * smooth_ratio) as u8;
845 let g = (base_g as f64 + (target_g as f64 - base_g as f64) * smooth_ratio) as u8;
846 let b = (base_b as f64 + (target_b as f64 - base_b as f64) * smooth_ratio) as u8;
847
848 format!("#{:02X}{:02X}{:02X}", r, g, b)
849 }
850}
851
852fn add_matrix_layout_section(
854 mut document: Document,
855 tracked_vars: &[&AllocationInfo],
856 start_x: i32,
857 start_y: i32,
858) -> TrackingResult<Document> {
859 let mut scope_groups: HashMap<String, Vec<&AllocationInfo>> = HashMap::new();
861 for var in tracked_vars {
862 let scope = identify_precise_scope(var);
863 scope_groups.entry(scope).or_default().push(*var);
864 }
865
866 let prioritized_scopes = prioritize_scopes_for_display(&scope_groups);
868 let selected_scopes: Vec<_> = prioritized_scopes.into_iter().take(15).collect();
869
870 let max_duration = selected_scopes
878 .iter()
879 .map(|(_, vars)| {
880 if !vars.is_empty() {
881 let start = vars.iter().map(|v| v.timestamp_alloc).min().unwrap_or(0);
882 let end = vars.iter().map(|v| v.timestamp_alloc).max().unwrap_or(0);
883 (end - start) as u64
884 } else {
885 0
886 }
887 })
888 .max()
889 .unwrap_or(1); let base_matrix_width = 350;
893 let base_matrix_height = 180;
894 let spacing_x = 450; let spacing_y = 250;
896
897 let mut positions = Vec::new();
898
899 for (i, (scope_name, _)) in selected_scopes.iter().enumerate() {
901 let col = i % 3;
902 let row = i / 3;
903 let x = start_x + (col as i32 * spacing_x);
904 let y = start_y + (row as i32 * spacing_y);
905 positions.push((scope_name.clone(), x, y));
906 }
907
908 for (i, (scope_name, x, y)) in positions.iter().enumerate() {
910 if scope_name != "Global" && i > 0 {
911 if let Some((_, global_x, global_y)) =
913 positions.iter().find(|(name, _, _)| name == "Global")
914 {
915 let line = Line::new()
916 .set("x1", global_x + base_matrix_width / 2)
917 .set("y1", global_y + base_matrix_height)
918 .set("x2", x + base_matrix_width / 2)
919 .set("y2", *y)
920 .set("stroke", "#7F8C8D")
921 .set("stroke-width", 2)
922 .set("stroke-dasharray", "5,3");
923 document = document.add(line);
924 }
925 }
926 }
927
928 for ((scope_name, vars), (_, x, y)) in selected_scopes.iter().zip(positions.iter()) {
930 document = render_scope_matrix_fixed(
931 document,
932 scope_name,
933 vars,
934 *x,
935 *y,
936 base_matrix_width,
937 base_matrix_height,
938 max_duration,
939 )?;
940 }
941
942 if scope_groups.len() > 15 {
944 export_scope_analysis_json(&scope_groups, &selected_scopes)?;
945 }
946
947 Ok(document)
948}
949
950fn render_scope_matrix_fixed(
952 mut document: Document,
953 scope_name: &str,
954 vars: &[&AllocationInfo],
955 x: i32,
956 y: i32,
957 width: i32,
958 height: i32,
959 max_duration: u64, ) -> TrackingResult<Document> {
961 let (dynamic_width, dynamic_height) = calculate_dynamic_matrix_size(vars.len());
963 let actual_width = dynamic_width.max(width);
964 let actual_height = dynamic_height.max(height);
965
966 let mut matrix_group = Group::new().set("transform", format!("translate({}, {})", x, y));
967
968 let duration = calculate_scope_lifetime(scope_name, vars);
970 let total_memory = vars.iter().map(|v| v.size).sum::<usize>();
971 let _peak_memory = vars.iter().map(|v| v.size).max().unwrap_or(0);
972 let active_vars = vars.len();
973
974 let duration_ratio = if max_duration > 0 {
976 duration as f64 / max_duration as f64
977 } else {
978 0.0
979 };
980
981 let is_global = scope_name == "Global";
983 let border_color = get_duration_color(duration_ratio, is_global);
984
985 let container = Rectangle::new()
987 .set("width", actual_width)
988 .set("height", actual_height)
989 .set("fill", "rgba(30, 64, 175, 0.1)")
990 .set("stroke", border_color.as_str())
991 .set("stroke-width", 3)
992 .set(
993 "stroke-dasharray",
994 if scope_name != "Global" {
995 "8,4"
996 } else {
997 "none"
998 },
999 )
1000 .set("rx", 12);
1001 matrix_group = matrix_group.add(container);
1002
1003 let header_text = format!(
1005 "Scope: {} | Memory: {} | Variables: {} | Lifetime: {}ms",
1006 scope_name,
1007 format_bytes(total_memory),
1008 active_vars,
1009 duration
1010 );
1011 let enhanced_title = SvgText::new(header_text)
1012 .set("x", 15)
1013 .set("y", 25)
1014 .set("font-size", 11)
1015 .set("font-weight", "700")
1016 .set("fill", "#f8fafc");
1017 matrix_group = matrix_group.add(enhanced_title);
1018
1019 let var_start_y = 45;
1021 let card_height = 45; let var_spacing = 50; let _font_size = 10;
1024
1025 for (i, var) in vars.iter().take(4).enumerate() {
1026 let var_y = var_start_y + (i as i32 * var_spacing);
1028 let var_name = var.var_name.as_ref().unwrap();
1029 let type_name = get_simple_type(var.type_name.as_ref().unwrap_or(&"Unknown".to_string()));
1030 let duration_ms = estimate_variable_duration(var);
1031
1032 let max_size_in_scope = vars.iter().map(|v| v.size).max().unwrap_or(1);
1034 let progress_ratio = var.size as f64 / max_size_in_scope as f64;
1035 let _progress_width = (progress_ratio * 180.0) as i32;
1036
1037 let card_width = actual_width - 20;
1039 let card_bg = Rectangle::new()
1040 .set("x", 10)
1041 .set("y", var_y - 5)
1042 .set("width", card_width)
1043 .set("height", card_height)
1044 .set("fill", "rgba(255, 255, 255, 0.08)")
1045 .set("stroke", "rgba(255, 255, 255, 0.15)")
1046 .set("stroke-width", 1)
1047 .set("rx", 8)
1048 .set("ry", 8);
1049 matrix_group = matrix_group.add(card_bg);
1050
1051 let var_label = SvgText::new(var_name.clone())
1053 .set("x", 18)
1054 .set("y", var_y + 8)
1055 .set("font-size", 12)
1056 .set("font-weight", "bold")
1057 .set("fill", "#FFFFFF")
1058 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
1059 matrix_group = matrix_group.add(var_label);
1060
1061 let (type_start_color, _) = get_type_gradient_colors(&type_name);
1063 let type_label = SvgText::new(format!("({})", type_name))
1064 .set("x", 18)
1065 .set("y", var_y + 22)
1066 .set("font-size", 9)
1067 .set("fill", type_start_color)
1068 .set("font-weight", "600");
1069 matrix_group = matrix_group.add(type_label);
1070
1071 let available_width = card_width - 40; let progress_bar_width = (available_width as f64 * 0.5) as i32; let progress_x = 20; let progress_bg = Rectangle::new()
1077 .set("x", progress_x)
1078 .set("y", var_y + 15) .set("width", progress_bar_width)
1080 .set("height", 8)
1081 .set("fill", "rgba(255, 255, 255, 0.1)")
1082 .set("stroke", "rgba(255, 255, 255, 0.2)")
1083 .set("stroke-width", 1)
1084 .set("rx", 4)
1085 .set("ry", 4);
1086 matrix_group = matrix_group.add(progress_bg);
1087
1088 let (start_color, _) = get_type_gradient_colors(&type_name);
1090 let progress_fill_width = (progress_ratio * progress_bar_width as f64) as i32;
1091 let progress_fill = Rectangle::new()
1092 .set("x", progress_x)
1093 .set("y", var_y + 15)
1094 .set("width", progress_fill_width)
1095 .set("height", 8)
1096 .set("fill", start_color) .set("rx", 4)
1098 .set("ry", 4);
1099 matrix_group = matrix_group.add(progress_fill);
1100
1101 let size_display = format!(
1103 "{} / {}",
1104 format_bytes(var.size),
1105 format_bytes(max_size_in_scope)
1106 );
1107 let size_label = SvgText::new(size_display)
1108 .set("x", progress_x + progress_bar_width + 10)
1109 .set("y", var_y + 20)
1110 .set("font-size", 8)
1111 .set("font-weight", "bold")
1112 .set("fill", "#E2E8F0");
1113 matrix_group = matrix_group.add(size_label);
1114
1115 let time_label = SvgText::new(format!("Active {}ms", duration_ms))
1117 .set("x", 20)
1118 .set("y", var_y + 30)
1119 .set("font-size", 7)
1120 .set("fill", "#FCD34D")
1121 .set("font-weight", "500");
1122 matrix_group = matrix_group.add(time_label);
1123 }
1124
1125 if vars.len() > 4 {
1127 let more_text = format!("+ {} more variables", vars.len() - 4);
1128 let more_label = SvgText::new(more_text)
1129 .set("x", 20)
1130 .set("y", var_start_y + (4 * var_spacing) + 10)
1131 .set("font-size", 9)
1132 .set("font-weight", "500")
1133 .set("fill", "#94A3B8")
1134 .set("font-style", "italic");
1135 matrix_group = matrix_group.add(more_label);
1136 }
1137
1138 let explanation_y = actual_height - 15;
1140 let explanation_text = "Progress Bar: Current Size / Max Size in Scope";
1141 let explanation = SvgText::new(explanation_text)
1142 .set("x", 15)
1143 .set("y", explanation_y)
1144 .set("font-size", 8)
1145 .set("font-weight", "500")
1146 .set("fill", "#FCD34D")
1147 .set("font-style", "italic");
1148 matrix_group = matrix_group.add(explanation);
1149
1150 document = document.add(matrix_group);
1151 Ok(document)
1152}
1153
1154fn identify_precise_scope(allocation: &AllocationInfo) -> String {
1156 if let Some(var_name) = &allocation.var_name {
1157 if var_name.contains("global") {
1158 return "Global".to_string();
1159 }
1160 match allocation.timestamp_alloc {
1162 0..=1000 => "Global".to_string(),
1163 1001..=2000 => "demonstrate_builtin_types".to_string(),
1164 2001..=3000 => "demonstrate_smart_pointers".to_string(),
1165 3001..=4000 => "demonstrate_custom_structures".to_string(),
1166 4001..=5000 => "demonstrate_complex_patterns".to_string(),
1167 5001..=6000 => "simulate_web_server_scenario".to_string(),
1168 _ => "simulate_data_processing_pipeline".to_string(),
1169 }
1170 } else {
1171 "Global".to_string()
1172 }
1173}
1174
1175fn estimate_variable_duration(var: &AllocationInfo) -> u64 {
1177 let base_duration = match var.size {
1178 0..=100 => 10,
1179 101..=1000 => 50,
1180 1001..=10000 => 100,
1181 _ => 200,
1182 };
1183
1184 let type_multiplier = if let Some(type_name) = &var.type_name {
1185 if type_name.contains("Vec") || type_name.contains("HashMap") {
1186 2.0
1187 } else if type_name.contains("Box") || type_name.contains("Rc") {
1188 1.5
1189 } else {
1190 1.0
1191 }
1192 } else {
1193 1.0
1194 };
1195
1196 (base_duration as f64 * type_multiplier) as u64
1197}