1use std::fs;
2use std::sync::OnceLock;
3
4static SCRIPT_JS_CACHE: OnceLock<String> = OnceLock::new();
5
6pub fn get_embedded_script_js() -> &'static str {
7 SCRIPT_JS_CACHE.get_or_init(|| {
8 if let Ok(external_path) = std::env::var("MEMSCOPE_JS_TEMPLATE") {
10 if let Ok(content) = fs::read_to_string(&external_path) {
11 println!("๐ Loaded external JS template: {}", external_path);
12 return content;
13 }
14 }
15
16 EMBEDDED_SCRIPT_JS.to_string()
18 })
19}
20
21pub const EMBEDDED_SCRIPT_JS: &str = r#"
22// MemScope Dashboard JavaScript - Clean rendering for clean_dashboard.html
23// This file contains comprehensive functions for memory analysis dashboard
24
25// Global data store - will be populated by HTML template
26window.analysisData = window.analysisData || {};
27
28// FFI dashboard render style: 'svg' to mimic Rust SVG dashboard, 'cards' for card-based UI
29const FFI_STYLE = 'svg';
30
31// Initialize all dashboard components - Clean layout
32function initCleanTemplate() {
33 console.log('๐ Initializing MemScope Dashboard...');
34 console.log('๐ Available data:', Object.keys(window.analysisData||{}));
35 const data = window.analysisData || {};
36
37 // KPI
38 updateKPICards(data);
39
40 // Memory by type (Chart.js)
41 const typeChartEl = document.getElementById('typeChart');
42 if (typeChartEl) {
43 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
44 const byType = {};
45 for (const a of allocs) { const t=a.type_name||'Unknown'; byType[t]=(byType[t]||0)+(a.size||0); }
46 const top = Object.entries(byType).sort((a,b)=>b[1]-a[1]).slice(0,10);
47 if (top.length>0) {
48 const ctx = typeChartEl.getContext('2d');
49 if (window.chartInstances['clean-type']) window.chartInstances['clean-type'].destroy();
50 window.chartInstances['clean-type'] = new Chart(ctx, {
51 type:'bar',
52 data:{ labels: top.map(x=>x[0]), datasets:[{ label:'Bytes', data: top.map(x=>x[1]), backgroundColor:'#3b82f6' }] },
53 options:{ responsive:true, plugins:{legend:{display:false}}, scales:{y:{beginAtZero:true}} }
54 });
55 const legend = document.getElementById('typeLegend');
56 if (legend) legend.innerHTML = top.map(([n,v])=>`<span class="pill">${n}: ${formatBytes(v)}</span>`).join(' ');
57 }
58 }
59
60 // Timeline (Chart.js)
61 const timelineEl = document.getElementById('timelineChart');
62 if (timelineEl) {
63 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
64 const rawTimeline = (data.memory_analysis && data.memory_analysis.memory_timeline) || [];
65 let points = [];
66 if (rawTimeline.length) {
67 points = rawTimeline.map((p,i)=>({ x:i, y:(p.memory_usage||0) }));
68 } else {
69 const sorted = allocs.slice().sort((a,b)=>(a.timestamp_alloc||0)-(b.timestamp_alloc||0));
70 let cum=0; const step=Math.max(1, Math.floor(sorted.length/50));
71 for(let i=0;i<sorted.length;i+=step){ cum += sorted[i].size||0; points.push({x:i, y:cum}); }
72 }
73 if (points.length>1) {
74 const ctx = timelineEl.getContext('2d');
75 if (window.chartInstances['clean-timeline']) window.chartInstances['clean-timeline'].destroy();
76 window.chartInstances['clean-timeline'] = new Chart(ctx, {
77 type:'line',
78 data:{ labels: points.map(p=>p.x), datasets:[{ label:'Cumulative', data: points.map(p=>p.y), borderColor:'#ef4444', backgroundColor:'rgba(239,68,68,0.1)', fill:true, tension:0.25 }] },
79 options:{ responsive:true, plugins:{legend:{display:false}}, scales:{y:{beginAtZero:true}} }
80 });
81 }
82 }
83
84 // Treemap
85 const treemapEl = document.getElementById('treemap');
86 if (treemapEl) {
87 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
88 treemapEl.innerHTML = createTreemapVisualization(allocs);
89 }
90
91 // Growth
92 const growthEl = document.getElementById('growth');
93 if (growthEl) {
94 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
95 const total = allocs.reduce((s,a)=>s+(a.size||0),0);
96 growthEl.innerHTML = createAdvancedGrowthTrendVisualization(allocs, Math.max(1,total));
97 }
98
99 // Lifetimes (top 10)
100 const lifetimesEl = document.getElementById('lifetimes');
101 if (lifetimesEl) {
102 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
103 const top = allocs.filter(a=>a.var_name && a.var_name!=='unknown').sort((a,b)=>(b.size||0)-(a.size||0)).slice(0,10);
104 lifetimesEl.innerHTML = top.map(a=>`<div class="flex items-center justify-between py-1 border-b">
105 <div class="text-xs font-medium">${a.var_name}</div>
106 <div class="text-xs text-gray-500">${formatBytes(a.size||0)}</div>
107 </div>`).join('');
108 }
109
110 // Update memory allocation table
111 updateAllocationsTable(data);
112
113 // Update unsafe risk table
114 updateUnsafeTable(data);
115
116 // Initialize all charts and visualizations
117 initCharts(data);
118
119 // Initialize lifecycle visualization
120 initLifetimeVisualization(data);
121
122 // Complex types
123 const complexSummary = document.getElementById('complexSummary');
124 if (complexSummary) {
125 const ct = data.complex_types || {};
126 const s = ct.summary || {};
127 const items = [
128 {label:'Complex Types', val: s.total_complex_types||0},
129 {label:'Smart Pointers', val: s.smart_pointers_count||0},
130 {label:'Collections', val: s.collections_count||0},
131 {label:'Generic Types', val: s.generic_types_count||s.generic_type_count||0},
132 ];
133 complexSummary.innerHTML = items.map(x=>`<div class="pill">${x.label}: ${x.val}</div>`).join('');
134 document.getElementById('complexSmart')?.replaceChildren();
135 document.getElementById('complexCollections')?.replaceChildren();
136 document.getElementById('complexGenerics')?.replaceChildren();
137 }
138
139 // Variable relationships
140 const graphEl = document.getElementById('graph');
141 if (graphEl) {
142 // reuse our D3 relationship graph init but mount into #graph
143 const container = document.createElement('div');
144 container.id = 'variable-graph-container';
145 container.style.width = '100%';
146 container.style.height = '260px';
147 graphEl.appendChild(container);
148 try { initVariableGraph(); } catch(e) { console.warn('variable graph init failed', e); }
149 }
150
151 // Security violations
152 const secEl = document.getElementById('security');
153 if (secEl) {
154 const root = data.unsafe_ffi || {};
155 const list = root.security_hotspots || root.unsafe_reports || [];
156 secEl.innerHTML = (list||[]).slice(0,12).map(h=>{
157 const score = h.risk_score || h.risk_assessment?.confidence_score || 0;
158 const level = h.risk_level || h.risk_assessment?.risk_level || 'Unknown';
159 const width = Math.min(100, Math.round((score||0)*10));
160 return `<div class="card">
161 <div class="text-sm font-semibold">${h.location||h.report_id||'Unknown'}</div>
162 <div class="text-xs text-gray-500">${h.description||h.source?.type||''}</div>
163 <div class="mt-2 bg-red-100 h-2 rounded"><div style="width:${width}%; background:#ef4444; height:100%" class="rounded"></div></div>
164 <div class="text-xs text-gray-500 mt-1">Risk: ${level} (${score})</div>
165 </div>`;
166 }).join('') || '<div class="muted">No security violations</div>';
167 }
168}
169function initializeDashboard() {
170 console.log('๐ Initializing MemScope dashboard...');
171 console.log('๐ Available data:', Object.keys(window.analysisData || {}));
172
173 // Initialize theme system first
174 initThemeToggle();
175
176 // Initialize enhanced dashboard with comprehensive data
177 initEnhancedSummaryStats();
178
179 // Initialize all components
180 initSummaryStats();
181 initCharts();
182 initMemoryUsageAnalysis();
183 initLifetimeVisualization();
184 initFFIVisualization();
185 initMemoryFragmentation();
186 initMemoryGrowthTrends();
187 initAllocationsTable();
188 initVariableGraph();
189}
190
191// Initialize theme toggle functionality
192function initThemeToggle() {
193 const themeToggle = document.getElementById('theme-toggle');
194 const html = document.documentElement;
195
196 // Check for saved theme preference or default to light mode
197 const savedTheme = localStorage.getItem('memscope-theme') || 'light';
198
199 console.log('๐จ Initializing theme system, saved theme:', savedTheme);
200
201 // Apply initial theme
202 applyTheme(savedTheme === 'dark');
203
204 if (themeToggle) {
205 themeToggle.addEventListener('click', () => {
206 const isDark = html.classList.contains('dark');
207
208 if (isDark) {
209 applyTheme(false);
210 localStorage.setItem('memscope-theme', 'light');
211 console.log('๐จ Theme switched to: light mode');
212 } else {
213 applyTheme(true);
214 localStorage.setItem('memscope-theme', 'dark');
215 console.log('๐จ Theme switched to: dark mode');
216 }
217 });
218
219 console.log('โ
Theme toggle initialized successfully');
220 } else {
221 console.warn('โ ๏ธ Theme toggle button not found');
222 }
223}
224
225// Apply theme to all modules
226function applyTheme(isDark) {
227 const html = document.documentElement;
228 const body = document.body;
229
230 if (isDark) {
231 html.classList.remove('light');
232 html.classList.add('dark');
233 body.classList.add('dark');
234 } else {
235 html.classList.remove('dark');
236 html.classList.add('light');
237 body.classList.remove('dark');
238 }
239
240 // Force immediate repaint
241 html.style.display = 'none';
242 html.offsetHeight; // Trigger reflow
243 html.style.display = '';
244
245 // Apply theme to all modules that need explicit dark mode support
246 applyThemeToAllModules(isDark);
247
248 // Update theme toggle button icon
249 updateThemeToggleIcon(isDark);
250
251 // Destroy existing charts before reinitializing
252 destroyAllCharts();
253
254 // Reinitialize charts to apply theme changes
255 setTimeout(() => {
256 initCharts();
257 initFFIRiskChart();
258 }, 100);
259}
260
261// Update theme toggle button icon
262function updateThemeToggleIcon(isDark) {
263 const themeToggle = document.getElementById('theme-toggle');
264 if (themeToggle) {
265 const icon = themeToggle.querySelector('i');
266 if (icon) {
267 if (isDark) {
268 icon.className = 'fa fa-sun';
269 } else {
270 icon.className = 'fa fa-moon';
271 }
272 }
273 }
274}
275
276// Global chart instances storage
277window.chartInstances = {};
278
279// Destroy all existing charts
280function destroyAllCharts() {
281 Object.keys(window.chartInstances).forEach(chartId => {
282 if (window.chartInstances[chartId]) {
283 window.chartInstances[chartId].destroy();
284 delete window.chartInstances[chartId];
285 }
286 });
287}
288
289// Apply theme to specific modules
290function applyThemeToAllModules(isDark) {
291 const modules = [
292 'memory-usage-analysis',
293 'generic-types-details',
294 'variable-relationship-graph',
295 'complex-type-analysis',
296 'memory-optimization-recommendations',
297 'unsafe-ffi-data'
298 ];
299
300 modules.forEach(moduleId => {
301 const module = document.getElementById(moduleId);
302 if (module) {
303 module.classList.toggle('dark', isDark);
304 }
305 });
306
307 // Also apply to any table elements that might need it
308 const tables = document.querySelectorAll('table');
309 tables.forEach(table => {
310 table.classList.toggle('dark', isDark);
311 });
312
313 // Apply to any chart containers
314 const chartContainers = document.querySelectorAll('canvas');
315 chartContainers.forEach(container => {
316 if (container.parentElement) {
317 container.parentElement.classList.toggle('dark', isDark);
318 }
319 });
320}
321
322// Initialize summary statistics
323function initSummaryStats() {
324 console.log('๐ Initializing summary stats...');
325
326 const data = window.analysisData;
327
328 // Update complex types count
329 const complexTypesCount = data.complex_types?.summary?.total_complex_types || 0;
330 updateElement('total-complex-types', complexTypesCount);
331
332 // Update total allocations
333 const totalAllocations = data.memory_analysis?.allocations?.length || 0;
334 updateElement('total-allocations', totalAllocations);
335
336 // Update generic types count
337 const genericTypeCount = data.complex_types?.summary?.generic_type_count || 0;
338 updateElement('generic-type-count', genericTypeCount);
339
340 // Update unsafe FFI count
341 const unsafeFFICount = data.unsafe_ffi?.enhanced_ffi_data?.length || 0;
342 updateElement('unsafe-ffi-count', unsafeFFICount);
343
344 // Update category counts
345 const smartPointersCount = data.complex_types?.categorized_types?.smart_pointers?.length || 0;
346 const collectionsCount = data.complex_types?.categorized_types?.collections?.length || 0;
347 const primitivesCount = 0; // Calculate from data if available
348
349 updateElement('smart-pointers-count', smartPointersCount);
350 updateElement('collections-count', collectionsCount);
351 updateElement('primitives-count', primitivesCount);
352}
353
354// Initialize charts - simplified
355function initCharts() {
356 console.log('๐ Initializing charts...');
357
358 // Initialize memory distribution chart
359 initMemoryDistributionChart();
360
361 // Initialize allocation size chart
362 initAllocationSizeChart();
363}
364
365
366
367// Initialize memory distribution chart
368function initMemoryDistributionChart() {
369 const ctx = document.getElementById('memory-distribution-chart');
370 if (!ctx) return;
371
372 const allocations = window.analysisData.memory_analysis?.allocations || [];
373 const typeDistribution = {};
374
375 allocations.forEach(alloc => {
376 const type = alloc.type_name || 'System Allocation';
377 typeDistribution[type] = (typeDistribution[type] || 0) + alloc.size;
378 });
379
380 const sortedTypes = Object.entries(typeDistribution)
381 .sort(([, a], [, b]) => b - a)
382 .slice(0, 10);
383
384 const isDark = document.documentElement.classList.contains('dark');
385
386 // Destroy existing chart if it exists
387 if (window.chartInstances['memory-distribution-chart']) {
388 window.chartInstances['memory-distribution-chart'].destroy();
389 }
390
391 window.chartInstances['memory-distribution-chart'] = new Chart(ctx, {
392 type: 'bar',
393 data: {
394 labels: sortedTypes.map(([type]) => formatTypeName(type)),
395 datasets: [{
396 label: 'Memory Usage (bytes)',
397 data: sortedTypes.map(([, size]) => size),
398 backgroundColor: '#3b82f6'
399 }]
400 },
401 options: {
402 responsive: true,
403 maintainAspectRatio: false,
404 plugins: {
405 legend: {
406 labels: {
407 color: isDark ? '#ffffff' : '#374151'
408 }
409 }
410 },
411 scales: {
412 x: {
413 ticks: {
414 color: isDark ? '#d1d5db' : '#6b7280'
415 },
416 grid: {
417 color: isDark ? '#374151' : '#e5e7eb'
418 }
419 },
420 y: {
421 beginAtZero: true,
422 ticks: {
423 color: isDark ? '#d1d5db' : '#6b7280',
424 callback: function (value) {
425 return formatBytes(value);
426 }
427 },
428 grid: {
429 color: isDark ? '#374151' : '#e5e7eb'
430 }
431 }
432 }
433 }
434 });
435}
436
437// Initialize allocation size chart
438function initAllocationSizeChart() {
439 const ctx = document.getElementById('allocation-size-chart');
440 if (!ctx) return;
441
442 const allocations = window.analysisData.memory_analysis?.allocations || [];
443 const sizeDistribution = {
444 'Tiny (< 64B)': 0,
445 'Small (64B - 1KB)': 0,
446 'Medium (1KB - 64KB)': 0,
447 'Large (64KB - 1MB)': 0,
448 'Huge (> 1MB)': 0
449 };
450
451 allocations.forEach(alloc => {
452 const size = alloc.size || 0;
453 if (size < 64) sizeDistribution['Tiny (< 64B)']++;
454 else if (size < 1024) sizeDistribution['Small (64B - 1KB)']++;
455 else if (size < 65536) sizeDistribution['Medium (1KB - 64KB)']++;
456 else if (size < 1048576) sizeDistribution['Large (64KB - 1MB)']++;
457 else sizeDistribution['Huge (> 1MB)']++;
458 });
459
460 // Destroy existing chart if it exists
461 if (window.chartInstances['allocation-size-chart']) {
462 window.chartInstances['allocation-size-chart'].destroy();
463 }
464
465 window.chartInstances['allocation-size-chart'] = new Chart(ctx, {
466 type: 'pie',
467 data: {
468 labels: Object.keys(sizeDistribution),
469 datasets: [{
470 data: Object.values(sizeDistribution),
471 backgroundColor: ['#10b981', '#3b82f6', '#f59e0b', '#ef4444', '#7c2d12']
472 }]
473 },
474 options: {
475 responsive: true,
476 maintainAspectRatio: false,
477 plugins: {
478 legend: {
479 labels: {
480 color: document.documentElement.classList.contains('dark') ? '#ffffff' : '#374151'
481 }
482 }
483 }
484 }
485 });
486}
487
488
489
490// Process memory analysis data with validation and fallback
491function processMemoryAnalysisData(rawData) {
492 if (!rawData || !rawData.memory_analysis) {
493 console.warn('โ ๏ธ No memory analysis data found, generating fallback data');
494 return generateFallbackMemoryData();
495 }
496
497 const memoryData = rawData.memory_analysis;
498 const processedData = {
499 stats: {
500 total_allocations: memoryData.stats?.total_allocations || 0,
501 active_allocations: memoryData.stats?.active_allocations || 0,
502 total_memory: memoryData.stats?.total_memory || 0,
503 active_memory: memoryData.stats?.active_memory || 0
504 },
505 allocations: memoryData.allocations || [],
506 trends: {
507 peak_memory: memoryData.peak_memory || 0,
508 growth_rate: memoryData.growth_rate || 0,
509 fragmentation_score: memoryData.fragmentation_score || 0
510 }
511 };
512
513 // Calculate additional metrics if not present
514 if (processedData.allocations.length > 0) {
515 const totalSize = processedData.allocations.reduce((sum, alloc) => sum + (alloc.size || 0), 0);
516 if (!processedData.stats.total_memory) {
517 processedData.stats.total_memory = totalSize;
518 }
519 if (!processedData.stats.total_allocations) {
520 processedData.stats.total_allocations = processedData.allocations.length;
521 }
522 }
523
524 console.log('โ
Processed memory analysis data:', processedData);
525 return processedData;
526}
527
528// Generate fallback memory data when real data is unavailable
529function generateFallbackMemoryData() {
530 console.log('๐ Generating fallback memory data');
531
532 return {
533 stats: {
534 total_allocations: 0,
535 active_allocations: 0,
536 total_memory: 0,
537 active_memory: 0
538 },
539 allocations: [],
540 trends: {
541 peak_memory: 0,
542 growth_rate: 0,
543 fragmentation_score: 0
544 },
545 isFallback: true
546 };
547}
548
549// Validate memory data structure
550function validateMemoryData(data) {
551 if (!data) return false;
552
553 const hasStats = data.stats && typeof data.stats === 'object';
554 const hasAllocations = Array.isArray(data.allocations);
555
556 return hasStats && hasAllocations;
557}
558
559// Calculate memory statistics from allocations
560function calculateMemoryStatistics(allocations) {
561 if (!Array.isArray(allocations) || allocations.length === 0) {
562 return {
563 totalSize: 0,
564 averageSize: 0,
565 largestAllocation: 0,
566 userAllocations: 0,
567 systemAllocations: 0
568 };
569 }
570
571 const totalSize = allocations.reduce((sum, alloc) => sum + (alloc.size || 0), 0);
572 const averageSize = totalSize / allocations.length;
573 const largestAllocation = Math.max(...allocations.map(alloc => alloc.size || 0));
574
575 const userAllocations = allocations.filter(alloc =>
576 alloc.var_name && alloc.var_name !== 'unknown' &&
577 alloc.type_name && alloc.type_name !== 'unknown'
578 ).length;
579
580 const systemAllocations = allocations.length - userAllocations;
581
582 return {
583 totalSize,
584 averageSize,
585 largestAllocation,
586 userAllocations,
587 systemAllocations
588 };
589}
590
591// Initialize memory usage analysis with enhanced SVG-style visualization
592function initMemoryUsageAnalysis() {
593 const container = document.getElementById('memory-usage-analysis');
594 if (!container) return;
595
596 // Process memory data with validation
597 const memoryData = processMemoryAnalysisData(window.analysisData);
598 const allocations = memoryData.allocations;
599
600 if (allocations.length === 0 || memoryData.isFallback) {
601 container.innerHTML = createEnhancedEmptyState();
602 return;
603 }
604
605 // Calculate comprehensive statistics
606 const stats = calculateMemoryStatistics(allocations);
607 const totalMemory = stats.totalSize;
608
609 const userAllocations = allocations.filter(alloc =>
610 alloc.var_name && alloc.var_name !== 'unknown' &&
611 alloc.type_name && alloc.type_name !== 'unknown'
612 );
613 const systemAllocations = allocations.filter(alloc =>
614 !alloc.var_name || alloc.var_name === 'unknown' ||
615 !alloc.type_name || alloc.type_name === 'unknown'
616 );
617
618 const userMemory = userAllocations.reduce((sum, alloc) => sum + (alloc.size || 0), 0);
619 const systemMemory = systemAllocations.reduce((sum, alloc) => sum + (alloc.size || 0), 0);
620
621 // Create enhanced SVG-style visualization
622 container.innerHTML = createMemoryAnalysisSVG(stats, allocations, userMemory, systemMemory, totalMemory);
623}
624
625// Create enhanced empty state with better styling
626function createEnhancedEmptyState() {
627 return `
628 <div class="h-full flex items-center justify-center">
629 <div class="text-center p-8 bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-800 dark:to-gray-700 rounded-xl border-2 border-dashed border-blue-200 dark:border-gray-600">
630 <div class="mb-4">
631 <svg class="w-16 h-16 mx-auto text-blue-400 dark:text-blue-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
632 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
633 </svg>
634 </div>
635 <h4 class="text-lg font-semibold mb-2 text-gray-800 dark:text-gray-200">Memory Analysis Ready</h4>
636 <p class="text-sm text-gray-600 dark:text-gray-400 mb-2">No memory allocation data found for analysis</p>
637 <p class="text-xs text-gray-500 dark:text-gray-500">Run your application with memory tracking enabled to see detailed analysis</p>
638 </div>
639 </div>
640 `;
641}
642
643// Create comprehensive SVG-style memory analysis visualization inspired by the memoryAnalysis.svg
644function createMemoryAnalysisSVG(stats, allocations, userMemory, systemMemory, totalMemory) {
645 const userPercentage = totalMemory > 0 ? (userMemory / totalMemory * 100) : 0;
646 const systemPercentage = totalMemory > 0 ? (systemMemory / totalMemory * 100) : 0;
647
648 // Calculate comprehensive efficiency metrics
649 const efficiency = totalMemory > 0 ? Math.min(100, (userMemory / totalMemory * 100)) : 0;
650 const reclamationRate = allocations.length > 0 ? Math.min(100, ((allocations.filter(a => a.timestamp_dealloc).length / allocations.length) * 100)) : 0;
651 const fragmentation = Math.min(100, (allocations.length / Math.max(1, totalMemory / 1024)) * 10);
652
653 // Advanced size distribution analysis
654 const sizeDistribution = {
655 tiny: allocations.filter(a => a.size < 64).length,
656 small: allocations.filter(a => a.size >= 64 && a.size < 1024).length,
657 medium: allocations.filter(a => a.size >= 1024 && a.size < 65536).length,
658 large: allocations.filter(a => a.size >= 65536).length
659 };
660
661 // Calculate median and P95 sizes
662 const sizes = allocations.map(a => a.size || 0).sort((a, b) => a - b);
663 const medianSize = sizes.length > 0 ? sizes[Math.floor(sizes.length / 2)] : 0;
664 const p95Size = sizes.length > 0 ? sizes[Math.floor(sizes.length * 0.95)] : 0;
665
666 return `
667 <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden">
668 <!-- Header with gradient background -->
669 <div class="bg-gradient-to-r from-blue-600 to-purple-600 text-white p-6">
670 <div class="text-center">
671 <h2 class="text-3xl font-bold mb-2">Rust Memory Usage Analysis</h2>
672 <p class="text-blue-100 uppercase tracking-wider text-sm">Key Performance Metrics</p>
673 </div>
674 </div>
675
676 <div class="p-6">
677 <!-- Key Performance Metrics Grid -->
678 <div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-4 mb-8">
679 ${createAdvancedMetricCard('Active Memory', formatBytes(userMemory), Math.round(userPercentage), '#3498db', 'MEDIUM')}
680 ${createAdvancedMetricCard('Peak Memory', formatBytes(totalMemory), 100, '#e74c3c', 'HIGH')}
681 ${createAdvancedMetricCard('Active Allocs', allocations.length, 100, '#2ecc71', 'HIGH')}
682 ${createAdvancedMetricCard('Reclamation', reclamationRate.toFixed(1) + '%', Math.round(reclamationRate), '#f39c12', reclamationRate > 70 ? 'OPTIMAL' : 'MEDIUM')}
683 ${createAdvancedMetricCard('Efficiency', efficiency.toFixed(1) + '%', Math.round(efficiency), '#9b59b6', efficiency > 70 ? 'OPTIMAL' : 'MEDIUM')}
684 ${createAdvancedMetricCard('Median Size', formatBytes(medianSize), Math.min(100, medianSize / 1024), '#1abc9c', medianSize < 100 ? 'OPTIMAL' : 'MEDIUM')}
685 ${createAdvancedMetricCard('P95 Size', formatBytes(p95Size), Math.min(100, p95Size / 1024), '#e67e22', p95Size < 1024 ? 'OPTIMAL' : 'MEDIUM')}
686 ${createAdvancedMetricCard('Fragmentation', fragmentation.toFixed(1) + '%', Math.round(fragmentation), '#95a5a6', fragmentation < 30 ? 'OPTIMAL' : 'MEDIUM')}
687 </div>
688
689
690 <!-- Memory Usage by Type - Enhanced Treemap -->
691 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6 mb-8 border border-gray-200 dark:border-gray-600">
692 <h3 class="text-xl font-semibold mb-4 text-gray-800 dark:text-white text-center">Memory Usage by Type - Treemap Visualization</h3>
693 <div class="bg-gray-100 dark:bg-gray-600 rounded-lg p-4 h-64 relative overflow-hidden">
694 ${createAdvancedTreemapVisualization(allocations, totalMemory)}
695 </div>
696 <div class="mt-4 grid grid-cols-3 gap-4 text-xs">
697 <div class="flex items-center">
698 <div class="w-3 h-3 bg-blue-500 rounded mr-2"></div>
699 <span class="text-gray-600 dark:text-gray-300">Collections</span>
700 </div>
701 <div class="flex items-center">
702 <div class="w-3 h-3 bg-green-500 rounded mr-2"></div>
703 <span class="text-gray-600 dark:text-gray-300">Basic Types</span>
704 </div>
705 <div class="flex items-center">
706 <div class="w-3 h-3 bg-gray-500 rounded mr-2"></div>
707 <span class="text-gray-600 dark:text-gray-300">System</span>
708 </div>
709 </div>
710 </div>
711
712 <!-- Advanced Analysis Grid -->
713 <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
714 <!-- Memory Fragmentation Analysis -->
715 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6 border border-gray-200 dark:border-gray-600">
716 <h3 class="text-xl font-semibold mb-4 text-gray-800 dark:text-white text-center">Memory Fragmentation Analysis</h3>
717 <div class="space-y-4">
718 ${createAdvancedFragmentationBar('Tiny (0-64B)', sizeDistribution.tiny, allocations.length, '#27ae60')}
719 ${createAdvancedFragmentationBar('Small (65B-1KB)', sizeDistribution.small, allocations.length, '#f39c12')}
720 ${createAdvancedFragmentationBar('Medium (1KB-64KB)', sizeDistribution.medium, allocations.length, '#e74c3c')}
721 ${createAdvancedFragmentationBar('Large (>64KB)', sizeDistribution.large, allocations.length, '#8e44ad')}
722 </div>
723 </div>
724
725 <!-- Call Stack Analysis -->
726 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6 border border-gray-200 dark:border-gray-600">
727 <h3 class="text-xl font-semibold mb-4 text-gray-800 dark:text-white text-center">Call Stack Analysis</h3>
728 <div class="space-y-3 max-h-64 overflow-y-auto">
729 ${createCallStackAnalysis(allocations)}
730 </div>
731 </div>
732 </div>
733
734 <!-- Memory Statistics Summary -->
735 <div class="mt-8 bg-gray-50 dark:bg-gray-700 rounded-lg p-6 border border-gray-200 dark:border-gray-600">
736 <h3 class="text-xl font-semibold mb-4 text-gray-800 dark:text-white text-center">Memory Statistics</h3>
737 <div class="grid grid-cols-3 gap-4 text-sm text-center">
738 <div>
739 <span class="text-gray-600 dark:text-gray-400">Peak Memory:</span>
740 <span class="font-semibold text-red-600 dark:text-red-400 ml-2">${formatBytes(totalMemory)}</span>
741 </div>
742 <div>
743 <span class="text-gray-600 dark:text-gray-400">Fragmentation:</span>
744 <span class="font-semibold text-orange-600 dark:text-orange-400 ml-2">${fragmentation.toFixed(1)}%</span>
745 </div>
746 <div>
747 <span class="text-gray-600 dark:text-gray-400">Efficiency:</span>
748 <span class="font-semibold text-purple-600 dark:text-purple-400 ml-2">${efficiency.toFixed(1)}%</span>
749 </div>
750 </div>
751 </div>
752
753 <!-- Variable Allocation Timeline -->
754 <div class="mt-8 bg-gray-50 dark:bg-gray-700 rounded-lg p-6 border border-gray-200 dark:border-gray-600">
755 <h3 class="text-xl font-semibold mb-4 text-gray-800 dark:text-white text-center">Variable Allocation Timeline</h3>
756 <div class="space-y-3 max-h-64 overflow-y-auto">
757 ${createVariableAllocationTimeline(allocations)}
758 </div>
759 </div>
760 </div>
761 </div>
762 `;
763}
764
765// Create metric card with circular progress indicator
766function createMetricCard(title, value, percentage, color, status) {
767 const circumference = 2 * Math.PI * 25;
768 const strokeDasharray = circumference;
769 const strokeDashoffset = circumference - (percentage / 100) * circumference;
770
771 const statusColors = {
772 'OPTIMAL': '#27ae60',
773 'MEDIUM': '#f39c12',
774 'HIGH': '#e74c3c'
775 };
776
777 return `
778 <div class="bg-white dark:bg-gray-700 rounded-lg p-4 shadow-sm hover:shadow-md transition-shadow">
779 <div class="flex items-center justify-between">
780 <div class="flex-1">
781 <p class="text-xs font-medium text-gray-600 dark:text-gray-400 uppercase">${title}</p>
782 <p class="text-lg font-bold text-gray-900 dark:text-white">${value}</p>
783 <div class="flex items-center mt-1">
784 <div class="w-2 h-2 rounded-full mr-2" style="background-color: ${statusColors[status]}"></div>
785 <span class="text-xs font-semibold" style="color: ${statusColors[status]}">${status}</span>
786 </div>
787 </div>
788 <div class="relative w-12 h-12">
789 <svg class="w-12 h-12 transform -rotate-90" viewBox="0 0 60 60">
790 <circle cx='30' cy='30' r='25' stroke='#e5e7eb' stroke-width='6' fill='none' class='dark:stroke-gray-600'/>
791 <circle cx='30' cy='30' r='25' stroke='${color}' stroke-width='6' fill='none'
792 stroke-dasharray='${strokeDasharray}' stroke-dashoffset='${strokeDashoffset}'
793 stroke-linecap='round' class='transition-all duration-500'/>
794 </svg>
795 <div class="absolute inset-0 flex items-center justify-center">
796 <span class="text-xs font-bold" style="color: ${color}">${Math.round(percentage)}%</span>
797 </div>
798 </div>
799 </div>
800 </div>
801 `;
802}
803
804// Create timeline visualization
805function createTimelineVisualization(allocations) {
806 if (allocations.length === 0) return '<div class="flex items-center justify-center h-full text-gray-400">No timeline data</div>';
807
808 const sortedAllocs = allocations.sort((a, b) => (a.timestamp_alloc || 0) - (b.timestamp_alloc || 0));
809 const minTime = sortedAllocs[0]?.timestamp_alloc || 0;
810 const maxTime = sortedAllocs[sortedAllocs.length - 1]?.timestamp_alloc || minTime + 1;
811 const timeRange = maxTime - minTime || 1;
812
813 return sortedAllocs.slice(0, 20).map((alloc, index) => {
814 const position = ((alloc.timestamp_alloc - minTime) / timeRange) * 100;
815 const height = Math.min(80, Math.max(4, (alloc.size / 1024) * 20));
816 const color = alloc.var_name && alloc.var_name !== 'unknown' ? '#3498db' : '#95a5a6';
817
818 return `
819 <div class="absolute bottom-0 bg-opacity-80 rounded-t transition-all hover:bg-opacity-100"
820 style="left: ${position}%; width: 4px; height: ${height}%; background-color: ${color};"
821 title="${alloc.var_name || 'System'}: ${formatBytes(alloc.size)}">
822 </div>
823 `;
824 }).join('');
825}
826
827// Create treemap-style visualization
828function createTreemapVisualization(allocations) {
829 const typeGroups = {};
830 allocations.forEach(alloc => {
831 const type = alloc.type_name || 'System';
832 if (!typeGroups[type]) {
833 typeGroups[type] = { count: 0, size: 0 };
834 }
835 typeGroups[type].count++;
836 typeGroups[type].size += alloc.size || 0;
837 });
838
839 const sortedTypes = Object.entries(typeGroups)
840 .sort(([, a], [, b]) => b.size - a.size)
841 .slice(0, 8);
842
843 const totalSize = sortedTypes.reduce((sum, [, data]) => sum + data.size, 0);
844
845 let currentX = 0;
846 return sortedTypes.map(([type, data], index) => {
847 const width = totalSize > 0 ? (data.size / totalSize) * 100 : 12.5;
848 const color = getTypeColor(type, index);
849 const result = `
850 <div class="absolute h-full transition-all hover:brightness-110 cursor-pointer rounded"
851 style="left: ${currentX}%; width: ${width}%; background-color: ${color};"
852 title="${type}: ${formatBytes(data.size)} (${data.count} allocs)">
853 <div class="p-2 h-full flex flex-col justify-center text-white text-xs font-semibold text-center">
854 <div class="truncate">${type.length > 10 ? type.substring(0, 8) + '...' : type}</div>
855 <div class="text-xs opacity-90">${formatBytes(data.size)}</div>
856 </div>
857 </div>
858 `;
859 currentX += width;
860 return result;
861 }).join('');
862}
863
864// Create fragmentation bar
865function createFragmentationBar(label, count, total, color) {
866 const percentage = total > 0 ? (count / total) * 100 : 0;
867 return `
868 <div class="flex items-center justify-between">
869 <span class="text-sm font-medium text-gray-700 dark:text-gray-300 w-24">${label}</span>
870 <div class="flex-1 mx-3">
871 <div class="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-4">
872 <div class="h-4 rounded-full transition-all duration-500"
873 style="width: ${percentage}%; background-color: ${color}"></div>
874 </div>
875 </div>
876 <span class="text-sm font-bold text-gray-900 dark:text-white w-12 text-right">${count}</span>
877 </div>
878 `;
879}
880
881// Create growth trend visualization
882function createGrowthTrendVisualization(allocations) {
883 if (allocations.length < 2) return '<div class="flex items-center justify-center h-full text-gray-400">Insufficient data</div>';
884
885 const sortedAllocs = allocations.sort((a, b) => (a.timestamp_alloc || 0) - (b.timestamp_alloc || 0));
886 const points = [];
887 let cumulativeSize = 0;
888
889 sortedAllocs.forEach((alloc, index) => {
890 cumulativeSize += alloc.size || 0;
891 if (index % Math.max(1, Math.floor(sortedAllocs.length / 10)) === 0) {
892 points.push(cumulativeSize);
893 }
894 });
895
896 const maxSize = Math.max(...points);
897
898 return points.map((size, index) => {
899 const x = (index / (points.length - 1)) * 100;
900 const y = 100 - (size / maxSize) * 80;
901
902 return `
903 <div class="absolute w-2 h-2 bg-green-500 rounded-full transform -translate-x-1 -translate-y-1"
904 style="left: ${x}%; top: ${y}%"
905 title="Memory: ${formatBytes(size)}">
906 </div>
907 ${index > 0 ? `
908 <div class="absolute h-0.5 bg-green-500"
909 style="left: ${((index - 1) / (points.length - 1)) * 100}%;
910 top: ${100 - (points[index - 1] / maxSize) * 80}%;
911 width: ${(100 / (points.length - 1))}%;
912 transform: rotate(${Math.atan2(y - (100 - (points[index - 1] / maxSize) * 80), 100 / (points.length - 1)) * 180 / Math.PI}deg);
913 transform-origin: left center;">
914 </div>
915 ` : ''}
916 `;
917 }).join('');
918}
919
920// Get color for type visualization
921function getTypeColor(type, index) {
922 const colors = [
923 '#3498db', '#e74c3c', '#2ecc71', '#f39c12',
924 '#9b59b6', '#1abc9c', '#e67e22', '#95a5a6'
925 ];
926
927 if (type.toLowerCase().includes('vec')) return '#3498db';
928 if (type.toLowerCase().includes('string')) return '#f39c12';
929 if (type.toLowerCase().includes('hash')) return '#e74c3c';
930 if (type.toLowerCase().includes('btree')) return '#2ecc71';
931
932 return colors[index % colors.length];
933}
934
935// Create advanced metric card with enhanced styling
936function createAdvancedMetricCard(title, value, percentage, color, status) {
937 const circumference = 2 * Math.PI * 20;
938 const strokeDasharray = circumference;
939 const strokeDashoffset = circumference - (percentage / 100) * circumference;
940
941 const statusColors = {
942 'OPTIMAL': '#27ae60',
943 'MEDIUM': '#f39c12',
944 'HIGH': '#e74c3c'
945 };
946
947 return `
948 <div class="bg-white dark:bg-gray-700 rounded-lg p-3 shadow-sm hover:shadow-md transition-all border border-gray-200 dark:border-gray-600">
949 <div class="flex flex-col items-center">
950 <div class="relative w-10 h-10 mb-2">
951 <svg class="w-10 h-10 transform -rotate-90" viewBox="0 0 50 50">
952 <circle cx='25' cy='25' r='20' stroke='#e5e7eb' stroke-width='4' fill='none' class='dark:stroke-gray-600'/>
953 <circle cx='25' cy='25' r='20' stroke='${color}' stroke-width='4' fill='none'
954 stroke-dasharray='${strokeDasharray}' stroke-dashoffset='${strokeDashoffset}'
955 stroke-linecap='round' class='transition-all duration-500'/>
956 </svg>
957 <div class="absolute inset-0 flex items-center justify-center">
958 <span class="text-xs font-bold" style="color: ${color}">${Math.round(percentage)}%</span>
959 </div>
960 </div>
961 <p class="text-xs font-medium text-gray-600 dark:text-gray-400 uppercase text-center">${title}</p>
962 <p class="text-sm font-bold text-gray-900 dark:text-white text-center">${value}</p>
963 <div class="flex items-center mt-1">
964 <div class="w-1.5 h-1.5 rounded-full mr-1" style="background-color: ${statusColors[status]}"></div>
965 <span class="text-xs font-semibold" style="color: ${statusColors[status]}">${status}</span>
966 </div>
967 </div>
968 </div>
969 `;
970}
971
972// Create advanced timeline visualization
973function createAdvancedTimelineVisualization(allocations, totalMemory) {
974 if (allocations.length === 0) return '<div class="flex items-center justify-center h-full text-gray-400">No timeline data</div>';
975
976 const sortedAllocs = allocations.sort((a, b) => (a.timestamp_alloc || 0) - (b.timestamp_alloc || 0));
977 const minTime = sortedAllocs[0]?.timestamp_alloc || 0;
978 const maxTime = sortedAllocs[sortedAllocs.length - 1]?.timestamp_alloc || minTime + 1;
979 const timeRange = maxTime - minTime || 1;
980
981 // Group allocations by scope/type for better visualization
982 const scopeGroups = {};
983 sortedAllocs.forEach(alloc => {
984 const scope = alloc.scope_name || (alloc.var_name ? 'User Variables' : 'System');
985 if (!scopeGroups[scope]) scopeGroups[scope] = [];
986 scopeGroups[scope].push(alloc);
987 });
988
989 const scopeColors = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c'];
990 let scopeIndex = 0;
991
992 return Object.entries(scopeGroups).map(([scope, allocs]) => {
993 const color = scopeColors[scopeIndex % scopeColors.length];
994 scopeIndex++;
995 const yOffset = scopeIndex * 25;
996
997 return `
998 <div class="absolute" style="top: ${yOffset}px; left: 0; right: 0; height: 20px;">
999 <div class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-1" style="color: ${color}">
1000 ${scope} (${allocs.length} allocs)
1001 </div>
1002 ${allocs.slice(0, 20).map(alloc => {
1003 const position = ((alloc.timestamp_alloc - minTime) / timeRange) * 100;
1004 const width = Math.max(2, (alloc.size / totalMemory) * 100);
1005
1006 return `
1007 <div class="absolute h-4 rounded opacity-80 hover:opacity-100 transition-opacity cursor-pointer"
1008 style="left: ${position}%; width: ${Math.max(4, width)}px; background-color: ${color};"
1009 title="${alloc.var_name || 'System'}: ${formatBytes(alloc.size)}">
1010 </div>
1011 `;
1012 }).join('')}
1013 </div>
1014 `;
1015 }).join('');
1016}
1017
1018// Create advanced treemap visualization inspired by SVG design
1019function createAdvancedTreemapVisualization(allocations, totalMemory) {
1020 if (allocations.length === 0) return '<div class="flex items-center justify-center h-full text-gray-400">No allocation data</div>';
1021
1022 // Group allocations by type and category
1023 const typeGroups = {};
1024 const categoryGroups = {
1025 'Collections': { types: {}, totalSize: 0, color: '#3498db' },
1026 'Basic Types': { types: {}, totalSize: 0, color: '#27ae60' },
1027 'Smart Pointers': { types: {}, totalSize: 0, color: '#9b59b6' },
1028 'System': { types: {}, totalSize: 0, color: '#95a5a6' }
1029 };
1030
1031 allocations.forEach(alloc => {
1032 const type = alloc.type_name || 'System';
1033 const category = getTypeCategory(type);
1034 const categoryName = getCategoryName(category);
1035
1036 if (!typeGroups[type]) {
1037 typeGroups[type] = { count: 0, size: 0, category: categoryName };
1038 }
1039 typeGroups[type].count++;
1040 typeGroups[type].size += alloc.size || 0;
1041
1042 // Add to category groups
1043 if (!categoryGroups[categoryName].types[type]) {
1044 categoryGroups[categoryName].types[type] = { count: 0, size: 0 };
1045 }
1046 categoryGroups[categoryName].types[type].count++;
1047 categoryGroups[categoryName].types[type].size += alloc.size || 0;
1048 categoryGroups[categoryName].totalSize += alloc.size || 0;
1049 });
1050
1051 // Sort categories by size
1052 const sortedCategories = Object.entries(categoryGroups)
1053 .filter(([, data]) => data.totalSize > 0)
1054 .sort(([, a], [, b]) => b.totalSize - a.totalSize);
1055
1056 let html = '';
1057 let currentY = 0;
1058 const containerHeight = 240;
1059 const padding = 8;
1060
1061 sortedCategories.forEach(([categoryName, categoryData], categoryIndex) => {
1062 const categoryPercentage = (categoryData.totalSize / totalMemory) * 100;
1063 const categoryHeight = Math.max(40, (categoryPercentage / 100) * containerHeight * 0.8);
1064
1065 // Category container with background
1066 html += `
1067 <div class="absolute w-full rounded-lg border-2 border-white shadow-sm transition-all hover:shadow-md"
1068 style="top: ${currentY}px; height: ${categoryHeight}px; background-color: ${categoryData.color}; opacity: 0.15;">
1069 </div>
1070 `;
1071
1072 // Category label
1073 html += `
1074 <div class="absolute left-2 font-bold text-sm z-10"
1075 style="top: ${currentY + 8}px; color: ${categoryData.color};">
1076 ${categoryName} (${categoryPercentage.toFixed(1)}%)
1077 </div>
1078 `;
1079
1080 // Sort types within category
1081 const sortedTypes = Object.entries(categoryData.types)
1082 .sort(([, a], [, b]) => b.size - a.size)
1083 .slice(0, 6); // Limit to top 6 types per category
1084
1085 let currentX = 20;
1086 const typeY = currentY + 25;
1087 const availableWidth = 95; // Leave some margin
1088
1089 sortedTypes.forEach(([type, typeData], typeIndex) => {
1090 const typePercentage = (typeData.size / categoryData.totalSize) * 100;
1091 const typeWidth = Math.max(60, (typePercentage / 100) * availableWidth);
1092 const typeHeight = Math.max(20, categoryHeight - 35);
1093
1094 // Type rectangle with enhanced styling
1095 html += `
1096 <div class="absolute rounded-md border border-white shadow-sm cursor-pointer transition-all hover:brightness-110 hover:scale-105 hover:z-20"
1097 style="left: ${currentX}px; top: ${typeY}px; width: ${typeWidth}px; height: ${typeHeight}px;
1098 background-color: ${categoryData.color}; opacity: 0.9;"
1099 title="${type}: ${formatBytes(typeData.size)} (${typeData.count} allocs, ${typePercentage.toFixed(1)}% of ${categoryName})">
1100 <div class="p-1 h-full flex flex-col justify-center text-white text-xs font-bold text-center">
1101 <div class="truncate text-shadow" style="text-shadow: 1px 1px 2px rgba(0,0,0,0.8);">
1102 ${type.length > 12 ? type.substring(0, 10) + '..' : type}
1103 </div>
1104 <div class="text-xs opacity-90 font-semibold" style="text-shadow: 1px 1px 2px rgba(0,0,0,0.6);">
1105 ${formatBytes(typeData.size)}
1106 </div>
1107 <div class="text-xs opacity-75" style="text-shadow: 1px 1px 2px rgba(0,0,0,0.6);">
1108 (${typePercentage.toFixed(1)}%)
1109 </div>
1110 </div>
1111 </div>
1112 `;
1113
1114 currentX += typeWidth + 4;
1115 });
1116
1117 currentY += categoryHeight + padding;
1118 });
1119
1120 return html;
1121}
1122
1123// Helper function to get category name
1124function getCategoryName(category) {
1125 const categoryMap = {
1126 'collections': 'Collections',
1127 'basic': 'Basic Types',
1128 'smart_pointers': 'Smart Pointers',
1129 'system': 'System'
1130 };
1131 return categoryMap[category] || 'System';
1132}
1133
1134// Create advanced fragmentation bar
1135function createAdvancedFragmentationBar(label, count, total, color) {
1136 const percentage = total > 0 ? (count / total) * 100 : 0;
1137 const barHeight = Math.max(8, (count / total) * 60);
1138
1139 return `
1140 <div class="flex items-center justify-between ">
1141 <div class="flex items-center w-32">
1142 <div class="w-4 rounded mr-3 border border-gray-300 dark:border-gray-500"
1143 style="height: ${barHeight}px; background-color: ${color}"></div>
1144 <span class="text-sm font-medium text-gray-700 dark:text-gray-300">${label}</span>
1145 </div>
1146 <div class="flex-1 mx-3">
1147 <div class="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-3">
1148 <div class="h-3 rounded-full transition-all duration-500"
1149 style="width: ${percentage}%; background-color: ${color}"></div>
1150 </div>
1151 </div>
1152 <span class="text-sm font-bold text-gray-900 dark:text-white w-12 text-right ">${count}</span>
1153 </div>
1154 `;
1155}
1156
1157// Create call stack analysis
1158function createCallStackAnalysis(allocations) {
1159 const userAllocs = allocations.filter(a => a.var_name && a.var_name !== 'unknown');
1160 const systemAllocs = allocations.filter(a => !a.var_name || a.var_name === 'unknown');
1161
1162 const topAllocations = [...userAllocs, ...systemAllocs.slice(0, 3)]
1163 .sort((a, b) => (b.size || 0) - (a.size || 0))
1164 .slice(0, 10);
1165
1166 return topAllocations.map(alloc => {
1167 const isSystem = !alloc.var_name || alloc.var_name === 'unknown';
1168 const color = isSystem ? '#e74c3c' : getTypeColor(alloc.type_name || '', 0);
1169 const radius = Math.min(8, Math.max(3, Math.sqrt((alloc.size || 0) / 100)));
1170
1171 return `
1172 <div class="flex items-center space-x-3 p-2 bg-white dark:bg-gray-600 rounded border ">
1173 <div class="w-4 h-4 rounded-full border-2 border-gray-300 dark:border-gray-500"
1174 style="background-color: ${color}"></div>
1175 <div class="flex-1 min-w-0">
1176 <div class="text-sm font-medium text-gray-900 dark:text-white truncate ">
1177 ${alloc.var_name || 'System/Runtime allocations'}
1178 </div>
1179 <div class="text-xs text-gray-500 dark:text-gray-400 ">
1180 ${alloc.type_name || 'no type info'} โข ${formatBytes(alloc.size || 0)}
1181 </div>
1182 </div>
1183 </div>
1184 `;
1185 }).join('');
1186}
1187
1188// Create advanced growth trend visualization
1189function createAdvancedGrowthTrendVisualization(allocations, totalMemory) {
1190 if (allocations.length < 2) return '<div class="flex items-center justify-center h-full text-gray-400 ">Insufficient data</div>';
1191
1192 const sortedAllocs = allocations.sort((a, b) => (a.timestamp_alloc || 0) - (b.timestamp_alloc || 0));
1193 const points = [];
1194 let cumulativeSize = 0;
1195
1196 sortedAllocs.forEach((alloc, index) => {
1197 cumulativeSize += alloc.size || 0;
1198 if (index % Math.max(1, Math.floor(sortedAllocs.length / 15)) === 0) {
1199 points.push({
1200 x: (index / sortedAllocs.length) * 100,
1201 y: 100 - (cumulativeSize / totalMemory) * 80,
1202 size: cumulativeSize
1203 });
1204 }
1205 });
1206
1207 return `
1208 <!-- Background Grid -->
1209 <div class="absolute inset-0">
1210 ${[20, 40, 60, 80].map(y => `
1211 <div class="absolute w-full border-t border-gray-200 dark:border-gray-500 opacity-30"
1212 style="top: ${y}%"></div>
1213 `).join('')}
1214 </div>
1215
1216 <!-- Growth Line -->
1217 <svg class="absolute inset-0 w-full h-full ">
1218 <polyline
1219 fill="none"
1220 stroke='#27ae60'
1221 stroke-width="3"
1222 stroke-linecap="round"
1223 stroke-linejoin="round"
1224 points="${points.map(p => `${p.x},${p.y}`).join(' ')}"
1225 class="drop-shadow-sm "
1226 />
1227 </svg>
1228
1229 <!-- Data Points -->
1230 ${points.map(point => `
1231 <div class="absolute w-2 h-2 bg-green-500 rounded-full border border-white dark:border-gray-600 transform -translate-x-1/2 -translate-y-1/2 hover:scale-150 transition-transform cursor-pointer "
1232 style="left: ${point.x}%; top: ${point.y}%"
1233 title="Memory: ${formatBytes(point.size)}">
1234 </div>
1235 `).join('')}
1236
1237 <!-- Peak Memory Line -->
1238 <div class="absolute w-full border-t-2 border-red-500 border-dashed opacity-60" style="top: 20%">
1239 <div class="absolute -top-1 right-0 text-xs text-red-500 bg-white dark:bg-gray-600 px-1 rounded ">
1240 Peak: ${formatBytes(totalMemory)}
1241 </div>
1242 </div>
1243 `;
1244}
1245
1246// Create variable allocation timeline
1247function createVariableAllocationTimeline(allocations) {
1248 const userAllocs = allocations.filter(a => a.var_name && a.var_name !== 'unknown')
1249 .sort((a, b) => (a.timestamp_alloc || 0) - (b.timestamp_alloc || 0))
1250 .slice(0, 10);
1251
1252 return userAllocs.map((alloc, index) => {
1253 const color = getTypeColor(alloc.type_name || '', index);
1254
1255 return `
1256 <div class="flex items-center space-x-3 p-2 bg-white dark:bg-gray-600 rounded border ">
1257 <div class="w-3 h-3 rounded-full" style="background-color: ${color}"></div>
1258 <div class="flex-1 min-w-0">
1259 <div class="text-sm font-medium text-gray-900 dark:text-white ">
1260 ${alloc.var_name}
1261 </div>
1262 <div class="text-xs text-gray-500 dark:text-gray-400">
1263 ${alloc.type_name || 'unknown'} โข ${formatBytes(alloc.size || 0)}
1264 </div>
1265 </div>
1266 <div class="text-xs text-gray-500 dark:text-gray-400">
1267 ${new Date(alloc.timestamp_alloc / 1000000).toLocaleTimeString()}
1268 </div>
1269 </div>
1270 `;
1271 }).join('');
1272}
1273
1274// Helper functions for type categorization
1275function getTypeCategory(type) {
1276 if (!type || type === 'System' || type === 'unknown') return 'system';
1277
1278 const typeLower = type.toLowerCase();
1279
1280 // Collections
1281 if (typeLower.includes('vec') || typeLower.includes('hash') || typeLower.includes('btree') ||
1282 typeLower.includes('deque') || typeLower.includes('set') || typeLower.includes('map')) {
1283 return 'collections';
1284 }
1285
1286 // Smart Pointers
1287 if (typeLower.includes('box') || typeLower.includes('rc') || typeLower.includes('arc') ||
1288 typeLower.includes('refcell') || typeLower.includes('cell') || typeLower.includes('weak')) {
1289 return 'smart_pointers';
1290 }
1291
1292 // Basic types (String, primitives, etc.)
1293 return 'basic';
1294}
1295
1296function getCategoryColor(category) {
1297 const colors = {
1298 'collections': '#3498db', // Bright blue
1299 'basic': '#27ae60', // Bright green
1300 'smart_pointers': '#9b59b6', // Purple
1301 'system': '#95a5a6' // Gray
1302 };
1303 return colors[category] || '#95a5a6';
1304}
1305
1306// Initialize allocations table with improved collapsible functionality
1307function initAllocationsTable() {
1308 console.log('๐ Initializing allocations table...');
1309
1310 const tbody = document.getElementById('allocations-table');
1311 const toggleButton = document.getElementById('toggle-allocations');
1312
1313 if (!tbody) {
1314 console.warn('โ ๏ธ Allocations table body not found');
1315 return;
1316 }
1317
1318 const allocations = window.analysisData.memory_analysis?.allocations || [];
1319
1320 if (allocations.length === 0) {
1321 tbody.innerHTML = '<tr><td colspan="5" class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">No allocations found</td></tr>';
1322 if (toggleButton) {
1323 toggleButton.style.display = 'none';
1324 }
1325 return;
1326 }
1327
1328 let isExpanded = false;
1329 const maxInitialRows = 5;
1330
1331 function renderTable(showAll = false) {
1332 console.log(`๐ Rendering table, showAll: ${showAll}, total allocations: ${allocations.length}`);
1333
1334 const displayAllocations = showAll ? allocations : allocations.slice(0, maxInitialRows);
1335
1336 tbody.innerHTML = displayAllocations.map(alloc => `
1337 <tr class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors ">
1338 <td class="px-4 py-2 text-gray-900 dark:text-gray-100 font-mono ">0x${(alloc.ptr ? parseInt(alloc.ptr.toString().replace('0x', ''), 16) : 0).toString(16).padStart(8, '0')}</td>
1339 <td class="px-4 py-2 text-gray-900 dark:text-gray-100">${alloc.var_name || 'System Allocation'}</td>
1340 <td class="px-4 py-2 text-gray-900 dark:text-gray-100">${formatTypeName(alloc.type_name || 'System Allocation')}</td>
1341 <td class="px-4 py-2 text-right text-gray-900 dark:text-gray-100">${formatBytes(alloc.size || 0)}</td>
1342 <td class="px-4 py-2 text-right text-gray-900 dark:text-gray-100">
1343 <span class="px-2 py-1 text-xs rounded-full ${alloc.is_active ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' : 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 '} ">
1344 ${alloc.is_active ? 'Active' : 'Deallocated'}
1345 </span>
1346 </td>
1347 </tr>
1348 `).join('');
1349
1350 if (!showAll && allocations.length > maxInitialRows) {
1351 tbody.innerHTML += `
1352 <tr class="bg-gray-50 dark:bg-gray-700">
1353 <td colspan="5" class="px-4 py-2 text-center text-gray-500 dark:text-gray-400 text-sm">
1354 ... and ${allocations.length - maxInitialRows} more allocations
1355 </td>
1356 </tr>
1357 `;
1358 }
1359 }
1360
1361 // Initial render
1362 renderTable(false);
1363
1364 // Toggle functionality - Fixed event binding
1365 if (toggleButton && allocations.length > maxInitialRows) {
1366 console.log('๐ Setting up toggle button for', allocations.length, 'allocations');
1367
1368 // Clear any existing event listeners and add new one
1369 toggleButton.replaceWith(toggleButton.cloneNode(true));
1370 const newToggleButton = document.getElementById('toggle-allocations');
1371
1372 newToggleButton.addEventListener('click', function (e) {
1373 e.preventDefault();
1374 e.stopPropagation();
1375 console.log('๐ Toggle button clicked, current state:', isExpanded);
1376
1377 isExpanded = !isExpanded;
1378 renderTable(isExpanded);
1379
1380 const icon = newToggleButton.querySelector('i');
1381 const text = newToggleButton.querySelector('span');
1382
1383 if (isExpanded) {
1384 icon.className = 'fa fa-chevron-up mr-1';
1385 text.textContent = 'Show Less';
1386 console.log('๐ Expanded table to show all allocations');
1387 } else {
1388 icon.className = 'fa fa-chevron-down mr-1';
1389 text.textContent = 'Show All';
1390 console.log('๐ Collapsed table to show first', maxInitialRows, 'allocations');
1391 }
1392 });
1393
1394 console.log('โ
Toggle button initialized successfully');
1395 } else if (toggleButton) {
1396 // Hide button if not needed
1397 toggleButton.style.display = 'none';
1398 console.log('๐ Toggle button hidden (not enough data)');
1399 }
1400}
1401
1402// Initialize lifetime visualization from JSON data with collapsible functionality
1403function initLifetimeVisualization() {
1404 console.log('๐ Initializing lifetime visualization...');
1405
1406 // Get lifetime data from various sources (support extended data structure)
1407 let lifetimeData = null;
1408 let lifecycleEvents = [];
1409
1410 // Smart data source selection: merge memory_analysis and complex_types data
1411 let memoryAllocations = window.analysisData.memory_analysis?.allocations || [];
1412 let complexAllocations = window.analysisData.complex_types?.allocations || [];
1413
1414 console.log('๐ Memory analysis allocations:', memoryAllocations.length);
1415 console.log('๐ Complex types allocations:', complexAllocations.length);
1416
1417 // ๅๅนถๆฐๆฎ๏ผไฝฟ็จmemory_analysis็lifetime_ms + complex_types็ๆฉๅฑๅญๆฎต
1418 if (memoryAllocations.length > 0 && complexAllocations.length > 0) {
1419 // Create mapping from pointer to memory analysis data
1420 const memoryMap = new Map();
1421 memoryAllocations.forEach(alloc => {
1422 if (alloc.ptr) {
1423 memoryMap.set(alloc.ptr, alloc);
1424 }
1425 });
1426
1427 // Merge data: complex_types + lifetime_ms from memory_analysis
1428 lifecycleEvents = complexAllocations.map(complexAlloc => {
1429 const memoryAlloc = memoryMap.get(complexAlloc.ptr);
1430 return {
1431 ...complexAlloc,
1432 lifetime_ms: memoryAlloc?.lifetime_ms || null,
1433 timestamp_dealloc: memoryAlloc?.timestamp_dealloc || null
1434 };
1435 });
1436 console.log('๐ Merged allocation data:', lifecycleEvents.length);
1437 } else if (memoryAllocations.length > 0) {
1438 lifecycleEvents = memoryAllocations;
1439 console.log('๐ Using memory analysis data:', lifecycleEvents.length);
1440 } else if (complexAllocations.length > 0) {
1441 lifecycleEvents = complexAllocations;
1442 console.log('๐ Using complex types data:', lifecycleEvents.length);
1443 } else if (window.analysisData.lifetime?.lifecycle_events) {
1444 lifecycleEvents = window.analysisData.lifetime.lifecycle_events;
1445 console.log('๐ Using lifetime events data:', lifecycleEvents.length);
1446 }
1447
1448 if (!lifecycleEvents || lifecycleEvents.length === 0) {
1449 console.warn('โ ๏ธ No lifetime data found');
1450 console.log('Available data keys:', Object.keys(window.analysisData || {}));
1451 showEmptyLifetimeState();
1452 return;
1453 }
1454
1455 console.log(`๐ Total lifecycle events: ${lifecycleEvents.length}`);
1456
1457 // Check if we have Rust-preprocessed data
1458 if (lifetimeData?.visualization_ready && lifetimeData?.variable_groups) {
1459 console.log(`๐ Using Rust-preprocessed data with ${lifetimeData.variable_groups.length} variable groups`);
1460 renderLifetimeVisualizationFromRustWithCollapse(lifetimeData.variable_groups);
1461 return;
1462 }
1463
1464 // Filter for user-defined variables (non-unknown var_name and type_name)
1465 const userVariables = lifecycleEvents.filter(event =>
1466 event.var_name && event.var_name !== 'unknown' &&
1467 event.type_name && event.type_name !== 'unknown'
1468 );
1469
1470 console.log(`๐ Found ${userVariables.length} user-defined variables in lifetime data`);
1471
1472 // Debug: Show some examples of what we found
1473 if (userVariables.length > 0) {
1474 console.log('๐ Sample user variables:', userVariables.slice(0, 3));
1475 } else {
1476 // Show some examples of unknown variables for debugging
1477 const unknownSamples = lifecycleEvents.slice(0, 3);
1478 console.log('๐ Sample unknown variables:', unknownSamples);
1479 }
1480
1481 if (userVariables.length === 0) {
1482 showEmptyLifetimeState();
1483 return;
1484 }
1485
1486 // Group by variable name to get allocation/deallocation pairs
1487 const variableGroups = groupVariablesByName(userVariables);
1488
1489 // Render the lifetime visualization with collapse functionality
1490 renderLifetimeVisualizationWithCollapse(variableGroups);
1491}
1492
1493// Group variables by name to track their lifecycle (enhanced for multiple instances)
1494function groupVariablesByName(events) {
1495 const groups = {};
1496
1497 events.forEach(event => {
1498 const varName = event.var_name;
1499 const instanceKey = `${varName}_${event.ptr || event.timestamp_alloc}`; // ไธบๆฏไธชๅฎไพๅๅปบๅฏไธkey
1500
1501 if (!groups[instanceKey]) {
1502 groups[instanceKey] = {
1503 var_name: `${varName}#${Object.keys(groups).filter(k => k.startsWith(varName)).length + 1}`, // ๆทปๅ ๅฎไพ็ผๅท
1504 original_var_name: varName,
1505 type_name: event.type_name,
1506 events: [],
1507 instance_info: {
1508 ptr: event.ptr,
1509 timestamp: event.timestamp_alloc,
1510 thread_id: event.thread_id
1511 }
1512 };
1513 }
1514 groups[instanceKey].events.push(event);
1515 });
1516
1517
1518 const groupValues = Object.values(groups);
1519 const varCounts = {};
1520 groupValues.forEach(group => {
1521 const originalName = group.original_var_name;
1522 varCounts[originalName] = (varCounts[originalName] || 0) + 1;
1523 });
1524
1525 groupValues.forEach(group => {
1526 if (varCounts[group.original_var_name] === 1) {
1527 group.var_name = group.original_var_name;
1528 }
1529 });
1530
1531 return groupValues;
1532}
1533
1534// Render lifetime visualization from Rust-preprocessed data with collapsible functionality
1535function renderLifetimeVisualizationFromRustWithCollapse(variableGroups) {
1536 console.log(`๐ Rendering ${variableGroups.length} Rust-preprocessed variable groups with collapse functionality`);
1537
1538 const container = document.getElementById('lifetimeVisualization');
1539 const toggleButton = document.getElementById('toggle-lifecycle');
1540
1541 if (!container) return;
1542
1543 // Clear loading state
1544 container.innerHTML = '';
1545
1546 if (!variableGroups || variableGroups.length === 0) {
1547 showEmptyLifetimeState();
1548 if (toggleButton) {
1549 toggleButton.style.display = 'none';
1550 }
1551 return;
1552 }
1553
1554 let isExpanded = false;
1555 const maxInitialRows = 5;
1556
1557 // Calculate timeline bounds from preprocessed data
1558 const allTimestamps = variableGroups.flatMap(group =>
1559 group.events ? group.events.map(e => e.timestamp) : [group.start_time, group.end_time].filter(t => t !== undefined)
1560 );
1561
1562 const minTime = Math.min(...allTimestamps);
1563 const maxTime = Math.max(...allTimestamps);
1564 const timeRange = maxTime - minTime || 1;
1565
1566 console.log(`๐ Rust data timeline: ${minTime} to ${maxTime} (range: ${timeRange})`);
1567
1568 // Color palette for different data types and visualizations
1569 const COLOR_PALETTE = {
1570 progress: [
1571 '#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57',
1572 '#ff9ff3', '#54a0ff', '#5f27cd', '#00d2d3', '#ff9f43'
1573 ]
1574 };
1575
1576 function renderLifetimeRows(showAll = false) {
1577 console.log(`๐ Rendering lifecycle rows, showAll: ${showAll}, total groups: ${variableGroups.length}`);
1578
1579 container.innerHTML = '';
1580
1581 const displayGroups = showAll ? variableGroups : variableGroups.slice(0, maxInitialRows);
1582
1583 // Render each variable with colorful progress bars
1584 displayGroups.forEach((group, index) => {
1585 const varDiv = document.createElement('div');
1586 varDiv.className = 'flex items-center py-4 border-b border-gray-100 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors';
1587
1588 // Get color from palette (cycle through colors)
1589 const colorIndex = index % COLOR_PALETTE.progress.length;
1590 const progressColor = COLOR_PALETTE.progress[colorIndex];
1591
1592 // Use preprocessed timing data or fallback to events
1593 const startTime = group.start_time || (group.events && group.events[0] ? group.events[0].timestamp : minTime);
1594 const firstEvent = group.events && group.events[0];
1595
1596 const startPercent = timeRange > 0 ? ((startTime - minTime) / timeRange) * 100 : 0;
1597
1598
1599 let widthPercent;
1600 if (firstEvent && firstEvent.lifetime_ms && firstEvent.lifetime_ms > 0) {
1601
1602 const lifetimeNs = firstEvent.lifetime_ms * 1000000;
1603 widthPercent = timeRange > 0 ? Math.max(1, (lifetimeNs / timeRange) * 100) : 6.8;
1604 } else {
1605 //
1606 widthPercent = 6.8;
1607 }
1608
1609 // ๅฎๅ
จ็ๅ้ๅฎไน๏ผ้ฒๆญขNaN
1610 const finalStartPercent = isNaN(startPercent) ? 0 : Math.max(0, Math.min(95, startPercent));
1611 const finalWidthPercent = isNaN(widthPercent) ? 40 : Math.max(2, Math.min(100 - finalStartPercent, widthPercent));
1612
1613 // Format type name for display
1614 const displayTypeName = formatTypeName(group.type_name);
1615
1616 // Create gradient background for more visual appeal
1617 const gradientStyle = `background: linear-gradient(90deg, ${progressColor}, ${progressColor}dd);`;
1618
1619 varDiv.innerHTML = `
1620 <div class="w-48 flex-shrink-0 pr-4">
1621 <div class="text-sm font-semibold text-gray-800 dark:text-gray-200">${group.var_name}</div>
1622 <div class="text-xs text-gray-500 dark:text-gray-400">${displayTypeName}</div>
1623 </div>
1624 <div class="flex-grow relative bg-gray-200 dark:bg-gray-600 rounded-full h-6 overflow-hidden">
1625 <div class="absolute inset-0 rounded-full"
1626 style="${gradientStyle} width: ${finalWidthPercent}%; margin-left: ${finalStartPercent}%;
1627 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1628 transition: all 0.3s ease;"
1629 title="Variable: ${group.var_name}, Type: ${displayTypeName}">
1630 <div class="absolute inset-0 flex items-center justify-center">
1631 <span class="text-xs font-bold text-white drop-shadow-sm">
1632 ${Math.round(finalWidthPercent)}%
1633 </span>
1634 </div>
1635 </div>
1636 <div class="absolute -top-8 left-0 text-xs bg-gray-700 text-white px-2 py-1 rounded opacity-0 hover:opacity-100 transition-opacity whitespace-nowrap">
1637 Duration: ${firstEvent && firstEvent.lifetime_ms ? firstEvent.lifetime_ms + 'ms' : 'Active'}
1638 </div>
1639 </div>
1640 <div class="w-20 flex-shrink-0 pl-4 text-right">
1641 <div class="text-xs text-gray-600 dark:text-gray-400">
1642 ${formatBytes(group.size || (group.events && group.events[0] ? group.events[0].size : 0) || 0)}
1643 </div>
1644 </div>
1645 `;
1646
1647 container.appendChild(varDiv);
1648 });
1649
1650 // Add "show more" indicator if collapsed
1651 if (!showAll && variableGroups.length > maxInitialRows) {
1652 const moreDiv = document.createElement('div');
1653 moreDiv.className = 'flex items-center py-4 bg-gray-50 dark:bg-gray-700 border-b border-gray-100 dark:border-gray-600';
1654 moreDiv.innerHTML = `
1655 <div class="w-full text-center text-gray-500 dark:text-gray-400 text-sm">
1656 ... and ${variableGroups.length - maxInitialRows} more variables
1657 </div>
1658 `;
1659 container.appendChild(moreDiv);
1660 }
1661 }
1662
1663 // Initial render
1664 renderLifetimeRows(false);
1665
1666 // Toggle functionality
1667 if (toggleButton && variableGroups.length > maxInitialRows) {
1668 console.log('๐ Setting up lifecycle toggle button for', variableGroups.length, 'variables');
1669
1670 // Remove any existing event listeners
1671 const newToggleButton = toggleButton.cloneNode(true);
1672 toggleButton.parentNode.replaceChild(newToggleButton, toggleButton);
1673
1674 newToggleButton.addEventListener('click', function (e) {
1675 e.preventDefault();
1676 console.log('๐ Lifecycle toggle button clicked, current state:', isExpanded);
1677
1678 isExpanded = !isExpanded;
1679 renderLifetimeRows(isExpanded);
1680
1681 const icon = newToggleButton.querySelector('i');
1682 const text = newToggleButton.querySelector('span');
1683
1684 if (isExpanded) {
1685 icon.className = 'fa fa-chevron-up mr-1';
1686 text.textContent = 'Show Less';
1687 console.log('๐ Expanded lifecycle to show all variables');
1688 } else {
1689 icon.className = 'fa fa-chevron-down mr-1';
1690 text.textContent = 'Show All';
1691 console.log('๐ Collapsed lifecycle to show first', maxInitialRows, 'variables');
1692 }
1693 });
1694
1695 console.log('โ
Lifecycle toggle button initialized successfully');
1696 } else if (toggleButton) {
1697 // Hide button if not needed
1698 toggleButton.style.display = 'none';
1699 console.log('๐ Lifecycle toggle button hidden (not enough data)');
1700 }
1701
1702 console.log(`โ
Rendered ${variableGroups.length} Rust-preprocessed variables in lifetime visualization with collapse functionality`);
1703}
1704
1705// Render the lifetime visualization with collapsible functionality
1706function renderLifetimeVisualizationWithCollapse(variableGroups) {
1707 const container = document.getElementById('lifetimeVisualization');
1708 const toggleButton = document.getElementById('toggle-lifecycle');
1709
1710 if (!container) return;
1711
1712 // Clear loading state
1713 container.innerHTML = '';
1714
1715 if (!variableGroups || variableGroups.length === 0) {
1716 showEmptyLifetimeState();
1717 if (toggleButton) {
1718 toggleButton.style.display = 'none';
1719 }
1720 return;
1721 }
1722
1723 let isExpanded = false;
1724 const maxInitialRows = 5;
1725
1726 // Get color scheme for different types
1727 const typeColors = {
1728 'Vec': { bg: 'bg-blue-500', border: 'border-blue-500' },
1729 'Box': { bg: 'bg-purple-500', border: 'border-purple-500' },
1730 'Rc': { bg: 'bg-yellow-500', border: 'border-yellow-500' },
1731 'Arc': { bg: 'bg-green-500', border: 'border-green-500' },
1732 'String': { bg: 'bg-pink-500', border: 'border-pink-500' },
1733 'default': { bg: 'bg-gray-500', border: 'border-gray-500' }
1734 };
1735
1736 // Calculate timeline bounds
1737 const allTimestamps = variableGroups.flatMap(group =>
1738 group.events.map(e => e.timestamp)
1739 );
1740 const minTime = Math.min(...allTimestamps);
1741 const maxTime = Math.max(...allTimestamps);
1742 const timeRange = maxTime - minTime;
1743
1744 console.log(`๐ Timeline: ${minTime} to ${maxTime} (range: ${timeRange})`);
1745
1746 function renderLifetimeRows(showAll = false) {
1747 console.log(`๐ Rendering lifecycle rows, showAll: ${showAll}, total groups: ${variableGroups.length}`);
1748
1749 container.innerHTML = '';
1750
1751 const displayGroups = showAll ? variableGroups : variableGroups.slice(0, maxInitialRows);
1752
1753 // Render each variable
1754 displayGroups.forEach((group) => {
1755 const varDiv = document.createElement('div');
1756 varDiv.className = 'flex items-end py-3 border-b border-gray-100 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors';
1757
1758 // Determine color based on type
1759 const typeKey = Object.keys(typeColors).find(key =>
1760 group.type_name.includes(key)
1761 ) || 'default';
1762 const colors = typeColors[typeKey];
1763
1764 // Calculate position and width based on timestamps
1765 const firstEvent = group.events[0];
1766 const startTime = firstEvent.timestamp;
1767 const startPositionPercent = timeRange > 0 ? ((startTime - minTime) / timeRange) * 100 : 0;
1768
1769 // real time correct time axis calculation: based on actual allocation and survival time
1770 const allocTime = firstEvent.timestamp;
1771 const deallocTime = firstEvent.timestamp_dealloc;
1772 const lifetimeMs = firstEvent.lifetime_ms || 1; // default 1ms lifetime
1773
1774 // calculate survival time length (percentage)
1775 let durationPercent;
1776 if (deallocTime && deallocTime > allocTime) {
1777 // if there is a clear release time, use actual time span
1778 const actualDuration = deallocTime - allocTime;
1779 durationPercent = (actualDuration / timeRange) * 100;
1780 } else {
1781 // if there is no release time, use lifetime_ms calculation
1782 const lifetimeNs = lifetimeMs * 1000000; // convert to nanoseconds
1783 durationPercent = (lifetimeNs / timeRange) * 100;
1784 }
1785
1786 // ensure value is within reasonable range
1787 const widthPercent = Math.max(0.5, Math.min(100 - startPositionPercent, durationPercent));
1788
1789 // ๅฎๅ
จ็ๅ้ๅฎไน๏ผ้ฒๆญขNaN
1790 const finalStartPercent = isNaN(startPositionPercent) ? 0 : Math.max(0, Math.min(95, startPositionPercent));
1791 const finalWidthPercent = isNaN(widthPercent) ? 30 : Math.max(2, Math.min(100 - finalStartPercent, widthPercent));
1792
1793 // Format type name for display
1794 const displayTypeName = formatTypeName(group.type_name);
1795
1796 varDiv.innerHTML = `
1797 <div class="w-40 flex-shrink-0 text-sm font-medium dark:text-gray-200">
1798 ${group.var_name} (${displayTypeName})
1799 </div>
1800 <div class="flex-grow relative">
1801 <div class="lifespan-indicator ${colors.bg}"
1802 style="width: ${finalWidthPercent}%; margin-left: ${finalStartPercent}%;"
1803 title="Variable: ${group.var_name}, Type: ${displayTypeName}">
1804 <div class="absolute -top-6 left-0 text-xs ${colors.bg} text-white px-2 py-1 rounded whitespace-nowrap">
1805 Allocated: ${formatTimestamp(startTime, minTime)}
1806 </div>
1807 </div>
1808 </div>
1809 `;
1810
1811 container.appendChild(varDiv);
1812 });
1813
1814 // Add "show more" indicator if collapsed
1815 if (!showAll && variableGroups.length > maxInitialRows) {
1816 const moreDiv = document.createElement('div');
1817 moreDiv.className = 'flex items-center py-3 bg-gray-50 dark:bg-gray-700 border-b border-gray-100 dark:border-gray-600';
1818 moreDiv.innerHTML = `
1819 <div class="w-full text-center text-gray-500 dark:text-gray-400 text-sm">
1820 ... and ${variableGroups.length - maxInitialRows} more variables
1821 </div>
1822 `;
1823 container.appendChild(moreDiv);
1824 }
1825 }
1826
1827 // Initial render
1828 renderLifetimeRows(false);
1829
1830 // Toggle functionality
1831 if (toggleButton && variableGroups.length > maxInitialRows) {
1832 console.log('๐ Setting up lifecycle toggle button for', variableGroups.length, 'variables');
1833
1834 // Remove any existing event listeners
1835 const newToggleButton = toggleButton.cloneNode(true);
1836 toggleButton.parentNode.replaceChild(newToggleButton, toggleButton);
1837
1838 newToggleButton.addEventListener('click', function (e) {
1839 e.preventDefault();
1840 console.log('๐ Lifecycle toggle button clicked, current state:', isExpanded);
1841
1842 isExpanded = !isExpanded;
1843 renderLifetimeRows(isExpanded);
1844
1845 const icon = newToggleButton.querySelector('i');
1846 const text = newToggleButton.querySelector('span');
1847
1848 if (isExpanded) {
1849 icon.className = 'fa fa-chevron-up mr-1';
1850 text.textContent = 'Show Less';
1851 console.log('๐ Expanded lifecycle to show all variables');
1852 } else {
1853 icon.className = 'fa fa-chevron-down mr-1';
1854 text.textContent = 'Show All';
1855 console.log('๐ Collapsed lifecycle to show first', maxInitialRows, 'variables');
1856 }
1857 });
1858
1859 console.log('โ
Lifecycle toggle button initialized successfully');
1860 } else if (toggleButton) {
1861 // Hide button if not needed
1862 toggleButton.style.display = 'none';
1863 console.log('๐ Lifecycle toggle button hidden (not enough data)');
1864 }
1865
1866 console.log(`โ
Rendered ${variableGroups.length} variables in lifetime visualization with collapse functionality`);
1867}
1868
1869// Initialize FFI visualization with enhanced support for improve.md fields
1870function initFFIVisualization() {
1871 console.log('๐ Initializing FFI visualization...');
1872
1873 const container = document.getElementById('ffiVisualization');
1874 if (!container) return;
1875
1876 // Get FFI data from multiple sources with comprehensive field support
1877 let allocations = [];
1878 let unsafeReports = [];
1879 let memoryPassports = [];
1880 let ffiStatistics = {};
1881
1882 console.log('๐ Checking analysisData structure:', Object.keys(window.analysisData || {}));
1883
1884 // Enhanced data extraction supporting improve.md structure
1885 if (window.analysisData) {
1886 // Debug: Show what data structure we actually have FIRST
1887 console.log('๐ Available data keys:', Object.keys(window.analysisData));
1888 if (window.analysisData.unsafe_ffi) {
1889 console.log('๐ unsafe_ffi keys:', Object.keys(window.analysisData.unsafe_ffi));
1890 console.log('๐ unsafe_ffi.allocations exists:', !!window.analysisData.unsafe_ffi.allocations);
1891
1892 // Data will be handled by initializeAnalysis function
1893 console.log('๐ unsafe_ffi.allocations length:', window.analysisData.unsafe_ffi.allocations ? window.analysisData.unsafe_ffi.allocations.length : 'undefined');
1894 }
1895
1896 // Try unsafe_ffi data first (improve.md structure)
1897 if (window.analysisData.unsafe_ffi) {
1898 allocations = window.analysisData.unsafe_ffi.allocations || [];
1899 unsafeReports = window.analysisData.unsafe_ffi.unsafe_reports || [];
1900 memoryPassports = window.analysisData.unsafe_ffi.memory_passports || [];
1901 ffiStatistics = window.analysisData.unsafe_ffi.ffi_statistics || {};
1902 console.log('๐ Found unsafe_ffi data - allocations:', allocations.length, 'reports:', unsafeReports.length, 'passports:', memoryPassports.length);
1903 }
1904 // Try complex_types structure (for large_scale_user files)
1905 else if (window.analysisData.complex_types && window.analysisData.complex_types.allocations) {
1906 allocations = window.analysisData.complex_types.allocations;
1907 console.log('๐ Found complex_types allocations:', allocations.length);
1908 }
1909 // Try direct allocations array (for files like large_scale_user_unsafe_ffi.json)
1910 else if (window.analysisData.allocations) {
1911 allocations = window.analysisData.allocations;
1912 console.log('๐ Found direct allocations:', allocations.length);
1913 }
1914 // Fallback to memory_analysis
1915 else if (window.analysisData.memory_analysis && window.analysisData.memory_analysis.allocations) {
1916 allocations = window.analysisData.memory_analysis.allocations;
1917 console.log('๐ Using memory_analysis allocations:', allocations.length);
1918 }
1919
1920 // Debug: Show what data structure we actually have
1921 console.log('๐ Available data keys:', Object.keys(window.analysisData));
1922 if (window.analysisData.unsafe_ffi) {
1923 console.log('๐ unsafe_ffi keys:', Object.keys(window.analysisData.unsafe_ffi));
1924 }
1925
1926 // Extract metadata if available
1927 const metadata = window.analysisData.metadata || {};
1928 console.log('๐ Metadata:', metadata);
1929 }
1930
1931 // Filter for FFI-tracked allocations with enhanced field support
1932 const ffiAllocations = allocations.filter(alloc =>
1933 alloc.ffi_tracked === true ||
1934 (alloc.safety_violations && alloc.safety_violations.length > 0) ||
1935 alloc.ownership_history_available === true ||
1936 (alloc.borrow_info && (alloc.borrow_info.immutable_borrows > 0 || alloc.borrow_info.mutable_borrows > 0)) ||
1937 (alloc.clone_info && alloc.clone_info.clone_count > 0)
1938 );
1939 console.log('๐ Found FFI-tracked allocations:', ffiAllocations.length);
1940
1941 // Debug: show first few allocations with improve.md fields
1942 if (allocations.length > 0) {
1943 console.log('๐ Sample allocation with improve.md fields:', allocations[0]);
1944 console.log('๐ FFI tracked allocations sample:', ffiAllocations.slice(0, 3));
1945
1946 // Check for improve.md specific fields
1947 const sampleAlloc = allocations[0];
1948 console.log('๐ Improve.md fields check:');
1949 console.log(' - borrow_info:', sampleAlloc.borrow_info);
1950 console.log(' - clone_info:', sampleAlloc.clone_info);
1951 console.log(' - ownership_history_available:', sampleAlloc.ownership_history_available);
1952 console.log(' - ffi_tracked:', sampleAlloc.ffi_tracked);
1953 console.log(' - safety_violations:', sampleAlloc.safety_violations);
1954 }
1955
1956 // Debug: Show what we found before filtering
1957 console.log('๐ Before filtering - Total allocations:', allocations.length);
1958 console.log('๐ Sample allocation fields:', allocations[0] ? Object.keys(allocations[0]) : 'No allocations');
1959 console.log('๐ FFI tracked count:', allocations.filter(a => a.ffi_tracked === true).length);
1960 console.log('๐ Borrow info count:', allocations.filter(a => a.borrow_info).length);
1961 console.log('๐ Clone info count:', allocations.filter(a => a.clone_info).length);
1962
1963 // Enhanced rendering with improve.md support - ALWAYS show if we have any allocations
1964 if (allocations.length === 0) {
1965 container.innerHTML = createFFIEmptyState();
1966 return;
1967 }
1968
1969 // If we have allocations but no FFI-specific ones, still show the dashboard with all data
1970 const displayAllocations = ffiAllocations.length > 0 ? ffiAllocations : allocations.slice(0, 20);
1971 console.log('๐ฏ Rendering FFI dashboard with:', displayAllocations.length, 'allocations,', unsafeReports.length, 'reports,', memoryPassports.length, 'passports');
1972
1973 // Generate enhanced FFI analysis with improve.md fields
1974 try {
1975 if (FFI_STYLE === 'svg') {
1976 const boundaryEvents = window.analysisData.unsafe_ffi?.boundary_events || [];
1977 const unsafeAllocs = displayAllocations.filter(a => (a.safety_violations || []).length > 0).length;
1978 const ffiAllocs = displayAllocations.filter(a => a.ffi_tracked).length;
1979 const safetyViolations = displayAllocations.reduce((sum, a) => sum + ((a.safety_violations || []).length || 0), 0);
1980 const unsafeMemory = displayAllocations
1981 .filter(a => (a.safety_violations || []).length > 0)
1982 .reduce((sum, a) => sum + (a.size || 0), 0);
1983
1984 container.innerHTML = createFFIDashboardSVG(
1985 unsafeAllocs,
1986 ffiAllocs,
1987 boundaryEvents.length,
1988 safetyViolations,
1989 unsafeMemory,
1990 displayAllocations,
1991 boundaryEvents,
1992 unsafeReports
1993 );
1994 console.log('โ
FFI SVG-style dashboard rendered');
1995 return;
1996 }
1997 console.log('๐ Generating FFI analysis...');
1998 const ffiAnalysis = generateEnhancedFFIAnalysisWithImproveFields(displayAllocations, unsafeReports, memoryPassports, ffiStatistics);
1999 console.log('โ
FFI analysis generated:', ffiAnalysis);
2000
2001 console.log('๐ Creating FFI dashboard...');
2002 const dashboardHTML = createEnhancedFFIDashboardWithImproveFields(ffiAnalysis, displayAllocations, unsafeReports, memoryPassports);
2003 console.log('โ
Dashboard HTML created, length:', dashboardHTML.length);
2004
2005 container.innerHTML = dashboardHTML;
2006 console.log('โ
Dashboard rendered successfully!');
2007 } catch (error) {
2008 console.error('โ Error in FFI rendering:', error);
2009 container.innerHTML = `<div class="bg-red-100 p-4 rounded text-red-800">Error rendering FFI data: ${error.message}</div>`;
2010 }
2011}
2012
2013// Generate enhanced FFI analysis with improve.md fields support
2014function generateEnhancedFFIAnalysisWithImproveFields(ffiAllocations, unsafeReports, memoryPassports, ffiStatistics) {
2015 let totalFFI = ffiAllocations.length;
2016 let totalViolations = 0;
2017 let totalMemory = 0;
2018 let highRiskCount = 0;
2019 let mediumRiskCount = 0;
2020 let lowRiskCount = 0;
2021 let totalBorrows = 0;
2022 let totalClones = 0;
2023 let leakedAllocations = 0;
2024
2025 const analysisData = ffiAllocations.map(alloc => {
2026 const violations = alloc.safety_violations?.length || 0;
2027 const size = alloc.size || 0;
2028
2029 // Enhanced borrow analysis from improve.md fields
2030 const borrowConflicts = alloc.borrow_info ?
2031 (alloc.borrow_info.mutable_borrows > 0 && alloc.borrow_info.immutable_borrows > 0) : false;
2032 const totalBorrowsForAlloc = alloc.borrow_info ?
2033 (alloc.borrow_info.immutable_borrows || 0) + (alloc.borrow_info.mutable_borrows || 0) : 0;
2034 totalBorrows += totalBorrowsForAlloc;
2035
2036 // Enhanced clone analysis from improve.md fields
2037 const cloneCount = alloc.clone_info?.clone_count || 0;
2038 const isClone = alloc.clone_info?.is_clone || false;
2039 totalClones += cloneCount;
2040
2041 // Enhanced ownership and lifecycle analysis
2042 const ownershipHistoryAvailable = alloc.ownership_history_available || false;
2043 const isLeaked = alloc.is_leaked || false;
2044 if (isLeaked) leakedAllocations++;
2045
2046 // Enhanced risk calculation with improve.md fields
2047 let riskScore = 0;
2048 if (violations > 0) riskScore += 50;
2049 if (borrowConflicts) riskScore += 30;
2050 if (size > 1024) riskScore += 20;
2051 if (isLeaked) riskScore += 40;
2052 if (cloneCount > 3) riskScore += 15;
2053 if (totalBorrowsForAlloc > 5) riskScore += 10;
2054
2055 let riskLevel = 'Low';
2056 if (riskScore >= 70) {
2057 riskLevel = 'High';
2058 highRiskCount++;
2059 } else if (riskScore >= 35) {
2060 riskLevel = 'Medium';
2061 mediumRiskCount++;
2062 } else {
2063 lowRiskCount++;
2064 }
2065
2066 totalViolations += violations;
2067 totalMemory += size;
2068
2069 return {
2070 ...alloc,
2071 riskScore,
2072 riskLevel,
2073 violations,
2074 borrowConflicts,
2075 totalBorrowsForAlloc,
2076 cloneCount,
2077 isClone,
2078 ownershipHistoryAvailable,
2079 isLeaked
2080 };
2081 });
2082
2083 // Enhanced statistics from improve.md structure
2084 const enhancedStats = {
2085 boundary_crossings: ffiStatistics.boundary_crossings || 0,
2086 memory_violations: ffiStatistics.memory_violations || 0,
2087 total_ffi_calls: ffiStatistics.total_ffi_calls || 0,
2088 unsafe_operations: ffiStatistics.unsafe_operations || 0
2089 };
2090
2091 return {
2092 totalFFI,
2093 totalViolations,
2094 totalMemory,
2095 highRiskCount,
2096 mediumRiskCount,
2097 lowRiskCount,
2098 totalBorrows,
2099 totalClones,
2100 leakedAllocations,
2101 analysisData,
2102 unsafeReports,
2103 memoryPassports,
2104 ffiStatistics: enhancedStats
2105 };
2106}
2107
2108// Legacy function for backward compatibility
2109function generateEnhancedFFIAnalysis(ffiAllocations) {
2110 return generateEnhancedFFIAnalysisWithImproveFields(ffiAllocations, [], [], {});
2111}
2112
2113// Create enhanced FFI dashboard with improve.md fields support
2114function createEnhancedFFIDashboardWithImproveFields(analysis, ffiAllocations, unsafeReports, memoryPassports) {
2115 return `
2116 <div class="space-y-6">
2117 <!-- Enhanced FFI Overview Cards with improve.md metrics -->
2118 <div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
2119 <div class="bg-blue-100 dark:bg-blue-900 rounded-lg p-4 text-center">
2120 <div class="text-2xl font-bold text-blue-600 dark:text-blue-300">${analysis.totalFFI}</div>
2121 <div class="text-sm text-blue-700 dark:text-blue-400">FFI Allocations</div>
2122 </div>
2123 <div class="bg-red-100 dark:bg-red-900 rounded-lg p-4 text-center">
2124 <div class="text-2xl font-bold text-red-600 dark:text-red-300">${analysis.highRiskCount}</div>
2125 <div class="text-sm text-red-700 dark:text-red-400">High Risk</div>
2126 </div>
2127 <div class="bg-orange-100 dark:bg-orange-900 rounded-lg p-4 text-center">
2128 <div class="text-2xl font-bold text-orange-600 dark:text-orange-300">${analysis.mediumRiskCount}</div>
2129 <div class="text-sm text-orange-700 dark:text-orange-400">Medium Risk</div>
2130 </div>
2131 <div class="bg-green-100 dark:bg-green-900 rounded-lg p-4 text-center">
2132 <div class="text-2xl font-bold text-green-600 dark:text-green-300">${analysis.lowRiskCount}</div>
2133 <div class="text-sm text-green-700 dark:text-green-400">Low Risk</div>
2134 </div>
2135 <div class="bg-purple-100 dark:bg-purple-900 rounded-lg p-4 text-center">
2136 <div class="text-2xl font-bold text-purple-600 dark:text-purple-300">${analysis.totalBorrows}</div>
2137 <div class="text-sm text-purple-700 dark:text-purple-400">Total Borrows</div>
2138 </div>
2139 <div class="bg-indigo-100 dark:bg-indigo-900 rounded-lg p-4 text-center">
2140 <div class="text-2xl font-bold text-indigo-600 dark:text-indigo-300">${analysis.totalClones}</div>
2141 <div class="text-sm text-indigo-700 dark:text-indigo-400">Total Clones</div>
2142 </div>
2143 </div>
2144
2145 <!-- FFI Statistics from improve.md -->
2146 ${analysis.ffiStatistics && Object.keys(analysis.ffiStatistics).length > 0 ? `
2147 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6">
2148 <h3 class="text-lg font-semibold mb-4 text-gray-800 dark:text-white">FFI Statistics</h3>
2149 <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
2150 <div class="text-center">
2151 <div class="text-xl font-bold text-gray-900 dark:text-white">${analysis.ffiStatistics.boundary_crossings}</div>
2152 <div class="text-sm text-gray-600 dark:text-gray-400">Boundary Crossings</div>
2153 </div>
2154 <div class="text-center">
2155 <div class="text-xl font-bold text-gray-900 dark:text-white">${analysis.ffiStatistics.memory_violations}</div>
2156 <div class="text-sm text-gray-600 dark:text-gray-400">Memory Violations</div>
2157 </div>
2158 <div class="text-center">
2159 <div class="text-xl font-bold text-gray-900 dark:text-white">${analysis.ffiStatistics.total_ffi_calls}</div>
2160 <div class="text-sm text-gray-600 dark:text-gray-400">Total FFI Calls</div>
2161 </div>
2162 <div class="text-center">
2163 <div class="text-xl font-bold text-gray-900 dark:text-white">${analysis.ffiStatistics.unsafe_operations}</div>
2164 <div class="text-sm text-gray-600 dark:text-gray-400">Unsafe Operations</div>
2165 </div>
2166 </div>
2167 </div>
2168 ` : ''}
2169
2170 <!-- Unsafe Reports from improve.md structure -->
2171 ${analysis.unsafeReports && analysis.unsafeReports.length > 0 ? `
2172 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6">
2173 <h3 class="text-lg font-semibold mb-4 text-gray-800 dark:text-white">Unsafe Reports</h3>
2174 <div class="space-y-4">
2175 ${analysis.unsafeReports.map(report => createUnsafeReportCard(report)).join('')}
2176 </div>
2177 </div>
2178 ` : ''}
2179
2180 <!-- Memory Passports from improve.md structure -->
2181 ${analysis.memoryPassports && analysis.memoryPassports.length > 0 ? `
2182 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6">
2183 <h3 class="text-lg font-semibold mb-4 text-gray-800 dark:text-white">Memory Passports</h3>
2184 <div class="space-y-3">
2185 ${analysis.memoryPassports.map(passport => createMemoryPassportCard(passport)).join('')}
2186 </div>
2187 </div>
2188 ` : ''}
2189
2190 <!-- Enhanced FFI Risk Analysis with improve.md fields -->
2191 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6">
2192 <h3 class="text-lg font-semibold mb-4 text-gray-800 dark:text-white">Enhanced FFI Risk Analysis</h3>
2193 <div class="space-y-4">
2194 ${analysis.analysisData.map(alloc => createEnhancedFFIAllocationCard(alloc)).join('')}
2195 </div>
2196 </div>
2197
2198 <!-- Enhanced Borrow Checker Analysis with improve.md fields -->
2199 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6">
2200 <h3 class="text-lg font-semibold mb-4 text-gray-800 dark:text-white">Enhanced Borrow Checker Analysis</h3>
2201 <div class="space-y-3">
2202 ${ffiAllocations.filter(alloc => alloc.borrow_info).map(alloc => createEnhancedBorrowAnalysisCard(alloc)).join('')}
2203 </div>
2204 </div>
2205
2206 <!-- Clone Analysis from improve.md fields -->
2207 ${analysis.totalClones > 0 ? `
2208 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6">
2209 <h3 class="text-lg font-semibold mb-4 text-gray-800 dark:text-white">Clone Analysis</h3>
2210 <div class="space-y-3">
2211 ${ffiAllocations.filter(alloc => alloc.clone_info && alloc.clone_info.clone_count > 0).map(alloc => createCloneAnalysisCard(alloc)).join('')}
2212 </div>
2213 </div>
2214 ` : ''}
2215
2216 <!-- Ownership History Analysis -->
2217 ${ffiAllocations.some(alloc => alloc.ownership_history_available) ? `
2218 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6">
2219 <h3 class="text-lg font-semibold mb-4 text-gray-800 dark:text-white">Ownership History Analysis</h3>
2220 <div class="space-y-3">
2221 ${ffiAllocations.filter(alloc => alloc.ownership_history_available).map(alloc => createOwnershipHistoryCard(alloc)).join('')}
2222 </div>
2223 </div>
2224 ` : ''}
2225 </div>
2226 `;
2227}
2228
2229// Legacy function for backward compatibility
2230function createEnhancedFFIDashboard(analysis, ffiAllocations) {
2231 return createEnhancedFFIDashboardWithImproveFields(analysis, ffiAllocations, [], []);
2232}
2233
2234// Create enhanced FFI allocation card with improve.md fields
2235function createEnhancedFFIAllocationCard(alloc) {
2236 const riskColor = alloc.riskLevel === 'High' ? 'red' : alloc.riskLevel === 'Medium' ? 'orange' : 'green';
2237 const hasViolations = alloc.violations > 0;
2238 const hasBorrowConflicts = alloc.borrowConflicts;
2239 const hasClones = alloc.cloneCount > 0;
2240 const isLeaked = alloc.isLeaked;
2241 const hasOwnershipHistory = alloc.ownershipHistoryAvailable;
2242
2243 return `
2244 <div class="bg-white dark:bg-gray-600 rounded-lg p-4 border-l-4 border-${riskColor}-500">
2245 <div class="flex justify-between items-start mb-3">
2246 <div>
2247 <h4 class="font-semibold text-gray-900 dark:text-white">${alloc.var_name || 'Unknown Variable'}</h4>
2248 <p class="text-sm text-gray-600 dark:text-gray-300">${formatTypeName(alloc.type_name || 'Unknown Type')}</p>
2249 ${alloc.isClone ? '<span class="inline-block px-2 py-1 text-xs bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 rounded-full mt-1">Clone</span>' : ''}
2250 </div>
2251 <div class="text-right">
2252 <span class="px-2 py-1 text-xs font-bold rounded-full bg-${riskColor}-100 text-${riskColor}-800 dark:bg-${riskColor}-900 dark:text-${riskColor}-200">
2253 ${alloc.riskLevel} Risk
2254 </span>
2255 ${isLeaked ? '<div class="mt-1"><span class="px-2 py-1 text-xs bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 rounded-full">LEAKED</span></div>' : ''}
2256 </div>
2257 </div>
2258
2259 <div class="grid grid-cols-2 gap-4 text-sm mb-3">
2260 <div>
2261 <span class="text-gray-500 dark:text-gray-400">Size:</span>
2262 <span class="ml-2 font-mono">${formatBytes(alloc.size || 0)}</span>
2263 </div>
2264 <div>
2265 <span class="text-gray-500 dark:text-gray-400">Risk Score:</span>
2266 <span class="ml-2 font-bold text-${riskColor}-600">${alloc.riskScore}/100</span>
2267 </div>
2268 <div>
2269 <span class="text-gray-500 dark:text-gray-400">Pointer:</span>
2270 <span class="ml-2 font-mono text-xs">${alloc.ptr}</span>
2271 </div>
2272 <div>
2273 <span class="text-gray-500 dark:text-gray-400">Thread:</span>
2274 <span class="ml-2">${alloc.thread_id || 'Unknown'}</span>
2275 </div>
2276 </div>
2277
2278 <!-- Enhanced improve.md fields -->
2279 <div class="grid grid-cols-3 gap-4 text-sm mb-3">
2280 <div>
2281 <span class="text-gray-500 dark:text-gray-400">Total Borrows:</span>
2282 <span class="ml-2 font-bold">${alloc.totalBorrowsForAlloc || 0}</span>
2283 </div>
2284 <div>
2285 <span class="text-gray-500 dark:text-gray-400">Clone Count:</span>
2286 <span class="ml-2 font-bold">${alloc.cloneCount || 0}</span>
2287 </div>
2288 <div>
2289 <span class="text-gray-500 dark:text-gray-400">FFI Tracked:</span>
2290 <span class="ml-2">${alloc.ffi_tracked ? 'โ
' : 'โ'}</span>
2291 </div>
2292 </div>
2293
2294 ${hasViolations || hasBorrowConflicts || hasClones || hasOwnershipHistory ? `
2295 <div class="mt-3 pt-3 border-t border-gray-200 dark:border-gray-500">
2296 <div class="text-sm space-y-1">
2297 ${hasViolations ? `<div class="text-red-600 dark:text-red-400">โ ๏ธ ${alloc.violations} safety violations</div>` : ''}
2298 ${hasBorrowConflicts ? `<div class="text-orange-600 dark:text-orange-400">โ ๏ธ Borrow conflicts detected</div>` : ''}
2299 ${hasClones ? `<div class="text-blue-600 dark:text-blue-400">๐ ${alloc.cloneCount} clones created</div>` : ''}
2300 ${hasOwnershipHistory ? `<div class="text-green-600 dark:text-green-400">๐ Ownership history available</div>` : ''}
2301 </div>
2302 </div>
2303 ` : ''}
2304 </div>
2305 `;
2306}
2307
2308// Legacy function for backward compatibility
2309function createFFIAllocationCard(alloc) {
2310 return createEnhancedFFIAllocationCard(alloc);
2311}
2312
2313// Create enhanced borrow analysis card with improve.md fields
2314function createEnhancedBorrowAnalysisCard(alloc) {
2315 const borrowInfo = alloc.borrow_info;
2316 const hasConflict = borrowInfo.mutable_borrows > 0 && borrowInfo.immutable_borrows > 0;
2317 const lastBorrowTime = borrowInfo.last_borrow_timestamp ? new Date(borrowInfo.last_borrow_timestamp / 1000000).toLocaleTimeString() : 'Unknown';
2318
2319 return `
2320 <div class="bg-white dark:bg-gray-600 rounded-lg p-3 ${hasConflict ? 'border-l-4 border-red-500' : 'border border-gray-200 dark:border-gray-500'}">
2321 <div class="flex justify-between items-start">
2322 <div>
2323 <h5 class="font-medium text-gray-900 dark:text-white">${alloc.var_name}</h5>
2324 <p class="text-xs text-gray-500 dark:text-gray-400">${formatTypeName(alloc.type_name)}</p>
2325 <p class="text-xs text-gray-500 dark:text-gray-400">Last borrow: ${lastBorrowTime}</p>
2326 </div>
2327 <div class="text-right text-sm">
2328 <div class="text-blue-600 dark:text-blue-400">Immutable: ${borrowInfo.immutable_borrows}</div>
2329 <div class="text-red-600 dark:text-red-400">Mutable: ${borrowInfo.mutable_borrows}</div>
2330 <div class="text-purple-600 dark:text-purple-400">Max Concurrent: ${borrowInfo.max_concurrent_borrows}</div>
2331 <div class="text-xs text-gray-500 dark:text-gray-400">Total: ${(borrowInfo.immutable_borrows || 0) + (borrowInfo.mutable_borrows || 0)}</div>
2332 </div>
2333 </div>
2334 ${hasConflict ? `
2335 <div class="mt-2 text-xs text-red-600 dark:text-red-400 font-bold">
2336 โ ๏ธ BORROW CONFLICT: Simultaneous mutable and immutable borrows detected
2337 </div>
2338 ` : ''}
2339 </div>
2340 `;
2341}
2342
2343// Legacy function for backward compatibility
2344function createBorrowAnalysisCard(alloc) {
2345 return createEnhancedBorrowAnalysisCard(alloc);
2346}
2347
2348// Create clone analysis card for improve.md clone_info fields
2349function createCloneAnalysisCard(alloc) {
2350 const cloneInfo = alloc.clone_info;
2351 const isClone = cloneInfo.is_clone;
2352 const cloneCount = cloneInfo.clone_count;
2353 const originalPtr = cloneInfo.original_ptr;
2354
2355 return `
2356 <div class="bg-white dark:bg-gray-600 rounded-lg p-3 border-l-4 border-blue-500">
2357 <div class="flex justify-between items-start">
2358 <div>
2359 <h5 class="font-medium text-gray-900 dark:text-white">${alloc.var_name}</h5>
2360 <p class="text-xs text-gray-500 dark:text-gray-400">${formatTypeName(alloc.type_name)}</p>
2361 ${isClone ? `<p class="text-xs text-blue-600 dark:text-blue-400">Clone of: ${originalPtr}</p>` : ''}
2362 </div>
2363 <div class="text-right">
2364 <div class="text-blue-600 dark:text-blue-400 font-bold text-lg">${cloneCount}</div>
2365 <div class="text-xs text-gray-500 dark:text-gray-400">Clones Created</div>
2366 ${isClone ? '<div class="text-xs bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 px-2 py-1 rounded mt-1">IS CLONE</div>' : ''}
2367 </div>
2368 </div>
2369 <div class="mt-2 text-sm text-gray-600 dark:text-gray-300">
2370 ${cloneCount > 0 ? `๐ This allocation has been cloned ${cloneCount} times` : ''}
2371 ${isClone ? `<br>๐ This is a clone of allocation at ${originalPtr}` : ''}
2372 </div>
2373 </div>
2374 `;
2375}
2376
2377// Create ownership history card for improve.md ownership_history_available field
2378function createOwnershipHistoryCard(alloc) {
2379 return `
2380 <div class="bg-white dark:bg-gray-600 rounded-lg p-3 border-l-4 border-green-500">
2381 <div class="flex justify-between items-center">
2382 <div>
2383 <h5 class="font-medium text-gray-900 dark:text-white">${alloc.var_name}</h5>
2384 <p class="text-xs text-gray-500 dark:text-gray-400">${formatTypeName(alloc.type_name)}</p>
2385 </div>
2386 <div class="text-right">
2387 <div class="text-green-600 dark:text-green-400">๐ History Available</div>
2388 <div class="text-xs text-gray-500 dark:text-gray-400">Detailed tracking enabled</div>
2389 </div>
2390 </div>
2391 <div class="mt-2 text-sm text-gray-600 dark:text-gray-300">
2392 โ
Ownership history is available for this allocation in lifetime.json
2393 </div>
2394 </div>
2395 `;
2396}
2397
2398// Create unsafe report card for improve.md UnsafeReport structure
2399function createUnsafeReportCard(report) {
2400 const riskLevel = report.risk_assessment?.risk_level || 'Unknown';
2401 const riskColor = riskLevel === 'High' ? 'red' : riskLevel === 'Medium' ? 'orange' : 'green';
2402 const confidenceScore = report.risk_assessment?.confidence_score || 0;
2403 const riskFactors = report.risk_assessment?.risk_factors || [];
2404 const dynamicViolations = report.dynamic_violations || [];
2405
2406 return `
2407 <div class="bg-white dark:bg-gray-600 rounded-lg p-4 border-l-4 border-${riskColor}-500">
2408 <div class="flex justify-between items-start mb-3">
2409 <div>
2410 <h4 class="font-semibold text-gray-900 dark:text-white">Unsafe Report: ${report.report_id || 'Unknown'}</h4>
2411 <p class="text-sm text-gray-600 dark:text-gray-300">${report.source?.type || 'Unknown'} at ${report.source?.location || 'Unknown location'}</p>
2412 </div>
2413 <div class="text-right">
2414 <span class="px-2 py-1 text-xs font-bold rounded-full bg-${riskColor}-100 text-${riskColor}-800 dark:bg-${riskColor}-900 dark:text-${riskColor}-200">
2415 ${riskLevel} Risk
2416 </span>
2417 <div class="text-xs text-gray-500 dark:text-gray-400 mt-1">Confidence: ${(confidenceScore * 100).toFixed(1)}%</div>
2418 </div>
2419 </div>
2420
2421 ${riskFactors.length > 0 ? `
2422 <div class="mb-3">
2423 <h5 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Risk Factors:</h5>
2424 <div class="space-y-1">
2425 ${riskFactors.map(factor => `
2426 <div class="text-sm">
2427 <span class="font-medium text-${riskColor}-600 dark:text-${riskColor}-400">${factor.factor_type}</span>
2428 <span class="text-gray-600 dark:text-gray-400"> (Severity: ${factor.severity}/10)</span>
2429 <div class="text-xs text-gray-500 dark:text-gray-400">${factor.description}</div>
2430 </div>
2431 `).join('')}
2432 </div>
2433 </div>
2434 ` : ''}
2435
2436 ${dynamicViolations.length > 0 ? `
2437 <div class="mt-3 pt-3 border-t border-gray-200 dark:border-gray-500">
2438 <h5 class="text-sm font-medium text-red-700 dark:text-red-300 mb-2">Dynamic Violations:</h5>
2439 <div class="space-y-1">
2440 ${dynamicViolations.map(violation => `
2441 <div class="text-sm text-red-600 dark:text-red-400">
2442 โ ๏ธ ${violation.violation_type}: ${violation.description}
2443 </div>
2444 `).join('')}
2445 </div>
2446 </div>
2447 ` : ''}
2448 </div>
2449 `;
2450}
2451
2452// Create memory passport card for improve.md MemoryPassport structure
2453function createMemoryPassportCard(passport) {
2454 const status = passport.status_at_shutdown || 'Unknown';
2455 const statusColor = status === 'Reclaimed' ? 'green' : status === 'InForeignCustody' ? 'red' : 'orange';
2456 const lifecycleEvents = passport.lifecycle_events || [];
2457
2458 return `
2459 <div class="bg-white dark:bg-gray-600 rounded-lg p-3 border-l-4 border-${statusColor}-500">
2460 <div class="flex justify-between items-start">
2461 <div>
2462 <h5 class="font-medium text-gray-900 dark:text-white">Passport: ${passport.passport_id || 'Unknown'}</h5>
2463 <p class="text-xs text-gray-500 dark:text-gray-400">Allocation: ${passport.allocation_ptr} (${formatBytes(passport.size_bytes || 0)})</p>
2464 </div>
2465 <div class="text-right">
2466 <span class="px-2 py-1 text-xs font-bold rounded-full bg-${statusColor}-100 text-${statusColor}-800 dark:bg-${statusColor}-900 dark:text-${statusColor}-200">
2467 ${status}
2468 </span>
2469 </div>
2470 </div>
2471
2472 ${lifecycleEvents.length > 0 ? `
2473 <div class="mt-2">
2474 <h6 class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Lifecycle Events:</h6>
2475 <div class="space-y-1">
2476 ${lifecycleEvents.slice(0, 3).map(event => `
2477 <div class="text-xs text-gray-600 dark:text-gray-400">
2478 ๐
${event.event_type} ${event.how ? `(${event.how})` : ''}
2479 </div>
2480 `).join('')}
2481 ${lifecycleEvents.length > 3 ? `<div class="text-xs text-gray-500 dark:text-gray-400">... and ${lifecycleEvents.length - 3} more events</div>` : ''}
2482 </div>
2483 </div>
2484 ` : ''}
2485 </div>
2486 `;
2487}
2488
2489// Create FFI empty state
2490function createFFIEmptyState() {
2491 return `
2492 <div class="text-center py-8">
2493 <div class="mb-4">
2494 <svg class="w-16 h-16 mx-auto text-green-400 dark:text-green-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2495 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
2496 </svg>
2497 </div>
2498 <h4 class="text-lg font-semibold mb-2 text-gray-800 dark:text-gray-200">Memory Safety Verified</h4>
2499 <p class="text-sm text-gray-600 dark:text-gray-400">No unsafe FFI operations detected in this analysis</p>
2500 <p class="text-xs mt-2 text-gray-500 dark:text-gray-500">Your code appears to be using safe Rust patterns</p>
2501 </div>
2502 `;
2503}
2504
2505// Create comprehensive FFI dashboard with SVG-style visualization
2506function createFFIDashboardSVG(unsafeAllocs, ffiAllocs, boundaryCrossings, safetyViolations, unsafeMemory, enhancedData, boundaryEvents, violations) {
2507 return `
2508 <div class="bg-gradient-to-br from-gray-800 to-gray-900 rounded-xl p-6 text-white shadow-2xl">
2509 <!-- Header -->
2510 <div class="text-center mb-6">
2511 <h2 class="text-2xl font-bold mb-2 flex items-center justify-center">
2512 <i class="fa fa-shield mr-3 text-red-400"></i>
2513 Unsafe Rust & FFI Memory Analysis Dashboard
2514 </h2>
2515 </div>
2516
2517 <!-- Key Metrics Row -->
2518 <div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-8">
2519 ${createFFIMetricCard('Unsafe Allocations', unsafeAllocs, '#e74c3c', 'fa-exclamation-triangle')}
2520 ${createFFIMetricCard('FFI Allocations', ffiAllocs, '#3498db', 'fa-exchange')}
2521 ${createFFIMetricCard('Boundary Crossings', boundaryCrossings, '#f39c12', 'fa-arrows-h')}
2522 ${createFFIMetricCard('Safety Violations', safetyViolations, '#e67e22', 'fa-warning')}
2523 ${createFFIMetricCard('Unsafe Memory', formatBytes(unsafeMemory), '#9b59b6', 'fa-memory')}
2524 </div>
2525
2526 <!-- Main Dashboard Content -->
2527 <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
2528 <!-- Memory Allocation Sources -->
2529 <div class="bg-gray-700/50 rounded-lg p-4 backdrop-blur-sm">
2530 <h3 class="text-lg font-semibold mb-4 text-white">Memory Allocation Sources</h3>
2531 <div class="space-y-4">
2532 ${createAllocationSourceBar('Unsafe Rust', unsafeAllocs, Math.max(unsafeAllocs, ffiAllocs), '#e74c3c')}
2533 ${createAllocationSourceBar('FFI', ffiAllocs, Math.max(unsafeAllocs, ffiAllocs), '#3498db')}
2534 </div>
2535 </div>
2536
2537 <!-- Memory Safety Status -->
2538 <div class="bg-gray-700/50 rounded-lg p-4 backdrop-blur-sm">
2539 <h3 class="text-lg font-semibold mb-4 text-white">Memory Safety Status</h3>
2540 ${safetyViolations > 0 ? `
2541 <div class="bg-red-900/30 border border-red-500/50 rounded-lg p-4">
2542 <h4 class="text-red-300 font-semibold mb-2 flex items-center">
2543 <i class="fa fa-exclamation-triangle mr-2"></i>
2544 ${safetyViolations} Safety Violations Detected
2545 </h4>
2546 ${enhancedData.filter(item => (item.safety_violations || 0) > 0).slice(0, 2).map(item => `
2547 <div class="text-red-400 text-sm flex items-center mb-1">
2548 <i class="fa fa-dot-circle-o mr-2 text-xs"></i>
2549 Pointer ${item.ptr}: ${item.safety_violations} violations
2550 </div>
2551 `).join('')}
2552 </div>
2553 ` : `
2554 <div class="bg-green-900/30 border border-green-500/50 rounded-lg p-4">
2555 <h4 class="text-green-300 font-semibold flex items-center mb-2">
2556 <i class="fa fa-check-circle mr-2"></i>
2557 No Safety Violations Detected
2558 </h4>
2559 <p class="text-green-400 text-sm">All unsafe operations appear to be handled correctly</p>
2560 </div>
2561 `}
2562 </div>
2563 </div>
2564
2565 <!-- Cross-Language Memory Flow -->
2566 <div class="bg-gray-700/50 rounded-lg p-6 mb-6 backdrop-blur-sm">
2567 <h3 class="text-lg font-semibold mb-6 text-white text-center">Cross-Language Memory Flow</h3>
2568 <div class="flex items-center justify-center space-x-8">
2569 <!-- Rust Side -->
2570 <div class="bg-green-800/30 border-2 border-green-400/50 rounded-lg p-6 text-center backdrop-blur-sm">
2571 <div class="text-green-300 font-bold text-xl mb-2">RUST</div>
2572 <div class="text-green-400 text-sm">${unsafeAllocs} allocations</div>
2573 <div class="w-16 h-16 mx-auto mt-3 bg-green-500/20 rounded-full flex items-center justify-center">
2574 <i class="fa fa-rust text-green-400 text-2xl"></i>
2575 </div>
2576 </div>
2577
2578 <!-- Flow Arrows -->
2579 <div class="flex flex-col items-center space-y-4">
2580 <div class="flex items-center space-x-2">
2581 <div class="flex items-center space-x-1">
2582 <div class="w-8 h-0.5 bg-red-400"></div>
2583 <div class="w-0 h-0 border-l-4 border-l-red-400 border-t-2 border-t-transparent border-b-2 border-b-transparent"></div>
2584 </div>
2585 <span class="text-red-400 text-sm font-bold bg-red-900/30 px-2 py-1 rounded">
2586 ${boundaryEvents.filter(e => e.event_type === 'RustToFfi').length}
2587 </span>
2588 </div>
2589 <div class="flex items-center space-x-2">
2590 <span class="text-orange-400 text-sm font-bold bg-orange-900/30 px-2 py-1 rounded">
2591 ${boundaryEvents.filter(e => e.event_type === 'FfiToRust').length}
2592 </span>
2593 <div class="flex items-center space-x-1">
2594 <div class="w-0 h-0 border-r-4 border-r-orange-400 border-t-2 border-t-transparent border-b-2 border-b-transparent"></div>
2595 <div class="w-8 h-0.5 bg-orange-400"></div>
2596 </div>
2597 </div>
2598 </div>
2599
2600 <!-- FFI/C Side -->
2601 <div class="bg-blue-800/30 border-2 border-blue-400/50 rounded-lg p-6 text-center backdrop-blur-sm">
2602 <div class="text-blue-300 font-bold text-xl mb-2">FFI / C</div>
2603 <div class="text-blue-400 text-sm">${ffiAllocs} allocations</div>
2604 <div class="w-16 h-16 mx-auto mt-3 bg-blue-500/20 rounded-full flex items-center justify-center">
2605 <i class="fa fa-code text-blue-400 text-2xl"></i>
2606 </div>
2607 </div>
2608 </div>
2609 </div>
2610
2611 <!-- Unsafe Memory Hotspots -->
2612 <div class="bg-gray-700/50 rounded-lg p-4 backdrop-blur-sm">
2613 <h3 class="text-lg font-semibold mb-4 text-white">Unsafe Memory Hotspots</h3>
2614 <div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
2615 ${enhancedData.slice(0, 12).map(item => createMemoryHotspot(item)).join('')}
2616 </div>
2617 ${enhancedData.length === 0 ? `
2618 <div class="text-center py-8 text-gray-400">
2619 <i class="fa fa-shield-alt text-4xl mb-2"></i>
2620 <p>No unsafe memory hotspots detected</p>
2621 </div>
2622 ` : ''}
2623 </div>
2624 </div>
2625 `;
2626}
2627
2628// Create FFI metric card
2629function createFFIMetricCard(title, value, color, icon) {
2630 return `
2631 <div class="bg-gray-700/30 border border-gray-600/50 rounded-lg p-4 text-center backdrop-blur-sm hover:bg-gray-600/30 transition-all">
2632 <div class="flex items-center justify-center mb-2">
2633 <i class="fa ${icon} text-2xl" style="color: ${color}"></i>
2634 </div>
2635 <div class="text-2xl font-bold mb-1" style="color: ${color}">${value}</div>
2636 <div class="text-xs text-gray-300 uppercase tracking-wide">${title}</div>
2637 </div>
2638 `;
2639}
2640
2641// Create allocation source bar
2642function createAllocationSourceBar(label, count, maxCount, color) {
2643 const percentage = maxCount > 0 ? (count / maxCount) * 100 : 0;
2644 const barHeight = Math.max(20, (count / maxCount) * 80);
2645
2646 return `
2647 <div class="flex items-end space-x-4">
2648 <div class="flex-1">
2649 <div class="flex justify-between items-center mb-2">
2650 <span class="text-sm font-medium text-gray-300">${label}</span>
2651 <span class="text-lg font-bold text-white">${count}</span>
2652 </div>
2653 <div class="w-full bg-gray-600 rounded-full h-6 overflow-hidden">
2654 <div class="h-full rounded-full transition-all duration-500 flex items-center justify-center text-white text-xs font-bold"
2655 style="width: ${percentage}%; background-color: ${color};">
2656 ${count > 0 ? count : ''}
2657 </div>
2658 </div>
2659 </div>
2660 </div>
2661 `;
2662}
2663
2664// Create memory hotspot visualization
2665function createMemoryHotspot(item) {
2666 const size = item.size || 0;
2667 const isUnsafe = !item.ffi_tracked;
2668 const radius = Math.min(30, Math.max(12, Math.sqrt(size / 50)));
2669 const color = isUnsafe ? '#e74c3c' : '#3498db';
2670 const bgColor = isUnsafe ? 'bg-red-900/20' : 'bg-blue-900/20';
2671 const borderColor = isUnsafe ? 'border-red-500/50' : 'border-blue-500/50';
2672
2673 // Interactive hotspot with data attributes for detail panel
2674 return `
2675 <div class="flex flex-col items-center p-3 ${bgColor} border ${borderColor} rounded-lg backdrop-blur-sm hover:scale-105 transition-transform cursor-pointer"
2676 data-ptr="${item.ptr || ''}"
2677 data-var="${(item.var_name || 'Unknown').toString().replace(/\"/g, '"')}"
2678 data-type="${(item.type_name || 'Unknown').toString().replace(/\"/g, '"')}"
2679 data-size="${size}"
2680 data-violations="${(item.safety_violations || 0)}"
2681 onclick="window.showFFIDetailFromDataset && window.showFFIDetailFromDataset(this)">
2682 <div class="relative mb-2">
2683 <div class="rounded-full border-2 flex items-center justify-center text-white text-xs font-bold shadow-lg"
2684 style="width: ${radius * 2}px; height: ${radius * 2}px; background-color: ${color}; border-color: ${color};">
2685 ${size > 1024 ? Math.round(size / 1024) + 'K' : size + 'B'}
2686 </div>
2687 ${(item.safety_violations || 0) > 0 ? `
2688 <div class="absolute -top-1 -right-1 w-4 h-4 bg-red-500 rounded-full flex items-center justify-center">
2689 <i class="fa fa-exclamation text-white text-xs"></i>
2690 </div>
2691 ` : ''}
2692 </div>
2693 <div class="text-xs text-center">
2694 <div class="font-semibold" style="color: ${color}">
2695 ${isUnsafe ? 'UNSAFE' : 'FFI'}
2696 </div>
2697 <div class="text-gray-400 text-xs">
2698 ${formatBytes(size)}
2699 </div>
2700 </div>
2701 </div>
2702 `;
2703}
2704
2705// Simple detail panel for FFI hotspot items
2706window.showFFIDetailFromDataset = function(el) {
2707 try {
2708 const container = document.getElementById('ffiVisualization');
2709 if (!container) return;
2710
2711 // Remove existing panel
2712 const existing = container.querySelector('#ffi-detail-panel');
2713 if (existing) existing.remove();
2714
2715 // Build panel
2716 const panel = document.createElement('div');
2717 panel.id = 'ffi-detail-panel';
2718 panel.style.position = 'absolute';
2719 panel.style.right = '16px';
2720 panel.style.top = '16px';
2721 panel.style.zIndex = '1000';
2722 panel.style.minWidth = '280px';
2723 panel.style.maxWidth = '360px';
2724 panel.style.background = 'var(--bg-primary)';
2725 panel.style.border = '1px solid var(--border-light)';
2726 panel.style.borderRadius = '10px';
2727 panel.style.boxShadow = '0 10px 25px rgba(0,0,0,0.2)';
2728 panel.style.padding = '12px';
2729
2730 const name = el.getAttribute('data-var');
2731 const type = el.getAttribute('data-type');
2732 const size = parseInt(el.getAttribute('data-size') || '0', 10);
2733 const ptr = el.getAttribute('data-ptr');
2734 const violations = parseInt(el.getAttribute('data-violations') || '0', 10);
2735
2736 panel.innerHTML = `
2737 <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
2738 <div style="font-weight:700; font-size:14px; color: var(--text-primary);">FFI Allocation Detail</div>
2739 <button onclick="this.parentNode.parentNode.remove()" style="border:none; background:transparent; color: var(--text-secondary); font-size:18px; cursor:pointer">ร</button>
2740 </div>
2741 <div style="font-size:12px; color: var(--text-primary);">
2742 <div style="margin-bottom:6px;"><strong>Name:</strong> ${name}</div>
2743 <div style="margin-bottom:6px;"><strong>Type:</strong> ${type}</div>
2744 <div style="margin-bottom:6px;"><strong>Size:</strong> ${formatBytes(size)}</div>
2745 ${ptr ? `<div style=\"margin-bottom:6px;\"><strong>Pointer:</strong> <code>${ptr}</code></div>` : ''}
2746 <div style="margin-bottom:6px;"><strong>Safety Violations:</strong> ${violations}</div>
2747 </div>
2748 `;
2749
2750 container.appendChild(panel);
2751 } catch(e) {
2752 console.warn('Failed to show FFI detail panel', e);
2753 }
2754};
2755
2756// Initialize memory fragmentation analysis with enhanced SVG-style visualization
2757function initMemoryFragmentation() {
2758 const container = document.getElementById('memoryFragmentation');
2759 if (!container) return;
2760
2761 const allocations = window.analysisData.memory_analysis?.allocations || [];
2762
2763 if (allocations.length === 0) {
2764 container.innerHTML = createFragmentationEmptyState();
2765 return;
2766 }
2767
2768 // Fixed memory fragmentation analysis: based on allocation size distribution rather than address gaps
2769 const sortedAllocs = allocations
2770 .filter(alloc => alloc.size && alloc.size > 0)
2771 .map(alloc => ({
2772 size: alloc.size,
2773 type: alloc.type_name || 'System Allocation',
2774 var_name: alloc.var_name || 'unknown'
2775 }))
2776 .sort((a, b) => a.size - b.size);
2777
2778 const totalMemory = sortedAllocs.reduce((sum, alloc) => sum + alloc.size, 0);
2779
2780 // Calculate fragmentation based on allocation size distribution
2781 const sizeVariance = calculateSizeVariance(sortedAllocs);
2782 const smallAllocRatio = sortedAllocs.filter(a => a.size < 1024).length / sortedAllocs.length;
2783
2784 // Fragmentation score: based on size distribution unevenness
2785 const fragmentationRatio = Math.min(100, (sizeVariance / 1000 + smallAllocRatio * 50));
2786
2787 // Simplified gap analysis: only count quantity, not fake address gaps
2788 const gaps = Math.max(0, sortedAllocs.length - 1);
2789 const maxGap = 0; // No longer calculate address gaps
2790 let totalGapSize = 0; // Reset to 0 to avoid huge fake values
2791
2792 // Size distribution analysis (inspired by SVG)
2793 const sizeDistribution = {
2794 tiny: sortedAllocs.filter(a => a.size < 64).length,
2795 small: sortedAllocs.filter(a => a.size >= 64 && a.size < 1024).length,
2796 medium: sortedAllocs.filter(a => a.size >= 1024 && a.size < 65536).length,
2797 large: sortedAllocs.filter(a => a.size >= 65536).length
2798 };
2799
2800 container.innerHTML = createFragmentationAnalysisSVG(
2801 fragmentationRatio, gaps, maxGap, sortedAllocs.length,
2802 totalMemory, sizeDistribution, sortedAllocs
2803 );
2804}
2805
2806// Create fragmentation empty state
2807function createFragmentationEmptyState() {
2808 return `
2809 <div class="bg-white dark:bg-gray-800 rounded-xl p-6 card-shadow transition-colors">
2810 <h2 class="text-xl font-semibold mb-4 flex items-center text-heading">
2811 <i class="fa fa-puzzle-piece text-orange-500 mr-2"></i>Memory Fragmentation Analysis
2812 </h2>
2813 <div class="text-center py-8">
2814 <div class="mb-4">
2815 <svg class="w-16 h-16 mx-auto text-gray-400 dark:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2816 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
2817 </svg>
2818 </div>
2819 <h4 class="text-lg font-semibold mb-2 text-gray-800 dark:text-gray-200">No Memory Data for Analysis</h4>
2820 <p class="text-sm text-gray-600 dark:text-gray-400">Memory fragmentation analysis requires allocation data</p>
2821 </div>
2822 </div>
2823 `;
2824}
2825
2826// Create comprehensive fragmentation analysis with SVG-style visualization
2827function createFragmentationAnalysisSVG(fragmentationRatio, gaps, maxGap, blockCount, totalMemory, sizeDistribution, sortedAllocs) {
2828 return `
2829 <div class="bg-white dark:bg-gray-800 rounded-xl p-6 card-shadow transition-colors">
2830 <h2 class="text-xl font-semibold mb-6 flex items-center text-heading">
2831 <i class="fa fa-puzzle-piece text-orange-500 mr-2"></i>Memory Fragmentation Analysis
2832 </h2>
2833
2834 <!-- Key Metrics Grid -->
2835 <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
2836 ${createFragmentationMetricCard('Fragmentation', fragmentationRatio.toFixed(1) + '%', fragmentationRatio, '#f39c12')}
2837 ${createFragmentationMetricCard('Memory Gaps', gaps, 100, '#3498db')}
2838 ${createFragmentationMetricCard('Largest Gap', formatBytes(maxGap), 100, '#27ae60')}
2839 ${createFragmentationMetricCard('Memory Blocks', blockCount, 100, '#9b59b6')}
2840 </div>
2841
2842 <!-- Main Analysis Content -->
2843 <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
2844 <!-- Fragmentation Assessment -->
2845 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
2846 <h4 class="font-semibold mb-4 text-gray-800 dark:text-white">Fragmentation Assessment</h4>
2847 <div class="space-y-4">
2848 <div>
2849 <div class="flex justify-between items-center mb-2">
2850 <span class="text-sm font-medium text-gray-700 dark:text-gray-300">Overall Health</span>
2851 <span class="text-sm font-bold ${getFragmentationColor(fragmentationRatio)}">${fragmentationRatio.toFixed(1)}%</span>
2852 </div>
2853 <div class="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-4">
2854 <div class="h-4 rounded-full transition-all duration-500 ${getFragmentationBgColor(fragmentationRatio)}"
2855 style="width: ${Math.min(fragmentationRatio, 100)}%"></div>
2856 </div>
2857 </div>
2858 <div class="text-sm text-gray-600 dark:text-gray-300">
2859 ${getFragmentationAssessment(fragmentationRatio)}
2860 </div>
2861 </div>
2862 </div>
2863
2864 <!-- Size Distribution (inspired by SVG bar chart) -->
2865 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
2866 <h4 class="font-semibold mb-4 text-gray-800 dark:text-white">Size Distribution</h4>
2867 <div class="space-y-3">
2868 ${createSizeDistributionBar('Tiny (0-64B)', sizeDistribution.tiny, blockCount, '#27ae60')}
2869 ${createSizeDistributionBar('Small (64B-1KB)', sizeDistribution.small, blockCount, '#f39c12')}
2870 ${createSizeDistributionBar('Medium (1KB-64KB)', sizeDistribution.medium, blockCount, '#e74c3c')}
2871 ${createSizeDistributionBar('Large (>64KB)', sizeDistribution.large, blockCount, '#8e44ad')}
2872 </div>
2873 </div>
2874 </div>
2875
2876 <!-- Memory Layout Visualization -->
2877 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
2878 <h4 class="font-semibold mb-4 text-gray-800 dark:text-white">Memory Layout Visualization</h4>
2879 <div class="relative">
2880 <!-- Memory blocks visualization -->
2881 <div class="h-16 bg-gray-200 dark:bg-gray-600 rounded relative overflow-hidden mb-4">
2882 ${createMemoryLayoutVisualization(sortedAllocs, totalMemory)}
2883 </div>
2884
2885 <!-- Memory address timeline -->
2886 <div class="flex justify-between text-xs text-gray-500 dark:text-gray-400 mb-2">
2887 <span>Low Address</span>
2888 <span>Memory Layout</span>
2889 <span>High Address</span>
2890 </div>
2891
2892 <!-- Legend -->
2893 <div class="flex flex-wrap gap-4 text-xs">
2894 <div class="flex items-center">
2895 <div class="w-3 h-3 bg-blue-500 rounded mr-2"></div>
2896 <span class="text-gray-600 dark:text-gray-300">User Allocations</span>
2897 </div>
2898 <div class="flex items-center">
2899 <div class="w-3 h-3 bg-gray-400 rounded mr-2"></div>
2900 <span class="text-gray-600 dark:text-gray-300">System Allocations</span>
2901 </div>
2902 <div class="flex items-center">
2903 <div class="w-3 h-3 bg-red-300 rounded mr-2"></div>
2904 <span class="text-gray-600 dark:text-gray-300">Memory Gaps</span>
2905 </div>
2906 </div>
2907 </div>
2908 </div>
2909 </div>
2910 `;
2911}
2912
2913// Create fragmentation metric card with circular progress
2914function createFragmentationMetricCard(title, value, percentage, color) {
2915 const normalizedPercentage = Math.min(100, Math.max(0, percentage));
2916 const circumference = 2 * Math.PI * 20;
2917 const strokeDashoffset = circumference - (normalizedPercentage / 100) * circumference;
2918
2919 return `
2920 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4 text-center hover:shadow-md transition-shadow">
2921 <div class="flex items-center justify-between">
2922 <div class="flex-1">
2923 <p class="text-xs font-medium text-gray-600 dark:text-gray-400 uppercase">${title}</p>
2924 <p class="text-lg font-bold text-gray-900 dark:text-white">${value}</p>
2925 </div>
2926 <div class="relative w-10 h-10">
2927 <svg class="w-10 h-10 transform -rotate-90" viewBox="0 0 50 50">
2928 <circle cx="25" cy="25" r="20" stroke='#e5e7eb' stroke-width='4' fill='none' class='dark:stroke-gray-600'/>
2929 <circle cx='25' cy='25' r='20' stroke='${color}' stroke-width='4' fill='none'
2930 stroke-dasharray='${circumference}' stroke-dashoffset='${strokeDashoffset}'
2931 stroke-linecap='round' class='transition-all duration-500'/>
2932 </svg>
2933 </div>
2934 </div>
2935 </div>
2936 `;
2937}
2938
2939// Create size distribution bar
2940function createSizeDistributionBar(label, count, total, color) {
2941 const percentage = total > 0 ? (count / total) * 100 : 0;
2942 return `
2943 <div class="flex items-center justify-between">
2944 <span class="text-sm font-medium text-gray-700 dark:text-gray-300 w-28">${label}</span>
2945 <div class="flex-1 mx-3">
2946 <div class="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-4">
2947 <div class="h-4 rounded-full transition-all duration-500"
2948 style="width: ${percentage}%; background-color: ${color}"></div>
2949 </div>
2950 </div>
2951 <span class="text-sm font-bold text-gray-900 dark:text-white w-8 text-right">${count}</span>
2952 </div>
2953 `;
2954}
2955
2956// Create memory layout visualization
2957function createMemoryLayoutVisualization(sortedAllocs, totalMemory) {
2958 if (sortedAllocs.length === 0) return '<div class="flex items-center justify-center h-full text-gray-400">No memory layout data</div>';
2959
2960 return sortedAllocs.slice(0, 30).map((alloc, index) => {
2961 const width = Math.max(1, (alloc.size / totalMemory) * 100);
2962 const left = (index / 30) * 100;
2963 const isUserAlloc = alloc.type !== 'System Allocation';
2964 const color = isUserAlloc ? '#3498db' : '#95a5a6';
2965
2966 return `
2967 <div class="absolute h-full transition-all hover:brightness-110 cursor-pointer"
2968 style="left: ${left}%; width: ${width}%; background-color: ${color}; opacity: 0.8;"
2969 title="${alloc.type}: ${formatBytes(alloc.size)} at ${(alloc.address || 0).toString(16)}">
2970 </div>
2971 `;
2972 }).join('');
2973}
2974
2975// Calculate variance of allocation sizes to assess fragmentation level
2976function calculateSizeVariance(allocations) {
2977 if (allocations.length === 0) return 0;
2978
2979 const sizes = allocations.map(a => a.size);
2980 const mean = sizes.reduce((sum, size) => sum + size, 0) / sizes.length;
2981 const variance = sizes.reduce((sum, size) => sum + Math.pow(size - mean, 2), 0) / sizes.length;
2982
2983 return Math.sqrt(variance); // ่ฟๅๆ ๅๅทฎ
2984}
2985
2986// Helper functions for fragmentation analysis
2987function getFragmentationColor(ratio) {
2988 if (ratio < 10) return 'text-green-600 dark:text-green-400';
2989 if (ratio < 25) return 'text-yellow-600 dark:text-yellow-400';
2990 if (ratio < 50) return 'text-orange-600 dark:text-orange-400';
2991 return 'text-red-600 dark:text-red-400';
2992}
2993
2994function getFragmentationBgColor(ratio) {
2995 if (ratio < 10) return 'bg-green-500';
2996 if (ratio < 25) return 'bg-yellow-500';
2997 if (ratio < 50) return 'bg-orange-500';
2998 return 'bg-red-500';
2999}
3000
3001function getFragmentationAssessment(ratio) {
3002 if (ratio < 10) return 'Excellent memory layout with minimal fragmentation. Memory is well-organized.';
3003 if (ratio < 25) return 'Good memory layout with low fragmentation. No immediate concerns.';
3004 if (ratio < 50) return 'Moderate fragmentation detected. Consider memory pool allocation strategies.';
3005 return 'High fragmentation detected. Memory layout optimization strongly recommended.';
3006}
3007
3008// Initialize memory growth trends with enhanced SVG-style visualization
3009function initMemoryGrowthTrends() {
3010 const container = document.getElementById('memoryGrowthTrends');
3011 if (!container) return;
3012
3013 const allocations = window.analysisData.memory_analysis?.allocations || [];
3014
3015 // Sort allocations by timestamp
3016 const sortedAllocs = allocations
3017 .filter(alloc => alloc.timestamp_alloc)
3018 .sort((a, b) => a.timestamp_alloc - b.timestamp_alloc);
3019
3020 if (sortedAllocs.length === 0) {
3021 container.innerHTML = createGrowthTrendsEmptyState();
3022 return;
3023 }
3024
3025 // Calculate cumulative memory usage over time
3026 let cumulativeMemory = 0;
3027 let peakMemory = 0;
3028 const timePoints = [];
3029
3030 sortedAllocs.forEach((alloc, index) => {
3031 cumulativeMemory += alloc.size || 0;
3032 peakMemory = Math.max(peakMemory, cumulativeMemory);
3033
3034 if (index % Math.max(1, Math.floor(sortedAllocs.length / 20)) === 0) {
3035 timePoints.push({
3036 timestamp: alloc.timestamp_alloc,
3037 memory: cumulativeMemory,
3038 index: index,
3039 allocCount: index + 1
3040 });
3041 }
3042 });
3043
3044 const startMemory = timePoints[0]?.memory || 0;
3045 const endMemory = timePoints[timePoints.length - 1]?.memory || 0;
3046 const growthRate = startMemory > 0 ? ((endMemory - startMemory) / startMemory * 100) : 0;
3047 const averageMemory = timePoints.reduce((sum, point) => sum + point.memory, 0) / timePoints.length;
3048
3049 container.innerHTML = createMemoryGrowthTrendsSVG(
3050 peakMemory, averageMemory, growthRate, timePoints, sortedAllocs.length
3051 );
3052}
3053
3054// Create growth trends empty state
3055function createGrowthTrendsEmptyState() {
3056 return `
3057 <div class="bg-white dark:bg-gray-800 rounded-xl p-6 card-shadow transition-colors">
3058 <h2 class="text-xl font-semibold mb-4 flex items-center text-heading">
3059 <i class="fa fa-line-chart text-green-500 mr-2"></i>Memory Growth Trends
3060 </h2>
3061 <div class="text-center py-8">
3062 <div class="mb-4">
3063 <svg class="w-16 h-16 mx-auto text-gray-400 dark:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3064 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
3065 </svg>
3066 </div>
3067 <h4 class="text-lg font-semibold mb-2 text-gray-800 dark:text-gray-200">No Timeline Data Available</h4>
3068 <p class="text-sm text-gray-600 dark:text-gray-400">Memory growth analysis requires timestamp data</p>
3069 </div>
3070 </div>
3071 `;
3072}
3073
3074// Create comprehensive memory growth trends visualization
3075function createMemoryGrowthTrendsSVG(peakMemory, averageMemory, growthRate, timePoints, totalAllocs) {
3076 return `
3077 <div class="bg-white dark:bg-gray-800 rounded-xl p-6 card-shadow transition-colors">
3078 <h2 class="text-xl font-semibold mb-6 flex items-center text-heading">
3079 <i class="fa fa-line-chart text-green-500 mr-2"></i>Memory Growth Trends
3080 </h2>
3081
3082 <!-- Key Metrics Grid -->
3083 <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
3084 ${createGrowthMetricCard('Peak Memory', formatBytes(peakMemory), 100, '#e74c3c')}
3085 ${createGrowthMetricCard('Average Memory', formatBytes(averageMemory), Math.round((averageMemory / peakMemory) * 100), '#3498db')}
3086 ${createGrowthMetricCard('Growth Rate', (growthRate > 0 ? '+' : '') + growthRate.toFixed(1) + '%', Math.abs(growthRate), getGrowthRateColor(growthRate))}
3087 ${createGrowthMetricCard('Total Allocations', totalAllocs, 100, '#9b59b6')}
3088 </div>
3089
3090 <!-- Main Growth Chart -->
3091 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6 mb-6">
3092 <h4 class="font-semibold mb-4 text-gray-800 dark:text-white">Memory Usage Over Time</h4>
3093 <div class="relative">
3094 <!-- Chart Container -->
3095 <div class="h-48 relative bg-white dark:bg-gray-600 rounded border dark:border-gray-500 overflow-hidden">
3096 ${createMemoryGrowthChart(timePoints, peakMemory)}
3097 </div>
3098
3099 <!-- Chart Labels -->
3100 <div class="flex justify-between text-xs text-gray-500 dark:text-gray-400 mt-2">
3101 <span>Start</span>
3102 <span>Memory Usage Timeline</span>
3103 <span>End</span>
3104 </div>
3105
3106 <!-- Peak Memory Line -->
3107 <div class="absolute top-2 right-2 text-xs text-red-500 dark:text-red-400 bg-white dark:bg-gray-800 px-2 py-1 rounded shadow">
3108 Peak: ${formatBytes(peakMemory)}
3109 </div>
3110 </div>
3111 </div>
3112
3113 <!-- Growth Analysis -->
3114 <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
3115 <!-- Growth Assessment -->
3116 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
3117 <h4 class="font-semibold mb-4 text-gray-800 dark:text-white">Growth Assessment</h4>
3118 <div class="space-y-3">
3119 <div class="flex items-center justify-between">
3120 <span class="text-sm text-gray-600 dark:text-gray-300">Memory Efficiency</span>
3121 <span class="text-sm font-bold ${getEfficiencyColor(averageMemory, peakMemory)}">${((averageMemory / peakMemory) * 100).toFixed(1)}%</span>
3122 </div>
3123 <div class="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-2">
3124 <div class="h-2 rounded-full transition-all duration-500 ${getEfficiencyBgColor(averageMemory, peakMemory)}"
3125 style="width: ${(averageMemory / peakMemory) * 100}%"></div>
3126 </div>
3127 <div class="text-sm text-gray-600 dark:text-gray-300">
3128 ${getGrowthAssessment(growthRate)}
3129 </div>
3130 </div>
3131 </div>
3132
3133 <!-- Memory Allocation Timeline -->
3134 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
3135 <h4 class="font-semibold mb-4 text-gray-800 dark:text-white">Recent Allocations</h4>
3136 <div class="space-y-2 max-h-32 overflow-y-auto">
3137 ${timePoints.slice(-6).map((point, index) => `
3138 <div class="flex justify-between items-center text-sm">
3139 <span class="text-gray-600 dark:text-gray-300">Alloc #${point.allocCount}</span>
3140 <span class="font-mono text-xs font-bold text-gray-900 dark:text-white">${formatBytes(point.memory)}</span>
3141 </div>
3142 `).join('')}
3143 </div>
3144 <div class="text-xs text-gray-500 dark:text-gray-400 mt-2">
3145 Showing latest allocation points
3146 </div>
3147 </div>
3148 </div>
3149 </div>
3150 `;
3151}
3152
3153// Create growth metric card
3154function createGrowthMetricCard(title, value, percentage, color) {
3155 const normalizedPercentage = Math.min(100, Math.max(0, percentage));
3156
3157 return `
3158 <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4 text-center hover:shadow-md transition-shadow">
3159 <div class="mb-2">
3160 <div class="text-2xl font-bold" style="color: ${color}">${value}</div>
3161 <div class="text-xs text-gray-600 dark:text-gray-400 uppercase tracking-wide">${title}</div>
3162 </div>
3163 <div class="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-2">
3164 <div class="h-2 rounded-full transition-all duration-500"
3165 style="width: ${normalizedPercentage}%; background-color: ${color}"></div>
3166 </div>
3167 </div>
3168 `;
3169}
3170
3171// Create memory growth chart
3172function createMemoryGrowthChart(timePoints, peakMemory) {
3173 if (timePoints.length < 2) return '<div class="flex items-center justify-center h-full text-gray-400">Insufficient data points</div>';
3174
3175 const chartHeight = 180;
3176 const chartWidth = 100; // percentage
3177
3178 // Create SVG path for the growth line
3179 const pathPoints = timePoints.map((point, index) => {
3180 const x = (index / (timePoints.length - 1)) * chartWidth;
3181 const y = chartHeight - ((point.memory / peakMemory) * (chartHeight - 20));
3182 return `${x},${y}`;
3183 });
3184
3185 return `
3186 <!-- Background Grid -->
3187 <div class="absolute inset-0">
3188 ${[0, 25, 50, 75, 100].map(y => `
3189 <div class="absolute w-full border-t border-gray-200 dark:border-gray-500 opacity-30"
3190 style="top: ${y}%"></div>
3191 `).join('')}
3192 </div>
3193
3194 <!-- Growth Line -->
3195 <svg class="absolute inset-0 w-full h-full" preserveAspectRatio="none">
3196 <polyline
3197 fill="none"
3198 stroke='#27ae60'
3199 stroke-width="3"
3200 stroke-linecap='round'
3201 stroke-linejoin='round'
3202 points='${timePoints.map((point, index) => {
3203 const x = (index / (timePoints.length - 1)) * 100;
3204 const y = 100 - ((point.memory / peakMemory) * 90);
3205 return `${x},${y}`;
3206 }).join(' ')}'
3207 class='drop-shadow-sm'
3208 />
3209 </svg>
3210
3211 <!-- Data Points -->
3212 ${timePoints.map((point, index) => {
3213 const x = (index / (timePoints.length - 1)) * 100;
3214 const y = 100 - ((point.memory / peakMemory) * 90);
3215 return `
3216 <div class="absolute w-3 h-3 bg-green-500 rounded-full border-2 border-white dark:border-gray-600 shadow-sm transform -translate-x-1/2 -translate-y-1/2 hover:scale-125 transition-transform cursor-pointer"
3217 style="left: ${x}%; top: ${y}%"
3218 title="Memory: ${formatBytes(point.memory)} at allocation #${point.allocCount}">
3219 </div>
3220 `;
3221 }).join('')}
3222
3223 <!-- Peak Memory Indicator -->
3224 <div class="absolute w-full border-t-2 border-red-500 border-dashed opacity-60" style="top: 10%">
3225 <div class="absolute -top-1 right-0 text-xs text-red-500 bg-white dark:bg-gray-600 px-1 rounded">
3226 Peak
3227 </div>
3228 </div>
3229 `;
3230}
3231
3232// Helper functions for growth analysis
3233function getGrowthRateColor(rate) {
3234 if (rate < -10) return '#27ae60'; // Green for decreasing
3235 if (rate < 10) return '#3498db'; // Blue for stable
3236 if (rate < 50) return '#f39c12'; // Orange for moderate growth
3237 return '#e74c3c'; // Red for high growth
3238}
3239
3240function getEfficiencyColor(avg, peak) {
3241 const efficiency = (avg / peak) * 100;
3242 if (efficiency > 80) return 'text-red-600 dark:text-red-400';
3243 if (efficiency > 60) return 'text-orange-600 dark:text-orange-400';
3244 if (efficiency > 40) return 'text-yellow-600 dark:text-yellow-400';
3245 return 'text-green-600 dark:text-green-400';
3246}
3247
3248function getEfficiencyBgColor(avg, peak) {
3249 const efficiency = (avg / peak) * 100;
3250 if (efficiency > 80) return 'bg-red-500';
3251 if (efficiency > 60) return 'bg-orange-500';
3252 if (efficiency > 40) return 'bg-yellow-500';
3253 return 'bg-green-500';
3254}
3255
3256function getGrowthAssessment(rate) {
3257 if (rate < -10) return 'Excellent: Memory usage is decreasing, indicating good cleanup.';
3258 if (rate < 10) return 'Good: Stable memory usage with minimal growth.';
3259 if (rate < 50) return 'Moderate: Some memory growth detected, monitor for trends.';
3260 return 'Concerning: High memory growth detected, investigate for potential leaks.';
3261}
3262
3263// Node Detail Panel for Variable Relationship Graph
3264class NodeDetailPanel {
3265 constructor(containerId) {
3266 this.container = document.getElementById(containerId);
3267 this.panel = null;
3268 this.currentNode = null;
3269 }
3270
3271 show(nodeData, position) {
3272 console.log('Showing panel for:', nodeData.id);
3273 this.hide(); // Close existing panel
3274 this.panel = this.createPanel(nodeData);
3275 console.log('Panel created:', this.panel);
3276 this.positionPanel(position);
3277 this.container.appendChild(this.panel);
3278 console.log('Panel added to container');
3279 this.currentNode = nodeData;
3280 }
3281
3282 hide() {
3283 if (this.panel) {
3284 this.panel.remove();
3285 this.panel = null;
3286 this.currentNode = null;
3287 }
3288 }
3289
3290 createPanel(nodeData) {
3291 const panel = document.createElement('div');
3292 panel.className = 'node-detail-panel';
3293
3294 // Find related allocation data
3295 const allocations = window.analysisData.memory_analysis?.allocations || [];
3296 const allocation = allocations.find(alloc => alloc.var_name === nodeData.id);
3297
3298 // Calculate relationships
3299 const sameTypeCount = allocations.filter(alloc =>
3300 alloc.type_name === nodeData.type_name && alloc.var_name !== nodeData.id
3301 ).length;
3302
3303 const sameCategoryCount = allocations.filter(alloc =>
3304 getTypeCategory(alloc.type_name || '') === (nodeData.category || 'primitive') && alloc.var_name !== nodeData.id
3305 ).length;
3306
3307 panel.innerHTML = `
3308 <div class="flex justify-between items-center mb-3">
3309 <h3>Variable Details</h3>
3310 <button class="close-button text-xl leading-none">×</button>
3311 </div>
3312
3313 <div class="space-y-3">
3314 <div>
3315 <label>Variable Name</label>
3316 <p class="font-mono">${nodeData.id}</p>
3317 </div>
3318
3319 <div>
3320 <label>Type</label>
3321 <p class="font-mono">${nodeData.type_name || 'Unknown'}</p>
3322 <div class="flex items-center mt-1">
3323 <div class="w-3 h-3 rounded-full mr-2" style="background-color: ${getEnhancedTypeColor(nodeData.type_name || 'unknown', nodeData.category || 'primitive')}"></div>
3324 <span class="text-xs capitalize">${(nodeData.category || 'primitive').replace('_', ' ')}</span>
3325 </div>
3326 </div>
3327
3328 <div>
3329 <label>Memory Size</label>
3330 <p>${formatBytes(nodeData.size)}</p>
3331 </div>
3332
3333 <div>
3334 <label>Complexity Score</label>
3335 <div class="flex items-center mb-2">
3336 <div class="w-5 h-5 rounded-full mr-2 flex items-center justify-center text-white font-bold text-xs" style="background-color: ${getComplexityColor(nodeData.complexity || 2)}">${nodeData.complexity || 2}</div>
3337 <span class="font-semibold">${nodeData.complexity || 2}/10 - ${getComplexityLevel(nodeData.complexity || 2)}</span>
3338 </div>
3339 <div class="text-xs text-gray-600 dark:text-gray-400">
3340 ${getComplexityExplanation(nodeData.complexity || 2)}
3341 </div>
3342 </div>
3343
3344 ${allocation ? `
3345 <div>
3346 <label>Memory Address</label>
3347 <p class="font-mono text-xs">${allocation.ptr}</p>
3348 </div>
3349
3350 <div>
3351 <label>Allocated At</label>
3352 <p class="text-sm">${new Date(allocation.timestamp_alloc / 1000000).toLocaleString()}</p>
3353 </div>
3354 ` : ''}
3355
3356 <div>
3357 <label>Relationships</label>
3358 <div class="text-sm space-y-1">
3359 <div class="flex justify-between">
3360 <span>Same type:</span>
3361 <span class="font-semibold">${sameTypeCount}</span>
3362 </div>
3363 <div class="flex justify-between">
3364 <span>Same category:</span>
3365 <span class="font-semibold">${sameCategoryCount}</span>
3366 </div>
3367 </div>
3368 </div>
3369
3370 <div>
3371 <label>Type Analysis</label>
3372 <div class="text-xs space-y-1">
3373 ${getTypeAnalysis(nodeData.type_name || 'unknown', nodeData.size)}
3374 </div>
3375 </div>
3376 </div>
3377 `;
3378
3379 // Add close button functionality
3380 const closeButton = panel.querySelector('.close-button');
3381 closeButton.addEventListener('click', () => {
3382 this.hide();
3383 });
3384
3385 return panel;
3386 }
3387
3388 positionPanel(position) {
3389 if (!this.panel) return;
3390
3391 // Simple positioning - place panel at a fixed position relative to container
3392 this.panel.style.position = 'absolute';
3393 this.panel.style.left = '20px';
3394 this.panel.style.top = '20px';
3395 this.panel.style.zIndex = '1000';
3396
3397 console.log('Panel positioned at:', this.panel.style.left, this.panel.style.top);
3398 }
3399}
3400
3401// Initialize variable relationship graph with enhanced D3.js force simulation
3402function initVariableGraph() {
3403 const container = document.getElementById('variable-graph-container');
3404 if (!container) return;
3405
3406 const allocations = window.analysisData.memory_analysis?.allocations || [];
3407 const userAllocations = allocations.filter(alloc =>
3408 alloc.var_name && alloc.var_name !== 'unknown' &&
3409 alloc.type_name && alloc.type_name !== 'unknown'
3410 );
3411
3412 if (userAllocations.length === 0) {
3413 container.innerHTML = `
3414 <div class="flex items-center justify-center h-full text-gray-500 dark:text-gray-400">
3415 <div class="text-center">
3416 <i class="fa fa-sitemap text-4xl mb-4"></i>
3417 <p class="text-lg font-semibold mb-2">No User Variables Found</p>
3418 <p class="text-sm">Use track_var! macro to track variable relationships</p>
3419 </div>
3420 </div>
3421 `;
3422 return;
3423 }
3424
3425 // Clear container
3426 container.innerHTML = '';
3427
3428 // Set up dimensions
3429 const width = container.clientWidth;
3430 const height = container.clientHeight;
3431
3432 // Create SVG
3433 const svg = d3.select(container)
3434 .append('svg')
3435 .attr('width', width)
3436 .attr('height', height)
3437 .style('background', 'transparent');
3438
3439 // Create zoom behavior
3440 const zoom = d3.zoom()
3441 .scaleExtent([0.1, 4])
3442 .on('zoom', (event) => {
3443 g.attr('transform', event.transform);
3444 });
3445
3446 svg.call(zoom);
3447
3448 // Create main group for zooming/panning
3449 const g = svg.append('g');
3450
3451 // Prepare nodes data
3452 const nodes = userAllocations.map((alloc, index) => ({
3453 id: alloc.var_name,
3454 type: alloc.type_name,
3455 size: alloc.size || 0,
3456 complexity: getComplexityFromType(alloc.type_name),
3457 category: getTypeCategory(alloc.type_name),
3458 allocation: alloc
3459 }));
3460
3461 // Create more sophisticated relationships
3462 const links = [];
3463
3464 // Type similarity relationships
3465 for (let i = 0; i < nodes.length; i++) {
3466 for (let j = i + 1; j < nodes.length; j++) {
3467 const node1 = nodes[i];
3468 const node2 = nodes[j];
3469
3470 // Same type relationship
3471 if (node1.type === node2.type) {
3472 links.push({
3473 source: node1.id,
3474 target: node2.id,
3475 type: 'same_type',
3476 strength: 1.0
3477 });
3478 }
3479 // Similar category relationship
3480 else if (node1.category === node2.category && node1.category !== 'primitive') {
3481 links.push({
3482 source: node1.id,
3483 target: node2.id,
3484 type: 'similar_category',
3485 strength: 0.6
3486 });
3487 }
3488 // Generic type relationship (Vec<T>, Box<T>, etc.)
3489 else if (getGenericBase(node1.type) === getGenericBase(node2.type)) {
3490 links.push({
3491 source: node1.id,
3492 target: node2.id,
3493 type: 'generic_family',
3494 strength: 0.8
3495 });
3496 }
3497 }
3498 }
3499
3500 // Create force simulation
3501 const simulation = d3.forceSimulation(nodes)
3502 .force('link', d3.forceLink(links)
3503 .id(d => d.id)
3504 .distance(d => 80 + (1 - d.strength) * 40)
3505 .strength(d => d.strength * 0.7)
3506 )
3507 .force('charge', d3.forceManyBody()
3508 .strength(d => -200 - (d.size / 100))
3509 )
3510 .force('center', d3.forceCenter(width / 2, height / 2))
3511 .force('collision', d3.forceCollide()
3512 .radius(d => {
3513 const minRadius = 15;
3514 const maxRadius = 50;
3515 const maxSize = Math.max(...nodes.map(n => n.size));
3516 const sizeRatio = maxSize > 0 ? d.size / maxSize : 0;
3517 const nodeRadius = minRadius + (sizeRatio * (maxRadius - minRadius));
3518 return nodeRadius + 5;
3519 })
3520 );
3521
3522 // Create link elements
3523 const link = g.append('g')
3524 .attr('class', 'links')
3525 .selectAll('line')
3526 .data(links)
3527 .enter().append('line')
3528 .attr('stroke', d => getLinkColor(d.type))
3529 .attr('stroke-opacity', d => 0.3 + d.strength * 0.4)
3530 .attr('stroke-width', d => 1 + d.strength * 2)
3531 .attr('stroke-dasharray', d => d.type === 'similar_category' ? '5,5' : null);
3532
3533 // Create node groups
3534 const node = g.append('g')
3535 .attr('class', 'nodes')
3536 .selectAll('g')
3537 .data(nodes)
3538 .enter().append('g')
3539 .attr('class', 'graph-node')
3540 .style('cursor', 'pointer')
3541 .call(d3.drag()
3542 .on('start', dragstarted)
3543 .on('drag', dragged)
3544 .on('end', dragended)
3545 );
3546
3547 // Add circles to nodes - size based on memory usage
3548 node.append('circle')
3549 .attr('r', d => {
3550 // Scale node size based on memory usage (larger memory = larger node)
3551 const minRadius = 15;
3552 const maxRadius = 50;
3553 const maxSize = Math.max(...nodes.map(n => n.size));
3554 const sizeRatio = maxSize > 0 ? d.size / maxSize : 0;
3555 return minRadius + (sizeRatio * (maxRadius - minRadius));
3556 })
3557 .attr('fill', d => getEnhancedTypeColor(d.type, d.category))
3558 .attr('stroke', '#fff')
3559 .attr('stroke-width', 2)
3560 .style('filter', 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))')
3561 .on('mouseover', function (event, d) {
3562 const currentRadius = d3.select(this).attr('r');
3563 d3.select(this)
3564 .transition()
3565 .duration(200)
3566 .attr('r', parseFloat(currentRadius) * 1.2)
3567 .style('filter', 'drop-shadow(0px 4px 8px rgba(0,0,0,0.3))');
3568
3569 // Highlight connected links
3570 link.style('stroke-opacity', l =>
3571 (l.source.id === d.id || l.target.id === d.id) ? 0.8 : 0.1
3572 );
3573 })
3574 .on('mouseout', function (event, d) {
3575 const minRadius = 15;
3576 const maxRadius = 50;
3577 const maxSize = Math.max(...nodes.map(n => n.size));
3578 const sizeRatio = maxSize > 0 ? d.size / maxSize : 0;
3579 const originalRadius = minRadius + (sizeRatio * (maxRadius - minRadius));
3580
3581 d3.select(this)
3582 .transition()
3583 .duration(200)
3584 .attr('r', originalRadius)
3585 .style('filter', 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))');
3586
3587 // Reset link opacity
3588 link.style('stroke-opacity', l => 0.3 + l.strength * 0.4);
3589 });
3590
3591 // Add complexity indicators (small circles with numbers)
3592 const complexityGroup = node.append('g')
3593 .attr('class', 'complexity-indicator');
3594
3595 complexityGroup.append('circle')
3596 .attr('r', 8)
3597 .attr('cx', d => {
3598 const minRadius = 15;
3599 const maxRadius = 50;
3600 const maxSize = Math.max(...nodes.map(n => n.size));
3601 const sizeRatio = maxSize > 0 ? d.size / maxSize : 0;
3602 const nodeRadius = minRadius + (sizeRatio * (maxRadius - minRadius));
3603 return nodeRadius + 8;
3604 })
3605 .attr('cy', d => {
3606 const minRadius = 15;
3607 const maxRadius = 50;
3608 const maxSize = Math.max(...nodes.map(n => n.size));
3609 const sizeRatio = maxSize > 0 ? d.size / maxSize : 0;
3610 const nodeRadius = minRadius + (sizeRatio * (maxRadius - minRadius));
3611 return -nodeRadius - 8;
3612 })
3613 .attr('fill', d => getComplexityColor(d.complexity))
3614 .attr('stroke', '#fff')
3615 .attr('stroke-width', 2);
3616
3617 // Add complexity score text
3618 complexityGroup.append('text')
3619 .text(d => d.complexity || 2)
3620 .attr('x', d => {
3621 const minRadius = 15;
3622 const maxRadius = 50;
3623 const maxSize = Math.max(...nodes.map(n => n.size));
3624 const sizeRatio = maxSize > 0 ? d.size / maxSize : 0;
3625 const nodeRadius = minRadius + (sizeRatio * (maxRadius - minRadius));
3626 return nodeRadius + 8;
3627 })
3628 .attr('y', d => {
3629 const minRadius = 15;
3630 const maxRadius = 50;
3631 const maxSize = Math.max(...nodes.map(n => n.size));
3632 const sizeRatio = maxSize > 0 ? d.size / maxSize : 0;
3633 const nodeRadius = minRadius + (sizeRatio * (maxRadius - minRadius));
3634 return -nodeRadius - 8 + 3;
3635 })
3636 .attr('text-anchor', 'middle')
3637 .style('font-size', '10px')
3638 .style('font-weight', 'bold')
3639 .style('fill', '#fff')
3640 .style('pointer-events', 'none');
3641
3642 // Add variable names
3643 node.append('text')
3644 .text(d => d.id)
3645 .attr('text-anchor', 'middle')
3646 .attr('dy', d => {
3647 const minRadius = 15;
3648 const maxRadius = 50;
3649 const maxSize = Math.max(...nodes.map(n => n.size));
3650 const sizeRatio = maxSize > 0 ? d.size / maxSize : 0;
3651 const nodeRadius = minRadius + (sizeRatio * (maxRadius - minRadius));
3652 return nodeRadius + 15;
3653 })
3654 .style('font-size', '11px')
3655 .style('font-weight', 'bold')
3656 .style('fill', 'var(--text-primary)')
3657 .style('pointer-events', 'none');
3658
3659 // Add type labels
3660 node.append('text')
3661 .text(d => formatTypeName(d.type))
3662 .attr('text-anchor', 'middle')
3663 .attr('dy', d => {
3664 const minRadius = 15;
3665 const maxRadius = 50;
3666 const maxSize = Math.max(...nodes.map(n => n.size));
3667 const sizeRatio = maxSize > 0 ? d.size / maxSize : 0;
3668 const nodeRadius = minRadius + (sizeRatio * (maxRadius - minRadius));
3669 return nodeRadius + 28;
3670 })
3671 .style('font-size', '9px')
3672 .style('fill', 'var(--text-secondary)')
3673 .style('pointer-events', 'none');
3674
3675 // Add click interaction
3676 const detailPanel = new NodeDetailPanel('variable-graph-container');
3677
3678 node.on('click', function (event, d) {
3679 event.stopPropagation();
3680 console.log('Node clicked:', d.id, d);
3681 const position = {
3682 x: event.pageX,
3683 y: event.pageY
3684 };
3685 detailPanel.show(d, position);
3686 });
3687
3688 // Click on empty space to hide panel
3689 svg.on('click', function (event) {
3690 if (event.target === this) {
3691 detailPanel.hide();
3692 }
3693 });
3694
3695 // Update positions on simulation tick
3696 simulation.on('tick', () => {
3697 link
3698 .attr('x1', d => d.source.x)
3699 .attr('y1', d => d.source.y)
3700 .attr('x2', d => d.target.x)
3701 .attr('y2', d => d.target.y);
3702
3703 node
3704 .attr('transform', d => `translate(${d.x},${d.y})`);
3705 });
3706
3707 // Add control buttons
3708 const controls = d3.select(container)
3709 .append('div')
3710 .attr('class', 'absolute top-2 right-2 flex space-x-2');
3711
3712 controls.append('button')
3713 .attr('class', 'px-3 py-1 bg-blue-500 hover:bg-blue-600 text-white text-xs rounded transition-colors')
3714 .text('Reset View')
3715 .on('click', () => {
3716 svg.transition().duration(750).call(
3717 zoom.transform,
3718 d3.zoomIdentity
3719 );
3720 });
3721
3722 controls.append('button')
3723 .attr('class', 'px-3 py-1 bg-green-500 hover:bg-green-600 text-white text-xs rounded transition-colors')
3724 .text('Reheat')
3725 .on('click', () => {
3726 simulation.alpha(0.3).restart();
3727 });
3728
3729 // Drag functions
3730 function dragstarted(event, d) {
3731 if (!event.active) simulation.alphaTarget(0.3).restart();
3732 d.fx = d.x;
3733 d.fy = d.y;
3734 }
3735
3736 function dragged(event, d) {
3737 d.fx = event.x;
3738 d.fy = event.y;
3739 }
3740
3741 function dragended(event, d) {
3742 if (!event.active) simulation.alphaTarget(0);
3743 d.fx = null;
3744 d.fy = null;
3745 }
3746}
3747
3748// Get color for variable type
3749function getTypeColor(typeName) {
3750 if (typeName.includes('Vec')) return '#3b82f6';
3751 if (typeName.includes('Box')) return '#8b5cf6';
3752 if (typeName.includes('Rc') || typeName.includes('Arc')) return '#10b981';
3753 if (typeName.includes('String')) return '#f59e0b';
3754 return '#6b7280';
3755}
3756
3757// Enhanced type color with comprehensive type mapping
3758function getEnhancedTypeColor(typeName, category) {
3759 // Comprehensive color mapping for specific types
3760 const typeColorMap = {
3761 // Smart Pointers - Purple/Violet family
3762 'Box': '#8b5cf6', // Purple
3763 'Rc': '#a855f7', // Purple-500
3764 'Arc': '#9333ea', // Violet-600
3765 'RefCell': '#7c3aed', // Violet-700
3766 'Cell': '#6d28d9', // Violet-800
3767 'Weak': '#5b21b6', // Violet-900
3768
3769 // Collections - Blue family
3770 'Vec': '#3b82f6', // Blue-500
3771 'VecDeque': '#2563eb', // Blue-600
3772 'LinkedList': '#1d4ed8', // Blue-700
3773 'HashMap': '#1e40af', // Blue-800
3774 'BTreeMap': '#1e3a8a', // Blue-900
3775 'HashSet': '#60a5fa', // Blue-400
3776 'BTreeSet': '#93c5fd', // Blue-300
3777
3778 // String types - Orange/Amber family
3779 'String': '#f59e0b', // Amber-500
3780 'str': '#d97706', // Amber-600
3781 'OsString': '#b45309', // Amber-700
3782 'OsStr': '#92400e', // Amber-800
3783 'CString': '#78350f', // Amber-900
3784 'CStr': '#fbbf24', // Amber-400
3785
3786 // Numeric types - Green family
3787 'i8': '#10b981', // Emerald-500
3788 'i16': '#059669', // Emerald-600
3789 'i32': '#047857', // Emerald-700
3790 'i64': '#065f46', // Emerald-800
3791 'i128': '#064e3b', // Emerald-900
3792 'u8': '#34d399', // Emerald-400
3793 'u16': '#6ee7b7', // Emerald-300
3794 'u32': '#a7f3d0', // Emerald-200
3795 'u64': '#d1fae5', // Emerald-100
3796 'u128': '#ecfdf5', // Emerald-50
3797 'f32': '#14b8a6', // Teal-500
3798 'f64': '#0d9488', // Teal-600
3799 'usize': '#0f766e', // Teal-700
3800 'isize': '#115e59', // Teal-800
3801
3802 // Boolean and char - Pink family
3803 'bool': '#ec4899', // Pink-500
3804 'char': '#db2777', // Pink-600
3805
3806 // Option and Result - Indigo family
3807 'Option': '#6366f1', // Indigo-500
3808 'Result': '#4f46e5', // Indigo-600
3809 'Some': '#4338ca', // Indigo-700
3810 'None': '#3730a3', // Indigo-800
3811 'Ok': '#312e81', // Indigo-900
3812 'Err': '#6366f1', // Indigo-500
3813
3814 // Synchronization types - Red family
3815 'Mutex': '#ef4444', // Red-500
3816 'RwLock': '#dc2626', // Red-600
3817 'Condvar': '#b91c1c', // Red-700
3818 'Barrier': '#991b1b', // Red-800
3819 'Once': '#7f1d1d', // Red-900
3820
3821 // Channel types - Cyan family
3822 'Sender': '#06b6d4', // Cyan-500
3823 'Receiver': '#0891b2', // Cyan-600
3824 'mpsc': '#0e7490', // Cyan-700
3825
3826 // Path types - Lime family
3827 'Path': '#84cc16', // Lime-500
3828 'PathBuf': '#65a30d', // Lime-600
3829
3830 // Time types - Yellow family
3831 'Duration': '#eab308', // Yellow-500
3832 'Instant': '#ca8a04', // Yellow-600
3833 'SystemTime': '#a16207', // Yellow-700
3834
3835 // IO types - Stone family
3836 'File': '#78716c', // Stone-500
3837 'BufReader': '#57534e', // Stone-600
3838 'BufWriter': '#44403c', // Stone-700
3839
3840 // Thread types - Rose family
3841 'Thread': '#f43f5e', // Rose-500
3842 'JoinHandle': '#e11d48', // Rose-600
3843
3844 // Custom/Unknown types - Gray family
3845 'unknown': '#6b7280', // Gray-500
3846 'custom': '#4b5563', // Gray-600
3847 };
3848
3849 // First, try exact type name match
3850 if (typeColorMap[typeName]) {
3851 return typeColorMap[typeName];
3852 }
3853
3854 // Then try to match by type name contains
3855 for (const [type, color] of Object.entries(typeColorMap)) {
3856 if (typeName.includes(type)) {
3857 return color;
3858 }
3859 }
3860
3861 // Extract generic base type and try to match
3862 const genericBase = getGenericBase(typeName);
3863 if (typeColorMap[genericBase]) {
3864 return typeColorMap[genericBase];
3865 }
3866
3867 // Fall back to category-based colors
3868 switch (category) {
3869 case 'smart_pointer': return '#8b5cf6'; // Purple
3870 case 'collection': return '#3b82f6'; // Blue
3871 case 'string': return '#f59e0b'; // Amber
3872 case 'numeric': return '#10b981'; // Emerald
3873 case 'sync': return '#ef4444'; // Red
3874 case 'channel': return '#06b6d4'; // Cyan
3875 case 'path': return '#84cc16'; // Lime
3876 case 'time': return '#eab308'; // Yellow
3877 case 'io': return '#78716c'; // Stone
3878 case 'thread': return '#f43f5e'; // Rose
3879 default: return '#6b7280'; // Gray
3880 }
3881}
3882
3883// Get type category for grouping with comprehensive type recognition
3884function getTypeCategory(typeName) {
3885 // Smart pointers
3886 if (typeName.includes('Box') || typeName.includes('Rc') || typeName.includes('Arc') ||
3887 typeName.includes('RefCell') || typeName.includes('Cell') || typeName.includes('Weak')) {
3888 return 'smart_pointer';
3889 }
3890
3891 // Collections
3892 if (typeName.includes('Vec') || typeName.includes('HashMap') || typeName.includes('BTreeMap') ||
3893 typeName.includes('HashSet') || typeName.includes('BTreeSet') || typeName.includes('VecDeque') ||
3894 typeName.includes('LinkedList')) {
3895 return 'collection';
3896 }
3897
3898 // String types
3899 if (typeName.includes('String') || typeName.includes('str') || typeName.includes('OsString') ||
3900 typeName.includes('OsStr') || typeName.includes('CString') || typeName.includes('CStr')) {
3901 return 'string';
3902 }
3903
3904 // Numeric types
3905 if (typeName.match(/^[iuf]\d+$/) || typeName === 'usize' || typeName === 'isize' ||
3906 typeName === 'bool' || typeName === 'char') {
3907 return 'numeric';
3908 }
3909
3910 // Synchronization types
3911 if (typeName.includes('Mutex') || typeName.includes('RwLock') || typeName.includes('Condvar') ||
3912 typeName.includes('Barrier') || typeName.includes('Once')) {
3913 return 'sync';
3914 }
3915
3916 // Channel types
3917 if (typeName.includes('Sender') || typeName.includes('Receiver') || typeName.includes('mpsc')) {
3918 return 'channel';
3919 }
3920
3921 // Path types
3922 if (typeName.includes('Path') || typeName.includes('PathBuf')) {
3923 return 'path';
3924 }
3925
3926 // Time types
3927 if (typeName.includes('Duration') || typeName.includes('Instant') || typeName.includes('SystemTime')) {
3928 return 'time';
3929 }
3930
3931 // IO types
3932 if (typeName.includes('File') || typeName.includes('BufReader') || typeName.includes('BufWriter')) {
3933 return 'io';
3934 }
3935
3936 // Thread types
3937 if (typeName.includes('Thread') || typeName.includes('JoinHandle')) {
3938 return 'thread';
3939 }
3940
3941 // Option and Result
3942 if (typeName.includes('Option') || typeName.includes('Result')) {
3943 return 'option_result';
3944 }
3945
3946 return 'primitive';
3947}
3948
3949// Get generic base type (Vec<T> -> Vec, Box<T> -> Box)
3950function getGenericBase(typeName) {
3951 const match = typeName.match(/^([^<]+)/);
3952 return match ? match[1] : typeName;
3953}
3954
3955// Get complexity score from type with comprehensive scoring
3956function getComplexityFromType(typeName) {
3957 // Very high complexity (9-10)
3958 if (typeName.includes('HashMap') || typeName.includes('BTreeMap') ||
3959 typeName.includes('BTreeSet') || typeName.includes('LinkedList')) return 9;
3960
3961 // High complexity (7-8)
3962 if (typeName.includes('Arc') || typeName.includes('Mutex') || typeName.includes('RwLock') ||
3963 typeName.includes('Condvar') || typeName.includes('Barrier')) return 8;
3964 if (typeName.includes('Rc') || typeName.includes('RefCell') || typeName.includes('HashSet') ||
3965 typeName.includes('VecDeque')) return 7;
3966
3967 // Medium complexity (5-6)
3968 if (typeName.includes('Vec') || typeName.includes('Box') || typeName.includes('Option') ||
3969 typeName.includes('Result')) return 6;
3970 if (typeName.includes('String') || typeName.includes('PathBuf') || typeName.includes('OsString') ||
3971 typeName.includes('CString')) return 5;
3972
3973 // Low complexity (3-4)
3974 if (typeName.includes('str') || typeName.includes('Path') || typeName.includes('OsStr') ||
3975 typeName.includes('CStr') || typeName.includes('Duration') || typeName.includes('Instant')) return 4;
3976 if (typeName.includes('Sender') || typeName.includes('Receiver') || typeName.includes('File') ||
3977 typeName.includes('Thread') || typeName.includes('JoinHandle')) return 3;
3978
3979 // Very low complexity (1-2)
3980 if (typeName.match(/^[iuf]\d+$/) || typeName === 'usize' || typeName === 'isize' ||
3981 typeName === 'bool' || typeName === 'char') return 1;
3982
3983 // Default for unknown types
3984 return 2;
3985}
3986
3987// Get link color based on relationship type
3988function getLinkColor(linkType) {
3989 switch (linkType) {
3990 case 'same_type': return '#ef4444';
3991 case 'similar_category': return '#3b82f6';
3992 case 'generic_family': return '#10b981';
3993 default: return '#6b7280';
3994 }
3995}
3996
3997// Get complexity level description
3998function getComplexityLevel(score) {
3999 if (score <= 2) return 'Simple';
4000 if (score <= 5) return 'Medium';
4001 if (score <= 8) return 'Complex';
4002 return 'Very Complex';
4003}
4004
4005// Get complexity explanation
4006function getComplexityExplanation(score) {
4007 if (score <= 2) return 'Basic types with minimal performance overhead and simple memory usage';
4008 if (score <= 5) return 'Medium complexity with some memory management overhead';
4009 if (score <= 8) return 'Complex types involving heap allocation and smart pointers, performance considerations needed';
4010 return 'Very complex types with significant performance overhead, optimization recommended';
4011}
4012
4013// Get type analysis information
4014function getTypeAnalysis(typeName, size) {
4015 const analysis = [];
4016
4017 if (typeName.includes('Vec')) {
4018 analysis.push('โข Dynamic array with heap allocation');
4019 analysis.push('โข Grows automatically as needed');
4020 if (size > 1000) analysis.push('โข Large allocation - consider capacity optimization');
4021 } else if (typeName.includes('Box')) {
4022 analysis.push('โข Single heap allocation');
4023 analysis.push('โข Unique ownership semantics');
4024 } else if (typeName.includes('Rc')) {
4025 analysis.push('โข Reference counted smart pointer');
4026 analysis.push('โข Shared ownership with runtime checks');
4027 } else if (typeName.includes('Arc')) {
4028 analysis.push('โข Atomic reference counted pointer');
4029 analysis.push('โข Thread-safe shared ownership');
4030 } else if (typeName.includes('String')) {
4031 analysis.push('โข Growable UTF-8 string');
4032 analysis.push('โข Heap allocated with capacity buffer');
4033 } else {
4034 analysis.push('โข Basic type allocation');
4035 }
4036
4037 if (size === 0) {
4038 analysis.push('โข Zero-sized type (ZST)');
4039 } else if (size < 64) {
4040 analysis.push('โข Small allocation - good for performance');
4041 } else if (size > 1024) {
4042 analysis.push('โข Large allocation - monitor memory usage');
4043 }
4044
4045 return analysis.join('<br>');
4046}
4047
4048// Initialize generic types table
4049function initGenericTypesTable() {
4050 const tbody = document.getElementById('generic-types-table-body');
4051 if (!tbody) return;
4052
4053 const genericTypes = window.analysisData.complex_types?.categorized_types?.generic_types || [];
4054
4055 if (genericTypes.length === 0) {
4056 tbody.innerHTML = '<tr><td colspan="6" class="px-6 py-8 text-center text-gray-500 dark:text-gray-400">No generic types found</td></tr>';
4057 return;
4058 }
4059
4060 tbody.innerHTML = genericTypes.map(type => `
4061 <tr class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
4062 <td class="px-6 py-4 text-gray-900 dark:text-gray-100">${type.var_name || 'System Allocation'}</td>
4063 <td class="px-6 py-4 text-gray-900 dark:text-gray-100">${formatTypeName(type.type_name || 'System Allocation')}</td>
4064 <td class="px-6 py-4 font-mono text-xs text-gray-900 dark:text-gray-100">${type.ptr}</td>
4065 <td class="px-6 py-4 text-gray-900 dark:text-gray-100">${formatBytes(type.size || 0)}</td>
4066 <td class="px-6 py-4 text-gray-900 dark:text-gray-100">N/A</td>
4067 <td class="px-6 py-4">
4068 <span class="px-2 py-1 rounded text-xs ${getComplexityColor(type.complexity_score)} text-white">
4069 ${type.complexity_score || 0}
4070 </span>
4071 </td>
4072 </tr>
4073 `).join('');
4074}
4075
4076// Initialize complex type analysis
4077function initComplexTypeAnalysis() {
4078 const tbody = document.getElementById('complex-type-analysis-table');
4079 if (!tbody) return;
4080
4081 const complexTypeAnalysis = window.analysisData.complex_types?.complex_type_analysis || [];
4082
4083 if (complexTypeAnalysis.length === 0) {
4084 tbody.innerHTML = '<tr><td colspan="6" class="px-6 py-8 text-center text-gray-500 dark:text-gray-400">No complex type analysis available</td></tr>';
4085 return;
4086 }
4087
4088 tbody.innerHTML = complexTypeAnalysis.map(analysis => `
4089 <tr class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
4090 <td class="px-6 py-4 text-gray-900 dark:text-gray-100">${formatTypeName(analysis.type_name)}</td>
4091 <td class="px-6 py-4 text-center">
4092 <span class="px-2 py-1 rounded text-xs ${getComplexityColor(analysis.complexity_score)} text-white">
4093 ${analysis.complexity_score}
4094 </span>
4095 </td>
4096 <td class="px-6 py-4 text-center">
4097 <span class="px-2 py-1 rounded text-xs ${getEfficiencyColor(analysis.memory_efficiency)} text-white">
4098 ${analysis.memory_efficiency}%
4099 </span>
4100 </td>
4101 <td class="px-6 py-4 text-center text-gray-900 dark:text-gray-100">${analysis.allocation_count || 0}</td>
4102 <td class="px-6 py-4 text-center text-gray-900 dark:text-gray-100">${formatBytes(analysis.total_size || 0)}</td>
4103 <td class="px-6 py-4 text-gray-700 dark:text-gray-300">
4104 ${Array.isArray(analysis.optimization_suggestions) && analysis.optimization_suggestions.length > 0
4105 ? analysis.optimization_suggestions.join(', ')
4106 : '<span class="text-gray-400 italic">No optimization suggestions available</span>'}
4107 </td>
4108 </tr>
4109 `).join('');
4110}
4111
4112// Initialize memory optimization recommendations
4113function initMemoryOptimizationRecommendations() {
4114 const container = document.getElementById('memory-optimization-recommendations');
4115 if (!container) return;
4116
4117 const recommendations = window.analysisData.complex_types?.optimization_recommendations || [];
4118
4119 if (recommendations.length === 0) {
4120 container.innerHTML = '<li class="text-gray-500 dark:text-gray-400">No specific recommendations available</li>';
4121 return;
4122 }
4123
4124 container.innerHTML = recommendations.map(rec => `
4125 <li class="flex items-start">
4126 <i class="fa fa-lightbulb-o text-yellow-500 mr-2 mt-1"></i>
4127 <span class="dark:text-gray-200">${rec}</span>
4128 </li>
4129 `).join('');
4130}
4131
4132// Initialize FFI risk chart
4133function initFFIRiskChart() {
4134 // Guard: ensure canvas isn't holding an existing chart instance
4135 try {
4136 if (window.chartInstances && window.chartInstances['ffi-risk-chart']) {
4137 window.chartInstances['ffi-risk-chart'].destroy();
4138 delete window.chartInstances['ffi-risk-chart'];
4139 }
4140 } catch (_) {}
4141
4142 const ctx = document.getElementById('ffi-risk-chart');
4143 if (!ctx) return;
4144
4145 const ffiData = window.analysisData.unsafe_ffi?.enhanced_ffi_data || [];
4146
4147 const riskLevels = {
4148 'Low Risk': ffiData.filter(item => (item.safety_violations || 0) === 0).length,
4149 'Medium Risk': ffiData.filter(item => (item.safety_violations || 0) > 0 && (item.safety_violations || 0) <= 2).length,
4150 'High Risk': ffiData.filter(item => (item.safety_violations || 0) > 2).length
4151 };
4152
4153 const isDark = document.documentElement.classList.contains('dark');
4154
4155 // Destroy existing chart if it exists
4156 if (window.chartInstances['ffi-risk-chart']) {
4157 window.chartInstances['ffi-risk-chart'].destroy();
4158 }
4159
4160 window.chartInstances['ffi-risk-chart'] = new Chart(ctx, {
4161 type: 'doughnut',
4162 data: {
4163 labels: Object.keys(riskLevels),
4164 datasets: [{
4165 data: Object.values(riskLevels),
4166 backgroundColor: ['#10b981', '#f59e0b', '#ef4444'],
4167 borderColor: isDark ? '#374151' : '#ffffff',
4168 borderWidth: 2
4169 }]
4170 },
4171 options: {
4172 responsive: true,
4173 maintainAspectRatio: false,
4174 plugins: {
4175 legend: {
4176 labels: {
4177 color: isDark ? '#f9fafb' : '#374151',
4178 font: {
4179 size: 12
4180 }
4181 }
4182 },
4183 tooltip: {
4184 backgroundColor: isDark ? '#1f2937' : '#ffffff',
4185 titleColor: isDark ? '#f9fafb' : '#374151',
4186 bodyColor: isDark ? '#f9fafb' : '#374151',
4187 borderColor: isDark ? '#374151' : '#e5e7eb',
4188 borderWidth: 1
4189 }
4190 }
4191 }
4192 });
4193}
4194
4195// Initialize complex type analysis chart
4196function initComplexTypeAnalysisChart() {
4197 const ctx = document.getElementById('complex-type-analysis-chart');
4198 if (!ctx) return;
4199
4200 const complexTypeAnalysis = window.analysisData.complex_types?.complex_type_analysis || [];
4201
4202 if (complexTypeAnalysis.length === 0) {
4203 // Show empty state
4204 const container = ctx.parentElement;
4205 container.innerHTML = `
4206 <div class="h-64 flex items-center justify-center text-gray-500 dark:text-gray-400">
4207 <div class="text-center">
4208 <i class="fa fa-chart-bar text-4xl mb-4"></i>
4209 <p class="text-lg font-semibold mb-2">No Complex Type Data</p>
4210 <p class="text-sm">No complex type analysis data available</p>
4211 </div>
4212 </div>
4213 `;
4214 return;
4215 }
4216
4217 const isDark = document.documentElement.classList.contains('dark');
4218
4219 // Destroy existing chart if it exists
4220 if (window.chartInstances['complex-type-analysis-chart']) {
4221 window.chartInstances['complex-type-analysis-chart'].destroy();
4222 }
4223
4224 window.chartInstances['complex-type-analysis-chart'] = new Chart(ctx, {
4225 type: 'scatter',
4226 data: {
4227 datasets: [{
4228 label: 'Type Complexity vs Memory Efficiency',
4229 data: complexTypeAnalysis.map(analysis => ({
4230 x: analysis.complexity_score || 0,
4231 y: analysis.memory_efficiency || 0,
4232 typeName: analysis.type_name
4233 })),
4234 backgroundColor: 'rgba(59, 130, 246, 0.6)',
4235 borderColor: 'rgba(59, 130, 246, 1)',
4236 borderWidth: 2,
4237 pointRadius: 6,
4238 pointHoverRadius: 8
4239 }]
4240 },
4241 options: {
4242 responsive: true,
4243 maintainAspectRatio: false,
4244 scales: {
4245 x: {
4246 title: {
4247 display: true,
4248 text: 'Complexity Score',
4249 color: isDark ? '#f9fafb' : '#374151'
4250 },
4251 ticks: {
4252 color: isDark ? '#d1d5db' : '#6b7280'
4253 },
4254 grid: {
4255 color: isDark ? '#374151' : '#e5e7eb'
4256 }
4257 },
4258 y: {
4259 title: {
4260 display: true,
4261 text: 'Memory Efficiency (%)',
4262 color: isDark ? '#f9fafb' : '#374151'
4263 },
4264 ticks: {
4265 color: isDark ? '#d1d5db' : '#6b7280'
4266 },
4267 grid: {
4268 color: isDark ? '#374151' : '#e5e7eb'
4269 }
4270 }
4271 },
4272 plugins: {
4273 legend: {
4274 labels: {
4275 color: isDark ? '#f9fafb' : '#374151'
4276 }
4277 },
4278 tooltip: {
4279 backgroundColor: isDark ? '#1f2937' : '#ffffff',
4280 titleColor: isDark ? '#f9fafb' : '#374151',
4281 bodyColor: isDark ? '#f9fafb' : '#374151',
4282 borderColor: isDark ? '#374151' : '#e5e7eb',
4283 borderWidth: 1,
4284 callbacks: {
4285 title: function (context) {
4286 return context[0].raw.typeName || 'Unknown Type';
4287 },
4288 label: function (context) {
4289 return [
4290 `Complexity: ${context.parsed.x}`,
4291 `Efficiency: ${context.parsed.y}%`
4292 ];
4293 }
4294 }
4295 }
4296 }
4297 }
4298 });
4299}
4300
4301// Format type name for better display
4302function formatTypeName(typeName) {
4303 if (!typeName || typeName === 'unknown') return 'System Allocation';
4304 // Simplify complex type names
4305 return typeName
4306 .replace(/alloc::/g, '')
4307 .replace(/std::/g, '')
4308 .replace(/::Vec/g, 'Vec')
4309 .replace(/::Box/g, 'Box')
4310 .replace(/::Rc/g, 'Rc')
4311 .replace(/::Arc/g, 'Arc')
4312 .replace(/::String/g, 'String');
4313}
4314
4315// Format timestamp relative to start time
4316function formatTimestamp(timestamp, minTime) {
4317 const relativeMs = Math.round((timestamp - minTime) / 1000000); // Convert nanoseconds to milliseconds
4318 return `${relativeMs}ms`;
4319}
4320
4321// Enhanced summary statistics with comprehensive data analysis
4322function initEnhancedSummaryStats() {
4323 console.log('๐ Initializing enhanced summary statistics...');
4324
4325 try {
4326 // Get merged data from all sources
4327 const memoryAllocations = window.analysisData.memory_analysis?.allocations || [];
4328 const complexAllocations = window.analysisData.complex_types?.allocations || [];
4329 const unsafeAllocations = window.analysisData.unsafe_ffi?.allocations || [];
4330
4331 // Merge all data sources for comprehensive analysis
4332 const allData = mergeAllDataSources(memoryAllocations, complexAllocations, unsafeAllocations);
4333
4334 // Calculate comprehensive statistics
4335 const stats = calculateComprehensiveStats(allData);
4336
4337 // Update enhanced dashboard
4338 updateElement('total-allocations', stats.totalAllocations);
4339 updateElement('allocation-rate', `${stats.allocationRate.toFixed(1)}/ms`);
4340 updateElement('active-variables', stats.activeVariables);
4341 updateElement('variable-types', `${stats.uniqueTypes} types`);
4342 updateElement('borrow-operations', stats.totalBorrows);
4343 updateElement('max-concurrent', `Max: ${stats.maxConcurrent}`);
4344 updateElement('safety-score', `${stats.safetyScore}%`);
4345 updateElement('ffi-tracked', `${stats.ffiTracked} FFI`);
4346
4347 console.log('โ
Enhanced dashboard updated successfully');
4348 } catch (error) {
4349 console.error('โ Error initializing enhanced stats:', error);
4350 }
4351}
4352
4353// Merge data from all sources with comprehensive field mapping
4354function mergeAllDataSources(memory, complex, unsafe) {
4355 const dataMap = new Map();
4356
4357 // Add memory analysis data (has lifetime_ms)
4358 memory.forEach(alloc => {
4359 if (alloc.ptr) {
4360 dataMap.set(alloc.ptr, { ...alloc, source: 'memory' });
4361 }
4362 });
4363
4364 // Merge complex types data (has extended fields)
4365 complex.forEach(alloc => {
4366 if (alloc.ptr) {
4367 const existing = dataMap.get(alloc.ptr) || {};
4368 dataMap.set(alloc.ptr, {
4369 ...existing,
4370 ...alloc,
4371 source: existing.source ? `${existing.source}+complex` : 'complex'
4372 });
4373 }
4374 });
4375
4376 // Merge unsafe FFI data (has safety info)
4377 unsafe.forEach(alloc => {
4378 if (alloc.ptr) {
4379 const existing = dataMap.get(alloc.ptr) || {};
4380 dataMap.set(alloc.ptr, {
4381 ...existing,
4382 ...alloc,
4383 source: existing.source ? `${existing.source}+unsafe` : 'unsafe'
4384 });
4385 }
4386 });
4387
4388 return Array.from(dataMap.values());
4389}
4390
4391// Calculate comprehensive statistics from merged data
4392function calculateComprehensiveStats(allData) {
4393 const validData = allData.filter(d => d.var_name && d.var_name !== 'unknown');
4394
4395 // Basic counts
4396 const totalAllocations = validData.length;
4397 const uniqueVars = new Set(validData.map(d => d.var_name)).size;
4398 const uniqueTypes = new Set(validData.map(d => d.type_name)).size;
4399
4400 // Time-based calculations
4401 const timestamps = validData.map(d => d.timestamp_alloc).filter(t => t);
4402 const timeRange = timestamps.length > 0 ? (Math.max(...timestamps) - Math.min(...timestamps)) / 1000000 : 1;
4403 const allocationRate = totalAllocations / Math.max(timeRange, 1);
4404
4405 // Borrow analysis
4406 let totalBorrows = 0;
4407 let maxConcurrent = 0;
4408 validData.forEach(d => {
4409 if (d.borrow_info) {
4410 totalBorrows += (d.borrow_info.immutable_borrows || 0) + (d.borrow_info.mutable_borrows || 0);
4411 maxConcurrent = Math.max(maxConcurrent, d.borrow_info.max_concurrent_borrows || 0);
4412 }
4413 });
4414
4415 // Safety analysis
4416 const ffiTracked = validData.filter(d => d.ffi_tracked).length;
4417 const leaked = validData.filter(d => d.is_leaked).length;
4418 const withSafetyViolations = validData.filter(d => d.safety_violations && d.safety_violations.length > 0).length;
4419 const safetyScore = Math.max(0, 100 - (leaked * 20) - (withSafetyViolations * 10));
4420
4421 return {
4422 totalAllocations,
4423 activeVariables: uniqueVars,
4424 uniqueTypes,
4425 allocationRate,
4426 totalBorrows,
4427 maxConcurrent,
4428 ffiTracked,
4429 safetyScore: Math.round(safetyScore)
4430 };
4431}
4432
4433// Helper function to safely update DOM elements
4434function updateElement(id, value) {
4435 const element = document.getElementById(id);
4436 if (element) {
4437 element.textContent = value;
4438 }
4439}
4440
4441// Utility function to format bytes
4442function formatBytes(bytes) {
4443 if (bytes === 0) return '0 B';
4444 const k = 1024;
4445 const sizes = ['B', 'KB', 'MB', 'GB'];
4446 const i = Math.floor(Math.log(bytes) / Math.log(k));
4447 return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
4448}
4449
4450// Show empty state when no user variables found
4451function showEmptyLifetimeState() {
4452 const container = document.getElementById('lifetimeVisualization');
4453 if (!container) return;
4454
4455 container.innerHTML = `
4456 <div class="text-center py-8 text-gray-500 dark:text-gray-400">
4457 <i class="fa fa-info-circle text-2xl mb-2"></i>
4458 <p>No user-defined variables found in lifetime data</p>
4459 <p class="text-sm mt-1">Use track_var! macro to track variable lifetimes</p>
4460 </div>
4461 `;
4462}
4463
4464// Utility functions
4465function updateElement(id, value) {
4466 const element = document.getElementById(id);
4467 if (element) {
4468 element.textContent = value;
4469 }
4470}
4471
4472function getComplexityColor(score) {
4473 if (score <= 2) return '#10b981'; // Green - Low complexity
4474 if (score <= 5) return '#eab308'; // Yellow - Medium complexity
4475 if (score <= 8) return '#f97316'; // Orange - High complexity
4476 return '#ef4444'; // Red - Very high complexity
4477}
4478
4479function getEfficiencyColor(efficiency) {
4480 if (efficiency >= 80) return 'bg-green-500';
4481 if (efficiency >= 60) return 'bg-yellow-500';
4482 if (efficiency >= 40) return 'bg-orange-500';
4483 return 'bg-red-500';
4484}
4485
4486// Update KPI Cards
4487function updateKPICards(data) {
4488 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
4489 const total = allocs.reduce((s,a)=>s+(a.size||0),0);
4490 const active = allocs.filter(a=>!a.timestamp_dealloc).length;
4491 const safetyScore = calculateSafetyScore(allocs);
4492
4493 updateElement('total-allocations', allocs.length.toLocaleString());
4494 updateElement('active-variables', active.toLocaleString());
4495 updateElement('total-memory', formatBytes(total));
4496 updateElement('safety-score', safetyScore + '%');
4497}
4498
4499// Calculate Safety Score
4500function calculateSafetyScore(allocs) {
4501 if (!allocs.length) return 100;
4502 const leaked = allocs.filter(a => a.is_leaked).length;
4503 const violations = allocs.filter(a => a.safety_violations && a.safety_violations.length > 0).length;
4504 return Math.max(0, 100 - (leaked * 20) - (violations * 10));
4505}
4506
4507// Theme Toggle Functionality
4508function initThemeToggle() {
4509 const toggleBtn = document.getElementById('theme-toggle');
4510 if (!toggleBtn) return;
4511
4512 // Check local storage for theme
4513 const savedTheme = localStorage.getItem('memscope-theme') || 'light';
4514 applyTheme(savedTheme === 'dark');
4515
4516 toggleBtn.addEventListener('click', () => {
4517 const isDark = document.documentElement.classList.contains('dark');
4518 const newTheme = isDark ? 'light' : 'dark';
4519
4520 applyTheme(newTheme === 'dark');
4521 localStorage.setItem('memscope-theme', newTheme);
4522
4523 // Update button text
4524 const icon = toggleBtn.querySelector('i');
4525 const text = toggleBtn.querySelector('span');
4526 if (newTheme === 'dark') {
4527 icon.className = 'fa fa-sun';
4528 text.textContent = 'Light Mode';
4529 } else {
4530 icon.className = 'fa fa-moon';
4531 text.textContent = 'Dark Mode';
4532 }
4533
4534 console.log('๐จ Theme switched to:', newTheme);
4535 });
4536}
4537
4538// ๅบ็จไธป้ข
4539function applyTheme(isDark) {
4540 const html = document.documentElement;
4541 if (isDark) {
4542 html.classList.add('dark');
4543 } else {
4544 html.classList.remove('dark');
4545 }
4546}
4547
4548// Update Memory Allocation Table
4549function updateAllocationsTable(data) {
4550 const allocTable = document.getElementById('allocTable');
4551 if (!allocTable) return;
4552
4553 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
4554 const top = allocs.slice().sort((a,b)=>(b.size||0)-(a.size||0)).slice(0,50);
4555
4556 allocTable.innerHTML = top.map(a => {
4557 const status = a.is_leaked ? 'Leaked' : (a.timestamp_dealloc ? 'Freed' : 'Active');
4558 const statusClass = a.is_leaked ? 'status-leaked' : (a.timestamp_dealloc ? 'status-freed' : 'status-active');
4559
4560 return `<tr>
4561 <td>${a.var_name || 'Unknown'}</td>
4562 <td>${formatTypeName(a.type_name || 'Unknown')}</td>
4563 <td>${formatBytes(a.size || 0)}</td>
4564 <td><span class="status-badge ${statusClass}">${status}</span></td>
4565 </tr>`;
4566 }).join('');
4567}
4568
4569// Update Unsafe Risk Table
4570function updateUnsafeTable(data) {
4571 const unsafeTable = document.getElementById('unsafeTable');
4572 if (!unsafeTable) return;
4573
4574 const root = data.unsafe_ffi || {};
4575 const ops = root.enhanced_ffi_data || root.unsafe_operations || root.allocations || [];
4576
4577 unsafeTable.innerHTML = (ops || []).slice(0, 50).map(op => {
4578 const riskLevel = op.risk_level || ((op.safety_violations||[]).length > 2 ? 'High' :
4579 ((op.safety_violations||[]).length > 0 ? 'Medium' : 'Low'));
4580
4581 const riskText = riskLevel === 'High' ? 'High Risk' : (riskLevel === 'Medium' ? 'Medium Risk' : 'Low Risk');
4582 const riskClass = riskLevel === 'High' ? 'risk-high' : (riskLevel === 'Medium' ? 'risk-medium' : 'risk-low');
4583
4584 return `<tr>
4585 <td>${op.location || op.var_name || 'Unknown'}</td>
4586 <td>${op.operation_type || op.type_name || 'Unknown'}</td>
4587 <td><span class="status-badge ${riskClass}">${riskText}</span></td>
4588 </tr>`;
4589 }).join('');
4590}
4591
4592// Initialize Charts
4593function initCharts(data) {
4594 console.log('๐ Initializing charts...');
4595
4596 // Memory type distribution chart
4597 initTypeChart(data);
4598
4599 // Memory timeline chart
4600 initTimelineChart(data);
4601
4602 // Type treemap chart
4603 initTreemapChart(data);
4604
4605 // FFI risk chart
4606 initFFIRiskChart(data);
4607
4608 // Memory growth trends
4609 initGrowthTrends(data);
4610
4611 // Memory fragmentation
4612 initMemoryFragmentation(data);
4613
4614 // Variable relationship graph
4615 initVariableGraph(data);
4616}
4617
4618// Memory Type Distribution Chart
4619function initTypeChart(data) {
4620 const ctx = document.getElementById('typeChart');
4621 if (!ctx) return;
4622
4623 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
4624 const byType = {};
4625
4626 allocs.forEach(a => {
4627 const type = a.type_name || 'Unknown';
4628 byType[type] = (byType[type] || 0) + (a.size || 0);
4629 });
4630
4631 const top = Object.entries(byType).sort((a,b) => b[1] - a[1]).slice(0, 8);
4632
4633 if (top.length > 0 && window.Chart) {
4634 const chart = new Chart(ctx, {
4635 type: 'bar',
4636 data: {
4637 labels: top.map(x => {
4638 const formatted = formatTypeName(x[0]);
4639 return formatted.length > 15 ? formatted.substring(0, 12) + '...' : formatted;
4640 }),
4641 datasets: [{
4642 label: 'Memory Usage',
4643 data: top.map(x => x[1]),
4644 backgroundColor: '#2563eb',
4645 borderRadius: 6
4646 }]
4647 },
4648 options: {
4649 responsive: true,
4650 maintainAspectRatio: false,
4651 plugins: {
4652 legend: { display: false }
4653 },
4654 scales: {
4655 x: {
4656 ticks: {
4657 maxRotation: 45,
4658 minRotation: 0,
4659 font: {
4660 size: 10
4661 }
4662 }
4663 },
4664 y: {
4665 beginAtZero: true,
4666 ticks: {
4667 callback: function(value) {
4668 return formatBytes(value);
4669 }
4670 }
4671 }
4672 }
4673 }
4674 });
4675 }
4676}
4677
4678// Memory Timeline Chart with optional Growth Rate (dual y-axes)
4679function initTimelineChart(data) {
4680 const ctx = document.getElementById('timelineChart');
4681 if (!ctx || !window.Chart) return;
4682
4683 // Comprehensive cleanup for timeline chart
4684 try {
4685 if (ctx.chart) {
4686 ctx.chart.destroy();
4687 delete ctx.chart;
4688 }
4689
4690 if (window.Chart.instances) {
4691 Object.values(window.Chart.instances).forEach(instance => {
4692 if (instance.canvas === ctx) {
4693 instance.destroy();
4694 }
4695 });
4696 }
4697
4698 if (window.chartInstances && window.chartInstances['timelineChart']) {
4699 window.chartInstances['timelineChart'].destroy();
4700 delete window.chartInstances['timelineChart'];
4701 }
4702
4703 const context = ctx.getContext('2d');
4704 context.clearRect(0, 0, ctx.width, ctx.height);
4705
4706 } catch(e) {
4707 console.warn('Timeline chart cleanup warning:', e);
4708 }
4709
4710 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
4711 const sorted = allocs.slice().sort((a,b) => (a.timestamp_alloc||0) - (b.timestamp_alloc||0));
4712
4713 // Bucketize by timestamp to ~200 buckets; compute bytes/sec
4714 if (sorted.length === 0) return;
4715 const minTs = sorted[0].timestamp_alloc || 0;
4716 const maxTs = sorted[sorted.length-1].timestamp_alloc || minTs;
4717 const rangeNs = Math.max(1, maxTs - minTs);
4718 const bucketCount = Math.min(200, Math.max(1, Math.floor(sorted.length / 2)));
4719 const bucketNs = Math.max(1, Math.floor(rangeNs / bucketCount));
4720
4721 const cumSeries = [];
4722 const rateSeries = [];
4723
4724 let cumulative = 0;
4725 let windowQueue = [];
4726 const windowBuckets = 3; // simple smoothing window
4727
4728 for (let b = 0; b <= bucketCount; b++) {
4729 const start = minTs + b * bucketNs;
4730 const end = Math.min(maxTs, start + bucketNs);
4731 const slice = sorted.filter(a => (a.timestamp_alloc||0) >= start && (a.timestamp_alloc||0) < end);
4732 const sum = slice.reduce((s,a)=>s+(a.size||0),0);
4733 cumulative += sum;
4734 cumSeries.push({ x: start, y: cumulative });
4735 const dtSec = Math.max(1e-9, (end - start) / 1e9);
4736 const rate = sum / dtSec; // bytes/sec in this bucket
4737 windowQueue.push(rate);
4738 if (windowQueue.length > windowBuckets) windowQueue.shift();
4739 const smoothed = windowQueue.reduce((s,v)=>s+v,0) / windowQueue.length;
4740 rateSeries.push({ x: start, y: smoothed });
4741 }
4742
4743 if (cumSeries.length > 1 && window.Chart) {
4744 // destroy previous chart if exists
4745 if (window.chartInstances && window.chartInstances['timelineChart']) {
4746 try { window.chartInstances['timelineChart'].destroy(); } catch(_) {}
4747 delete window.chartInstances['timelineChart'];
4748 }
4749
4750 const labels = cumSeries.map(p=> new Date(p.x/1e6).toLocaleTimeString());
4751 const showGrowthCheckbox = document.getElementById('toggleGrowthRate');
4752 const datasets = [
4753 {
4754 type: 'line',
4755 label: 'Cumulative Memory',
4756 data: cumSeries.map(p=>p.y),
4757 borderColor: '#059669',
4758 backgroundColor: 'rgba(5, 150, 105, 0.1)',
4759 fill: true,
4760 tension: 0.3,
4761 yAxisID: 'y'
4762 }
4763 ];
4764
4765 // add growth rate dataset (hidden by default; user toggles it)
4766 datasets.push({
4767 type: 'line',
4768 label: 'Growth Rate (bytes/sec)',
4769 data: rateSeries.map(p=>p.y),
4770 borderColor: '#eab308',
4771 backgroundColor: 'rgba(234, 179, 8, 0.15)',
4772 fill: true,
4773 tension: 0.2,
4774 hidden: showGrowthCheckbox ? !showGrowthCheckbox.checked : true,
4775 yAxisID: 'y1'
4776 });
4777
4778 const chart = new Chart(ctx, {
4779 data: { labels, datasets },
4780 options: {
4781 responsive: true,
4782 maintainAspectRatio: false,
4783 plugins: {
4784 legend: { position: 'bottom' }
4785 },
4786 scales: {
4787 y: {
4788 beginAtZero: true,
4789 title: { display: true, text: 'Cumulative Memory' },
4790 ticks: { callback: v => formatBytes(v) }
4791 },
4792 y1: {
4793 beginAtZero: true,
4794 position: 'right',
4795 grid: { drawOnChartArea: false },
4796 title: { display: true, text: 'Growth Rate (bytes/step)' }
4797 },
4798 x: { title: { display: false } }
4799 }
4800 }
4801 });
4802
4803 window.chartInstances = window.chartInstances || {};
4804 window.chartInstances['timelineChart'] = chart;
4805
4806 if (showGrowthCheckbox) {
4807 showGrowthCheckbox.onchange = () => {
4808 const ds = chart.data.datasets.find(d => d.yAxisID === 'y1');
4809 if (!ds) return;
4810 ds.hidden = !showGrowthCheckbox.checked;
4811 chart.update();
4812 };
4813 }
4814 }
4815}
4816
4817// Enhanced type chart with better label handling for complex Rust types
4818function initEnhancedTypeChart(data) {
4819 const ctx = document.getElementById('typeChart');
4820 if (!ctx || !window.Chart) return;
4821
4822 // Comprehensive cleanup for this specific chart
4823 try {
4824 // Check if there's already a chart attached to this canvas
4825 if (ctx.chart) {
4826 ctx.chart.destroy();
4827 delete ctx.chart;
4828 }
4829
4830 // Clear any Chart.js instance for this canvas
4831 if (window.Chart.instances) {
4832 Object.values(window.Chart.instances).forEach(instance => {
4833 if (instance.canvas === ctx) {
4834 instance.destroy();
4835 }
4836 });
4837 }
4838
4839 // Clear our tracked instance
4840 if (window.chartInstances && window.chartInstances['typeChart']) {
4841 window.chartInstances['typeChart'].destroy();
4842 delete window.chartInstances['typeChart'];
4843 }
4844
4845 // Clear canvas context
4846 const context = ctx.getContext('2d');
4847 context.clearRect(0, 0, ctx.width, ctx.height);
4848
4849 } catch(e) {
4850 console.warn('Chart cleanup warning:', e);
4851 }
4852
4853 const typeData = {};
4854 const allocs = data.memory_analysis?.allocations || data.allocations || [];
4855
4856 console.log('Type chart data extraction:', { totalAllocs: allocs.length });
4857
4858 allocs.forEach(alloc => {
4859 let type = alloc.type_name || 'Unknown';
4860 const originalType = type;
4861
4862 // Simplify complex Rust type names for better readability
4863 type = type.replace(/alloc::sync::Arc/g, 'Arc');
4864 type = type.replace(/alloc::rc::Rc/g, 'Rc');
4865 type = type.replace(/alloc::string::String/g, 'String');
4866 type = type.replace(/alloc::vec::Vec/g, 'Vec');
4867 type = type.replace(/std::collections::hash::map::HashMap/g, 'HashMap');
4868 type = type.replace(/std::collections::btree::map::BTreeMap/g, 'BTreeMap');
4869 type = type.replace(/alloc::collections::\w+::\w+::/g, '');
4870
4871 // Remove generic parameters for cleaner display
4872 type = type.replace(/<[^>]+>/g, '<T>');
4873
4874 // Truncate very long type names
4875 if (type.length > 25) {
4876 type = type.substring(0, 22) + '...';
4877 }
4878
4879 const size = alloc.size || 0;
4880 typeData[type] = (typeData[type] || 0) + size;
4881
4882 if (size > 0) {
4883 console.log(`Adding ${originalType} -> ${type}: ${size} bytes`);
4884 }
4885 });
4886
4887 console.log('Type data aggregated:', typeData);
4888
4889 const sortedEntries = Object.entries(typeData).sort((a, b) => b[1] - a[1]);
4890 const labels = sortedEntries.map(([k, v]) => k);
4891 const values = sortedEntries.map(([k, v]) => v);
4892
4893 if (labels.length > 0 && window.Chart) {
4894 const chart = new Chart(ctx, {
4895 type: 'bar',
4896 data: {
4897 labels: labels,
4898 datasets: [{
4899 label: 'Memory Usage',
4900 data: values,
4901 backgroundColor: labels.map((_, i) => {
4902 const colors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4', '#84cc16'];
4903 return colors[i % colors.length] + '80';
4904 }),
4905 borderColor: labels.map((_, i) => {
4906 const colors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4', '#84cc16'];
4907 return colors[i % colors.length];
4908 }),
4909 borderWidth: 2
4910 }]
4911 },
4912 options: {
4913 responsive: true,
4914 maintainAspectRatio: false,
4915 plugins: {
4916 legend: { display: false },
4917 tooltip: {
4918 callbacks: {
4919 label: (context) => {
4920 const originalType = allocs.find(a => {
4921 let simplified = a.type_name || 'Unknown';
4922 simplified = simplified.replace(/alloc::(sync::Arc|rc::Rc|collections::\w+::\w+::|string::String|vec::Vec)/g, (match, p1) => {
4923 switch(p1) {
4924 case 'sync::Arc': return 'Arc';
4925 case 'rc::Rc': return 'Rc';
4926 case 'string::String': return 'String';
4927 case 'vec::Vec': return 'Vec';
4928 default: return p1.split('::').pop();
4929 }
4930 });
4931 simplified = simplified.replace(/std::collections::hash::map::HashMap/g, 'HashMap');
4932 simplified = simplified.replace(/std::collections::btree::map::BTreeMap/g, 'BTreeMap');
4933 if (simplified.length > 30) simplified = simplified.substring(0, 27) + '...';
4934 return simplified === context.label;
4935 })?.type_name || context.label;
4936 return [`Type: ${originalType}`, `Memory: ${formatBytes(context.parsed.y)}`];
4937 }
4938 }
4939 }
4940 },
4941 scales: {
4942 x: {
4943 ticks: {
4944 maxRotation: 45,
4945 minRotation: 0,
4946 font: {
4947 size: 11,
4948 weight: '500',
4949 family: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
4950 },
4951 color: function(context) {
4952 return document.documentElement.classList.contains('dark-theme') ? '#e2e8f0' : '#475569';
4953 },
4954 padding: 8,
4955 callback: function(value, index) {
4956 const label = this.getLabelForValue(value);
4957 // Ensure readability by adding spacing
4958 return label.length > 15 ? label.substring(0, 13) + '...' : label;
4959 }
4960 },
4961 grid: {
4962 display: false
4963 }
4964 },
4965 y: {
4966 beginAtZero: true,
4967 ticks: {
4968 callback: (value) => formatBytes(value),
4969 color: function(context) {
4970 return document.documentElement.classList.contains('dark-theme') ? '#cbd5e1' : '#64748b';
4971 },
4972 font: { size: 10, weight: '400' }
4973 },
4974 grid: {
4975 color: function(context) {
4976 return document.documentElement.classList.contains('dark-theme') ? '#374151' : '#e2e8f0';
4977 },
4978 lineWidth: 1
4979 }
4980 }
4981 }
4982 }
4983 });
4984
4985 window.chartInstances = window.chartInstances || {};
4986 window.chartInstances['typeChart'] = chart;
4987 }
4988}
4989
4990// Type Treemap Chart
4991function initTreemapChart(data) {
4992 const container = document.getElementById('treemap');
4993 if (!container) return;
4994
4995 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
4996 const byType = {};
4997
4998 allocs.forEach(a => {
4999 const type = a.type_name || 'Unknown';
5000 byType[type] = (byType[type] || 0) + (a.size || 0);
5001 });
5002
5003 const top = Object.entries(byType).sort((a,b) => b[1] - a[1]).slice(0, 12);
5004 const totalSize = top.reduce((sum, [, size]) => sum + size, 0);
5005
5006 if (totalSize > 0) {
5007 let html = '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; height: 100%; padding: 16px;">';
5008
5009 top.forEach(([type, size], index) => {
5010 const percentage = (size / totalSize) * 100;
5011 const color = `hsl(${index * 30}, 70%, 55%)`;
5012
5013 html += `
5014 <div style="
5015 background: ${color};
5016 color: white;
5017 padding: 12px;
5018 border-radius: 8px;
5019 font-size: 11px;
5020 font-weight: 600;
5021 display: flex;
5022 flex-direction: column;
5023 justify-content: center;
5024 text-align: center;
5025 min-height: 80px;
5026 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
5027 transition: transform 0.2s ease;
5028 " title="${type}: ${formatBytes(size)}" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
5029 <div style="margin-bottom: 4px;">${formatTypeName(type)}</div>
5030 <div style="font-size: 10px; opacity: 0.9;">${formatBytes(size)}</div>
5031 <div style="font-size: 9px; opacity: 0.7;">${percentage.toFixed(1)}%</div>
5032 </div>
5033 `;
5034 });
5035
5036 html += '</div>';
5037 container.innerHTML = html;
5038 } else {
5039 container.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">No data available</div>';
5040 }
5041}
5042
5043// FFI Risk Chart
5044function initFFIRiskChart(data) {
5045 // Guard destroy if exists
5046 try {
5047 if (window.chartInstances && window.chartInstances['ffi-risk-chart']) {
5048 window.chartInstances['ffi-risk-chart'].destroy();
5049 delete window.chartInstances['ffi-risk-chart'];
5050 }
5051 } catch (_) {}
5052
5053 const ctx = document.getElementById('ffi-risk-chart');
5054 if (!ctx) return;
5055
5056 const ffiData = data.unsafe_ffi?.enhanced_ffi_data || [];
5057
5058 const riskLevels = {
5059 'Low Risk': ffiData.filter(item => (item.safety_violations || []).length === 0).length,
5060 'Medium Risk': ffiData.filter(item => (item.safety_violations || []).length > 0 && (item.safety_violations || []).length <= 2).length,
5061 'High Risk': ffiData.filter(item => (item.safety_violations || []).length > 2).length
5062 };
5063
5064 if (window.Chart) {
5065 const chart = new Chart(ctx, {
5066 type: 'doughnut',
5067 data: {
5068 labels: Object.keys(riskLevels),
5069 datasets: [{
5070 data: Object.values(riskLevels),
5071 backgroundColor: ['#059669', '#ea580c', '#dc2626'],
5072 borderWidth: 2,
5073 borderColor: 'var(--bg-primary)'
5074 }]
5075 },
5076 options: {
5077 responsive: true,
5078 maintainAspectRatio: false,
5079 plugins: {
5080 legend: {
5081 position: 'bottom',
5082 labels: {
5083 padding: 20,
5084 usePointStyle: true
5085 }
5086 }
5087 }
5088 }
5089 });
5090 }
5091}
5092
5093// Add missing chart and graph functions
5094function initGrowthTrends(data) {
5095 const container = document.getElementById('growth');
5096 if (!container) return;
5097
5098 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
5099 if (allocs.length === 0) {
5100 container.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">No growth data available</div>';
5101 return;
5102 }
5103
5104 // Simple growth visualization
5105 const sorted = allocs.slice().sort((a,b) => (a.timestamp_alloc||0) - (b.timestamp_alloc||0));
5106 let cumulative = 0;
5107 const points = [];
5108
5109 for (let i = 0; i < Math.min(sorted.length, 20); i++) {
5110 cumulative += sorted[i].size || 0;
5111 points.push(cumulative);
5112 }
5113
5114 const maxValue = Math.max(...points);
5115 let html = '<div style="display: flex; align-items: end; height: 200px; gap: 4px; padding: 20px;">';
5116
5117 points.forEach((value, i) => {
5118 const height = (value / maxValue) * 160;
5119 html += `
5120 <div style="
5121 width: 12px;
5122 height: ${height}px;
5123 background: linear-gradient(to top, #2563eb, #3b82f6);
5124 border-radius: 2px;
5125 margin: 0 1px;
5126 " title="Step ${i + 1}: ${formatBytes(value)}"></div>
5127 `;
5128 });
5129
5130 html += '</div>';
5131 container.innerHTML = html;
5132}
5133
5134function initMemoryFragmentation(data) {
5135 const container = document.getElementById('memoryFragmentation');
5136 if (!container) return;
5137
5138 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
5139 const totalMemory = allocs.reduce((sum, a) => sum + (a.size || 0), 0);
5140 const activeMemory = allocs.filter(a => !a.timestamp_dealloc).reduce((sum, a) => sum + (a.size || 0), 0);
5141 const fragmentationRate = totalMemory > 0 ? ((totalMemory - activeMemory) / totalMemory * 100) : 0;
5142
5143 container.innerHTML = `
5144 <div style="padding: 20px;">
5145 <div style="display: flex; justify-content: space-between; margin-bottom: 16px;">
5146 <div>
5147 <div style="color: var(--text-secondary); font-size: 0.9rem;">Fragmentation Rate</div>
5148 <div style="font-size: 2rem; font-weight: 700; color: ${fragmentationRate > 30 ? '#dc2626' : fragmentationRate > 15 ? '#ea580c' : '#059669'};">
5149 ${fragmentationRate.toFixed(1)}%
5150 </div>
5151 </div>
5152 <div>
5153 <div style="color: var(--text-secondary); font-size: 0.9rem;">Active Memory</div>
5154 <div style="font-size: 1.2rem; font-weight: 600;">${formatBytes(activeMemory)}</div>
5155 </div>
5156 </div>
5157 <div style="background: var(--bg-secondary); height: 8px; border-radius: 4px; overflow: hidden;">
5158 <div style="
5159 background: linear-gradient(to right, #059669, #ea580c);
5160 width: ${Math.min(100, fragmentationRate)}%;
5161 height: 100%;
5162 border-radius: 4px;
5163 transition: width 0.8s ease;
5164 "></div>
5165 </div>
5166 <div style="margin-top: 12px; font-size: 0.8rem; color: var(--text-secondary);">
5167 ${fragmentationRate > 30 ? 'High fragmentation detected' : fragmentationRate > 15 ? 'Moderate fragmentation' : 'Low fragmentation'}
5168 </div>
5169 </div>
5170 `;
5171}
5172
5173function initVariableGraph(data) {
5174 const container = document.getElementById('graph');
5175 if (!container) return;
5176
5177 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
5178 if (allocs.length === 0) {
5179 container.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">No relationship data available</div>';
5180 return;
5181 }
5182
5183 // Create a simple node-link visualization
5184 const nodes = allocs.slice(0, 20).map((a, i) => ({
5185 id: i,
5186 name: a.var_name || `var_${i}`,
5187 type: a.type_name || 'unknown',
5188 size: a.size || 0,
5189 x: 50 + (i % 4) * 80,
5190 y: 50 + Math.floor(i / 4) * 60
5191 }));
5192
5193 let svg = `
5194 <svg width="100%" height="100%" style="background: transparent;">
5195 <defs>
5196 <filter id="glow">
5197 <feGaussianBlur stdDeviation="3" result="coloredBlur"/>
5198 <feMerge>
5199 <feMergeNode in="coloredBlur"/>
5200 <feMergeNode in="SourceGraphic"/>
5201 </feMerge>
5202 </filter>
5203 </defs>
5204 `;
5205
5206 // Add links between nearby nodes
5207 for (let i = 0; i < nodes.length - 1; i++) {
5208 if (i % 4 !== 3) { // Connect horizontally
5209 svg += `<line x1="${nodes[i].x}" y1="${nodes[i].y}" x2="${nodes[i+1].x}" y2="${nodes[i+1].y}" stroke="var(--border-light)" stroke-width="1" opacity="0.3"/>`;
5210 }
5211 if (i < nodes.length - 4) { // Connect vertically
5212 svg += `<line x1="${nodes[i].x}" y1="${nodes[i].y}" x2="${nodes[i+4].x}" y2="${nodes[i+4].y}" stroke="var(--border-light)" stroke-width="1" opacity="0.3"/>`;
5213 }
5214 }
5215
5216 // Add nodes
5217 nodes.forEach(node => {
5218 const radius = Math.max(8, Math.min(20, Math.log(node.size + 1) * 2));
5219 const color = node.type.includes('String') ? '#fbbf24' :
5220 node.type.includes('Vec') ? '#3b82f6' :
5221 node.type.includes('Box') || node.type.includes('Rc') ? '#8b5cf6' : '#6b7280';
5222
5223 svg += `
5224 <circle
5225 cx="${node.x}"
5226 cy="${node.y}"
5227 r="${radius}"
5228 fill="${color}"
5229 stroke="white"
5230 stroke-width="2"
5231 filter="url(#glow)"
5232 style="cursor: pointer;"
5233 onmouseover="this.r.baseVal.value = ${radius + 3}"
5234 onmouseout="this.r.baseVal.value = ${radius}"
5235 >
5236 <title>${node.name} (${node.type})</title>
5237 </circle>
5238 <text
5239 x="${node.x}"
5240 y="${node.y + radius + 12}"
5241 text-anchor="middle"
5242 font-size="10"
5243 fill="var(--text-primary)"
5244 style="font-weight: 500;"
5245 >${node.name.length > 8 ? node.name.substring(0, 8) + '...' : node.name}</text>
5246 `;
5247 });
5248
5249 svg += '</svg>';
5250 container.innerHTML = svg;
5251}
5252
5253// Initialize lifetime visualization
5254function initLifetimeVisualization(data) {
5255 const container = document.getElementById('lifetimes');
5256 if (!container) return;
5257
5258 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
5259 if (allocs.length === 0) {
5260 container.innerHTML = '<div style="padding: 20px; text-align: center; color: var(--text-secondary);">No lifetime data available</div>';
5261 return;
5262 }
5263
5264 // Show top allocations by lifetime
5265 const withLifetime = allocs.filter(a => a.lifetime_ms || (a.timestamp_alloc && a.timestamp_dealloc));
5266 const sorted = withLifetime.sort((a, b) => {
5267 const aLifetime = a.lifetime_ms || (a.timestamp_dealloc - a.timestamp_alloc);
5268 const bLifetime = b.lifetime_ms || (b.timestamp_dealloc - b.timestamp_alloc);
5269 return bLifetime - aLifetime;
5270 }).slice(0, 10);
5271
5272 if (sorted.length === 0) {
5273 container.innerHTML = '<div style="padding: 20px; text-align: center; color: var(--text-secondary);">No lifetime data available</div>';
5274 return;
5275 }
5276
5277 let html = '<div style="padding: 16px;">';
5278
5279 sorted.forEach((alloc, index) => {
5280 const lifetime = alloc.lifetime_ms || (alloc.timestamp_dealloc - alloc.timestamp_alloc);
5281 const isActive = !alloc.timestamp_dealloc;
5282 const varName = alloc.var_name || `allocation_${index}`;
5283 const size = formatBytes(alloc.size || 0);
5284
5285 html += `
5286 <div style="
5287 margin-bottom: 12px;
5288 padding: 12px;
5289 background: var(--bg-secondary);
5290 border-radius: 8px;
5291 border-left: 4px solid ${isActive ? '#059669' : '#2563eb'};
5292 ">
5293 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
5294 <span style="font-weight: 600; color: var(--text-primary);">${varName}</span>
5295 <span style="font-size: 0.9rem; color: var(--text-secondary);">${size}</span>
5296 </div>
5297 <div style="display: flex; justify-content: space-between; align-items: center;">
5298 <span style="font-size: 0.8rem; color: var(--text-secondary);">
5299 ${formatTypeName(alloc.type_name || 'Unknown')}
5300 </span>
5301 <span style="
5302 font-size: 0.8rem;
5303 font-weight: 600;
5304 color: ${isActive ? '#059669' : '#2563eb'};
5305 ">
5306 ${isActive ? 'Active' : `${lifetime}ms`}
5307 </span>
5308 </div>
5309 </div>
5310 `;
5311 });
5312
5313 html += '</div>';
5314 container.innerHTML = html;
5315}
5316
5317// Helper function to update elements
5318function updateElement(id, value) {
5319 const element = document.getElementById(id);
5320 if (element) {
5321 element.textContent = value;
5322 }
5323}
5324
5325// Original dashboard functions from dashboard.html
5326function renderKpis() {
5327 const data = window.analysisData || {};
5328 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
5329 const total = allocs.reduce((s,a)=>s+(a.size||0),0);
5330 const active = allocs.filter(a=>!a.timestamp_dealloc).length;
5331 const leaks = allocs.filter(a=>a.is_leaked).length;
5332 const safety = Math.max(0, 100 - (leaks * 20));
5333
5334 updateElement('total-allocations', allocs.length.toLocaleString());
5335 updateElement('active-variables', active.toLocaleString());
5336 updateElement('total-memory', formatBytes(total));
5337 updateElement('safety-score', safety + '%');
5338}
5339
5340function populateAllocationsTable() {
5341 const data = window.analysisData || {};
5342 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
5343 const allocTable = document.getElementById('allocTable');
5344 if (!allocTable) return;
5345
5346 const top = allocs.slice().sort((a,b)=>(b.size||0)-(a.size||0)).slice(0,50);
5347 allocTable.innerHTML = top.map(a => {
5348 const status = a.is_leaked ? 'Leaked' : (a.timestamp_dealloc ? 'Freed' : 'Active');
5349 const statusClass = a.is_leaked ? 'status-leaked' : (a.timestamp_dealloc ? 'status-freed' : 'status-active');
5350
5351 return `<tr>
5352 <td>${a.var_name || 'Unknown'}</td>
5353 <td>${formatTypeName(a.type_name || 'Unknown')}</td>
5354 <td>${formatBytes(a.size || 0)}</td>
5355 <td><span class="status-badge ${statusClass}">${status}</span></td>
5356 </tr>`;
5357 }).join('');
5358}
5359
5360function renderTypeChart() {
5361 const ctx = document.getElementById('typeChart');
5362 if (!ctx || !window.Chart) return;
5363
5364 const data = window.analysisData || {};
5365 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
5366 const byType = {};
5367
5368 allocs.forEach(a => {
5369 const type = a.type_name || 'Unknown';
5370 byType[type] = (byType[type] || 0) + (a.size || 0);
5371 });
5372
5373 const top = Object.entries(byType).sort((a,b) => b[1] - a[1]).slice(0, 8);
5374
5375 if (top.length > 0) {
5376 new Chart(ctx, {
5377 type: 'bar',
5378 data: {
5379 labels: top.map(x => formatTypeName(x[0])),
5380 datasets: [{
5381 label: 'Memory Usage',
5382 data: top.map(x => x[1]),
5383 backgroundColor: '#2563eb',
5384 borderRadius: 6
5385 }]
5386 },
5387 options: {
5388 responsive: true,
5389 maintainAspectRatio: false,
5390 plugins: { legend: { display: false } },
5391 scales: {
5392 y: {
5393 beginAtZero: true,
5394 ticks: { callback: function(value) { return formatBytes(value); } }
5395 }
5396 }
5397 }
5398 });
5399 }
5400}
5401
5402function renderTimelineChart() {
5403 const ctx = document.getElementById('timelineChart');
5404 if (!ctx || !window.Chart) return;
5405
5406 const data = window.analysisData || {};
5407 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
5408 const sorted = allocs.slice().sort((a,b) => (a.timestamp_alloc||0) - (b.timestamp_alloc||0));
5409
5410 let cumulative = 0;
5411 const points = [];
5412 const step = Math.max(1, Math.floor(sorted.length / 30));
5413
5414 for (let i = 0; i < sorted.length; i += step) {
5415 cumulative += sorted[i].size || 0;
5416 points.push({ x: i, y: cumulative });
5417 }
5418
5419 if (points.length > 1) {
5420 new Chart(ctx, {
5421 type: 'line',
5422 data: {
5423 labels: points.map(p => p.x),
5424 datasets: [{
5425 label: 'Cumulative Memory',
5426 data: points.map(p => p.y),
5427 borderColor: '#059669',
5428 backgroundColor: 'rgba(5, 150, 105, 0.1)',
5429 fill: true,
5430 tension: 0.4
5431 }]
5432 },
5433 options: {
5434 responsive: true,
5435 maintainAspectRatio: false,
5436 plugins: { legend: { display: false } },
5437 scales: {
5438 y: {
5439 beginAtZero: true,
5440 ticks: { callback: function(value) { return formatBytes(value); } }
5441 }
5442 }
5443 }
5444 });
5445 }
5446}
5447
5448function renderTreemap() {
5449 const container = document.getElementById('treemap');
5450 if (!container) return;
5451
5452 const data = window.analysisData || {};
5453 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
5454 const byType = {};
5455
5456 allocs.forEach(a => {
5457 const type = a.type_name || 'Unknown';
5458 byType[type] = (byType[type] || 0) + (a.size || 0);
5459 });
5460
5461 const top = Object.entries(byType).sort((a,b) => b[1] - a[1]).slice(0, 12);
5462 const totalSize = top.reduce((sum, [, size]) => sum + size, 0);
5463
5464 if (totalSize > 0) {
5465 let html = '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; height: 100%; padding: 16px;">';
5466
5467 top.forEach(([type, size], index) => {
5468 const percentage = (size / totalSize) * 100;
5469 const color = `hsl(${index * 30}, 70%, 55%)`;
5470
5471 html += `
5472 <div style="
5473 background: ${color};
5474 color: white;
5475 padding: 12px;
5476 border-radius: 8px;
5477 font-size: 11px;
5478 font-weight: 600;
5479 display: flex;
5480 flex-direction: column;
5481 justify-content: center;
5482 text-align: center;
5483 min-height: 80px;
5484 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
5485 transition: transform 0.2s ease;
5486 " title="${type}: ${formatBytes(size)}" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
5487 <div style="margin-bottom: 4px;">${formatTypeName(type)}</div>
5488 <div style="font-size: 10px; opacity: 0.9;">${formatBytes(size)}</div>
5489 <div style="font-size: 9px; opacity: 0.7;">${percentage.toFixed(1)}%</div>
5490 </div>
5491 `;
5492 });
5493
5494 html += '</div>';
5495 container.innerHTML = html;
5496 } else {
5497 container.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">No data available</div>';
5498 }
5499}
5500
5501function renderLifetimes() {
5502 const container = document.getElementById('lifetimes');
5503 if (!container) return;
5504
5505 const data = window.analysisData || {};
5506 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
5507 const withLifetime = allocs.filter(a => a.lifetime_ms || (a.timestamp_alloc && a.timestamp_dealloc));
5508 const sorted = withLifetime.sort((a, b) => {
5509 const aLifetime = a.lifetime_ms || (a.timestamp_dealloc - a.timestamp_alloc);
5510 const bLifetime = b.lifetime_ms || (b.timestamp_dealloc - b.timestamp_alloc);
5511 return bLifetime - aLifetime;
5512 }).slice(0, 10);
5513
5514 if (sorted.length === 0) {
5515 container.innerHTML = '<div style="padding: 20px; text-align: center; color: var(--text-secondary);">No lifetime data available</div>';
5516 return;
5517 }
5518
5519 let html = '<div style="padding: 16px;">';
5520
5521 sorted.forEach((alloc, index) => {
5522 const lifetime = alloc.lifetime_ms || (alloc.timestamp_dealloc - alloc.timestamp_alloc);
5523 const isActive = !alloc.timestamp_dealloc;
5524 const varName = alloc.var_name || `allocation_${index}`;
5525 const size = formatBytes(alloc.size || 0);
5526
5527 html += `
5528 <div style="
5529 margin-bottom: 12px;
5530 padding: 12px;
5531 background: var(--bg-secondary);
5532 border-radius: 8px;
5533 border-left: 4px solid ${isActive ? '#059669' : '#2563eb'};
5534 ">
5535 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
5536 <span style="font-weight: 600; color: var(--text-primary);">${varName}</span>
5537 <span style="font-size: 0.9rem; color: var(--text-secondary);">${size}</span>
5538 </div>
5539 <div style="display: flex; justify-content: space-between; align-items: center;">
5540 <span style="font-size: 0.8rem; color: var(--text-secondary);">
5541 ${formatTypeName(alloc.type_name || 'Unknown')}
5542 </span>
5543 <span style="
5544 font-size: 0.8rem;
5545 font-weight: 600;
5546 color: ${isActive ? '#059669' : '#2563eb'};
5547 ">
5548 ${isActive ? 'Active' : `${lifetime}ms`}
5549 </span>
5550 </div>
5551 </div>
5552 `;
5553 });
5554
5555 html += '</div>';
5556 container.innerHTML = html;
5557}
5558
5559function renderFFI() {
5560 // First try the chart container for simple chart
5561 const chartContainer = document.getElementById('ffi-risk-chart');
5562 if (chartContainer && window.Chart) {
5563 const data = window.analysisData || {};
5564 const ffiData = data.unsafe_ffi || data.unsafeFFI || {};
5565 const operations = ffiData.enhanced_ffi_data || ffiData.allocations || ffiData.unsafe_operations || [];
5566
5567 if (operations.length > 0) {
5568 // Guard: destroy existing chart instance if any
5569 try {
5570 if (window.chartInstances && window.chartInstances['ffi-risk-chart']) {
5571 window.chartInstances['ffi-risk-chart'].destroy();
5572 delete window.chartInstances['ffi-risk-chart'];
5573 }
5574 } catch (_) {}
5575
5576 const highRisk = operations.filter(op => (op.safety_violations || []).length > 2).length;
5577 const mediumRisk = operations.filter(op => (op.safety_violations || []).length > 0 && (op.safety_violations || []).length <= 2).length;
5578 const lowRisk = operations.filter(op => (op.safety_violations || []).length === 0).length;
5579
5580 window.chartInstances = window.chartInstances || {};
5581 window.chartInstances['ffi-risk-chart'] = new Chart(chartContainer, {
5582 type: 'doughnut',
5583 data: {
5584 labels: ['Low Risk', 'Medium Risk', 'High Risk'],
5585 datasets: [{
5586 data: [lowRisk, mediumRisk, highRisk],
5587 backgroundColor: ['#059669', '#ea580c', '#dc2626'],
5588 borderWidth: 2,
5589 borderColor: 'var(--bg-primary)'
5590 }]
5591 },
5592 options: {
5593 responsive: true,
5594 maintainAspectRatio: false,
5595 plugins: {
5596 legend: {
5597 position: 'bottom',
5598 labels: {
5599 padding: 20,
5600 usePointStyle: true,
5601 generateLabels: function(chart) {
5602 const data = chart.data;
5603 return data.labels.map((label, i) => ({
5604 text: `${label}: ${data.datasets[0].data[i]}`,
5605 fillStyle: data.datasets[0].backgroundColor[i],
5606 strokeStyle: data.datasets[0].backgroundColor[i],
5607 pointStyle: 'circle'
5608 }));
5609 }
5610 }
5611 }
5612 }
5613 }
5614 });
5615 return;
5616 }
5617 }
5618
5619 // Main comprehensive FFI visualization based on project's actual SVG code
5620 const container = document.getElementById('ffiVisualization');
5621 if (!container) return;
5622
5623 const data = window.analysisData || {};
5624 const ffiData = data.unsafe_ffi || data.unsafeFFI || {};
5625 const allocations = ffiData.allocations || ffiData.enhanced_ffi_data || [];
5626 const violations = ffiData.violations || [];
5627
5628 if (allocations.length === 0) {
5629 container.innerHTML = '<div style="padding: 20px; text-align: center; color: var(--text-secondary);">No FFI data available</div>';
5630 return;
5631 }
5632
5633 // Create the ACTUAL project-based unsafe/FFI dashboard SVG
5634 createProjectBasedUnsafeFFIDashboard(container, allocations, violations);
5635}
5636
5637// PROJECT-BASED Unsafe/FFI Dashboard - Direct implementation from visualization.rs
5638function createProjectBasedUnsafeFFIDashboard(container, allocations, violations) {
5639 const width = 1400;
5640 const height = 1000;
5641
5642 // Calculate key metrics exactly like the Rust code
5643 const unsafeCount = allocations.filter(a =>
5644 (a.source && (a.source.UnsafeRust || a.source === 'UnsafeRust')) ||
5645 (a.allocation_source === 'UnsafeRust')
5646 ).length;
5647
5648 const ffiCount = allocations.filter(a =>
5649 (a.source && (a.source.FfiC || a.source === 'FfiC')) ||
5650 (a.allocation_source === 'FfiC')
5651 ).length;
5652
5653 const crossBoundaryEvents = allocations.reduce((sum, a) =>
5654 sum + ((a.cross_boundary_events && a.cross_boundary_events.length) || 0), 0
5655 );
5656
5657 const totalUnsafeMemory = allocations
5658 .filter(a => a.source !== 'RustSafe' && a.allocation_source !== 'RustSafe')
5659 .reduce((sum, a) => sum + ((a.base && a.base.size) || a.size || 0), 0);
5660
5661 // Create the full SVG dashboard exactly like the Rust implementation
5662 let html = `
5663 <div style="width: 100%; height: ${height}px; background: linear-gradient(135deg, #2c3e50 0%, #34495e 50%, #2c3e50 100%); border-radius: 12px; overflow: hidden; position: relative;">
5664 <svg width="100%" height="100%" viewBox="0 0 ${width} ${height}" style="font-family: 'Segoe UI', Arial, sans-serif;">
5665 <!-- SVG Definitions -->
5666 <defs>
5667 <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
5668 <polygon points="0 0, 10 3.5, 0 7" fill='#e74c3c'/>
5669 </marker>
5670 <filter id="glow">
5671 <feGaussianBlur stdDeviation="3" result="coloredBlur"/>
5672 <feMerge>
5673 <feMergeNode in="coloredBlur"/>
5674 <feMergeNode in="SourceGraphic"/>
5675 </feMerge>
5676 </filter>
5677 </defs>
5678
5679 <!-- Main Title -->
5680 <text x="${width/2}" y="40" text-anchor="middle" font-size="24" font-weight="bold" fill='#ecf0f1'>
5681 Unsafe Rust & FFI Memory Analysis Dashboard
5682 </text>
5683
5684 <!-- Key Metrics Cards -->
5685 <g id="metrics-cards">
5686 ${createMetricsCards(unsafeCount, ffiCount, crossBoundaryEvents, violations.length, totalUnsafeMemory)}
5687 </g>
5688
5689 <!-- Allocation Source Breakdown -->
5690 <g id="allocation-breakdown" transform="translate(50, 150)">
5691 ${createAllocationSourceBreakdown(allocations)}
5692 </g>
5693
5694 <!-- Memory Safety Status -->
5695 <g id="safety-status" transform="translate(750, 150)">
5696 ${createMemorySafetyStatus(violations)}
5697 </g>
5698
5699 <!-- Cross-Language Memory Flow -->
5700 <g id="boundary-flow" transform="translate(50, 500)">
5701 ${createBoundaryFlow(allocations)}
5702 </g>
5703
5704 <!-- Unsafe Memory Hotspots -->
5705 <g id="unsafe-hotspots" transform="translate(750, 500)">
5706 ${createUnsafeHotspots(allocations)}
5707 </g>
5708 </svg>
5709 </div>
5710 `;
5711
5712 container.innerHTML = html;
5713
5714 // Add interactivity
5715 setTimeout(() => {
5716 addFFIInteractivity();
5717 }, 100);
5718}
5719
5720// Create metrics cards exactly like Rust implementation
5721function createMetricsCards(unsafeCount, ffiCount, crossBoundaryEvents, violationCount, totalUnsafeMemory) {
5722 const metrics = [
5723 { label: 'Unsafe Allocations', value: unsafeCount, color: '#e74c3c', x: 100 },
5724 { label: 'FFI Allocations', value: ffiCount, color: '#3498db', x: 350 },
5725 { label: 'Boundary Crossings', value: crossBoundaryEvents, color: '#f39c12', x: 600 },
5726 { label: 'Safety Violations', value: violationCount, color: '#e67e22', x: 850 },
5727 { label: 'Unsafe Memory', value: formatBytes(totalUnsafeMemory), color: '#9b59b6', x: 1100 }
5728 ];
5729
5730 return metrics.map(metric => `
5731 <!-- Card background -->
5732 <rect x="${metric.x - 60}" y="55" width="120" height="50"
5733 fill="${metric.color}" fill-opacity="0.2"
5734 stroke="${metric.color}" stroke-width="2" rx="8"/>
5735
5736 <!-- Value -->
5737 <text x="${metric.x}" y="70" text-anchor="middle" font-size="16" font-weight="bold" fill="${metric.color}">
5738 ${metric.value}
5739 </text>
5740
5741 <!-- Label -->
5742 <text x="${metric.x}" y="95" text-anchor="middle" font-size="10" fill='#bdc3c7'>
5743 ${metric.label}
5744 </text>
5745 `).join('');
5746}
5747
5748// Create allocation source breakdown
5749function createAllocationSourceBreakdown(allocations) {
5750 let safeCount = 0, unsafeCount = 0, ffiCount = 0, crossBoundaryCount = 0;
5751
5752 allocations.forEach(allocation => {
5753 const source = allocation.source || allocation.allocation_source || 'Unknown';
5754 if (source === 'RustSafe' || source.RustSafe) safeCount++;
5755 else if (source === 'UnsafeRust' || source.UnsafeRust) unsafeCount++;
5756 else if (source === 'FfiC' || source.FfiC) ffiCount++;
5757 else if (source === 'CrossBoundary' || source.CrossBoundary) crossBoundaryCount++;
5758 });
5759
5760 const total = safeCount + unsafeCount + ffiCount + crossBoundaryCount;
5761 if (total === 0) {
5762 return `<text x="300" y="150" text-anchor="middle" font-size="14" fill='#95a5a6'>No allocation data available</text>`;
5763 }
5764
5765 const sources = [
5766 { label: 'Safe Rust', count: safeCount, color: '#2ecc71', x: 50 },
5767 { label: 'Unsafe Rust', count: unsafeCount, color: '#e74c3c', x: 170 },
5768 { label: 'FFI', count: ffiCount, color: '#3498db', x: 290 },
5769 { label: 'Cross-boundary', count: crossBoundaryCount, color: '#9b59b6', x: 410 }
5770 ];
5771
5772 let svg = `
5773 <!-- Section background -->
5774 <rect x="0" y="0" width="600" height="300" fill="rgba(52, 73, 94, 0.3)"
5775 stroke='#34495e' stroke-width="2" rx="10"/>
5776
5777 <!-- Section title -->
5778 <text x="300" y="-10" text-anchor="middle" font-size="18" font-weight="bold" fill='#ecf0f1'>
5779 Memory Allocation Sources
5780 </text>
5781 `;
5782
5783 sources.forEach(source => {
5784 if (source.count > 0) {
5785 const barHeight = (source.count / total * 100);
5786 svg += `
5787 <!-- Bar -->
5788 <rect x="${source.x}" y="${200 - barHeight}" width="40" height="${barHeight}" fill="${source.color}"/>
5789
5790 <!-- Count label -->
5791 <text x="${source.x + 20}" y="${200 - barHeight - 5}" text-anchor="middle"
5792 font-size="12" font-weight="bold" fill="${source.color}">
5793 ${source.count}
5794 </text>
5795
5796 <!-- Label -->
5797 <text x="${source.x + 20}" y="220" text-anchor="middle" font-size="10" fill='#ecf0f1'>
5798 ${source.label}
5799 </text>
5800 `;
5801 }
5802 });
5803
5804 return svg;
5805}
5806
5807// Create memory safety status
5808function createMemorySafetyStatus(violations) {
5809 const bgColor = violations.length === 0 ? '#27ae60' : '#e74c3c';
5810
5811 let svg = `
5812 <!-- Section background -->
5813 <rect x="0" y="0" width="600" height="300" fill="${bgColor}20"
5814 stroke="${bgColor}" stroke-width="2" rx="10"/>
5815
5816 <!-- Section title -->
5817 <text x="300" y="-10" text-anchor="middle" font-size="18" font-weight="bold" fill='#ecf0f1'>
5818 Memory Safety Status
5819 </text>
5820 `;
5821
5822 if (violations.length === 0) {
5823 svg += `
5824 <text x="300" y="150" text-anchor="middle" font-size="16" font-weight="bold" fill='#27ae60'>
5825 No Safety Violations Detected
5826 </text>
5827 <text x="300" y="180" text-anchor="middle" font-size="12" fill='#2ecc71'>
5828 All unsafe operations and FFI calls appear to be memory-safe
5829 </text>
5830 `;
5831 } else {
5832 svg += `
5833 <text x="300" y="120" text-anchor="middle" font-size="16" font-weight="bold" fill='#e74c3c'>
5834 ${violations.length} Safety Violations Detected
5835 </text>
5836 `;
5837
5838 violations.slice(0, 5).forEach((violation, i) => {
5839 const y = 160 + i * 20;
5840 const description = getViolationDescription(violation);
5841 svg += `
5842 <text x="30" y="${y}" font-size="12" fill='#e74c3c'>โข ${description}</text>
5843 `;
5844 });
5845 }
5846
5847 return svg;
5848}
5849
5850// Create boundary flow diagram
5851function createBoundaryFlow(allocations) {
5852 let rustToFfi = 0, ffiToRust = 0;
5853
5854 allocations.forEach(allocation => {
5855 if (allocation.cross_boundary_events) {
5856 allocation.cross_boundary_events.forEach(event => {
5857 const eventType = event.event_type || event.type;
5858 if (eventType === 'RustToFfi' || eventType === 'OwnershipTransfer') rustToFfi++;
5859 else if (eventType === 'FfiToRust') ffiToRust++;
5860 else if (eventType === 'SharedAccess') {
5861 rustToFfi++;
5862 ffiToRust++;
5863 }
5864 });
5865 }
5866 });
5867
5868 return `
5869 <!-- Section background -->
5870 <rect x="0" y="0" width="600" height="200" fill="rgba(52, 73, 94, 0.3)"
5871 stroke='#34495e' stroke-width="2" rx="10"/>
5872
5873 <!-- Section title -->
5874 <text x="300" y="-10" text-anchor="middle" font-size="18" font-weight="bold" fill='#ecf0f1'>
5875 Cross-Language Memory Flow
5876 </text>
5877
5878 <!-- Rust territory -->
5879 <rect x="50" y="50" width="200" height="100" fill='#2ecc71' fill-opacity="0.2"
5880 stroke='#2ecc71' stroke-width="2" rx="8"/>
5881 <text x="150" y="110" text-anchor="middle" font-size="14" font-weight="bold" fill='#2ecc71'>
5882 RUST
5883 </text>
5884
5885 <!-- FFI territory -->
5886 <rect x="350" y="50" width="200" height="100" fill='#3498db' fill-opacity="0.2"
5887 stroke='#3498db' stroke-width="2" rx="8"/>
5888 <text x="450" y="110" text-anchor="middle" font-size="14" font-weight="bold" fill='#3498db'>
5889 FFI / C
5890 </text>
5891
5892 ${rustToFfi > 0 ? `
5893 <!-- Rust to FFI arrow -->
5894 <line x1="250" y1="80" x2="350" y2="80" stroke='#e74c3c' stroke-width="3" marker-end="url(#arrowhead)"/>
5895 <text x="300" y="75" text-anchor="middle" font-size="12" font-weight="bold" fill='#e74c3c'>
5896 ${rustToFfi}
5897 </text>
5898 ` : ''}
5899
5900 ${ffiToRust > 0 ? `
5901 <!-- FFI to Rust indicator -->
5902 <text x="300" y="135" text-anchor="middle" font-size="12" font-weight="bold" fill='#f39c12'>
5903 โ ${ffiToRust}
5904 </text>
5905 ` : ''}
5906 `;
5907}
5908
5909// Create unsafe hotspots
5910function createUnsafeHotspots(allocations) {
5911 const unsafeAllocations = allocations.filter(a =>
5912 a.source !== 'RustSafe' && a.allocation_source !== 'RustSafe'
5913 );
5914
5915 if (unsafeAllocations.length === 0) {
5916 return `
5917 <rect x="0" y="0" width="600" height="200" fill="rgba(52, 73, 94, 0.3)"
5918 stroke='#34495e' stroke-width="2" rx="10"/>
5919 <text x="300" y="-10" text-anchor="middle" font-size="18" font-weight="bold" fill='#ecf0f1'>
5920 Unsafe Memory Hotspots
5921 </text>
5922 <text x="300" y="100" text-anchor="middle" font-size="14" fill='#2ecc71'>
5923 No unsafe memory allocations detected
5924 </text>
5925 `;
5926 }
5927
5928 let svg = `
5929 <rect x="0" y="0" width="600" height="200" fill="rgba(52, 73, 94, 0.3)"
5930 stroke='#34495e' stroke-width="2" rx="10"/>
5931 <text x="300" y="-10" text-anchor="middle" font-size="18" font-weight="bold" fill='#ecf0f1'>
5932 Unsafe Memory Hotspots
5933 </text>
5934 `;
5935
5936 unsafeAllocations.slice(0, 6).forEach((allocation, i) => {
5937 const x = 80 + (i % 3) * 180;
5938 const y = 80 + Math.floor(i / 3) * 70;
5939 const size = (allocation.base && allocation.base.size) || allocation.size || 0;
5940 const sizeFactor = Math.max(5, Math.min(20, Math.log(size + 1) * 2));
5941
5942 const source = allocation.source || allocation.allocation_source || 'Unknown';
5943 let color = '#95a5a6';
5944 let label = 'OTHER';
5945
5946 if (source === 'UnsafeRust' || source.UnsafeRust) {
5947 color = '#e74c3c';
5948 label = 'UNSAFE';
5949 } else if (source === 'FfiC' || source.FfiC) {
5950 color = '#3498db';
5951 label = 'FFI';
5952 } else if (source === 'CrossBoundary' || source.CrossBoundary) {
5953 color = '#9b59b6';
5954 label = 'CROSS';
5955 }
5956
5957 svg += `
5958 <!-- Hotspot circle -->
5959 <circle cx="${x}" cy="${y}" r="${sizeFactor}" fill="${color}" fill-opacity="0.7"
5960 stroke="${color}" stroke-width="2" filter="url(#glow)"/>
5961
5962 <!-- Size label -->
5963 <text x="${x}" y="${y + 4}" text-anchor="middle" font-size="8" font-weight="bold" fill='#ffffff'>
5964 ${formatBytes(size)}
5965 </text>
5966
5967 <!-- Type label -->
5968 <text x="${x}" y="${y + 35}" text-anchor="middle" font-size="10" fill="${color}">
5969 ${label}
5970 </text>
5971 `;
5972 });
5973
5974 return svg;
5975}
5976
5977// Helper functions
5978function getViolationDescription(violation) {
5979 if (violation.DoubleFree || violation.type === 'DoubleFree') return 'Double Free';
5980 if (violation.InvalidFree || violation.type === 'InvalidFree') return 'Invalid Free';
5981 if (violation.PotentialLeak || violation.type === 'PotentialLeak') return 'Memory Leak';
5982 if (violation.CrossBoundaryRisk || violation.type === 'CrossBoundaryRisk') return 'Cross-Boundary Risk';
5983 return 'Unknown Violation';
5984}
5985
5986function addFFIInteractivity() {
5987 // Add hover effects to hotspots
5988 const hotspots = document.querySelectorAll('#unsafe-hotspots circle');
5989 hotspots.forEach(hotspot => {
5990 hotspot.addEventListener('mouseover', function() {
5991 this.setAttribute('r', parseInt(this.getAttribute('r')) * 1.2);
5992 });
5993 hotspot.addEventListener('mouseout', function() {
5994 this.setAttribute('r', parseInt(this.getAttribute('r')) / 1.2);
5995 });
5996 });
5997}
5998
5999function renderAllocationSourceChart(allocations) {
6000 const container = document.getElementById('allocation-source-chart');
6001 if (!container) return;
6002
6003 // Count allocations by source
6004 let safeCount = 0, unsafeCount = 0, ffiCount = 0, crossBoundaryCount = 0;
6005
6006 allocations.forEach(allocation => {
6007 if (allocation.source) {
6008 if (allocation.source.includes && allocation.source.includes('Safe')) safeCount++;
6009 else if (allocation.source.includes && allocation.source.includes('Unsafe')) unsafeCount++;
6010 else if (allocation.source.includes && allocation.source.includes('Ffi')) ffiCount++;
6011 else if (allocation.source.includes && allocation.source.includes('Cross')) crossBoundaryCount++;
6012 }
6013 });
6014
6015 const total = safeCount + unsafeCount + ffiCount + crossBoundaryCount;
6016 if (total === 0) {
6017 container.innerHTML = '<div style="text-align: center; color: #95a5a6;">No allocation data available</div>';
6018 return;
6019 }
6020
6021 const sources = [
6022 { label: 'Safe Rust', count: safeCount, color: '#2ecc71' },
6023 { label: 'Unsafe Rust', count: unsafeCount, color: '#e74c3c' },
6024 { label: 'FFI', count: ffiCount, color: '#3498db' },
6025 { label: 'Cross-boundary', count: crossBoundaryCount, color: '#9b59b6' }
6026 ];
6027
6028 let html = '<div style="display: flex; justify-content: space-around; align-items: end; height: 100px;">';
6029
6030 sources.forEach(source => {
6031 if (source.count > 0) {
6032 const barHeight = (source.count / total * 80);
6033 html += `
6034 <div style="text-align: center;">
6035 <div style="font-size: 12px; font-weight: bold; color: ${source.color}; margin-bottom: 5px;">${source.count}</div>
6036 <div style="width: 30px; height: ${barHeight}px; background: ${source.color}; margin: 0 auto 5px;"></div>
6037 <div style="font-size: 10px; color: #ecf0f1; writing-mode: vertical-rl; text-orientation: mixed;">${source.label}</div>
6038 </div>
6039 `;
6040 }
6041 });
6042
6043 html += '</div>';
6044 container.innerHTML = html;
6045}
6046
6047function renderSafetyStatusPanel(violations) {
6048 const container = document.getElementById('safety-status-panel');
6049 if (!container) return;
6050
6051 if (violations.length === 0) {
6052 container.innerHTML = `
6053 <div style="text-align: center; color: #27ae60;">
6054 <div style="font-size: 16px; font-weight: bold; margin-bottom: 10px;">No Safety Violations Detected</div>
6055 <div style="font-size: 12px; color: #2ecc71;">All unsafe operations and FFI calls appear to be memory-safe</div>
6056 </div>
6057 `;
6058 } else {
6059 let html = `
6060 <div style="text-align: center; color: #e74c3c; margin-bottom: 15px;">
6061 <div style="font-size: 16px; font-weight: bold;">${violations.length} Safety Violations Detected</div>
6062 </div>
6063 <div style="max-height: 80px; overflow-y: auto;">
6064 `;
6065
6066 violations.slice(0, 5).forEach(violation => {
6067 const description = violation.type || 'Unknown Violation';
6068 html += `<div style="font-size: 12px; color: #e74c3c; margin-bottom: 5px;">โข ${description}</div>`;
6069 });
6070
6071 html += '</div>';
6072 container.innerHTML = html;
6073 }
6074}
6075
6076function renderBoundaryFlowDiagram(allocations) {
6077 const container = document.getElementById('boundary-flow-diagram');
6078 if (!container) return;
6079
6080 // Count boundary events
6081 let rustToFfi = 0, ffiToRust = 0;
6082
6083 allocations.forEach(allocation => {
6084 if (allocation.cross_boundary_events) {
6085 allocation.cross_boundary_events.forEach(event => {
6086 if (event.event_type === 'RustToFfi') rustToFfi++;
6087 else if (event.event_type === 'FfiToRust') ffiToRust++;
6088 else if (event.event_type === 'OwnershipTransfer') rustToFfi++;
6089 else if (event.event_type === 'SharedAccess') {
6090 rustToFfi++;
6091 ffiToRust++;
6092 }
6093 });
6094 }
6095 });
6096
6097 container.innerHTML = `
6098 <div style="display: flex; justify-content: space-around; align-items: center; height: 80px;">
6099 <!-- Rust territory -->
6100 <div style="width: 150px; height: 60px; background: rgba(46, 204, 113, 0.2); border: 2px solid #2ecc71; border-radius: 8px; display: flex; align-items: center; justify-content: center;">
6101 <div style="text-align: center;">
6102 <div style="font-size: 14px; font-weight: bold; color: #2ecc71;">RUST</div>
6103 </div>
6104 </div>
6105
6106 <!-- Flow arrows -->
6107 <div style="text-align: center;">
6108 ${rustToFfi > 0 ? `
6109 <div style="margin-bottom: 5px;">
6110 <span style="color: #e74c3c; font-weight: bold;">${rustToFfi}</span>
6111 <span style="color: #e74c3c;"> โ</span>
6112 </div>
6113 ` : ''}
6114 ${ffiToRust > 0 ? `
6115 <div>
6116 <span style="color: #f39c12;"> โ</span>
6117 <span style="color: #f39c12; font-weight: bold;">${ffiToRust}</span>
6118 </div>
6119 ` : ''}
6120 </div>
6121
6122 <!-- FFI territory -->
6123 <div style="width: 150px; height: 60px; background: rgba(52, 152, 219, 0.2); border: 2px solid #3498db; border-radius: 8px; display: flex; align-items: center; justify-content: center;">
6124 <div style="text-align: center;">
6125 <div style="font-size: 14px; font-weight: bold; color: #3498db;">FFI / C</div>
6126 </div>
6127 </div>
6128 </div>
6129 `;
6130}
6131
6132function renderMemoryUsageAnalysis() {
6133 // Will be implemented if container exists
6134}
6135
6136function renderMemoryFragmentation() {
6137 const container = document.getElementById('memoryFragmentation');
6138 if (!container) return;
6139
6140 const data = window.analysisData || {};
6141 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
6142
6143 if (allocs.length === 0) {
6144 container.innerHTML = '<div style="text-align: center; color: var(--text-secondary); padding: 40px;">No allocation data available</div>';
6145 return;
6146 }
6147
6148 // Calculate fragmentation metrics
6149 const sizes = allocs.map(a => a.size || 0).filter(s => s > 0);
6150 const totalMemory = sizes.reduce((sum, size) => sum + size, 0);
6151 const avgSize = totalMemory / sizes.length;
6152 const variance = sizes.reduce((sum, size) => sum + Math.pow(size - avgSize, 2), 0) / sizes.length;
6153 const stdDev = Math.sqrt(variance);
6154 const fragmentation = Math.min(100, (stdDev / avgSize) * 100);
6155
6156 // Sort allocations by size for visualization
6157 const sortedAllocs = allocs.slice().sort((a, b) => (a.size || 0) - (b.size || 0));
6158
6159 // Create memory fragmentation visualization
6160 container.innerHTML = `
6161 <div style="height: 100%; display: flex; flex-direction: column; gap: 12px; padding: 12px;">
6162 <!-- Fragmentation Score -->
6163 <div style="display: flex; justify-content: space-between; align-items: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
6164 <div>
6165 <div style="font-size: 14px; font-weight: 600; color: var(--text-primary);">Fragmentation Level</div>
6166 <div style="font-size: 11px; color: var(--text-secondary);">Memory size variance indicator</div>
6167 </div>
6168 <div style="text-align: right;">
6169 <div style="font-size: 24px; font-weight: 700; color: ${fragmentation > 50 ? 'var(--primary-red)' : fragmentation > 25 ? 'var(--primary-orange)' : 'var(--primary-green)'};">
6170 ${fragmentation.toFixed(1)}%
6171 </div>
6172 <div style="font-size: 10px; color: var(--text-secondary);">
6173 ${fragmentation > 50 ? 'High' : fragmentation > 25 ? 'Medium' : 'Low'}
6174 </div>
6175 </div>
6176 </div>
6177
6178 <!-- Memory Layout Visualization -->
6179 <div style="flex: 1; background: var(--bg-secondary); border-radius: 8px; padding: 12px;">
6180 <div style="font-size: 12px; font-weight: 600; color: var(--text-primary); margin-bottom: 8px;">Memory Layout (${allocs.length} allocations)</div>
6181 <div style="height: 80px; background: var(--bg-primary); border-radius: 6px; padding: 4px; position: relative; overflow: hidden;">
6182 <!-- Memory blocks representing allocations -->
6183 <div style="display: flex; height: 100%; align-items: end; gap: 1px;">
6184 ${sortedAllocs.slice(0, 40).map((alloc, i) => {
6185 const size = alloc.size || 0;
6186 const maxSize = Math.max(...sizes);
6187 const height = Math.max(8, (size / maxSize) * 70);
6188 const width = Math.max(2, Math.min(8, 100 / Math.min(40, allocs.length)));
6189
6190 let color = '#10b981'; // Green for small
6191 if (size > 10240) color = '#ef4444'; // Red for large
6192 else if (size > 1024) color = '#f59e0b'; // Orange for medium
6193 else if (size > 100) color = '#3b82f6'; // Blue for small-medium
6194
6195 return `
6196 <div style="width: ${width}px; height: ${height}px; background: ${color};
6197 border-radius: 1px; cursor: pointer; transition: all 0.2s; opacity: 0.8;"
6198 title="${alloc.var_name}: ${formatBytes(size)}"
6199 onmouseover="this.style.transform='scaleY(1.2)'; this.style.opacity='1'"
6200 onmouseout="this.style.transform='scaleY(1)'; this.style.opacity='0.8'"
6201 onclick="showAllocationDetail('${alloc.ptr}')"></div>
6202 `;
6203 }).join('')}
6204 </div>
6205
6206 <!-- Size legend -->
6207 <div style="position: absolute; bottom: 4px; right: 4px; display: flex; gap: 4px; font-size: 8px;">
6208 <div style="display: flex; align-items: center; gap: 2px;">
6209 <div style="width: 8px; height: 4px; background: #10b981;"></div>
6210 <span style="color: var(--text-secondary);">Tiny</span>
6211 </div>
6212 <div style="display: flex; align-items: center; gap: 2px;">
6213 <div style="width: 8px; height: 4px; background: #3b82f6;"></div>
6214 <span style="color: var(--text-secondary);">Small</span>
6215 </div>
6216 <div style="display: flex; align-items: center; gap: 2px;">
6217 <div style="width: 8px; height: 4px; background: #f59e0b;"></div>
6218 <span style="color: var(--text-secondary);">Medium</span>
6219 </div>
6220 <div style="display: flex; align-items: center; gap: 2px;">
6221 <div style="width: 8px; height: 4px; background: #ef4444;"></div>
6222 <span style="color: var(--text-secondary);">Large</span>
6223 </div>
6224 </div>
6225 </div>
6226
6227 <!-- Fragmentation Analysis -->
6228 <div style="margin-top: 8px; display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; font-size: 11px;">
6229 <div style="text-align: center; padding: 6px; background: var(--bg-primary); border-radius: 4px;">
6230 <div style="font-weight: 600; color: var(--text-primary);">${formatBytes(avgSize)}</div>
6231 <div style="color: var(--text-secondary);">Avg Size</div>
6232 </div>
6233 <div style="text-align: center; padding: 6px; background: var(--bg-primary); border-radius: 4px;">
6234 <div style="font-weight: 600; color: var(--text-primary);">${formatBytes(Math.max(...sizes))}</div>
6235 <div style="color: var(--text-secondary);">Max Size</div>
6236 </div>
6237 <div style="text-align: center; padding: 6px; background: var(--bg-primary); border-radius: 4px;">
6238 <div style="font-weight: 600; color: var(--text-primary);">${formatBytes(Math.min(...sizes))}</div>
6239 <div style="color: var(--text-secondary);">Min Size</div>
6240 </div>
6241 </div>
6242 </div>
6243 </div>
6244 `;
6245}
6246
6247function renderMemoryGrowthTrends() {
6248 const container = document.getElementById('growth');
6249 if (!container) return;
6250
6251 const data = window.analysisData || {};
6252 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
6253 if (allocs.length === 0) {
6254 container.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">No growth data available</div>';
6255 return;
6256 }
6257
6258 // Create a proper growth chart using Chart.js
6259 const canvas = document.createElement('canvas');
6260 canvas.style.width = '100%';
6261 canvas.style.height = '100%';
6262 container.innerHTML = '';
6263 container.appendChild(canvas);
6264
6265 const sorted = allocs.slice().sort((a,b) => (a.timestamp_alloc||0) - (b.timestamp_alloc||0));
6266 let cumulative = 0;
6267 const points = [];
6268
6269 for (let i = 0; i < Math.min(sorted.length, 30); i++) {
6270 cumulative += sorted[i].size || 0;
6271 points.push({ x: i, y: cumulative });
6272 }
6273
6274 if (points.length > 1 && window.Chart) {
6275 new Chart(canvas, {
6276 type: 'line',
6277 data: {
6278 labels: points.map((_, i) => `T${i}`),
6279 datasets: [{
6280 label: 'Memory Growth',
6281 data: points.map(p => p.y),
6282 borderColor: '#059669',
6283 backgroundColor: 'rgba(5, 150, 105, 0.1)',
6284 fill: true,
6285 tension: 0.3,
6286 pointRadius: 3,
6287 pointHoverRadius: 5
6288 }]
6289 },
6290 options: {
6291 responsive: true,
6292 maintainAspectRatio: false,
6293 plugins: {
6294 legend: { display: false }
6295 },
6296 scales: {
6297 x: {
6298 title: {
6299 display: true,
6300 text: 'Time Steps'
6301 }
6302 },
6303 y: {
6304 beginAtZero: true,
6305 title: {
6306 display: true,
6307 text: 'Cumulative Memory'
6308 },
6309 ticks: {
6310 callback: function(value) {
6311 return formatBytes(value);
6312 }
6313 }
6314 }
6315 }
6316 }
6317 });
6318 }
6319}
6320
6321function setupLifecycle() {
6322 // Lifecycle setup functionality
6323}
6324
6325function populateUnsafeTable() {
6326 const data = window.analysisData || {};
6327 const root = data.unsafe_ffi || {};
6328 const ops = root.enhanced_ffi_data || root.unsafe_operations || root.allocations || [];
6329 const unsafeTable = document.getElementById('unsafeTable');
6330 if (!unsafeTable) return;
6331
6332 unsafeTable.innerHTML = (ops || []).slice(0, 50).map(op => {
6333 const riskLevel = op.risk_level || ((op.safety_violations||[]).length > 2 ? 'High' :
6334 ((op.safety_violations||[]).length > 0 ? 'Medium' : 'Low'));
6335
6336 const riskText = riskLevel === 'High' ? 'High Risk' : (riskLevel === 'Medium' ? 'Medium Risk' : 'Low Risk');
6337 const riskClass = riskLevel === 'High' ? 'risk-high' : (riskLevel === 'Medium' ? 'risk-medium' : 'risk-low');
6338
6339 return `<tr>
6340 <td>${op.location || op.var_name || 'Unknown'}</td>
6341 <td>${op.operation_type || op.type_name || 'Unknown'}</td>
6342 <td><span class="status-badge ${riskClass}">${riskText}</span></td>
6343 </tr>`;
6344 }).join('');
6345}
6346
6347function renderVariableGraph() {
6348 const container = document.getElementById('graph');
6349 if (!container) return;
6350
6351 const data = window.analysisData || {};
6352 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
6353 if (allocs.length === 0) {
6354 container.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">No relationship data available</div>';
6355 return;
6356 }
6357
6358 // Enhanced variable relationship graph with pan/zoom and drag functionality
6359 const nodes = allocs.slice(0, 30).map((a, i) => ({
6360 id: i,
6361 name: a.var_name || `var_${i}`,
6362 type: a.type_name || 'unknown',
6363 size: a.size || 0,
6364 status: a.is_leaked ? 'leaked' : (a.timestamp_dealloc ? 'freed' : 'active'),
6365 ptr: a.ptr || 'unknown',
6366 timestamp_alloc: a.timestamp_alloc || 0,
6367 timestamp_dealloc: a.timestamp_dealloc || null,
6368 x: 200 + (i % 6) * 120 + Math.random() * 40,
6369 y: 200 + Math.floor(i / 6) * 120 + Math.random() * 40,
6370 isDragging: false
6371 }));
6372
6373 // Create enhanced links with copy/clone/move relationships
6374 const links = [];
6375 for (let i = 0; i < nodes.length; i++) {
6376 for (let j = i + 1; j < nodes.length; j++) {
6377 const nodeA = nodes[i];
6378 const nodeB = nodes[j];
6379 const allocA = allocs[i];
6380 const allocB = allocs[j];
6381
6382 // Clone relationship (based on clone_info)
6383 if (allocA.clone_info?.clone_count > 0 && allocB.clone_info?.is_clone) {
6384 links.push({
6385 source: i, target: j, type: 'clone',
6386 color: '#f59e0b', strokeWidth: 3, dashArray: '5,5'
6387 });
6388 }
6389 // Copy relationship (same type, similar size)
6390 else if (nodeA.type === nodeB.type &&
6391 Math.abs(nodeA.size - nodeB.size) < Math.max(nodeA.size, nodeB.size) * 0.1) {
6392 links.push({
6393 source: i, target: j, type: 'copy',
6394 color: '#06b6d4', strokeWidth: 2, dashArray: 'none'
6395 });
6396 }
6397 // Move relationship (same type, different timestamps)
6398 else if (nodeA.type === nodeB.type &&
6399 Math.abs(nodeA.timestamp_alloc - nodeB.timestamp_alloc) > 1000000) {
6400 links.push({
6401 source: i, target: j, type: 'move',
6402 color: '#8b5cf6', strokeWidth: 2, dashArray: '10,5'
6403 });
6404 }
6405 // General relationship (same type prefix)
6406 else if (nodeA.type === nodeB.type ||
6407 nodeA.name.startsWith(nodeB.name.substring(0, 3))) {
6408 links.push({
6409 source: i, target: j, type: 'related',
6410 color: 'var(--border-light)', strokeWidth: 1, dashArray: 'none'
6411 });
6412 }
6413 }
6414 }
6415
6416 const width = 1200; // Larger virtual canvas
6417 const height = 800;
6418 const viewWidth = container.offsetWidth || 500;
6419 const viewHeight = 400;
6420
6421 // Create SVG with pan/zoom capabilities and legend
6422 let html = `
6423 <div style="position: relative; width: 100%; height: ${viewHeight}px; overflow: hidden; border: 1px solid var(--border-light); border-radius: 8px;">
6424 <!-- Relationship Legend -->
6425 <div style="position: absolute; top: 10px; right: 10px; background: var(--bg-primary); padding: 12px; border-radius: 8px; font-size: 11px; z-index: 10; border: 1px solid var(--border-light); box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
6426 <div style="font-weight: bold; margin-bottom: 8px; color: var(--text-primary); font-size: 12px;">Variable Relationships</div>
6427 <div style="display: flex; align-items: center; margin-bottom: 4px;">
6428 <svg width="20" height="3" style="margin-right: 8px;">
6429 <line x1="0" y1="1.5" x2="20" y2="1.5" stroke='#06b6d4' stroke-width="2"/>
6430 </svg>
6431 <span style="color: var(--text-secondary);">Copy</span>
6432 </div>
6433 <div style="display: flex; align-items: center; margin-bottom: 4px;">
6434 <svg width="20" height="3" style="margin-right: 8px;">
6435 <line x1="0" y1="1.5" x2="20" y2="1.5" stroke='#f59e0b' stroke-width="3" stroke-dasharray="5,5"/>
6436 </svg>
6437 <span style="color: var(--text-secondary);">Clone</span>
6438 </div>
6439 <div style="display: flex; align-items: center; margin-bottom: 4px;">
6440 <svg width="20" height="3" style="margin-right: 8px;">
6441 <line x1="0" y1="1.5" x2="20" y2="1.5" stroke='#8b5cf6' stroke-width="2" stroke-dasharray="10,5"/>
6442 </svg>
6443 <span style="color: var(--text-secondary);">Move</span>
6444 </div>
6445 <div style="display: flex; align-items: center;">
6446 <svg width="20" height="3" style="margin-right: 8px;">
6447 <line x1="0" y1="1.5" x2="20" y2="1.5" stroke='#64748b' stroke-width="1"/>
6448 </svg>
6449 <span style="color: var(--text-secondary);">Related</span>
6450 </div>
6451 </div>
6452
6453 <svg id="graph-svg" width="100%" height="100%" viewBox="0 0 ${viewWidth} ${viewHeight}" style="background: var(--bg-secondary); cursor: grab;">
6454 <defs>
6455 <filter id="node-glow">
6456 <feGaussianBlur stdDeviation="3" result="coloredBlur"/>
6457 <feMerge>
6458 <feMergeNode in="coloredBlur"/>
6459 <feMergeNode in="SourceGraphic"/>
6460 </feMerge>
6461 </filter>
6462 <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="3"
6463 markerWidth="6" markerHeight="6" orient="auto">
6464 <path d="M0,0 L0,6 L9,3 z" fill="var(--border-light)" opacity="0.6"/>
6465 </marker>
6466 </defs>
6467 <g id="graph-container" transform="translate(0,0) scale(1)">
6468 <g id="links-group">`;
6469
6470 // Draw enhanced links with relationship types
6471 links.forEach((link, linkIndex) => {
6472 const source = nodes[link.source];
6473 const target = nodes[link.target];
6474 const strokeDashArray = link.dashArray !== 'none' ? link.dashArray : '';
6475
6476 html += `
6477 <line id="link-${linkIndex}" x1="${source.x}" y1="${source.y}" x2="${target.x}" y2="${target.y}"
6478 stroke="${link.color}" stroke-width="${link.strokeWidth}" opacity="0.8"
6479 stroke-dasharray="${strokeDashArray}">
6480 <animate attributeName="opacity" values="0.8;1;0.8" dur="3s" repeatCount="indefinite"/>
6481 </line>`;
6482
6483 // Add relationship label for special types
6484 const midX = (source.x + target.x) / 2;
6485 const midY = (source.y + target.y) / 2;
6486 if (link.type !== 'related') {
6487 html += `
6488 <text x="${midX}" y="${midY - 5}" text-anchor="middle" font-size="8"
6489 fill="${link.color}" font-weight="bold" opacity="0.9">
6490 ${link.type.toUpperCase()}
6491 </text>`;
6492 }
6493 });
6494
6495 html += '</g><g id="nodes-group">';
6496
6497 // Draw nodes
6498 nodes.forEach((node, nodeIndex) => {
6499 const radius = Math.max(12, Math.min(30, Math.log(node.size + 1) * 4));
6500 let color = '#6b7280'; // default
6501
6502 if (node.type.includes('String')) color = '#fbbf24';
6503 else if (node.type.includes('Vec')) color = '#3b82f6';
6504 else if (node.type.includes('Box') || node.type.includes('Rc')) color = '#8b5cf6';
6505 else if (node.type.includes('HashMap')) color = '#10b981';
6506 else if (node.type.includes('Arc')) color = '#f59e0b';
6507
6508 if (node.status === 'leaked') color = '#dc2626';
6509 else if (node.status === 'freed') color = '#9ca3af';
6510
6511 html += `
6512 <circle
6513 id="node-${nodeIndex}"
6514 cx="${node.x}"
6515 cy="${node.y}"
6516 r="${radius}"
6517 fill="${color}"
6518 stroke="white"
6519 stroke-width="3"
6520 filter="url(#node-glow)"
6521 style="cursor: grab;"
6522 class="graph-node"
6523 data-index="${nodeIndex}"
6524 data-name="${node.name}"
6525 data-type="${node.type}"
6526 data-size="${node.size}"
6527 data-status="${node.status}"
6528 data-ptr="${node.ptr}"
6529 data-alloc="${node.timestamp_alloc}"
6530 data-dealloc="${node.timestamp_dealloc || 'null'}"
6531 />
6532 <text
6533 id="text-${nodeIndex}"
6534 x="${node.x}"
6535 y="${node.y + radius + 20}"
6536 text-anchor="middle"
6537 font-size="12"
6538 font-weight="600"
6539 fill="var(--text-primary)"
6540 style="pointer-events: none;"
6541 >${node.name.length > 12 ? node.name.substring(0, 10) + '...' : node.name}</text>
6542 `;
6543 });
6544
6545 html += `
6546 </g>
6547 </g>
6548 </svg>
6549
6550 <!-- Controls -->
6551 <div style="position: absolute; top: 10px; right: 10px; display: flex; gap: 8px;">
6552 <button id="zoom-in" style="background: var(--primary-blue); color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 14px;">
6553 <i class="fa fa-plus"></i>
6554 </button>
6555 <button id="zoom-out" style="background: var(--primary-blue); color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 14px;">
6556 <i class="fa fa-minus"></i>
6557 </button>
6558 <button id="reset-view" style="background: var(--primary-green); color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 14px;">
6559 <i class="fa fa-home"></i>
6560 </button>
6561 </div>
6562
6563 <!-- Node detail panel -->
6564 <div id="node-detail-panel" style="
6565 position: absolute;
6566 background: var(--bg-primary);
6567 border: 1px solid var(--border-light);
6568 border-radius: 8px;
6569 padding: 12px;
6570 width: 280px;
6571 box-shadow: 0 10px 25px rgba(0,0,0,0.1);
6572 z-index: 1000;
6573 font-size: 0.875rem;
6574 display: none;
6575 backdrop-filter: blur(10px);
6576 ">
6577 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
6578 <h4 id="detail-title" style="margin: 0; font-size: 1rem; font-weight: 600;"></h4>
6579 <button onclick="hideNodeDetails()" style="background: none; border: none; font-size: 16px; cursor: pointer; color: var(--text-secondary);">ร</button>
6580 </div>
6581 <div id="detail-content"></div>
6582 </div>
6583
6584 <!-- Legend -->
6585 <div style="display: flex; gap: 12px; margin-top: 12px; font-size: 0.75rem; flex-wrap: wrap;">
6586 <div style="display: flex; align-items: center; gap: 4px;">
6587 <div style="width: 10px; height: 10px; background: #fbbf24; border-radius: 50%;"></div>
6588 <span>String</span>
6589 </div>
6590 <div style="display: flex; align-items: center; gap: 4px;">
6591 <div style="width: 10px; height: 10px; background: #3b82f6; border-radius: 50%;"></div>
6592 <span>Vec</span>
6593 </div>
6594 <div style="display: flex; align-items: center; gap: 4px;">
6595 <div style="width: 10px; height: 10px; background: #8b5cf6; border-radius: 50%;"></div>
6596 <span>Smart Ptr</span>
6597 </div>
6598 <div style="display: flex; align-items: center; gap: 4px;">
6599 <div style="width: 10px; height: 10px; background: #dc2626; border-radius: 50%;"></div>
6600 <span>Leaked</span>
6601 </div>
6602 <div style="display: flex; align-items: center; gap: 4px;">
6603 <div style="width: 10px; height: 10px; background: #9ca3af; border-radius: 50%;"></div>
6604 <span>Freed</span>
6605 </div>
6606 </div>
6607 </div>
6608 `;
6609
6610 container.innerHTML = html;
6611
6612 // Store nodes and links data for interaction
6613 window.graphNodes = nodes;
6614 window.graphLinks = links;
6615 window.graphTransform = { x: 0, y: 0, scale: 1 };
6616
6617 // Add pan/zoom and drag functionality
6618 setTimeout(() => {
6619 setupGraphInteractions();
6620 setupPanZoom();
6621 }, 100);
6622}
6623
6624// Graph interaction functions
6625function setupGraphInteractions() {
6626 const svg = document.getElementById('graph-svg');
6627 const nodeElements = document.querySelectorAll('.graph-node');
6628
6629 let draggedNode = null;
6630 let isDragging = false;
6631 let startX, startY;
6632
6633 nodeElements.forEach(node => {
6634 // Mouse events for drag
6635 node.addEventListener('mousedown', function(e) {
6636 e.preventDefault();
6637 draggedNode = this;
6638 isDragging = false;
6639 startX = e.clientX;
6640 startY = e.clientY;
6641 this.style.cursor = 'grabbing';
6642 svg.style.cursor = 'grabbing';
6643 });
6644
6645 // Click event for details
6646 node.addEventListener('click', function(e) {
6647 if (!isDragging) {
6648 showNodeDetails(this);
6649 }
6650 });
6651
6652 // Hover effects
6653 node.addEventListener('mouseover', function() {
6654 if (!draggedNode) {
6655 const currentRadius = parseInt(this.getAttribute('r'));
6656 this.setAttribute('r', Math.round(currentRadius * 1.2));
6657 }
6658 });
6659
6660 node.addEventListener('mouseout', function() {
6661 if (!draggedNode) {
6662 const currentRadius = parseInt(this.getAttribute('r'));
6663 this.setAttribute('r', Math.round(currentRadius / 1.2));
6664 }
6665 });
6666 });
6667
6668 // Global mouse events for dragging
6669 document.addEventListener('mousemove', function(e) {
6670 if (draggedNode) {
6671 e.preventDefault();
6672 const deltaX = e.clientX - startX;
6673 const deltaY = e.clientY - startY;
6674
6675 if (Math.abs(deltaX) > 3 || Math.abs(deltaY) > 3) {
6676 isDragging = true;
6677 }
6678
6679 if (isDragging) {
6680 const rect = svg.getBoundingClientRect();
6681 const svgX = Math.max(20, Math.min(rect.width - 20, e.clientX - rect.left));
6682 const svgY = Math.max(20, Math.min(rect.height - 20, e.clientY - rect.top));
6683
6684 // Update node position
6685 draggedNode.setAttribute('cx', svgX);
6686 draggedNode.setAttribute('cy', svgY);
6687
6688 // Update text position
6689 const nodeIndex = draggedNode.getAttribute('data-index');
6690 const textElement = document.getElementById(`text-${nodeIndex}`);
6691 if (textElement) {
6692 textElement.setAttribute('x', svgX);
6693 textElement.setAttribute('y', svgY + parseInt(draggedNode.getAttribute('r')) + 15);
6694 }
6695
6696 // Update connected links
6697 updateConnectedLinks(parseInt(nodeIndex), svgX, svgY);
6698
6699 // Update stored node position
6700 if (window.graphNodes && window.graphNodes[nodeIndex]) {
6701 window.graphNodes[nodeIndex].x = svgX;
6702 window.graphNodes[nodeIndex].y = svgY;
6703 }
6704 }
6705 }
6706 });
6707
6708 document.addEventListener('mouseup', function(e) {
6709 if (draggedNode) {
6710 draggedNode.style.cursor = 'grab';
6711 svg.style.cursor = 'grab';
6712
6713 // Reset hover effect
6714 const originalRadius = parseInt(draggedNode.getAttribute('r'));
6715 draggedNode.setAttribute('r', originalRadius);
6716
6717 draggedNode = null;
6718 setTimeout(() => { isDragging = false; }, 100);
6719 }
6720 });
6721}
6722
6723function updateConnectedLinks(nodeIndex, newX, newY) {
6724 if (!window.graphLinks) return;
6725
6726 window.graphLinks.forEach((link, linkIndex) => {
6727 const linkElement = document.getElementById(`link-${linkIndex}`);
6728 if (!linkElement) return;
6729
6730 if (link.source === nodeIndex) {
6731 linkElement.setAttribute('x1', newX);
6732 linkElement.setAttribute('y1', newY);
6733 }
6734 if (link.target === nodeIndex) {
6735 linkElement.setAttribute('x2', newX);
6736 linkElement.setAttribute('y2', newY);
6737 }
6738 });
6739}
6740
6741function showNodeDetails(nodeElement) {
6742 const panel = document.getElementById('node-detail-panel');
6743 const title = document.getElementById('detail-title');
6744 const content = document.getElementById('detail-content');
6745
6746 if (!panel || !title || !content) return;
6747
6748 const name = nodeElement.getAttribute('data-name');
6749 const type = nodeElement.getAttribute('data-type');
6750 const size = parseInt(nodeElement.getAttribute('data-size'));
6751 const status = nodeElement.getAttribute('data-status');
6752 const ptr = nodeElement.getAttribute('data-ptr');
6753 const alloc = nodeElement.getAttribute('data-alloc');
6754 const dealloc = nodeElement.getAttribute('data-dealloc');
6755
6756 title.textContent = name;
6757
6758 const lifetime = dealloc !== 'null' ? parseInt(dealloc) - parseInt(alloc) : 'Active';
6759
6760 content.innerHTML = `
6761 <div style="margin-bottom: 8px;">
6762 <strong>Type:</strong> ${formatTypeName(type)}
6763 </div>
6764 <div style="margin-bottom: 8px;">
6765 <strong>Size:</strong> ${formatBytes(size)}
6766 </div>
6767 <div style="margin-bottom: 8px;">
6768 <strong>Status:</strong> <span style="color: ${status === 'leaked' ? '#dc2626' : status === 'freed' ? '#6b7280' : '#059669'};">${status.charAt(0).toUpperCase() + status.slice(1)}</span>
6769 </div>
6770 <div style="margin-bottom: 8px;">
6771 <strong>Pointer:</strong> <code style="font-size: 0.8rem; background: var(--bg-secondary); padding: 2px 4px; border-radius: 3px;">${ptr}</code>
6772 </div>
6773 <div style="margin-bottom: 8px;">
6774 <strong>Allocated:</strong> ${alloc}ms
6775 </div>
6776 <div style="margin-bottom: 8px;">
6777 <strong>Lifetime:</strong> ${typeof lifetime === 'number' ? lifetime + 'ms' : lifetime}
6778 </div>
6779 `;
6780
6781 // Position panel near the node
6782 const rect = nodeElement.getBoundingClientRect();
6783 const containerRect = nodeElement.closest('#graph').getBoundingClientRect();
6784
6785 panel.style.left = Math.min(rect.left - containerRect.left + 30, containerRect.width - 300) + 'px';
6786 panel.style.top = Math.max(rect.top - containerRect.top - 50, 10) + 'px';
6787 panel.style.display = 'block';
6788}
6789
6790function hideNodeDetails() {
6791 const panel = document.getElementById('node-detail-panel');
6792 if (panel) {
6793 panel.style.display = 'none';
6794 }
6795}
6796
6797// Pan and zoom functionality for the graph
6798function setupPanZoom() {
6799 const svg = document.getElementById('graph-svg');
6800 const container = document.getElementById('graph-container');
6801 const zoomInBtn = document.getElementById('zoom-in');
6802 const zoomOutBtn = document.getElementById('zoom-out');
6803 const resetBtn = document.getElementById('reset-view');
6804
6805 if (!svg || !container) return;
6806
6807 let isPanning = false;
6808 let startX, startY;
6809 let transform = window.graphTransform;
6810
6811 // Zoom functions
6812 function updateTransform() {
6813 container.setAttribute('transform', `translate(${transform.x},${transform.y}) scale(${transform.scale})`);
6814 }
6815
6816 function zoom(factor, centerX = 0, centerY = 0) {
6817 const newScale = Math.max(0.1, Math.min(3, transform.scale * factor));
6818
6819 // Zoom towards center point
6820 const dx = centerX - transform.x;
6821 const dy = centerY - transform.y;
6822
6823 transform.x = centerX - dx * (newScale / transform.scale);
6824 transform.y = centerY - dy * (newScale / transform.scale);
6825 transform.scale = newScale;
6826
6827 updateTransform();
6828 }
6829
6830 // Button controls
6831 if (zoomInBtn) {
6832 zoomInBtn.addEventListener('click', () => {
6833 const rect = svg.getBoundingClientRect();
6834 zoom(1.2, rect.width / 2, rect.height / 2);
6835 });
6836 }
6837
6838 if (zoomOutBtn) {
6839 zoomOutBtn.addEventListener('click', () => {
6840 const rect = svg.getBoundingClientRect();
6841 zoom(0.8, rect.width / 2, rect.height / 2);
6842 });
6843 }
6844
6845 if (resetBtn) {
6846 resetBtn.addEventListener('click', () => {
6847 transform.x = 0;
6848 transform.y = 0;
6849 transform.scale = 1;
6850 updateTransform();
6851 });
6852 }
6853
6854 // Mouse wheel zoom
6855 svg.addEventListener('wheel', function(e) {
6856 e.preventDefault();
6857 const rect = svg.getBoundingClientRect();
6858 const mouseX = e.clientX - rect.left;
6859 const mouseY = e.clientY - rect.top;
6860
6861 const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
6862 zoom(zoomFactor, mouseX, mouseY);
6863 });
6864
6865 // Pan functionality
6866 svg.addEventListener('mousedown', function(e) {
6867 if (e.target === svg || e.target === container) {
6868 isPanning = true;
6869 startX = e.clientX - transform.x;
6870 startY = e.clientY - transform.y;
6871 svg.style.cursor = 'grabbing';
6872 }
6873 });
6874
6875 document.addEventListener('mousemove', function(e) {
6876 if (isPanning) {
6877 e.preventDefault();
6878 transform.x = e.clientX - startX;
6879 transform.y = e.clientY - startY;
6880 updateTransform();
6881 }
6882 });
6883
6884 document.addEventListener('mouseup', function() {
6885 if (isPanning) {
6886 isPanning = false;
6887 svg.style.cursor = 'grab';
6888 }
6889 });
6890}
6891
6892// Lifecycle toggle functionality
6893function setupLifecycleToggle() {
6894 // Hard reset any previous click bindings by cloning the button
6895 const oldBtn = document.getElementById('toggle-lifecycle');
6896 if (oldBtn) {
6897 const cloned = oldBtn.cloneNode(true);
6898 oldBtn.parentNode.replaceChild(cloned, oldBtn);
6899 }
6900
6901 const toggleBtn = document.getElementById('toggle-lifecycle');
6902 if (!toggleBtn) return;
6903
6904 const lifeContainer = document.getElementById('lifetimeVisualization');
6905
6906 let isExpanded = false;
6907
6908 toggleBtn.addEventListener('click', function() {
6909 const container = document.getElementById('lifetimeVisualization');
6910 if (!container) return;
6911
6912 const data = window.analysisData || {};
6913 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
6914
6915 if (allocs.length === 0) {
6916 container.innerHTML = '<div style="padding: 20px; text-align: center; color: var(--text-secondary);">No lifecycle data available</div>';
6917 return;
6918 }
6919
6920 const icon = toggleBtn.querySelector('i');
6921 const text = toggleBtn.querySelector('span');
6922
6923 if (!isExpanded) {
6924 renderFullLifecycleTimeline(allocs);
6925 icon.className = 'fa fa-chevron-up';
6926 text.textContent = 'Show Less';
6927 isExpanded = true;
6928 } else {
6929 renderLimitedLifecycleTimeline(allocs);
6930 icon.className = 'fa fa-chevron-down';
6931 text.textContent = 'Show All';
6932 isExpanded = false;
6933 }
6934 // Ensure the container scrolls to top after toggle for visual confirmation
6935 if (lifeContainer) { lifeContainer.scrollTop = 0; }
6936 });
6937}
6938
6939function renderLimitedLifecycleTimeline(allocs) {
6940 const container = document.getElementById('lifetimeVisualization');
6941 if (!container) return;
6942
6943 // Create timeline visualization (limited to 20)
6944 const maxTime = Math.max(...allocs.map(a => a.timestamp_dealloc || a.timestamp_alloc || 0));
6945 const minTime = Math.min(...allocs.map(a => a.timestamp_alloc || 0));
6946 const timeRange = maxTime - minTime || 1;
6947
6948 let html = '<div style="padding: 16px; max-height: 300px; overflow-y: auto;">';
6949
6950 allocs.slice(0, 20).forEach((alloc, index) => {
6951 const startTime = alloc.timestamp_alloc || 0;
6952 const endTime = alloc.timestamp_dealloc || maxTime;
6953 const startPercent = ((startTime - minTime) / timeRange) * 100;
6954 const widthPercent = ((endTime - startTime) / timeRange) * 100;
6955 const isActive = !alloc.timestamp_dealloc;
6956
6957 html += `
6958 <div style="margin-bottom: 8px;">
6959 <div style="display: flex; justify-content: space-between; margin-bottom: 4px; font-size: 0.8rem;">
6960 <span style="font-weight: 600;">${alloc.var_name || `var_${index}`}</span>
6961 <span style="color: var(--text-secondary);">${formatBytes(alloc.size || 0)}</span>
6962 </div>
6963 <div style="position: relative; background: var(--bg-secondary); height: 8px; border-radius: 4px;">
6964 <div style="
6965 position: absolute;
6966 left: ${startPercent}%;
6967 width: ${widthPercent}%;
6968 height: 100%;
6969 background: ${isActive ? 'linear-gradient(to right, #059669, #34d399)' : 'linear-gradient(to right, #2563eb, #60a5fa)'};
6970 border-radius: 4px;
6971 ${isActive ? 'animation: pulse 2s infinite;' : ''}
6972 " title="Lifetime: ${endTime - startTime}ms"></div>
6973 </div>
6974 </div>
6975 `;
6976 });
6977
6978 html += '</div>';
6979
6980 // Add CSS for pulse animation
6981 html += `
6982 <style>
6983 @keyframes pulse {
6984 0%, 100% { opacity: 1; }
6985 50% { opacity: 0.7; }
6986 }
6987 </style>
6988 `;
6989
6990 container.innerHTML = html;
6991}
6992
6993function renderFullLifecycleTimeline(allocs) {
6994 const container = document.getElementById('lifetimeVisualization');
6995 if (!container) return;
6996
6997 // Create full timeline visualization
6998 const maxTime = Math.max(...allocs.map(a => a.timestamp_dealloc || a.timestamp_alloc || 0));
6999 const minTime = Math.min(...allocs.map(a => a.timestamp_alloc || 0));
7000 const timeRange = maxTime - minTime || 1;
7001
7002 let html = '<div style="padding: 16px; max-height: 600px; overflow-y: auto;">';
7003
7004 // Add timeline header
7005 html += `
7006 <div style="margin-bottom: 16px; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
7007 <div style="font-weight: 600; margin-bottom: 8px;">Full Lifecycle Timeline</div>
7008 <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; font-size: 0.8rem;">
7009 <div>
7010 <div style="color: var(--text-secondary);">Total Variables</div>
7011 <div style="font-weight: 600;">${allocs.length}</div>
7012 </div>
7013 <div>
7014 <div style="color: var(--text-secondary);">Active</div>
7015 <div style="font-weight: 600; color: #059669;">${allocs.filter(a => !a.timestamp_dealloc).length}</div>
7016 </div>
7017 <div>
7018 <div style="color: var(--text-secondary);">Freed</div>
7019 <div style="font-weight: 600; color: #2563eb;">${allocs.filter(a => a.timestamp_dealloc && !a.is_leaked).length}</div>
7020 </div>
7021 <div>
7022 <div style="color: var(--text-secondary);">Leaked</div>
7023 <div style="font-weight: 600; color: #dc2626;">${allocs.filter(a => a.is_leaked).length}</div>
7024 </div>
7025 </div>
7026 </div>
7027 `;
7028
7029 allocs.forEach((alloc, index) => {
7030 const startTime = alloc.timestamp_alloc || 0;
7031 const endTime = alloc.timestamp_dealloc || maxTime;
7032 const startPercent = ((startTime - minTime) / timeRange) * 100;
7033 const widthPercent = ((endTime - startTime) / timeRange) * 100;
7034 const isActive = !alloc.timestamp_dealloc;
7035 const isLeaked = alloc.is_leaked;
7036
7037 let barColor = 'linear-gradient(to right, #2563eb, #60a5fa)'; // freed
7038 if (isActive) barColor = 'linear-gradient(to right, #059669, #34d399)'; // active
7039 if (isLeaked) barColor = 'linear-gradient(to right, #dc2626, #f87171)'; // leaked
7040
7041 html += `
7042 <div style="margin-bottom: 6px;">
7043 <div style="display: flex; justify-content: space-between; margin-bottom: 3px; font-size: 0.75rem;">
7044 <span style="font-weight: 600;">${alloc.var_name || `var_${index}`}</span>
7045 <div style="display: flex; gap: 8px;">
7046 <span style="color: var(--text-secondary);">${formatTypeName(alloc.type_name || 'Unknown')}</span>
7047 <span style="color: var(--text-secondary);">${formatBytes(alloc.size || 0)}</span>
7048 </div>
7049 </div>
7050 <div style="position: relative; background: var(--bg-secondary); height: 6px; border-radius: 3px;">
7051 <div style="
7052 position: absolute;
7053 left: ${startPercent}%;
7054 width: ${widthPercent}%;
7055 height: 100%;
7056 background: ${barColor};
7057 border-radius: 3px;
7058 ${isActive ? 'animation: pulse 2s infinite;' : ''}
7059 " title="Lifetime: ${endTime - startTime}ms | Status: ${isLeaked ? 'Leaked' : isActive ? 'Active' : 'Freed'}"></div>
7060 </div>
7061 </div>
7062 `;
7063 });
7064
7065 html += '</div>';
7066
7067 // Add CSS for pulse animation
7068 html += `
7069 <style>
7070 @keyframes pulse {
7071 0%, 100% { opacity: 1; }
7072 50% { opacity: 0.7; }
7073 }
7074 </style>
7075 `;
7076
7077 container.innerHTML = html;
7078}
7079
7080function setupLifecycleVisualization() {
7081 const container = document.getElementById('lifetimeVisualization');
7082 if (!container) return;
7083
7084 const data = window.analysisData || {};
7085 const allocs = (data.memory_analysis && data.memory_analysis.allocations) || [];
7086
7087 if (allocs.length === 0) {
7088 container.innerHTML = '<div style="padding: 20px; text-align: center; color: var(--text-secondary);">No lifecycle data available</div>';
7089 return;
7090 }
7091
7092 // Create timeline visualization
7093 const maxTime = Math.max(...allocs.map(a => a.timestamp_dealloc || a.timestamp_alloc || 0));
7094 const minTime = Math.min(...allocs.map(a => a.timestamp_alloc || 0));
7095 const timeRange = maxTime - minTime || 1;
7096
7097 let html = '<div style="padding: 16px; max-height: 300px; overflow-y: auto;">';
7098
7099 allocs.slice(0, 20).forEach((alloc, index) => {
7100 const startTime = alloc.timestamp_alloc || 0;
7101 const endTime = alloc.timestamp_dealloc || maxTime;
7102 const startPercent = ((startTime - minTime) / timeRange) * 100;
7103 const widthPercent = ((endTime - startTime) / timeRange) * 100;
7104 const isActive = !alloc.timestamp_dealloc;
7105
7106 html += `
7107 <div style="margin-bottom: 8px;">
7108 <div style="display: flex; justify-content: space-between; margin-bottom: 4px; font-size: 0.8rem;">
7109 <span style="font-weight: 600;">${alloc.var_name || `var_${index}`}</span>
7110 <span style="color: var(--text-secondary);">${formatBytes(alloc.size || 0)}</span>
7111 </div>
7112 <div style="position: relative; background: var(--bg-secondary); height: 8px; border-radius: 4px;">
7113 <div style="
7114 position: absolute;
7115 left: ${startPercent}%;
7116 width: ${widthPercent}%;
7117 height: 100%;
7118 background: ${isActive ? 'linear-gradient(to right, #059669, #34d399)' : 'linear-gradient(to right, #2563eb, #60a5fa)'};
7119 border-radius: 4px;
7120 ${isActive ? 'animation: pulse 2s infinite;' : ''}
7121 " title="Lifetime: ${endTime - startTime}ms"></div>
7122 </div>
7123 </div>
7124 `;
7125 });
7126
7127 html += '</div>';
7128
7129 // Add CSS for pulse animation
7130 html += `
7131 <style>
7132 @keyframes pulse {
7133 0%, 100% { opacity: 1; }
7134 50% { opacity: 0.7; }
7135 }
7136 </style>
7137 `;
7138
7139 container.innerHTML = html;
7140}
7141
7142function initFFIVisualization() {
7143 // Additional FFI initialization if needed
7144 renderFFI();
7145}
7146
7147// Complete JSON Data Explorer
7148function initCompleteJSONExplorer() {
7149 const container = document.getElementById('jsonDataExplorer');
7150 const expandBtn = document.getElementById('expand-all-json');
7151 const collapseBtn = document.getElementById('collapse-all-json');
7152
7153 if (!container) return;
7154
7155 const data = window.analysisData || {};
7156
7157 if (Object.keys(data).length === 0) {
7158 container.innerHTML = '<div style="text-align: center; color: var(--text-secondary); padding: 40px;">No JSON data available</div>';
7159 return;
7160 }
7161
7162 // Generate comprehensive JSON explorer
7163 let html = '<div class="json-explorer">';
7164
7165 // Add data summary
7166 html += `
7167 <div style="background: var(--bg-primary); border: 1px solid var(--border-light); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
7168 <h3 style="margin: 0 0 12px 0; color: var(--text-primary);">Data Summary</h3>
7169 <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px;">
7170 ${Object.keys(data).map(key => {
7171 const value = data[key];
7172 let itemCount = 'N/A';
7173 let dataType = typeof value;
7174
7175 if (Array.isArray(value)) {
7176 itemCount = value.length + ' items';
7177 dataType = 'Array';
7178 } else if (value && typeof value === 'object') {
7179 itemCount = Object.keys(value).length + ' properties';
7180 dataType = 'Object';
7181 }
7182
7183 return `
7184 <div style="background: var(--bg-secondary); padding: 12px; border-radius: 6px;">
7185 <div style="font-weight: 600; color: var(--text-primary);">${key}</div>
7186 <div style="font-size: 0.8rem; color: var(--text-secondary);">${dataType}</div>
7187 <div style="font-size: 0.8rem; color: var(--text-secondary);">${itemCount}</div>
7188 </div>
7189 `;
7190 }).join('')}
7191 </div>
7192 </div>
7193 `;
7194
7195 // Generate expandable JSON tree for each top-level key
7196 Object.keys(data).forEach((key, index) => {
7197 const value = data[key];
7198 html += createJSONSection(key, value, index);
7199 });
7200
7201 html += '</div>';
7202 container.innerHTML = html;
7203
7204 // Setup expand/collapse functionality
7205 if (expandBtn) {
7206 expandBtn.addEventListener('click', () => {
7207 const details = container.querySelectorAll('details');
7208 details.forEach(detail => detail.open = true);
7209 });
7210 }
7211
7212 if (collapseBtn) {
7213 collapseBtn.addEventListener('click', () => {
7214 const details = container.querySelectorAll('details');
7215 details.forEach(detail => detail.open = false);
7216 });
7217 }
7218}
7219
7220function createJSONSection(key, value, index) {
7221 const isOpen = index < 3; // Open first 3 sections by default
7222
7223 let html = `
7224 <details class="json-section" ${isOpen ? 'open' : ''} style="
7225 border: 1px solid var(--border-light);
7226 border-radius: 8px;
7227 margin-bottom: 12px;
7228 background: var(--bg-primary);
7229 ">
7230 <summary style="
7231 cursor: pointer;
7232 padding: 12px 16px;
7233 font-weight: 600;
7234 color: var(--text-primary);
7235 background: var(--bg-secondary);
7236 border-radius: 8px 8px 0 0;
7237 user-select: none;
7238 ">
7239 <i class="fa fa-chevron-right" style="margin-right: 8px; transition: transform 0.2s;"></i>
7240 ${key}
7241 <span style="font-weight: normal; color: var(--text-secondary); margin-left: 8px;">
7242 ${getDataTypeInfo(value)}
7243 </span>
7244 </summary>
7245 <div style="padding: 16px;">
7246 `;
7247
7248 if (Array.isArray(value)) {
7249 html += createArrayView(value, key);
7250 } else if (value && typeof value === 'object') {
7251 html += createObjectView(value, key);
7252 } else {
7253 html += `<pre style="margin: 0; color: var(--text-primary); font-size: 0.9rem;">${JSON.stringify(value, null, 2)}</pre>`;
7254 }
7255
7256 html += '</div></details>';
7257 return html;
7258}
7259
7260function createArrayView(array, parentKey) {
7261 if (array.length === 0) {
7262 return '<div style="color: var(--text-secondary); font-style: italic;">Empty array</div>';
7263 }
7264
7265 let html = `
7266 <div style="margin-bottom: 12px;">
7267 <strong>Array with ${array.length} items</strong>
7268 ${array.length > 10 ? `<span style="color: var(--text-secondary);"> (showing first 10)</span>` : ''}
7269 </div>
7270 `;
7271
7272 // Show first 10 items
7273 const itemsToShow = array.slice(0, 10);
7274
7275 itemsToShow.forEach((item, index) => {
7276 html += `
7277 <details style="margin-bottom: 8px; border: 1px solid var(--border-light); border-radius: 6px;">
7278 <summary style="cursor: pointer; padding: 8px 12px; background: var(--bg-secondary); font-size: 0.9rem;">
7279 [${index}] ${getDataTypeInfo(item)}
7280 </summary>
7281 <div style="padding: 12px;">
7282 <pre style="margin: 0; font-size: 0.8rem; color: var(--text-primary); max-height: 300px; overflow: auto;">${JSON.stringify(item, null, 2)}</pre>
7283 </div>
7284 </details>
7285 `;
7286 });
7287
7288 if (array.length > 10) {
7289 html += `<div style="color: var(--text-secondary); font-style: italic; margin-top: 12px;">... and ${array.length - 10} more items</div>`;
7290 }
7291
7292 return html;
7293}
7294
7295function createObjectView(obj, parentKey) {
7296 const keys = Object.keys(obj);
7297
7298 if (keys.length === 0) {
7299 return '<div style="color: var(--text-secondary); font-style: italic;">Empty object</div>';
7300 }
7301
7302 let html = `
7303 <div style="margin-bottom: 12px;">
7304 <strong>Object with ${keys.length} properties</strong>
7305 </div>
7306 `;
7307
7308 keys.forEach(key => {
7309 const value = obj[key];
7310 html += `
7311 <details style="margin-bottom: 8px; border: 1px solid var(--border-light); border-radius: 6px;">
7312 <summary style="cursor: pointer; padding: 8px 12px; background: var(--bg-secondary); font-size: 0.9rem;">
7313 <code style="background: var(--bg-primary); padding: 2px 6px; border-radius: 3px;">${key}</code>
7314 <span style="margin-left: 8px; color: var(--text-secondary);">${getDataTypeInfo(value)}</span>
7315 </summary>
7316 <div style="padding: 12px;">
7317 <pre style="margin: 0; font-size: 0.8rem; color: var(--text-primary); max-height: 300px; overflow: auto;">${JSON.stringify(value, null, 2)}</pre>
7318 </div>
7319 </details>
7320 `;
7321 });
7322
7323 return html;
7324}
7325
7326function getDataTypeInfo(value) {
7327 if (value === null) return 'null';
7328 if (value === undefined) return 'undefined';
7329 if (Array.isArray(value)) return `Array[${value.length}]`;
7330 if (typeof value === 'object') return `Object{${Object.keys(value).length}}`;
7331 if (typeof value === 'string') return `String(${value.length})`;
7332 if (typeof value === 'number') return `Number(${value})`;
7333 if (typeof value === 'boolean') return `Boolean(${value})`;
7334 return typeof value;
7335}
7336
7337// Enhanced FFI Visualization with rich lifecycle data
7338function initEnhancedFFIVisualization() {
7339 const container = document.getElementById('ffiVisualization');
7340 if (!container) return;
7341
7342 const data = window.analysisData || {};
7343 const allocs = data.unsafe_ffi?.allocations || data.memory_analysis?.allocations || data.allocations || [];
7344
7345 if (allocs.length === 0) {
7346 container.innerHTML = `<div style="background: var(--bg-secondary); border-radius:8px; padding:16px; text-align:center; color: var(--text-secondary);"><i class="fa fa-exclamation-triangle" style="font-size:24px; margin-bottom:8px; color: var(--primary-red);"></i><div>Critical: No FFI allocation data found!</div></div>`;
7347 return;
7348 }
7349
7350 // Rich data analysis
7351 const ffiTracked = allocs.filter(a => a.ffi_tracked).length;
7352 const withViolations = allocs.filter(a => a.safety_violations && a.safety_violations.length > 0).length;
7353 const withClones = allocs.filter(a => a.clone_info?.clone_count > 0).length;
7354 const leaked = allocs.filter(a => a.is_leaked).length;
7355 const totalBorrows = allocs.reduce((sum, a) => sum + (a.borrow_info?.immutable_borrows || 0) + (a.borrow_info?.mutable_borrows || 0), 0);
7356 const totalMemory = allocs.reduce((sum, a) => sum + (a.size || 0), 0);
7357 const avgLifetime = allocs.filter(a => a.lifetime_ms).reduce((sum, a) => sum + a.lifetime_ms, 0) / allocs.filter(a => a.lifetime_ms).length || 0;
7358
7359 // Get time range for lifecycle visualization
7360 const timestamps = allocs.map(a => a.timestamp_alloc).filter(t => t).sort((a, b) => a - b);
7361 const minTime = timestamps[0] || 0;
7362 const maxTime = timestamps[timestamps.length - 1] || minTime;
7363 const timeRange = maxTime - minTime || 1;
7364
7365 // Content that will be contained within the section's background
7366 container.innerHTML = `
7367 <!-- KPI Cards -->
7368 <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 20px;">
7369 <div style="background: var(--bg-primary); padding: 14px; border-radius: 8px; text-align: center; border-left: 4px solid var(--primary-blue);">
7370 <div style="font-size: 1.6rem; font-weight: 700; color: var(--primary-blue);">${allocs.length}</div>
7371 <div style="font-size: 0.75rem; color: var(--text-secondary);">FFI Allocations</div>
7372 </div>
7373 <div style="background: var(--bg-primary); padding: 14px; border-radius: 8px; text-align: center; border-left: 4px solid var(--primary-green);">
7374 <div style="font-size: 1.6rem; font-weight: 700; color: var(--primary-green);">${totalBorrows}</div>
7375 <div style="font-size: 0.75rem; color: var(--text-secondary);">Total Borrows</div>
7376 </div>
7377 <div style="background: var(--bg-primary); padding: 14px; border-radius: 8px; text-align: center; border-left: 4px solid var(--primary-orange);">
7378 <div style="font-size: 1.6rem; font-weight: 700; color: var(--primary-orange);">${withClones}</div>
7379 <div style="font-size: 0.75rem; color: var(--text-secondary);">With Clones</div>
7380 </div>
7381 <div style="background: var(--bg-primary); padding: 14px; border-radius: 8px; text-align: center; border-left: 4px solid ${leaked > 0 ? 'var(--primary-red)' : 'var(--primary-green)'};">
7382 <div style="font-size: 1.6rem; font-weight: 700; color: ${leaked > 0 ? 'var(--primary-red)' : 'var(--primary-green)'};">${leaked}</div>
7383 <div style="font-size: 0.75rem; color: var(--text-secondary);">Memory Leaks</div>
7384 </div>
7385 <div style="background: var(--bg-primary); padding: 14px; border-radius: 8px; text-align: center; border-left: 4px solid var(--primary-red);">
7386 <div style="font-size: 1.6rem; font-weight: 700; color: ${withViolations > 0 ? 'var(--primary-red)' : 'var(--primary-green)'};">${withViolations}</div>
7387 <div style="font-size: 0.75rem; color: var(--text-secondary);">Safety Violations</div>
7388 </div>
7389 </div>
7390
7391 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;">
7392 <div style="background: var(--bg-secondary); padding: 16px; border-radius: 8px;">
7393 <h3 style="margin: 0 0 12px 0; color: var(--text-primary); display: flex; align-items: center;"><i class="fa fa-clock-o" style="margin-right: 8px;"></i>Lifecycle Metrics</h3>
7394 <div style="margin-bottom: 10px;">
7395 <span style="color: var(--text-secondary); font-size: 0.9rem;">Avg Lifetime:</span>
7396 <span style="color: var(--text-primary); font-weight: 600; margin-left: 8px;">${avgLifetime.toFixed(2)}ms</span>
7397 </div>
7398 <div style="margin-bottom: 10px;">
7399 <span style="color: var(--text-secondary); font-size: 0.9rem;">Total Memory:</span>
7400 <span style="color: var(--text-primary); font-weight: 600; margin-left: 8px;">${formatBytes(totalMemory)}</span>
7401 </div>
7402 <div style="margin-bottom: 10px;">
7403 <span style="color: var(--text-secondary); font-size: 0.9rem;">Time Span:</span>
7404 <span style="color: var(--text-primary); font-weight: 600; margin-left: 8px;">${(timeRange / 1e6).toFixed(2)}ms</span>
7405 </div>
7406 </div>
7407
7408 <div style="background: var(--bg-secondary); padding: 16px; border-radius: 8px;">
7409 <h3 style="margin: 0 0 12px 0; color: var(--text-primary); display: flex; align-items: center;"><i class="fa fa-share-alt" style="margin-right: 8px;"></i>Borrow & Clone Activity</h3>
7410 <div style="margin-bottom: 8px;">
7411 <span style="color: var(--text-secondary); font-size: 0.9rem;">Immutable Borrows:</span>
7412 <span style="color: var(--primary-blue); font-weight: 600; margin-left: 8px;">${allocs.reduce((s, a) => s + (a.borrow_info?.immutable_borrows || 0), 0)}</span>
7413 </div>
7414 <div style="margin-bottom: 8px;">
7415 <span style="color: var(--text-secondary); font-size: 0.9rem;">Mutable Borrows:</span>
7416 <span style="color: var(--primary-orange); font-weight: 600; margin-left: 8px;">${allocs.reduce((s, a) => s + (a.borrow_info?.mutable_borrows || 0), 0)}</span>
7417 </div>
7418 <div>
7419 <span style="color: var(--text-secondary); font-size: 0.9rem;">Clone Operations:</span>
7420 <span style="color: var(--primary-green); font-weight: 600; margin-left: 8px;">${allocs.reduce((s, a) => s + (a.clone_info?.clone_count || 0), 0)}</span>
7421 </div>
7422 </div>
7423 </div>
7424
7425 <!-- FFI Data Flow Visualization (data stream) -->
7426 <div style="margin-top: 20px; background: var(--bg-secondary); padding: 16px; border-radius: 8px;">
7427 <h3 style="margin: 0 0 12px 0; color: var(--text-primary); display: flex; align-items: center;">
7428 <i class="fa fa-exchange" style="margin-right: 8px;"></i>FFI Data Flow
7429 <button id="ffi-flow-toggle" style="margin-left: auto; background: var(--primary-green); color: white; border: none; padding: 4px 8px; border-radius: 4px; font-size: 11px; cursor: pointer;">
7430 <i class="fa fa-play"></i> Animate
7431 </button>
7432 </h3>
7433 <div id="ffi-flow-container" style="height: 200px; position: relative; border: 1px solid var(--border-light); border-radius: 6px; overflow: hidden; background: linear-gradient(135deg, #1e293b 0%, #0f172a 50%, #1e293b 100%);">
7434 ${createFFIDataFlow(allocs)}
7435 </div>
7436 <div style="margin-top: 8px; font-size: 11px; color: var(--text-secondary); text-align: center;">
7437 ๐ฆ Rust โ C Data Flow โข ${ffiTracked} FFI-tracked allocations โข Click nodes for details
7438 </div>
7439 </div>
7440
7441 <!-- Interactive Allocation Analysis (Two Column Layout) -->
7442 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;">
7443 <!-- Timeline Column -->
7444 <div style="background: var(--bg-secondary); padding: 16px; border-radius: 8px;">
7445 <h3 style="margin: 0 0 12px 0; color: var(--text-primary); display: flex; align-items: center;">
7446 <i class="fa fa-clock-o" style="margin-right: 8px;"></i>Allocation Timeline
7447 <button id="ffi-timeline-toggle" style="margin-left: auto; background: var(--primary-blue); color: white; border: none; padding: 4px 8px; border-radius: 4px; font-size: 11px; cursor: pointer;">
7448 <i class="fa fa-expand"></i> Details
7449 </button>
7450 </h3>
7451 <div id="ffi-timeline-container" style="height: 160px; position: relative; border: 1px solid var(--border-light); border-radius: 6px; overflow: hidden; background: linear-gradient(45deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);">
7452 ${createAllocationTimeline(allocs, minTime, timeRange)}
7453 </div>
7454 <div style="margin-top: 8px; font-size: 11px; color: var(--text-secondary); text-align: center;">
7455 Timeline spans ${(timeRange / 1e6).toFixed(1)}ms โข Click dots for details
7456 </div>
7457 </div>
7458
7459 <!-- Details Column -->
7460 <div style="background: var(--bg-secondary); padding: 16px; border-radius: 8px;">
7461 <h3 style="margin: 0 0 12px 0; color: var(--text-primary); display: flex; align-items: center;">
7462 <i class="fa fa-list" style="margin-right: 8px;"></i>Allocation Details
7463 <select id="ffi-filter" style="margin-left: auto; background: var(--bg-primary); border: 1px solid var(--border-light); border-radius: 4px; padding: 4px 8px; font-size: 11px; color: var(--text-primary);">
7464 <option value="all">All (${allocs.length})</option>
7465 <option value="leaked">Leaked (${allocs.filter(a => a.is_leaked).length})</option>
7466 <option value="cloned">With Clones (${withClones})</option>
7467 <option value="borrowed">With Borrows (${allocs.filter(a => ((a.borrow_info?.immutable_borrows || 0) + (a.borrow_info?.mutable_borrows || 0)) > 0).length})</option>
7468 </select>
7469 </h3>
7470 <div id="ffi-allocation-table" style="max-height: 160px; overflow-y: auto; border: 1px solid var(--border-light); border-radius: 6px;">
7471 ${createAllocationTable(allocs)}
7472 </div>
7473 <div style="margin-top: 8px; font-size: 11px; color: var(--text-secondary); text-align: center;">
7474 Click rows for detailed view โข Use filter to narrow results
7475 </div>
7476 </div>
7477 </div>
7478 `;
7479
7480 // Add interactivity
7481 setupFFIInteractivity(allocs, minTime, timeRange);
7482 setupFFIFlowInteractivity(allocs);
7483}
7484
7485// Create super cool FFI data flow visualization
7486function createFFIDataFlow(allocs) {
7487 const ffiAllocs = allocs.filter(a => a.ffi_tracked);
7488 const rustAllocs = allocs.filter(a => !a.ffi_tracked);
7489
7490 // Create dynamic SVG-based flow visualization
7491 let html = `
7492 <svg width="100%" height="100%" viewBox="0 0 800 200" style="position: absolute; top: 0; left: 0;">
7493 <!-- Background grid pattern -->
7494 <defs>
7495 <pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
7496 <path d="M 20 0 L 0 0 0 20" fill="none" stroke='#334155' stroke-width="0.5" opacity="0.3"/>
7497 </pattern>
7498 <filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
7499 <feGaussianBlur stdDeviation="3" result="coloredBlur"/>
7500 <feMerge>
7501 <feMergeNode in="coloredBlur"/>
7502 <feMergeNode in="SourceGraphic"/>
7503 </feMerge>
7504 </filter>
7505 <linearGradient id="rustGradient" x1="0%" y1="0%" x2="100%" y2="0%">
7506 <stop offset="0%" style="stop-color:#f97316;stop-opacity:1" />
7507 <stop offset="100%" style="stop-color:#ea580c;stop-opacity:1" />
7508 </linearGradient>
7509 <linearGradient id="cGradient" x1="0%" y1="0%" x2="100%" y2="0%">
7510 <stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
7511 <stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
7512 </linearGradient>
7513 </defs>
7514
7515 <rect width="100%" height="100%" fill="url(#grid)"/>
7516
7517 <!-- Rust Side (Left) -->
7518 <g id="rust-side">
7519 <rect x="50" y="40" width="200" height="120" rx="15" fill="url(#rustGradient)" opacity="0.2" stroke='#f97316' stroke-width="2"/>
7520 <text x="150" y="30" text-anchor="middle" font-size="16" font-weight="bold" fill='#f97316' filter="url(#glow)">
7521 ๐ฆ RUST
7522 </text>
7523 <text x="150" y="190" text-anchor="middle" font-size="12" fill='#f97316'>
7524 ${rustAllocs.length} allocations
7525 </text>
7526
7527 <!-- Rust memory nodes -->
7528 ${rustAllocs.slice(0, 8).map((alloc, i) => {
7529 const x = 80 + (i % 4) * 35;
7530 const y = 60 + Math.floor(i / 4) * 35;
7531 const size = Math.max(8, Math.min(16, Math.sqrt((alloc.size || 0) / 1000)));
7532 return `
7533 <circle cx="${x}" cy="${y}" r="${size}" fill='#f97316' opacity="0.8"
7534 stroke='#fff' stroke-width="2" class='rust-node '
7535 data-ptr="${alloc.ptr}" data-size="${alloc.size || 0}"
7536 style="cursor: pointer; transition: all 0.3s;">
7537 <animate attributeName="opacity" values="0.8;1;0.8" dur="2s" repeatCount="indefinite"/>
7538 </circle>
7539 `;
7540 }).join('')}
7541 </g>
7542
7543 <!-- C/FFI Side (Right) -->
7544 <g id="c-side">
7545 <rect x="550" y="40" width="200" height="120" rx="15" fill="url(#cGradient)" opacity="0.2" stroke='#3b82f6' stroke-width="2"/>
7546 <text x="650" y="30" text-anchor="middle" font-size="16" font-weight="bold" fill='#3b82f6' filter="url(#glow)">
7547 โ๏ธ C/FFI
7548 </text>
7549 <text x="650" y="190" text-anchor="middle" font-size="12" fill='#3b82f6'>
7550 ${ffiAllocs.length} FFI allocations
7551 </text>
7552
7553 <!-- FFI memory nodes -->
7554 ${ffiAllocs.slice(0, 8).map((alloc, i) => {
7555 const x = 580 + (i % 4) * 35;
7556 const y = 60 + Math.floor(i / 4) * 35;
7557 const size = Math.max(8, Math.min(16, Math.sqrt((alloc.size || 0) / 1000)));
7558 return `
7559 <circle cx="${x}" cy="${y}" r="${size}" fill='#3b82f6' opacity="0.9"
7560 stroke='#fff' stroke-width="2" class='ffi-node '
7561 data-ptr="${alloc.ptr}" data-size="${alloc.size || 0}"
7562 style="cursor: pointer;">
7563 </circle>
7564 `;
7565 }).join('')}
7566 </g>
7567
7568 <!-- Data Flow Arrows -->
7569 <g id="data-flows">
7570 <!-- Rust to C flow -->
7571 <path d="M 250 80 Q 400 60 550 80" stroke='#10b981' stroke-width="3" fill="none" opacity="0.7">
7572 <animate attributeName="stroke-dasharray " values="0,1000;1000,0" dur="3s" repeatCount="indefinite "/>
7573 </path>
7574 <text x="400" y="55" text-anchor="middle" font-size="10" fill='#10b981' font-weight="bold">
7575 Rust โ C
7576 </text>
7577
7578 <!-- C to Rust flow -->
7579 <path d="M 550 120 Q 400 140 250 120" stroke='#ec4899' stroke-width="3" fill="none" opacity="0.7">
7580 <animate attributeName="stroke-dasharray" values="0,1000;1000,0" dur="3.5s" repeatCount="indefinite"/>
7581 </path>
7582 <text x="400" y="155" text-anchor="middle" font-size="10" fill='#ec4899' font-weight="bold">
7583 C โ Rust
7584 </text>
7585
7586 <!-- Central processing hub -->
7587 <circle cx="400" cy="100" r="20" fill='#8b5cf6' opacity="0.3" stroke='#8b5cf6' stroke-width="2">
7588 <animate attributeName="r" values="20;25;20" dur="2s" repeatCount="indefinite"/>
7589 </circle>
7590 <text x="400" y="105" text-anchor="middle" font-size="10" fill='#8b5cf6' font-weight="bold">FFI</text>
7591 </g>
7592
7593 <!-- Memory flow particles -->
7594 <g id="flow-particles">
7595 ${Array.from({length: 6}, (_, i) => `
7596 <circle r="3" fill='#fbbf24' opacity="0.8">
7597 <animateMotion dur="${3 + i * 0.5}s" repeatCount="indefinite">
7598 <path d="M 250 80 Q 400 60 550 80"/>
7599 </animateMotion>
7600 <animate attributeName="opacity" values="0;1;0" dur="1s" repeatCount="indefinite"/>
7601 </circle>
7602 `).join('')}
7603
7604 ${Array.from({length: 4}, (_, i) => `
7605 <circle r="3" fill='#06d6a0' opacity="0.8">
7606 <animateMotion dur="${3.5 + i * 0.7}s" repeatCount="indefinite">
7607 <path d="M 550 120 Q 400 140 250 120"/>
7608 </animateMotion>
7609 <animate attributeName="opacity" values="0;1;0" dur="1s" repeatCount="indefinite"/>
7610 </circle>
7611 `).join('')}
7612 </g>
7613 </svg>
7614 `;
7615
7616 return html;
7617}
7618
7619// Create allocation timeline visualization
7620function createAllocationTimeline(allocs, minTime, timeRange) {
7621 const sorted = allocs.slice().sort((a, b) => (a.timestamp_alloc || 0) - (b.timestamp_alloc || 0));
7622 let html = '<div style="position: relative; height: 100%; background: linear-gradient(90deg, rgba(59,130,246,0.1) 0%, rgba(16,185,129,0.1) 50%, rgba(239,68,68,0.1) 100%);">';
7623
7624 // Add time axis with better spacing
7625 html += '<div style="position: absolute; bottom: 25px; left: 0; right: 0; height: 1px; background: var(--border-light);"></div>';
7626 html += '<div style="position: absolute; bottom: 18px; left: 12px; font-size: 10px; color: var(--text-secondary); background: var(--bg-primary); padding: 2px 4px; border-radius: 3px;">0ms</div>';
7627 html += '<div style="position: absolute; bottom: 18px; right: 12px; font-size: 10px; color: var(--text-secondary); background: var(--bg-primary); padding: 2px 4px; border-radius: 3px;">' + (timeRange / 1e6).toFixed(1) + 'ms</div>';
7628
7629 // Add middle time markers for better readability
7630 const midTime = (timeRange / 1e6) / 2;
7631 html += '<div style="position: absolute; bottom: 18px; left: 50%; transform: translateX(-50%); font-size: 10px; color: var(--text-secondary); background: var(--bg-primary); padding: 2px 4px; border-radius: 3px;">' + midTime.toFixed(1) + 'ms</div>';
7632
7633 // Group nearby allocations to prevent overlap
7634 const groups = [];
7635 const threshold = timeRange * 0.05; // 5% of time range
7636
7637 sorted.forEach(alloc => {
7638 const found = groups.find(g => Math.abs(g.avgTime - alloc.timestamp_alloc) < threshold);
7639 if (found) {
7640 found.allocs.push(alloc);
7641 found.avgTime = found.allocs.reduce((sum, a) => sum + a.timestamp_alloc, 0) / found.allocs.length;
7642 } else {
7643 groups.push({ allocs: [alloc], avgTime: alloc.timestamp_alloc });
7644 }
7645 });
7646
7647 groups.forEach((group, groupIndex) => {
7648 const relativeTime = (group.avgTime - minTime) / timeRange;
7649 const left = Math.max(2, Math.min(93, relativeTime * 90 + 5));
7650
7651 if (group.allocs.length === 1) {
7652 const alloc = group.allocs[0];
7653 const size = Math.max(10, Math.min(20, Math.sqrt((alloc.size || 0) / 50)));
7654 const isLeaked = alloc.is_leaked;
7655 const hasClones = alloc.clone_info?.clone_count > 0;
7656 const color = isLeaked ? '#dc2626' : hasClones ? '#ea580c' : '#2563eb';
7657
7658 html += `<div style="position: absolute; left: ${left}%; top: 60%; transform: translateY(-50%);
7659 width: ${size}px; height: ${size}px; background: ${color}; border-radius: 50%;
7660 border: 2px solid white; cursor: pointer; z-index: 100;
7661 box-shadow: 0 2px 4px rgba(0,0,0,0.2); transition: transform 0.2s;"
7662 onmouseover="this.style.transform='translateY(-50%) scale(1.2)'"
7663 onmouseout="this.style.transform='translateY(-50%) scale(1)'"
7664 title="${alloc.var_name || 'unnamed'} | ${formatBytes(alloc.size || 0)} | ${new Date(alloc.timestamp_alloc / 1e6).toLocaleTimeString()}"
7665 onclick="showAllocationDetail('${alloc.ptr}')"></div>`;
7666 } else {
7667 // Multiple allocations - create a cluster
7668 const totalSize = group.allocs.reduce((sum, a) => sum + (a.size || 0), 0);
7669 const hasLeaks = group.allocs.some(a => a.is_leaked);
7670 const hasClones = group.allocs.some(a => a.clone_info?.clone_count > 0);
7671 const clusterSize = Math.max(16, Math.min(28, Math.sqrt(totalSize / 100)));
7672 const color = hasLeaks ? '#dc2626' : hasClones ? '#ea580c' : '#2563eb';
7673
7674 html += `<div style="position: absolute; left: ${left}%; top: 60%; transform: translateY(-50%);
7675 width: ${clusterSize}px; height: ${clusterSize}px; background: ${color}; border-radius: 50%;
7676 border: 3px solid white; cursor: pointer; z-index: 100;
7677 box-shadow: 0 2px 8px rgba(0,0,0,0.3); display: flex; align-items: center; justify-content: center;
7678 color: white; font-size: 9px; font-weight: bold; transition: transform 0.2s;"
7679 onmouseover="this.style.transform='translateY(-50%) scale(1.2)'"
7680 onmouseout="this.style.transform='translateY(-50%) scale(1)'"
7681 title="${group.allocs.length} allocations | Total: ${formatBytes(totalSize)} | Avg time: ${new Date(group.avgTime / 1e6).toLocaleTimeString()}"
7682 onclick="showClusterDetail(${JSON.stringify(group.allocs.map(a => a.ptr)).replace(/"/g, '"')})">${group.allocs.length}</div>`;
7683 }
7684 });
7685
7686 html += '</div>';
7687 return html;
7688}
7689
7690// Create allocation details table
7691function createAllocationTable(allocs) {
7692 const sorted = allocs.slice().sort((a, b) => (b.timestamp_alloc || 0) - (a.timestamp_alloc || 0));
7693
7694 let html = `
7695 <div style="border: 1px solid var(--border-light); border-radius: 6px; overflow: hidden;">
7696 <table style="width: 100%; border-collapse: collapse; font-size: 12px;">
7697 <thead style="background: var(--bg-primary); border-bottom: 1px solid var(--border-light);">
7698 <tr>
7699 <th style="padding: 8px; text-align: left; color: var(--text-primary);">Variable</th>
7700 <th style="padding: 8px; text-align: left; color: var(--text-primary);">Type</th>
7701 <th style="padding: 8px; text-align: right; color: var(--text-primary);">Size</th>
7702 <th style="padding: 8px; text-align: center; color: var(--text-primary);">Borrows</th>
7703 <th style="padding: 8px; text-align: center; color: var(--text-primary);">Clones</th>
7704 <th style="padding: 8px; text-align: center; color: var(--text-primary);">Status</th>
7705 <th style="padding: 8px; text-align: right; color: var(--text-primary);">Lifetime</th>
7706 </tr>
7707 </thead>
7708 <tbody>`;
7709
7710 sorted.forEach((alloc, i) => {
7711 const typeName = (alloc.type_name || 'Unknown').replace(/alloc::|std::/g, '').replace(/collections::\w+::/g, '');
7712 const shortType = typeName.length > 20 ? typeName.substring(0, 17) + '...' : typeName;
7713 const totalBorrows = (alloc.borrow_info?.immutable_borrows || 0) + (alloc.borrow_info?.mutable_borrows || 0);
7714 const cloneCount = alloc.clone_info?.clone_count || 0;
7715 const isLeaked = alloc.is_leaked;
7716 const lifetime = alloc.lifetime_ms || 'Active';
7717
7718 const statusColor = isLeaked ? 'var(--primary-red)' : 'var(--primary-green)';
7719 const statusText = isLeaked ? 'LEAKED' : 'OK';
7720
7721 html += `
7722 <tr style="border-bottom: 1px solid var(--border-light); cursor: pointer;"
7723 onclick="showAllocationDetail('${alloc.ptr}')"
7724 onmouseover="this.style.background='var(--bg-secondary)'"
7725 onmouseout="this.style.background='transparent'">
7726 <td style="padding: 8px; color: var(--text-primary); font-weight: 500;">${alloc.var_name || 'unnamed'}</td>
7727 <td style="padding: 8px; color: var(--text-secondary);" title="${alloc.type_name}">${shortType}</td>
7728 <td style="padding: 8px; text-align: right; color: var(--text-primary); font-weight: 600;">${formatBytes(alloc.size || 0)}</td>
7729 <td style="padding: 8px; text-align: center; color: var(--primary-blue);">${totalBorrows}</td>
7730 <td style="padding: 8px; text-align: center; color: var(--primary-orange);">${cloneCount}</td>
7731 <td style="padding: 8px; text-align: center;">
7732 <span style="color: ${statusColor}; font-weight: 600; font-size: 11px;">${statusText}</span>
7733 </td>
7734 <td style="padding: 8px; text-align: right; color: var(--text-secondary); font-size: 11px;">
7735 ${typeof lifetime === 'number' ? lifetime.toFixed(2) + 'ms' : lifetime}
7736 </td>
7737 </tr>`;
7738 });
7739
7740 html += '</tbody></table></div>';
7741 return html;
7742}
7743
7744// Setup FFI interactivity
7745function setupFFIInteractivity(allocs, minTime, timeRange) {
7746 // Timeline toggle
7747 const toggleBtn = document.getElementById('ffi-timeline-toggle');
7748 if (toggleBtn) {
7749 let expanded = false;
7750 toggleBtn.onclick = () => {
7751 const container = document.getElementById('ffi-timeline-container');
7752 if (!container) return;
7753
7754 expanded = !expanded;
7755 container.style.height = expanded ? '200px' : '120px';
7756 toggleBtn.textContent = expanded ? 'Hide Details' : 'Show Details';
7757
7758 if (expanded) {
7759 // Add detailed timeline with labels
7760 container.innerHTML = createAllocationTimeline(allocs, minTime, timeRange) +
7761 '<div style="position: absolute; bottom: 4px; left: 8px; font-size: 10px; color: var(--text-secondary);">Start</div>' +
7762 '<div style="position: absolute; bottom: 4px; right: 8px; font-size: 10px; color: var(--text-secondary);">End</div>';
7763 } else {
7764 container.innerHTML = createAllocationTimeline(allocs, minTime, timeRange);
7765 }
7766 };
7767 }
7768
7769 // Table filter
7770 const filterSelect = document.getElementById('ffi-filter');
7771 if (filterSelect) {
7772 filterSelect.onchange = () => {
7773 const filterValue = filterSelect.value;
7774 let filteredAllocs = allocs;
7775
7776 switch(filterValue) {
7777 case 'leaked':
7778 filteredAllocs = allocs.filter(a => a.is_leaked);
7779 break;
7780 case 'cloned':
7781 filteredAllocs = allocs.filter(a => a.clone_info?.clone_count > 0);
7782 break;
7783 case 'borrowed':
7784 filteredAllocs = allocs.filter(a => {
7785 const borrows = (a.borrow_info?.immutable_borrows || 0) + (a.borrow_info?.mutable_borrows || 0);
7786 return borrows > 0;
7787 });
7788 break;
7789 default:
7790 filteredAllocs = allocs;
7791 }
7792
7793 const tableContainer = document.getElementById('ffi-allocation-table');
7794 if (tableContainer) {
7795 tableContainer.innerHTML = createAllocationTable(filteredAllocs);
7796 }
7797 };
7798 }
7799}
7800
7801// Show allocation detail modal
7802window.showAllocationDetail = function(ptr) {
7803 const data = window.analysisData || {};
7804 const allocs = data.unsafe_ffi?.allocations || data.memory_analysis?.allocations || data.allocations || [];
7805 const alloc = allocs.find(a => a.ptr === ptr);
7806
7807 if (!alloc) return;
7808
7809 const modal = document.createElement('div');
7810 modal.style.cssText = `
7811 position: fixed; top: 0; left: 0; right: 0; bottom: 0;
7812 background: rgba(0,0,0,0.5); z-index: 1000;
7813 display: flex; align-items: center; justify-content: center;
7814 `;
7815
7816 modal.innerHTML = `
7817 <div style="background: var(--bg-primary); border-radius: 12px; padding: 24px; min-width: 400px; max-width: 600px; border: 1px solid var(--border-light);">
7818 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
7819 <h3 style="margin: 0; color: var(--text-primary);">Allocation Details</h3>
7820 <button onclick="this.closest('div').parentNode.remove()" style="background: none; border: none; font-size: 20px; color: var(--text-secondary); cursor: pointer;">ร</button>
7821 </div>
7822 <div style="color: var(--text-primary); line-height: 1.6;">
7823 <div style="margin-bottom: 12px;"><strong>Variable:</strong> ${alloc.var_name || 'unnamed'}</div>
7824 <div style="margin-bottom: 12px;"><strong>Type:</strong> ${alloc.type_name || 'Unknown'}</div>
7825 <div style="margin-bottom: 12px;"><strong>Size:</strong> ${formatBytes(alloc.size || 0)}</div>
7826 <div style="margin-bottom: 12px;"><strong>Pointer:</strong> <code>${alloc.ptr}</code></div>
7827 <div style="margin-bottom: 12px;"><strong>Thread:</strong> ${alloc.thread_id}</div>
7828 <div style="margin-bottom: 12px;"><strong>Allocated:</strong> ${new Date(alloc.timestamp_alloc / 1e6).toLocaleString()}</div>
7829 <div style="margin-bottom: 12px;"><strong>Lifetime:</strong> ${alloc.lifetime_ms ? alloc.lifetime_ms.toFixed(2) + 'ms' : 'Active'}</div>
7830 <div style="margin-bottom: 12px;"><strong>Immutable Borrows:</strong> ${alloc.borrow_info?.immutable_borrows || 0}</div>
7831 <div style="margin-bottom: 12px;"><strong>Mutable Borrows:</strong> ${alloc.borrow_info?.mutable_borrows || 0}</div>
7832 <div style="margin-bottom: 12px;"><strong>Clone Count:</strong> ${alloc.clone_info?.clone_count || 0}</div>
7833 <div style="margin-bottom: 12px;"><strong>FFI Tracked:</strong> ${alloc.ffi_tracked ? 'Yes' : 'No'}</div>
7834 <div style="margin-bottom: 12px;"><strong>Status:</strong>
7835 <span style="color: ${alloc.is_leaked ? 'var(--primary-red)' : 'var(--primary-green)'}; font-weight: 600;">
7836 ${alloc.is_leaked ? 'LEAKED' : 'OK'}
7837 </span>
7838 </div>
7839 ${alloc.safety_violations && alloc.safety_violations.length > 0 ?
7840 `<div style="margin-bottom: 12px; color: var(--primary-red);"><strong>Safety Violations:</strong> ${alloc.safety_violations.join(', ')}</div>`
7841 : ''}
7842 </div>
7843 </div>
7844 `;
7845
7846 document.body.appendChild(modal);
7847 modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
7848};
7849
7850// Show cluster detail for grouped allocations
7851window.showClusterDetail = function(ptrs) {
7852 const data = window.analysisData || {};
7853 const allocs = data.unsafe_ffi?.allocations || data.memory_analysis?.allocations || data.allocations || [];
7854 const clusterAllocs = allocs.filter(a => ptrs.includes(a.ptr));
7855
7856 if (clusterAllocs.length === 0) return;
7857
7858 const totalSize = clusterAllocs.reduce((sum, a) => sum + (a.size || 0), 0);
7859 const totalBorrows = clusterAllocs.reduce((sum, a) => sum + (a.borrow_info?.immutable_borrows || 0) + (a.borrow_info?.mutable_borrows || 0), 0);
7860 const totalClones = clusterAllocs.reduce((sum, a) => sum + (a.clone_info?.clone_count || 0), 0);
7861 const leakCount = clusterAllocs.filter(a => a.is_leaked).length;
7862
7863 const modal = document.createElement('div');
7864 modal.style.cssText = `
7865 position: fixed; top: 0; left: 0; right: 0; bottom: 0;
7866 background: rgba(0,0,0,0.5); z-index: 1000;
7867 display: flex; align-items: center; justify-content: center;
7868 `;
7869
7870 modal.innerHTML = `
7871 <div style="background: var(--bg-primary); border-radius: 12px; padding: 24px; min-width: 500px; max-width: 700px; max-height: 80vh; overflow-y: auto; border: 1px solid var(--border-light);">
7872 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
7873 <h3 style="margin: 0; color: var(--text-primary);">Allocation Cluster (${clusterAllocs.length} items)</h3>
7874 <button onclick="this.closest('div').parentNode.remove()" style="background: none; border: none; font-size: 20px; color: var(--text-secondary); cursor: pointer;">ร</button>
7875 </div>
7876 <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 16px;">
7877 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 6px;">
7878 <div style="font-size: 1.2rem; font-weight: 600; color: var(--primary-blue);">${formatBytes(totalSize)}</div>
7879 <div style="font-size: 0.8rem; color: var(--text-secondary);">Total Size</div>
7880 </div>
7881 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 6px;">
7882 <div style="font-size: 1.2rem; font-weight: 600; color: var(--primary-green);">${totalBorrows}</div>
7883 <div style="font-size: 0.8rem; color: var(--text-secondary);">Total Borrows</div>
7884 </div>
7885 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 6px;">
7886 <div style="font-size: 1.2rem; font-weight: 600; color: var(--primary-orange);">${totalClones}</div>
7887 <div style="font-size: 0.8rem; color: var(--text-secondary);">Total Clones</div>
7888 </div>
7889 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 6px;">
7890 <div style="font-size: 1.2rem; font-weight: 600; color: ${leakCount > 0 ? 'var(--primary-red)' : 'var(--primary-green)'};">${leakCount}</div>
7891 <div style="font-size: 0.8rem; color: var(--text-secondary);">Leaks</div>
7892 </div>
7893 </div>
7894 <div style="color: var(--text-primary);">
7895 <h4 style="margin: 0 0 12px 0; color: var(--text-primary);">Individual Allocations:</h4>
7896 <div style="max-height: 300px; overflow-y: auto; border: 1px solid var(--border-light); border-radius: 6px;">
7897 <table style="width: 100%; border-collapse: collapse; font-size: 12px;">
7898 <thead style="background: var(--bg-secondary); position: sticky; top: 0;">
7899 <tr>
7900 <th style="padding: 8px; text-align: left;">Variable</th>
7901 <th style="padding: 8px; text-align: right;">Size</th>
7902 <th style="padding: 8px; text-align: center;">Borrows</th>
7903 <th style="padding: 8px; text-align: center;">Clones</th>
7904 <th style="padding: 8px; text-align: center;">Status</th>
7905 </tr>
7906 </thead>
7907 <tbody>
7908 ${clusterAllocs.map(alloc => {
7909 const totalBorrows = (alloc.borrow_info?.immutable_borrows || 0) + (alloc.borrow_info?.mutable_borrows || 0);
7910 const cloneCount = alloc.clone_info?.clone_count || 0;
7911 const isLeaked = alloc.is_leaked;
7912 const statusColor = isLeaked ? 'var(--primary-red)' : 'var(--primary-green)';
7913 const statusText = isLeaked ? 'LEAKED' : 'OK';
7914
7915 return `
7916 <tr style="border-bottom: 1px solid var(--border-light); cursor: pointer;" onclick="showAllocationDetail('${alloc.ptr}')">
7917 <td style="padding: 8px; font-weight: 500;">${alloc.var_name || 'unnamed'}</td>
7918 <td style="padding: 8px; text-align: right; font-weight: 600;">${formatBytes(alloc.size || 0)}</td>
7919 <td style="padding: 8px; text-align: center; color: var(--primary-blue);">${totalBorrows}</td>
7920 <td style="padding: 8px; text-align: center; color: var(--primary-orange);">${cloneCount}</td>
7921 <td style="padding: 8px; text-align: center;"><span style="color: ${statusColor}; font-weight: 600; font-size: 11px;">${statusText}</span></td>
7922 </tr>
7923 `;
7924 }).join('')}
7925 </tbody>
7926 </table>
7927 </div>
7928 </div>
7929 </div>
7930 `;
7931
7932 document.body.appendChild(modal);
7933 modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
7934};
7935
7936// Render enhanced data insights with beautiful visualizations
7937function renderEnhancedDataInsights() {
7938 const data = window.analysisData || {};
7939 const allocs = data.memory_analysis?.allocations || data.allocations || [];
7940
7941 if (allocs.length === 0) return;
7942
7943 // Calculate timeline insights
7944 const timestamps = allocs.map(a => a.timestamp_alloc).filter(t => t).sort((a, b) => a - b);
7945 const timeSpanMs = timestamps.length > 1 ? (timestamps[timestamps.length - 1] - timestamps[0]) / 1e6 : 0;
7946 const allocationBurst = (allocs.length / Math.max(1, timeSpanMs / 1000)).toFixed(1);
7947
7948 // Calculate borrow patterns
7949 const borrowPatterns = {};
7950 let totalBorrows = 0;
7951 let totalMutable = 0;
7952 let totalImmutable = 0;
7953
7954 allocs.forEach(alloc => {
7955 const bi = alloc.borrow_info || {};
7956 const immut = bi.immutable_borrows || 0;
7957 const mut = bi.mutable_borrows || 0;
7958 const pattern = `${immut}i+${mut}m`;
7959 borrowPatterns[pattern] = (borrowPatterns[pattern] || 0) + 1;
7960 totalBorrows += immut + mut;
7961 totalImmutable += immut;
7962 totalMutable += mut;
7963 });
7964
7965 // Calculate clone operations
7966 const totalClones = allocs.reduce((sum, a) => sum + (a.clone_info?.clone_count || 0), 0);
7967
7968 // Update Timeline Insights
7969 document.getElementById('time-span').textContent = timeSpanMs.toFixed(2) + 'ms';
7970 document.getElementById('allocation-burst').textContent = allocationBurst + '/sec';
7971 document.getElementById('peak-concurrency').textContent = Math.max(...allocs.map(a => (a.borrow_info?.max_concurrent_borrows || 0)));
7972 document.getElementById('thread-activity').textContent = 'Single Thread';
7973
7974 // Update Memory Operations
7975 document.getElementById('borrow-ops').textContent = totalBorrows;
7976 document.getElementById('clone-ops').textContent = totalClones;
7977 document.getElementById('mut-ratio').textContent = totalImmutable > 0 ? (totalMutable / totalImmutable).toFixed(1) : '0';
7978 document.getElementById('avg-borrows').textContent = (totalBorrows / allocs.length).toFixed(1);
7979
7980 // Render charts with forced data refresh
7981 renderBorrowPatternChart(borrowPatterns);
7982
7983 // Force Type Memory Distribution to render with debug info
7984 console.log('๐ Forcing Type Memory Distribution render with data:', allocs.length, 'allocations');
7985 setTimeout(() => {
7986 renderMemoryDistributionChart(allocs);
7987 }, 100);
7988
7989 console.log('โ
Enhanced data insights rendered:', {
7990 timeSpan: timeSpanMs.toFixed(2) + 'ms',
7991 totalBorrows,
7992 totalClones,
7993 borrowPatterns,
7994 allocCount: allocs.length
7995 });
7996}
7997
7998// Render borrow activity heatmap (ๆฐๅฅ็ด่ง็ๅฏ่งๅ)
7999function renderBorrowPatternChart(patterns) {
8000 const container = document.getElementById('borrowPatternChart');
8001 if (!container) return;
8002
8003 const data = window.analysisData || {};
8004 const allocs = data.memory_analysis?.allocations || data.allocations || [];
8005
8006 // Create interactive borrow activity heatmap
8007 container.innerHTML = '';
8008 container.style.cssText = 'height: 200px; overflow-y: auto; padding: 8px; background: var(--bg-primary); border-radius: 8px; border: 1px solid var(--border-light);';
8009
8010 // Group variables by borrow intensity
8011 const borrowGroups = {
8012 'High Activity (4i+2m)': [],
8013 'Normal Activity (2i+1m)': [],
8014 'Low Activity (0-1 borrows)': []
8015 };
8016
8017 allocs.forEach(alloc => {
8018 const bi = alloc.borrow_info || {};
8019 const immut = bi.immutable_borrows || 0;
8020 const mut = bi.mutable_borrows || 0;
8021 const total = immut + mut;
8022
8023 if (total >= 5) {
8024 borrowGroups['High Activity (4i+2m)'].push(alloc);
8025 } else if (total >= 2) {
8026 borrowGroups['Normal Activity (2i+1m)'].push(alloc);
8027 } else {
8028 borrowGroups['Low Activity (0-1 borrows)'].push(alloc);
8029 }
8030 });
8031
8032 // Create visual representation
8033 Object.entries(borrowGroups).forEach(([groupName, groupAllocs], groupIndex) => {
8034 if (groupAllocs.length === 0) return;
8035
8036 const groupDiv = document.createElement('div');
8037 groupDiv.style.cssText = 'margin-bottom: 12px;';
8038
8039 const groupHeader = document.createElement('div');
8040 groupHeader.style.cssText = `
8041 font-size: 11px; font-weight: 600; margin-bottom: 6px;
8042 color: var(--text-primary); display: flex; align-items: center; gap: 8px;
8043 `;
8044
8045 const colors = ['#ef4444', '#f59e0b', '#10b981'];
8046 const icons = ['๐ฅ', 'โก', '๐ง'];
8047
8048 groupHeader.innerHTML = `
8049 <span style="font-size: 14px;">${icons[groupIndex]}</span>
8050 <span>${groupName}</span>
8051 <span style="background: ${colors[groupIndex]}; color: white; padding: 2px 6px; border-radius: 10px; font-size: 9px;">
8052 ${groupAllocs.length}
8053 </span>
8054 `;
8055
8056 const bubbleContainer = document.createElement('div');
8057 bubbleContainer.style.cssText = `
8058 display: flex; flex-wrap: wrap; gap: 4px; padding: 12px;
8059 background: var(--bg-secondary); border-radius: 6px; min-height: 60px;
8060 align-items: center; justify-content: flex-start;
8061 `;
8062
8063 // Create borrow activity bubbles
8064 groupAllocs.forEach((alloc, index) => {
8065 const bi = alloc.borrow_info || {};
8066 const immut = bi.immutable_borrows || 0;
8067 const mut = bi.mutable_borrows || 0;
8068 const maxConcurrent = bi.max_concurrent_borrows || 0;
8069
8070 const bubble = document.createElement('div');
8071 const size = Math.max(16, Math.min(32, 12 + (immut + mut) * 2));
8072
8073 bubble.style.cssText = `
8074 width: ${size}px; height: ${size}px; border-radius: 50%;
8075 background: linear-gradient(45deg, ${colors[groupIndex]}80, ${colors[groupIndex]});
8076 border: 2px solid ${colors[groupIndex]}; cursor: pointer;
8077 display: flex; align-items: center; justify-content: center;
8078 font-size: 8px; font-weight: bold; color: white;
8079 transition: transform 0.2s, box-shadow 0.2s;
8080 position: relative;
8081 `;
8082
8083 bubble.textContent = immut + mut;
8084 bubble.title = `${alloc.var_name}: ${immut}i + ${mut}m (max: ${maxConcurrent})`;
8085
8086 // Add interactive effects
8087 bubble.onmouseover = () => {
8088 bubble.style.transform = 'scale(1.2)';
8089 bubble.style.boxShadow = `0 4px 12px ${colors[groupIndex]}40`;
8090 };
8091 bubble.onmouseout = () => {
8092 bubble.style.transform = 'scale(1)';
8093 bubble.style.boxShadow = 'none';
8094 };
8095
8096 // Add click to show details
8097 bubble.onclick = () => {
8098 showBorrowDetail(alloc);
8099 };
8100
8101 bubbleContainer.appendChild(bubble);
8102 });
8103
8104 groupDiv.appendChild(groupHeader);
8105 groupDiv.appendChild(bubbleContainer);
8106 container.appendChild(groupDiv);
8107 });
8108
8109 // Add summary stats at bottom
8110 const summaryDiv = document.createElement('div');
8111 summaryDiv.style.cssText = `
8112 margin-top: 8px; padding: 8px; background: var(--bg-secondary);
8113 border-radius: 6px; font-size: 10px; color: var(--text-secondary);
8114 display: flex; justify-content: space-between;
8115 `;
8116
8117 const totalBorrows = allocs.reduce((sum, a) => sum + (a.borrow_info?.immutable_borrows || 0) + (a.borrow_info?.mutable_borrows || 0), 0);
8118 const avgBorrows = (totalBorrows / allocs.length).toFixed(1);
8119 const maxConcurrent = Math.max(...allocs.map(a => a.borrow_info?.max_concurrent_borrows || 0));
8120
8121 summaryDiv.innerHTML = `
8122 <span>Total Borrows: <strong>${totalBorrows}</strong></span>
8123 <span>Avg/Variable: <strong>${avgBorrows}</strong></span>
8124 <span>Peak Concurrent: <strong>${maxConcurrent}</strong></span>
8125 `;
8126
8127 container.appendChild(summaryDiv);
8128}
8129
8130// Show borrow detail modal
8131function showBorrowDetail(alloc) {
8132 const modal = document.createElement('div');
8133 modal.style.cssText = `
8134 position: fixed; top: 0; left: 0; right: 0; bottom: 0;
8135 background: rgba(0,0,0,0.5); z-index: 1000;
8136 display: flex; align-items: center; justify-content: center;
8137 `;
8138
8139 const bi = alloc.borrow_info || {};
8140
8141 modal.innerHTML = `
8142 <div style="background: var(--bg-primary); border-radius: 12px; padding: 20px; min-width: 350px; border: 1px solid var(--border-light);">
8143 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
8144 <h3 style="margin: 0; color: var(--text-primary);">๐ Borrow Analysis</h3>
8145 <button onclick="this.closest('div').parentNode.remove()" style="background: none; border: none; font-size: 18px; color: var(--text-secondary); cursor: pointer;">ร</button>
8146 </div>
8147 <div style="color: var(--text-primary); line-height: 1.6;">
8148 <div style="margin-bottom: 12px;"><strong>Variable:</strong> ${alloc.var_name}</div>
8149 <div style="margin-bottom: 12px;"><strong>Type:</strong> ${alloc.type_name}</div>
8150 <div style="margin-bottom: 12px;"><strong>Size:</strong> ${formatBytes(alloc.size || 0)}</div>
8151 <hr style="border: none; border-top: 1px solid var(--border-light); margin: 16px 0;">
8152 <div style="margin-bottom: 8px;"><strong>๐ Immutable Borrows:</strong> <span style="color: var(--primary-blue); font-weight: 600;">${bi.immutable_borrows || 0}</span></div>
8153 <div style="margin-bottom: 8px;"><strong>โ๏ธ Mutable Borrows:</strong> <span style="color: var(--primary-orange); font-weight: 600;">${bi.mutable_borrows || 0}</span></div>
8154 <div style="margin-bottom: 8px;"><strong>๐ฅ Max Concurrent:</strong> <span style="color: var(--primary-red); font-weight: 600;">${bi.max_concurrent_borrows || 0}</span></div>
8155 <div style="margin-bottom: 8px;"><strong>โก Total Activity:</strong> <span style="color: var(--primary-green); font-weight: 600;">${(bi.immutable_borrows || 0) + (bi.mutable_borrows || 0)}</span></div>
8156 </div>
8157 </div>
8158 `;
8159
8160 document.body.appendChild(modal);
8161 modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
8162}
8163
8164// Render type memory distribution as interactive memory blocks
8165function renderMemoryDistributionChart(allocs) {
8166 const container = document.getElementById('memoryDistributionChart');
8167 if (!container) return;
8168
8169 // Create unique memory blocks visualization (not pie chart)
8170 container.innerHTML = '';
8171
8172 // Group by type and sum memory
8173 const typeMemory = {};
8174 console.log('๐ Processing allocations for memory blocks:', allocs.length);
8175
8176 allocs.forEach((alloc, index) => {
8177 let typeName = alloc.type_name || 'Unknown';
8178 const originalType = typeName;
8179 const size = alloc.size || 0;
8180
8181 // Simplify type names
8182 if (typeName.includes('HashMap')) {
8183 typeName = 'HashMap';
8184 } else if (typeName.includes('BTreeMap')) {
8185 typeName = 'BTreeMap';
8186 } else if (typeName.includes('Arc')) {
8187 typeName = 'Arc';
8188 } else if (typeName.includes('Rc')) {
8189 typeName = 'Rc';
8190 } else if (typeName.includes('String')) {
8191 typeName = 'String';
8192 } else if (typeName.includes('Vec')) {
8193 typeName = 'Vec';
8194 } else {
8195 typeName = originalType.split('::').pop() || 'Unknown';
8196 }
8197
8198 if (!typeMemory[typeName]) {
8199 typeMemory[typeName] = { size: 0, count: 0, allocations: [] };
8200 }
8201 typeMemory[typeName].size += size;
8202 typeMemory[typeName].count += 1;
8203 typeMemory[typeName].allocations.push(alloc);
8204
8205 console.log(`[${index}] ${originalType} -> ${typeName}: ${size} bytes`);
8206 });
8207
8208 // Sort by memory size
8209 const sortedTypes = Object.entries(typeMemory)
8210 .filter(([type, data]) => data.size > 0)
8211 .sort((a, b) => b[1].size - a[1].size);
8212
8213 console.log('Memory blocks data:', sortedTypes);
8214
8215 if (sortedTypes.length === 0) {
8216 container.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">No type data available</div>';
8217 return;
8218 }
8219
8220 const totalMemory = sortedTypes.reduce((sum, [_, data]) => sum + data.size, 0);
8221 const colors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4', '#84cc16', '#f97316'];
8222
8223 // Create memory blocks visualization
8224 container.innerHTML = `
8225 <div style="height: 100%; display: flex; flex-direction: column; gap: 8px; padding: 8px;">
8226 ${sortedTypes.map(([typeName, data], index) => {
8227 const percentage = ((data.size / totalMemory) * 100);
8228 const color = colors[index % colors.length];
8229 const blockHeight = Math.max(20, Math.min(60, percentage * 2));
8230
8231 return `
8232 <div style="display: flex; align-items: center; gap: 12px; cursor: pointer; padding: 6px; border-radius: 6px; transition: all 0.2s;"
8233 onmouseover="this.style.background='var(--bg-primary)'; this.style.transform='scale(1.02)'"
8234 onmouseout="this.style.background='transparent'; this.style.transform='scale(1)'"
8235 onclick="showTypeDetail('${typeName}', ${JSON.stringify(data).replace(/"/g, '"')})">
8236
8237 <!-- Memory Block -->
8238 <div style="width: 40px; height: ${blockHeight}px; background: linear-gradient(135deg, ${color}, ${color}80);
8239 border-radius: 4px; position: relative; border: 2px solid ${color};">
8240 <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
8241 color: white; font-size: 8px; font-weight: bold;">
8242 ${data.count}
8243 </div>
8244 </div>
8245
8246 <!-- Type Info -->
8247 <div style="flex: 1; min-width: 0;">
8248 <div style="font-size: 13px; font-weight: 600; color: var(--text-primary); margin-bottom: 2px;">
8249 ${typeName}
8250 </div>
8251 <div style="font-size: 11px; color: var(--text-secondary);">
8252 ${formatBytes(data.size)} โข ${data.count} allocation${data.count > 1 ? 's' : ''}
8253 </div>
8254 </div>
8255
8256 <!-- Percentage Bar -->
8257 <div style="width: 60px; text-align: right;">
8258 <div style="font-size: 12px; font-weight: 600; color: ${color}; margin-bottom: 2px;">
8259 ${percentage.toFixed(1)}%
8260 </div>
8261 <div style="width: 100%; height: 4px; background: var(--border-light); border-radius: 2px; overflow: hidden;">
8262 <div style="width: ${percentage}%; height: 100%; background: ${color}; border-radius: 2px;"></div>
8263 </div>
8264 </div>
8265 </div>
8266 `;
8267 }).join('')}
8268 </div>
8269 `;
8270}
8271
8272// Show type detail modal
8273window.showTypeDetail = function(typeName, data) {
8274 const modal = document.createElement('div');
8275 modal.style.cssText = `
8276 position: fixed; top: 0; left: 0; right: 0; bottom: 0;
8277 background: rgba(0,0,0,0.6); z-index: 1000;
8278 display: flex; align-items: center; justify-content: center;
8279 `;
8280
8281 const typeColors = {
8282 'HashMap': '#3b82f6', 'BTreeMap': '#10b981', 'Arc': '#f59e0b',
8283 'Rc': '#ef4444', 'String': '#8b5cf6', 'Vec': '#06b6d4'
8284 };
8285 const color = typeColors[typeName] || '#64748b';
8286
8287 modal.innerHTML = `
8288 <div style="background: var(--bg-primary); border-radius: 16px; padding: 24px; min-width: 500px; max-width: 700px; max-height: 80vh; overflow-y: auto; border: 1px solid var(--border-light);">
8289 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
8290 <h3 style="margin: 0; color: var(--text-primary); display: flex; align-items: center; gap: 12px;">
8291 <div style="width: 24px; height: 24px; background: ${color}; border-radius: 4px; display: flex; align-items: center; justify-content: center; color: white; font-size: 12px; font-weight: bold;">
8292 ${data.count}
8293 </div>
8294 ${typeName} Memory Analysis
8295 </h3>
8296 <button onclick="this.closest('div').parentNode.remove()" style="background: none; border: none; font-size: 20px; color: var(--text-secondary); cursor: pointer;">ร</button>
8297 </div>
8298
8299 <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 20px;">
8300 <div style="text-align: center; padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
8301 <div style="font-size: 1.8rem; font-weight: 700; color: ${color};">${formatBytes(data.size)}</div>
8302 <div style="font-size: 0.8rem; color: var(--text-secondary);">Total Memory</div>
8303 </div>
8304 <div style="text-align: center; padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
8305 <div style="font-size: 1.8rem; font-weight: 700; color: var(--text-primary);">${data.count}</div>
8306 <div style="font-size: 0.8rem; color: var(--text-secondary);">Allocations</div>
8307 </div>
8308 <div style="text-align: center; padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
8309 <div style="font-size: 1.8rem; font-weight: 700; color: var(--text-primary);">${formatBytes(data.size / data.count)}</div>
8310 <div style="font-size: 0.8rem; color: var(--text-secondary);">Avg Size</div>
8311 </div>
8312 </div>
8313
8314 <h4 style="margin: 0 0 12px 0; color: var(--text-primary);">Individual Allocations:</h4>
8315 <div style="max-height: 300px; overflow-y: auto; border: 1px solid var(--border-light); border-radius: 8px;">
8316 <table style="width: 100%; border-collapse: collapse; font-size: 12px;">
8317 <thead style="background: var(--bg-secondary); position: sticky; top: 0;">
8318 <tr>
8319 <th style="padding: 8px; text-align: left; color: var(--text-primary);">Variable</th>
8320 <th style="padding: 8px; text-align: right; color: var(--text-primary);">Size</th>
8321 <th style="padding: 8px; text-align: center; color: var(--text-primary);">Status</th>
8322 <th style="padding: 8px; text-align: right; color: var(--text-primary);">Lifetime</th>
8323 </tr>
8324 </thead>
8325 <tbody>
8326 ${data.allocations.map(alloc => {
8327 const isLeaked = alloc.is_leaked;
8328 const statusColor = isLeaked ? 'var(--primary-red)' : 'var(--primary-green)';
8329 const statusText = isLeaked ? 'LEAKED' : 'OK';
8330 const lifetime = alloc.lifetime_ms || 'Active';
8331
8332 return `
8333 <tr style="border-bottom: 1px solid var(--border-light); cursor: pointer;" onclick="showAllocationDetail('${alloc.ptr}')">
8334 <td style="padding: 8px; color: var(--text-primary); font-weight: 500;">${alloc.var_name || 'unnamed'}</td>
8335 <td style="padding: 8px; text-align: right; color: var(--text-primary); font-weight: 600;">${formatBytes(alloc.size || 0)}</td>
8336 <td style="padding: 8px; text-align: center;">
8337 <span style="color: ${statusColor}; font-weight: 600; font-size: 11px;">${statusText}</span>
8338 </td>
8339 <td style="padding: 8px; text-align: right; color: var(--text-secondary); font-size: 11px;">
8340 ${typeof lifetime === 'number' ? lifetime.toFixed(2) + 'ms' : lifetime}
8341 </td>
8342 </tr>
8343 `;
8344 }).join('')}
8345 </tbody>
8346 </table>
8347 </div>
8348 </div>
8349 `;
8350
8351 document.body.appendChild(modal);
8352 modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
8353};
8354
8355// Render detailed allocation timeline with heap/stack and timing info
8356function renderAllocationTimelineDetail() {
8357 const container = document.getElementById('allocationTimelineDetail');
8358 if (!container) return;
8359
8360 const data = window.analysisData || {};
8361 const allocs = data.memory_analysis?.allocations || data.allocations || [];
8362
8363 if (allocs.length === 0) {
8364 container.innerHTML = '<div style="text-align: center; color: var(--text-secondary); margin-top: 80px;">No allocation data available</div>';
8365 return;
8366 }
8367
8368 // Sort by allocation time
8369 const sortedAllocs = allocs.slice().sort((a, b) => (a.timestamp_alloc || 0) - (b.timestamp_alloc || 0));
8370 const minTime = sortedAllocs[0].timestamp_alloc || 0;
8371
8372 // Classify allocations as heap/stack
8373 const classifyAllocation = (typeName) => {
8374 const heapIndicators = ['Arc', 'Rc', 'Box', 'Vec', 'HashMap', 'BTreeMap', 'String'];
8375 const stackIndicators = ['&', 'i32', 'u32', 'i64', 'u64', 'f32', 'f64', 'bool', 'char'];
8376
8377 if (heapIndicators.some(indicator => typeName.includes(indicator))) {
8378 return { type: 'heap', color: '#ef4444', icon: '๐๏ธ' };
8379 } else if (stackIndicators.some(indicator => typeName.includes(indicator))) {
8380 return { type: 'stack', color: '#10b981', icon: '๐' };
8381 } else {
8382 return { type: 'unknown', color: '#64748b', icon: 'โ' };
8383 }
8384 };
8385
8386 container.innerHTML = `
8387 <div style="display: flex; flex-direction: column; gap: 4px; height: 100%;">
8388 ${sortedAllocs.slice(0, 15).map((alloc, index) => {
8389 const allocTime = alloc.timestamp_alloc || 0;
8390 const lifetime = alloc.lifetime_ms || 0;
8391 const dropTime = allocTime + (lifetime * 1_000_000); // Convert ms to ns
8392 const relativeTime = ((allocTime - minTime) / 1_000_000).toFixed(2); // Convert to ms
8393
8394 const classification = classifyAllocation(alloc.type_name || '');
8395 const typeName = (alloc.type_name || 'Unknown').split('::').pop().split('<')[0];
8396
8397 return `
8398 <div style="display: flex; align-items: center; gap: 8px; padding: 6px; border-radius: 4px; cursor: pointer; transition: all 0.2s;"
8399 onmouseover="this.style.background='var(--bg-primary)'"
8400 onmouseout="this.style.background='transparent'"
8401 onclick="showAllocationTimeDetail('${alloc.ptr}', ${allocTime}, ${dropTime}, '${classification.type}')">
8402
8403 <!-- Allocation Type Icon -->
8404 <div style="width: 24px; height: 24px; background: ${classification.color}20; border: 1px solid ${classification.color}; border-radius: 4px; display: flex; align-items: center; justify-content: center; font-size: 12px;">
8405 ${classification.icon}
8406 </div>
8407
8408 <!-- Variable Info -->
8409 <div style="flex: 1; min-width: 0;">
8410 <div style="font-size: 11px; font-weight: 600; color: var(--text-primary); margin-bottom: 1px;">
8411 ${alloc.var_name || 'unnamed'} (${typeName})
8412 </div>
8413 <div style="font-size: 9px; color: var(--text-secondary);">
8414 ${formatBytes(alloc.size || 0)} โข ${classification.type.toUpperCase()}
8415 </div>
8416 </div>
8417
8418 <!-- Timing Info -->
8419 <div style="text-align: right; font-size: 9px;">
8420 <div style="color: var(--primary-blue); font-weight: 600;">+${relativeTime}ms</div>
8421 <div style="color: var(--text-secondary);">โ ${lifetime}ms</div>
8422 </div>
8423 </div>
8424 `;
8425 }).join('')}
8426
8427 ${sortedAllocs.length > 15 ? `
8428 <div style="text-align: center; padding: 8px; color: var(--text-secondary); font-size: 10px; border-top: 1px solid var(--border-light);">
8429 ... and ${sortedAllocs.length - 15} more allocations
8430 </div>
8431 ` : ''}
8432 </div>
8433 `;
8434}
8435
8436// Show detailed allocation timing modal
8437window.showAllocationTimeDetail = function(ptr, allocTime, dropTime, allocationType) {
8438 const data = window.analysisData || {};
8439 const allocs = data.memory_analysis?.allocations || data.allocations || [];
8440 const alloc = allocs.find(a => a.ptr === ptr);
8441
8442 if (!alloc) return;
8443
8444 const modal = document.createElement('div');
8445 modal.style.cssText = `
8446 position: fixed; top: 0; left: 0; right: 0; bottom: 0;
8447 background: rgba(0,0,0,0.6); z-index: 1000;
8448 display: flex; align-items: center; justify-content: center;
8449 `;
8450
8451 const allocDate = new Date(allocTime / 1_000_000);
8452 const dropDate = new Date(dropTime / 1_000_000);
8453 const typeColor = allocationType === 'heap' ? '#ef4444' : allocationType === 'stack' ? '#10b981' : '#64748b';
8454 const typeIcon = allocationType === 'heap' ? '๐๏ธ' : allocationType === 'stack' ? '๐' : 'โ';
8455
8456 modal.innerHTML = `
8457 <div style="background: var(--bg-primary); border-radius: 16px; padding: 24px; min-width: 500px; color: var(--text-primary); border: 1px solid var(--border-light);">
8458 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
8459 <h3 style="margin: 0; display: flex; align-items: center; gap: 12px;">
8460 <span style="font-size: 24px;">${typeIcon}</span>
8461 ${allocationType.toUpperCase()} Allocation Timeline
8462 </h3>
8463 <button onclick="this.closest('div').parentNode.remove()" style="background: none; border: none; font-size: 20px; color: var(--text-secondary); cursor: pointer;">ร</button>
8464 </div>
8465
8466 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px;">
8467 <div style="padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
8468 <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Variable</div>
8469 <div style="font-size: 16px; font-weight: 600;">${alloc.var_name || 'unnamed'}</div>
8470 </div>
8471 <div style="padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
8472 <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Type</div>
8473 <div style="font-size: 14px; font-weight: 600; word-break: break-all;">${alloc.type_name || 'Unknown'}</div>
8474 </div>
8475 </div>
8476
8477 <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; margin-bottom: 20px;">
8478 <div style="text-align: center; padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
8479 <div style="font-size: 18px; font-weight: 700; color: ${typeColor};">${formatBytes(alloc.size || 0)}</div>
8480 <div style="font-size: 12px; color: var(--text-secondary);">Size</div>
8481 </div>
8482 <div style="text-align: center; padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
8483 <div style="font-size: 18px; font-weight: 700; color: var(--primary-blue);">${alloc.lifetime_ms || 0}ms</div>
8484 <div style="font-size: 12px; color: var(--text-secondary);">Lifetime</div>
8485 </div>
8486 <div style="text-align: center; padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
8487 <div style="font-size: 18px; font-weight: 700; color: ${typeColor};">${allocationType.toUpperCase()}</div>
8488 <div style="font-size: 12px; color: var(--text-secondary);">Location</div>
8489 </div>
8490 </div>
8491
8492 <div style="background: var(--bg-secondary); padding: 16px; border-radius: 8px;">
8493 <h4 style="margin: 0 0 12px 0; color: var(--text-primary);">Timeline Details</h4>
8494 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
8495 <div>
8496 <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">๐ข Allocated At</div>
8497 <div style="font-size: 14px; font-weight: 600; color: var(--primary-green);">${allocDate.toLocaleString()}</div>
8498 <div style="font-size: 11px; color: var(--text-secondary); margin-top: 2px;">Timestamp: ${allocTime}</div>
8499 </div>
8500 <div>
8501 <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">๐ด Dropped At</div>
8502 <div style="font-size: 14px; font-weight: 600; color: var(--primary-red);">${dropDate.toLocaleString()}</div>
8503 <div style="font-size: 11px; color: var(--text-secondary); margin-top: 2px;">Timestamp: ${dropTime}</div>
8504 </div>
8505 </div>
8506 </div>
8507 </div>
8508 `;
8509
8510 document.body.appendChild(modal);
8511 modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
8512};
8513
8514// Update lifecycle statistics and render distribution chart
8515function updateLifecycleStatistics() {
8516 const data = window.analysisData || {};
8517 const allocs = data.memory_analysis?.allocations || data.allocations || [];
8518
8519 if (allocs.length === 0) return;
8520
8521 // Calculate lifecycle statistics
8522 const activeVars = allocs.filter(a => !a.is_leaked && a.lifetime_ms === undefined).length;
8523 const freedVars = allocs.filter(a => !a.is_leaked && a.lifetime_ms !== undefined).length;
8524 const leakedVars = allocs.filter(a => a.is_leaked).length;
8525 const avgLifetime = allocs.filter(a => a.lifetime_ms).reduce((sum, a) => sum + a.lifetime_ms, 0) / Math.max(1, allocs.filter(a => a.lifetime_ms).length);
8526
8527 // Update statistics display
8528 document.getElementById('active-vars').textContent = activeVars;
8529 document.getElementById('freed-vars').textContent = freedVars;
8530 document.getElementById('leaked-vars').textContent = leakedVars;
8531 document.getElementById('avg-lifetime-stat').textContent = avgLifetime.toFixed(2) + 'ms';
8532
8533 // Render lifecycle distribution chart
8534 renderLifecycleDistributionChart(allocs);
8535}
8536
8537// Render lifecycle distribution chart
8538function renderLifecycleDistributionChart(allocs) {
8539 const ctx = document.getElementById('lifecycleDistributionChart');
8540 if (!ctx || !window.Chart) return;
8541
8542 // Cleanup existing chart
8543 if (window.chartInstances && window.chartInstances['lifecycleDistributionChart']) {
8544 try { window.chartInstances['lifecycleDistributionChart'].destroy(); } catch(_) {}
8545 delete window.chartInstances['lifecycleDistributionChart'];
8546 }
8547
8548 // Group allocations by lifetime ranges
8549 const lifetimeRanges = {
8550 'Instant (0ms)': 0,
8551 'Quick (0-1ms)': 0,
8552 'Short (1-10ms)': 0,
8553 'Long (10ms+)': 0,
8554 'Active': 0
8555 };
8556
8557 allocs.forEach(alloc => {
8558 const lifetime = alloc.lifetime_ms;
8559 if (lifetime === undefined) {
8560 lifetimeRanges['Active']++;
8561 } else if (lifetime === 0) {
8562 lifetimeRanges['Instant (0ms)']++;
8563 } else if (lifetime <= 1) {
8564 lifetimeRanges['Quick (0-1ms)']++;
8565 } else if (lifetime <= 10) {
8566 lifetimeRanges['Short (1-10ms)']++;
8567 } else {
8568 lifetimeRanges['Long (10ms+)']++;
8569 }
8570 });
8571
8572 const labels = Object.keys(lifetimeRanges);
8573 const values = Object.values(lifetimeRanges);
8574 const colors = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#8b5cf6'];
8575
8576 if (values.some(v => v > 0)) {
8577 const chart = new Chart(ctx, {
8578 type: 'bar',
8579 data: {
8580 labels: labels,
8581 datasets: [{
8582 data: values,
8583 backgroundColor: colors,
8584 borderWidth: 1,
8585 borderColor: colors.map(c => c + '80')
8586 }]
8587 },
8588 options: {
8589 responsive: true,
8590 maintainAspectRatio: false,
8591 plugins: {
8592 legend: { display: false },
8593 tooltip: {
8594 callbacks: {
8595 label: (context) => {
8596 const range = context.label;
8597 const count = context.parsed.y;
8598 const total = context.dataset.data.reduce((a, b) => a + b, 0);
8599 const percentage = ((count / total) * 100).toFixed(1);
8600 return `${range}: ${count} vars (${percentage}%)`;
8601 }
8602 }
8603 }
8604 },
8605 scales: {
8606 x: {
8607 ticks: {
8608 font: { size: 9 },
8609 color: function(context) {
8610 return document.documentElement.classList.contains('dark-theme') ? '#cbd5e1' : '#64748b';
8611 },
8612 maxRotation: 45
8613 },
8614 grid: { display: false }
8615 },
8616 y: {
8617 beginAtZero: true,
8618 ticks: {
8619 font: { size: 9 },
8620 color: function(context) {
8621 return document.documentElement.classList.contains('dark-theme') ? '#cbd5e1' : '#64748b';
8622 }
8623 },
8624 grid: {
8625 color: function(context) {
8626 return document.documentElement.classList.contains('dark-theme') ? '#374151' : '#e2e8f0';
8627 }
8628 }
8629 }
8630 }
8631 }
8632 });
8633
8634 window.chartInstances = window.chartInstances || {};
8635 window.chartInstances['lifecycleDistributionChart'] = chart;
8636 }
8637}
8638
8639// Render memory hotspots visualization
8640function renderMemoryHotspots() {
8641 const container = document.getElementById('memoryHotspots');
8642 if (!container) return;
8643
8644 const data = window.analysisData || {};
8645 const allocs = data.memory_analysis?.allocations || data.allocations || [];
8646
8647 if (allocs.length === 0) {
8648 container.innerHTML = '<div style="text-align: center; color: var(--text-secondary); margin-top: 100px;">No allocation data available</div>';
8649 return;
8650 }
8651
8652 // Sort allocations by size to identify hotspots
8653 const sortedAllocs = allocs.slice().sort((a, b) => (b.size || 0) - (a.size || 0));
8654 const topHotspots = sortedAllocs.slice(0, 10); // Top 10 largest allocations
8655
8656 container.innerHTML = '';
8657
8658 // Create hotspot visualization
8659 topHotspots.forEach((alloc, index) => {
8660 const size = alloc.size || 0;
8661 const maxSize = sortedAllocs[0].size || 1;
8662 const intensity = (size / maxSize) * 100;
8663
8664 const hotspotDiv = document.createElement('div');
8665 hotspotDiv.style.cssText = `
8666 display: flex; align-items: center; padding: 8px; margin-bottom: 6px;
8667 background: linear-gradient(90deg, var(--bg-primary) 0%, var(--primary-red)${Math.floor(intensity/4)} ${intensity}%, var(--bg-primary) 100%);
8668 border-radius: 6px; border: 1px solid var(--border-light);
8669 cursor: pointer; transition: transform 0.2s;
8670 `;
8671
8672 const heatColor = intensity > 80 ? '#ef4444' : intensity > 60 ? '#f59e0b' : intensity > 40 ? '#10b981' : '#3b82f6';
8673
8674 hotspotDiv.innerHTML = `
8675 <div style="width: 24px; height: 24px; border-radius: 50%; background: ${heatColor};
8676 display: flex; align-items: center; justify-content: center; margin-right: 12px;
8677 font-size: 10px; font-weight: bold; color: white;">
8678 ${index + 1}
8679 </div>
8680 <div style="flex: 1;">
8681 <div style="font-size: 12px; font-weight: 600; color: var(--text-primary);">
8682 ${alloc.var_name || 'unnamed'}
8683 </div>
8684 <div style="font-size: 10px; color: var(--text-secondary);">
8685 ${(alloc.type_name || 'Unknown').replace(/std::|alloc::/g, '').substring(0, 30)}...
8686 </div>
8687 </div>
8688 <div style="text-align: right;">
8689 <div style="font-size: 12px; font-weight: 700; color: ${heatColor};">
8690 ${formatBytes(size)}
8691 </div>
8692 <div style="font-size: 9px; color: var(--text-secondary);">
8693 ${intensity.toFixed(1)}% of max
8694 </div>
8695 </div>
8696 `;
8697
8698 hotspotDiv.onmouseover = () => {
8699 hotspotDiv.style.transform = 'scale(1.02)';
8700 hotspotDiv.style.boxShadow = `0 4px 12px ${heatColor}40`;
8701 };
8702 hotspotDiv.onmouseout = () => {
8703 hotspotDiv.style.transform = 'scale(1)';
8704 hotspotDiv.style.boxShadow = 'none';
8705 };
8706
8707 hotspotDiv.onclick = () => {
8708 showAllocationDetail(alloc.ptr);
8709 };
8710
8711 container.appendChild(hotspotDiv);
8712 });
8713
8714 // Add summary at bottom
8715 const summaryDiv = document.createElement('div');
8716 summaryDiv.style.cssText = `
8717 margin-top: 12px; padding: 8px; background: var(--bg-primary);
8718 border-radius: 6px; font-size: 10px; color: var(--text-secondary);
8719 text-align: center; border: 1px solid var(--border-light);
8720 `;
8721
8722 const totalHotspotMemory = topHotspots.reduce((sum, a) => sum + (a.size || 0), 0);
8723 const totalMemory = allocs.reduce((sum, a) => sum + (a.size || 0), 0);
8724 const hotspotPercentage = ((totalHotspotMemory / totalMemory) * 100).toFixed(1);
8725
8726 summaryDiv.innerHTML = `
8727 Top ${topHotspots.length} hotspots: <strong>${formatBytes(totalHotspotMemory)}</strong>
8728 (${hotspotPercentage}% of total memory)
8729 `;
8730
8731 container.appendChild(summaryDiv);
8732}
8733
8734// Render thread analysis
8735function renderThreadAnalysis() {
8736 const data = window.analysisData || {};
8737 const allocs = data.memory_analysis?.allocations || data.allocations || [];
8738
8739 // Update thread timeline
8740 renderThreadTimeline(allocs);
8741
8742 // Update contention analysis
8743 updateContentionAnalysis(allocs);
8744}
8745
8746// Render thread timeline
8747function renderThreadTimeline(allocs) {
8748 const container = document.getElementById('threadTimeline');
8749 if (!container) return;
8750
8751 container.innerHTML = '';
8752
8753 // Get unique threads
8754 const threads = [...new Set(allocs.map(a => a.thread_id).filter(t => t))];
8755
8756 if (threads.length <= 1) {
8757 // Single thread visualization
8758 container.innerHTML = `
8759 <div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">
8760 <div style="text-align: center;">
8761 <div style="font-size: 14px; margin-bottom: 4px;">๐งต</div>
8762 <div style="font-size: 10px;">Single Thread</div>
8763 <div style="font-size: 9px;">ThreadId(${threads[0] || 1})</div>
8764 </div>
8765 </div>
8766 `;
8767 return;
8768 }
8769
8770 // Multi-thread visualization (if applicable)
8771 threads.forEach((threadId, index) => {
8772 const threadAllocs = allocs.filter(a => a.thread_id === threadId);
8773 const threadDiv = document.createElement('div');
8774 threadDiv.style.cssText = `
8775 height: ${100/threads.length}%; display: flex; align-items: center;
8776 padding: 0 8px; border-bottom: 1px solid var(--border-light);
8777 `;
8778
8779 threadDiv.innerHTML = `
8780 <div style="width: 60px; font-size: 9px; color: var(--text-secondary);">
8781 Thread ${threadId}
8782 </div>
8783 <div style="flex: 1; height: 4px; background: var(--bg-secondary); border-radius: 2px; position: relative;">
8784 <div style="height: 100%; background: var(--primary-blue); border-radius: 2px; width: ${(threadAllocs.length / allocs.length) * 100}%;"></div>
8785 </div>
8786 <div style="width: 40px; text-align: right; font-size: 9px; color: var(--text-primary);">
8787 ${threadAllocs.length}
8788 </div>
8789 `;
8790
8791 container.appendChild(threadDiv);
8792 });
8793}
8794
8795// Update contention analysis
8796function updateContentionAnalysis(allocs) {
8797 const levelEl = document.getElementById('contention-level');
8798 const detailsEl = document.getElementById('contention-details');
8799
8800 if (!levelEl || !detailsEl) return;
8801
8802 // Calculate contention metrics
8803 const maxConcurrentBorrows = Math.max(...allocs.map(a => a.borrow_info?.max_concurrent_borrows || 0));
8804 const avgConcurrentBorrows = allocs.reduce((sum, a) => sum + (a.borrow_info?.max_concurrent_borrows || 0), 0) / allocs.length;
8805
8806 let level = 'LOW';
8807 let color = 'var(--primary-green)';
8808 let details = 'Single-threaded';
8809
8810 if (maxConcurrentBorrows > 5) {
8811 level = 'HIGH';
8812 color = 'var(--primary-red)';
8813 details = `Max ${maxConcurrentBorrows} concurrent`;
8814 } else if (maxConcurrentBorrows > 2) {
8815 level = 'MEDIUM';
8816 color = 'var(--primary-orange)';
8817 details = `Avg ${avgConcurrentBorrows.toFixed(1)} concurrent`;
8818 }
8819
8820 levelEl.textContent = level;
8821 levelEl.style.color = color;
8822 detailsEl.textContent = details;
8823}
8824
8825// Update Performance Metrics and Thread Safety Analysis
8826function updateEnhancedMetrics() {
8827 const data = window.analysisData || {};
8828 const allocs = data.memory_analysis?.allocations || data.allocations || [];
8829
8830 if (allocs.length === 0) return;
8831
8832 // Calculate Performance Metrics
8833 const totalMemory = allocs.reduce((sum, a) => sum + (a.size || 0), 0);
8834 const peakMemory = Math.max(...allocs.map(a => a.size || 0));
8835 const timestamps = allocs.map(a => a.timestamp_alloc).filter(t => t).sort((a, b) => a - b);
8836 const timeRangeNs = timestamps.length > 1 ? timestamps[timestamps.length - 1] - timestamps[0] : 1e9;
8837 const allocationRate = allocs.length / (timeRangeNs / 1e9); // allocations per second
8838 const avgLifetime = allocs.filter(a => a.lifetime_ms).reduce((sum, a) => sum + a.lifetime_ms, 0) / Math.max(1, allocs.filter(a => a.lifetime_ms).length);
8839
8840 // Simple fragmentation calculation: variance in allocation sizes
8841 const avgSize = totalMemory / allocs.length;
8842 const variance = allocs.reduce((sum, a) => sum + Math.pow((a.size || 0) - avgSize, 2), 0) / allocs.length;
8843 const fragmentation = Math.min(100, (Math.sqrt(variance) / avgSize) * 100);
8844
8845 // Update Performance Metrics
8846 const peakEl = document.getElementById('peak-memory');
8847 const rateEl = document.getElementById('allocation-rate');
8848 const lifetimeEl = document.getElementById('avg-lifetime');
8849 const fragEl = document.getElementById('fragmentation');
8850
8851 if (peakEl) peakEl.textContent = formatBytes(peakMemory);
8852 if (rateEl) rateEl.textContent = allocationRate.toFixed(1) + '/sec';
8853 if (lifetimeEl) lifetimeEl.textContent = avgLifetime.toFixed(2) + 'ms';
8854 if (fragEl) fragEl.textContent = fragmentation.toFixed(1) + '%';
8855
8856 // Calculate Thread Safety Analysis
8857 const arcCount = allocs.filter(a => (a.type_name || '').includes('Arc')).length;
8858 const rcCount = allocs.filter(a => (a.type_name || '').includes('Rc')).length;
8859 const collectionsCount = allocs.filter(a => {
8860 const type = a.type_name || '';
8861 return type.includes('HashMap') || type.includes('BTreeMap') || type.includes('Vec') || type.includes('HashSet');
8862 }).length;
8863
8864 // Update Thread Safety Analysis
8865 const arcEl = document.getElementById('arc-count');
8866 const rcEl = document.getElementById('rc-count');
8867 const collEl = document.getElementById('collections-count');
8868
8869 if (arcEl) arcEl.textContent = arcCount;
8870 if (rcEl) rcEl.textContent = rcCount;
8871 if (collEl) collEl.textContent = collectionsCount;
8872
8873 console.log('โ
Enhanced metrics updated:', {
8874 peakMemory: formatBytes(peakMemory),
8875 allocationRate: allocationRate.toFixed(1) + '/sec',
8876 avgLifetime: avgLifetime.toFixed(2) + 'ms',
8877 fragmentation: fragmentation.toFixed(1) + '%',
8878 arcCount, rcCount, collectionsCount
8879 });
8880}
8881
8882// Enhanced chart rendering with comprehensive cleanup
8883function renderEnhancedCharts() {
8884 const data = window.analysisData || {};
8885
8886 // Step 1: Destroy all Chart.js instances globally
8887 if (window.Chart && window.Chart.instances) {
8888 Object.keys(window.Chart.instances).forEach(id => {
8889 try {
8890 window.Chart.instances[id].destroy();
8891 } catch(e) {
8892 console.warn('Failed to destroy Chart.js instance:', id, e);
8893 }
8894 });
8895 }
8896
8897 // Step 2: Destroy our tracked instances
8898 if (window.chartInstances) {
8899 Object.keys(window.chartInstances).forEach(chartId => {
8900 try {
8901 window.chartInstances[chartId].destroy();
8902 delete window.chartInstances[chartId];
8903 } catch(e) {
8904 console.warn('Failed to destroy tracked chart:', chartId, e);
8905 }
8906 });
8907 }
8908 window.chartInstances = {};
8909
8910 // Step 3: Clear canvas contexts manually
8911 ['typeChart', 'timelineChart', 'ffi-risk-chart'].forEach(canvasId => {
8912 const canvas = document.getElementById(canvasId);
8913 if (canvas && canvas.getContext) {
8914 try {
8915 const ctx = canvas.getContext('2d');
8916 ctx.clearRect(0, 0, canvas.width, canvas.height);
8917 // Remove Chart.js specific properties
8918 delete canvas.chart;
8919 canvas.removeAttribute('style');
8920 } catch(e) {
8921 console.warn('Failed to clear canvas:', canvasId, e);
8922 }
8923 }
8924 });
8925
8926 // Step 4: Force garbage collection hint
8927 if (window.gc) { try { window.gc(); } catch(_) {} }
8928
8929 // Step 5: Small delay to ensure cleanup, then create new charts
8930 setTimeout(() => {
8931 initEnhancedTypeChart(data);
8932 initTimelineChart(data);
8933 }, 10);
8934}
8935
8936document.addEventListener("DOMContentLoaded", () => {
8937 console.log('๐ MemScope Dashboard Loaded');
8938
8939 // Initialize theme toggle
8940 try {
8941 initThemeToggle();
8942 console.log('โ
Theme toggle initialized');
8943 } catch(e) {
8944 console.warn('โ ๏ธ Theme toggle initialization failed:', e?.message);
8945 }
8946
8947 // Initialize main dashboard with all original functions
8948 try {
8949 // Use original dashboard functions
8950 renderKpis();
8951 renderTypeChart();
8952 renderTimelineChart();
8953 renderTreemap();
8954 renderLifetimes();
8955 updateEnhancedMetrics();
8956 renderEnhancedCharts();
8957 renderMemoryFragmentation();
8958 renderEnhancedDataInsights();
8959 renderAllocationTimelineDetail();
8960 updateLifecycleStatistics();
8961 renderMemoryHotspots();
8962 renderThreadAnalysis();
8963 populateAllocationsTable();
8964 populateUnsafeTable();
8965 renderVariableGraph();
8966 initEnhancedFFIVisualization();
8967 setupLifecycleVisualization();
8968 setupLifecycleToggle();
8969
8970 // Optional hooks (no-op if undefined)
8971 try { updateEmbeddedFFISVG && updateEmbeddedFFISVG(); } catch(_) {}
8972 try { updatePerformanceMetrics && updatePerformanceMetrics(); } catch(_) {}
8973
8974 console.log('โ
All dashboard components initialized');
8975 } catch(e) {
8976 console.error('โ Dashboard initialization failed:', e);
8977 }
8978});
8979
8980// Setup FFI flow visualization interactivity
8981function setupFFIFlowInteractivity(allocs) {
8982 // Add click handlers to FFI flow nodes
8983 setTimeout(() => {
8984 const rustNodes = document.querySelectorAll('.rust-node');
8985 const ffiNodes = document.querySelectorAll('.ffi-node');
8986
8987 [...rustNodes, ...ffiNodes].forEach(node => {
8988 node.addEventListener('click', (e) => {
8989 const ptr = e.target.getAttribute('data-ptr');
8990 const size = e.target.getAttribute('data-size');
8991 showFFIFlowNodeDetail(ptr, size, allocs);
8992 });
8993
8994 node.addEventListener('mouseover', (e) => {
8995 e.target.style.transform = 'scale(1.3)';
8996 e.target.style.filter = 'drop-shadow(0 0 8px currentColor)';
8997 });
8998
8999 node.addEventListener('mouseout', (e) => {
9000 e.target.style.transform = 'scale(1)';
9001 e.target.style.filter = 'none';
9002 });
9003 });
9004
9005 // Setup flow animation toggle
9006 const flowToggle = document.getElementById('ffi-flow-toggle');
9007 if (flowToggle) {
9008 let isAnimating = true;
9009 flowToggle.onclick = () => {
9010 const particles = document.querySelectorAll('#flow-particles circle');
9011 const flows = document.querySelectorAll('#data-flows path');
9012
9013 if (isAnimating) {
9014 // Pause animations
9015 [...particles, ...flows].forEach(el => {
9016 el.style.animationPlayState = 'paused';
9017 });
9018 flowToggle.innerHTML = '<i class="fa fa-pause"></i> Paused';
9019 flowToggle.style.background = 'var(--primary-red)';
9020 isAnimating = false;
9021 } else {
9022 // Resume animations
9023 [...particles, ...flows].forEach(el => {
9024 el.style.animationPlayState = 'running';
9025 });
9026 flowToggle.innerHTML = '<i class="fa fa-play"></i> Animate';
9027 flowToggle.style.background = 'var(--primary-green)';
9028 isAnimating = true;
9029 }
9030 };
9031 }
9032 }, 100);
9033}
9034
9035// Show FFI flow node detail
9036function showFFIFlowNodeDetail(ptr, size, allocs) {
9037 const alloc = allocs.find(a => a.ptr === ptr);
9038 if (!alloc) return;
9039
9040 const modal = document.createElement('div');
9041 modal.style.cssText = `
9042 position: fixed; top: 0; left: 0; right: 0; bottom: 0;
9043 background: rgba(0,0,0,0.6); z-index: 1000;
9044 display: flex; align-items: center; justify-content: center;
9045 `;
9046
9047 const isFFI = alloc.ffi_tracked;
9048 const bgGradient = isFFI ? 'linear-gradient(135deg, #1e40af, #3b82f6)' : 'linear-gradient(135deg, #ea580c, #f97316)';
9049 const icon = isFFI ? 'โ๏ธ' : '๐ฆ';
9050 const title = isFFI ? 'FFI Allocation' : 'Rust Allocation';
9051
9052 modal.innerHTML = `
9053 <div style="background: ${bgGradient}; border-radius: 16px; padding: 24px; min-width: 400px; color: white; position: relative; overflow: hidden;">
9054 <div style="position: absolute; top: -50px; right: -50px; font-size: 120px; opacity: 0.1;">${icon}</div>
9055 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; position: relative; z-index: 1;">
9056 <h3 style="margin: 0; font-size: 18px;">${icon} ${title}</h3>
9057 <button onclick="this.closest('div').parentNode.remove()" style="background: rgba(255,255,255,0.2); border: none; border-radius: 50%; width: 32px; height: 32px; color: white; cursor: pointer; font-size: 16px;">ร</button>
9058 </div>
9059 <div style="position: relative; z-index: 1; line-height: 1.8;">
9060 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
9061 <div style="background: rgba(255,255,255,0.1); padding: 12px; border-radius: 8px;">
9062 <div style="font-size: 12px; opacity: 0.8;">Variable</div>
9063 <div style="font-weight: 600;">${alloc.var_name || 'unnamed'}</div>
9064 </div>
9065 <div style="background: rgba(255,255,255,0.1); padding: 12px; border-radius: 8px;">
9066 <div style="font-size: 12px; opacity: 0.8;">Size</div>
9067 <div style="font-weight: 600; font-size: 16px;">${formatBytes(alloc.size || 0)}</div>
9068 </div>
9069 </div>
9070 <div style="background: rgba(255,255,255,0.1); padding: 12px; border-radius: 8px; margin-bottom: 16px;">
9071 <div style="font-size: 12px; opacity: 0.8; margin-bottom: 4px;">Type</div>
9072 <div style="font-weight: 600; font-size: 14px; word-break: break-all;">${alloc.type_name || 'Unknown'}</div>
9073 </div>
9074 <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px;">
9075 <div style="text-align: center; background: rgba(255,255,255,0.1); padding: 8px; border-radius: 6px;">
9076 <div style="font-size: 16px; font-weight: 700;">${(alloc.borrow_info?.immutable_borrows || 0) + (alloc.borrow_info?.mutable_borrows || 0)}</div>
9077 <div style="font-size: 10px; opacity: 0.8;">Borrows</div>
9078 </div>
9079 <div style="text-align: center; background: rgba(255,255,255,0.1); padding: 8px; border-radius: 6px;">
9080 <div style="font-size: 16px; font-weight: 700;">${alloc.clone_info?.clone_count || 0}</div>
9081 <div style="font-size: 10px; opacity: 0.8;">Clones</div>
9082 </div>
9083 <div style="text-align: center; background: rgba(255,255,255,0.1); padding: 8px; border-radius: 6px;">
9084 <div style="font-size: 16px; font-weight: 700;">${alloc.thread_id || 1}</div>
9085 <div style="font-size: 10px; opacity: 0.8;">Thread</div>
9086 </div>
9087 </div>
9088 </div>
9089 </div>
9090 `;
9091
9092 document.body.appendChild(modal);
9093 modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
9094}
9095
9096"#;