1use 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
13use crate::cli::commands::html_from_json::{html::EMBEDDED_STYLES_CSS, js::EMBEDDED_SCRIPT_JS};
15
16pub 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 let comprehensive_data =
33 crate::variable_registry::VariableRegistry::generate_comprehensive_export(tracker)?;
34
35 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 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 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 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 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
85fn generate_memory_analysis_svg_data(tracker: &MemoryTracker) -> TrackingResult<String> {
87 use crate::export::visualization::export_memory_analysis;
88
89 export_memory_analysis(tracker, "moderate_unsafe_ffi_memory_analysis.svg")?;
91
92 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 std::fs::remove_file(temp_path).ok();
102
103 let encoded = base64_encode(svg_content.as_bytes());
105 Ok(format!("data:image/svg+xml;base64,{encoded}"))
106}
107
108fn 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
138fn 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
155fn 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
172fn 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 let timestamp = SystemTime::now()
185 .duration_since(UNIX_EPOCH)
186 .unwrap_or_default()
187 .as_secs();
188
189 let processed_allocations = if allocations.len() > 1000 {
191 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 let type_distribution = precompute_type_distribution(&processed_allocations);
201 let performance_metrics = precompute_performance_metrics(stats, &processed_allocations);
202
203 let formatted_allocations: Vec<serde_json::Value> = processed_allocations
205 .iter()
206 .map(|alloc| {
207 json!({
208 "ptr": format!("0x{:x}", alloc.ptr), "size": alloc.size,
210 "timestamp_alloc": alloc.timestamp_alloc, "timestamp_dealloc": alloc.timestamp_dealloc, "var_name": alloc.var_name, "type_name": alloc.type_name, "scope_name": alloc.scope_name, "stack_trace": alloc.stack_trace, "borrow_count": alloc.borrow_count, "is_leaked": alloc.is_leaked, "lifetime_ms": alloc.lifetime_ms, "smart_pointer_info": alloc.smart_pointer_info, "memory_layout": alloc.memory_layout, "generic_info": alloc.generic_info, "dynamic_type_info": alloc.dynamic_type_info, "runtime_state": alloc.runtime_state, "stack_allocation": alloc.stack_allocation, "temporary_object": alloc.temporary_object, "fragmentation_analysis": alloc.fragmentation_analysis, "generic_instantiation": alloc.generic_instantiation, "type_relationships": alloc.type_relationships, "type_usage": alloc.type_usage, "function_call_tracking": alloc.function_call_tracking, "lifecycle_tracking": alloc.lifecycle_tracking, "access_tracking": alloc.access_tracking })
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 "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 "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 "lifecycle": {
288 "lifecycle_events": [],
289 "scope_analysis": {},
290 "variable_lifetimes": {}
291 },
292 "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
304fn 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
462fn 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
480fn 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
500fn 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 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); serde_json::json!(sorted_types)
530}
531
532fn 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}