memscope_rs/
visualization.rs

1//! Unified visualization module for memscope-rs
2//! Provides memory analysis and lifecycle timeline SVG exports
3
4use crate::tracker::MemoryTracker;
5use crate::types::{AllocationInfo, MemoryStats, TrackingError, TrackingResult};
6use crate::utils::{format_bytes, get_simple_type, get_type_color, get_type_gradient_colors};
7use std::collections::HashMap;
8use std::fs::File;
9use std::path::Path;
10use svg::node::element::{Circle, Group, Line, Rectangle, Style, Text as SvgText};
11use svg::Document;
12
13/// Export memory analysis visualization showing variable names, types, and usage
14pub fn export_memory_analysis<P: AsRef<Path>>(
15    tracker: &MemoryTracker,
16    path: P,
17) -> TrackingResult<()> {
18    let path = path.as_ref();
19    tracing::info!("Exporting memory analysis to: {}", path.display());
20
21    if let Some(parent) = path.parent() {
22        if !parent.exists() {
23            std::fs::create_dir_all(parent)?;
24        }
25    }
26
27    let active_allocations = tracker.get_active_allocations()?;
28    let stats = tracker.get_stats()?;
29
30    let document = create_memory_analysis_svg(&active_allocations, &stats, tracker)?;
31
32    let mut file = File::create(path)?;
33    svg::write(&mut file, &document)
34        .map_err(|e| TrackingError::SerializationError(format!("Failed to write SVG: {e}")))?;
35
36    tracing::info!("Successfully exported memory analysis SVG");
37    Ok(())
38}
39
40/// Export interactive lifecycle timeline showing variable lifecycles and relationships
41pub fn export_lifecycle_timeline<P: AsRef<Path>>(
42    tracker: &MemoryTracker,
43    path: P,
44) -> TrackingResult<()> {
45    let path = path.as_ref();
46    tracing::info!("Exporting lifecycle timeline to: {}", path.display());
47
48    if let Some(parent) = path.parent() {
49        if !parent.exists() {
50            std::fs::create_dir_all(parent)?;
51        }
52    }
53
54    let active_allocations = tracker.get_active_allocations()?;
55    let stats = tracker.get_stats()?;
56
57    let document = create_lifecycle_timeline_svg(&active_allocations, &stats)?;
58
59    let mut file = File::create(path)?;
60    svg::write(&mut file, &document)
61        .map_err(|e| TrackingError::SerializationError(format!("Failed to write SVG: {e}")))?;
62
63    tracing::info!("Successfully exported lifecycle timeline SVG");
64    Ok(())
65}
66
67/// Create comprehensive memory analysis SVG with original 12-section layout
68fn create_memory_analysis_svg(
69    allocations: &[AllocationInfo],
70    stats: &MemoryStats,
71    tracker: &MemoryTracker,
72) -> TrackingResult<Document> {
73    // Create comprehensive memory analysis using the original enhanced export logic
74    let width = 1800;
75    let height = 2400; // Tall document for comprehensive analysis
76
77    let mut document = Document::new()
78        .set("viewBox", (0, 0, width, height))
79        .set("width", width)
80        .set("height", height)
81        .set("style", "background: linear-gradient(135deg, #ecf0f1 0%, #bdc3c7 100%); font-family: 'Segoe UI', Arial, sans-serif;");
82
83    // 1. Title: Rust Memory Usage Analysis (handled by add_enhanced_header)
84    document = crate::export_enhanced::add_enhanced_header(document, stats, allocations)?;
85
86    // 3. Performance Dashboard
87    document =
88        crate::export_enhanced::add_enhanced_timeline_dashboard(document, stats, allocations)?;
89
90    // 4. Memory Allocation Heatmap
91    document = crate::export_enhanced::add_memory_heatmap(document, allocations)?;
92
93    // 5. Left side: Memory Usage by Type
94    // 修复:获取实际的内存类型数据而不是空数组
95    let memory_by_type_data = tracker.get_memory_by_type().unwrap_or_default();
96    let memory_by_type =
97        crate::export_enhanced::enhance_type_information(&memory_by_type_data, allocations);
98    document = crate::export_enhanced::add_enhanced_type_chart(document, &memory_by_type)?;
99
100    // 6. Right side: Memory Fragmentation Analysis
101    document = crate::export_enhanced::add_fragmentation_analysis(document, allocations)?;
102
103    // 7. Left side: Tracked Variables by Category
104    // FIXED: Use same enhanced data source as Memory Usage by Type for consistency
105    let categorized = crate::export_enhanced::categorize_enhanced_allocations(&memory_by_type);
106    document = crate::export_enhanced::add_categorized_allocations(document, &categorized)?;
107
108    // 8. Right side: Call Stack Analysis
109    document = crate::export_enhanced::add_callstack_analysis(document, allocations)?;
110
111    // 9. Memory Growth Trends
112    document = crate::export_enhanced::add_memory_growth_trends(document, allocations, stats)?;
113
114    // 10. Variable Allocation Timeline
115    document = crate::export_enhanced::add_memory_timeline(document, allocations, stats)?;
116
117    // 11. Bottom left: Interactive Legend & Guide
118    document = crate::export_enhanced::add_interactive_legend(document)?;
119
120    // 12. Bottom right: Memory Analysis Summary
121    document = crate::export_enhanced::add_comprehensive_summary(document, stats, allocations)?;
122
123    Ok(document)
124}
125
126/// Create lifecycle timeline SVG with interactive features
127fn create_lifecycle_timeline_svg(
128    allocations: &[AllocationInfo],
129    stats: &MemoryStats,
130) -> TrackingResult<Document> {
131    let width = 1600;
132    let height = 1200;
133
134    let mut document = Document::new()
135        .set("viewBox", (0, 0, width, height))
136        .set("width", width)
137        .set("height", height)
138        .set("style", "background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); font-family: 'Inter', 'Segoe UI', sans-serif;");
139
140    // Add interactive styles
141    let styles = Style::new(
142        r#"
143        .timeline-bar { transition: all 0.3s ease; cursor: pointer; }
144        .timeline-bar:hover { stroke: #FFFFFF; stroke-width: 3; filter: drop-shadow(0 0 12px rgba(255,255,255,0.8)); }
145        .variable-label { fill: #FFFFFF; font-size: 13px; font-weight: 600; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
146        .memory-label { fill: #E2E8F0; font-size: 11px; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
147        .section-title { fill: #FFFFFF; font-size: 20px; font-weight: 700; text-shadow: 2px 2px 4px rgba(0,0,0,0.5); }
148        .section-bg { fill: rgba(255,255,255,0.1); stroke: rgba(255,255,255,0.2); stroke-width: 1; rx: 12; }
149    "#,
150    );
151    document = document.add(styles);
152
153    // Title - Scope Matrix & Lifecycle Visualization as specified in task.md
154    let title = SvgText::new("Scope Matrix & Lifecycle Visualization")
155        .set("x", width / 2)
156        .set("y", 40)
157        .set("text-anchor", "middle")
158        .set("font-size", 32)
159        .set("font-weight", "bold")
160        .set("fill", "#FFFFFF")
161        .set("style", "text-shadow: 3px 3px 6px rgba(0,0,0,0.5);");
162    document = document.add(title);
163
164    // PROMINENT GLOBAL LEGEND for Progress Bar explanation
165    document = add_prominent_progress_bar_legend(document, width);
166
167    let tracked_vars: Vec<_> = allocations
168        .iter()
169        .filter(|a| a.var_name.is_some())
170        .collect();
171
172    // tracing::info!(
173    //     "Found {} total allocations, {} with variable names",
174    //     allocations.len(),
175    //     tracked_vars.len()
176    // );
177
178    // Debug: Print the tracked variables we found
179    // for (i, var) in tracked_vars.iter().enumerate() {
180    //     tracing::info!(
181    //         "Tracked var {}: {} ({})",
182    //         i + 1,
183    //         var.var_name.as_ref().unwrap_or(&"None".to_string()),
184    //         var.type_name.as_ref().unwrap_or(&"Unknown".to_string())
185    //     );
186    // }
187
188    if tracked_vars.is_empty() {
189        let no_data = SvgText::new(format!(
190            "No tracked variables found (checked {} allocations)",
191            allocations.len()
192        ))
193        .set("x", width / 2)
194        .set("y", height / 2)
195        .set("text-anchor", "middle")
196        .set("font-size", 18)
197        .set("fill", "#FFFFFF");
198        document = document.add(no_data);
199        return Ok(document);
200    }
201
202    // Add matrix layout instead of timeline - ADJUSTED Y position for global legend
203    document = add_matrix_layout_section(document, &tracked_vars, 50, 130)?;
204
205    // Add memory analysis section - aligned and consistent width
206    document = add_memory_section(document, &tracked_vars, stats, 550, width - 100)?;
207
208    // Add variable relationships section - aligned and consistent width
209    document = add_relationships_section(document, &tracked_vars, 900, width - 100)?;
210
211    Ok(document)
212}
213
214/// Add memory section for lifecycle visualization
215fn add_memory_section(
216    mut document: Document,
217    tracked_vars: &[&AllocationInfo],
218    _stats: &MemoryStats,
219    start_y: i32,
220    section_height: i32,
221) -> TrackingResult<Document> {
222    // Section background
223    let section_bg = Rectangle::new()
224        .set("x", 50)
225        .set("y", start_y - 20)
226        .set("width", 1500)
227        .set("height", section_height)
228        .set("class", "section-bg");
229    document = document.add(section_bg);
230
231    // Section title - TOP 3 MEMORY ANALYSIS as specified in task.md
232    let section_title = SvgText::new("Top 3 Memory Analysis")
233        .set("x", 70)
234        .set("y", start_y + 10)
235        .set("class", "section-title");
236    document = document.add(section_title);
237
238    // Group by type and get TOP 3 ONLY
239    let mut type_stats: HashMap<String, (usize, usize, Vec<String>)> = HashMap::new();
240    for allocation in tracked_vars {
241        if let Some(var_name) = &allocation.var_name {
242            let type_name = allocation.type_name.as_deref().unwrap_or("Unknown");
243            let simple_type = get_simple_type(type_name);
244
245            let entry = type_stats.entry(simple_type).or_insert((0, 0, Vec::new()));
246            entry.0 += 1;
247            entry.1 += allocation.size;
248            entry
249                .2
250                .push(format!("{}({})", var_name, format_bytes(allocation.size)));
251        }
252    }
253
254    // Sort by total size and take TOP 3 ONLY
255    let mut sorted_types: Vec<_> = type_stats.into_iter().collect();
256    sorted_types.sort_by(|a, b| b.1 .1.cmp(&a.1 .1)); // Sort by total size descending
257    sorted_types.truncate(3); // STRICTLY TOP 3
258
259    // Draw memory bars - TOP 3 ONLY
260    let chart_x = 100;
261    let chart_y = start_y + 50;
262    let max_size = sorted_types
263        .iter()
264        .map(|(_, (_, size, _))| *size)
265        .max()
266        .unwrap_or(1);
267
268    for (i, (type_name, (count, total_size, vars))) in sorted_types.iter().enumerate() {
269        let y = chart_y + (i as i32) * 40;
270        let bar_width = ((*total_size as f64 / max_size as f64) * 400.0) as i32;
271
272        let color = get_type_color(type_name);
273        let memory_bar = Rectangle::new()
274            .set("x", chart_x)
275            .set("y", y)
276            .set("width", bar_width)
277            .set("height", 25)
278            .set("fill", color)
279            .set("rx", 4);
280        document = document.add(memory_bar);
281
282        // Type label inside the bar
283        let type_label = SvgText::new(format!("{type_name} ({count} vars)"))
284            .set("x", chart_x + 10)
285            .set("y", y + 17)
286            .set("font-size", 12)
287            .set("font-weight", "bold")
288            .set("fill", "#FFFFFF")
289            .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
290        document = document.add(type_label);
291
292        // Enhanced variable names display - more prominent
293        let vars_text = vars.join(" | ");
294        let vars_label = SvgText::new(if vars_text.len() > 80 {
295            format!("{}...", &vars_text[..77])
296        } else {
297            vars_text
298        })
299        .set("x", chart_x + bar_width + 15)
300        .set("y", y + 17)
301        .set("font-size", 11)
302        .set("font-weight", "600")
303        .set("fill", "#FFFFFF")
304        .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.6)");
305        document = document.add(vars_label);
306    }
307
308    // Placeholder for lifecycle timeline
309    Ok(document)
310}
311
312/// Add relationships section for lifecycle visualization - ENHANCED VARIABLE RELATIONSHIPS
313fn add_relationships_section(
314    mut document: Document,
315    tracked_vars: &[&AllocationInfo],
316    start_y: i32,
317    section_height: i32,
318) -> TrackingResult<Document> {
319    // Section background
320    let section_bg = Rectangle::new()
321        .set("x", 50)
322        .set("y", start_y - 20)
323        .set("width", 1500)
324        .set("height", section_height)
325        .set("class", "section-bg");
326    document = document.add(section_bg);
327
328    // Section title
329    let section_title = SvgText::new("Variable Relationships - Ownership & Borrowing")
330        .set("x", 70)
331        .set("y", start_y + 10)
332        .set("class", "section-title");
333    document = document.add(section_title);
334
335    // Group variables by scope for better organization
336    let mut scope_groups: HashMap<String, Vec<&AllocationInfo>> = HashMap::new();
337    for var in tracked_vars {
338        let scope = identify_precise_scope(var);
339        scope_groups.entry(scope).or_default().push(*var);
340    }
341
342    // Draw scope group backgrounds first
343    let mut scope_positions = HashMap::new();
344    let start_x = 100;
345    let group_spacing_x = 400;
346    let group_spacing_y = 200;
347
348    for (i, (scope_name, _vars)) in scope_groups.iter().enumerate() {
349        let group_x = start_x + (i % 3) as i32 * group_spacing_x;
350        let group_y = start_y + 50 + (i / 3) as i32 * group_spacing_y;
351
352        // Scope group background with subtle color
353        let group_bg = Rectangle::new()
354            .set("x", group_x - 20)
355            .set("y", group_y - 20)
356            .set("width", 300)
357            .set("height", 150)
358            .set("fill", get_scope_background_color(scope_name))
359            .set("stroke", get_scope_border_color(scope_name))
360            .set("stroke-width", 2)
361            .set(
362                "stroke-dasharray",
363                if scope_name == "Global" {
364                    "none"
365                } else {
366                    "5,3"
367                },
368            )
369            .set("rx", 8)
370            .set("opacity", "0.3");
371        document = document.add(group_bg);
372
373        // Scope label
374        let scope_label = SvgText::new(format!("Scope: {}", scope_name))
375            .set("x", group_x - 10)
376            .set("y", group_y - 5)
377            .set("font-size", 12)
378            .set("font-weight", "bold")
379            .set("fill", "#FFFFFF");
380        document = document.add(scope_label);
381
382        scope_positions.insert(scope_name.clone(), (group_x, group_y));
383    }
384
385    // Note: Relationship analysis removed to eliminate unused code
386
387    // Draw variable nodes AFTER lines (on top)
388    for (scope_name, vars) in &scope_groups {
389        if let Some((group_x, group_y)) = scope_positions.get(scope_name) {
390            for (i, allocation) in vars.iter().take(4).enumerate() {
391                // Limit to 4 per scope
392                let node_x = group_x + (i % 2) as i32 * 120 + 40;
393                let node_y = group_y + (i / 2) as i32 * 60 + 30;
394
395                document = draw_variable_node(document, allocation, node_x, node_y)?;
396            }
397        }
398    }
399
400    // Add relationship legend
401    document = add_relationship_legend(document, start_y + section_height - 100)?;
402
403    Ok(document)
404}
405
406/// Draw variable node with enhanced styling
407fn draw_variable_node(
408    mut document: Document,
409    allocation: &AllocationInfo,
410    x: i32,
411    y: i32,
412) -> TrackingResult<Document> {
413    let var_name = allocation.var_name.as_ref().unwrap();
414    let type_name = allocation.type_name.as_deref().unwrap_or("Unknown");
415    let simple_type = get_simple_type(type_name);
416    let color = get_type_color(&simple_type);
417
418    // Variable node with tooltip support
419    let node = Circle::new()
420        .set("cx", x)
421        .set("cy", y)
422        .set("r", 20)
423        .set("fill", color)
424        .set("stroke", "#FFFFFF")
425        .set("stroke-width", 2)
426        .set(
427            "title",
428            format!(
429                "{}: {} ({})",
430                var_name,
431                simple_type,
432                format_bytes(allocation.size)
433            ),
434        );
435    document = document.add(node);
436
437    // Variable name (truncated if too long)
438    let display_name = if var_name.len() > 8 {
439        format!("{}...", &var_name[..6])
440    } else {
441        var_name.clone()
442    };
443
444    let name_label = SvgText::new(display_name)
445        .set("x", x)
446        .set("y", y + 3)
447        .set("text-anchor", "middle")
448        .set("font-size", 9)
449        .set("font-weight", "bold")
450        .set("fill", "#FFFFFF");
451    document = document.add(name_label);
452
453    // Type and size below
454    let info_text = format!("{} | {}", simple_type, format_bytes(allocation.size));
455    let info_label = SvgText::new(info_text)
456        .set("x", x)
457        .set("y", y + 35)
458        .set("text-anchor", "middle")
459        .set("font-size", 7)
460        .set("fill", "#E2E8F0");
461    document = document.add(info_label);
462
463    Ok(document)
464}
465
466/// Add relationship legend
467fn add_relationship_legend(mut document: Document, start_y: i32) -> TrackingResult<Document> {
468    let legend_items = [
469        ("Ownership Transfer", "#E74C3C", "solid", "4"),
470        ("Mutable Borrow", "#3498DB", "solid", "3"),
471        ("Immutable Borrow", "#27AE60", "solid", "2"),
472        ("Clone", "#95A5A6", "solid", "2"),
473        ("Shared Pointer", "#9B59B6", "8,4", "3"),
474        ("Indirect Reference", "#F39C12", "4,2", "1"),
475    ];
476
477    for (i, (label, color, dash, width)) in legend_items.iter().enumerate() {
478        let x = 100 + (i % 3) as i32 * 200;
479        let y = start_y + (i / 3) as i32 * 25;
480
481        // Legend line
482        let legend_line = Line::new()
483            .set("x1", x)
484            .set("y1", y)
485            .set("x2", x + 30)
486            .set("y2", y)
487            .set("stroke", *color)
488            .set("stroke-width", *width)
489            .set("stroke-dasharray", *dash);
490        document = document.add(legend_line);
491
492        // Legend label
493        let legend_label = SvgText::new(*label)
494            .set("x", x + 35)
495            .set("y", y + 4)
496            .set("font-size", 10)
497            .set("fill", "#FFFFFF");
498        document = document.add(legend_label);
499    }
500
501    Ok(document)
502}
503
504/// Get scope background color
505fn get_scope_background_color(scope_name: &str) -> &'static str {
506    match scope_name {
507        "Global" => "rgba(52, 73, 94, 0.2)",
508        _ => "rgba(52, 152, 219, 0.2)",
509    }
510}
511
512/// Get scope border color
513fn get_scope_border_color(scope_name: &str) -> &'static str {
514    match scope_name {
515        "Global" => "#34495E",
516        _ => "#3498DB",
517    }
518}
519
520/// Calculate matrix size based on 5-variable standard with dynamic shrinking
521fn calculate_dynamic_matrix_size(var_count: usize) -> (i32, i32) {
522    let standard_width = 350; // Standard size for 5 variables
523    let standard_height = 280; // Standard size for 5 variables
524    let card_height = 40; // Height per variable card
525    let header_height = 80; // Header and footer space
526    let standard_vars = 5;
527
528    if var_count <= standard_vars {
529        // SHRINK: Reduce size for fewer variables
530        let actual_content_height = header_height + (var_count as i32 * card_height) + 40; // Bottom space
531        let width_reduction = ((standard_vars - var_count) * 15) as i32; // Gentle width reduction
532        let actual_width = standard_width - width_reduction;
533
534        (actual_width.max(250), actual_content_height.max(150)) // Minimum size protection
535    } else {
536        // FIXED STANDARD SIZE: Always use standard size, show only 5 + "more" indicator
537        (standard_width, standard_height)
538    }
539}
540
541/// Calculate scope lifetime with FIXED Global scope logic using program runtime
542fn calculate_scope_lifetime(scope_name: &str, vars: &[&AllocationInfo]) -> u64 {
543    if vars.is_empty() {
544        return 0;
545    }
546
547    if scope_name == "Global" {
548        // FIXED: Global scope uses total program runtime, not just variable span
549        estimate_program_runtime()
550    } else {
551        // Local scope: calculate based on variable lifetimes
552        let start = vars.iter().map(|v| v.timestamp_alloc).min().unwrap_or(0);
553        let end = vars.iter().map(|v| v.timestamp_alloc).max().unwrap_or(0);
554        let span = (end - start) as u64;
555        if span == 0 {
556            // If variables allocated at same time, estimate reasonable duration
557            100 // 100ms default for local scopes
558        } else {
559            span
560        }
561    }
562}
563
564/// Estimate total program runtime for Global scope lifetime calculation
565fn estimate_program_runtime() -> u64 {
566    // Method A: Use a reasonable estimate for program execution time
567    // For memory tracking programs, typically run for at least a few seconds
568    2000 // 2 seconds - reasonable for Global variable lifetime
569}
570
571/// Add prominent global legend for Progress Bar explanation
572fn add_prominent_progress_bar_legend(mut document: Document, svg_width: i32) -> Document {
573    // Prominent background for the legend
574    let legend_bg = Rectangle::new()
575        .set("x", 50)
576        .set("y", 60)
577        .set("width", svg_width - 100)
578        .set("height", 35)
579        .set("fill", "rgba(252, 211, 77, 0.15)")
580        .set("stroke", "#FCD34D")
581        .set("stroke-width", 2)
582        .set("rx", 8)
583        .set("ry", 8);
584    document = document.add(legend_bg);
585
586    // Progress bar icon/example
587    let example_bg = Rectangle::new()
588        .set("x", 70)
589        .set("y", 70)
590        .set("width", 60)
591        .set("height", 8)
592        .set("fill", "rgba(255, 255, 255, 0.2)")
593        .set("rx", 4);
594    document = document.add(example_bg);
595
596    let example_fill = Rectangle::new()
597        .set("x", 70)
598        .set("y", 70)
599        .set("width", 40)
600        .set("height", 8)
601        .set("fill", "#4CAF50")
602        .set("rx", 4);
603    document = document.add(example_fill);
604
605    // Prominent explanation text
606    let legend_text =
607        SvgText::new("📊 Progress Bars show: Variable Size / Largest Variable in Same Scope")
608            .set("x", 150)
609            .set("y", 78)
610            .set("font-size", 14)
611            .set("font-weight", "bold")
612            .set("fill", "#FCD34D")
613            .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
614    document = document.add(legend_text);
615
616    // Size example
617    let size_example = SvgText::new("Example: 2.4KB / 5.6KB")
618        .set("x", 150)
619        .set("y", 88)
620        .set("font-size", 10)
621        .set("fill", "#E2E8F0")
622        .set("font-style", "italic");
623    document = document.add(size_example);
624
625    document
626}
627
628/// Prioritize scopes for display based on importance
629fn prioritize_scopes_for_display<'a>(
630    scope_groups: &'a HashMap<String, Vec<&'a AllocationInfo>>,
631) -> Vec<(String, Vec<&'a AllocationInfo>)> {
632    let mut scopes_with_priority: Vec<_> = scope_groups
633        .iter()
634        .map(|(name, vars)| {
635            let priority = calculate_scope_priority(name, vars);
636            let total_memory: usize = vars.iter().map(|v| v.size).sum();
637            (name.clone(), vars.clone(), priority, total_memory)
638        })
639        .collect();
640
641    // Sort by priority (higher first), then by memory usage (larger first)
642    scopes_with_priority.sort_by(|a, b| b.2.cmp(&a.2).then(b.3.cmp(&a.3)));
643
644    scopes_with_priority
645        .into_iter()
646        .map(|(name, vars, _, _)| (name, vars))
647        .collect()
648}
649
650/// Calculate scope priority based on name patterns and characteristics
651fn calculate_scope_priority(scope_name: &str, vars: &[&AllocationInfo]) -> u8 {
652    let name_lower = scope_name.to_lowercase();
653
654    // CRITICAL SCOPES (Priority: 100)
655    if name_lower == "global"
656        || name_lower == "main"
657        || name_lower.contains("error")
658        || name_lower.contains("panic")
659    {
660        return 100;
661    }
662
663    // HIGH PRIORITY (Priority: 80)
664    if name_lower.contains("process")
665        || name_lower.contains("parse")
666        || name_lower.contains("compute")
667        || name_lower.contains("algorithm")
668        || name_lower.contains("core")
669        || name_lower.contains("engine")
670    {
671        return 80;
672    }
673
674    // MEDIUM PRIORITY (Priority: 60)
675    if name_lower.contains("util")
676        || name_lower.contains("helper")
677        || name_lower.contains("format")
678        || name_lower.contains("convert")
679    {
680        return 60;
681    }
682
683    // LOW PRIORITY (Priority: 40)
684    if name_lower.contains("test")
685        || name_lower.contains("debug")
686        || name_lower.contains("macro")
687        || name_lower.contains("generated")
688    {
689        return 40;
690    }
691
692    // DEFAULT PRIORITY based on memory usage and variable count
693    let total_memory: usize = vars.iter().map(|v| v.size).sum();
694    let var_count = vars.len();
695
696    if total_memory > 1024 || var_count > 3 {
697        70 // High memory/variable count
698    } else if total_memory > 256 || var_count > 1 {
699        50 // Medium memory/variable count
700    } else {
701        30 // Low memory/variable count
702    }
703}
704
705/// Export complete scope analysis to JSON file
706fn export_scope_analysis_json(
707    all_scopes: &HashMap<String, Vec<&AllocationInfo>>,
708    displayed_scopes: &[(String, Vec<&AllocationInfo>)],
709) -> TrackingResult<()> {
710    use serde_json::{Map, Value};
711
712    let mut analysis = Map::new();
713
714    // Project analysis summary
715    let mut project_analysis = Map::new();
716    project_analysis.insert(
717        "total_scopes".to_string(),
718        Value::Number((all_scopes.len() as u64).into()),
719    );
720    project_analysis.insert(
721        "displayed_in_svg".to_string(),
722        Value::Number((displayed_scopes.len() as u64).into()),
723    );
724    project_analysis.insert(
725        "exported_to_json".to_string(),
726        Value::Number(((all_scopes.len() - displayed_scopes.len()) as u64).into()),
727    );
728    project_analysis.insert(
729        "layout_strategy".to_string(),
730        Value::String("hierarchical_priority".to_string()),
731    );
732    project_analysis.insert(
733        "generation_timestamp".to_string(),
734        Value::String(
735            std::time::SystemTime::now()
736                .duration_since(std::time::UNIX_EPOCH)
737                .unwrap()
738                .as_secs()
739                .to_string(),
740        ),
741    );
742    analysis.insert(
743        "project_analysis".to_string(),
744        Value::Object(project_analysis),
745    );
746
747    // All scopes data
748    let mut all_scopes_data = Vec::new();
749    for (scope_name, vars) in all_scopes {
750        let total_memory: usize = vars.iter().map(|v| v.size).sum();
751        let is_displayed = displayed_scopes.iter().any(|(name, _)| name == scope_name);
752
753        let mut scope_data = Map::new();
754        scope_data.insert("scope_name".to_string(), Value::String(scope_name.clone()));
755        scope_data.insert(
756            "total_memory".to_string(),
757            Value::Number((total_memory as u64).into()),
758        );
759        scope_data.insert(
760            "variable_count".to_string(),
761            Value::Number((vars.len() as u64).into()),
762        );
763        scope_data.insert(
764            "display_status".to_string(),
765            Value::String(if is_displayed {
766                "shown_in_svg".to_string()
767            } else {
768                "json_only".to_string()
769            }),
770        );
771        scope_data.insert(
772            "priority".to_string(),
773            Value::Number((calculate_scope_priority(scope_name, vars) as u64).into()),
774        );
775
776        // Variables in this scope
777        let mut variables = Vec::new();
778        for var in vars {
779            if let Some(var_name) = &var.var_name {
780                let mut var_data = Map::new();
781                var_data.insert("name".to_string(), Value::String(var_name.clone()));
782                var_data.insert(
783                    "type".to_string(),
784                    Value::String(var.type_name.as_deref().unwrap_or("Unknown").to_string()),
785                );
786                var_data.insert(
787                    "size_bytes".to_string(),
788                    Value::Number((var.size as u64).into()),
789                );
790                var_data.insert(
791                    "timestamp".to_string(),
792                    Value::Number((var.timestamp_alloc as u64).into()),
793                );
794                variables.push(Value::Object(var_data));
795            }
796        }
797        scope_data.insert("variables".to_string(), Value::Array(variables));
798
799        all_scopes_data.push(Value::Object(scope_data));
800    }
801    analysis.insert("all_scopes".to_string(), Value::Array(all_scopes_data));
802
803    // Write to JSON file
804    let json_content = serde_json::to_string_pretty(&Value::Object(analysis)).map_err(|e| {
805        TrackingError::SerializationError(format!("JSON serialization failed: {}", e))
806    })?;
807
808    std::fs::write("scope_analysis.json", json_content).map_err(|e| TrackingError::IoError(e))?;
809
810    tracing::info!("Exported complete scope analysis to scope_analysis.json");
811    Ok(())
812}
813
814/// Get simple type name
815
816/// Get color based on duration ratio (0.0 to 1.0)
817/// 根据相对生命周期长度分配颜色:最长时间=深色,最短时间=白色,全局作用域=特殊深蓝色
818fn get_duration_color(ratio: f64, is_global: bool) -> String {
819    if is_global {
820        // 全局作用域使用特殊的深蓝色
821        return "#0A2540".to_string();
822    }
823
824    // 创建从白色到深蓝色的渐变
825    // ratio = 0.0 (最短时间) -> 接近白色
826    // ratio = 1.0 (最长时间) -> 深蓝色
827
828    if ratio <= 0.01 {
829        // 极短时间或无时间差 -> 浅灰白色
830        "#F8FAFC".to_string()
831    } else {
832        // 计算RGB值,从浅蓝白色渐变到深蓝色
833        let base_r = 248; // 起始红色值 (接近白色)
834        let base_g = 250; // 起始绿色值
835        let base_b = 252; // 起始蓝色值
836
837        let target_r = 30; // 目标红色值 (深蓝色)
838        let target_g = 64; // 目标绿色值
839        let target_b = 175; // 目标蓝色值
840
841        // 使用平滑的渐变函数
842        let smooth_ratio = ratio.powf(0.7); // 使渐变更平滑
843
844        let r = (base_r as f64 + (target_r as f64 - base_r as f64) * smooth_ratio) as u8;
845        let g = (base_g as f64 + (target_g as f64 - base_g as f64) * smooth_ratio) as u8;
846        let b = (base_b as f64 + (target_b as f64 - base_b as f64) * smooth_ratio) as u8;
847
848        format!("#{:02X}{:02X}{:02X}", r, g, b)
849    }
850}
851
852/// Add matrix layout section with INTELLIGENT 15-SCOPE LIMITATION
853fn add_matrix_layout_section(
854    mut document: Document,
855    tracked_vars: &[&AllocationInfo],
856    start_x: i32,
857    start_y: i32,
858) -> TrackingResult<Document> {
859    // Group variables by scope
860    let mut scope_groups: HashMap<String, Vec<&AllocationInfo>> = HashMap::new();
861    for var in tracked_vars {
862        let scope = identify_precise_scope(var);
863        scope_groups.entry(scope).or_default().push(*var);
864    }
865
866    // INTELLIGENT SCOPE PRIORITIZATION - Maximum 15 scopes
867    let prioritized_scopes = prioritize_scopes_for_display(&scope_groups);
868    let selected_scopes: Vec<_> = prioritized_scopes.into_iter().take(15).collect();
869
870    // tracing::info!(
871    //     "Total scopes found: {}, displaying: {}",
872    //     scope_groups.len(),
873    //     selected_scopes.len()
874    // );
875
876    // Calculate maximum duration across all SELECTED scopes for relative color scaling
877    let max_duration = selected_scopes
878        .iter()
879        .map(|(_, vars)| {
880            if !vars.is_empty() {
881                let start = vars.iter().map(|v| v.timestamp_alloc).min().unwrap_or(0);
882                let end = vars.iter().map(|v| v.timestamp_alloc).max().unwrap_or(0);
883                (end - start) as u64
884            } else {
885                0
886            }
887        })
888        .max()
889        .unwrap_or(1); // Avoid division by zero
890
891    // DYNAMIC GRID LAYOUT - 3 columns, up to 5 rows
892    let base_matrix_width = 350;
893    let base_matrix_height = 180;
894    let spacing_x = 450; // Increased spacing to prevent matrix overlap
895    let spacing_y = 250;
896
897    let mut positions = Vec::new();
898
899    // Calculate positions for SELECTED matrices only
900    for (i, (scope_name, _)) in selected_scopes.iter().enumerate() {
901        let col = i % 3;
902        let row = i / 3;
903        let x = start_x + (col as i32 * spacing_x);
904        let y = start_y + (row as i32 * spacing_y);
905        positions.push((scope_name.clone(), x, y));
906    }
907
908    // Draw relationship lines first (only for displayed scopes)
909    for (i, (scope_name, x, y)) in positions.iter().enumerate() {
910        if scope_name != "Global" && i > 0 {
911            // Find Global scope position
912            if let Some((_, global_x, global_y)) =
913                positions.iter().find(|(name, _, _)| name == "Global")
914            {
915                let line = Line::new()
916                    .set("x1", global_x + base_matrix_width / 2)
917                    .set("y1", global_y + base_matrix_height)
918                    .set("x2", x + base_matrix_width / 2)
919                    .set("y2", *y)
920                    .set("stroke", "#7F8C8D")
921                    .set("stroke-width", 2)
922                    .set("stroke-dasharray", "5,3");
923                document = document.add(line);
924            }
925        }
926    }
927
928    // Render SELECTED scope matrices with relative color scaling
929    for ((scope_name, vars), (_, x, y)) in selected_scopes.iter().zip(positions.iter()) {
930        document = render_scope_matrix_fixed(
931            document,
932            scope_name,
933            vars,
934            *x,
935            *y,
936            base_matrix_width,
937            base_matrix_height,
938            max_duration,
939        )?;
940    }
941
942    // Export complete data to JSON if there are overflow scopes
943    if scope_groups.len() > 15 {
944        export_scope_analysis_json(&scope_groups, &selected_scopes)?;
945    }
946
947    Ok(document)
948}
949
950/// Render single scope matrix with DYNAMIC SIZING and ENHANCED MEMORY VISUALIZATION
951fn render_scope_matrix_fixed(
952    mut document: Document,
953    scope_name: &str,
954    vars: &[&AllocationInfo],
955    x: i32,
956    y: i32,
957    width: i32,
958    height: i32,
959    max_duration: u64, // Maximum duration across all scopes for normalization
960) -> TrackingResult<Document> {
961    // DYNAMIC MATRIX SIZING based on variable count
962    let (dynamic_width, dynamic_height) = calculate_dynamic_matrix_size(vars.len());
963    let actual_width = dynamic_width.max(width);
964    let actual_height = dynamic_height.max(height);
965
966    let mut matrix_group = Group::new().set("transform", format!("translate({}, {})", x, y));
967
968    // ENHANCED SCOPE LIFETIME CALCULATION
969    let duration = calculate_scope_lifetime(scope_name, vars);
970    let total_memory = vars.iter().map(|v| v.size).sum::<usize>();
971    let _peak_memory = vars.iter().map(|v| v.size).max().unwrap_or(0);
972    let active_vars = vars.len();
973
974    // Calculate duration ratio (0.0 to 1.0)
975    let duration_ratio = if max_duration > 0 {
976        duration as f64 / max_duration as f64
977    } else {
978        0.0
979    };
980
981    // Get border color based on duration ratio and scope type
982    let is_global = scope_name == "Global";
983    let border_color = get_duration_color(duration_ratio, is_global);
984
985    // ENHANCED MATRIX CONTAINER with dynamic sizing
986    let container = Rectangle::new()
987        .set("width", actual_width)
988        .set("height", actual_height)
989        .set("fill", "rgba(30, 64, 175, 0.1)")
990        .set("stroke", border_color.as_str())
991        .set("stroke-width", 3)
992        .set(
993            "stroke-dasharray",
994            if scope_name != "Global" {
995                "8,4"
996            } else {
997                "none"
998            },
999        )
1000        .set("rx", 12);
1001    matrix_group = matrix_group.add(container);
1002
1003    // ENHANCED SCOPE HEADER with comprehensive memory overview - ENGLISH ONLY
1004    let header_text = format!(
1005        "Scope: {} | Memory: {} | Variables: {} | Lifetime: {}ms",
1006        scope_name,
1007        format_bytes(total_memory),
1008        active_vars,
1009        duration
1010    );
1011    let enhanced_title = SvgText::new(header_text)
1012        .set("x", 15)
1013        .set("y", 25)
1014        .set("font-size", 11)
1015        .set("font-weight", "700")
1016        .set("fill", "#f8fafc");
1017    matrix_group = matrix_group.add(enhanced_title);
1018
1019    // Variables section with ENHANCED MODERN CARD DESIGN
1020    let var_start_y = 45;
1021    let card_height = 45; // Increased height for vertical layout (3 lines)
1022    let var_spacing = 50; // More spacing for taller cards
1023    let _font_size = 10;
1024
1025    for (i, var) in vars.iter().take(4).enumerate() {
1026        // Limit to 4 for better layout
1027        let var_y = var_start_y + (i as i32 * var_spacing);
1028        let var_name = var.var_name.as_ref().unwrap();
1029        let type_name = get_simple_type(var.type_name.as_ref().unwrap_or(&"Unknown".to_string()));
1030        let duration_ms = estimate_variable_duration(var);
1031
1032        // Calculate progress percentage for the progress bar
1033        let max_size_in_scope = vars.iter().map(|v| v.size).max().unwrap_or(1);
1034        let progress_ratio = var.size as f64 / max_size_in_scope as f64;
1035        let _progress_width = (progress_ratio * 180.0) as i32;
1036
1037        // ENHANCED MODERN CARD with dynamic width
1038        let card_width = actual_width - 20;
1039        let card_bg = Rectangle::new()
1040            .set("x", 10)
1041            .set("y", var_y - 5)
1042            .set("width", card_width)
1043            .set("height", card_height)
1044            .set("fill", "rgba(255, 255, 255, 0.08)")
1045            .set("stroke", "rgba(255, 255, 255, 0.15)")
1046            .set("stroke-width", 1)
1047            .set("rx", 8)
1048            .set("ry", 8);
1049        matrix_group = matrix_group.add(card_bg);
1050
1051        // Variable name with enhanced styling
1052        let var_label = SvgText::new(var_name.clone())
1053            .set("x", 18)
1054            .set("y", var_y + 8)
1055            .set("font-size", 12)
1056            .set("font-weight", "bold")
1057            .set("fill", "#FFFFFF")
1058            .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
1059        matrix_group = matrix_group.add(var_label);
1060
1061        // Type label with enhanced color coding
1062        let (type_start_color, _) = get_type_gradient_colors(&type_name);
1063        let type_label = SvgText::new(format!("({})", type_name))
1064            .set("x", 18)
1065            .set("y", var_y + 22)
1066            .set("font-size", 9)
1067            .set("fill", type_start_color)
1068            .set("font-weight", "600");
1069        matrix_group = matrix_group.add(type_label);
1070
1071        // DYNAMIC PROGRESS BAR - Responsive to matrix width
1072        let available_width = card_width - 40; // Leave margins
1073        let progress_bar_width = (available_width as f64 * 0.5) as i32; // 50% of available width
1074        let progress_x = 20; // Fixed left margin
1075
1076        let progress_bg = Rectangle::new()
1077            .set("x", progress_x)
1078            .set("y", var_y + 15) // Moved down to avoid overlap
1079            .set("width", progress_bar_width)
1080            .set("height", 8)
1081            .set("fill", "rgba(255, 255, 255, 0.1)")
1082            .set("stroke", "rgba(255, 255, 255, 0.2)")
1083            .set("stroke-width", 1)
1084            .set("rx", 4)
1085            .set("ry", 4);
1086        matrix_group = matrix_group.add(progress_bg);
1087
1088        // ENHANCED GRADIENT PROGRESS BAR with type-specific colors
1089        let (start_color, _) = get_type_gradient_colors(&type_name);
1090        let progress_fill_width = (progress_ratio * progress_bar_width as f64) as i32;
1091        let progress_fill = Rectangle::new()
1092            .set("x", progress_x)
1093            .set("y", var_y + 15)
1094            .set("width", progress_fill_width)
1095            .set("height", 8)
1096            .set("fill", start_color) // Enhanced with gradient colors
1097            .set("rx", 4)
1098            .set("ry", 4);
1099        matrix_group = matrix_group.add(progress_fill);
1100
1101        // VERTICAL LAYOUT - Size display below progress bar to avoid overlap
1102        let size_display = format!(
1103            "{} / {}",
1104            format_bytes(var.size),
1105            format_bytes(max_size_in_scope)
1106        );
1107        let size_label = SvgText::new(size_display)
1108            .set("x", progress_x + progress_bar_width + 10)
1109            .set("y", var_y + 20)
1110            .set("font-size", 8)
1111            .set("font-weight", "bold")
1112            .set("fill", "#E2E8F0");
1113        matrix_group = matrix_group.add(size_label);
1114
1115        // LIFETIME on separate line to prevent overlap
1116        let time_label = SvgText::new(format!("Active {}ms", duration_ms))
1117            .set("x", 20)
1118            .set("y", var_y + 30)
1119            .set("font-size", 7)
1120            .set("fill", "#FCD34D")
1121            .set("font-weight", "500");
1122        matrix_group = matrix_group.add(time_label);
1123    }
1124
1125    // Show "more" indicator if needed
1126    if vars.len() > 4 {
1127        let more_text = format!("+ {} more variables", vars.len() - 4);
1128        let more_label = SvgText::new(more_text)
1129            .set("x", 20)
1130            .set("y", var_start_y + (4 * var_spacing) + 10)
1131            .set("font-size", 9)
1132            .set("font-weight", "500")
1133            .set("fill", "#94A3B8")
1134            .set("font-style", "italic");
1135        matrix_group = matrix_group.add(more_label);
1136    }
1137
1138    // INTUITIVE EXPLANATION at bottom of matrix - ENGLISH ONLY
1139    let explanation_y = actual_height - 15;
1140    let explanation_text = "Progress Bar: Current Size / Max Size in Scope";
1141    let explanation = SvgText::new(explanation_text)
1142        .set("x", 15)
1143        .set("y", explanation_y)
1144        .set("font-size", 8)
1145        .set("font-weight", "500")
1146        .set("fill", "#FCD34D")
1147        .set("font-style", "italic");
1148    matrix_group = matrix_group.add(explanation);
1149
1150    document = document.add(matrix_group);
1151    Ok(document)
1152}
1153
1154/// Identify precise scope for allocation
1155fn identify_precise_scope(allocation: &AllocationInfo) -> String {
1156    if let Some(var_name) = &allocation.var_name {
1157        if var_name.contains("global") {
1158            return "Global".to_string();
1159        }
1160        // Use timestamp to infer scope
1161        match allocation.timestamp_alloc {
1162            0..=1000 => "Global".to_string(),
1163            1001..=2000 => "demonstrate_builtin_types".to_string(),
1164            2001..=3000 => "demonstrate_smart_pointers".to_string(),
1165            3001..=4000 => "demonstrate_custom_structures".to_string(),
1166            4001..=5000 => "demonstrate_complex_patterns".to_string(),
1167            5001..=6000 => "simulate_web_server_scenario".to_string(),
1168            _ => "simulate_data_processing_pipeline".to_string(),
1169        }
1170    } else {
1171        "Global".to_string()
1172    }
1173}
1174
1175/// Estimate variable duration
1176fn estimate_variable_duration(var: &AllocationInfo) -> u64 {
1177    let base_duration = match var.size {
1178        0..=100 => 10,
1179        101..=1000 => 50,
1180        1001..=10000 => 100,
1181        _ => 200,
1182    };
1183
1184    let type_multiplier = if let Some(type_name) = &var.type_name {
1185        if type_name.contains("Vec") || type_name.contains("HashMap") {
1186            2.0
1187        } else if type_name.contains("Box") || type_name.contains("Rc") {
1188            1.5
1189        } else {
1190            1.0
1191        }
1192    } else {
1193        1.0
1194    };
1195
1196    (base_duration as f64 * type_multiplier) as u64
1197}