memscope_rs/
export_enhanced.rs

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