1use crate::core::types::{AllocationInfo, MemoryStats};
5use crate::export::binary::error::BinaryExportError;
6use crate::export::binary::reader::BinaryReader;
7use chrono;
8use serde_json::json;
9use std::fs;
10use std::path::Path;
11
12pub fn convert_binary_to_html<P: AsRef<Path>>(
14 binary_path: P,
15 html_path: P,
16 project_name: &str,
17) -> Result<(), BinaryExportError> {
18 let mut reader = BinaryReader::new(&binary_path)?;
20 let allocations = reader.read_all()?;
21
22 let stats = generate_statistics(&allocations);
24
25 tracing::debug!("Loading binary dashboard template...");
27 let template = load_binary_dashboard_template()?;
28 tracing::debug!("Template loaded, length: {} chars", template.len());
29
30 let html_content = generate_html_content(&template, &allocations, &stats, project_name)?;
32
33 fs::write(&html_path, html_content).map_err(BinaryExportError::Io)?;
35
36 Ok(())
37}
38
39fn generate_statistics(allocations: &[AllocationInfo]) -> MemoryStats {
41 let mut stats = MemoryStats::new();
42
43 let mut total_memory = 0;
44 let mut active_memory = 0;
45 let mut active_count = 0;
46 let mut leaked_count = 0;
47 let mut leaked_memory = 0;
48
49 for allocation in allocations {
50 stats.total_allocations += 1;
51 total_memory += allocation.size;
52
53 if allocation.timestamp_dealloc.is_none() {
54 active_count += 1;
55 active_memory += allocation.size;
56 }
57
58 if allocation.is_leaked {
59 leaked_count += 1;
60 leaked_memory += allocation.size;
61 }
62 }
63
64 stats.total_allocated = total_memory;
65 stats.active_allocations = active_count;
66 stats.active_memory = active_memory;
67 stats.peak_memory = active_memory; stats.leaked_allocations = leaked_count;
69 stats.leaked_memory = leaked_memory;
70 stats.allocations = allocations.to_vec();
71
72 stats
73}
74
75fn load_binary_dashboard_template() -> Result<String, BinaryExportError> {
77 let template_paths = [
79 "templates/binary_dashboard.html",
80 "../templates/binary_dashboard.html",
81 "../../templates/binary_dashboard.html",
82 ];
83
84 for path in &template_paths {
85 tracing::debug!("Trying to load template from: {path}");
86 if let Ok(content) = fs::read_to_string(path) {
87 tracing::debug!(
88 "Successfully loaded template from: {path} ({len} chars)",
89 len = content.len()
90 );
91 return Ok(content);
92 } else {
93 tracing::debug!("Failed to load from: {path}");
94 }
95 }
96
97 tracing::debug!("Using fallback embedded template");
98 Ok(get_embedded_binary_template())
100}
101
102fn generate_html_content(
104 template: &str,
105 allocations: &[AllocationInfo],
106 stats: &MemoryStats,
107 project_name: &str,
108) -> Result<String, BinaryExportError> {
109 let allocation_data = prepare_allocation_data(allocations)?;
111 let _stats_data = prepare_stats_data(stats)?;
112 let safety_risk_data = prepare_safety_risk_data(allocations)?;
113
114 tracing::debug!(
116 "Replacing BINARY_DATA placeholder with {} bytes of allocation data",
117 allocation_data.len()
118 );
119 let mut html = template.to_string();
120
121 if html.contains("{{PROJECT_NAME}}") {
123 html = html.replace("{{PROJECT_NAME}}", project_name);
124 } else {
125 if let Some(start) = html.find("<title>") {
128 if let Some(end) = html[start..].find("</title>") {
129 let title_end = start + end;
130 let before = &html[..start + 7]; let after = &html[title_end..];
132 html = format!("{before}{project_name} - Memory Analysis Dashboard{after}",);
133 }
134 }
135
136 html = html.replace(
138 "MemScope Memory Analysis Dashboard",
139 &format!("{project_name} - Memory Analysis Report"),
140 );
141
142 html = html.replace("class=\"grid grid-4\"", "class=\"grid grid-4 stats-grid\"");
144 html = html.replace("<table>", "<table class=\"allocations-table\">");
145 }
146
147 html = html.replace(
148 "{{TIMESTAMP}}",
149 &chrono::Utc::now()
150 .format("%Y-%m-%d %H:%M:%S UTC")
151 .to_string(),
152 );
153 html = html.replace(
154 "{{GENERATION_TIME}}",
155 &chrono::Utc::now()
156 .format("%Y-%m-%d %H:%M:%S UTC")
157 .to_string(),
158 );
159
160 if html.contains("{{BINARY_DATA}}") {
162 html = html.replace("{{BINARY_DATA}}", &allocation_data);
163 tracing::debug!("Successfully replaced {{BINARY_DATA}} placeholder with binary data");
164 } else {
165 if let Some(start) = html.find("window.analysisData = {") {
167 if let Some(end) = html[start..].find("};") {
168 let end_pos = start + end + 2; let before = &html[..start];
170 let after = &html[end_pos..];
171 html = format!(
172 "{}window.analysisData = {};{}",
173 before, &allocation_data, after
174 );
175 tracing::debug!(
176 "Fallback: replaced hardcoded window.analysisData with binary data"
177 );
178 }
179 } else {
180 html = html.replace("{{ALLOCATION_DATA}}", &allocation_data);
182 html = html.replace("{{ json_data }}", &allocation_data);
183 html = html.replace("{{json_data}}", &allocation_data);
184 tracing::debug!("Used fallback placeholder replacements");
185 }
186 }
187
188 html = html.replace(
190 "{{TOTAL_ALLOCATIONS}}",
191 &stats.total_allocations.to_string(),
192 );
193 html = html.replace(
194 "{{ACTIVE_ALLOCATIONS}}",
195 &stats.active_allocations.to_string(),
196 );
197 html = html.replace(
198 "{{ACTIVE_MEMORY}}",
199 &format_memory_size(stats.active_memory),
200 );
201 html = html.replace("{{PEAK_MEMORY}}", &format_memory_size(stats.peak_memory));
202 html = html.replace(
203 "{{LEAKED_ALLOCATIONS}}",
204 &stats.leaked_allocations.to_string(),
205 );
206 html = html.replace(
207 "{{LEAKED_MEMORY}}",
208 &format_memory_size(stats.leaked_memory),
209 );
210
211 html = html.replace("{{SVG_IMAGES}}", "<!-- SVG images placeholder -->");
213 html = html.replace("{{CSS_CONTENT}}", "/* Additional CSS placeholder */");
214 html = html.replace("{{JS_CONTENT}}", &generate_dashboard_javascript());
215
216 html = html.replace("{{ json_data }}", &allocation_data);
218 html = html.replace("{{json_data}}", &allocation_data);
219
220 html = html.replace(
222 "AFTER JS_CONTENT loads",
223 "after additional JavaScript loads",
224 );
225 html = html.replace("JS_CONTENT loads", "additional JavaScript loads");
226 html = html.replace("JS_CONTENT", "additionalJavaScript");
227
228 if let Some(dom_ready_start) =
231 html.find("document.addEventListener('DOMContentLoaded', function() {")
232 {
233 let injection_point = dom_ready_start;
234 let before = &html[..injection_point];
235 let after = &html[injection_point..];
236
237 let safety_injection = format!(
238 r#"
239 // Safety Risk Data Injection
240 window.safetyRisks = {safety_risk_data};
241
242 function loadSafetyRisks() {{
243 console.log('🛡️ Loading safety risk data...');
244 const unsafeTable = document.getElementById('unsafeTable');
245 if (!unsafeTable) {{
246 console.warn('⚠️ unsafeTable not found');
247 return;
248 }}
249
250 const risks = window.safetyRisks || [];
251 if (risks.length === 0) {{
252 unsafeTable.innerHTML = '<tr><td colspan="3" class="text-center text-gray-500">No safety risks detected</td></tr>';
253 return;
254 }}
255
256 unsafeTable.innerHTML = '';
257 risks.forEach((risk, index) => {{
258 const row = document.createElement('tr');
259 row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700';
260
261 const riskLevelClass = risk.risk_level === 'High' ? 'text-red-600 font-bold' :
262 risk.risk_level === 'Medium' ? 'text-yellow-600 font-semibold' :
263 'text-green-600';
264
265 row.innerHTML = `
266 <td class="px-3 py-2 text-sm">${{risk.location || 'Unknown'}}</td>
267 <td class="px-3 py-2 text-sm">${{risk.operation || 'Unknown'}}</td>
268 <td class="px-3 py-2 text-sm"><span class="${{riskLevelClass}}">${{risk.risk_level || 'Low'}}</span></td>
269 `;
270 unsafeTable.appendChild(row);
271 }});
272
273 console.log('✅ Safety risks loaded:', risks.length, 'items');
274 }}
275
276 "#,
277 );
278
279 html = format!("{before}{safety_injection}{after}");
280 } else {
281 tracing::debug!("Could not find DOMContentLoaded event listener for safety risk injection");
282 }
283
284 if let Some(manual_init_start) =
286 html.find("manualBtn.addEventListener('click', manualInitialize);")
287 {
288 let after_manual_init =
289 manual_init_start + "manualBtn.addEventListener('click', manualInitialize);".len();
290 let before = &html[..after_manual_init];
291 let after = &html[after_manual_init..];
292
293 let safety_call_injection = r#"
294
295 // Load safety risks after manual initialization
296 setTimeout(function() {
297 loadSafetyRisks();
298 }, 100);
299"#;
300
301 html = format!("{before}{safety_call_injection}{after}");
302 }
303
304 html = html.replace(
306 "console.log('✅ Enhanced dashboard initialized');",
307 "console.log('✅ Enhanced dashboard initialized'); loadSafetyRisks();",
308 );
309
310 tracing::debug!(
311 "Data injection completed: {} allocations, {} stats, safety risks injected",
312 allocations.len(),
313 stats.total_allocations
314 );
315
316 Ok(html)
317}
318
319fn prepare_allocation_data(allocations: &[AllocationInfo]) -> Result<String, BinaryExportError> {
321 let mut allocation_data = Vec::new();
322
323 for allocation in allocations {
324 let mut item = json!({
325 "ptr": format!("0x{:x}", allocation.ptr),
326 "size": allocation.size,
327 "var_name": allocation.var_name.as_deref().unwrap_or("unknown"),
328 "type_name": allocation.type_name.as_deref().unwrap_or("unknown"),
329 "scope_name": allocation.scope_name.as_deref().unwrap_or("global"),
330 "thread_id": allocation.thread_id,
331 "timestamp_alloc": allocation.timestamp_alloc,
332 "timestamp_dealloc": allocation.timestamp_dealloc,
333 "is_leaked": allocation.is_leaked,
334 "lifetime_ms": allocation.lifetime_ms,
335 "borrow_count": allocation.borrow_count,
336 });
337
338 if let Some(ref borrow_info) = allocation.borrow_info {
340 item["borrow_info"] = json!({
341 "immutable_borrows": borrow_info.immutable_borrows,
342 "mutable_borrows": borrow_info.mutable_borrows,
343 "max_concurrent_borrows": borrow_info.max_concurrent_borrows,
344 "last_borrow_timestamp": borrow_info.last_borrow_timestamp,
345 });
346 }
347
348 if let Some(ref clone_info) = allocation.clone_info {
349 item["clone_info"] = json!({
350 "clone_count": clone_info.clone_count,
351 "is_clone": clone_info.is_clone,
352 "original_ptr": clone_info.original_ptr.map(|p| format!("0x{p:x}")),
353 });
354 }
355
356 item["ownership_history_available"] = json!(allocation.ownership_history_available);
357
358 allocation_data.push(item);
359 }
360
361 let (lifetime_data, complex_types, unsafe_ffi, performance_data) =
363 generate_enhanced_data(&allocation_data);
364
365 let data_structure = json!({
367 "memory_analysis": {
368 "allocations": allocation_data.clone()
369 },
370 "allocations": allocation_data, "lifetime": lifetime_data,
372 "complex_types": complex_types,
373 "unsafe_ffi": unsafe_ffi,
374 "performance": performance_data,
375 "metadata": {
376 "generation_time": chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(),
377 "data_source": "binary_direct",
378 "version": "1.0"
379 }
380 });
381
382 serde_json::to_string(&data_structure).map_err(|e| {
383 BinaryExportError::SerializationError(format!("Failed to serialize allocation data: {e}"))
384 })
385}
386
387fn prepare_stats_data(stats: &MemoryStats) -> Result<String, BinaryExportError> {
389 let data = json!({
390 "total_allocations": stats.total_allocations,
391 "total_allocated": stats.total_allocated,
392 "active_allocations": stats.active_allocations,
393 "active_memory": stats.active_memory,
394 "peak_allocations": stats.peak_allocations,
395 "peak_memory": stats.peak_memory,
396 "total_deallocations": stats.total_deallocations,
397 "total_deallocated": stats.total_deallocated,
398 "leaked_allocations": stats.leaked_allocations,
399 "leaked_memory": stats.leaked_memory,
400 });
401
402 serde_json::to_string(&data).map_err(|e| {
403 BinaryExportError::SerializationError(format!("Failed to serialize stats data: {e}"))
404 })
405}
406
407fn format_memory_size(bytes: usize) -> String {
409 const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
410 let mut size = bytes as f64;
411 let mut unit_index = 0;
412
413 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
414 size /= 1024.0;
415 unit_index += 1;
416 }
417
418 if unit_index == 0 {
419 format!("{bytes} {}", UNITS[unit_index])
420 } else {
421 format!("{size:.2} {}", UNITS[unit_index])
422 }
423}
424
425fn get_embedded_binary_template() -> String {
427 match std::fs::read_to_string("templates/working_dashboard.html") {
429 Ok(content) => {
430 tracing::debug!(
431 "Successfully loaded working_dashboard.html template ({} chars)",
432 content.len()
433 );
434 return content;
435 }
436 Err(e) => {
437 tracing::debug!("Failed to load working_dashboard.html: {e}");
438 }
439 }
440
441 r#"<!DOCTYPE html>
443<html lang="en" class="light">
444<head>
445 <meta charset="UTF-8">
446 <meta name="viewport" content="width=device-width, initial-scale=1.0">
447 <title>{{PROJECT_NAME}} - Binary Memory Analysis</title>
448 <script src="https://cdn.tailwindcss.com"></script>
449 <script>
450 tailwind.config = {
451 darkMode: 'class',
452 }
453 </script>
454 <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
455
456 <!-- Binary Data injection script -->
457 <script>
458 // Global data store - populated directly from binary data
459 console.log('Binary data injection script loaded');
460 try {
461 window.analysisData = {{ALLOCATION_DATA}};
462 window.embeddedJsonData = {{ALLOCATION_DATA}};
463 window.dataSource = "binary_direct";
464 window.generationTime = "{{TIMESTAMP}}";
465 console.log('Analysis data loaded successfully:', window.analysisData ? 'YES' : 'NO');
466 } catch (e) {
467 console.error('Error loading analysis data:', e);
468 window.analysisData = null;
469 }
470 </script>
471</head>
472
473<body class="bg-gray-50 dark:bg-gray-900 font-sans text-neutral dark:text-gray-100 transition-colors">
474 <!-- Header -->
475 <header class="bg-gradient-to-r from-blue-600 to-purple-600 text-white py-8">
476 <div class="container mx-auto px-4 text-center">
477 <h1 class="text-4xl font-bold mb-2">{{PROJECT_NAME}}</h1>
478 <p class="text-xl opacity-90">Binary Memory Analysis Report - {{TIMESTAMP}}</p>
479 </div>
480 </header>
481
482 <!-- Main Content -->
483 <main class="container mx-auto px-4 py-8">
484 <!-- Stats Grid -->
485 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
486 <div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
487 <h3 class="text-sm font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">Total Allocations</h3>
488 <p class="text-3xl font-bold text-gray-900 dark:text-white">{{TOTAL_ALLOCATIONS}}</p>
489 </div>
490 <div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
491 <h3 class="text-sm font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">Active Memory</h3>
492 <p class="text-3xl font-bold text-green-600">{{ACTIVE_MEMORY}}</p>
493 </div>
494 <div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
495 <h3 class="text-sm font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">Leaked Memory</h3>
496 <p class="text-3xl font-bold text-red-600">{{LEAKED_MEMORY}}</p>
497 </div>
498 </div>
499
500 <!-- Allocations Table -->
501 <div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
502 <div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
503 <h2 class="text-xl font-semibold text-gray-900 dark:text-white">Memory Allocations</h2>
504 </div>
505 <div class="overflow-x-auto">
506 <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
507 <thead class="bg-gray-50 dark:bg-gray-700">
508 <tr>
509 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Pointer</th>
510 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Size</th>
511 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Variable</th>
512 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Type</th>
513 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Status</th>
514 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Borrow Info</th>
515 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Clone Info</th>
516 </tr>
517 </thead>
518 <tbody id="allocations-tbody" class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
519 <tr><td colspan="7" class="px-6 py-4 text-center">Loading allocation data...</td></tr>
520 </tbody>
521 </table>
522 </div>
523 </div>
524 </main>
525
526 <script>
527 console.log('🚀 Binary Dashboard Loading...');
528
529 // Use the injected data
530 const allocations = window.analysisData || [];
531
532 function formatSize(bytes) {
533 if (!bytes || bytes === 0) return '0 B';
534 const units = ['B', 'KB', 'MB', 'GB'];
535 let size = bytes;
536 let unitIndex = 0;
537
538 while (size >= 1024 && unitIndex < units.length - 1) {
539 size /= 1024;
540 unitIndex++;
541 }
542
543 return unitIndex === 0 ?
544 bytes + ' ' + units[unitIndex] :
545 size.toFixed(2) + ' ' + units[unitIndex];
546 }
547
548 function loadBinaryAllocations() {
549 console.log('📊 Loading', allocations.length, 'allocations from binary data');
550
551 const tbody = document.getElementById('allocations-tbody');
552 if (!tbody) {
553 console.error('❌ Table body not found');
554 return;
555 }
556
557 if (!allocations || allocations.length === 0) {
558 tbody.innerHTML = '<tr><td colspan="7" class="px-6 py-4 text-center">No allocation data available</td></tr>';
559 return;
560 }
561
562 tbody.innerHTML = '';
563
564 allocations.forEach((alloc, index) => {
565 try {
566 const row = document.createElement('tr');
567 row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700';
568
569 const status = alloc.timestamp_dealloc ?
570 '<span class="text-gray-600">Deallocated</span>' :
571 (alloc.is_leaked ? '<span class="text-red-600 font-semibold">Leaked</span>' : '<span class="text-green-600 font-semibold">Active</span>');
572
573 const borrowInfo = alloc.borrow_info ?
574 '<div class="text-xs text-gray-600">I:' + (alloc.borrow_info.immutable_borrows || 0) + ' M:' + (alloc.borrow_info.mutable_borrows || 0) + '</div>' :
575 '<span class="text-gray-400">-</span>';
576
577 const cloneInfo = alloc.clone_info ?
578 '<div class="text-xs text-blue-600">Count:' + (alloc.clone_info.clone_count || 0) + '</div>' :
579 '<span class="text-gray-400">-</span>';
580
581 row.innerHTML = `
582 <td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900 dark:text-gray-100">${alloc.ptr || 'N/A'}</td>
583 <td class="px-6 py-4 whitespace-nowrap text-sm font-semibold text-green-600">${formatSize(alloc.size || 0)}</td>
584 <td class="px-6 py-4 whitespace-nowrap text-sm text-orange-600 font-medium">${alloc.var_name || 'unnamed'}</td>
585 <td class="px-6 py-4 whitespace-nowrap text-sm text-purple-600 font-medium">${alloc.type_name || 'unknown'}</td>
586 <td class="px-6 py-4 whitespace-nowrap text-sm">${status}</td>
587 <td class="px-6 py-4 whitespace-nowrap text-sm">${borrowInfo}</td>
588 <td class="px-6 py-4 whitespace-nowrap text-sm">${cloneInfo}</td>
589 `;
590
591 tbody.appendChild(row);
592 } catch (e) {
593 console.error('❌ Error processing allocation', index, ':', e);
594 }
595 });
596
597 console.log('✅ Binary allocations loaded successfully');
598 }
599
600 // Initialize when DOM is ready
601 document.addEventListener('DOMContentLoaded', function() {
602 console.log('📋 Binary Dashboard DOM ready');
603 try {
604 loadBinaryAllocations();
605 console.log('✅ Binary dashboard initialized successfully');
606 } catch (error) {
607 console.error('❌ Failed to initialize binary dashboard:', error);
608 }
609 });
610 </script>
611</body>
612</html>"#.to_string()
613}
614
615fn generate_enhanced_data(
617 allocations: &[serde_json::Value],
618) -> (
619 serde_json::Value,
620 serde_json::Value,
621 serde_json::Value,
622 serde_json::Value,
623) {
624 let lifetime_allocations: Vec<serde_json::Value> = allocations
626 .iter()
627 .map(|alloc| {
628 let mut lifetime_alloc = alloc.clone();
629 lifetime_alloc["ownership_transfer_points"] =
630 json!(generate_ownership_transfer_points(alloc));
631 lifetime_alloc
632 })
633 .collect();
634
635 let lifetime_data = json!({
636 "allocations": lifetime_allocations,
637 "metadata": {
638 "generation_time": chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(),
639 "data_source": "binary_direct"
640 }
641 });
642
643 let complex_allocations: Vec<serde_json::Value> = allocations
645 .iter()
646 .filter_map(|alloc| {
647 let type_name = alloc["type_name"].as_str().unwrap_or("");
648 if type_name.contains('<')
650 || type_name.contains("Arc")
651 || type_name.contains("Box")
652 || type_name.contains("Vec")
653 || type_name.contains("HashMap")
654 || type_name.contains("BTreeMap")
655 || type_name.contains("Rc")
656 || type_name.contains("RefCell")
657 {
658 let mut complex_alloc = alloc.clone();
659 complex_alloc["generic_params"] = json!(extract_generic_params(type_name));
660 complex_alloc["complexity_score"] = json!(calculate_complexity_score(type_name));
661 complex_alloc["memory_layout"] = json!({
662 "alignment": 8,
663 "padding": 0,
664 "size_bytes": alloc["size"]
665 });
666 Some(complex_alloc)
667 } else {
668 None
669 }
670 })
671 .collect();
672
673 let complex_types = json!({
674 "allocations": complex_allocations,
675 "metadata": {
676 "generation_time": chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(),
677 "data_source": "binary_direct"
678 }
679 });
680
681 let unsafe_allocations: Vec<serde_json::Value> = allocations
683 .iter()
684 .map(|alloc| {
685 let type_name = alloc["type_name"].as_str().unwrap_or("");
686 let is_ffi_tracked = type_name.contains("*mut")
687 || type_name.contains("*const")
688 || type_name.contains("c_void")
689 || type_name.contains("CString")
690 || type_name.contains("extern")
691 || type_name.contains("CStr");
692
693 let safety_violations: Vec<&str> = if is_ffi_tracked {
694 vec!["raw_pointer_usage", "ffi_boundary_crossing"]
695 } else if alloc["is_leaked"].as_bool().unwrap_or(false) {
696 vec!["memory_leak"]
697 } else {
698 vec![]
699 };
700
701 let mut unsafe_alloc = alloc.clone();
702 unsafe_alloc["ffi_tracked"] = json!(is_ffi_tracked);
703 unsafe_alloc["safety_violations"] = json!(safety_violations);
704 unsafe_alloc
705 })
706 .collect();
707
708 let unsafe_ffi = json!({
709 "allocations": unsafe_allocations,
710 "metadata": {
711 "generation_time": chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(),
712 "data_source": "binary_direct"
713 }
714 });
715
716 let performance_allocations: Vec<serde_json::Value> = allocations
718 .iter()
719 .map(|alloc| {
720 let size = alloc["size"].as_u64().unwrap_or(0);
721 let lifetime_ms = alloc["lifetime_ms"].as_u64().unwrap_or(0);
722
723 let mut perf_alloc = alloc.clone();
724 perf_alloc["fragmentation_analysis"] = json!({
725 "fragmentation_score": if size > 1024 { 0.3 } else { 0.1 },
726 "alignment_efficiency": if size % 8 == 0 { 100.0 } else { 85.0 },
727 "memory_density": calculate_memory_density(size)
728 });
729 perf_alloc["allocation_efficiency"] = json!({
730 "reuse_potential": if lifetime_ms > 1000 { 0.2 } else { 0.8 },
731 "memory_locality": if size < 1024 { "high" } else { "medium" },
732 "cache_efficiency": calculate_cache_efficiency(size)
733 });
734 perf_alloc
735 })
736 .collect();
737
738 let performance_data = json!({
739 "allocations": performance_allocations,
740 "metadata": {
741 "generation_time": chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(),
742 "data_source": "binary_direct"
743 }
744 });
745
746 (lifetime_data, complex_types, unsafe_ffi, performance_data)
747}
748
749fn extract_generic_params(type_name: &str) -> Vec<String> {
751 if let Some(start) = type_name.find('<') {
752 if let Some(end) = type_name.rfind('>') {
753 let params_str = &type_name[start + 1..end];
754 return params_str
755 .split(',')
756 .map(|s| s.trim().to_string())
757 .collect();
758 }
759 }
760 vec![]
761}
762
763fn calculate_complexity_score(type_name: &str) -> u32 {
765 let mut score = 1;
766
767 score += type_name.matches('<').count() as u32 * 2;
769
770 if type_name.contains("Arc") || type_name.contains("Rc") {
772 score += 3;
773 }
774 if type_name.contains("Box") {
775 score += 2;
776 }
777 if type_name.contains("Vec") {
778 score += 2;
779 }
780 if type_name.contains("HashMap") || type_name.contains("BTreeMap") {
781 score += 4;
782 }
783
784 if type_name.contains("*mut") || type_name.contains("*const") {
786 score += 5;
787 }
788
789 score
790}
791
792fn calculate_memory_density(size: u64) -> f64 {
794 if size < 64 {
796 1.0
797 } else if size < 1024 {
798 0.8
799 } else if size < 4096 {
800 0.6
801 } else {
802 0.4
803 }
804}
805
806fn calculate_cache_efficiency(size: u64) -> f64 {
808 let cache_line_size = 64;
810 let lines_used = size.div_ceil(cache_line_size);
811 let efficiency = size as f64 / (lines_used * cache_line_size) as f64;
812 efficiency.min(1.0)
813}
814
815fn generate_ownership_transfer_points(allocation: &serde_json::Value) -> Vec<serde_json::Value> {
817 let mut transfer_points = Vec::new();
818
819 if let Some(clone_info) = allocation.get("clone_info") {
821 if clone_info
822 .get("is_clone")
823 .and_then(|v| v.as_bool())
824 .unwrap_or(false)
825 {
826 transfer_points.push(json!({
827 "event": "clone_created",
828 "timestamp": allocation.get("timestamp_alloc"),
829 "original_ptr": clone_info.get("original_ptr")
830 }));
831 }
832
833 let clone_count = clone_info
834 .get("clone_count")
835 .and_then(|v| v.as_u64())
836 .unwrap_or(0);
837 if clone_count > 0 {
838 transfer_points.push(json!({
839 "event": "clones_created",
840 "count": clone_count,
841 "timestamp": allocation.get("timestamp_alloc")
842 }));
843 }
844 }
845
846 if let Some(borrow_info) = allocation.get("borrow_info") {
848 if let Some(last_borrow) = borrow_info.get("last_borrow_timestamp") {
849 transfer_points.push(json!({
850 "event": "last_borrow",
851 "timestamp": last_borrow,
852 "borrow_type": "mixed"
853 }));
854 }
855 }
856
857 transfer_points
858}
859
860fn generate_dashboard_javascript() -> String {
862 r#"
863// Dashboard initialization and chart rendering functions
864let charts = {};
865let memoryTimelineChart = null;
866let typeTreemapData = null;
867
868function initDashboard() {
869 console.log('🚀 Initializing dashboard...');
870
871 if (!window.analysisData || !window.analysisData.memory_analysis) {
872 console.warn('No analysis data available');
873 return;
874 }
875
876 const allocations = window.analysisData.memory_analysis.allocations || [];
877 console.log('📊 Processing', allocations.length, 'allocations');
878
879 // Initialize all dashboard components
880 updateKPIs(allocations);
881 renderMemoryOperationsAnalysis(allocations);
882 renderMemoryOverTime(allocations);
883 renderEnhancedTypeTreemap(allocations);
884 renderEnhancedBorrowHeatmap(allocations);
885 renderInteractiveVariableGraph(allocations);
886 populateAllocationTable(allocations);
887
888 // Update Performance Metrics
889 updatePerformanceMetrics(allocations);
890
891 console.log('✅ Dashboard initialized successfully');
892}
893
894function updatePerformanceMetrics(allocations) {
895 console.log('⚡ Updating Performance Metrics...');
896
897 // Calculate allocation efficiency (successful vs total attempts)
898 const totalAllocations = allocations.length;
899 const successfulAllocations = allocations.filter(a => a.size > 0).length;
900 const allocationEfficiency = totalAllocations > 0 ?
901 Math.round((successfulAllocations / totalAllocations) * 100) : 100;
902
903 // Calculate memory utilization (allocated vs deallocated)
904 const totalAllocated = allocations.reduce((sum, a) => sum + (a.size || 0), 0);
905 const totalDeallocated = allocations.filter(a => a.timestamp_dealloc)
906 .reduce((sum, a) => sum + (a.size || 0), 0);
907 const memoryUtilization = totalAllocated > 0 ?
908 Math.round(((totalAllocated - totalDeallocated) / totalAllocated) * 100) : 0;
909
910 // Calculate fragmentation index (estimate based on allocation sizes)
911 const allocationSizes = allocations.map(a => a.size || 0).filter(s => s > 0);
912 const avgSize = allocationSizes.length > 0 ?
913 allocationSizes.reduce((sum, s) => sum + s, 0) / allocationSizes.length : 0;
914 const sizeVariance = allocationSizes.length > 0 ?
915 allocationSizes.reduce((sum, s) => sum + Math.pow(s - avgSize, 2), 0) / allocationSizes.length : 0;
916 const fragmentation = avgSize > 0 ? Math.min(100, Math.round((Math.sqrt(sizeVariance) / avgSize) * 100)) : 0;
917
918 // Calculate leak ratio
919 const leakedAllocations = allocations.filter(a => a.is_leaked).length;
920 const leakRatio = totalAllocations > 0 ?
921 Math.round((leakedAllocations / totalAllocations) * 100) : 0;
922
923 // Calculate thread efficiency (allocations per thread)
924 const uniqueThreads = new Set(allocations.map(a => a.thread_id)).size;
925 const threadEfficiency = uniqueThreads > 0 ?
926 Math.round(totalAllocations / uniqueThreads) : 0;
927
928 // Calculate borrow efficiency (safe borrows vs total)
929 const totalBorrows = allocations.reduce((sum, a) => sum + (a.borrow_count || 0), 0);
930 const immutableBorrows = allocations.reduce((sum, a) => {
931 return sum + (a.borrow_info ? (a.borrow_info.immutable_borrows || 0) : 0);
932 }, 0);
933 const borrowSafety = totalBorrows > 0 ?
934 Math.round((immutableBorrows / totalBorrows) * 100) : 100;
935
936 // Update UI elements
937 safeUpdateElement('allocation-efficiency', allocationEfficiency + '%');
938 safeUpdateElement('memory-utilization', memoryUtilization + '%');
939 safeUpdateElement('fragmentation-index', fragmentation + '%');
940 safeUpdateElement('leak-ratio', leakRatio + '%');
941 safeUpdateElement('thread-efficiency', threadEfficiency + ' allocs/thread');
942 safeUpdateElement('borrow-safety', borrowSafety + '%');
943
944 console.log('✅ Performance Metrics updated');
945}
946
947function updateKPIs(allocations) {
948 console.log('📊 Updating KPIs...');
949
950 const totalAllocations = allocations.length;
951 const activeAllocations = allocations.filter(a => !a.timestamp_dealloc).length;
952 const totalMemory = allocations.reduce((sum, a) => sum + (a.size || 0), 0);
953 const leakedCount = allocations.filter(a => a.is_leaked).length;
954
955 // Calculate safety score (percentage of non-leaked allocations)
956 const safetyScore = totalAllocations > 0 ?
957 Math.round(((totalAllocations - leakedCount) / totalAllocations) * 100) : 100;
958
959 safeUpdateElement('total-allocations', totalAllocations);
960 safeUpdateElement('active-variables', activeAllocations);
961 safeUpdateElement('total-memory', formatBytes(totalMemory));
962 safeUpdateElement('safety-score', safetyScore + '%');
963
964 console.log('✅ KPIs updated');
965}
966
967function renderMemoryOperationsAnalysis(allocations) {
968 console.log('🔧 Rendering Memory Operations Analysis...');
969
970 // Calculate time span
971 const timestamps = allocations.map(a => a.timestamp_alloc).filter(t => t);
972 const timeSpan = timestamps.length > 0 ?
973 Math.max(...timestamps) - Math.min(...timestamps) : 0;
974
975 // Calculate allocation burst (max allocations in a time window)
976 const sortedAllocs = allocations.filter(a => a.timestamp_alloc).sort((a, b) => a.timestamp_alloc - b.timestamp_alloc);
977 let maxBurst = 0;
978 const windowSize = 1000000; // 1ms in nanoseconds
979
980 for (let i = 0; i < sortedAllocs.length; i++) {
981 const windowStart = sortedAllocs[i].timestamp_alloc;
982 const windowEnd = windowStart + windowSize;
983 let count = 0;
984
985 for (let j = i; j < sortedAllocs.length && sortedAllocs[j].timestamp_alloc <= windowEnd; j++) {
986 count++;
987 }
988 maxBurst = Math.max(maxBurst, count);
989 }
990
991 // Calculate peak concurrency (max active allocations at any time)
992 let peakConcurrency = 0;
993 let currentActive = 0;
994
995 const events = [];
996 allocations.forEach(alloc => {
997 if (alloc.timestamp_alloc) events.push({ time: alloc.timestamp_alloc, type: 'alloc' });
998 if (alloc.timestamp_dealloc) events.push({ time: alloc.timestamp_dealloc, type: 'dealloc' });
999 });
1000
1001 events.sort((a, b) => a.time - b.time);
1002 events.forEach(event => {
1003 if (event.type === 'alloc') currentActive++;
1004 else currentActive--;
1005 peakConcurrency = Math.max(peakConcurrency, currentActive);
1006 });
1007
1008 // Calculate thread activity
1009 const threads = new Set(allocations.map(a => a.thread_id));
1010 const threadActivity = threads.size;
1011
1012 // Calculate borrow operations with detailed analysis
1013 const borrowOps = allocations.reduce((sum, a) => sum + (a.borrow_count || 0), 0);
1014 let mutableBorrows = 0;
1015 let immutableBorrows = 0;
1016
1017 allocations.forEach(a => {
1018 if (a.borrow_info) {
1019 mutableBorrows += a.borrow_info.mutable_borrows || 0;
1020 immutableBorrows += a.borrow_info.immutable_borrows || 0;
1021 }
1022 });
1023
1024 // Calculate clone operations
1025 const cloneOps = allocations.reduce((sum, a) => {
1026 return sum + (a.clone_info ? (a.clone_info.clone_count || 0) : 0);
1027 }, 0);
1028
1029 // Calculate average allocation size
1030 const totalSize = allocations.reduce((sum, a) => sum + (a.size || 0), 0);
1031 const avgAllocSize = allocations.length > 0 ? totalSize / allocations.length : 0;
1032
1033 // Better time span calculation - use realistic timestamps if available
1034 let timeSpanDisplay = 'N/A';
1035 if (timeSpan > 0) {
1036 if (timeSpan > 1000000000) { // > 1 second
1037 timeSpanDisplay = (timeSpan / 1000000000).toFixed(2) + 's';
1038 } else if (timeSpan > 1000000) { // > 1 millisecond
1039 timeSpanDisplay = (timeSpan / 1000000).toFixed(2) + 'ms';
1040 } else if (timeSpan > 1000) { // > 1 microsecond
1041 timeSpanDisplay = (timeSpan / 1000).toFixed(2) + 'μs';
1042 } else {
1043 timeSpanDisplay = timeSpan + 'ns';
1044 }
1045 } else if (allocations.length > 0) {
1046 // If no timestamps, show based on allocation count
1047 timeSpanDisplay = allocations.length + ' allocs';
1048 }
1049
1050 // Update UI elements
1051 safeUpdateElement('time-span', timeSpanDisplay);
1052 safeUpdateElement('allocation-burst', maxBurst || allocations.length);
1053 safeUpdateElement('peak-concurrency', peakConcurrency || allocations.length);
1054 safeUpdateElement('thread-activity', threadActivity + ' threads');
1055 safeUpdateElement('borrow-ops', borrowOps);
1056 safeUpdateElement('clone-ops', cloneOps);
1057
1058 // Update the missing fields
1059 safeUpdateElement('mut-immut', `${mutableBorrows}/${immutableBorrows}`);
1060 safeUpdateElement('avg-alloc', formatBytes(avgAllocSize));
1061
1062 console.log('✅ Memory Operations Analysis updated');
1063}
1064
1065function renderMemoryOverTime(allocations) {
1066 console.log('📈 Rendering Memory Over Time chart...');
1067
1068 const canvas = document.getElementById('timelineChart');
1069 if (!canvas) {
1070 console.warn('timelineChart canvas not found');
1071 return;
1072 }
1073
1074 const ctx = canvas.getContext('2d');
1075
1076 // Destroy existing chart if it exists
1077 if (memoryTimelineChart) {
1078 memoryTimelineChart.destroy();
1079 }
1080
1081 // Sort allocations by timestamp
1082 const sortedAllocs = allocations
1083 .filter(a => a.timestamp_alloc)
1084 .sort((a, b) => (a.timestamp_alloc || 0) - (b.timestamp_alloc || 0));
1085
1086 if (sortedAllocs.length === 0) {
1087 console.warn('No allocations with timestamps found');
1088 ctx.fillStyle = '#666';
1089 ctx.font = '16px Arial';
1090 ctx.textAlign = 'center';
1091 ctx.fillText('No timeline data available', canvas.width / 2, canvas.height / 2);
1092 return;
1093 }
1094
1095 // Create simple indexed timeline data (avoid time scale issues)
1096 const timelineData = [];
1097 let cumulativeMemory = 0;
1098
1099 sortedAllocs.forEach((alloc, index) => {
1100 cumulativeMemory += alloc.size || 0;
1101 timelineData.push({
1102 x: index,
1103 y: cumulativeMemory
1104 });
1105
1106 // Add deallocation point if available
1107 if (alloc.timestamp_dealloc) {
1108 cumulativeMemory -= alloc.size || 0;
1109 timelineData.push({
1110 x: index + 0.5,
1111 y: cumulativeMemory
1112 });
1113 }
1114 });
1115
1116 // Create labels from allocation names
1117 const labels = sortedAllocs.map((alloc, index) =>
1118 `${index}: ${alloc.var_name || 'unnamed'}`);
1119
1120 memoryTimelineChart = new Chart(ctx, {
1121 type: 'line',
1122 data: {
1123 labels: labels,
1124 datasets: [{
1125 label: 'Memory Usage',
1126 data: timelineData.map(d => d.y),
1127 borderColor: 'rgb(59, 130, 246)',
1128 backgroundColor: 'rgba(59, 130, 246, 0.1)',
1129 fill: true,
1130 tension: 0.4,
1131 pointRadius: 3,
1132 pointHoverRadius: 5
1133 }]
1134 },
1135 options: {
1136 responsive: true,
1137 maintainAspectRatio: false,
1138 interaction: {
1139 intersect: false,
1140 mode: 'index'
1141 },
1142 scales: {
1143 x: {
1144 title: {
1145 display: true,
1146 text: 'Allocation Sequence'
1147 },
1148 ticks: {
1149 maxTicksLimit: 10
1150 }
1151 },
1152 y: {
1153 title: {
1154 display: true,
1155 text: 'Memory (bytes)'
1156 },
1157 ticks: {
1158 callback: function(value) {
1159 return formatBytes(value);
1160 }
1161 }
1162 }
1163 },
1164 plugins: {
1165 tooltip: {
1166 callbacks: {
1167 title: function(context) {
1168 const index = context[0].dataIndex;
1169 if (sortedAllocs[index]) {
1170 return `${sortedAllocs[index].var_name || 'unnamed'} (${sortedAllocs[index].type_name || 'unknown'})`;
1171 }
1172 return 'Allocation ' + index;
1173 },
1174 label: function(context) {
1175 return 'Memory: ' + formatBytes(context.parsed.y);
1176 }
1177 }
1178 }
1179 }
1180 }
1181 });
1182
1183 // Add growth rate toggle functionality
1184 const growthRateToggle = document.getElementById('toggleGrowthRate');
1185 if (growthRateToggle) {
1186 growthRateToggle.addEventListener('change', function() {
1187 updateTimelineChart(allocations, this.checked);
1188 });
1189 }
1190
1191 console.log('✅ Memory Over Time chart rendered with', timelineData.length, 'data points');
1192}
1193
1194function updateTimelineChart(allocations, showGrowthRate) {
1195 const canvas = document.getElementById('timelineChart');
1196 if (!canvas) return;
1197
1198 const ctx = canvas.getContext('2d');
1199
1200 // Destroy existing chart if it exists
1201 if (memoryTimelineChart) {
1202 memoryTimelineChart.destroy();
1203 }
1204
1205 // Sort allocations by timestamp
1206 const sortedAllocs = allocations
1207 .filter(a => a.timestamp_alloc)
1208 .sort((a, b) => (a.timestamp_alloc || 0) - (b.timestamp_alloc || 0));
1209
1210 if (sortedAllocs.length === 0) {
1211 ctx.fillStyle = '#666';
1212 ctx.font = '16px Arial';
1213 ctx.textAlign = 'center';
1214 ctx.fillText('No timeline data available', canvas.width / 2, canvas.height / 2);
1215 return;
1216 }
1217
1218 const timelineData = [];
1219 const growthRateData = [];
1220 let cumulativeMemory = 0;
1221 let previousMemory = 0;
1222
1223 sortedAllocs.forEach((alloc, index) => {
1224 previousMemory = cumulativeMemory;
1225 cumulativeMemory += alloc.size || 0;
1226
1227 timelineData.push({
1228 x: index,
1229 y: cumulativeMemory
1230 });
1231
1232 // Calculate growth rate (percentage change)
1233 const growthRate = previousMemory > 0 ?
1234 ((cumulativeMemory - previousMemory) / previousMemory) * 100 : 0;
1235 growthRateData.push({
1236 x: index,
1237 y: growthRate
1238 });
1239
1240 // Add deallocation point if available
1241 if (alloc.timestamp_dealloc) {
1242 previousMemory = cumulativeMemory;
1243 cumulativeMemory -= alloc.size || 0;
1244 timelineData.push({
1245 x: index + 0.5,
1246 y: cumulativeMemory
1247 });
1248
1249 const deallocGrowthRate = previousMemory > 0 ?
1250 ((cumulativeMemory - previousMemory) / previousMemory) * 100 : 0;
1251 growthRateData.push({
1252 x: index + 0.5,
1253 y: deallocGrowthRate
1254 });
1255 }
1256 });
1257
1258 const labels = sortedAllocs.map((alloc, index) =>
1259 `${index}: ${alloc.var_name || 'unnamed'}`);
1260
1261 const datasets = [{
1262 label: 'Memory Usage',
1263 data: timelineData.map(d => d.y),
1264 borderColor: 'rgb(59, 130, 246)',
1265 backgroundColor: 'rgba(59, 130, 246, 0.1)',
1266 fill: true,
1267 tension: 0.4,
1268 pointRadius: 3,
1269 pointHoverRadius: 5,
1270 yAxisID: 'y'
1271 }];
1272
1273 if (showGrowthRate) {
1274 datasets.push({
1275 label: 'Growth Rate (%)',
1276 data: growthRateData.map(d => d.y),
1277 borderColor: 'rgb(239, 68, 68)',
1278 backgroundColor: 'rgba(239, 68, 68, 0.1)',
1279 fill: false,
1280 tension: 0.4,
1281 pointRadius: 2,
1282 pointHoverRadius: 4,
1283 yAxisID: 'y1'
1284 });
1285 }
1286
1287 const scales = {
1288 x: {
1289 title: {
1290 display: true,
1291 text: 'Allocation Sequence'
1292 },
1293 ticks: {
1294 maxTicksLimit: 10
1295 }
1296 },
1297 y: {
1298 type: 'linear',
1299 display: true,
1300 position: 'left',
1301 title: {
1302 display: true,
1303 text: 'Memory (bytes)'
1304 },
1305 ticks: {
1306 callback: function(value) {
1307 return formatBytes(value);
1308 }
1309 }
1310 }
1311 };
1312
1313 if (showGrowthRate) {
1314 scales.y1 = {
1315 type: 'linear',
1316 display: true,
1317 position: 'right',
1318 title: {
1319 display: true,
1320 text: 'Growth Rate (%)'
1321 },
1322 grid: {
1323 drawOnChartArea: false
1324 },
1325 ticks: {
1326 callback: function(value) {
1327 return value.toFixed(1) + '%';
1328 }
1329 }
1330 };
1331 }
1332
1333 memoryTimelineChart = new Chart(ctx, {
1334 type: 'line',
1335 data: {
1336 labels: labels,
1337 datasets: datasets
1338 },
1339 options: {
1340 responsive: true,
1341 maintainAspectRatio: false,
1342 interaction: {
1343 intersect: false,
1344 mode: 'index'
1345 },
1346 scales: scales,
1347 plugins: {
1348 tooltip: {
1349 callbacks: {
1350 title: function(context) {
1351 const index = context[0].dataIndex;
1352 if (sortedAllocs[index]) {
1353 return `${sortedAllocs[index].var_name || 'unnamed'} (${sortedAllocs[index].type_name || 'unknown'})`;
1354 }
1355 return 'Allocation ' + index;
1356 },
1357 label: function(context) {
1358 if (context.dataset.label.includes('Growth Rate')) {
1359 return 'Growth Rate: ' + context.parsed.y.toFixed(2) + '%';
1360 }
1361 return 'Memory: ' + formatBytes(context.parsed.y);
1362 }
1363 }
1364 }
1365 }
1366 }
1367 });
1368}
1369
1370function renderEnhancedTypeTreemap(allocations) {
1371 console.log('🌳 Rendering Enhanced Type Treemap...');
1372
1373 const container = document.getElementById('treemap');
1374 if (!container) {
1375 console.warn('treemap container not found');
1376 return;
1377 }
1378
1379 // Clear existing content
1380 container.innerHTML = '';
1381 container.style.position = 'relative';
1382
1383 // Aggregate by type
1384 const typeData = {};
1385 allocations.forEach(alloc => {
1386 const type = alloc.type_name || 'unknown';
1387 if (!typeData[type]) {
1388 typeData[type] = { count: 0, totalSize: 0 };
1389 }
1390 typeData[type].count++;
1391 typeData[type].totalSize += alloc.size || 0;
1392 });
1393
1394 // Convert to treemap format and sort by size
1395 const treemapData = Object.entries(typeData)
1396 .map(([type, data]) => ({
1397 name: type,
1398 value: data.totalSize,
1399 count: data.count
1400 }))
1401 .sort((a, b) => b.value - a.value);
1402
1403 if (treemapData.length === 0) {
1404 container.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">No type data available</div>';
1405 return;
1406 }
1407
1408 // Use squarified treemap algorithm for better layout
1409 const containerRect = container.getBoundingClientRect();
1410 const containerWidth = containerRect.width || 400;
1411 const containerHeight = containerRect.height || 300;
1412 const totalValue = treemapData.reduce((sum, d) => sum + d.value, 0);
1413
1414 // Calculate areas proportional to values
1415 treemapData.forEach(d => {
1416 d.area = (d.value / totalValue) * containerWidth * containerHeight;
1417 d.ratio = containerWidth / containerHeight;
1418 });
1419
1420 // Simple recursive treemap layout
1421 function layoutTreemap(data, x, y, width, height) {
1422 if (data.length === 0) return;
1423
1424 if (data.length === 1) {
1425 const item = data[0];
1426 createTreemapTile(item, x, y, width, height);
1427 return;
1428 }
1429
1430 // Split the data into two groups
1431 const totalArea = data.reduce((sum, d) => sum + d.area, 0);
1432 const midValue = totalArea / 2;
1433 let currentSum = 0;
1434 let splitIndex = 0;
1435
1436 for (let i = 0; i < data.length; i++) {
1437 currentSum += data[i].area;
1438 if (currentSum >= midValue) {
1439 splitIndex = i + 1;
1440 break;
1441 }
1442 }
1443
1444 const group1 = data.slice(0, splitIndex);
1445 const group2 = data.slice(splitIndex);
1446
1447 if (width > height) {
1448 // Split vertically
1449 const splitWidth = width * (currentSum / totalArea);
1450 layoutTreemap(group1, x, y, splitWidth, height);
1451 layoutTreemap(group2, x + splitWidth, y, width - splitWidth, height);
1452 } else {
1453 // Split horizontally
1454 const splitHeight = height * (currentSum / totalArea);
1455 layoutTreemap(group1, x, y, width, splitHeight);
1456 layoutTreemap(group2, x, y + splitHeight, width, height - splitHeight);
1457 }
1458 }
1459
1460 function createTreemapTile(item, x, y, width, height) {
1461 const tile = document.createElement('div');
1462 const minSize = Math.min(width, height);
1463 const fontSize = Math.max(Math.min(minSize / 8, 14), 10);
1464
1465 tile.style.cssText = `
1466 position: absolute;
1467 left: ${x + 1}px;
1468 top: ${y + 1}px;
1469 width: ${width - 2}px;
1470 height: ${height - 2}px;
1471 background: hsl(${(item.name.length * 37) % 360}, 65%, 55%);
1472 border: 2px solid rgba(255,255,255,0.8);
1473 border-radius: 6px;
1474 display: flex;
1475 flex-direction: column;
1476 align-items: center;
1477 justify-content: center;
1478 font-size: ${fontSize}px;
1479 font-weight: 600;
1480 color: white;
1481 text-shadow: 1px 1px 2px rgba(0,0,0,0.7);
1482 cursor: pointer;
1483 transition: all 0.3s ease;
1484 overflow: hidden;
1485 box-shadow: 0 2px 8px rgba(0,0,0,0.2);
1486 `;
1487
1488 const shortName = item.name.length > 12 ? item.name.substring(0, 12) + '...' : item.name;
1489 tile.innerHTML = `
1490 <div style="text-align: center; padding: 4px;">
1491 <div style="font-weight: 700; margin-bottom: 2px;" title="${item.name}">${shortName}</div>
1492 <div style="font-size: ${Math.max(fontSize - 2, 8)}px; opacity: 0.9;">${formatBytes(item.value)}</div>
1493 <div style="font-size: ${Math.max(fontSize - 3, 7)}px; opacity: 0.8;">(${item.count} items)</div>
1494 </div>
1495 `;
1496
1497 tile.addEventListener('mouseenter', () => {
1498 tile.style.transform = 'scale(1.05)';
1499 tile.style.zIndex = '10';
1500 tile.style.boxShadow = '0 4px 16px rgba(0,0,0,0.4)';
1501 });
1502
1503 tile.addEventListener('mouseleave', () => {
1504 tile.style.transform = 'scale(1)';
1505 tile.style.zIndex = '1';
1506 tile.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
1507 });
1508
1509 tile.addEventListener('click', () => {
1510 const totalMemorySize = treemapData.reduce((sum, d) => sum + d.value, 0);
1511 const modalContent = `
1512 <div style="text-align: center; margin-bottom: 20px;">
1513 <div style="font-size: 48px; margin-bottom: 10px;">📊</div>
1514 <div style="font-size: 24px; font-weight: 600; margin-bottom: 8px;">${item.name}</div>
1515 </div>
1516 <div style="background: rgba(255, 255, 255, 0.1); padding: 20px; border-radius: 12px; margin-bottom: 20px;">
1517 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
1518 <div style="text-align: center;">
1519 <div style="font-size: 28px; font-weight: 700; color: #4ade80;">${formatBytes(item.value)}</div>
1520 <div style="opacity: 0.8; font-size: 14px;">Total Size</div>
1521 </div>
1522 <div style="text-align: center;">
1523 <div style="font-size: 28px; font-weight: 700; color: #60a5fa;">${item.count}</div>
1524 <div style="opacity: 0.8; font-size: 14px;">Allocations</div>
1525 </div>
1526 </div>
1527 </div>
1528 <div style="background: rgba(255, 255, 255, 0.05); padding: 16px; border-radius: 8px;">
1529 <div style="font-size: 14px; opacity: 0.9;">
1530 <div style="margin-bottom: 8px;"><strong>Average Size:</strong> ${formatBytes(item.value / item.count)}</div>
1531 <div style="margin-bottom: 8px;"><strong>Memory Share:</strong> ${((item.value / totalMemorySize) * 100).toFixed(1)}%</div>
1532 <div><strong>Type Category:</strong> ${item.name.includes('Vec') ? 'Dynamic Array' : item.name.includes('HashMap') ? 'Hash Map' : item.name.includes('String') ? 'String Type' : 'Custom Type'}</div>
1533 </div>
1534 </div>
1535 `;
1536 createModal(`📋 Type Analysis`, modalContent);
1537 });
1538
1539 container.appendChild(tile);
1540 }
1541
1542 // Start the layout process
1543 layoutTreemap(treemapData, 0, 0, containerWidth, containerHeight);
1544
1545 console.log('✅ Enhanced Type Treemap rendered with', treemapData.length, 'types');
1546}
1547
1548function renderEnhancedBorrowHeatmap(allocations) {
1549 console.log('🔥 Rendering Enhanced Borrow Activity Heatmap...');
1550
1551 const container = document.getElementById('borrowPatternChart');
1552 if (!container) {
1553 console.warn('borrowPatternChart container not found');
1554 return;
1555 }
1556
1557 container.innerHTML = '';
1558 container.style.position = 'relative';
1559
1560 // Enhanced borrow data collection - include borrow_info if available
1561 const borrowData = allocations.map(alloc => {
1562 const borrowCount = alloc.borrow_count || 0;
1563 const borrowInfo = alloc.borrow_info || {};
1564 const immutableBorrows = borrowInfo.immutable_borrows || 0;
1565 const mutableBorrows = borrowInfo.mutable_borrows || 0;
1566 const totalBorrows = Math.max(borrowCount, immutableBorrows + mutableBorrows);
1567
1568 return {
1569 ...alloc,
1570 totalBorrows,
1571 immutableBorrows,
1572 mutableBorrows,
1573 hasActivity: totalBorrows > 0 || borrowCount > 0
1574 };
1575 }).filter(a => a.hasActivity || allocations.length <= 20); // Show all if few allocations
1576
1577 if (borrowData.length === 0) {
1578 // Create synthetic data for demonstration
1579 const syntheticData = allocations.slice(0, Math.min(50, allocations.length)).map((alloc, i) => ({
1580 ...alloc,
1581 totalBorrows: Math.floor(Math.random() * 10) + 1,
1582 immutableBorrows: Math.floor(Math.random() * 5),
1583 mutableBorrows: Math.floor(Math.random() * 3),
1584 hasActivity: true
1585 }));
1586
1587 if (syntheticData.length > 0) {
1588 renderHeatmapGrid(container, syntheticData, true);
1589 } else {
1590 container.innerHTML = `
1591 <div style="display: flex; align-items: center; justify-content: center; height: 100%;
1592 color: var(--text-secondary); font-size: 14px; text-align: center;">
1593 <div>
1594 <div style="margin-bottom: 8px;">📊 No borrow activity detected</div>
1595 <div style="font-size: 12px; opacity: 0.7;">This indicates efficient memory usage with minimal borrowing</div>
1596 </div>
1597 </div>
1598 `;
1599 }
1600 return;
1601 }
1602
1603 renderHeatmapGrid(container, borrowData, false);
1604
1605 function renderHeatmapGrid(container, data, isSynthetic) {
1606 const containerRect = container.getBoundingClientRect();
1607 const containerWidth = containerRect.width || 400;
1608 const containerHeight = containerRect.height || 300;
1609
1610 // Calculate optimal cell size and grid dimensions
1611 const maxCells = Math.min(data.length, 200);
1612 const aspectRatio = containerWidth / containerHeight;
1613 const cols = Math.floor(Math.sqrt(maxCells * aspectRatio));
1614 const rows = Math.ceil(maxCells / cols);
1615 const cellSize = Math.min((containerWidth - 10) / cols, (containerHeight - 10) / rows) - 2;
1616
1617 const maxBorrows = Math.max(...data.map(a => a.totalBorrows), 1);
1618
1619 // Add legend
1620 const legend = document.createElement('div');
1621 legend.style.cssText = `
1622 position: absolute;
1623 top: 5px;
1624 right: 5px;
1625 background: rgba(0,0,0,0.8);
1626 color: white;
1627 padding: 8px;
1628 border-radius: 4px;
1629 font-size: 10px;
1630 z-index: 100;
1631 `;
1632 legend.innerHTML = `
1633 <div>Borrow Activity ${isSynthetic ? '(Demo)' : ''}</div>
1634 <div style="margin-top: 4px;">
1635 <div style="display: flex; align-items: center; margin: 2px 0;">
1636 <div style="width: 12px; height: 12px; background: rgba(239, 68, 68, 0.3); margin-right: 4px;"></div>
1637 <span>Low</span>
1638 </div>
1639 <div style="display: flex; align-items: center; margin: 2px 0;">
1640 <div style="width: 12px; height: 12px; background: rgba(239, 68, 68, 0.7); margin-right: 4px;"></div>
1641 <span>Medium</span>
1642 </div>
1643 <div style="display: flex; align-items: center; margin: 2px 0;">
1644 <div style="width: 12px; height: 12px; background: rgba(239, 68, 68, 1.0); margin-right: 4px;"></div>
1645 <span>High</span>
1646 </div>
1647 </div>
1648 `;
1649 container.appendChild(legend);
1650
1651 data.slice(0, maxCells).forEach((alloc, i) => {
1652 const row = Math.floor(i / cols);
1653 const col = i % cols;
1654 const intensity = Math.max(0.1, alloc.totalBorrows / maxBorrows);
1655
1656 const cell = document.createElement('div');
1657 const x = col * (cellSize + 2) + 5;
1658 const y = row * (cellSize + 2) + 30; // Offset for legend
1659
1660 // Color based on borrow type
1661 let backgroundColor;
1662 if (alloc.mutableBorrows > alloc.immutableBorrows) {
1663 backgroundColor = `rgba(239, 68, 68, ${intensity})`; // Red for mutable
1664 } else if (alloc.immutableBorrows > 0) {
1665 backgroundColor = `rgba(59, 130, 246, ${intensity})`; // Blue for immutable
1666 } else {
1667 backgroundColor = `rgba(16, 185, 129, ${intensity})`; // Green for mixed/unknown
1668 }
1669
1670 cell.style.cssText = `
1671 position: absolute;
1672 left: ${x}px;
1673 top: ${y}px;
1674 width: ${cellSize}px;
1675 height: ${cellSize}px;
1676 background: ${backgroundColor};
1677 border: 1px solid rgba(255,255,255,0.3);
1678 border-radius: 2px;
1679 cursor: pointer;
1680 transition: all 0.2s ease;
1681 `;
1682
1683 const tooltipText = `
1684Variable: ${alloc.var_name || 'unnamed'}
1685Type: ${alloc.type_name || 'unknown'}
1686Total Borrows: ${alloc.totalBorrows}
1687Immutable: ${alloc.immutableBorrows}
1688Mutable: ${alloc.mutableBorrows}
1689 `.trim();
1690
1691 cell.title = tooltipText;
1692
1693 cell.addEventListener('mouseenter', () => {
1694 cell.style.transform = 'scale(1.2)';
1695 cell.style.zIndex = '10';
1696 cell.style.boxShadow = '0 2px 8px rgba(0,0,0,0.5)';
1697 });
1698
1699 cell.addEventListener('mouseleave', () => {
1700 cell.style.transform = 'scale(1)';
1701 cell.style.zIndex = '1';
1702 cell.style.boxShadow = 'none';
1703 });
1704
1705 cell.addEventListener('click', () => {
1706 const modalContent = `
1707 <div style="text-align: center; margin-bottom: 20px;">
1708 <div style="font-size: 48px; margin-bottom: 10px;">🔥</div>
1709 <div style="font-size: 24px; font-weight: 600; margin-bottom: 8px;">${alloc.var_name || 'unnamed'}</div>
1710 <div style="opacity: 0.8; font-size: 16px;">${alloc.type_name || 'unknown'}</div>
1711 </div>
1712 <div style="background: rgba(255, 255, 255, 0.1); padding: 20px; border-radius: 12px; margin-bottom: 20px;">
1713 <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; text-align: center;">
1714 <div>
1715 <div style="font-size: 24px; font-weight: 700; color: #f87171;">${alloc.totalBorrows}</div>
1716 <div style="opacity: 0.8; font-size: 12px;">Total Borrows</div>
1717 </div>
1718 <div>
1719 <div style="font-size: 24px; font-weight: 700; color: #60a5fa;">${alloc.immutableBorrows}</div>
1720 <div style="opacity: 0.8; font-size: 12px;">Immutable</div>
1721 </div>
1722 <div>
1723 <div style="font-size: 24px; font-weight: 700; color: #fb7185;">${alloc.mutableBorrows}</div>
1724 <div style="opacity: 0.8; font-size: 12px;">Mutable</div>
1725 </div>
1726 </div>
1727 </div>
1728 <div style="background: rgba(255, 255, 255, 0.05); padding: 16px; border-radius: 8px;">
1729 <div style="font-size: 14px; opacity: 0.9;">
1730 <div style="margin-bottom: 8px;"><strong>Variable Size:</strong> ${formatBytes(alloc.size || 0)}</div>
1731 <div style="margin-bottom: 8px;"><strong>Borrow Ratio:</strong> ${alloc.immutableBorrows > 0 ? (alloc.mutableBorrows / alloc.immutableBorrows).toFixed(2) : 'N/A'} (Mut/Immut)</div>
1732 <div style="margin-bottom: 8px;"><strong>Activity Level:</strong> ${alloc.totalBorrows > 10 ? 'High' : alloc.totalBorrows > 5 ? 'Medium' : 'Low'}</div>
1733 <div><strong>Safety:</strong> ${alloc.mutableBorrows === 0 ? '✅ Read-only' : alloc.mutableBorrows < alloc.immutableBorrows ? '⚠️ Mostly read' : '🔥 Write-heavy'}</div>
1734 </div>
1735 </div>
1736 `;
1737 createModal(`🔥 Borrow Analysis`, modalContent);
1738 });
1739
1740 container.appendChild(cell);
1741 });
1742
1743 console.log(`✅ Enhanced Borrow Heatmap rendered with ${Math.min(data.length, maxCells)} cells${isSynthetic ? ' (synthetic data)' : ''}`);
1744 }
1745}
1746
1747function renderInteractiveVariableGraph(allocations) {
1748 console.log('🕸️ Rendering Interactive Variable Relationships Graph...');
1749
1750 const container = document.getElementById('graph');
1751 if (!container) {
1752 console.warn('graph container not found');
1753 return;
1754 }
1755
1756 container.innerHTML = '';
1757 container.style.position = 'relative';
1758 container.style.overflow = 'hidden';
1759 container.style.background = 'var(--bg-primary)';
1760 container.style.border = '1px solid var(--border-light)';
1761 container.style.borderRadius = '8px';
1762
1763 // Create interactive graph with D3-like functionality
1764 const containerRect = container.getBoundingClientRect();
1765 const width = containerRect.width || 600;
1766 const height = containerRect.height || 400;
1767
1768 // Graph state
1769 let zoomLevel = 1;
1770 let panX = 0;
1771 let panY = 0;
1772 let selectedNode = null;
1773 let isDragging = false;
1774 let dragTarget = null;
1775
1776 // Create nodes with relationship analysis
1777 const nodes = allocations.slice(0, 100).map((alloc, i) => {
1778 const baseSize = Math.sqrt(alloc.size || 100) / 10 + 8;
1779 return {
1780 id: i,
1781 name: alloc.var_name || ('var_' + i),
1782 type: alloc.type_name || 'unknown',
1783 size: alloc.size || 0,
1784 nodeSize: Math.max(baseSize, 12),
1785 x: Math.random() * (width - 100) + 50,
1786 y: Math.random() * (height - 100) + 50,
1787 vx: 0,
1788 vy: 0,
1789 alloc: alloc,
1790 isLeaked: alloc.is_leaked,
1791 borrowCount: alloc.borrow_count || 0,
1792 cloneInfo: alloc.clone_info,
1793 fixed: false
1794 };
1795 });
1796
1797 // Create relationships based on various criteria
1798 const links = [];
1799 for (let i = 0; i < nodes.length; i++) {
1800 for (let j = i + 1; j < nodes.length; j++) {
1801 const nodeA = nodes[i];
1802 const nodeB = nodes[j];
1803 let relationship = null;
1804 let strength = 0;
1805
1806 // Check for clone relationships
1807 if (nodeA.cloneInfo && nodeB.cloneInfo) {
1808 if (nodeA.cloneInfo.original_ptr === nodeB.alloc.ptr ||
1809 nodeB.cloneInfo.original_ptr === nodeA.alloc.ptr) {
1810 relationship = 'clone';
1811 strength = 0.8;
1812 }
1813 }
1814
1815 // Check for type similarity
1816 if (!relationship && nodeA.type === nodeB.type && nodeA.type !== 'unknown') {
1817 relationship = 'type_similar';
1818 strength = 0.3;
1819 }
1820
1821 // Check for thread affinity
1822 if (!relationship && nodeA.alloc.thread_id === nodeB.alloc.thread_id &&
1823 nodeA.alloc.thread_id !== undefined) {
1824 relationship = 'thread_affinity';
1825 strength = 0.2;
1826 }
1827
1828 // Check for temporal proximity (allocated around same time)
1829 if (!relationship && nodeA.alloc.timestamp_alloc && nodeB.alloc.timestamp_alloc) {
1830 const timeDiff = Math.abs(nodeA.alloc.timestamp_alloc - nodeB.alloc.timestamp_alloc);
1831 if (timeDiff < 1000000) { // Within 1ms
1832 relationship = 'temporal';
1833 strength = 0.4;
1834 }
1835 }
1836
1837 // Add link if relationship found
1838 if (relationship && (strength > 0.2 || Math.random() < 0.05)) {
1839 links.push({
1840 source: i,
1841 target: j,
1842 relationship,
1843 strength,
1844 sourceNode: nodeA,
1845 targetNode: nodeB
1846 });
1847 }
1848 }
1849 }
1850
1851 // Add control panel
1852 const controls = document.createElement('div');
1853 controls.style.cssText = `
1854 position: absolute;
1855 top: 10px;
1856 left: 10px;
1857 background: rgba(0,0,0,0.8);
1858 color: white;
1859 padding: 10px;
1860 border-radius: 6px;
1861 font-size: 12px;
1862 z-index: 1000;
1863 user-select: none;
1864 `;
1865 controls.innerHTML = `
1866 <div style="margin-bottom: 8px; font-weight: bold;">🎮 Graph Controls</div>
1867 <button id="zoom-in" style="margin: 2px; padding: 4px 8px; font-size: 11px;">🔍+ Zoom In</button>
1868 <button id="zoom-out" style="margin: 2px; padding: 4px 8px; font-size: 11px;">🔍- Zoom Out</button>
1869 <button id="reset-view" style="margin: 2px; padding: 4px 8px; font-size: 11px;">🏠 Reset</button>
1870 <button id="auto-layout" style="margin: 2px; padding: 4px 8px; font-size: 11px;">🔄 Layout</button>
1871 <div style="margin-top: 8px; font-size: 10px;">
1872 <div>Nodes: ${nodes.length}</div>
1873 <div>Links: ${links.length}</div>
1874 <div>Zoom: <span id="zoom-display">100%</span></div>
1875 </div>
1876 `;
1877 container.appendChild(controls);
1878
1879 // Add legend
1880 const legend = document.createElement('div');
1881 legend.style.cssText = `
1882 position: absolute;
1883 top: 10px;
1884 right: 10px;
1885 background: rgba(0,0,0,0.8);
1886 color: white;
1887 padding: 10px;
1888 border-radius: 6px;
1889 font-size: 11px;
1890 z-index: 1000;
1891 user-select: none;
1892 `;
1893 legend.innerHTML = `
1894 <div style="font-weight: bold; margin-bottom: 6px;">🔗 Relationships</div>
1895 <div style="margin: 3px 0;"><span style="color: #ff6b6b;">━━</span> Clone</div>
1896 <div style="margin: 3px 0;"><span style="color: #4ecdc4;">━━</span> Type Similar</div>
1897 <div style="margin: 3px 0;"><span style="color: #45b7d1;">━━</span> Thread Affinity</div>
1898 <div style="margin: 3px 0;"><span style="color: #f9ca24;">━━</span> Temporal</div>
1899 <div style="margin-top: 8px; font-weight: bold;">🎯 Nodes</div>
1900 <div style="margin: 3px 0;"><span style="color: #ff6b6b;">●</span> Leaked</div>
1901 <div style="margin: 3px 0;"><span style="color: #6c5ce7;">●</span> High Borrow</div>
1902 <div style="margin: 3px 0;"><span style="color: #a8e6cf;">●</span> Normal</div>
1903 `;
1904 container.appendChild(legend);
1905
1906 // Create info panel for selected node
1907 const infoPanel = document.createElement('div');
1908 infoPanel.style.cssText = `
1909 position: absolute;
1910 bottom: 10px;
1911 left: 10px;
1912 background: rgba(0,0,0,0.9);
1913 color: white;
1914 padding: 12px;
1915 border-radius: 6px;
1916 font-size: 11px;
1917 max-width: 250px;
1918 z-index: 1000;
1919 display: none;
1920 `;
1921 container.appendChild(infoPanel);
1922
1923 // Render function
1924 function render() {
1925 // Clear existing nodes and links
1926 container.querySelectorAll('.graph-node, .graph-link').forEach(el => el.remove());
1927
1928 // Render links first (behind nodes)
1929 links.forEach(link => {
1930 const sourceNode = nodes[link.source];
1931 const targetNode = nodes[link.target];
1932
1933 const linkEl = document.createElement('div');
1934 linkEl.className = 'graph-link';
1935
1936 const dx = (targetNode.x - sourceNode.x) * zoomLevel;
1937 const dy = (targetNode.y - sourceNode.y) * zoomLevel;
1938 const length = Math.sqrt(dx * dx + dy * dy);
1939 const angle = Math.atan2(dy, dx) * 180 / Math.PI;
1940
1941 const x = sourceNode.x * zoomLevel + panX;
1942 const y = sourceNode.y * zoomLevel + panY;
1943
1944 let color;
1945 switch(link.relationship) {
1946 case 'clone': color = '#ff6b6b'; break;
1947 case 'type_similar': color = '#4ecdc4'; break;
1948 case 'thread_affinity': color = '#45b7d1'; break;
1949 case 'temporal': color = '#f9ca24'; break;
1950 default: color = '#666';
1951 }
1952
1953 linkEl.style.cssText = `
1954 position: absolute;
1955 left: ${x}px;
1956 top: ${y}px;
1957 width: ${length}px;
1958 height: ${Math.max(link.strength * 2, 1)}px;
1959 background: linear-gradient(90deg, ${color} 60%, transparent 60%);
1960 background-size: 8px 100%;
1961 opacity: ${0.4 + link.strength * 0.3};
1962 transform-origin: 0 50%;
1963 transform: rotate(${angle}deg);
1964 z-index: 1;
1965 pointer-events: none;
1966 `;
1967
1968 container.appendChild(linkEl);
1969 });
1970
1971 // Render nodes
1972 nodes.forEach((node, i) => {
1973 const nodeEl = document.createElement('div');
1974 nodeEl.className = 'graph-node';
1975 nodeEl.dataset.nodeId = i;
1976
1977 const x = node.x * zoomLevel + panX - (node.nodeSize * zoomLevel) / 2;
1978 const y = node.y * zoomLevel + panY - (node.nodeSize * zoomLevel) / 2;
1979 const size = node.nodeSize * zoomLevel;
1980
1981 // Determine node color based on properties
1982 let color;
1983 if (node.isLeaked) {
1984 color = '#ff6b6b'; // Red for leaked
1985 } else if (node.borrowCount > 5) {
1986 color = '#6c5ce7'; // Purple for high borrow activity
1987 } else {
1988 color = `hsl(${(node.type.length * 47) % 360}, 65%, 60%)`;
1989 }
1990
1991 nodeEl.style.cssText = `
1992 position: absolute;
1993 left: ${x}px;
1994 top: ${y}px;
1995 width: ${size}px;
1996 height: ${size}px;
1997 background: ${color};
1998 border: ${selectedNode === i ? '3px solid #fff' : '2px solid rgba(255,255,255,0.7)'};
1999 border-radius: 50%;
2000 cursor: ${node.fixed ? 'move' : 'pointer'};
2001 transition: none;
2002 z-index: 10;
2003 box-shadow: 0 2px 8px rgba(0,0,0,0.3);
2004 `;
2005
2006 // Add node label for larger nodes
2007 if (size > 20) {
2008 const label = document.createElement('div');
2009 label.style.cssText = `
2010 position: absolute;
2011 top: ${size + 4}px;
2012 left: 50%;
2013 transform: translateX(-50%);
2014 font-size: ${Math.max(zoomLevel * 10, 8)}px;
2015 color: var(--text-primary);
2016 white-space: nowrap;
2017 pointer-events: none;
2018 text-shadow: 1px 1px 2px rgba(255,255,255,0.8);
2019 font-weight: 600;
2020 `;
2021 label.textContent = node.name.length > 8 ? node.name.substring(0, 8) + '...' : node.name;
2022 nodeEl.appendChild(label);
2023 }
2024
2025 // Add event listeners
2026 nodeEl.addEventListener('click', () => selectNode(i));
2027 nodeEl.addEventListener('mousedown', (e) => startDrag(e, i));
2028
2029 container.appendChild(nodeEl);
2030 });
2031
2032 // Update zoom display
2033 document.getElementById('zoom-display').textContent = Math.round(zoomLevel * 100) + '%';
2034 }
2035
2036 // Event handlers
2037 function selectNode(nodeId) {
2038 selectedNode = nodeId;
2039 const node = nodes[nodeId];
2040
2041 // Show info panel
2042 infoPanel.style.display = 'block';
2043 infoPanel.innerHTML = `
2044 <div style="font-weight: bold; margin-bottom: 8px; color: #4ecdc4;">📋 ${node.name}</div>
2045 <div><strong>Type:</strong> ${node.type}</div>
2046 <div><strong>Size:</strong> ${formatBytes(node.size)}</div>
2047 <div><strong>Leaked:</strong> ${node.isLeaked ? '❌ Yes' : '✅ No'}</div>
2048 <div><strong>Borrows:</strong> ${node.borrowCount}</div>
2049 ${node.cloneInfo ? `<div><strong>Clones:</strong> ${node.cloneInfo.clone_count || 0}</div>` : ''}
2050 <div><strong>Thread:</strong> ${node.alloc.thread_id || 'Unknown'}</div>
2051 <div style="margin-top: 8px; font-size: 10px; opacity: 0.8;">
2052 Click and drag to move • Double-click to pin
2053 </div>
2054 `;
2055
2056 render();
2057 }
2058
2059 function startDrag(e, nodeId) {
2060 e.preventDefault();
2061 e.stopPropagation(); // Prevent container panning
2062 isDragging = true;
2063 dragTarget = nodeId;
2064
2065 const rect = container.getBoundingClientRect();
2066 const startX = e.clientX;
2067 const startY = e.clientY;
2068 const startNodeX = nodes[nodeId].x;
2069 const startNodeY = nodes[nodeId].y;
2070
2071 // Visual feedback
2072 const nodeEl = document.querySelector(`[data-node-id="${nodeId}"]`);
2073 if (nodeEl) {
2074 nodeEl.style.transform = 'scale(1.2)';
2075 nodeEl.style.zIndex = '100';
2076 }
2077
2078 function onMouseMove(e) {
2079 if (!isDragging || dragTarget === null) return;
2080
2081 // Calculate movement in world coordinates
2082 const dx = (e.clientX - startX) / zoomLevel;
2083 const dy = (e.clientY - startY) / zoomLevel;
2084
2085 // Update node position
2086 nodes[dragTarget].x = Math.max(20, Math.min(width - 20, startNodeX + dx));
2087 nodes[dragTarget].y = Math.max(20, Math.min(height - 20, startNodeY + dy));
2088 nodes[dragTarget].fixed = true;
2089
2090 render();
2091 }
2092
2093 function onMouseUp() {
2094 isDragging = false;
2095
2096 // Reset visual feedback
2097 if (nodeEl) {
2098 nodeEl.style.transform = '';
2099 nodeEl.style.zIndex = '10';
2100 }
2101
2102 dragTarget = null;
2103 document.removeEventListener('mousemove', onMouseMove);
2104 document.removeEventListener('mouseup', onMouseUp);
2105 }
2106
2107 document.addEventListener('mousemove', onMouseMove);
2108 document.addEventListener('mouseup', onMouseUp);
2109 }
2110
2111 // Control event listeners
2112 document.getElementById('zoom-in').addEventListener('click', () => {
2113 zoomLevel = Math.min(zoomLevel * 1.2, 3);
2114 render();
2115 });
2116
2117 document.getElementById('zoom-out').addEventListener('click', () => {
2118 zoomLevel = Math.max(zoomLevel / 1.2, 0.3);
2119 render();
2120 });
2121
2122 document.getElementById('reset-view').addEventListener('click', () => {
2123 zoomLevel = 1;
2124 panX = 0;
2125 panY = 0;
2126 selectedNode = null;
2127 infoPanel.style.display = 'none';
2128 nodes.forEach(node => node.fixed = false);
2129 render();
2130 });
2131
2132 document.getElementById('auto-layout').addEventListener('click', () => {
2133 // Simple force-directed layout simulation
2134 for (let iteration = 0; iteration < 50; iteration++) {
2135 // Repulsion between nodes
2136 for (let i = 0; i < nodes.length; i++) {
2137 nodes[i].vx = 0;
2138 nodes[i].vy = 0;
2139
2140 for (let j = 0; j < nodes.length; j++) {
2141 if (i === j) continue;
2142
2143 const dx = nodes[i].x - nodes[j].x;
2144 const dy = nodes[i].y - nodes[j].y;
2145 const distance = Math.sqrt(dx * dx + dy * dy) + 0.1;
2146 const force = 100 / (distance * distance);
2147
2148 nodes[i].vx += (dx / distance) * force;
2149 nodes[i].vy += (dy / distance) * force;
2150 }
2151 }
2152
2153 // Attraction along links
2154 links.forEach(link => {
2155 const source = nodes[link.source];
2156 const target = nodes[link.target];
2157 const dx = target.x - source.x;
2158 const dy = target.y - source.y;
2159 const distance = Math.sqrt(dx * dx + dy * dy) + 0.1;
2160 const force = distance * 0.01 * link.strength;
2161
2162 source.vx += (dx / distance) * force;
2163 source.vy += (dy / distance) * force;
2164 target.vx -= (dx / distance) * force;
2165 target.vy -= (dy / distance) * force;
2166 });
2167
2168 // Apply velocities
2169 nodes.forEach(node => {
2170 if (!node.fixed) {
2171 node.x += node.vx * 0.1;
2172 node.y += node.vy * 0.1;
2173
2174 // Keep within bounds
2175 node.x = Math.max(30, Math.min(width - 30, node.x));
2176 node.y = Math.max(30, Math.min(height - 30, node.y));
2177 }
2178 });
2179 }
2180
2181 render();
2182 });
2183
2184 // Mouse wheel zoom
2185 container.addEventListener('wheel', (e) => {
2186 e.preventDefault();
2187 const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
2188 const rect = container.getBoundingClientRect();
2189 const mouseX = e.clientX - rect.left;
2190 const mouseY = e.clientY - rect.top;
2191
2192 // Zoom towards mouse position
2193 const beforeZoomX = (mouseX - panX) / zoomLevel;
2194 const beforeZoomY = (mouseY - panY) / zoomLevel;
2195
2196 zoomLevel = Math.max(0.3, Math.min(3, zoomLevel * zoomFactor));
2197
2198 // Adjust pan to keep mouse position fixed
2199 panX = mouseX - beforeZoomX * zoomLevel;
2200 panY = mouseY - beforeZoomY * zoomLevel;
2201
2202 render();
2203 });
2204
2205 // Container pan functionality
2206 let isPanning = false;
2207 let panStartX = 0;
2208 let panStartY = 0;
2209 let panStartPanX = 0;
2210 let panStartPanY = 0;
2211
2212 container.addEventListener('mousedown', (e) => {
2213 // Only start panning if not clicking on a node
2214 if (!e.target.classList.contains('graph-node')) {
2215 isPanning = true;
2216 panStartX = e.clientX;
2217 panStartY = e.clientY;
2218 panStartPanX = panX;
2219 panStartPanY = panY;
2220 container.style.cursor = 'grabbing';
2221 }
2222 });
2223
2224 container.addEventListener('mousemove', (e) => {
2225 if (isPanning) {
2226 panX = panStartPanX + (e.clientX - panStartX);
2227 panY = panStartPanY + (e.clientY - panStartY);
2228 render();
2229 }
2230 });
2231
2232 container.addEventListener('mouseup', () => {
2233 isPanning = false;
2234 container.style.cursor = 'default';
2235 });
2236
2237 container.addEventListener('mouseleave', () => {
2238 isPanning = false;
2239 container.style.cursor = 'default';
2240 });
2241
2242 // Initial render
2243 render();
2244
2245 console.log(`✅ Interactive Variable Graph rendered with ${nodes.length} nodes and ${links.length} relationships`);
2246}
2247
2248function populateAllocationTable(allocations) {
2249 console.log('📋 Populating allocation table...');
2250
2251 const tbody = document.getElementById('allocTable');
2252 if (!tbody) {
2253 console.warn('allocTable not found');
2254 return;
2255 }
2256
2257 tbody.innerHTML = '';
2258
2259 // Show first 100 allocations
2260 allocations.slice(0, 100).forEach(alloc => {
2261 const row = document.createElement('tr');
2262 row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700';
2263
2264 const status = alloc.is_leaked ?
2265 '<span class="status-badge status-leaked">Leaked</span>' :
2266 alloc.timestamp_dealloc ?
2267 '<span class="status-badge status-freed">Freed</span>' :
2268 '<span class="status-badge status-active">Active</span>';
2269
2270 row.innerHTML = `
2271 <td class="px-3 py-2 text-sm font-mono">${alloc.var_name || 'unnamed'}</td>
2272 <td class="px-3 py-2 text-sm">${alloc.type_name || 'unknown'}</td>
2273 <td class="px-3 py-2 text-sm">${formatBytes(alloc.size || 0)}</td>
2274 <td class="px-3 py-2 text-sm">${status}</td>
2275 `;
2276
2277 tbody.appendChild(row);
2278 });
2279
2280 console.log('✅ Allocation table populated with', Math.min(allocations.length, 100), 'entries');
2281}
2282
2283// Utility function for formatting bytes
2284function formatBytes(bytes) {
2285 if (bytes === 0) return '0 B';
2286 const k = 1024;
2287 const sizes = ['B', 'KB', 'MB', 'GB'];
2288 const i = Math.floor(Math.log(bytes) / Math.log(k));
2289 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
2290}
2291
2292// Enhanced mode selector for memory analysis
2293document.addEventListener('DOMContentLoaded', function() {
2294 const modeButtons = document.querySelectorAll('.heatmap-mode-btn');
2295 const visualizations = {
2296 heatmap: document.getElementById('memoryHeatmap'),
2297 type: document.getElementById('typeChart'),
2298 distribution: document.getElementById('distributionChart')
2299 };
2300
2301 modeButtons.forEach(btn => {
2302 btn.addEventListener('click', () => {
2303 // Remove active from all buttons
2304 modeButtons.forEach(b => b.classList.remove('active'));
2305 btn.classList.add('active');
2306
2307 // Hide all visualizations
2308 Object.values(visualizations).forEach(viz => {
2309 if (viz) viz.style.display = 'none';
2310 });
2311
2312 // Show selected visualization
2313 const mode = btn.dataset.mode;
2314 if (visualizations[mode]) {
2315 visualizations[mode].style.display = 'block';
2316 }
2317 });
2318 });
2319});
2320
2321console.log('📦 Dashboard JavaScript loaded');
2322"#.to_string()
2323}
2324
2325fn prepare_safety_risk_data(allocations: &[AllocationInfo]) -> Result<String, BinaryExportError> {
2327 let mut safety_risks = Vec::new();
2328
2329 for allocation in allocations {
2331 if allocation.size > 1024 * 1024 {
2335 safety_risks.push(json!({
2337 "location": format!("{}::{}",
2338 allocation.scope_name.as_deref().unwrap_or("unknown"),
2339 allocation.var_name.as_deref().unwrap_or("unnamed")),
2340 "operation": "Large Memory Allocation",
2341 "risk_level": "Medium",
2342 "description": format!("Large allocation of {} bytes may indicate unsafe buffer operations", allocation.size)
2343 }));
2344 }
2345
2346 if allocation.is_leaked {
2348 safety_risks.push(json!({
2349 "location": format!("{}::{}",
2350 allocation.scope_name.as_deref().unwrap_or("unknown"),
2351 allocation.var_name.as_deref().unwrap_or("unnamed")),
2352 "operation": "Memory Leak",
2353 "risk_level": "High",
2354 "description": "Memory leak detected - potential unsafe memory management"
2355 }));
2356 }
2357
2358 if allocation.borrow_count > 10 {
2360 safety_risks.push(json!({
2361 "location": format!("{}::{}",
2362 allocation.scope_name.as_deref().unwrap_or("unknown"),
2363 allocation.var_name.as_deref().unwrap_or("unnamed")),
2364 "operation": "High Borrow Count",
2365 "risk_level": "Medium",
2366 "description": format!("High borrow count ({}) may indicate unsafe sharing patterns", allocation.borrow_count)
2367 }));
2368 }
2369
2370 if let Some(type_name) = &allocation.type_name {
2372 if type_name.contains("*mut") || type_name.contains("*const") {
2373 safety_risks.push(json!({
2374 "location": format!("{}::{}",
2375 allocation.scope_name.as_deref().unwrap_or("unknown"),
2376 allocation.var_name.as_deref().unwrap_or("unnamed")),
2377 "operation": "Raw Pointer Usage",
2378 "risk_level": "High",
2379 "description": format!("Raw pointer type '{}' requires unsafe operations", type_name)
2380 }));
2381 }
2382
2383 if type_name.contains("CString")
2385 || type_name.contains("CStr")
2386 || type_name.contains("c_void")
2387 || type_name.contains("extern")
2388 {
2389 safety_risks.push(json!({
2390 "location": format!("{}::{}",
2391 allocation.scope_name.as_deref().unwrap_or("unknown"),
2392 allocation.var_name.as_deref().unwrap_or("unnamed")),
2393 "operation": "FFI Boundary Crossing",
2394 "risk_level": "Medium",
2395 "description": format!("FFI type '{}' crosses safety boundaries", type_name)
2396 }));
2397 }
2398 }
2399
2400 if let Some(lifetime_ms) = allocation.lifetime_ms {
2402 if lifetime_ms < 1 {
2403 safety_risks.push(json!({
2405 "location": format!("{}::{}",
2406 allocation.scope_name.as_deref().unwrap_or("unknown"),
2407 allocation.var_name.as_deref().unwrap_or("unnamed")),
2408 "operation": "Short-lived Allocation",
2409 "risk_level": "Low",
2410 "description": format!("Very short lifetime ({}ms) may indicate unsafe temporary operations", lifetime_ms)
2411 }));
2412 }
2413 }
2414 }
2415
2416 if safety_risks.is_empty() {
2418 safety_risks.push(json!({
2419 "location": "Global Analysis",
2420 "operation": "Safety Scan Complete",
2421 "risk_level": "Low",
2422 "description": "No significant safety risks detected in current allocations"
2423 }));
2424 }
2425
2426 serde_json::to_string(&safety_risks).map_err(|e| {
2427 BinaryExportError::SerializationError(format!("Failed to serialize safety risk data: {e}",))
2428 })
2429}
2430
2431pub fn parse_binary_to_html_direct<P: AsRef<Path>>(
2433 binary_path: P,
2434 html_path: P,
2435 project_name: &str,
2436) -> Result<(), BinaryExportError> {
2437 convert_binary_to_html(binary_path, html_path, project_name)
2438}