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
13const CSS_CONTENT: &str = include_str!("../../templates/styles.css");
15const JS_CONTENT: &str = include_str!("../../templates/script.js");
16
17pub 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 let comprehensive_data =
34 crate::variable_registry::VariableRegistry::generate_comprehensive_export(tracker)?;
35
36 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 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 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 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 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
86fn generate_memory_analysis_svg_data(tracker: &MemoryTracker) -> TrackingResult<String> {
88 use crate::export::visualization::export_memory_analysis;
89
90 export_memory_analysis(tracker, "moderate_unsafe_ffi_memory_analysis.svg")?;
92
93 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 std::fs::remove_file(temp_path).ok();
103
104 let encoded = base64_encode(svg_content.as_bytes());
106 Ok(format!("data:image/svg+xml;base64,{encoded}"))
107}
108
109fn 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
139fn 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
156fn 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
173fn 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 let timestamp = SystemTime::now()
186 .duration_since(UNIX_EPOCH)
187 .unwrap_or_default()
188 .as_secs();
189
190 let processed_allocations = if allocations.len() > 1000 {
192 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 let type_distribution = precompute_type_distribution(&processed_allocations);
202 let performance_metrics = precompute_performance_metrics(stats, &processed_allocations);
203
204 let formatted_allocations: Vec<serde_json::Value> = processed_allocations
206 .iter()
207 .map(|alloc| {
208 json!({
209 "ptr": format!("0x{:x}", alloc.ptr), "size": alloc.size,
211 "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 })
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 "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 "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 "lifecycle": {
289 "lifecycle_events": [],
290 "scope_analysis": {},
291 "variable_lifetimes": {}
292 },
293 "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
305fn 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
463fn 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
481fn 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
501fn 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 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); serde_json::json!(sorted_types)
531}
532
533fn 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}