1use crate::core::tracker::MemoryTracker;
5use crate::core::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::{
11 Circle, Definitions, Group, Line, Marker, Polygon, Rectangle, Style, Text as SvgText,
12};
13use svg::Document;
14
15use crate::unsafe_ffi_tracker::{
16 AllocationSource, BoundaryEventType, EnhancedAllocationInfo, SafetyViolation, UnsafeFFITracker,
17};
18
19pub fn export_memory_analysis<P: AsRef<Path>>(
21 tracker: &MemoryTracker,
22 path: P,
23) -> TrackingResult<()> {
24 let path = path.as_ref();
25 tracing::info!("Exporting memory analysis to: {}", path.display());
26
27 if let Some(parent) = path.parent() {
28 if !parent.exists() {
29 std::fs::create_dir_all(parent)?;
30 }
31 }
32
33 let stats = tracker.get_stats()?;
35 let active_allocations = tracker.get_active_allocations()?;
36
37 tracing::info!(
39 "SVG Export - Using peak_memory: {} bytes ({})",
40 stats.peak_memory,
41 crate::utils::format_bytes(stats.peak_memory)
42 );
43
44 let document = create_memory_analysis_svg(&active_allocations, &stats, tracker)?;
45
46 let mut file = File::create(path)?;
47 svg::write(&mut file, &document)
48 .map_err(|e| TrackingError::SerializationError(format!("Failed to write SVG: {e}")))?;
49
50 tracing::info!("Successfully exported memory analysis SVG");
51 Ok(())
52}
53
54pub fn export_lifecycle_timeline<P: AsRef<Path>>(
56 tracker: &MemoryTracker,
57 path: P,
58) -> TrackingResult<()> {
59 let path = path.as_ref();
60 tracing::info!("Exporting lifecycle timeline to: {}", path.display());
61
62 if let Some(parent) = path.parent() {
63 if !parent.exists() {
64 std::fs::create_dir_all(parent)?;
65 }
66 }
67
68 let active_allocations = tracker.get_active_allocations()?;
69 let stats = tracker.get_stats()?;
70
71 let document = create_lifecycle_timeline_svg(&active_allocations, &stats)?;
72
73 let mut file = File::create(path)?;
74 svg::write(&mut file, &document)
75 .map_err(|e| TrackingError::SerializationError(format!("Failed to write SVG: {e}")))?;
76
77 tracing::info!("Successfully exported lifecycle timeline SVG");
78 Ok(())
79}
80
81fn create_memory_analysis_svg(
83 allocations: &[AllocationInfo],
84 stats: &MemoryStats,
85 tracker: &MemoryTracker,
86) -> TrackingResult<Document> {
87 let width = 1800;
89 let height = 2000; let mut document = Document::new()
92 .set("viewBox", (0, 0, width, height))
93 .set("width", width)
94 .set("height", height)
95 .set("style", "background: linear-gradient(135deg, #ecf0f1 0%, #bdc3c7 100%); font-family: 'Segoe UI', Arial, sans-serif;");
96
97 document = crate::export_enhanced::add_enhanced_header(document, stats, allocations)?;
99
100 document = crate::export_enhanced::add_memory_heatmap(document, allocations)?;
106
107 let memory_by_type_data = tracker.get_memory_by_type().unwrap_or_default();
110 let memory_by_type = enhance_type_information(&memory_by_type_data, allocations);
111 document = crate::export_enhanced::add_enhanced_type_chart(document, &memory_by_type)?;
112
113 document = crate::export_enhanced::add_fragmentation_analysis(document, allocations)?;
115
116 let categorized = categorize_enhanced_allocations(&memory_by_type);
119 document = crate::export_enhanced::add_categorized_allocations(document, &categorized)?;
120
121 document = crate::export_enhanced::add_callstack_analysis(document, allocations)?;
123
124 document = crate::export_enhanced::add_memory_growth_trends(document, allocations, stats)?;
126
127 document = crate::export_enhanced::add_memory_timeline(document, allocations, stats)?;
129
130 document = crate::export_enhanced::add_interactive_legend(document)?;
132
133 document = crate::export_enhanced::add_comprehensive_summary(document, stats, allocations)?;
135
136 Ok(document)
137}
138
139fn create_lifecycle_timeline_svg(
141 allocations: &[AllocationInfo],
142 stats: &MemoryStats,
143) -> TrackingResult<Document> {
144 let width = 1600;
145 let height = 1200;
146
147 let mut document = Document::new()
148 .set("viewBox", (0, 0, width, height))
149 .set("width", width)
150 .set("height", height)
151 .set("style", "background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); font-family: 'Inter', 'Segoe UI', sans-serif;");
152
153 let styles = Style::new(
155 r#"
156 .timeline-bar { transition: all 0.3s ease; cursor: pointer; }
157 .timeline-bar:hover { stroke: #FFFFFF; stroke-width: 3; filter: drop-shadow(0 0 12px rgba(255,255,255,0.8)); }
158 .variable-label { fill: #FFFFFF; font-size: 13px; font-weight: 600; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
159 .memory-label { fill: #E2E8F0; font-size: 11px; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
160 .section-title { fill: #FFFFFF; font-size: 20px; font-weight: 700; text-shadow: 2px 2px 4px rgba(0,0,0,0.5); }
161 .section-bg { fill: rgba(255,255,255,0.1); stroke: rgba(255,255,255,0.2); stroke-width: 1; rx: 12; }
162 "#,
163 );
164 document = document.add(styles);
165
166 let title = SvgText::new("Scope Matrix & Lifecycle Visualization")
168 .set("x", width / 2)
169 .set("y", 40)
170 .set("text-anchor", "middle")
171 .set("font-size", 32)
172 .set("font-weight", "bold")
173 .set("fill", "#FFFFFF")
174 .set("style", "text-shadow: 3px 3px 6px rgba(0,0,0,0.5);");
175 document = document.add(title);
176
177 document = add_prominent_progress_bar_legend(document, width);
179
180 let tracked_vars: Vec<_> = allocations
181 .iter()
182 .filter(|a| a.var_name.is_some())
183 .collect();
184
185 if tracked_vars.is_empty() {
202 let no_data = SvgText::new(format!(
203 "No tracked variables found (checked {} allocations)",
204 allocations.len()
205 ))
206 .set("x", width / 2)
207 .set("y", height / 2)
208 .set("text-anchor", "middle")
209 .set("font-size", 18)
210 .set("fill", "#FFFFFF");
211 document = document.add(no_data);
212 return Ok(document);
213 }
214
215 document = add_matrix_layout_section(document, &tracked_vars, 50, 130)?;
217
218 document = add_memory_section(document, &tracked_vars, stats, 550, width - 100)?;
220
221 document = add_relationships_section(document, &tracked_vars, 900, width - 100)?;
223
224 Ok(document)
225}
226
227fn add_memory_section(
229 mut document: Document,
230 tracked_vars: &[&AllocationInfo],
231 _stats: &MemoryStats,
232 start_y: i32,
233 section_height: i32,
234) -> TrackingResult<Document> {
235 let section_bg = Rectangle::new()
237 .set("x", 50)
238 .set("y", start_y - 20)
239 .set("width", 1500)
240 .set("height", section_height)
241 .set("class", "section-bg");
242 document = document.add(section_bg);
243
244 let section_title = SvgText::new("Top 3 Memory Analysis")
246 .set("x", 70)
247 .set("y", start_y + 10)
248 .set("class", "section-title");
249 document = document.add(section_title);
250
251 let mut type_stats: HashMap<String, (usize, usize, Vec<String>)> = HashMap::new();
253 for allocation in tracked_vars {
254 if let Some(var_name) = &allocation.var_name {
255 let type_name = allocation.type_name.as_deref().unwrap_or("Unknown");
256 let simple_type = get_simple_type(type_name);
257
258 let entry = type_stats.entry(simple_type).or_insert((0, 0, Vec::new()));
259 entry.0 += 1;
260 entry.1 += allocation.size;
261 entry
262 .2
263 .push(format!("{}({})", var_name, format_bytes(allocation.size)));
264 }
265 }
266
267 let mut sorted_types: Vec<_> = type_stats.into_iter().collect();
269 sorted_types.sort_by(|a, b| b.1 .1.cmp(&a.1 .1)); sorted_types.truncate(3); let chart_x = 100;
274 let chart_y = start_y + 50;
275 let max_size = sorted_types
276 .iter()
277 .map(|(_, (_, size, _))| *size)
278 .max()
279 .unwrap_or(1);
280
281 for (i, (type_name, (count, total_size, vars))) in sorted_types.iter().enumerate() {
282 let y = chart_y + (i as i32) * 40;
283 let bar_width = ((*total_size as f64 / max_size as f64) * 400.0) as i32;
284
285 let color = get_type_color(type_name);
286 let memory_bar = Rectangle::new()
287 .set("x", chart_x)
288 .set("y", y)
289 .set("width", bar_width)
290 .set("height", 25)
291 .set("fill", color)
292 .set("rx", 4);
293 document = document.add(memory_bar);
294
295 let type_label = SvgText::new(format!("{type_name} ({count} vars)"))
297 .set("x", chart_x + 10)
298 .set("y", y + 17)
299 .set("font-size", 12)
300 .set("font-weight", "bold")
301 .set("fill", "#FFFFFF")
302 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
303 document = document.add(type_label);
304
305 let vars_text = vars.join(" | ");
307 let vars_label = SvgText::new(if vars_text.len() > 80 {
308 format!("{}...", &vars_text[..77])
309 } else {
310 vars_text
311 })
312 .set("x", chart_x + bar_width + 15)
313 .set("y", y + 17)
314 .set("font-size", 11)
315 .set("font-weight", "600")
316 .set("fill", "#FFFFFF")
317 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.6)");
318 document = document.add(vars_label);
319 }
320
321 Ok(document)
323}
324
325fn add_relationships_section(
327 mut document: Document,
328 tracked_vars: &[&AllocationInfo],
329 start_y: i32,
330 section_height: i32,
331) -> TrackingResult<Document> {
332 let section_bg = Rectangle::new()
334 .set("x", 50)
335 .set("y", start_y - 20)
336 .set("width", 1500)
337 .set("height", section_height)
338 .set("class", "section-bg");
339 document = document.add(section_bg);
340
341 let section_title = SvgText::new("Variable Relationships - Ownership & Borrowing")
343 .set("x", 70)
344 .set("y", start_y + 10)
345 .set("class", "section-title");
346 document = document.add(section_title);
347
348 let mut scope_groups: HashMap<String, Vec<&AllocationInfo>> = HashMap::new();
350 for var in tracked_vars {
351 let scope = identify_precise_scope(var);
352 scope_groups.entry(scope).or_default().push(*var);
353 }
354
355 let mut scope_positions = HashMap::new();
357 let start_x = 100;
358 let group_spacing_x = 400;
359 let group_spacing_y = 200;
360
361 for (i, (scope_name, _vars)) in scope_groups.iter().enumerate() {
362 let group_x = start_x + (i % 3) as i32 * group_spacing_x;
363 let group_y = start_y + 50 + (i / 3) as i32 * group_spacing_y;
364
365 let group_bg = Rectangle::new()
367 .set("x", group_x - 20)
368 .set("y", group_y - 20)
369 .set("width", 300)
370 .set("height", 150)
371 .set("fill", get_scope_background_color(scope_name))
372 .set("stroke", get_scope_border_color(scope_name))
373 .set("stroke-width", 2)
374 .set(
375 "stroke-dasharray",
376 if scope_name == "Global" {
377 "none"
378 } else {
379 "5,3"
380 },
381 )
382 .set("rx", 8)
383 .set("opacity", "0.3");
384 document = document.add(group_bg);
385
386 let scope_label = SvgText::new(format!("Scope: {scope_name}"))
388 .set("x", group_x - 10)
389 .set("y", group_y - 5)
390 .set("font-size", 12)
391 .set("font-weight", "bold")
392 .set("fill", "#FFFFFF");
393 document = document.add(scope_label);
394
395 scope_positions.insert(scope_name, (group_x, group_y));
396 }
397
398 for (scope_name, vars) in &scope_groups {
402 if let Some((group_x, group_y)) = scope_positions.get(scope_name) {
403 for (i, allocation) in vars.iter().take(4).enumerate() {
404 let node_x = group_x + (i % 2) as i32 * 120 + 40;
406 let node_y = group_y + (i / 2) as i32 * 60 + 30;
407
408 document = draw_variable_node(document, allocation, node_x, node_y)?;
409 }
410 }
411 }
412
413 document = add_relationship_legend(document, start_y + section_height - 100)?;
415
416 Ok(document)
417}
418
419fn draw_variable_node(
421 mut document: Document,
422 allocation: &AllocationInfo,
423 x: i32,
424 y: i32,
425) -> TrackingResult<Document> {
426 let var_name = allocation.var_name.as_ref().unwrap();
427 let type_name = allocation.type_name.as_deref().unwrap_or("Unknown");
428 let simple_type = get_simple_type(type_name);
429 let color = get_type_color(&simple_type);
430
431 let node = Circle::new()
433 .set("cx", x)
434 .set("cy", y)
435 .set("r", 20)
436 .set("fill", color)
437 .set("stroke", "#FFFFFF")
438 .set("stroke-width", 2)
439 .set(
440 "title",
441 format!(
442 "{}: {} ({})",
443 var_name,
444 simple_type,
445 format_bytes(allocation.size)
446 ),
447 );
448 document = document.add(node);
449
450 let display_name = if var_name.len() > 8 {
452 format!("{}...", &var_name[..6])
453 } else {
454 var_name.to_string()
455 };
456
457 let name_label = SvgText::new(display_name)
458 .set("x", x)
459 .set("y", y + 3)
460 .set("text-anchor", "middle")
461 .set("font-size", 9)
462 .set("font-weight", "bold")
463 .set("fill", "#FFFFFF");
464 document = document.add(name_label);
465
466 let info_text = format!("{} | {}", simple_type, format_bytes(allocation.size));
468 let info_label = SvgText::new(info_text)
469 .set("x", x)
470 .set("y", y + 35)
471 .set("text-anchor", "middle")
472 .set("font-size", 7)
473 .set("fill", "#E2E8F0");
474 document = document.add(info_label);
475
476 Ok(document)
477}
478
479fn add_relationship_legend(mut document: Document, start_y: i32) -> TrackingResult<Document> {
481 let legend_items = [
482 ("Ownership Transfer", "#E74C3C", "solid", "4"),
483 ("Mutable Borrow", "#3498DB", "solid", "3"),
484 ("Immutable Borrow", "#27AE60", "solid", "2"),
485 ("Clone", "#95A5A6", "solid", "2"),
486 ("Shared Pointer", "#9B59B6", "8,4", "3"),
487 ("Indirect Reference", "#F39C12", "4,2", "1"),
488 ];
489
490 for (i, (label, color, dash, width)) in legend_items.iter().enumerate() {
491 let x = 100 + (i % 3) as i32 * 200;
492 let y = start_y + (i / 3) as i32 * 25;
493
494 let legend_line = Line::new()
496 .set("x1", x)
497 .set("y1", y)
498 .set("x2", x + 30)
499 .set("y2", y)
500 .set("stroke", *color)
501 .set("stroke-width", *width)
502 .set("stroke-dasharray", *dash);
503 document = document.add(legend_line);
504
505 let legend_label = SvgText::new(*label)
507 .set("x", x + 35)
508 .set("y", y + 4)
509 .set("font-size", 10)
510 .set("fill", "#FFFFFF");
511 document = document.add(legend_label);
512 }
513
514 Ok(document)
515}
516
517fn get_scope_background_color(scope_name: &str) -> &'static str {
519 match scope_name {
520 "Global" => "rgba(52, 73, 94, 0.2)",
521 _ => "rgba(52, 152, 219, 0.2)",
522 }
523}
524
525fn get_scope_border_color(scope_name: &str) -> &'static str {
527 match scope_name {
528 "Global" => "#34495E",
529 _ => "#3498DB",
530 }
531}
532
533fn calculate_dynamic_matrix_size(var_count: usize) -> (i32, i32) {
535 let standard_width = 350; let standard_height = 280; let card_height = 40; let header_height = 80; let standard_vars = 5;
540
541 if var_count <= standard_vars {
542 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;
546
547 (actual_width.max(250), actual_content_height.max(150)) } else {
549 (standard_width, standard_height)
551 }
552}
553
554fn calculate_scope_lifetime(scope_name: &str, vars: &[&AllocationInfo]) -> u64 {
556 if vars.is_empty() {
557 return 0;
558 }
559
560 if scope_name == "Global" {
561 estimate_program_runtime()
563 } else {
564 let start = vars.iter().map(|v| v.timestamp_alloc).min().unwrap_or(0);
566 let end = vars.iter().map(|v| v.timestamp_alloc).max().unwrap_or(0);
567 let span = end - start;
568 if span == 0 {
569 100 } else {
572 span
573 }
574 }
575}
576
577fn estimate_program_runtime() -> u64 {
579 2000 }
583
584fn add_prominent_progress_bar_legend(mut document: Document, svg_width: i32) -> Document {
586 let legend_bg = Rectangle::new()
588 .set("x", 50)
589 .set("y", 60)
590 .set("width", svg_width - 100)
591 .set("height", 35)
592 .set("fill", "rgba(252, 211, 77, 0.15)")
593 .set("stroke", "#FCD34D")
594 .set("stroke-width", 2)
595 .set("rx", 8)
596 .set("ry", 8);
597 document = document.add(legend_bg);
598
599 let example_bg = Rectangle::new()
601 .set("x", 70)
602 .set("y", 70)
603 .set("width", 60)
604 .set("height", 8)
605 .set("fill", "rgba(255, 255, 255, 0.2)")
606 .set("rx", 4);
607 document = document.add(example_bg);
608
609 let example_fill = Rectangle::new()
610 .set("x", 70)
611 .set("y", 70)
612 .set("width", 40)
613 .set("height", 8)
614 .set("fill", "#4CAF50")
615 .set("rx", 4);
616 document = document.add(example_fill);
617
618 let legend_text =
620 SvgText::new("📊 Progress Bars show: Variable Size / Largest Variable in Same Scope")
621 .set("x", 150)
622 .set("y", 78)
623 .set("font-size", 14)
624 .set("font-weight", "bold")
625 .set("fill", "#FCD34D")
626 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
627 document = document.add(legend_text);
628
629 let size_example = SvgText::new("Example: 2.4KB / 5.6KB")
631 .set("x", 150)
632 .set("y", 88)
633 .set("font-size", 10)
634 .set("fill", "#E2E8F0")
635 .set("font-style", "italic");
636 document = document.add(size_example);
637
638 document
639}
640
641fn prioritize_scopes_for_display<'a>(
643 scope_groups: &'a HashMap<String, Vec<&'a AllocationInfo>>,
644) -> Vec<(String, Vec<&'a AllocationInfo>)> {
645 let mut scopes_with_priority: Vec<_> = scope_groups
646 .iter()
647 .map(|(name, vars)| {
648 let priority = calculate_scope_priority(name, vars);
649 let total_memory: usize = vars.iter().map(|v| v.size).sum();
650 (name, vars, priority, total_memory)
651 })
652 .collect();
653
654 scopes_with_priority.sort_by(|a, b| b.2.cmp(&a.2).then(b.3.cmp(&a.3)));
656
657 scopes_with_priority
658 .into_iter()
659 .map(|(name, vars, _, _)| (name.clone(), vars.clone()))
660 .collect()
661}
662
663fn calculate_scope_priority(scope_name: &str, vars: &[&AllocationInfo]) -> u8 {
665 let name_lower = scope_name.to_lowercase();
666
667 if name_lower == "global"
669 || name_lower == "main"
670 || name_lower.contains("error")
671 || name_lower.contains("panic")
672 {
673 return 100;
674 }
675
676 if name_lower.contains("process")
678 || name_lower.contains("parse")
679 || name_lower.contains("compute")
680 || name_lower.contains("algorithm")
681 || name_lower.contains("core")
682 || name_lower.contains("engine")
683 {
684 return 80;
685 }
686
687 if name_lower.contains("util")
689 || name_lower.contains("helper")
690 || name_lower.contains("format")
691 || name_lower.contains("convert")
692 {
693 return 60;
694 }
695
696 if name_lower.contains("test")
698 || name_lower.contains("debug")
699 || name_lower.contains("macro")
700 || name_lower.contains("generated")
701 {
702 return 40;
703 }
704
705 let total_memory: usize = vars.iter().map(|v| v.size).sum();
707 let var_count = vars.len();
708
709 if total_memory > 1024 || var_count > 3 {
710 70 } else if total_memory > 256 || var_count > 1 {
712 50 } else {
714 30 }
716}
717
718fn export_scope_analysis_json(
720 all_scopes: &HashMap<String, Vec<&AllocationInfo>>,
721 displayed_scopes: &[(String, Vec<&AllocationInfo>)],
722) -> TrackingResult<()> {
723 use serde_json::{Map, Value};
724
725 let mut analysis = Map::new();
726
727 let mut project_analysis = Map::new();
729 project_analysis.insert(
730 "total_scopes".to_string(),
731 Value::Number((all_scopes.len() as u64).into()),
732 );
733 project_analysis.insert(
734 "displayed_in_svg".to_string(),
735 Value::Number((displayed_scopes.len() as u64).into()),
736 );
737 project_analysis.insert(
738 "exported_to_json".to_string(),
739 Value::Number(((all_scopes.len() - displayed_scopes.len()) as u64).into()),
740 );
741 project_analysis.insert(
742 "layout_strategy".to_string(),
743 Value::String("hierarchical_priority".to_string()),
744 );
745 project_analysis.insert(
746 "generation_timestamp".to_string(),
747 Value::String(
748 std::time::SystemTime::now()
749 .duration_since(std::time::UNIX_EPOCH)
750 .unwrap()
751 .as_secs()
752 .to_string(),
753 ),
754 );
755 analysis.insert(
756 "project_analysis".to_string(),
757 Value::Object(project_analysis),
758 );
759
760 let mut all_scopes_data = Vec::new();
762 for (scope_name, vars) in all_scopes {
763 let total_memory: usize = vars.iter().map(|v| v.size).sum();
764 let is_displayed = displayed_scopes.iter().any(|(name, _)| name == scope_name);
765
766 let mut scope_data = Map::new();
767 scope_data.insert(
768 "scope_name".to_string(),
769 Value::String(scope_name.to_string()),
770 );
771 scope_data.insert(
772 "total_memory".to_string(),
773 Value::Number((total_memory as u64).into()),
774 );
775 scope_data.insert(
776 "variable_count".to_string(),
777 Value::Number((vars.len() as u64).into()),
778 );
779 scope_data.insert(
780 "display_status".to_string(),
781 Value::String(if is_displayed {
782 "shown_in_svg".to_string()
783 } else {
784 "json_only".to_string()
785 }),
786 );
787 scope_data.insert(
788 "priority".to_string(),
789 Value::Number((calculate_scope_priority(scope_name, vars) as u64).into()),
790 );
791
792 let mut variables = Vec::new();
794 for var in vars {
795 if let Some(var_name) = &var.var_name {
796 let mut var_data = Map::new();
797 var_data.insert("name".to_string(), Value::String(var_name.to_string()));
798 var_data.insert(
799 "type".to_string(),
800 Value::String(var.type_name.as_deref().unwrap_or("Unknown").to_string()),
801 );
802 var_data.insert(
803 "size_bytes".to_string(),
804 Value::Number((var.size as u64).into()),
805 );
806 var_data.insert(
807 "timestamp".to_string(),
808 Value::Number(var.timestamp_alloc.into()),
809 );
810 variables.push(Value::Object(var_data));
811 }
812 }
813 scope_data.insert("variables".to_string(), Value::Array(variables));
814
815 all_scopes_data.push(Value::Object(scope_data));
816 }
817 analysis.insert("all_scopes".to_string(), Value::Array(all_scopes_data));
818
819 let json_content = serde_json::to_string(&Value::Object(analysis)).map_err(|e| {
821 TrackingError::SerializationError(format!("JSON serialization failed: {e}"))
822 })?;
823
824 std::fs::write("scope_analysis.json", json_content)
825 .map_err(|e| TrackingError::IoError(e.to_string()))?;
826
827 tracing::info!("Exported complete scope analysis to scope_analysis.json");
828 Ok(())
829}
830
831fn get_duration_color(ratio: f64, is_global: bool) -> String {
836 if is_global {
837 return "#0A2540".to_string();
839 }
840
841 if ratio <= 0.01 {
846 "#F8FAFC".to_string()
848 } else {
849 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;
862 let g = (base_g as f64 + (target_g as f64 - base_g as f64) * smooth_ratio) as u8;
863 let b = (base_b as f64 + (target_b as f64 - base_b as f64) * smooth_ratio) as u8;
864
865 format!("#{r:02X}{g:02X}{b:02X}")
866 }
867}
868
869fn add_matrix_layout_section(
871 mut document: Document,
872 tracked_vars: &[&AllocationInfo],
873 start_x: i32,
874 start_y: i32,
875) -> TrackingResult<Document> {
876 let mut scope_groups: HashMap<String, Vec<&AllocationInfo>> = HashMap::new();
878 for var in tracked_vars {
879 let scope = identify_precise_scope(var);
880 scope_groups.entry(scope).or_default().push(*var);
881 }
882
883 let prioritized_scopes = prioritize_scopes_for_display(&scope_groups);
885 let selected_scopes: Vec<_> = prioritized_scopes.into_iter().take(15).collect();
886
887 let max_duration = selected_scopes
895 .iter()
896 .map(|(_, vars)| {
897 if !vars.is_empty() {
898 let start = vars.iter().map(|v| v.timestamp_alloc).min().unwrap_or(0);
899 let end = vars.iter().map(|v| v.timestamp_alloc).max().unwrap_or(0);
900 end - start
901 } else {
902 0
903 }
904 })
905 .max()
906 .unwrap_or(1); let base_matrix_width = 350;
910 let base_matrix_height = 180;
911 let spacing_x = 450; let spacing_y = 250;
913
914 let mut positions = Vec::new();
915
916 for (i, (scope_name, _)) in selected_scopes.iter().enumerate() {
918 let col = i % 3;
919 let row = i / 3;
920 let x = start_x + (col as i32 * spacing_x);
921 let y = start_y + (row as i32 * spacing_y);
922 positions.push((scope_name, x, y));
923 }
924
925 for (i, (scope_name, x, y)) in positions.iter().enumerate() {
927 if *scope_name != "Global" && i > 0 {
928 if let Some((_, global_x, global_y)) =
930 positions.iter().find(|(name, _, _)| *name == "Global")
931 {
932 let line = Line::new()
933 .set("x1", global_x + base_matrix_width / 2)
934 .set("y1", global_y + base_matrix_height)
935 .set("x2", x + base_matrix_width / 2)
936 .set("y2", *y)
937 .set("stroke", "#7F8C8D")
938 .set("stroke-width", 2)
939 .set("stroke-dasharray", "5,3");
940 document = document.add(line);
941 }
942 }
943 }
944
945 for ((scope_name, vars), (_, x, y)) in selected_scopes.iter().zip(positions.iter()) {
947 document = render_scope_matrix_fixed(
948 document,
949 scope_name,
950 vars,
951 *x,
952 *y,
953 base_matrix_width,
954 base_matrix_height,
955 max_duration,
956 )?;
957 }
958
959 if scope_groups.len() > 15 {
961 export_scope_analysis_json(&scope_groups, &selected_scopes)?;
962 }
963
964 Ok(document)
965}
966
967fn render_scope_matrix_fixed(
969 mut document: Document,
970 scope_name: &str,
971 vars: &[&AllocationInfo],
972 x: i32,
973 y: i32,
974 width: i32,
975 height: i32,
976 max_duration: u64, ) -> TrackingResult<Document> {
978 let (dynamic_width, dynamic_height) = calculate_dynamic_matrix_size(vars.len());
980 let actual_width = dynamic_width.max(width);
981 let actual_height = dynamic_height.max(height);
982
983 let mut matrix_group = Group::new().set("transform", format!("translate({x}, {y})"));
984
985 let duration = calculate_scope_lifetime(scope_name, vars);
987 let total_memory = vars.iter().map(|v| v.size).sum::<usize>();
988 let active_vars = vars.len();
992
993 let duration_ratio = if max_duration > 0 {
995 duration as f64 / max_duration as f64
996 } else {
997 0.0
998 };
999
1000 let is_global = scope_name == "Global";
1002 let border_color = get_duration_color(duration_ratio, is_global);
1003
1004 let container = Rectangle::new()
1006 .set("width", actual_width)
1007 .set("height", actual_height)
1008 .set("fill", "rgba(30, 64, 175, 0.1)")
1009 .set("stroke", border_color.as_str())
1010 .set("stroke-width", 3)
1011 .set(
1012 "stroke-dasharray",
1013 if scope_name != "Global" {
1014 "8,4"
1015 } else {
1016 "none"
1017 },
1018 )
1019 .set("rx", 12);
1020 matrix_group = matrix_group.add(container);
1021
1022 let header_text = format!(
1024 "Scope: {} | Memory: {} | Variables: {} | Lifetime: {}ms",
1025 scope_name,
1026 format_bytes(total_memory),
1027 active_vars,
1028 duration
1029 );
1030 let enhanced_title = SvgText::new(header_text)
1031 .set("x", 15)
1032 .set("y", 25)
1033 .set("font-size", 11)
1034 .set("font-weight", "700")
1035 .set("fill", "#f8fafc");
1036 matrix_group = matrix_group.add(enhanced_title);
1037
1038 let var_start_y = 45;
1040 let card_height = 45; let var_spacing = 50; let _font_size = 10;
1043
1044 for (i, var) in vars.iter().take(4).enumerate() {
1045 let var_y = var_start_y + (i as i32 * var_spacing);
1047 let var_name = var.var_name.as_ref().unwrap();
1048 let type_name = get_simple_type(var.type_name.as_ref().unwrap_or(&"Unknown".to_string()));
1049 let duration_ms = estimate_variable_duration(var);
1050
1051 let max_size_in_scope = vars.iter().map(|v| v.size).max().unwrap_or(1);
1053 let progress_ratio = var.size as f64 / max_size_in_scope as f64;
1054 let _progress_width = (progress_ratio * 180.0) as i32;
1055
1056 let card_width = actual_width - 20;
1058 let card_bg = Rectangle::new()
1059 .set("x", 10)
1060 .set("y", var_y - 5)
1061 .set("width", card_width)
1062 .set("height", card_height)
1063 .set("fill", "rgba(255, 255, 255, 0.08)")
1064 .set("stroke", "rgba(255, 255, 255, 0.15)")
1065 .set("stroke-width", 1)
1066 .set("rx", 8)
1067 .set("ry", 8);
1068 matrix_group = matrix_group.add(card_bg);
1069
1070 let var_label = SvgText::new(var_name)
1072 .set("x", 18)
1073 .set("y", var_y + 8)
1074 .set("font-size", 12)
1075 .set("font-weight", "bold")
1076 .set("fill", "#FFFFFF")
1077 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
1078 matrix_group = matrix_group.add(var_label);
1079
1080 let (type_start_color, _) = get_type_gradient_colors(&type_name);
1082 let type_label = SvgText::new(format!("({type_name})"))
1083 .set("x", 18)
1084 .set("y", var_y + 22)
1085 .set("font-size", 9)
1086 .set("fill", type_start_color)
1087 .set("font-weight", "600");
1088 matrix_group = matrix_group.add(type_label);
1089
1090 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()
1096 .set("x", progress_x)
1097 .set("y", var_y + 15) .set("width", progress_bar_width)
1099 .set("height", 8)
1100 .set("fill", "rgba(255, 255, 255, 0.1)")
1101 .set("stroke", "rgba(255, 255, 255, 0.2)")
1102 .set("stroke-width", 1)
1103 .set("rx", 4)
1104 .set("ry", 4);
1105 matrix_group = matrix_group.add(progress_bg);
1106
1107 let (start_color, _) = get_type_gradient_colors(&type_name);
1109 let progress_fill_width = (progress_ratio * progress_bar_width as f64) as i32;
1110 let progress_fill = Rectangle::new()
1111 .set("x", progress_x)
1112 .set("y", var_y + 15)
1113 .set("width", progress_fill_width)
1114 .set("height", 8)
1115 .set("fill", start_color) .set("rx", 4)
1117 .set("ry", 4);
1118 matrix_group = matrix_group.add(progress_fill);
1119
1120 let size_display = format!(
1122 "{} / {}",
1123 format_bytes(var.size),
1124 format_bytes(max_size_in_scope)
1125 );
1126 let size_label = SvgText::new(size_display)
1127 .set("x", progress_x + progress_bar_width + 10)
1128 .set("y", var_y + 20)
1129 .set("font-size", 8)
1130 .set("font-weight", "bold")
1131 .set("fill", "#E2E8F0");
1132 matrix_group = matrix_group.add(size_label);
1133
1134 let time_label = SvgText::new(format!("Active {duration_ms}ms"))
1136 .set("x", 20)
1137 .set("y", var_y + 30)
1138 .set("font-size", 7)
1139 .set("fill", "#FCD34D")
1140 .set("font-weight", "500");
1141 matrix_group = matrix_group.add(time_label);
1142 }
1143
1144 if vars.len() > 4 {
1146 let more_text = format!("+ {} more variables", vars.len() - 4);
1147 let more_label = SvgText::new(more_text)
1148 .set("x", 20)
1149 .set("y", var_start_y + (4 * var_spacing) + 10)
1150 .set("font-size", 9)
1151 .set("font-weight", "500")
1152 .set("fill", "#94A3B8")
1153 .set("font-style", "italic");
1154 matrix_group = matrix_group.add(more_label);
1155 }
1156
1157 let explanation_y = actual_height - 15;
1159 let explanation_text = "Progress Bar: Current Size / Max Size in Scope";
1160 let explanation = SvgText::new(explanation_text)
1161 .set("x", 15)
1162 .set("y", explanation_y)
1163 .set("font-size", 8)
1164 .set("font-weight", "500")
1165 .set("fill", "#FCD34D")
1166 .set("font-style", "italic");
1167 matrix_group = matrix_group.add(explanation);
1168
1169 document = document.add(matrix_group);
1170 Ok(document)
1171}
1172
1173fn identify_precise_scope(allocation: &AllocationInfo) -> String {
1175 if let Some(var_name) = &allocation.var_name {
1176 if var_name.contains("global") {
1177 return "Global".to_string();
1178 }
1179 match allocation.timestamp_alloc {
1181 0..=1000 => "Global".to_string(),
1182 1001..=2000 => "demonstrate_builtin_types".to_string(),
1183 2001..=3000 => "demonstrate_smart_pointers".to_string(),
1184 3001..=4000 => "demonstrate_custom_structures".to_string(),
1185 4001..=5000 => "demonstrate_complex_patterns".to_string(),
1186 5001..=6000 => "simulate_web_server_scenario".to_string(),
1187 _ => "simulate_data_processing_pipeline".to_string(),
1188 }
1189 } else {
1190 "Global".to_string()
1191 }
1192}
1193
1194fn estimate_variable_duration(var: &AllocationInfo) -> u64 {
1196 let base_duration = match var.size {
1197 0..=100 => 10,
1198 101..=1000 => 50,
1199 1001..=10000 => 100,
1200 _ => 200,
1201 };
1202
1203 let type_multiplier = if let Some(type_name) = &var.type_name {
1204 if type_name.contains("Vec") || type_name.contains("HashMap") {
1205 2.0
1206 } else if type_name.contains("Box") || type_name.contains("Rc") {
1207 1.5
1208 } else {
1209 1.0
1210 }
1211 } else {
1212 1.0
1213 };
1214
1215 (base_duration as f64 * type_multiplier) as u64
1216}
1217
1218use crate::core::types::TypeMemoryUsage;
1223
1224pub fn enhance_type_information(
1228 memory_by_type: &[TypeMemoryUsage],
1229 allocations: &[AllocationInfo],
1230) -> Vec<crate::export_enhanced::EnhancedTypeInfo> {
1231 let mut enhanced_types = Vec::new();
1232
1233 for usage in memory_by_type {
1234 if usage.type_name == "Unknown" {
1236 continue;
1237 }
1238
1239 let (simplified_name, category, subcategory) =
1241 analyze_type_with_detailed_subcategory(&usage.type_name);
1242
1243 let variable_names: Vec<String> = allocations
1245 .iter()
1246 .filter_map(|alloc| {
1247 if let (Some(var_name), Some(type_name)) = (&alloc.var_name, &alloc.type_name) {
1248 let (alloc_simplified, _, _) =
1249 analyze_type_with_detailed_subcategory(type_name);
1250 if alloc_simplified == simplified_name {
1251 Some(var_name)
1252 } else {
1253 None
1254 }
1255 } else {
1256 None
1257 }
1258 })
1259 .take(5) .map(|s| s.to_string())
1261 .collect();
1262
1263 enhanced_types.push(crate::export_enhanced::EnhancedTypeInfo {
1265 simplified_name,
1266 category,
1267 subcategory,
1268 total_size: usage.total_size,
1269 allocation_count: usage.allocation_count,
1270 variable_names,
1271 });
1272 }
1273
1274 enhanced_types
1275}
1276
1277fn analyze_type_with_detailed_subcategory(type_name: &str) -> (String, String, String) {
1279 let clean_type = type_name.trim();
1280
1281 if clean_type.is_empty() || clean_type == "Unknown" {
1283 return (
1284 "Unknown Type".to_string(),
1285 "Unknown".to_string(),
1286 "Other".to_string(),
1287 );
1288 }
1289
1290 if clean_type.contains("Vec<") || clean_type.contains("vec::Vec") {
1292 return (
1293 "Vec<T>".to_string(),
1294 "Collections".to_string(),
1295 "Vec<T>".to_string(),
1296 );
1297 }
1298
1299 if clean_type.contains("HashMap") {
1300 return (
1301 "HashMap<K,V>".to_string(),
1302 "Collections".to_string(),
1303 "HashMap<K,V>".to_string(),
1304 );
1305 }
1306
1307 if clean_type.contains("String") {
1308 return (
1309 "String".to_string(),
1310 "Basic Types".to_string(),
1311 "Strings".to_string(),
1312 );
1313 }
1314
1315 if clean_type == "i32" || clean_type.ends_with("::i32") {
1317 return (
1318 "i32".to_string(),
1319 "Basic Types".to_string(),
1320 "Integers".to_string(),
1321 );
1322 }
1323
1324 if clean_type == "u8" || clean_type.ends_with("::u8") {
1325 return (
1326 "u8".to_string(),
1327 "Basic Types".to_string(),
1328 "Integers".to_string(),
1329 );
1330 }
1331
1332 (
1334 clean_type.to_string(),
1335 "Other".to_string(),
1336 "Custom".to_string(),
1337 )
1338}
1339
1340pub fn categorize_enhanced_allocations(
1342 enhanced_types: &[crate::export_enhanced::EnhancedTypeInfo],
1343) -> Vec<crate::export_enhanced::AllocationCategory> {
1344 crate::export_enhanced::categorize_enhanced_allocations(enhanced_types)
1345}
1346
1347pub fn export_unsafe_ffi_dashboard<P: AsRef<Path>>(
1349 tracker: &UnsafeFFITracker,
1350 path: P,
1351) -> TrackingResult<()> {
1352 let path = path.as_ref();
1353 tracing::info!("Exporting unsafe/FFI dashboard to: {}", path.display());
1354
1355 if let Some(parent) = path.parent() {
1356 if !parent.exists() {
1357 std::fs::create_dir_all(parent)?;
1358 }
1359 }
1360
1361 let enhanced_allocations = tracker.get_enhanced_allocations()?;
1362 let safety_violations = tracker.get_safety_violations()?;
1363
1364 let document = create_unsafe_ffi_dashboard(&enhanced_allocations, &safety_violations)?;
1365
1366 let mut file = File::create(path)?;
1367 svg::write(&mut file, &document)
1368 .map_err(|e| TrackingError::SerializationError(format!("Failed to write SVG: {e}")))?;
1369
1370 tracing::info!("Successfully exported unsafe/FFI dashboard SVG");
1371 Ok(())
1372}
1373
1374fn create_unsafe_ffi_dashboard(
1376 allocations: &[EnhancedAllocationInfo],
1377 violations: &[SafetyViolation],
1378) -> TrackingResult<Document> {
1379 let width = 1400;
1380 let height = 1000;
1381
1382 let mut document = Document::new()
1383 .set("viewBox", (0, 0, width, height))
1384 .set("width", width)
1385 .set("height", height)
1386 .set("style", "background: linear-gradient(135deg, #2c3e50 0%, #34495e 50%, #2c3e50 100%); font-family: 'Segoe UI', Arial, sans-serif;");
1387
1388 document = add_svg_definitions(document);
1390
1391 document = add_dashboard_header(document, allocations, violations)?;
1393
1394 document = add_allocation_source_breakdown(document, allocations)?;
1396 document = add_memory_safety_status(document, violations)?;
1397 document = add_boundary_crossing_flow(document, allocations)?;
1398 document = add_unsafe_hotspots(document, allocations)?;
1399
1400 Ok(document)
1401}
1402
1403fn add_svg_definitions(document: Document) -> Document {
1405 let mut defs = Definitions::new();
1406
1407 let arrow_marker = Marker::new()
1409 .set("id", "arrowhead")
1410 .set("markerWidth", 10)
1411 .set("markerHeight", 7)
1412 .set("refX", 9)
1413 .set("refY", 3.5)
1414 .set("orient", "auto")
1415 .add(
1416 Polygon::new()
1417 .set("points", "0 0, 10 3.5, 0 7")
1418 .set("fill", "#e74c3c"),
1419 );
1420 defs = defs.add(arrow_marker);
1421
1422 document.add(defs)
1423}
1424
1425fn add_dashboard_header(
1427 mut document: Document,
1428 allocations: &[EnhancedAllocationInfo],
1429 violations: &[SafetyViolation],
1430) -> TrackingResult<Document> {
1431 let title = SvgText::new("Unsafe Rust & FFI Memory Analysis Dashboard")
1433 .set("x", 700)
1434 .set("y", 40)
1435 .set("text-anchor", "middle")
1436 .set("font-size", 24)
1437 .set("font-weight", "bold")
1438 .set("fill", "#ecf0f1");
1439 document = document.add(title);
1440
1441 let unsafe_count = allocations
1443 .iter()
1444 .filter(|a| matches!(a.source, AllocationSource::UnsafeRust { .. }))
1445 .count();
1446 let ffi_count = allocations
1447 .iter()
1448 .filter(|a| matches!(a.source, AllocationSource::FfiC { .. }))
1449 .count();
1450 let cross_boundary_events: usize = allocations
1451 .iter()
1452 .map(|a| a.cross_boundary_events.len())
1453 .sum();
1454 let total_unsafe_memory: usize = allocations
1455 .iter()
1456 .filter(|a| !matches!(a.source, AllocationSource::RustSafe))
1457 .map(|a| a.base.size)
1458 .sum();
1459
1460 let metrics = vec![
1462 ("Unsafe Allocations", unsafe_count.to_string(), "#e74c3c"),
1463 ("FFI Allocations", ffi_count.to_string(), "#3498db"),
1464 (
1465 "Boundary Crossings",
1466 cross_boundary_events.to_string(),
1467 "#f39c12",
1468 ),
1469 ("Safety Violations", violations.len().to_string(), "#e67e22"),
1470 (
1471 "Unsafe Memory",
1472 format_bytes(total_unsafe_memory),
1473 "#9b59b6",
1474 ),
1475 ];
1476
1477 for (i, (label, value, color)) in metrics.iter().enumerate() {
1478 let x = 100 + i as i32 * 250;
1479 let y = 80;
1480
1481 let card = Rectangle::new()
1483 .set("x", x - 60)
1484 .set("y", y - 25)
1485 .set("width", 120)
1486 .set("height", 50)
1487 .set("fill", *color)
1488 .set("fill-opacity", 0.2)
1489 .set("stroke", *color)
1490 .set("stroke-width", 2)
1491 .set("rx", 8);
1492 document = document.add(card);
1493
1494 let value_text = SvgText::new(value)
1496 .set("x", x)
1497 .set("y", y - 5)
1498 .set("text-anchor", "middle")
1499 .set("font-size", 16)
1500 .set("font-weight", "bold")
1501 .set("fill", *color);
1502 document = document.add(value_text);
1503
1504 let label_text = SvgText::new(*label)
1506 .set("x", x)
1507 .set("y", y + 15)
1508 .set("text-anchor", "middle")
1509 .set("font-size", 10)
1510 .set("fill", "#bdc3c7");
1511 document = document.add(label_text);
1512 }
1513
1514 Ok(document)
1515}
1516
1517fn add_allocation_source_breakdown(
1519 mut document: Document,
1520 allocations: &[EnhancedAllocationInfo],
1521) -> TrackingResult<Document> {
1522 let start_x = 50;
1523 let start_y = 150;
1524 let width = 600;
1525 let height = 300;
1526
1527 let title = SvgText::new("Memory Allocation Sources")
1529 .set("x", start_x + width / 2)
1530 .set("y", start_y - 10)
1531 .set("text-anchor", "middle")
1532 .set("font-size", 18)
1533 .set("font-weight", "bold")
1534 .set("fill", "#ecf0f1");
1535 document = document.add(title);
1536
1537 let bg = Rectangle::new()
1539 .set("x", start_x)
1540 .set("y", start_y)
1541 .set("width", width)
1542 .set("height", height)
1543 .set("fill", "rgba(52, 73, 94, 0.3)")
1544 .set("stroke", "#34495e")
1545 .set("stroke-width", 2)
1546 .set("rx", 10);
1547 document = document.add(bg);
1548
1549 let mut safe_count = 0;
1551 let mut unsafe_count = 0;
1552 let mut ffi_count = 0;
1553 let mut cross_boundary_count = 0;
1554
1555 for allocation in allocations {
1556 match &allocation.source {
1557 AllocationSource::RustSafe => safe_count += 1,
1558 AllocationSource::UnsafeRust { .. } => unsafe_count += 1,
1559 AllocationSource::FfiC { .. } => ffi_count += 1,
1560 AllocationSource::CrossBoundary { .. } => cross_boundary_count += 1,
1561 }
1562 }
1563
1564 let total = safe_count + unsafe_count + ffi_count + cross_boundary_count;
1565 if total == 0 {
1566 let no_data = SvgText::new("No allocation data available")
1567 .set("x", start_x + width / 2)
1568 .set("y", start_y + height / 2)
1569 .set("text-anchor", "middle")
1570 .set("font-size", 14)
1571 .set("fill", "#95a5a6");
1572 document = document.add(no_data);
1573 return Ok(document);
1574 }
1575
1576 let sources = [
1578 ("Safe Rust", safe_count, "#2ecc71"),
1579 ("Unsafe Rust", unsafe_count, "#e74c3c"),
1580 ("FFI", ffi_count, "#3498db"),
1581 ("Cross-boundary", cross_boundary_count, "#9b59b6"),
1582 ];
1583
1584 for (i, (label, count, color)) in sources.iter().enumerate() {
1585 if *count > 0 {
1586 let x = start_x + 50 + i as i32 * 120;
1587 let y = start_y + 200;
1588 let bar_height = (*count as f32 / total as f32 * 100.0) as i32;
1589
1590 let bar = Rectangle::new()
1592 .set("x", x)
1593 .set("y", y - bar_height)
1594 .set("width", 40)
1595 .set("height", bar_height)
1596 .set("fill", *color);
1597 document = document.add(bar);
1598
1599 let count_text = SvgText::new(count.to_string())
1601 .set("x", x + 20)
1602 .set("y", y - bar_height - 5)
1603 .set("text-anchor", "middle")
1604 .set("font-size", 12)
1605 .set("font-weight", "bold")
1606 .set("fill", *color);
1607 document = document.add(count_text);
1608
1609 let label_text = SvgText::new(*label)
1611 .set("x", x + 20)
1612 .set("y", y + 20)
1613 .set("text-anchor", "middle")
1614 .set("font-size", 10)
1615 .set("fill", "#ecf0f1");
1616 document = document.add(label_text);
1617 }
1618 }
1619
1620 Ok(document)
1621}
1622
1623fn add_memory_safety_status(
1625 mut document: Document,
1626 violations: &[SafetyViolation],
1627) -> TrackingResult<Document> {
1628 let start_x = 750;
1629 let start_y = 150;
1630 let width = 600;
1631 let height = 300;
1632
1633 let title = SvgText::new("Memory Safety Status")
1635 .set("x", start_x + width / 2)
1636 .set("y", start_y - 10)
1637 .set("text-anchor", "middle")
1638 .set("font-size", 18)
1639 .set("font-weight", "bold")
1640 .set("fill", "#ecf0f1");
1641 document = document.add(title);
1642
1643 let bg_color = if violations.is_empty() {
1645 "#27ae60"
1646 } else {
1647 "#e74c3c"
1648 };
1649 let bg = Rectangle::new()
1650 .set("x", start_x)
1651 .set("y", start_y)
1652 .set("width", width)
1653 .set("height", height)
1654 .set("fill", format!("{bg_color}20"))
1655 .set("stroke", bg_color)
1656 .set("stroke-width", 2)
1657 .set("rx", 10);
1658 document = document.add(bg);
1659
1660 if violations.is_empty() {
1661 let safe_text = SvgText::new("No Safety Violations Detected")
1663 .set("x", start_x + width / 2)
1664 .set("y", start_y + 150)
1665 .set("text-anchor", "middle")
1666 .set("font-size", 16)
1667 .set("font-weight", "bold")
1668 .set("fill", "#27ae60");
1669 document = document.add(safe_text);
1670
1671 let safe_desc =
1672 SvgText::new("All unsafe operations and FFI calls appear to be memory-safe")
1673 .set("x", start_x + width / 2)
1674 .set("y", start_y + 180)
1675 .set("text-anchor", "middle")
1676 .set("font-size", 12)
1677 .set("fill", "#2ecc71");
1678 document = document.add(safe_desc);
1679 } else {
1680 let violation_text =
1682 SvgText::new(format!("{} Safety Violations Detected", violations.len()))
1683 .set("x", start_x + width / 2)
1684 .set("y", start_y + 120)
1685 .set("text-anchor", "middle")
1686 .set("font-size", 16)
1687 .set("font-weight", "bold")
1688 .set("fill", "#e74c3c");
1689 document = document.add(violation_text);
1690
1691 for (i, violation) in violations.iter().take(5).enumerate() {
1693 let y = start_y + 160 + i as i32 * 20;
1694
1695 let description = match violation {
1696 SafetyViolation::DoubleFree { .. } => "Double Free",
1697 SafetyViolation::InvalidFree { .. } => "Invalid Free",
1698 SafetyViolation::PotentialLeak { .. } => "Memory Leak",
1699 SafetyViolation::CrossBoundaryRisk { .. } => "Cross-Boundary Risk",
1700 };
1701
1702 let violation_item = SvgText::new(format!("• {description}"))
1703 .set("x", start_x + 30)
1704 .set("y", y)
1705 .set("font-size", 12)
1706 .set("fill", "#e74c3c");
1707 document = document.add(violation_item);
1708 }
1709 }
1710
1711 Ok(document)
1712}
1713
1714fn add_boundary_crossing_flow(
1716 mut document: Document,
1717 allocations: &[EnhancedAllocationInfo],
1718) -> TrackingResult<Document> {
1719 let start_x = 50;
1720 let start_y = 500;
1721 let width = 600;
1722 let height = 200;
1723
1724 let title = SvgText::new("Cross-Language Memory Flow")
1726 .set("x", start_x + width / 2)
1727 .set("y", start_y - 10)
1728 .set("text-anchor", "middle")
1729 .set("font-size", 18)
1730 .set("font-weight", "bold")
1731 .set("fill", "#ecf0f1");
1732 document = document.add(title);
1733
1734 let bg = Rectangle::new()
1736 .set("x", start_x)
1737 .set("y", start_y)
1738 .set("width", width)
1739 .set("height", height)
1740 .set("fill", "rgba(52, 73, 94, 0.3)")
1741 .set("stroke", "#34495e")
1742 .set("stroke-width", 2)
1743 .set("rx", 10);
1744 document = document.add(bg);
1745
1746 let rust_box = Rectangle::new()
1748 .set("x", start_x + 50)
1749 .set("y", start_y + 50)
1750 .set("width", 200)
1751 .set("height", 100)
1752 .set("fill", "#2ecc71")
1753 .set("fill-opacity", 0.2)
1754 .set("stroke", "#2ecc71")
1755 .set("stroke-width", 2)
1756 .set("rx", 8);
1757 document = document.add(rust_box);
1758
1759 let rust_label = SvgText::new("RUST")
1760 .set("x", start_x + 150)
1761 .set("y", start_y + 110)
1762 .set("text-anchor", "middle")
1763 .set("font-size", 14)
1764 .set("font-weight", "bold")
1765 .set("fill", "#2ecc71");
1766 document = document.add(rust_label);
1767
1768 let ffi_box = Rectangle::new()
1770 .set("x", start_x + 350)
1771 .set("y", start_y + 50)
1772 .set("width", 200)
1773 .set("height", 100)
1774 .set("fill", "#3498db")
1775 .set("fill-opacity", 0.2)
1776 .set("stroke", "#3498db")
1777 .set("stroke-width", 2)
1778 .set("rx", 8);
1779 document = document.add(ffi_box);
1780
1781 let ffi_label = SvgText::new("FFI / C")
1782 .set("x", start_x + 450)
1783 .set("y", start_y + 110)
1784 .set("text-anchor", "middle")
1785 .set("font-size", 14)
1786 .set("font-weight", "bold")
1787 .set("fill", "#3498db");
1788 document = document.add(ffi_label);
1789
1790 let mut rust_to_ffi = 0;
1792 let mut ffi_to_rust = 0;
1793
1794 for allocation in allocations {
1795 for event in &allocation.cross_boundary_events {
1796 match event.event_type {
1797 BoundaryEventType::RustToFfi => rust_to_ffi += 1,
1798 BoundaryEventType::FfiToRust => ffi_to_rust += 1,
1799 BoundaryEventType::OwnershipTransfer => rust_to_ffi += 1,
1800 BoundaryEventType::SharedAccess => {
1801 rust_to_ffi += 1;
1802 ffi_to_rust += 1;
1803 }
1804 }
1805 }
1806 }
1807
1808 if rust_to_ffi > 0 {
1810 let arrow = Line::new()
1811 .set("x1", start_x + 250)
1812 .set("y1", start_y + 80)
1813 .set("x2", start_x + 350)
1814 .set("y2", start_y + 80)
1815 .set("stroke", "#e74c3c")
1816 .set("stroke-width", 3)
1817 .set("marker-end", "url(#arrowhead)");
1818 document = document.add(arrow);
1819
1820 let count_text = SvgText::new(rust_to_ffi.to_string())
1821 .set("x", start_x + 300)
1822 .set("y", start_y + 75)
1823 .set("text-anchor", "middle")
1824 .set("font-size", 12)
1825 .set("font-weight", "bold")
1826 .set("fill", "#e74c3c");
1827 document = document.add(count_text);
1828 }
1829
1830 if ffi_to_rust > 0 {
1831 let count_text = SvgText::new(ffi_to_rust.to_string())
1832 .set("x", start_x + 300)
1833 .set("y", start_y + 135)
1834 .set("text-anchor", "middle")
1835 .set("font-size", 12)
1836 .set("font-weight", "bold")
1837 .set("fill", "#f39c12");
1838 document = document.add(count_text);
1839 }
1840
1841 Ok(document)
1842}
1843
1844fn add_unsafe_hotspots(
1846 mut document: Document,
1847 allocations: &[EnhancedAllocationInfo],
1848) -> TrackingResult<Document> {
1849 let start_x = 750;
1850 let start_y = 500;
1851 let width = 600;
1852 let height = 200;
1853
1854 let title = SvgText::new("Unsafe Memory Hotspots")
1856 .set("x", start_x + width / 2)
1857 .set("y", start_y - 10)
1858 .set("text-anchor", "middle")
1859 .set("font-size", 18)
1860 .set("font-weight", "bold")
1861 .set("fill", "#ecf0f1");
1862 document = document.add(title);
1863
1864 let bg = Rectangle::new()
1866 .set("x", start_x)
1867 .set("y", start_y)
1868 .set("width", width)
1869 .set("height", height)
1870 .set("fill", "rgba(52, 73, 94, 0.3)")
1871 .set("stroke", "#34495e")
1872 .set("stroke-width", 2)
1873 .set("rx", 10);
1874 document = document.add(bg);
1875
1876 let unsafe_allocations: Vec<_> = allocations
1878 .iter()
1879 .filter(|a| !matches!(a.source, AllocationSource::RustSafe))
1880 .collect();
1881
1882 if unsafe_allocations.is_empty() {
1883 let no_unsafe = SvgText::new("No unsafe memory allocations detected")
1884 .set("x", start_x + width / 2)
1885 .set("y", start_y + height / 2)
1886 .set("text-anchor", "middle")
1887 .set("font-size", 14)
1888 .set("fill", "#2ecc71");
1889 document = document.add(no_unsafe);
1890 return Ok(document);
1891 }
1892
1893 for (i, allocation) in unsafe_allocations.iter().take(6).enumerate() {
1895 let x = start_x + 80 + (i % 3) as i32 * 180;
1896 let y = start_y + 80 + (i / 3) as i32 * 70;
1897
1898 let size_factor = (allocation.base.size.min(1000) as f32 / 1000.0 * 15.0 + 5.0) as i32;
1900 let color = match &allocation.source {
1901 AllocationSource::UnsafeRust { .. } => "#e74c3c",
1902 AllocationSource::FfiC { .. } => "#3498db",
1903 AllocationSource::CrossBoundary { .. } => "#9b59b6",
1904 _ => "#95a5a6",
1905 };
1906
1907 let hotspot = Circle::new()
1908 .set("cx", x)
1909 .set("cy", y)
1910 .set("r", size_factor)
1911 .set("fill", color)
1912 .set("fill-opacity", 0.7)
1913 .set("stroke", color)
1914 .set("stroke-width", 2);
1915 document = document.add(hotspot);
1916
1917 let size_text = SvgText::new(format_bytes(allocation.base.size))
1919 .set("x", x)
1920 .set("y", y + 4)
1921 .set("text-anchor", "middle")
1922 .set("font-size", 8)
1923 .set("font-weight", "bold")
1924 .set("fill", "#ffffff");
1925 document = document.add(size_text);
1926
1927 let type_label = match &allocation.source {
1929 AllocationSource::UnsafeRust { .. } => "UNSAFE",
1930 AllocationSource::FfiC { .. } => "FFI",
1931 AllocationSource::CrossBoundary { .. } => "CROSS",
1932 _ => "OTHER",
1933 };
1934
1935 let type_text = SvgText::new(type_label)
1936 .set("x", x)
1937 .set("y", y + 35)
1938 .set("text-anchor", "middle")
1939 .set("font-size", 10)
1940 .set("fill", color);
1941 document = document.add(type_text);
1942 }
1943
1944 Ok(document)
1945}