memscope_rs/export/
html_export.rs

1//! HTML export functionality for interactive memory visualization
2//! Generates self-contained HTML files with embedded CSS/JS for offline viewing
3
4use crate::analysis::unsafe_ffi_tracker::UnsafeFFITracker;
5use crate::core::tracker::MemoryTracker;
6use crate::core::types::{AllocationInfo, MemoryStats, TrackingError, TrackingResult};
7use serde_json;
8use std::fs::File;
9use std::io::Read;
10use std::io::Write;
11use std::path::Path;
12
13// Embed CSS and JS content at compile time
14use crate::cli::commands::html_from_json::{html::EMBEDDED_STYLES_CSS, js::EMBEDDED_SCRIPT_JS};
15
16/// Export comprehensive interactive HTML report
17pub fn export_interactive_html<P: AsRef<Path>>(
18    tracker: &MemoryTracker,
19    unsafe_ffi_tracker: Option<&UnsafeFFITracker>,
20    path: P,
21) -> TrackingResult<()> {
22    let path = path.as_ref();
23    tracing::info!("Exporting interactive HTML report to: {}", path.display());
24
25    if let Some(parent) = path.parent() {
26        if !parent.exists() {
27            std::fs::create_dir_all(parent)?;
28        }
29    }
30
31    // Use registry to get comprehensive data with variable names
32    let comprehensive_data =
33        crate::variable_registry::VariableRegistry::generate_comprehensive_export(tracker)?;
34
35    // Extract data from comprehensive structure
36    let active_allocations = tracker.get_active_allocations()?;
37    let stats = tracker.get_stats()?;
38    let memory_by_type = tracker.get_memory_by_type().unwrap_or_default();
39
40    // Generate SVGs as base64 embedded data
41    let memory_analysis_svg = generate_memory_analysis_svg_data(tracker)?;
42    let lifecycle_timeline_svg = generate_lifecycle_timeline_svg_data(tracker)?;
43    let unsafe_ffi_svg = if let Some(ffi_tracker) = unsafe_ffi_tracker {
44        generate_unsafe_ffi_svg_data(ffi_tracker)?
45    } else {
46        String::new()
47    };
48
49    // Convert memory_by_type to HashMap format for optimization
50    let memory_by_type_map: std::collections::HashMap<String, (usize, usize)> = memory_by_type
51        .iter()
52        .map(|usage| {
53            (
54                usage.type_name.clone(),
55                (usage.total_size, usage.allocation_count),
56            )
57        })
58        .collect();
59
60    // Prepare optimized JSON data for JavaScript with comprehensive registry data
61    let json_data = prepare_comprehensive_json_data(
62        &comprehensive_data,
63        &active_allocations,
64        &stats,
65        &memory_by_type_map,
66        unsafe_ffi_tracker,
67    )?;
68
69    // Generate complete HTML
70    let html_content = generate_html_template(
71        &memory_analysis_svg,
72        &lifecycle_timeline_svg,
73        &unsafe_ffi_svg,
74        &json_data,
75    )?;
76
77    let mut file = File::create(path)?;
78    file.write_all(html_content.as_bytes())
79        .map_err(|e| TrackingError::SerializationError(format!("Failed to write HTML: {e}")))?;
80
81    tracing::info!("Successfully exported interactive HTML report");
82    Ok(())
83}
84
85/// Generate memory analysis SVG as base64 data URL
86fn generate_memory_analysis_svg_data(tracker: &MemoryTracker) -> TrackingResult<String> {
87    use crate::export::visualization::export_memory_analysis;
88
89    // FIXED: Also create the main SVG file with correct peak memory values
90    export_memory_analysis(tracker, "moderate_unsafe_ffi_memory_analysis.svg")?;
91
92    // Create temporary file for SVG
93    let temp_path = "tmp_rovodev_memory_analysis.svg";
94    export_memory_analysis(tracker, temp_path)?;
95
96    let mut file = File::open(temp_path)?;
97    let mut svg_content = String::new();
98    file.read_to_string(&mut svg_content)?;
99
100    // Clean up temp file
101    std::fs::remove_file(temp_path).ok();
102
103    // Convert to base64 data URL (simple base64 encoding)
104    let encoded = base64_encode(svg_content.as_bytes());
105    Ok(format!("data:image/svg+xml;base64,{encoded}"))
106}
107
108/// Simple base64 encoding function
109fn base64_encode(input: &[u8]) -> String {
110    const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
111    let mut result = String::new();
112
113    for chunk in input.chunks(3) {
114        let mut buf = [0u8; 3];
115        for (i, &b) in chunk.iter().enumerate() {
116            buf[i] = b;
117        }
118
119        let b = ((buf[0] as u32) << 16) | ((buf[1] as u32) << 8) | (buf[2] as u32);
120
121        result.push(CHARS[((b >> 18) & 63) as usize] as char);
122        result.push(CHARS[((b >> 12) & 63) as usize] as char);
123        result.push(if chunk.len() > 1 {
124            CHARS[((b >> 6) & 63) as usize] as char
125        } else {
126            '='
127        });
128        result.push(if chunk.len() > 2 {
129            CHARS[(b & 63) as usize] as char
130        } else {
131            '='
132        });
133    }
134
135    result
136}
137
138/// Generate lifecycle timeline SVG as base64 data URL
139fn generate_lifecycle_timeline_svg_data(tracker: &MemoryTracker) -> TrackingResult<String> {
140    use crate::export::visualization::export_lifecycle_timeline;
141
142    let temp_path = "tmp_rovodev_lifecycle_timeline.svg";
143    export_lifecycle_timeline(tracker, temp_path)?;
144
145    let mut file = File::open(temp_path)?;
146    let mut svg_content = String::new();
147    file.read_to_string(&mut svg_content)?;
148
149    std::fs::remove_file(temp_path).ok();
150
151    let encoded = base64_encode(svg_content.as_bytes());
152    Ok(format!("data:image/svg+xml;base64,{encoded}"))
153}
154
155/// Generate unsafe FFI SVG as base64 data URL
156fn generate_unsafe_ffi_svg_data(unsafe_ffi_tracker: &UnsafeFFITracker) -> TrackingResult<String> {
157    use crate::export::visualization::export_unsafe_ffi_dashboard;
158
159    let temp_path = "tmp_rovodev_unsafe_ffi.svg";
160    export_unsafe_ffi_dashboard(unsafe_ffi_tracker, temp_path)?;
161
162    let mut file = File::open(temp_path)?;
163    let mut svg_content = String::new();
164    file.read_to_string(&mut svg_content)?;
165
166    std::fs::remove_file(temp_path).ok();
167
168    let encoded = base64_encode(svg_content.as_bytes());
169    Ok(format!("data:image/svg+xml;base64,{encoded}"))
170}
171
172/// Prepare comprehensive JSON data for frontend consumption with registry-based variable names
173fn prepare_comprehensive_json_data(
174    _comprehensive_data: &serde_json::Value,
175    allocations: &[AllocationInfo],
176    stats: &MemoryStats,
177    memory_by_type: &std::collections::HashMap<String, (usize, usize)>,
178    unsafe_ffi_tracker: Option<&UnsafeFFITracker>,
179) -> TrackingResult<String> {
180    use serde_json::json;
181    use std::time::{SystemTime, UNIX_EPOCH};
182
183    // Get current timestamp
184    let timestamp = SystemTime::now()
185        .duration_since(UNIX_EPOCH)
186        .unwrap_or_default()
187        .as_secs();
188
189    // Preprocess data to reduce frontend computation burden
190    let processed_allocations = if allocations.len() > 1000 {
191        // Large dataset: intelligent sampling + representative samples
192        let mut sampled = sample_allocations(allocations, 500);
193        sampled.extend(get_representative_allocations(allocations, 100));
194        sampled
195    } else {
196        allocations.to_vec()
197    };
198
199    // Pre-calculate type distribution and performance metrics
200    let type_distribution = precompute_type_distribution(&processed_allocations);
201    let performance_metrics = precompute_performance_metrics(stats, &processed_allocations);
202
203    // Convert allocation data to format consistent with JSON export
204    let formatted_allocations: Vec<serde_json::Value> = processed_allocations
205        .iter()
206        .map(|alloc| {
207            json!({
208                "ptr": format!("0x{:x}", alloc.ptr),  // format as hex string, consistent with JSON
209                "size": alloc.size,
210                "timestamp_alloc": alloc.timestamp_alloc,  // use correct field name
211                "timestamp_dealloc": alloc.timestamp_dealloc,  // add deallocation timestamp
212                "var_name": alloc.var_name,  // keep original value, including null
213                "type_name": alloc.type_name,  // keep original value, including null
214                "scope_name": alloc.scope_name,  // add scope name
215                "stack_trace": alloc.stack_trace,  // use correct field name
216                "borrow_count": alloc.borrow_count,  // add borrow count
217                "is_leaked": alloc.is_leaked,  // add leak marker
218                "lifetime_ms": alloc.lifetime_ms,  // add lifetime
219                "smart_pointer_info": alloc.smart_pointer_info,  // add smart pointer info
220                "memory_layout": alloc.memory_layout,  // add memory layout
221                "generic_info": alloc.generic_info,  // add generic info
222                "dynamic_type_info": alloc.dynamic_type_info,  // add dynamic type info
223                "runtime_state": alloc.runtime_state,  // add runtime state
224                "stack_allocation": alloc.stack_allocation,  // add stack allocation info
225                "temporary_object": alloc.temporary_object,  // add temporary object info
226                "fragmentation_analysis": alloc.fragmentation_analysis,  // add fragmentation analysis
227                "generic_instantiation": alloc.generic_instantiation,  // add generic instantiation
228                "type_relationships": alloc.type_relationships,  // add type relationships
229                "type_usage": alloc.type_usage,  // add type usage
230                "function_call_tracking": alloc.function_call_tracking,  // add function call tracking
231                "lifecycle_tracking": alloc.lifecycle_tracking,  // add lifecycle tracking
232                "access_tracking": alloc.access_tracking  // add access tracking
233            })
234        })
235        .collect();
236
237    let json_obj = json!({
238        "allocations": formatted_allocations,
239        "stats": stats,
240        "memoryByType": memory_by_type,
241        "unsafeFFI": unsafe_ffi_tracker.map(|t| {
242            json!({
243                "allocations": t.get_enhanced_allocations().unwrap_or_default(),
244                "violations": t.get_safety_violations().unwrap_or_default(),
245                "boundaryEvents": Vec::<serde_json::Value>::new(),
246                "stats": serde_json::json!({})
247            })
248        }),
249        "timestamp": timestamp,
250        "version": env!("CARGO_PKG_VERSION"),
251        // Preprocessed data for direct frontend use, reducing computation time
252        "precomputed": {
253            "type_distribution": type_distribution,
254            "performance_metrics": performance_metrics,
255            "original_data_size": allocations.len(),
256            "processed_data_size": processed_allocations.len(),
257            "is_sampled": allocations.len() > 1000,
258            "optimization_info": {
259                "sampling_ratio": if allocations.len() > 1000 {
260                    format!("{:.1}%", (processed_allocations.len() as f64 / allocations.len() as f64) * 100.0)
261                } else {
262                    "100%".to_string()
263                },
264                "load_time_estimate": if allocations.len() > 1000 { "Fast" } else { "Instant" }
265            }
266        },
267        // New: complex type analysis data (simulated structure, should be obtained from tracker)
268        "complex_types": {
269            "categorized_types": {
270                "collections": [],
271                "generic_types": [],
272                "smart_pointers": [],
273                "trait_objects": []
274            },
275            "complex_type_analysis": [],
276            "summary": {
277                "total_complex_types": 0,
278                "complexity_distribution": {
279                    "low_complexity": 0,
280                    "medium_complexity": 0,
281                    "high_complexity": 0,
282                    "very_high_complexity": 0
283                }
284            }
285        },
286        // New: lifecycle event data (simulated structure, should be obtained from tracker)
287        "lifecycle": {
288            "lifecycle_events": [],
289            "scope_analysis": {},
290            "variable_lifetimes": {}
291        },
292        // New: variable relationship data (simulated structure, should be obtained from tracker)
293        "variable_relationships": {
294            "relationships": [],
295            "dependency_graph": {},
296            "scope_hierarchy": {}
297        }
298    });
299
300    serde_json::to_string(&json_obj)
301        .map_err(|e| TrackingError::SerializationError(format!("JSON serialization failed: {e}")))
302}
303
304/// Generate complete HTML template with embedded CSS and JavaScript
305fn generate_html_template(
306    _memory_analysis_svg: &str,
307    _lifecycle_timeline_svg: &str,
308    unsafe_ffi_svg: &str,
309    json_data: &str,
310) -> TrackingResult<String> {
311    let unsafe_ffi_html = if unsafe_ffi_svg.is_empty() {
312        r#"<div class="empty-state">
313            <h3>⚠️ No Unsafe/FFI Data Available</h3>
314            <p>This analysis did not detect any unsafe Rust code or FFI operations.</p>
315            <p>This is generally a good sign for memory safety! 🎉</p>
316        </div>"#
317            .to_string()
318    } else {
319        format!(
320            r#"<div class="svg-container">
321            <img src="{unsafe_ffi_svg}" alt="Unsafe FFI Dashboard" class="svg-image" />
322        </div>"#
323        )
324    };
325
326    let html = format!(
327        r#"<!DOCTYPE html>
328<html lang="en">
329<head>
330    <meta charset="UTF-8">
331    <meta name="viewport" content="width=device-width, initial-scale=1.0">
332    <title>MemScope-RS Interactive Memory Analysis</title>
333    <style>
334        {EMBEDDED_STYLES_CSS}
335    </style>
336</head>
337<body>
338    <div class="container">
339        <header class="header">
340            <h1>🔍 MemScope-RS Interactive Memory Analysis</h1>
341            <div class="header-stats">
342                <span class="stat-badge" id="totalMemory">Loading...</span>
343                <span class="stat-badge" id="activeAllocs">Loading...</span>
344                <span class="stat-badge" id="peakMemory">Loading...</span>
345            </div>
346        </header>
347
348        <nav class="tab-nav">
349            <button class="tab-btn active" data-tab="overview">📊 Overview</button>
350            <button class="tab-btn" data-tab="memory-analysis">🧠 Memory Analysis</button>
351            <button class="tab-btn" data-tab="lifecycle">⏱️ Lifecycle Timeline</button>
352            <button class="tab-btn" data-tab="complex-types">🔧 Complex Types</button>
353            <button class="tab-btn" data-tab="variable-relationships">🔗 Variable Relations</button>
354            <button class="tab-btn" data-tab="unsafe-ffi">⚠️ Unsafe/FFI</button>
355            <button class="tab-btn" data-tab="interactive">🎮 Interactive Explorer</button>
356        </nav>
357
358        <main class="content">
359            <!-- Overview Tab -->
360            <div class="tab-content active" id="overview">
361                <div class="overview-grid">
362                    <div class="overview-card">
363                        <h3>📈 Memory Statistics</h3>
364                        <div id="memoryStats">Loading...</div>
365                    </div>
366                    <div class="overview-card">
367                        <h3>🏷️ Type Distribution</h3>
368                        <div id="typeDistribution">Loading...</div>
369                    </div>
370                    <div class="overview-card">
371                        <h3>📋 Recent Allocations</h3>
372                        <div id="recentAllocations">Loading...</div>
373                    </div>
374                    <div class="overview-card">
375                        <h3>⚡ Performance Insights</h3>
376                        <div id="performanceInsights">Loading...</div>
377                    </div>
378                </div>
379            </div>
380
381            <!-- Memory Analysis Tab -->
382            <div class="tab-content" id="memory-analysis">
383                <!-- Dynamic visualization will be rendered here by JavaScript -->
384            </div>
385
386            <!-- Lifecycle Timeline Tab -->
387            <div class="tab-content" id="lifecycle">
388                <!-- Dynamic visualization will be rendered here by JavaScript -->
389            </div>
390
391            <!-- Complex Types Tab -->
392            <div class="tab-content" id="complex-types">
393                <!-- Complex types analysis will be rendered here by JavaScript -->
394            </div>
395
396            <!-- Variable Relationships Tab -->
397            <div class="tab-content" id="variable-relationships">
398                <!-- Variable relationships will be rendered here by JavaScript -->
399            </div>
400
401            <!-- Unsafe/FFI Tab -->
402            <div class="tab-content" id="unsafe-ffi">
403                {unsafe_ffi_html}
404            </div>
405
406            <!-- Interactive Explorer Tab -->
407            <div class="tab-content" id="interactive">
408                <div class="explorer-controls">
409                    <div class="control-group">
410                        <label for="filterType">Filter by Type:</label>
411                        <select id="filterType">
412                            <option value="">All Types</option>
413                        </select>
414                    </div>
415                    <div class="control-group">
416                        <label for="sizeRange">Size Range:</label>
417                        <input type="range" id="sizeRange" min="0" max="100" value="100">
418                        <span id="sizeRangeValue">All sizes</span>
419                    </div>
420                    <div class="control-group">
421                        <label for="sortBy">Sort by:</label>
422                        <select id="sortBy">
423                            <option value="size">Size</option>
424                            <option value="timestamp">Timestamp</option>
425                            <option value="type">Type</option>
426                        </select>
427                    </div>
428                </div>
429                <div class="explorer-content">
430                    <div class="allocation-grid" id="allocationGrid">
431                        Loading allocations...
432                    </div>
433                </div>
434            </div>
435        </main>
436    </div>
437
438    <script>
439        // Embedded data as fallback
440        const EMBEDDED_DATA = {json_data};
441        
442        // Global variables
443        let globalDataLoader;
444        let globalVisualizer;
445        
446        // Initialization functions will be defined in JS file
447        
448        {EMBEDDED_SCRIPT_JS}
449        
450        // Initialize after page load
451        document.addEventListener('DOMContentLoaded', function() {{
452            initializeMemScopeApp();
453        }});
454    </script>
455</body>
456</html>"#
457    );
458
459    Ok(html)
460}
461
462/// Intelligent sampling algorithm - maintain data representativeness
463fn sample_allocations(allocations: &[AllocationInfo], max_count: usize) -> Vec<AllocationInfo> {
464    if allocations.len() <= max_count {
465        return allocations.to_vec();
466    }
467
468    let step = allocations.len() / max_count;
469    let mut sampled = Vec::new();
470
471    for i in (0..allocations.len()).step_by(step) {
472        if sampled.len() < max_count {
473            sampled.push(allocations[i].clone());
474        }
475    }
476
477    sampled
478}
479
480/// Get representative allocations (max, min, median, etc.)
481fn get_representative_allocations(
482    allocations: &[AllocationInfo],
483    count: usize,
484) -> Vec<AllocationInfo> {
485    let mut sorted = allocations.to_vec();
486    sorted.sort_by(|a, b| b.size.cmp(&a.size));
487
488    let mut representatives = Vec::new();
489    let step = sorted.len().max(1) / count.min(sorted.len());
490
491    for i in (0..sorted.len()).step_by(step.max(1)) {
492        if representatives.len() < count {
493            representatives.push(sorted[i].clone());
494        }
495    }
496
497    representatives
498}
499
500/// Precompute type distribution
501fn precompute_type_distribution(allocations: &[AllocationInfo]) -> serde_json::Value {
502    use std::collections::HashMap;
503
504    let mut type_map: HashMap<String, (usize, usize)> = HashMap::new();
505
506    for alloc in allocations {
507        let type_name = alloc.type_name.clone().unwrap_or_else(|| {
508            // Intelligent type inference
509            if alloc.size <= 8 {
510                "Small Primitive".to_string()
511            } else if alloc.size <= 32 {
512                "Medium Object".to_string()
513            } else if alloc.size <= 1024 {
514                "Large Structure".to_string()
515            } else {
516                "Buffer/Collection".to_string()
517            }
518        });
519
520        let entry = type_map.entry(type_name).or_insert((0, 0));
521        entry.0 += alloc.size;
522        entry.1 += 1;
523    }
524
525    let mut sorted_types: Vec<_> = type_map.into_iter().collect();
526    sorted_types.sort_by(|a, b| b.1 .0.cmp(&a.1 .0));
527    sorted_types.truncate(10); // keep only top 10 types
528
529    serde_json::json!(sorted_types)
530}
531
532/// Precompute performance metrics
533fn precompute_performance_metrics(
534    stats: &MemoryStats,
535    allocations: &[AllocationInfo],
536) -> serde_json::Value {
537    let current_memory = stats.active_memory;
538    let peak_memory = stats.peak_memory;
539    let utilization = if peak_memory > 0 {
540        (current_memory as f64 / peak_memory as f64 * 100.0) as u32
541    } else {
542        0
543    };
544
545    let total_size: usize = allocations.iter().map(|a| a.size).sum();
546    let avg_size = if !allocations.is_empty() {
547        total_size / allocations.len()
548    } else {
549        0
550    };
551
552    let large_allocs = allocations.iter().filter(|a| a.size > 1024 * 1024).count();
553
554    serde_json::json!({
555        "utilization_percent": utilization,
556        "avg_allocation_size": avg_size,
557        "large_allocations_count": large_allocs,
558        "efficiency_score": if utilization > 80 { "HIGH" } else if utilization > 50 { "MEDIUM" } else { "LOW" },
559        "fragmentation_score": if allocations.len() > 100 { "HIGH" } else { "LOW" }
560    })
561}