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 thread_local! {
26 static SVG_EXPORT_MODE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
27 }
28
29 let already_exporting = SVG_EXPORT_MODE.with(|mode| mode.get());
31 if already_exporting {
32 return Ok(()); }
34
35 SVG_EXPORT_MODE.with(|mode| mode.set(true));
37
38 let path = path.as_ref();
39 tracing::info!("Exporting memory analysis to: {}", path.display());
40
41 if let Some(parent) = path.parent() {
42 if !parent.exists() {
43 std::fs::create_dir_all(parent)?;
44 }
45 }
46
47 let stats = tracker.get_stats()?;
49 let active_allocations = tracker.get_active_allocations()?;
50
51 let actual_memory_usage = active_allocations.iter().map(|a| a.size).sum::<usize>();
53
54 let corrected_peak_memory = if stats.peak_memory > actual_memory_usage * 2 {
56 actual_memory_usage.max(stats.active_memory)
59 } else {
60 stats.peak_memory
61 };
62
63 tracing::info!(
64 "Memory correction: original peak_memory={}, active_memory={}, actual_usage={}, corrected_peak={}",
65 stats.peak_memory, stats.active_memory, actual_memory_usage, corrected_peak_memory
66 );
67
68 tracing::info!(
70 "SVG Export - Using corrected peak_memory: {} bytes ({})",
71 corrected_peak_memory,
72 crate::utils::format_bytes(corrected_peak_memory)
73 );
74
75 let mut corrected_stats = stats.clone();
77 corrected_stats.peak_memory = corrected_peak_memory;
78
79 let document = create_memory_analysis_svg(&active_allocations, &corrected_stats, tracker)?;
80
81 let mut file = File::create(path)?;
82 svg::write(&mut file, &document)
83 .map_err(|e| TrackingError::SerializationError(format!("Failed to write SVG: {e}")))?;
84
85 tracing::info!("Successfully exported memory analysis SVG");
86
87 SVG_EXPORT_MODE.with(|mode| mode.set(false));
89
90 Ok(())
91}
92
93pub fn export_lifecycle_timeline<P: AsRef<Path>>(
95 tracker: &MemoryTracker,
96 path: P,
97) -> TrackingResult<()> {
98 let path = path.as_ref();
99 tracing::info!("Exporting lifecycle timeline to: {}", path.display());
100
101 if let Some(parent) = path.parent() {
102 if !parent.exists() {
103 std::fs::create_dir_all(parent)?;
104 }
105 }
106
107 let active_allocations = tracker.get_active_allocations()?;
108 let stats = tracker.get_stats()?;
109
110 let document = create_lifecycle_timeline_svg(&active_allocations, &stats)?;
111
112 let mut file = File::create(path)?;
113 svg::write(&mut file, &document)
114 .map_err(|e| TrackingError::SerializationError(format!("Failed to write SVG: {e}")))?;
115
116 tracing::info!("Successfully exported lifecycle timeline SVG");
117 Ok(())
118}
119
120fn create_memory_analysis_svg(
122 allocations: &[AllocationInfo],
123 stats: &MemoryStats,
124 tracker: &MemoryTracker,
125) -> TrackingResult<Document> {
126 let width = 1800;
128 let height = 2000; let mut document = Document::new()
131 .set("viewBox", (0, 0, width, height))
132 .set("width", width)
133 .set("height", height)
134 .set("style", "background: linear-gradient(135deg, #ecf0f1 0%, #bdc3c7 100%); font-family: 'Segoe UI', Arial, sans-serif;");
135
136 document = crate::export::export_enhanced::add_enhanced_header(document, stats, allocations)?;
138
139 document = crate::export::export_enhanced::add_memory_heatmap(document, allocations)?;
145
146 let memory_by_type_data = tracker.get_memory_by_type().unwrap_or_default();
149 let memory_by_type = enhance_type_information(&memory_by_type_data, allocations);
150 document = crate::export::export_enhanced::add_enhanced_type_chart(document, &memory_by_type)?;
151
152 document = crate::export::export_enhanced::add_fragmentation_analysis(document, allocations)?;
154
155 let categorized = categorize_enhanced_allocations(&memory_by_type);
158 document = crate::export::export_enhanced::add_categorized_allocations(document, &categorized)?;
159
160 document = crate::export::export_enhanced::add_callstack_analysis(document, allocations)?;
162
163 document =
165 crate::export::export_enhanced::add_memory_growth_trends(document, allocations, stats)?;
166
167 document = crate::export::export_enhanced::add_memory_timeline(document, allocations, stats)?;
169
170 document = crate::export::export_enhanced::add_interactive_legend(document)?;
172
173 document =
175 crate::export::export_enhanced::add_comprehensive_summary(document, stats, allocations)?;
176
177 Ok(document)
178}
179
180fn create_lifecycle_timeline_svg(
182 allocations: &[AllocationInfo],
183 stats: &MemoryStats,
184) -> TrackingResult<Document> {
185 let width = 1600;
186 let height = 1200;
187
188 let mut document = Document::new()
189 .set("viewBox", (0, 0, width, height))
190 .set("width", width)
191 .set("height", height)
192 .set("style", "background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); font-family: 'Inter', 'Segoe UI', sans-serif;");
193
194 let styles = Style::new(
196 r#"
197 .timeline-bar { transition: all 0.3s ease; cursor: pointer; }
198 .timeline-bar:hover { stroke: #FFFFFF; stroke-width: 3; filter: drop-shadow(0 0 12px rgba(255,255,255,0.8)); }
199 .variable-label { fill: #FFFFFF; font-size: 13px; font-weight: 600; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
200 .memory-label { fill: #E2E8F0; font-size: 11px; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
201 .section-title { fill: #FFFFFF; font-size: 20px; font-weight: 700; text-shadow: 2px 2px 4px rgba(0,0,0,0.5); }
202 .section-bg { fill: rgba(255,255,255,0.1); stroke: rgba(255,255,255,0.2); stroke-width: 1; rx: 12; }
203 "#,
204 );
205 document = document.add(styles);
206
207 let title = SvgText::new("Scope Matrix & Lifecycle Visualization")
209 .set("x", width / 2)
210 .set("y", 40)
211 .set("text-anchor", "middle")
212 .set("font-size", 32)
213 .set("font-weight", "bold")
214 .set("fill", "#FFFFFF")
215 .set("style", "text-shadow: 3px 3px 6px rgba(0,0,0,0.5);");
216 document = document.add(title);
217
218 document = add_prominent_progress_bar_legend(document, width);
220
221 let tracked_vars: Vec<_> = allocations
222 .iter()
223 .filter(|a| a.var_name.is_some())
224 .collect();
225
226 if tracked_vars.is_empty() {
243 let no_data = SvgText::new(format!(
244 "No tracked variables found (checked {} allocations)",
245 allocations.len()
246 ))
247 .set("x", width / 2)
248 .set("y", height / 2)
249 .set("text-anchor", "middle")
250 .set("font-size", 18)
251 .set("fill", "#FFFFFF");
252 document = document.add(no_data);
253 return Ok(document);
254 }
255
256 document = add_matrix_layout_section(document, &tracked_vars, 50, 130)?;
258
259 document = add_memory_section(document, &tracked_vars, stats, 550, width - 100)?;
261
262 document = add_relationships_section(document, &tracked_vars, 900, width - 100)?;
264
265 Ok(document)
266}
267
268fn add_memory_section(
270 mut document: Document,
271 tracked_vars: &[&AllocationInfo],
272 _stats: &MemoryStats,
273 start_y: i32,
274 section_height: i32,
275) -> TrackingResult<Document> {
276 let section_bg = Rectangle::new()
278 .set("x", 50)
279 .set("y", start_y - 20)
280 .set("width", 1500)
281 .set("height", section_height)
282 .set("class", "section-bg");
283 document = document.add(section_bg);
284
285 let section_title = SvgText::new("Top 3 Memory Analysis")
287 .set("x", 70)
288 .set("y", start_y + 10)
289 .set("class", "section-title");
290 document = document.add(section_title);
291
292 let mut type_stats: HashMap<String, (usize, usize, Vec<String>)> = HashMap::new();
294 for allocation in tracked_vars {
295 if let Some(var_name) = &allocation.var_name {
296 let type_name = allocation.type_name.as_deref().unwrap_or("Unknown");
297 let simple_type = get_simple_type(type_name);
298
299 let entry = type_stats.entry(simple_type).or_insert((0, 0, Vec::new()));
300 entry.0 += 1;
301 entry.1 += allocation.size;
302 entry
303 .2
304 .push(format!("{}({})", var_name, format_bytes(allocation.size)));
305 }
306 }
307
308 let mut sorted_types: Vec<_> = type_stats.into_iter().collect();
310 sorted_types.sort_by(|a, b| b.1 .1.cmp(&a.1 .1)); sorted_types.truncate(3); let chart_x = 100;
315 let chart_y = start_y + 50;
316 let max_size = sorted_types
317 .iter()
318 .map(|(_, (_, size, _))| *size)
319 .max()
320 .unwrap_or(1);
321
322 for (i, (type_name, (count, total_size, vars))) in sorted_types.iter().enumerate() {
323 let y = chart_y + (i as i32) * 40;
324 let bar_width = ((*total_size as f64 / max_size as f64) * 400.0) as i32;
325
326 let color = get_type_color(type_name);
327 let memory_bar = Rectangle::new()
328 .set("x", chart_x)
329 .set("y", y)
330 .set("width", bar_width)
331 .set("height", 25)
332 .set("fill", color)
333 .set("rx", 4);
334 document = document.add(memory_bar);
335
336 let type_label = SvgText::new(format!("{type_name} ({count} vars)"))
338 .set("x", chart_x + 10)
339 .set("y", y + 17)
340 .set("font-size", 12)
341 .set("font-weight", "bold")
342 .set("fill", "#FFFFFF")
343 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
344 document = document.add(type_label);
345
346 let vars_text = vars.join(" | ");
348 let vars_label = SvgText::new(if vars_text.len() > 80 {
349 format!("{}...", &vars_text[..77])
350 } else {
351 vars_text
352 })
353 .set("x", chart_x + bar_width + 15)
354 .set("y", y + 17)
355 .set("font-size", 11)
356 .set("font-weight", "600")
357 .set("fill", "#FFFFFF")
358 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.6)");
359 document = document.add(vars_label);
360 }
361
362 Ok(document)
364}
365
366fn add_relationships_section(
368 mut document: Document,
369 tracked_vars: &[&AllocationInfo],
370 start_y: i32,
371 section_height: i32,
372) -> TrackingResult<Document> {
373 let section_bg = Rectangle::new()
375 .set("x", 50)
376 .set("y", start_y - 20)
377 .set("width", 1500)
378 .set("height", section_height)
379 .set("class", "section-bg");
380 document = document.add(section_bg);
381
382 let section_title = SvgText::new("Variable Relationships - Ownership & Borrowing")
384 .set("x", 70)
385 .set("y", start_y + 10)
386 .set("class", "section-title");
387 document = document.add(section_title);
388
389 let mut scope_groups: HashMap<String, Vec<&AllocationInfo>> = HashMap::new();
391 for var in tracked_vars {
392 let scope = identify_precise_scope(var);
393 scope_groups.entry(scope).or_default().push(*var);
394 }
395
396 let mut scope_positions = HashMap::new();
398 let start_x = 100;
399 let group_spacing_x = 400;
400 let group_spacing_y = 200;
401
402 for (i, (scope_name, _vars)) in scope_groups.iter().enumerate() {
403 let group_x = start_x + (i % 3) as i32 * group_spacing_x;
404 let group_y = start_y + 50 + (i / 3) as i32 * group_spacing_y;
405
406 let group_bg = Rectangle::new()
408 .set("x", group_x - 20)
409 .set("y", group_y - 20)
410 .set("width", 300)
411 .set("height", 150)
412 .set("fill", get_scope_background_color(scope_name))
413 .set("stroke", get_scope_border_color(scope_name))
414 .set("stroke-width", 2)
415 .set(
416 "stroke-dasharray",
417 if scope_name == "Global" {
418 "none"
419 } else {
420 "5,3"
421 },
422 )
423 .set("rx", 8)
424 .set("opacity", "0.3");
425 document = document.add(group_bg);
426
427 let scope_label = SvgText::new(format!("Scope: {scope_name}"))
429 .set("x", group_x - 10)
430 .set("y", group_y - 5)
431 .set("font-size", 12)
432 .set("font-weight", "bold")
433 .set("fill", "#FFFFFF");
434 document = document.add(scope_label);
435
436 scope_positions.insert(scope_name, (group_x, group_y));
437 }
438
439 for (scope_name, vars) in &scope_groups {
443 if let Some((group_x, group_y)) = scope_positions.get(scope_name) {
444 for (i, allocation) in vars.iter().take(4).enumerate() {
445 let node_x = group_x + (i % 2) as i32 * 120 + 40;
447 let node_y = group_y + (i / 2) as i32 * 60 + 30;
448
449 document = draw_variable_node(document, allocation, node_x, node_y)?;
450 }
451 }
452 }
453
454 document = add_relationship_legend(document, start_y + section_height - 100)?;
456
457 Ok(document)
458}
459
460fn draw_variable_node(
462 mut document: Document,
463 allocation: &AllocationInfo,
464 x: i32,
465 y: i32,
466) -> TrackingResult<Document> {
467 let default_name = "<unknown>".to_string();
468 let var_name = allocation.var_name.as_ref().unwrap_or(&default_name);
469 let type_name = allocation.type_name.as_deref().unwrap_or("Unknown");
470 let simple_type = get_simple_type(type_name);
471 let color = get_type_color(&simple_type);
472
473 let node = Circle::new()
475 .set("cx", x)
476 .set("cy", y)
477 .set("r", 20)
478 .set("fill", color)
479 .set("stroke", "#FFFFFF")
480 .set("stroke-width", 2)
481 .set(
482 "title",
483 format!(
484 "{}: {} ({})",
485 var_name,
486 simple_type,
487 format_bytes(allocation.size)
488 ),
489 );
490 document = document.add(node);
491
492 let display_name = if var_name.len() > 8 {
494 format!("{}...", &var_name[..6])
495 } else {
496 var_name.clone()
497 };
498
499 let name_label = SvgText::new(display_name)
500 .set("x", x)
501 .set("y", y + 3)
502 .set("text-anchor", "middle")
503 .set("font-size", 9)
504 .set("font-weight", "bold")
505 .set("fill", "#FFFFFF");
506 document = document.add(name_label);
507
508 let info_text = format!("{} | {}", simple_type, format_bytes(allocation.size));
510 let info_label = SvgText::new(info_text)
511 .set("x", x)
512 .set("y", y + 35)
513 .set("text-anchor", "middle")
514 .set("font-size", 7)
515 .set("fill", "#E2E8F0");
516 document = document.add(info_label);
517
518 Ok(document)
519}
520
521fn add_relationship_legend(mut document: Document, start_y: i32) -> TrackingResult<Document> {
523 let legend_items = [
524 ("Ownership Transfer", "#E74C3C", "solid", "4"),
525 ("Mutable Borrow", "#3498DB", "solid", "3"),
526 ("Immutable Borrow", "#27AE60", "solid", "2"),
527 ("Clone", "#95A5A6", "solid", "2"),
528 ("Shared Pointer", "#9B59B6", "8,4", "3"),
529 ("Indirect Reference", "#F39C12", "4,2", "1"),
530 ];
531
532 for (i, (label, color, dash, width)) in legend_items.iter().enumerate() {
533 let x = 100 + (i % 3) as i32 * 200;
534 let y = start_y + (i / 3) as i32 * 25;
535
536 let legend_line = Line::new()
538 .set("x1", x)
539 .set("y1", y)
540 .set("x2", x + 30)
541 .set("y2", y)
542 .set("stroke", *color)
543 .set("stroke-width", *width)
544 .set("stroke-dasharray", *dash);
545 document = document.add(legend_line);
546
547 let legend_label = SvgText::new(*label)
549 .set("x", x + 35)
550 .set("y", y + 4)
551 .set("font-size", 10)
552 .set("fill", "#FFFFFF");
553 document = document.add(legend_label);
554 }
555
556 Ok(document)
557}
558
559fn get_scope_background_color(scope_name: &str) -> &'static str {
561 match scope_name {
562 "Global" => "rgba(52, 73, 94, 0.2)",
563 _ => "rgba(52, 152, 219, 0.2)",
564 }
565}
566
567fn get_scope_border_color(scope_name: &str) -> &'static str {
569 match scope_name {
570 "Global" => "#34495E",
571 _ => "#3498DB",
572 }
573}
574
575fn calculate_dynamic_matrix_size(var_count: usize) -> (i32, i32) {
577 let standard_width = 350; let standard_height = 280; let card_height = 40; let header_height = 80; let standard_vars = 5;
582
583 if var_count <= standard_vars {
584 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;
588
589 (actual_width.max(250), actual_content_height.max(150)) } else {
591 (standard_width, standard_height)
593 }
594}
595
596fn calculate_scope_lifetime(scope_name: &str, vars: &[&AllocationInfo]) -> u64 {
598 if vars.is_empty() {
599 return 0;
600 }
601
602 if scope_name == "Global" {
603 estimate_program_runtime()
605 } else {
606 let start = vars.iter().map(|v| v.timestamp_alloc).min().unwrap_or(0);
608 let end = vars.iter().map(|v| v.timestamp_alloc).max().unwrap_or(0);
609 let span = end - start;
610 if span == 0 {
611 100 } else {
614 span
615 }
616 }
617}
618
619fn estimate_program_runtime() -> u64 {
621 2000 }
625
626fn add_prominent_progress_bar_legend(mut document: Document, svg_width: i32) -> Document {
628 let legend_bg = Rectangle::new()
630 .set("x", 50)
631 .set("y", 60)
632 .set("width", svg_width - 100)
633 .set("height", 35)
634 .set("fill", "rgba(252, 211, 77, 0.15)")
635 .set("stroke", "#FCD34D")
636 .set("stroke-width", 2)
637 .set("rx", 8)
638 .set("ry", 8);
639 document = document.add(legend_bg);
640
641 let example_bg = Rectangle::new()
643 .set("x", 70)
644 .set("y", 70)
645 .set("width", 60)
646 .set("height", 8)
647 .set("fill", "rgba(255, 255, 255, 0.2)")
648 .set("rx", 4);
649 document = document.add(example_bg);
650
651 let example_fill = Rectangle::new()
652 .set("x", 70)
653 .set("y", 70)
654 .set("width", 40)
655 .set("height", 8)
656 .set("fill", "#4CAF50")
657 .set("rx", 4);
658 document = document.add(example_fill);
659
660 let legend_text =
662 SvgText::new("📊 Progress Bars show: Variable Size / Largest Variable in Same Scope")
663 .set("x", 150)
664 .set("y", 78)
665 .set("font-size", 14)
666 .set("font-weight", "bold")
667 .set("fill", "#FCD34D")
668 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
669 document = document.add(legend_text);
670
671 let size_example = SvgText::new("Example: 2.4KB / 5.6KB")
673 .set("x", 150)
674 .set("y", 88)
675 .set("font-size", 10)
676 .set("fill", "#E2E8F0")
677 .set("font-style", "italic");
678 document = document.add(size_example);
679
680 document
681}
682
683fn prioritize_scopes_for_display<'a>(
685 scope_groups: &'a HashMap<String, Vec<&'a AllocationInfo>>,
686) -> Vec<(String, Vec<&'a AllocationInfo>)> {
687 let mut scopes_with_priority: Vec<_> = scope_groups
688 .iter()
689 .map(|(name, vars)| {
690 let priority = calculate_scope_priority(name, vars);
691 let total_memory: usize = vars.iter().map(|v| v.size).sum();
692 (name, vars, priority, total_memory)
693 })
694 .collect();
695
696 scopes_with_priority.sort_by(|a, b| b.2.cmp(&a.2).then(b.3.cmp(&a.3)));
698
699 scopes_with_priority
700 .into_iter()
701 .map(|(name, vars, _, _)| (name.clone(), vars.clone()))
702 .collect()
703}
704
705fn calculate_scope_priority(scope_name: &str, vars: &[&AllocationInfo]) -> u8 {
707 let name_lower = scope_name.to_lowercase();
708
709 if name_lower == "global"
711 || name_lower == "main"
712 || name_lower.contains("error")
713 || name_lower.contains("panic")
714 {
715 return 100;
716 }
717
718 if name_lower.contains("process")
720 || name_lower.contains("parse")
721 || name_lower.contains("compute")
722 || name_lower.contains("algorithm")
723 || name_lower.contains("core")
724 || name_lower.contains("engine")
725 {
726 return 80;
727 }
728
729 if name_lower.contains("util")
731 || name_lower.contains("helper")
732 || name_lower.contains("format")
733 || name_lower.contains("convert")
734 {
735 return 60;
736 }
737
738 if name_lower.contains("test")
740 || name_lower.contains("debug")
741 || name_lower.contains("macro")
742 || name_lower.contains("generated")
743 {
744 return 40;
745 }
746
747 let total_memory: usize = vars.iter().map(|v| v.size).sum();
749 let var_count = vars.len();
750
751 if total_memory > 1024 || var_count > 3 {
752 70 } else if total_memory > 256 || var_count > 1 {
754 50 } else {
756 30 }
758}
759
760fn export_scope_analysis_json(
762 all_scopes: &HashMap<String, Vec<&AllocationInfo>>,
763 displayed_scopes: &[(String, Vec<&AllocationInfo>)],
764) -> TrackingResult<()> {
765 use serde_json::{Map, Value};
766
767 let mut analysis = Map::new();
768
769 let mut project_analysis = Map::new();
771 project_analysis.insert(
772 "total_scopes".to_string(),
773 Value::Number((all_scopes.len() as u64).into()),
774 );
775 project_analysis.insert(
776 "displayed_in_svg".to_string(),
777 Value::Number((displayed_scopes.len() as u64).into()),
778 );
779 project_analysis.insert(
780 "exported_to_json".to_string(),
781 Value::Number(((all_scopes.len() - displayed_scopes.len()) as u64).into()),
782 );
783 project_analysis.insert(
784 "layout_strategy".to_string(),
785 Value::String("hierarchical_priority".to_string()),
786 );
787 project_analysis.insert(
788 "generation_timestamp".to_string(),
789 Value::String(
790 std::time::SystemTime::now()
791 .duration_since(std::time::UNIX_EPOCH)
792 .unwrap_or_default()
793 .as_secs()
794 .to_string(),
795 ),
796 );
797 analysis.insert(
798 "project_analysis".to_string(),
799 Value::Object(project_analysis),
800 );
801
802 let mut all_scopes_data = Vec::new();
804 for (scope_name, vars) in all_scopes {
805 let total_memory: usize = vars.iter().map(|v| v.size).sum();
806 let is_displayed = displayed_scopes.iter().any(|(name, _)| name == scope_name);
807
808 let mut scope_data = Map::new();
809 scope_data.insert("scope_name".to_string(), Value::String(scope_name.clone()));
810 scope_data.insert(
811 "total_memory".to_string(),
812 Value::Number((total_memory as u64).into()),
813 );
814 scope_data.insert(
815 "variable_count".to_string(),
816 Value::Number((vars.len() as u64).into()),
817 );
818 scope_data.insert(
819 "display_status".to_string(),
820 Value::String(if is_displayed {
821 "shown_in_svg".to_string()
822 } else {
823 "json_only".to_string()
824 }),
825 );
826 scope_data.insert(
827 "priority".to_string(),
828 Value::Number((calculate_scope_priority(scope_name, vars) as u64).into()),
829 );
830
831 let mut variables = Vec::new();
833 for var in vars {
834 if let Some(var_name) = &var.var_name {
835 let mut var_data = Map::new();
836 var_data.insert("name".to_string(), Value::String(var_name.clone()));
837 var_data.insert(
838 "type".to_string(),
839 Value::String(var.type_name.as_deref().unwrap_or("Unknown").to_string()),
840 );
841 var_data.insert(
842 "size_bytes".to_string(),
843 Value::Number((var.size as u64).into()),
844 );
845 var_data.insert(
846 "timestamp".to_string(),
847 Value::Number(var.timestamp_alloc.into()),
848 );
849 variables.push(Value::Object(var_data));
850 }
851 }
852 scope_data.insert("variables".to_string(), Value::Array(variables));
853
854 all_scopes_data.push(Value::Object(scope_data));
855 }
856 analysis.insert("all_scopes".to_string(), Value::Array(all_scopes_data));
857
858 let json_content = serde_json::to_string(&Value::Object(analysis)).map_err(|e| {
860 TrackingError::SerializationError(format!("JSON serialization failed: {e}"))
861 })?;
862
863 std::fs::write("scope_analysis.json", json_content)
864 .map_err(|e| TrackingError::IoError(e.to_string()))?;
865
866 tracing::info!("Exported complete scope analysis to scope_analysis.json");
867 Ok(())
868}
869
870fn get_duration_color(ratio: f64, is_global: bool) -> String {
874 if is_global {
875 return "#0A2540".to_string();
877 }
878
879 if ratio <= 0.01 {
884 "#F8FAFC".to_string()
886 } else {
887 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;
900 let g = (base_g as f64 + (target_g as f64 - base_g as f64) * smooth_ratio) as u8;
901 let b = (base_b as f64 + (target_b as f64 - base_b as f64) * smooth_ratio) as u8;
902
903 format!("#{r:02X}{g:02X}{b:02X}")
904 }
905}
906
907fn add_matrix_layout_section(
909 mut document: Document,
910 tracked_vars: &[&AllocationInfo],
911 start_x: i32,
912 start_y: i32,
913) -> TrackingResult<Document> {
914 let mut scope_groups: HashMap<String, Vec<&AllocationInfo>> = HashMap::new();
916 for var in tracked_vars {
917 let scope = identify_precise_scope(var);
918 scope_groups.entry(scope).or_default().push(*var);
919 }
920
921 let prioritized_scopes = prioritize_scopes_for_display(&scope_groups);
923 let selected_scopes: Vec<_> = prioritized_scopes.into_iter().take(15).collect();
924
925 let max_duration = selected_scopes
933 .iter()
934 .map(|(_, vars)| {
935 if !vars.is_empty() {
936 let start = vars.iter().map(|v| v.timestamp_alloc).min().unwrap_or(0);
937 let end = vars.iter().map(|v| v.timestamp_alloc).max().unwrap_or(0);
938 end - start
939 } else {
940 0
941 }
942 })
943 .max()
944 .unwrap_or(1); let base_matrix_width = 350;
948 let base_matrix_height = 180;
949 let spacing_x = 450; let spacing_y = 250;
951
952 let mut positions = Vec::new();
953
954 for (i, (scope_name, _)) in selected_scopes.iter().enumerate() {
956 let col = i % 3;
957 let row = i / 3;
958 let x = start_x + (col as i32 * spacing_x);
959 let y = start_y + (row as i32 * spacing_y);
960 positions.push((scope_name, x, y));
961 }
962
963 for (i, (scope_name, x, y)) in positions.iter().enumerate() {
965 if *scope_name != "Global" && i > 0 {
966 if let Some((_, global_x, global_y)) =
968 positions.iter().find(|(name, _, _)| *name == "Global")
969 {
970 let line = Line::new()
971 .set("x1", global_x + base_matrix_width / 2)
972 .set("y1", global_y + base_matrix_height)
973 .set("x2", x + base_matrix_width / 2)
974 .set("y2", *y)
975 .set("stroke", "#7F8C8D")
976 .set("stroke-width", 2)
977 .set("stroke-dasharray", "5,3");
978 document = document.add(line);
979 }
980 }
981 }
982
983 for ((scope_name, vars), (_, x, y)) in selected_scopes.iter().zip(positions.iter()) {
985 document = render_scope_matrix_fixed(
986 document,
987 scope_name,
988 vars,
989 *x,
990 *y,
991 base_matrix_width,
992 base_matrix_height,
993 max_duration,
994 )?;
995 }
996
997 if scope_groups.len() > 15 {
999 export_scope_analysis_json(&scope_groups, &selected_scopes)?;
1000 }
1001
1002 Ok(document)
1003}
1004
1005#[allow(clippy::too_many_arguments)]
1007fn render_scope_matrix_fixed(
1008 mut document: Document,
1009 scope_name: &str,
1010 vars: &[&AllocationInfo],
1011 x: i32,
1012 y: i32,
1013 width: i32,
1014 height: i32,
1015 max_duration: u64, ) -> TrackingResult<Document> {
1017 let (dynamic_width, dynamic_height) = calculate_dynamic_matrix_size(vars.len());
1019 let actual_width = dynamic_width.max(width);
1020 let actual_height = dynamic_height.max(height);
1021
1022 let mut matrix_group = Group::new().set("transform", format!("translate({x}, {y})"));
1023
1024 let duration = calculate_scope_lifetime(scope_name, vars);
1026 let total_memory = vars.iter().map(|v| v.size).sum::<usize>();
1027 let active_vars = vars.len();
1031
1032 let duration_ratio = if max_duration > 0 {
1034 duration as f64 / max_duration as f64
1035 } else {
1036 0.0
1037 };
1038
1039 let is_global = scope_name == "Global";
1041 let border_color = get_duration_color(duration_ratio, is_global);
1042
1043 let container = Rectangle::new()
1045 .set("width", actual_width)
1046 .set("height", actual_height)
1047 .set("fill", "rgba(30, 64, 175, 0.1)")
1048 .set("stroke", border_color.as_str())
1049 .set("stroke-width", 3)
1050 .set(
1051 "stroke-dasharray",
1052 if scope_name != "Global" {
1053 "8,4"
1054 } else {
1055 "none"
1056 },
1057 )
1058 .set("rx", 12);
1059 matrix_group = matrix_group.add(container);
1060
1061 let header_text = format!(
1063 "Scope: {} | Memory: {} | Variables: {} | Lifetime: {}ms",
1064 scope_name,
1065 format_bytes(total_memory),
1066 active_vars,
1067 duration
1068 );
1069 let enhanced_title = SvgText::new(header_text)
1070 .set("x", 15)
1071 .set("y", 25)
1072 .set("font-size", 11)
1073 .set("font-weight", "700")
1074 .set("fill", "#f8fafc");
1075 matrix_group = matrix_group.add(enhanced_title);
1076
1077 let var_start_y = 45;
1079 let card_height = 45; let var_spacing = 50; let _font_size = 10;
1082
1083 for (i, var) in vars.iter().take(4).enumerate() {
1084 let var_y = var_start_y + (i as i32 * var_spacing);
1086 let default_name = "<unknown>".to_string();
1087 let var_name = var.var_name.as_ref().unwrap_or(&default_name);
1088 let type_name = get_simple_type(var.type_name.as_ref().unwrap_or(&"Unknown".to_string()));
1089 let duration_ms = estimate_variable_duration(var);
1090
1091 let max_size_in_scope = vars.iter().map(|v| v.size).max().unwrap_or(1);
1093 let progress_ratio = var.size as f64 / max_size_in_scope as f64;
1094 let _progress_width = (progress_ratio * 180.0) as i32;
1095
1096 let card_width = actual_width - 20;
1098 let card_bg = Rectangle::new()
1099 .set("x", 10)
1100 .set("y", var_y - 5)
1101 .set("width", card_width)
1102 .set("height", card_height)
1103 .set("fill", "rgba(255, 255, 255, 0.08)")
1104 .set("stroke", "rgba(255, 255, 255, 0.15)")
1105 .set("stroke-width", 1)
1106 .set("rx", 8)
1107 .set("ry", 8);
1108 matrix_group = matrix_group.add(card_bg);
1109
1110 let var_label = SvgText::new(var_name)
1112 .set("x", 18)
1113 .set("y", var_y + 8)
1114 .set("font-size", 12)
1115 .set("font-weight", "bold")
1116 .set("fill", "#FFFFFF")
1117 .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
1118 matrix_group = matrix_group.add(var_label);
1119
1120 let (type_start_color, _) = get_type_gradient_colors(&type_name);
1122 let type_label = SvgText::new(format!("({type_name})"))
1123 .set("x", 18)
1124 .set("y", var_y + 22)
1125 .set("font-size", 9)
1126 .set("fill", type_start_color)
1127 .set("font-weight", "600");
1128 matrix_group = matrix_group.add(type_label);
1129
1130 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()
1136 .set("x", progress_x)
1137 .set("y", var_y + 15) .set("width", progress_bar_width)
1139 .set("height", 8)
1140 .set("fill", "rgba(255, 255, 255, 0.1)")
1141 .set("stroke", "rgba(255, 255, 255, 0.2)")
1142 .set("stroke-width", 1)
1143 .set("rx", 4)
1144 .set("ry", 4);
1145 matrix_group = matrix_group.add(progress_bg);
1146
1147 let (start_color, _) = get_type_gradient_colors(&type_name);
1149 let progress_fill_width = (progress_ratio * progress_bar_width as f64) as i32;
1150 let progress_fill = Rectangle::new()
1151 .set("x", progress_x)
1152 .set("y", var_y + 15)
1153 .set("width", progress_fill_width)
1154 .set("height", 8)
1155 .set("fill", start_color) .set("rx", 4)
1157 .set("ry", 4);
1158 matrix_group = matrix_group.add(progress_fill);
1159
1160 let size_display = format!(
1162 "{} / {}",
1163 format_bytes(var.size),
1164 format_bytes(max_size_in_scope)
1165 );
1166 let size_label = SvgText::new(size_display)
1167 .set("x", progress_x + progress_bar_width + 10)
1168 .set("y", var_y + 20)
1169 .set("font-size", 8)
1170 .set("font-weight", "bold")
1171 .set("fill", "#E2E8F0");
1172 matrix_group = matrix_group.add(size_label);
1173
1174 let time_label = SvgText::new(format!("Active {duration_ms}ms"))
1176 .set("x", 20)
1177 .set("y", var_y + 30)
1178 .set("font-size", 7)
1179 .set("fill", "#FCD34D")
1180 .set("font-weight", "500");
1181 matrix_group = matrix_group.add(time_label);
1182 }
1183
1184 if vars.len() > 4 {
1186 let more_text = format!("+ {} more variables", vars.len() - 4);
1187 let more_label = SvgText::new(more_text)
1188 .set("x", 20)
1189 .set("y", var_start_y + (4 * var_spacing) + 10)
1190 .set("font-size", 9)
1191 .set("font-weight", "500")
1192 .set("fill", "#94A3B8")
1193 .set("font-style", "italic");
1194 matrix_group = matrix_group.add(more_label);
1195 }
1196
1197 let explanation_y = actual_height - 15;
1199 let explanation_text = "Progress Bar: Current Size / Max Size in Scope";
1200 let explanation = SvgText::new(explanation_text)
1201 .set("x", 15)
1202 .set("y", explanation_y)
1203 .set("font-size", 8)
1204 .set("font-weight", "500")
1205 .set("fill", "#FCD34D")
1206 .set("font-style", "italic");
1207 matrix_group = matrix_group.add(explanation);
1208
1209 document = document.add(matrix_group);
1210 Ok(document)
1211}
1212
1213fn identify_precise_scope(allocation: &AllocationInfo) -> String {
1215 if let Some(var_name) = &allocation.var_name {
1216 if var_name.contains("global") {
1217 return "Global".to_string();
1218 }
1219 match allocation.timestamp_alloc {
1221 0..=1000 => "Global".to_string(),
1222 1001..=2000 => "demonstrate_builtin_types".to_string(),
1223 2001..=3000 => "demonstrate_smart_pointers".to_string(),
1224 3001..=4000 => "demonstrate_custom_structures".to_string(),
1225 4001..=5000 => "demonstrate_complex_patterns".to_string(),
1226 5001..=6000 => "simulate_web_server_scenario".to_string(),
1227 _ => "simulate_data_processing_pipeline".to_string(),
1228 }
1229 } else {
1230 "Global".to_string()
1231 }
1232}
1233
1234fn estimate_variable_duration(var: &AllocationInfo) -> u64 {
1236 let base_duration = match var.size {
1237 0..=100 => 10,
1238 101..=1000 => 50,
1239 1001..=10000 => 100,
1240 _ => 200,
1241 };
1242
1243 let type_multiplier = if let Some(type_name) = &var.type_name {
1244 if type_name.contains("Vec") || type_name.contains("HashMap") {
1245 2.0
1246 } else if type_name.contains("Box") || type_name.contains("Rc") {
1247 1.5
1248 } else {
1249 1.0
1250 }
1251 } else {
1252 1.0
1253 };
1254
1255 (base_duration as f64 * type_multiplier) as u64
1256}
1257
1258use crate::core::types::TypeMemoryUsage;
1263
1264pub fn enhance_type_information(
1268 memory_by_type: &[TypeMemoryUsage],
1269 allocations: &[AllocationInfo],
1270) -> Vec<crate::export::export_enhanced::EnhancedTypeInfo> {
1271 let mut enhanced_types = Vec::new();
1272
1273 for usage in memory_by_type {
1274 if usage.type_name == "Unknown" {
1276 continue;
1277 }
1278
1279 let (simplified_name, category, subcategory) =
1281 analyze_type_with_detailed_subcategory(&usage.type_name);
1282
1283 let variable_names: Vec<String> = allocations
1285 .iter()
1286 .filter_map(|alloc| {
1287 if let (Some(var_name), Some(type_name)) = (&alloc.var_name, &alloc.type_name) {
1288 let (alloc_simplified, _, _) =
1289 analyze_type_with_detailed_subcategory(type_name);
1290 if alloc_simplified == simplified_name {
1291 Some(var_name)
1292 } else {
1293 None
1294 }
1295 } else {
1296 None
1297 }
1298 })
1299 .take(5)
1300 .cloned()
1301 .collect();
1302
1303 enhanced_types.push(crate::export::export_enhanced::EnhancedTypeInfo {
1305 simplified_name,
1306 category,
1307 subcategory,
1308 total_size: usage.total_size,
1309 allocation_count: usage.allocation_count,
1310 variable_names,
1311 });
1312 }
1313
1314 enhanced_types
1315}
1316
1317fn analyze_type_with_detailed_subcategory(type_name: &str) -> (String, String, String) {
1319 let clean_type = type_name.trim();
1320
1321 if clean_type.is_empty() || clean_type == "Unknown" {
1323 return (
1324 "Unknown Type".to_string(),
1325 "Unknown".to_string(),
1326 "Other".to_string(),
1327 );
1328 }
1329
1330 if clean_type.contains("Vec<") || clean_type.contains("vec::Vec") {
1332 return (
1333 "Vec<T>".to_string(),
1334 "Collections".to_string(),
1335 "Vec<T>".to_string(),
1336 );
1337 }
1338
1339 if clean_type.contains("HashMap") {
1340 return (
1341 "HashMap<K,V>".to_string(),
1342 "Collections".to_string(),
1343 "HashMap<K,V>".to_string(),
1344 );
1345 }
1346
1347 if clean_type.contains("String") {
1348 return (
1349 "String".to_string(),
1350 "Basic Types".to_string(),
1351 "Strings".to_string(),
1352 );
1353 }
1354
1355 if clean_type == "i32" || clean_type.ends_with("::i32") {
1357 return (
1358 "i32".to_string(),
1359 "Basic Types".to_string(),
1360 "Integers".to_string(),
1361 );
1362 }
1363
1364 if clean_type == "u8" || clean_type.ends_with("::u8") {
1365 return (
1366 "u8".to_string(),
1367 "Basic Types".to_string(),
1368 "Integers".to_string(),
1369 );
1370 }
1371
1372 (
1374 clean_type.to_string(),
1375 "Other".to_string(),
1376 "Custom".to_string(),
1377 )
1378}
1379
1380pub fn categorize_enhanced_allocations(
1382 enhanced_types: &[crate::export::export_enhanced::EnhancedTypeInfo],
1383) -> Vec<crate::export::export_enhanced::AllocationCategory> {
1384 crate::export::export_enhanced::categorize_enhanced_allocations(enhanced_types)
1385}
1386
1387pub fn export_unsafe_ffi_dashboard<P: AsRef<Path>>(
1389 tracker: &UnsafeFFITracker,
1390 path: P,
1391) -> TrackingResult<()> {
1392 let path = path.as_ref();
1393 tracing::info!("Exporting unsafe/FFI dashboard to: {}", path.display());
1394
1395 if let Some(parent) = path.parent() {
1396 if !parent.exists() {
1397 std::fs::create_dir_all(parent)?;
1398 }
1399 }
1400
1401 let enhanced_allocations = tracker.get_enhanced_allocations()?;
1402 let safety_violations = tracker.get_safety_violations()?;
1403
1404 let document = create_unsafe_ffi_dashboard(&enhanced_allocations, &safety_violations)?;
1405
1406 let mut file = File::create(path)?;
1407 svg::write(&mut file, &document)
1408 .map_err(|e| TrackingError::SerializationError(format!("Failed to write SVG: {e}")))?;
1409
1410 tracing::info!("Successfully exported unsafe/FFI dashboard SVG");
1411 Ok(())
1412}
1413
1414fn create_unsafe_ffi_dashboard(
1416 allocations: &[EnhancedAllocationInfo],
1417 violations: &[SafetyViolation],
1418) -> TrackingResult<Document> {
1419 let width = 1400;
1420 let height = 1000;
1421
1422 let mut document = Document::new()
1423 .set("viewBox", (0, 0, width, height))
1424 .set("width", width)
1425 .set("height", height)
1426 .set("style", "background: linear-gradient(135deg, #2c3e50 0%, #34495e 50%, #2c3e50 100%); font-family: 'Segoe UI', Arial, sans-serif;");
1427
1428 document = add_svg_definitions(document);
1430
1431 document = add_dashboard_header(document, allocations, violations)?;
1433
1434 document = add_allocation_source_breakdown(document, allocations)?;
1436 document = add_memory_safety_status(document, violations)?;
1437 document = add_boundary_crossing_flow(document, allocations)?;
1438 document = add_unsafe_hotspots(document, allocations)?;
1439
1440 Ok(document)
1441}
1442
1443fn add_svg_definitions(document: Document) -> Document {
1445 let mut defs = Definitions::new();
1446
1447 let arrow_marker = Marker::new()
1449 .set("id", "arrowhead")
1450 .set("markerWidth", 10)
1451 .set("markerHeight", 7)
1452 .set("refX", 9)
1453 .set("refY", 3.5)
1454 .set("orient", "auto")
1455 .add(
1456 Polygon::new()
1457 .set("points", "0 0, 10 3.5, 0 7")
1458 .set("fill", "#e74c3c"),
1459 );
1460 defs = defs.add(arrow_marker);
1461
1462 document.add(defs)
1463}
1464
1465fn add_dashboard_header(
1467 mut document: Document,
1468 allocations: &[EnhancedAllocationInfo],
1469 violations: &[SafetyViolation],
1470) -> TrackingResult<Document> {
1471 let title = SvgText::new("Unsafe Rust & FFI Memory Analysis Dashboard")
1473 .set("x", 700)
1474 .set("y", 40)
1475 .set("text-anchor", "middle")
1476 .set("font-size", 24)
1477 .set("font-weight", "bold")
1478 .set("fill", "#ecf0f1");
1479 document = document.add(title);
1480
1481 let unsafe_count = allocations
1483 .iter()
1484 .filter(|a| matches!(a.source, AllocationSource::UnsafeRust { .. }))
1485 .count();
1486 let ffi_count = allocations
1487 .iter()
1488 .filter(|a| matches!(a.source, AllocationSource::FfiC { .. }))
1489 .count();
1490 let cross_boundary_events: usize = allocations
1491 .iter()
1492 .map(|a| a.cross_boundary_events.len())
1493 .sum();
1494 let total_unsafe_memory: usize = allocations
1495 .iter()
1496 .filter(|a| !matches!(a.source, AllocationSource::RustSafe))
1497 .map(|a| a.base.size)
1498 .sum();
1499
1500 let metrics = [
1502 ("Unsafe Allocations", unsafe_count, "#e74c3c"),
1503 ("FFI Allocations", ffi_count, "#3498db"),
1504 ("Boundary Crossings", cross_boundary_events, "#f39c12"),
1505 ("Safety Violations", violations.len(), "#e67e22"),
1506 ("Unsafe Memory", total_unsafe_memory, "#9b59b6"),
1507 ];
1508
1509 for (i, (label, value, color)) in metrics.iter().enumerate() {
1510 let x = 100 + i as i32 * 250;
1511 let y = 80;
1512
1513 let card = Rectangle::new()
1515 .set("x", x - 60)
1516 .set("y", y - 25)
1517 .set("width", 120)
1518 .set("height", 50)
1519 .set("fill", *color)
1520 .set("fill-opacity", 0.2)
1521 .set("stroke", *color)
1522 .set("stroke-width", 2)
1523 .set("rx", 8);
1524 document = document.add(card);
1525
1526 let value_text = SvgText::new(value.to_string())
1528 .set("x", x)
1529 .set("y", y - 5)
1530 .set("text-anchor", "middle")
1531 .set("font-size", 16)
1532 .set("font-weight", "bold")
1533 .set("fill", *color);
1534 document = document.add(value_text);
1535
1536 let label_text = SvgText::new(*label)
1538 .set("x", x)
1539 .set("y", y + 15)
1540 .set("text-anchor", "middle")
1541 .set("font-size", 10)
1542 .set("fill", "#bdc3c7");
1543 document = document.add(label_text);
1544 }
1545
1546 Ok(document)
1547}
1548
1549fn add_allocation_source_breakdown(
1551 mut document: Document,
1552 allocations: &[EnhancedAllocationInfo],
1553) -> TrackingResult<Document> {
1554 let start_x = 50;
1555 let start_y = 150;
1556 let width = 600;
1557 let height = 300;
1558
1559 let title = SvgText::new("Memory Allocation Sources")
1561 .set("x", start_x + width / 2)
1562 .set("y", start_y - 10)
1563 .set("text-anchor", "middle")
1564 .set("font-size", 18)
1565 .set("font-weight", "bold")
1566 .set("fill", "#ecf0f1");
1567 document = document.add(title);
1568
1569 let bg = Rectangle::new()
1571 .set("x", start_x)
1572 .set("y", start_y)
1573 .set("width", width)
1574 .set("height", height)
1575 .set("fill", "rgba(52, 73, 94, 0.3)")
1576 .set("stroke", "#34495e")
1577 .set("stroke-width", 2)
1578 .set("rx", 10);
1579 document = document.add(bg);
1580
1581 let mut safe_count = 0;
1583 let mut unsafe_count = 0;
1584 let mut ffi_count = 0;
1585 let mut cross_boundary_count = 0;
1586
1587 for allocation in allocations {
1588 match &allocation.source {
1589 AllocationSource::RustSafe => safe_count += 1,
1590 AllocationSource::UnsafeRust { .. } => unsafe_count += 1,
1591 AllocationSource::FfiC { .. } => ffi_count += 1,
1592 AllocationSource::CrossBoundary { .. } => cross_boundary_count += 1,
1593 }
1594 }
1595
1596 let total = safe_count + unsafe_count + ffi_count + cross_boundary_count;
1597 if total == 0 {
1598 let no_data = SvgText::new("No allocation data available")
1599 .set("x", start_x + width / 2)
1600 .set("y", start_y + height / 2)
1601 .set("text-anchor", "middle")
1602 .set("font-size", 14)
1603 .set("fill", "#95a5a6");
1604 document = document.add(no_data);
1605 return Ok(document);
1606 }
1607
1608 let sources = [
1610 ("Safe Rust", safe_count, "#2ecc71"),
1611 ("Unsafe Rust", unsafe_count, "#e74c3c"),
1612 ("FFI", ffi_count, "#3498db"),
1613 ("Cross-boundary", cross_boundary_count, "#9b59b6"),
1614 ];
1615
1616 for (i, (label, count, color)) in sources.iter().enumerate() {
1617 if *count > 0 {
1618 let x = start_x + 50 + i as i32 * 120;
1619 let y = start_y + 200;
1620 let bar_height = (*count as f32 / total as f32 * 100.0) as i32;
1621
1622 let bar = Rectangle::new()
1624 .set("x", x)
1625 .set("y", y - bar_height)
1626 .set("width", 40)
1627 .set("height", bar_height)
1628 .set("fill", *color);
1629 document = document.add(bar);
1630
1631 let count_text = SvgText::new(count.to_string())
1633 .set("x", x + 20)
1634 .set("y", y - bar_height - 5)
1635 .set("text-anchor", "middle")
1636 .set("font-size", 12)
1637 .set("font-weight", "bold")
1638 .set("fill", *color);
1639 document = document.add(count_text);
1640
1641 let label_text = SvgText::new(*label)
1643 .set("x", x + 20)
1644 .set("y", y + 20)
1645 .set("text-anchor", "middle")
1646 .set("font-size", 10)
1647 .set("fill", "#ecf0f1");
1648 document = document.add(label_text);
1649 }
1650 }
1651
1652 Ok(document)
1653}
1654
1655fn add_memory_safety_status(
1657 mut document: Document,
1658 violations: &[SafetyViolation],
1659) -> TrackingResult<Document> {
1660 let start_x = 750;
1661 let start_y = 150;
1662 let width = 600;
1663 let height = 300;
1664
1665 let title = SvgText::new("Memory Safety Status")
1667 .set("x", start_x + width / 2)
1668 .set("y", start_y - 10)
1669 .set("text-anchor", "middle")
1670 .set("font-size", 18)
1671 .set("font-weight", "bold")
1672 .set("fill", "#ecf0f1");
1673 document = document.add(title);
1674
1675 let bg_color = if violations.is_empty() {
1677 "#27ae60"
1678 } else {
1679 "#e74c3c"
1680 };
1681 let bg = Rectangle::new()
1682 .set("x", start_x)
1683 .set("y", start_y)
1684 .set("width", width)
1685 .set("height", height)
1686 .set("fill", format!("{bg_color}20"))
1687 .set("stroke", bg_color)
1688 .set("stroke-width", 2)
1689 .set("rx", 10);
1690 document = document.add(bg);
1691
1692 if violations.is_empty() {
1693 let safe_text = SvgText::new("No Safety Violations Detected")
1695 .set("x", start_x + width / 2)
1696 .set("y", start_y + 150)
1697 .set("text-anchor", "middle")
1698 .set("font-size", 16)
1699 .set("font-weight", "bold")
1700 .set("fill", "#27ae60");
1701 document = document.add(safe_text);
1702
1703 let safe_desc =
1704 SvgText::new("All unsafe operations and FFI calls appear to be memory-safe")
1705 .set("x", start_x + width / 2)
1706 .set("y", start_y + 180)
1707 .set("text-anchor", "middle")
1708 .set("font-size", 12)
1709 .set("fill", "#2ecc71");
1710 document = document.add(safe_desc);
1711 } else {
1712 let violation_text =
1714 SvgText::new(format!("{} Safety Violations Detected", violations.len()))
1715 .set("x", start_x + width / 2)
1716 .set("y", start_y + 120)
1717 .set("text-anchor", "middle")
1718 .set("font-size", 16)
1719 .set("font-weight", "bold")
1720 .set("fill", "#e74c3c");
1721 document = document.add(violation_text);
1722
1723 for (i, violation) in violations.iter().take(5).enumerate() {
1725 let y = start_y + 160 + i as i32 * 20;
1726
1727 let description = match violation {
1728 SafetyViolation::DoubleFree { .. } => "Double Free",
1729 SafetyViolation::InvalidFree { .. } => "Invalid Free",
1730 SafetyViolation::PotentialLeak { .. } => "Memory Leak",
1731 SafetyViolation::CrossBoundaryRisk { .. } => "Cross-Boundary Risk",
1732 };
1733
1734 let violation_item = SvgText::new(format!("• {description}"))
1735 .set("x", start_x + 30)
1736 .set("y", y)
1737 .set("font-size", 12)
1738 .set("fill", "#e74c3c");
1739 document = document.add(violation_item);
1740 }
1741 }
1742
1743 Ok(document)
1744}
1745
1746fn add_boundary_crossing_flow(
1748 mut document: Document,
1749 allocations: &[EnhancedAllocationInfo],
1750) -> TrackingResult<Document> {
1751 let start_x = 50;
1752 let start_y = 500;
1753 let width = 600;
1754 let height = 200;
1755
1756 let title = SvgText::new("Cross-Language Memory Flow")
1758 .set("x", start_x + width / 2)
1759 .set("y", start_y - 10)
1760 .set("text-anchor", "middle")
1761 .set("font-size", 18)
1762 .set("font-weight", "bold")
1763 .set("fill", "#ecf0f1");
1764 document = document.add(title);
1765
1766 let bg = Rectangle::new()
1768 .set("x", start_x)
1769 .set("y", start_y)
1770 .set("width", width)
1771 .set("height", height)
1772 .set("fill", "rgba(52, 73, 94, 0.3)")
1773 .set("stroke", "#34495e")
1774 .set("stroke-width", 2)
1775 .set("rx", 10);
1776 document = document.add(bg);
1777
1778 let rust_box = Rectangle::new()
1780 .set("x", start_x + 50)
1781 .set("y", start_y + 50)
1782 .set("width", 200)
1783 .set("height", 100)
1784 .set("fill", "#2ecc71")
1785 .set("fill-opacity", 0.2)
1786 .set("stroke", "#2ecc71")
1787 .set("stroke-width", 2)
1788 .set("rx", 8);
1789 document = document.add(rust_box);
1790
1791 let rust_label = SvgText::new("RUST")
1792 .set("x", start_x + 150)
1793 .set("y", start_y + 110)
1794 .set("text-anchor", "middle")
1795 .set("font-size", 14)
1796 .set("font-weight", "bold")
1797 .set("fill", "#2ecc71");
1798 document = document.add(rust_label);
1799
1800 let ffi_box = Rectangle::new()
1802 .set("x", start_x + 350)
1803 .set("y", start_y + 50)
1804 .set("width", 200)
1805 .set("height", 100)
1806 .set("fill", "#3498db")
1807 .set("fill-opacity", 0.2)
1808 .set("stroke", "#3498db")
1809 .set("stroke-width", 2)
1810 .set("rx", 8);
1811 document = document.add(ffi_box);
1812
1813 let ffi_label = SvgText::new("FFI / C")
1814 .set("x", start_x + 450)
1815 .set("y", start_y + 110)
1816 .set("text-anchor", "middle")
1817 .set("font-size", 14)
1818 .set("font-weight", "bold")
1819 .set("fill", "#3498db");
1820 document = document.add(ffi_label);
1821
1822 let mut rust_to_ffi = 0;
1824 let mut ffi_to_rust = 0;
1825
1826 for allocation in allocations {
1827 for event in &allocation.cross_boundary_events {
1828 match event.event_type {
1829 BoundaryEventType::RustToFfi => rust_to_ffi += 1,
1830 BoundaryEventType::FfiToRust => ffi_to_rust += 1,
1831 BoundaryEventType::OwnershipTransfer => rust_to_ffi += 1,
1832 BoundaryEventType::SharedAccess => {
1833 rust_to_ffi += 1;
1834 ffi_to_rust += 1;
1835 }
1836 }
1837 }
1838 }
1839
1840 if rust_to_ffi > 0 {
1842 let arrow = Line::new()
1843 .set("x1", start_x + 250)
1844 .set("y1", start_y + 80)
1845 .set("x2", start_x + 350)
1846 .set("y2", start_y + 80)
1847 .set("stroke", "#e74c3c")
1848 .set("stroke-width", 3)
1849 .set("marker-end", "url(#arrowhead)");
1850 document = document.add(arrow);
1851
1852 let count_text = SvgText::new(rust_to_ffi.to_string())
1853 .set("x", start_x + 300)
1854 .set("y", start_y + 75)
1855 .set("text-anchor", "middle")
1856 .set("font-size", 12)
1857 .set("font-weight", "bold")
1858 .set("fill", "#e74c3c");
1859 document = document.add(count_text);
1860 }
1861
1862 if ffi_to_rust > 0 {
1863 let count_text = SvgText::new(ffi_to_rust.to_string())
1864 .set("x", start_x + 300)
1865 .set("y", start_y + 135)
1866 .set("text-anchor", "middle")
1867 .set("font-size", 12)
1868 .set("font-weight", "bold")
1869 .set("fill", "#f39c12");
1870 document = document.add(count_text);
1871 }
1872
1873 Ok(document)
1874}
1875
1876fn add_unsafe_hotspots(
1878 mut document: Document,
1879 allocations: &[EnhancedAllocationInfo],
1880) -> TrackingResult<Document> {
1881 let start_x = 750;
1882 let start_y = 500;
1883 let width = 600;
1884 let height = 200;
1885
1886 let title = SvgText::new("Unsafe Memory Hotspots")
1888 .set("x", start_x + width / 2)
1889 .set("y", start_y - 10)
1890 .set("text-anchor", "middle")
1891 .set("font-size", 18)
1892 .set("font-weight", "bold")
1893 .set("fill", "#ecf0f1");
1894 document = document.add(title);
1895
1896 let bg = Rectangle::new()
1898 .set("x", start_x)
1899 .set("y", start_y)
1900 .set("width", width)
1901 .set("height", height)
1902 .set("fill", "rgba(52, 73, 94, 0.3)")
1903 .set("stroke", "#34495e")
1904 .set("stroke-width", 2)
1905 .set("rx", 10);
1906 document = document.add(bg);
1907
1908 let unsafe_allocations: Vec<_> = allocations
1910 .iter()
1911 .filter(|a| !matches!(a.source, AllocationSource::RustSafe))
1912 .collect();
1913
1914 if unsafe_allocations.is_empty() {
1915 let no_unsafe = SvgText::new("No unsafe memory allocations detected")
1916 .set("x", start_x + width / 2)
1917 .set("y", start_y + height / 2)
1918 .set("text-anchor", "middle")
1919 .set("font-size", 14)
1920 .set("fill", "#2ecc71");
1921 document = document.add(no_unsafe);
1922 return Ok(document);
1923 }
1924
1925 for (i, allocation) in unsafe_allocations.iter().take(6).enumerate() {
1927 let x = start_x + 80 + (i % 3) as i32 * 180;
1928 let y = start_y + 80 + (i / 3) as i32 * 70;
1929
1930 let size_factor = (allocation.base.size.min(1000) as f32 / 1000.0 * 15.0 + 5.0) as i32;
1932 let color = match &allocation.source {
1933 AllocationSource::UnsafeRust { .. } => "#e74c3c",
1934 AllocationSource::FfiC { .. } => "#3498db",
1935 AllocationSource::CrossBoundary { .. } => "#9b59b6",
1936 _ => "#95a5a6",
1937 };
1938
1939 let hotspot = Circle::new()
1940 .set("cx", x)
1941 .set("cy", y)
1942 .set("r", size_factor)
1943 .set("fill", color)
1944 .set("fill-opacity", 0.7)
1945 .set("stroke", color)
1946 .set("stroke-width", 2);
1947 document = document.add(hotspot);
1948
1949 let size_text = SvgText::new(format_bytes(allocation.base.size))
1951 .set("x", x)
1952 .set("y", y + 4)
1953 .set("text-anchor", "middle")
1954 .set("font-size", 8)
1955 .set("font-weight", "bold")
1956 .set("fill", "#ffffff");
1957 document = document.add(size_text);
1958
1959 let type_label = match &allocation.source {
1961 AllocationSource::UnsafeRust { .. } => "UNSAFE",
1962 AllocationSource::FfiC { .. } => "FFI",
1963 AllocationSource::CrossBoundary { .. } => "CROSS",
1964 _ => "OTHER",
1965 };
1966
1967 let type_text = SvgText::new(type_label)
1968 .set("x", x)
1969 .set("y", y + 35)
1970 .set("text-anchor", "middle")
1971 .set("font-size", 10)
1972 .set("fill", color);
1973 document = document.add(type_text);
1974 }
1975
1976 Ok(document)
1977}
1978
1979#[cfg(test)]
1980mod tests {
1981 use super::*;
1982 use crate::core::tracker::MemoryTracker;
1983 use crate::core::types::{AllocationInfo, MemoryStats, TypeMemoryUsage};
1984 use tempfile::TempDir;
1985
1986 fn create_test_allocation(
1987 ptr: usize,
1988 size: usize,
1989 type_name: Option<String>,
1990 var_name: Option<String>,
1991 ) -> AllocationInfo {
1992 let mut allocation = AllocationInfo::new(ptr, size);
1993 allocation.type_name = type_name;
1994 allocation.var_name = var_name;
1995 allocation
1996 }
1997
1998 fn create_test_memory_stats() -> MemoryStats {
1999 let mut stats = MemoryStats::new();
2000 stats.total_allocations = 10;
2001 stats.total_deallocations = 5;
2002 stats.active_allocations = 5;
2003 stats.peak_memory = 1024;
2004 stats.active_memory = 512;
2005 stats.total_allocated = 2048;
2006 stats.total_deallocated = 1024;
2007 stats.leaked_allocations = 0;
2008 stats
2009 }
2010
2011 #[test]
2012 fn test_export_memory_analysis_creates_svg_file() {
2013 let temp_dir = TempDir::new().unwrap();
2014 let svg_path = temp_dir.path().join("memory_analysis.svg");
2015
2016 let tracker = MemoryTracker::new();
2018
2019 let result = export_memory_analysis(&tracker, &svg_path);
2021 assert!(result.is_ok());
2022 assert!(svg_path.exists());
2023
2024 let content = std::fs::read_to_string(&svg_path).unwrap();
2026 assert!(content.contains("<svg"));
2027 assert!(content.contains("</svg>"));
2028 }
2029
2030 #[test]
2031 fn test_export_memory_analysis_creates_parent_directory() {
2032 let temp_dir = TempDir::new().unwrap();
2033 let nested_path = temp_dir
2034 .path()
2035 .join("nested")
2036 .join("dir")
2037 .join("memory_analysis.svg");
2038
2039 let tracker = MemoryTracker::new();
2040 let result = export_memory_analysis(&tracker, &nested_path);
2041
2042 assert!(result.is_ok());
2043 assert!(nested_path.exists());
2044 assert!(nested_path.parent().unwrap().exists());
2045 }
2046
2047 #[test]
2048 fn test_export_lifecycle_timeline_creates_svg_file() {
2049 let temp_dir = TempDir::new().unwrap();
2050 let svg_path = temp_dir.path().join("lifecycle_timeline.svg");
2051
2052 let tracker = MemoryTracker::new();
2053 let result = export_lifecycle_timeline(&tracker, &svg_path);
2054
2055 assert!(result.is_ok());
2056 assert!(svg_path.exists());
2057
2058 let content = std::fs::read_to_string(&svg_path).unwrap();
2059 assert!(content.contains("<svg"));
2060 assert!(content.contains("viewBox"));
2061 }
2062
2063 #[test]
2064 fn test_create_memory_analysis_svg_basic_structure() {
2065 let allocations = vec![
2066 create_test_allocation(
2067 0x1000,
2068 64,
2069 Some("String".to_string()),
2070 Some("test_var".to_string()),
2071 ),
2072 create_test_allocation(
2073 0x2000,
2074 128,
2075 Some("Vec<i32>".to_string()),
2076 Some("test_vec".to_string()),
2077 ),
2078 ];
2079 let stats = create_test_memory_stats();
2080 let tracker = MemoryTracker::new();
2081
2082 let result = create_memory_analysis_svg(&allocations, &stats, &tracker);
2083 assert!(result.is_ok());
2084
2085 let document = result.unwrap();
2086 let svg_string = document.to_string();
2087 assert!(svg_string.contains("viewBox"));
2088 assert!(svg_string.contains("width"));
2089 assert!(svg_string.contains("height"));
2090 }
2091
2092 #[test]
2093 fn test_create_memory_analysis_svg_with_empty_allocations() {
2094 let allocations = vec![];
2095 let stats = create_test_memory_stats();
2096 let tracker = MemoryTracker::new();
2097
2098 let result = create_memory_analysis_svg(&allocations, &stats, &tracker);
2099 assert!(result.is_ok());
2100
2101 let document = result.unwrap();
2102 let svg_string = document.to_string();
2103 assert!(svg_string.contains("<svg"));
2104 }
2105
2106 #[test]
2107 fn test_create_lifecycle_timeline_svg_basic_structure() {
2108 let allocations = vec![
2109 create_test_allocation(
2110 0x1000,
2111 64,
2112 Some("String".to_string()),
2113 Some("var1".to_string()),
2114 ),
2115 create_test_allocation(
2116 0x2000,
2117 128,
2118 Some("i32".to_string()),
2119 Some("var2".to_string()),
2120 ),
2121 ];
2122 let stats = create_test_memory_stats();
2123
2124 let result = create_lifecycle_timeline_svg(&allocations, &stats);
2125 assert!(result.is_ok());
2126
2127 let document = result.unwrap();
2128 let svg_string = document.to_string();
2129 assert!(svg_string.contains("viewBox=\"0 0 1600 1200\""));
2130 assert!(svg_string.contains("timeline-bar"));
2131 }
2132
2133 #[test]
2134 fn test_create_lifecycle_timeline_svg_with_empty_allocations() {
2135 let allocations = vec![];
2136 let stats = create_test_memory_stats();
2137
2138 let result = create_lifecycle_timeline_svg(&allocations, &stats);
2139 assert!(result.is_ok());
2140
2141 let document = result.unwrap();
2142 let svg_string = document.to_string();
2143 assert!(svg_string.contains("<svg"));
2144 }
2145
2146 #[test]
2147 fn test_enhance_type_information_with_basic_types() {
2148 let memory_by_type = vec![
2149 TypeMemoryUsage {
2150 type_name: "String".to_string(),
2151 total_size: 256,
2152 allocation_count: 4,
2153 average_size: 64.0,
2154 peak_size: 128,
2155 current_size: 256,
2156 efficiency_score: 0.8,
2157 },
2158 TypeMemoryUsage {
2159 type_name: "Vec<i32>".to_string(),
2160 total_size: 512,
2161 allocation_count: 2,
2162 average_size: 256.0,
2163 peak_size: 512,
2164 current_size: 512,
2165 efficiency_score: 0.9,
2166 },
2167 TypeMemoryUsage {
2168 type_name: "Unknown".to_string(),
2169 total_size: 64,
2170 allocation_count: 1,
2171 average_size: 64.0,
2172 peak_size: 64,
2173 current_size: 64,
2174 efficiency_score: 0.5,
2175 },
2176 ];
2177
2178 let allocations = vec![
2179 create_test_allocation(
2180 0x1000,
2181 64,
2182 Some("String".to_string()),
2183 Some("str1".to_string()),
2184 ),
2185 create_test_allocation(
2186 0x2000,
2187 128,
2188 Some("String".to_string()),
2189 Some("str2".to_string()),
2190 ),
2191 create_test_allocation(
2192 0x3000,
2193 256,
2194 Some("Vec<i32>".to_string()),
2195 Some("vec1".to_string()),
2196 ),
2197 ];
2198
2199 let enhanced = enhance_type_information(&memory_by_type, &allocations);
2200
2201 assert_eq!(enhanced.len(), 2);
2203
2204 let string_type = enhanced
2206 .iter()
2207 .find(|t| t.simplified_name == "String")
2208 .unwrap();
2209 assert_eq!(string_type.category, "Basic Types");
2210 assert_eq!(string_type.subcategory, "Strings");
2211 assert_eq!(string_type.total_size, 256);
2212 assert_eq!(string_type.allocation_count, 4);
2213 assert_eq!(string_type.variable_names.len(), 2);
2214 assert!(string_type.variable_names.contains(&"str1".to_string()));
2215 assert!(string_type.variable_names.contains(&"str2".to_string()));
2216
2217 let vec_type = enhanced
2219 .iter()
2220 .find(|t| t.simplified_name == "Vec<T>")
2221 .unwrap();
2222 assert_eq!(vec_type.category, "Collections");
2223 assert_eq!(vec_type.subcategory, "Vec<T>");
2224 assert_eq!(vec_type.total_size, 512);
2225 assert_eq!(vec_type.allocation_count, 2);
2226 }
2227
2228 #[test]
2229 fn test_enhance_type_information_with_empty_input() {
2230 let memory_by_type = vec![];
2231 let allocations = vec![];
2232
2233 let enhanced = enhance_type_information(&memory_by_type, &allocations);
2234 assert!(enhanced.is_empty());
2235 }
2236
2237 #[test]
2238 fn test_analyze_type_with_detailed_subcategory_collections() {
2239 let (name, category, subcategory) = analyze_type_with_detailed_subcategory("Vec<String>");
2240 assert_eq!(name, "Vec<T>");
2241 assert_eq!(category, "Collections");
2242 assert_eq!(subcategory, "Vec<T>");
2243
2244 let (name, category, subcategory) =
2245 analyze_type_with_detailed_subcategory("std::vec::Vec<i32>");
2246 assert_eq!(name, "Vec<T>");
2247 assert_eq!(category, "Collections");
2248 assert_eq!(subcategory, "Vec<T>");
2249
2250 let (name, category, subcategory) =
2251 analyze_type_with_detailed_subcategory("HashMap<String, i32>");
2252 assert_eq!(name, "HashMap<K,V>");
2253 assert_eq!(category, "Collections");
2254 assert_eq!(subcategory, "HashMap<K,V>");
2255 }
2256
2257 #[test]
2258 fn test_analyze_type_with_detailed_subcategory_basic_types() {
2259 let (name, category, subcategory) = analyze_type_with_detailed_subcategory("String");
2260 assert_eq!(name, "String");
2261 assert_eq!(category, "Basic Types");
2262 assert_eq!(subcategory, "Strings");
2263
2264 let (name, category, subcategory) = analyze_type_with_detailed_subcategory("i32");
2265 assert_eq!(name, "i32");
2266 assert_eq!(category, "Basic Types");
2267 assert_eq!(subcategory, "Integers");
2268
2269 let (name, category, subcategory) = analyze_type_with_detailed_subcategory("u8");
2270 assert_eq!(name, "u8");
2271 assert_eq!(category, "Basic Types");
2272 assert_eq!(subcategory, "Integers");
2273 }
2274
2275 #[test]
2276 fn test_analyze_type_with_detailed_subcategory_unknown_types() {
2277 let (name, category, subcategory) = analyze_type_with_detailed_subcategory("");
2278 assert_eq!(name, "Unknown Type");
2279 assert_eq!(category, "Unknown");
2280 assert_eq!(subcategory, "Other");
2281
2282 let (name, category, subcategory) = analyze_type_with_detailed_subcategory("Unknown");
2283 assert_eq!(name, "Unknown Type");
2284 assert_eq!(category, "Unknown");
2285 assert_eq!(subcategory, "Other");
2286
2287 let (name, category, subcategory) = analyze_type_with_detailed_subcategory("CustomStruct");
2288 assert_eq!(name, "CustomStruct");
2289 assert_eq!(category, "Other");
2290 assert_eq!(subcategory, "Custom");
2291 }
2292
2293 #[test]
2294 fn test_categorize_enhanced_allocations() {
2295 let enhanced_types = vec![
2296 crate::export::export_enhanced::EnhancedTypeInfo {
2297 simplified_name: "String".to_string(),
2298 category: "Basic Types".to_string(),
2299 subcategory: "Strings".to_string(),
2300 total_size: 256,
2301 allocation_count: 2,
2302 variable_names: vec!["str1".to_string(), "str2".to_string()],
2303 },
2304 crate::export::export_enhanced::EnhancedTypeInfo {
2305 simplified_name: "Vec<T>".to_string(),
2306 category: "Collections".to_string(),
2307 subcategory: "Vec<T>".to_string(),
2308 total_size: 512,
2309 allocation_count: 1,
2310 variable_names: vec!["vec1".to_string()],
2311 },
2312 ];
2313
2314 let categorized = categorize_enhanced_allocations(&enhanced_types);
2315
2316 assert_eq!(categorized.len(), 2);
2318
2319 let basic_types_category = categorized.iter().find(|c| c.name == "Basic Types");
2321 assert!(basic_types_category.is_some());
2322 let basic_types = basic_types_category.unwrap();
2323 assert_eq!(basic_types.allocations.len(), 1);
2324 assert_eq!(basic_types.total_size, 256);
2325
2326 let collections_category = categorized.iter().find(|c| c.name == "Collections");
2328 assert!(collections_category.is_some());
2329 let collections = collections_category.unwrap();
2330 assert_eq!(collections.allocations.len(), 1);
2331 assert_eq!(collections.total_size, 512);
2332 }
2333
2334 #[test]
2335 fn test_calculate_dynamic_matrix_size_small_count() {
2336 let (width, height) = calculate_dynamic_matrix_size(3);
2337 assert!(width >= 250); assert!(height >= 150); assert!(width <= 350); }
2341
2342 #[test]
2343 fn test_calculate_dynamic_matrix_size_standard_count() {
2344 let (width, height) = calculate_dynamic_matrix_size(5);
2345 assert_eq!(width, 350); assert_eq!(height, 320); }
2348
2349 #[test]
2350 fn test_calculate_dynamic_matrix_size_large_count() {
2351 let (width, height) = calculate_dynamic_matrix_size(10);
2352 assert_eq!(width, 350); assert_eq!(height, 280); }
2355
2356 #[test]
2357 fn test_calculate_scope_lifetime_empty_vars() {
2358 let vars: Vec<&AllocationInfo> = vec![];
2359 let lifetime = calculate_scope_lifetime("test_scope", &vars);
2360 assert_eq!(lifetime, 0);
2361 }
2362
2363 #[test]
2364 fn test_calculate_scope_lifetime_global_scope() {
2365 let allocation = create_test_allocation(
2366 0x1000,
2367 64,
2368 Some("String".to_string()),
2369 Some("var1".to_string()),
2370 );
2371 let vars = vec![&allocation];
2372
2373 let lifetime = calculate_scope_lifetime("Global", &vars);
2374 assert_eq!(lifetime, 2000); }
2376
2377 #[test]
2378 fn test_calculate_scope_lifetime_local_scope_single_var() {
2379 let allocation = create_test_allocation(
2380 0x1000,
2381 64,
2382 Some("String".to_string()),
2383 Some("var1".to_string()),
2384 );
2385 let vars = vec![&allocation];
2386
2387 let lifetime = calculate_scope_lifetime("local_scope", &vars);
2388 assert_eq!(lifetime, 100); }
2390
2391 #[test]
2392 fn test_calculate_scope_lifetime_local_scope_multiple_vars() {
2393 let mut allocation1 = create_test_allocation(
2394 0x1000,
2395 64,
2396 Some("String".to_string()),
2397 Some("var1".to_string()),
2398 );
2399 let mut allocation2 = create_test_allocation(
2400 0x2000,
2401 128,
2402 Some("i32".to_string()),
2403 Some("var2".to_string()),
2404 );
2405
2406 allocation1.timestamp_alloc = 1000;
2408 allocation2.timestamp_alloc = 1500;
2409
2410 let vars = vec![&allocation1, &allocation2];
2411 let lifetime = calculate_scope_lifetime("local_scope", &vars);
2412 assert_eq!(lifetime, 500); }
2414
2415 #[test]
2416 fn test_estimate_program_runtime() {
2417 let runtime = estimate_program_runtime();
2418 assert_eq!(runtime, 2000); }
2420
2421 #[test]
2422 fn test_get_scope_background_color() {
2423 assert_eq!(
2424 get_scope_background_color("Global"),
2425 "rgba(52, 73, 94, 0.2)"
2426 );
2427 assert_eq!(
2428 get_scope_background_color("local"),
2429 "rgba(52, 152, 219, 0.2)"
2430 );
2431 assert_eq!(
2432 get_scope_background_color("function"),
2433 "rgba(52, 152, 219, 0.2)"
2434 );
2435 }
2436
2437 #[test]
2438 fn test_get_scope_border_color() {
2439 assert_eq!(get_scope_border_color("Global"), "#34495E");
2440 assert_eq!(get_scope_border_color("local"), "#3498DB");
2441 assert_eq!(get_scope_border_color("function"), "#3498DB");
2442 }
2443
2444 #[test]
2445 fn test_add_relationship_legend() {
2446 let document = Document::new();
2447 let result = add_relationship_legend(document, 100);
2448
2449 assert!(result.is_ok());
2450 let document = result.unwrap();
2451 let svg_string = document.to_string();
2452
2453 assert!(svg_string.contains("Ownership Transfer"));
2455 assert!(svg_string.contains("Mutable Borrow"));
2456 assert!(svg_string.contains("Immutable Borrow"));
2457 assert!(svg_string.contains("Clone"));
2458 assert!(svg_string.contains("Shared Pointer"));
2459 assert!(svg_string.contains("Indirect Reference"));
2460 }
2461
2462 #[test]
2463 fn test_enhance_type_information_variable_name_limit() {
2464 let memory_by_type = vec![TypeMemoryUsage {
2465 type_name: "String".to_string(),
2466 total_size: 1000,
2467 allocation_count: 10,
2468 average_size: 100.0,
2469 peak_size: 1000,
2470 current_size: 1000,
2471 efficiency_score: 0.9,
2472 }];
2473
2474 let allocations = (0..8)
2476 .map(|i| {
2477 create_test_allocation(
2478 0x1000 + i * 0x100,
2479 64,
2480 Some("String".to_string()),
2481 Some(format!("var{i}")),
2482 )
2483 })
2484 .collect::<Vec<_>>();
2485
2486 let enhanced = enhance_type_information(&memory_by_type, &allocations);
2487
2488 assert_eq!(enhanced.len(), 1);
2489 let string_type = &enhanced[0];
2490
2491 assert_eq!(string_type.variable_names.len(), 5);
2493 assert!(string_type.variable_names.contains(&"var0".to_string()));
2494 assert!(string_type.variable_names.contains(&"var4".to_string()));
2495 }
2496
2497 #[test]
2498 fn test_enhance_type_information_with_none_values() {
2499 let memory_by_type = vec![TypeMemoryUsage {
2500 type_name: "String".to_string(),
2501 total_size: 256,
2502 allocation_count: 2,
2503 average_size: 128.0,
2504 peak_size: 256,
2505 current_size: 256,
2506 efficiency_score: 0.5,
2507 }];
2508
2509 let allocations = vec![
2510 create_test_allocation(0x1000, 64, None, None), create_test_allocation(0x2000, 128, Some("String".to_string()), None), create_test_allocation(0x3000, 256, None, Some("var1".to_string())), ];
2514
2515 let enhanced = enhance_type_information(&memory_by_type, &allocations);
2516
2517 assert_eq!(enhanced.len(), 1);
2518 let string_type = &enhanced[0];
2519
2520 assert!(string_type.variable_names.is_empty());
2522 }
2523}