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