memscope_rs/export/binary/
html_converter.rs

1//! Binary to HTML conversion functionality
2//! Converts binary memscope files to HTML reports using clean_dashboard.html template
3
4use 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
12/// Convert binary memscope file to HTML report
13pub fn convert_binary_to_html<P: AsRef<Path>>(
14    binary_path: P,
15    html_path: P,
16    project_name: &str,
17) -> Result<(), BinaryExportError> {
18    // Read binary data
19    let mut reader = BinaryReader::new(&binary_path)?;
20    let allocations = reader.read_all()?;
21
22    // Generate statistics
23    let stats = generate_statistics(&allocations);
24
25    // Load binary dashboard template
26    tracing::debug!("Loading binary dashboard template...");
27    let template = load_binary_dashboard_template()?;
28    tracing::debug!("Template loaded, length: {} chars", template.len());
29
30    // Generate HTML content
31    let html_content = generate_html_content(&template, &allocations, &stats, project_name)?;
32
33    // Write HTML file
34    fs::write(&html_path, html_content).map_err(BinaryExportError::Io)?;
35
36    Ok(())
37}
38
39/// Generate statistics from allocations
40fn 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; // Simplified
68    stats.leaked_allocations = leaked_count;
69    stats.leaked_memory = leaked_memory;
70    stats.allocations = allocations.to_vec();
71
72    stats
73}
74
75/// Load binary dashboard template
76fn load_binary_dashboard_template() -> Result<String, BinaryExportError> {
77    // Try to load from templates directory - use binary_dashboard.html for binary conversion
78    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    // Fallback to embedded template
99    Ok(get_embedded_binary_template())
100}
101
102/// Generate HTML content from template and data
103fn generate_html_content(
104    template: &str,
105    allocations: &[AllocationInfo],
106    stats: &MemoryStats,
107    project_name: &str,
108) -> Result<String, BinaryExportError> {
109    // Prepare data for template
110    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    // Replace template placeholders for binary_dashboard.html
115    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    // Smart project name insertion - handle templates without {{PROJECT_NAME}} placeholder
122    if html.contains("{{PROJECT_NAME}}") {
123        html = html.replace("{{PROJECT_NAME}}", project_name);
124    } else {
125        // Insert project name into title and header intelligently
126        // Replace title
127        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]; // Include "<title>"
131                let after = &html[title_end..];
132                html = format!("{before}{project_name} - Memory Analysis Dashboard{after}",);
133            }
134        }
135
136        // Replace main header h1 - look for "MemScope Memory Analysis Dashboard"
137        html = html.replace(
138            "MemScope Memory Analysis Dashboard",
139            &format!("{project_name} - Memory Analysis Report"),
140        );
141
142        // Add stats-grid and allocations-table classes for test compatibility
143        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    // Replace BINARY_DATA placeholder in binary_dashboard.html
161    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        // Fallback: try to find and replace window.analysisData assignment
166        if let Some(start) = html.find("window.analysisData = {") {
167            if let Some(end) = html[start..].find("};") {
168                let end_pos = start + end + 2; // Include the "};"
169                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            // Last resort: try other common placeholders
181            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    // Replace statistics placeholders
189    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    // Replace additional binary dashboard placeholders
212    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    // Replace any remaining template variables to prevent errors
217    html = html.replace("{{ json_data }}", &allocation_data);
218    html = html.replace("{{json_data}}", &allocation_data);
219
220    // Fix any remaining references to JS_CONTENT in comments and code
221    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    // Inject safety risk data into the HTML for the unsafeTable
229    // Find the DOMContentLoaded event listener and inject safety risk data before it
230    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    // Find and modify the existing initialization to include safety risk loading
285    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    // Also try to inject into any existing initialization functions
305    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
319/// Prepare allocation data for JavaScript in binary_dashboard.html format
320fn 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        // Add improve.md extensions if available
339        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    // Generate comprehensive data structure for all dashboard modules
362    let (lifetime_data, complex_types, unsafe_ffi, performance_data) =
363        generate_enhanced_data(&allocation_data);
364
365    // Format data in the structure expected by binary_dashboard.html
366    let data_structure = json!({
367        "memory_analysis": {
368            "allocations": allocation_data.clone()
369        },
370        "allocations": allocation_data,  // Direct access for compatibility
371        "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
387/// Prepare statistics data for JavaScript
388fn 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
407/// Format memory size in human-readable format
408fn 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
425/// Get embedded binary dashboard template - use actual working_dashboard.html content
426fn get_embedded_binary_template() -> String {
427    // Force read the actual working_dashboard.html template
428    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    // Fallback to a simplified version if file not found
442    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
615/// Generate enhanced data for all dashboard modules - match exact JSON structure
616fn 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    // 1. Lifetime Analysis - Match large_scale_user_lifetime.json structure
625    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    // 2. Complex Types - Match large_scale_user_complex_types.json structure
644    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            // Include all types with generics or smart pointers
649            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    // 3. Unsafe/FFI - Match large_scale_user_unsafe_ffi.json structure EXACTLY
682    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    // 4. Performance - Match large_scale_user_performance.json structure
717    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
749/// Extract generic parameters from type name
750fn 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
763/// Calculate complexity score for a type
764fn calculate_complexity_score(type_name: &str) -> u32 {
765    let mut score = 1;
766
767    // Count angle brackets for generics
768    score += type_name.matches('<').count() as u32 * 2;
769
770    // Add score for smart pointers
771    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    // Add score for raw pointers
785    if type_name.contains("*mut") || type_name.contains("*const") {
786        score += 5;
787    }
788
789    score
790}
791
792/// Calculate memory density for performance analysis
793fn calculate_memory_density(size: u64) -> f64 {
794    // Simple heuristic: smaller allocations have higher density
795    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
806/// Calculate cache efficiency for performance analysis
807fn calculate_cache_efficiency(size: u64) -> f64 {
808    // Cache line is typically 64 bytes
809    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
815/// Generate ownership transfer points for lifetime analysis
816fn generate_ownership_transfer_points(allocation: &serde_json::Value) -> Vec<serde_json::Value> {
817    let mut transfer_points = Vec::new();
818
819    // Check if it's a clone
820    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    // Check for borrow events
847    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
860/// Generate comprehensive dashboard JavaScript code
861fn 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
2325/// Prepare safety risk data for JavaScript
2326fn prepare_safety_risk_data(allocations: &[AllocationInfo]) -> Result<String, BinaryExportError> {
2327    let mut safety_risks = Vec::new();
2328
2329    // Analyze allocations for potential safety risks
2330    for allocation in allocations {
2331        // Check for potential unsafe operations based on allocation patterns
2332
2333        // 1. Large allocations that might indicate unsafe buffer operations
2334        if allocation.size > 1024 * 1024 {
2335            // > 1MB
2336            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        // 2. Leaked memory indicates potential unsafe operations
2347        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        // 3. High borrow count might indicate unsafe sharing
2359        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        // 4. Raw pointer types indicate direct unsafe operations
2371        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            // 5. FFI-related types
2384            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        // 6. Very short-lived allocations might indicate unsafe temporary operations
2401        if let Some(lifetime_ms) = allocation.lifetime_ms {
2402            if lifetime_ms < 1 {
2403                // Less than 1ms
2404                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 no risks found, add a placeholder to show the system is working
2417    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
2431/// Public API function for binary to HTML conversion
2432pub 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}