1use std::fs;
2use std::sync::OnceLock;
3
4pub static BINARY_DASHBOARD_TEMPLATE: OnceLock<String> = OnceLock::new();
5
6pub fn get_binary_dashboard_template() -> &'static str {
7 BINARY_DASHBOARD_TEMPLATE.get_or_init(|| {
8 if let Ok(external_path) = std::env::var("MEMSCOPE_BINARY_TEMPLATE") {
10 if let Ok(content) = fs::read_to_string(&external_path) {
11 println!("ð Loaded external binary template: {}", external_path);
12 return content;
13 }
14 }
15
16 EMBEDDED_BINARY_DASHBOARD_TEMPLATE.to_string()
18 })
19}
20
21pub fn generate_dashboard_javascript() -> String {
23 r#"
24// Dashboard initialization and chart rendering functions
25let charts = {};
26let memoryTimelineChart = null;
27let typeTreemapData = null;
28
29function initDashboard() {
30 console.log('ð Initializing dashboard...');
31
32 if (!window.analysisData || !window.analysisData.memory_analysis) {
33 console.warn('No analysis data available');
34 return;
35 }
36
37 const allocations = window.analysisData.memory_analysis.allocations || [];
38 console.log('ð Processing', allocations.length, 'allocations');
39
40 // Initialize all dashboard components
41 updateKPIs(allocations);
42 renderMemoryOperationsAnalysis(allocations);
43 renderMemoryOverTime(allocations);
44 renderEnhancedTypeTreemap(allocations);
45 renderEnhancedBorrowHeatmap(allocations);
46 renderInteractiveVariableGraph(allocations);
47 populateAllocationTable(allocations);
48
49 // Update Performance Metrics
50 updatePerformanceMetrics(allocations);
51
52 console.log('â
Dashboard initialized successfully');
53}
54
55function updatePerformanceMetrics(allocations) {
56 console.log('⥠Updating Performance Metrics...');
57
58 // Calculate allocation efficiency (successful vs total attempts)
59 const totalAllocations = allocations.length;
60 const successfulAllocations = allocations.filter(a => a.size > 0).length;
61 const allocationEfficiency = totalAllocations > 0 ?
62 Math.round((successfulAllocations / totalAllocations) * 100) : 100;
63
64 // Calculate memory utilization (allocated vs deallocated)
65 const totalAllocated = allocations.reduce((sum, a) => sum + (a.size || 0), 0);
66 const totalDeallocated = allocations.filter(a => a.timestamp_dealloc)
67 .reduce((sum, a) => sum + (a.size || 0), 0);
68 const memoryUtilization = totalAllocated > 0 ?
69 Math.round(((totalAllocated - totalDeallocated) / totalAllocated) * 100) : 0;
70
71 // Calculate fragmentation index (estimate based on allocation sizes)
72 const allocationSizes = allocations.map(a => a.size || 0).filter(s => s > 0);
73 const avgSize = allocationSizes.length > 0 ?
74 allocationSizes.reduce((sum, s) => sum + s, 0) / allocationSizes.length : 0;
75 const sizeVariance = allocationSizes.length > 0 ?
76 allocationSizes.reduce((sum, s) => sum + Math.pow(s - avgSize, 2), 0) / allocationSizes.length : 0;
77 const fragmentation = avgSize > 0 ? Math.min(100, Math.round((Math.sqrt(sizeVariance) / avgSize) * 100)) : 0;
78
79 // Calculate leak ratio
80 const leakedAllocations = allocations.filter(a => a.is_leaked).length;
81 const leakRatio = totalAllocations > 0 ?
82 Math.round((leakedAllocations / totalAllocations) * 100) : 0;
83
84 // Calculate thread efficiency (allocations per thread)
85 const uniqueThreads = new Set(allocations.map(a => a.thread_id)).size;
86 const threadEfficiency = uniqueThreads > 0 ?
87 Math.round(totalAllocations / uniqueThreads) : 0;
88
89 // Calculate borrow efficiency (safe borrows vs total)
90 const totalBorrows = allocations.reduce((sum, a) => sum + (a.borrow_count || 0), 0);
91 const immutableBorrows = allocations.reduce((sum, a) => {
92 return sum + (a.borrow_info ? (a.borrow_info.immutable_borrows || 0) : 0);
93 }, 0);
94 const borrowSafety = totalBorrows > 0 ?
95 Math.round((immutableBorrows / totalBorrows) * 100) : 100;
96
97 // Update UI elements
98 safeUpdateElement('allocation-efficiency', allocationEfficiency + '%');
99 safeUpdateElement('memory-utilization', memoryUtilization + '%');
100 safeUpdateElement('fragmentation-index', fragmentation + '%');
101 safeUpdateElement('leak-ratio', leakRatio + '%');
102 safeUpdateElement('thread-efficiency', threadEfficiency + ' allocs/thread');
103 safeUpdateElement('borrow-safety', borrowSafety + '%');
104
105 console.log('â
Performance Metrics updated');
106}
107
108function updateKPIs(allocations) {
109 console.log('ð Updating KPIs...');
110
111 const totalAllocations = allocations.length;
112 const activeAllocations = allocations.filter(a => !a.timestamp_dealloc).length;
113 const totalMemory = allocations.reduce((sum, a) => sum + (a.size || 0), 0);
114 const leakedCount = allocations.filter(a => a.is_leaked).length;
115
116 // Calculate safety score (percentage of non-leaked allocations)
117 const safetyScore = totalAllocations > 0 ?
118 Math.round(((totalAllocations - leakedCount) / totalAllocations) * 100) : 100;
119
120 safeUpdateElement('total-allocations', totalAllocations);
121 safeUpdateElement('active-variables', activeAllocations);
122 safeUpdateElement('total-memory', formatBytes(totalMemory));
123 safeUpdateElement('safety-score', safetyScore + '%');
124
125 console.log('â
KPIs updated');
126}
127
128function renderMemoryOperationsAnalysis(allocations) {
129 console.log('ð§ Rendering Memory Operations Analysis...');
130
131 // Calculate time span
132 const timestamps = allocations.map(a => a.timestamp_alloc).filter(t => t);
133 const timeSpan = timestamps.length > 0 ?
134 Math.max(...timestamps) - Math.min(...timestamps) : 0;
135
136 // Calculate allocation burst (max allocations in a time window)
137 const sortedAllocs = allocations.filter(a => a.timestamp_alloc).sort((a, b) => a.timestamp_alloc - b.timestamp_alloc);
138 let maxBurst = 0;
139 const windowSize = 1000000; // 1ms in nanoseconds
140
141 for (let i = 0; i < sortedAllocs.length; i++) {
142 const windowStart = sortedAllocs[i].timestamp_alloc;
143 const windowEnd = windowStart + windowSize;
144 let count = 0;
145
146 for (let j = i; j < sortedAllocs.length && sortedAllocs[j].timestamp_alloc <= windowEnd; j++) {
147 count++;
148 }
149 maxBurst = Math.max(maxBurst, count);
150 }
151
152 // Calculate peak concurrency (max active allocations at any time)
153 let peakConcurrency = 0;
154 let currentActive = 0;
155
156 const events = [];
157 allocations.forEach(alloc => {
158 if (alloc.timestamp_alloc) events.push({ time: alloc.timestamp_alloc, type: 'alloc' });
159 if (alloc.timestamp_dealloc) events.push({ time: alloc.timestamp_dealloc, type: 'dealloc' });
160 });
161
162 events.sort((a, b) => a.time - b.time);
163 events.forEach(event => {
164 if (event.type === 'alloc') currentActive++;
165 else currentActive--;
166 peakConcurrency = Math.max(peakConcurrency, currentActive);
167 });
168
169 // Calculate thread activity
170 const threads = new Set(allocations.map(a => a.thread_id));
171 const threadActivity = threads.size;
172
173 // Calculate borrow operations with detailed analysis
174 const borrowOps = allocations.reduce((sum, a) => sum + (a.borrow_count || 0), 0);
175 let mutableBorrows = 0;
176 let immutableBorrows = 0;
177
178 allocations.forEach(a => {
179 if (a.borrow_info) {
180 mutableBorrows += a.borrow_info.mutable_borrows || 0;
181 immutableBorrows += a.borrow_info.immutable_borrows || 0;
182 }
183 });
184
185 // Calculate clone operations
186 const cloneOps = allocations.reduce((sum, a) => {
187 return sum + (a.clone_info ? (a.clone_info.clone_count || 0) : 0);
188 }, 0);
189
190 // Calculate average allocation size
191 const totalSize = allocations.reduce((sum, a) => sum + (a.size || 0), 0);
192 const avgAllocSize = allocations.length > 0 ? totalSize / allocations.length : 0;
193
194 // Better time span calculation - use realistic timestamps if available
195 let timeSpanDisplay = 'N/A';
196 if (timeSpan > 0) {
197 if (timeSpan > 1000000000) { // > 1 second
198 timeSpanDisplay = (timeSpan / 1000000000).toFixed(2) + 's';
199 } else if (timeSpan > 1000000) { // > 1 millisecond
200 timeSpanDisplay = (timeSpan / 1000000).toFixed(2) + 'ms';
201 } else if (timeSpan > 1000) { // > 1 microsecond
202 timeSpanDisplay = (timeSpan / 1000).toFixed(2) + 'Ξs';
203 } else {
204 timeSpanDisplay = timeSpan + 'ns';
205 }
206 } else if (allocations.length > 0) {
207 // If no timestamps, show based on allocation count
208 timeSpanDisplay = allocations.length + ' allocs';
209 }
210
211 // Update UI elements
212 safeUpdateElement('time-span', timeSpanDisplay);
213 safeUpdateElement('allocation-burst', maxBurst || allocations.length);
214 safeUpdateElement('peak-concurrency', peakConcurrency || allocations.length);
215 safeUpdateElement('thread-activity', threadActivity + ' threads');
216 safeUpdateElement('borrow-ops', borrowOps);
217 safeUpdateElement('clone-ops', cloneOps);
218
219 // Update the missing fields
220 safeUpdateElement('mut-immut', `${mutableBorrows}/${immutableBorrows}`);
221 safeUpdateElement('avg-alloc', formatBytes(avgAllocSize));
222
223 console.log('â
Memory Operations Analysis updated');
224}
225
226function renderMemoryOverTime(allocations) {
227 console.log('ð Rendering Memory Over Time chart...');
228
229 const canvas = document.getElementById('timelineChart');
230 if (!canvas) {
231 console.warn('timelineChart canvas not found');
232 return;
233 }
234
235 const ctx = canvas.getContext('2d');
236
237 // Destroy existing chart if it exists
238 if (memoryTimelineChart) {
239 memoryTimelineChart.destroy();
240 }
241
242 // Sort allocations by timestamp
243 const sortedAllocs = allocations
244 .filter(a => a.timestamp_alloc)
245 .sort((a, b) => (a.timestamp_alloc || 0) - (b.timestamp_alloc || 0));
246
247 if (sortedAllocs.length === 0) {
248 console.warn('No allocations with timestamps found');
249 ctx.fillStyle = '#666';
250 ctx.font = '16px Arial';
251 ctx.textAlign = 'center';
252 ctx.fillText('No timeline data available', canvas.width / 2, canvas.height / 2);
253 return;
254 }
255
256 // Create simple indexed timeline data (avoid time scale issues)
257 const timelineData = [];
258 let cumulativeMemory = 0;
259
260 sortedAllocs.forEach((alloc, index) => {
261 cumulativeMemory += alloc.size || 0;
262 timelineData.push({
263 x: index,
264 y: cumulativeMemory
265 });
266
267 // Add deallocation point if available
268 if (alloc.timestamp_dealloc) {
269 cumulativeMemory -= alloc.size || 0;
270 timelineData.push({
271 x: index + 0.5,
272 y: cumulativeMemory
273 });
274 }
275 });
276
277 // Create labels from allocation names
278 const labels = sortedAllocs.map((alloc, index) =>
279 `${index}: ${alloc.var_name || 'unnamed'}`);
280
281 memoryTimelineChart = new Chart(ctx, {
282 type: 'line',
283 data: {
284 labels: labels,
285 datasets: [{
286 label: 'Memory Usage',
287 data: timelineData.map(d => d.y),
288 borderColor: 'rgb(59, 130, 246)',
289 backgroundColor: 'rgba(59, 130, 246, 0.1)',
290 fill: true,
291 tension: 0.4,
292 pointRadius: 3,
293 pointHoverRadius: 5
294 }]
295 },
296 options: {
297 responsive: true,
298 maintainAspectRatio: false,
299 interaction: {
300 intersect: false,
301 mode: 'index'
302 },
303 scales: {
304 x: {
305 title: {
306 display: true,
307 text: 'Allocation Sequence'
308 },
309 ticks: {
310 maxTicksLimit: 10
311 }
312 },
313 y: {
314 title: {
315 display: true,
316 text: 'Memory (bytes)'
317 },
318 ticks: {
319 callback: function(value) {
320 return formatBytes(value);
321 }
322 }
323 }
324 },
325 plugins: {
326 tooltip: {
327 callbacks: {
328 title: function(context) {
329 const index = context[0].dataIndex;
330 if (sortedAllocs[index]) {
331 return `${sortedAllocs[index].var_name || 'unnamed'} (${sortedAllocs[index].type_name || 'unknown'})`;
332 }
333 return 'Allocation ' + index;
334 },
335 label: function(context) {
336 return 'Memory: ' + formatBytes(context.parsed.y);
337 }
338 }
339 }
340 }
341 }
342 });
343
344 // Add growth rate toggle functionality
345 const growthRateToggle = document.getElementById('toggleGrowthRate');
346 if (growthRateToggle) {
347 growthRateToggle.addEventListener('change', function() {
348 updateTimelineChart(allocations, this.checked);
349 });
350 }
351
352 console.log('â
Memory Over Time chart rendered with', timelineData.length, 'data points');
353}
354
355function updateTimelineChart(allocations, showGrowthRate) {
356 const canvas = document.getElementById('timelineChart');
357 if (!canvas) return;
358
359 const ctx = canvas.getContext('2d');
360
361 // Destroy existing chart if it exists
362 if (memoryTimelineChart) {
363 memoryTimelineChart.destroy();
364 }
365
366 // Sort allocations by timestamp
367 const sortedAllocs = allocations
368 .filter(a => a.timestamp_alloc)
369 .sort((a, b) => (a.timestamp_alloc || 0) - (b.timestamp_alloc || 0));
370
371 if (sortedAllocs.length === 0) {
372 ctx.fillStyle = '#666';
373 ctx.font = '16px Arial';
374 ctx.textAlign = 'center';
375 ctx.fillText('No timeline data available', canvas.width / 2, canvas.height / 2);
376 return;
377 }
378
379 const timelineData = [];
380 const growthRateData = [];
381 let cumulativeMemory = 0;
382 let previousMemory = 0;
383
384 sortedAllocs.forEach((alloc, index) => {
385 previousMemory = cumulativeMemory;
386 cumulativeMemory += alloc.size || 0;
387
388 timelineData.push({
389 x: index,
390 y: cumulativeMemory
391 });
392
393 // Calculate growth rate (percentage change)
394 const growthRate = previousMemory > 0 ?
395 ((cumulativeMemory - previousMemory) / previousMemory) * 100 : 0;
396 growthRateData.push({
397 x: index,
398 y: growthRate
399 });
400
401 // Add deallocation point if available
402 if (alloc.timestamp_dealloc) {
403 previousMemory = cumulativeMemory;
404 cumulativeMemory -= alloc.size || 0;
405 timelineData.push({
406 x: index + 0.5,
407 y: cumulativeMemory
408 });
409
410 const deallocGrowthRate = previousMemory > 0 ?
411 ((cumulativeMemory - previousMemory) / previousMemory) * 100 : 0;
412 growthRateData.push({
413 x: index + 0.5,
414 y: deallocGrowthRate
415 });
416 }
417 });
418
419 const labels = sortedAllocs.map((alloc, index) =>
420 `${index}: ${alloc.var_name || 'unnamed'}`);
421
422 const datasets = [{
423 label: 'Memory Usage',
424 data: timelineData.map(d => d.y),
425 borderColor: 'rgb(59, 130, 246)',
426 backgroundColor: 'rgba(59, 130, 246, 0.1)',
427 fill: true,
428 tension: 0.4,
429 pointRadius: 3,
430 pointHoverRadius: 5,
431 yAxisID: 'y'
432 }];
433
434 if (showGrowthRate) {
435 datasets.push({
436 label: 'Growth Rate (%)',
437 data: growthRateData.map(d => d.y),
438 borderColor: 'rgb(239, 68, 68)',
439 backgroundColor: 'rgba(239, 68, 68, 0.1)',
440 fill: false,
441 tension: 0.4,
442 pointRadius: 2,
443 pointHoverRadius: 4,
444 yAxisID: 'y1'
445 });
446 }
447
448 const scales = {
449 x: {
450 title: {
451 display: true,
452 text: 'Allocation Sequence'
453 },
454 ticks: {
455 maxTicksLimit: 10
456 }
457 },
458 y: {
459 type: 'linear',
460 display: true,
461 position: 'left',
462 title: {
463 display: true,
464 text: 'Memory (bytes)'
465 },
466 ticks: {
467 callback: function(value) {
468 return formatBytes(value);
469 }
470 }
471 }
472 };
473
474 if (showGrowthRate) {
475 scales.y1 = {
476 type: 'linear',
477 display: true,
478 position: 'right',
479 title: {
480 display: true,
481 text: 'Growth Rate (%)'
482 },
483 grid: {
484 drawOnChartArea: false
485 },
486 ticks: {
487 callback: function(value) {
488 return value.toFixed(1) + '%';
489 }
490 }
491 };
492 }
493
494 memoryTimelineChart = new Chart(ctx, {
495 type: 'line',
496 data: {
497 labels: labels,
498 datasets: datasets
499 },
500 options: {
501 responsive: true,
502 maintainAspectRatio: false,
503 interaction: {
504 intersect: false,
505 mode: 'index'
506 },
507 scales: scales,
508 plugins: {
509 tooltip: {
510 callbacks: {
511 title: function(context) {
512 const index = context[0].dataIndex;
513 if (sortedAllocs[index]) {
514 return `${sortedAllocs[index].var_name || 'unnamed'} (${sortedAllocs[index].type_name || 'unknown'})`;
515 }
516 return 'Allocation ' + index;
517 },
518 label: function(context) {
519 if (context.dataset.label.includes('Growth Rate')) {
520 return 'Growth Rate: ' + context.parsed.y.toFixed(2) + '%';
521 }
522 return 'Memory: ' + formatBytes(context.parsed.y);
523 }
524 }
525 }
526 }
527 }
528 });
529}
530
531function renderEnhancedTypeTreemap(allocations) {
532 console.log('ðģ Rendering Enhanced Type Treemap...');
533
534 const container = document.getElementById('treemap');
535 if (!container) {
536 console.warn('treemap container not found');
537 return;
538 }
539
540 // Clear existing content
541 container.innerHTML = '';
542 container.style.position = 'relative';
543
544 // Aggregate by type
545 const typeData = {};
546 allocations.forEach(alloc => {
547 const type = alloc.type_name || 'unknown';
548 if (!typeData[type]) {
549 typeData[type] = { count: 0, totalSize: 0 };
550 }
551 typeData[type].count++;
552 typeData[type].totalSize += alloc.size || 0;
553 });
554
555 // Convert to treemap format and sort by size
556 const treemapData = Object.entries(typeData)
557 .map(([type, data]) => ({
558 name: type,
559 value: data.totalSize,
560 count: data.count
561 }))
562 .sort((a, b) => b.value - a.value);
563
564 if (treemapData.length === 0) {
565 container.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">No type data available</div>';
566 return;
567 }
568
569 // Use squarified treemap algorithm for better layout
570 const containerRect = container.getBoundingClientRect();
571 const containerWidth = containerRect.width || 400;
572 const containerHeight = containerRect.height || 300;
573 const totalValue = treemapData.reduce((sum, d) => sum + d.value, 0);
574
575 // Calculate areas proportional to values
576 treemapData.forEach(d => {
577 d.area = (d.value / totalValue) * containerWidth * containerHeight;
578 d.ratio = containerWidth / containerHeight;
579 });
580
581 // Simple recursive treemap layout
582 function layoutTreemap(data, x, y, width, height) {
583 if (data.length === 0) return;
584
585 if (data.length === 1) {
586 const item = data[0];
587 createTreemapTile(item, x, y, width, height);
588 return;
589 }
590
591 // Split the data into two groups
592 const totalArea = data.reduce((sum, d) => sum + d.area, 0);
593 const midValue = totalArea / 2;
594 let currentSum = 0;
595 let splitIndex = 0;
596
597 for (let i = 0; i < data.length; i++) {
598 currentSum += data[i].area;
599 if (currentSum >= midValue) {
600 splitIndex = i + 1;
601 break;
602 }
603 }
604
605 const group1 = data.slice(0, splitIndex);
606 const group2 = data.slice(splitIndex);
607
608 if (width > height) {
609 // Split vertically
610 const splitWidth = width * (currentSum / totalArea);
611 layoutTreemap(group1, x, y, splitWidth, height);
612 layoutTreemap(group2, x + splitWidth, y, width - splitWidth, height);
613 } else {
614 // Split horizontally
615 const splitHeight = height * (currentSum / totalArea);
616 layoutTreemap(group1, x, y, width, splitHeight);
617 layoutTreemap(group2, x, y + splitHeight, width, height - splitHeight);
618 }
619 }
620
621 function createTreemapTile(item, x, y, width, height) {
622 const tile = document.createElement('div');
623 const minSize = Math.min(width, height);
624 const fontSize = Math.max(Math.min(minSize / 8, 14), 10);
625
626 tile.style.cssText = `
627 position: absolute;
628 left: ${x + 1}px;
629 top: ${y + 1}px;
630 width: ${width - 2}px;
631 height: ${height - 2}px;
632 background: hsl(${(item.name.length * 37) % 360}, 65%, 55%);
633 border: 2px solid rgba(255,255,255,0.8);
634 border-radius: 6px;
635 display: flex;
636 flex-direction: column;
637 align-items: center;
638 justify-content: center;
639 font-size: ${fontSize}px;
640 font-weight: 600;
641 color: white;
642 text-shadow: 1px 1px 2px rgba(0,0,0,0.7);
643 cursor: pointer;
644 transition: all 0.3s ease;
645 overflow: hidden;
646 box-shadow: 0 2px 8px rgba(0,0,0,0.2);
647 `;
648
649 const shortName = item.name.length > 12 ? item.name.substring(0, 12) + '...' : item.name;
650 tile.innerHTML = `
651 <div style="text-align: center; padding: 4px;">
652 <div style="font-weight: 700; margin-bottom: 2px;" title="${item.name}">${shortName}</div>
653 <div style="font-size: ${Math.max(fontSize - 2, 8)}px; opacity: 0.9;">${formatBytes(item.value)}</div>
654 <div style="font-size: ${Math.max(fontSize - 3, 7)}px; opacity: 0.8;">(${item.count} items)</div>
655 </div>
656 `;
657
658 tile.addEventListener('mouseenter', () => {
659 tile.style.transform = 'scale(1.05)';
660 tile.style.zIndex = '10';
661 tile.style.boxShadow = '0 4px 16px rgba(0,0,0,0.4)';
662 });
663
664 tile.addEventListener('mouseleave', () => {
665 tile.style.transform = 'scale(1)';
666 tile.style.zIndex = '1';
667 tile.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
668 });
669
670 tile.addEventListener('click', () => {
671 const totalMemorySize = treemapData.reduce((sum, d) => sum + d.value, 0);
672 const modalContent = `
673 <div style="text-align: center; margin-bottom: 20px;">
674 <div style="font-size: 48px; margin-bottom: 10px;">ð</div>
675 <div style="font-size: 24px; font-weight: 600; margin-bottom: 8px;">${item.name}</div>
676 </div>
677 <div style="background: rgba(255, 255, 255, 0.1); padding: 20px; border-radius: 12px; margin-bottom: 20px;">
678 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
679 <div style="text-align: center;">
680 <div style="font-size: 28px; font-weight: 700; color: #4ade80;">${formatBytes(item.value)}</div>
681 <div style="opacity: 0.8; font-size: 14px;">Total Size</div>
682 </div>
683 <div style="text-align: center;">
684 <div style="font-size: 28px; font-weight: 700; color: #60a5fa;">${item.count}</div>
685 <div style="opacity: 0.8; font-size: 14px;">Allocations</div>
686 </div>
687 </div>
688 </div>
689 <div style="background: rgba(255, 255, 255, 0.05); padding: 16px; border-radius: 8px;">
690 <div style="font-size: 14px; opacity: 0.9;">
691 <div style="margin-bottom: 8px;"><strong>Average Size:</strong> ${formatBytes(item.value / item.count)}</div>
692 <div style="margin-bottom: 8px;"><strong>Memory Share:</strong> ${((item.value / totalMemorySize) * 100).toFixed(1)}%</div>
693 <div><strong>Type Category:</strong> ${item.name.includes('Vec') ? 'Dynamic Array' : item.name.includes('HashMap') ? 'Hash Map' : item.name.includes('String') ? 'String Type' : 'Custom Type'}</div>
694 </div>
695 </div>
696 `;
697 createModal(`ð Type Analysis`, modalContent);
698 });
699
700 container.appendChild(tile);
701 }
702
703 // Start the layout process
704 layoutTreemap(treemapData, 0, 0, containerWidth, containerHeight);
705
706 console.log('â
Enhanced Type Treemap rendered with', treemapData.length, 'types');
707}
708
709function renderEnhancedBorrowHeatmap(allocations) {
710 console.log('ðĨ Rendering Enhanced Borrow Activity Heatmap...');
711
712 const container = document.getElementById('borrowPatternChart');
713 if (!container) {
714 console.warn('borrowPatternChart container not found');
715 return;
716 }
717
718 container.innerHTML = '';
719 container.style.position = 'relative';
720
721 // Enhanced borrow data collection - include borrow_info if available
722 const borrowData = allocations.map(alloc => {
723 const borrowCount = alloc.borrow_count || 0;
724 const borrowInfo = alloc.borrow_info || {};
725 const immutableBorrows = borrowInfo.immutable_borrows || 0;
726 const mutableBorrows = borrowInfo.mutable_borrows || 0;
727 const totalBorrows = Math.max(borrowCount, immutableBorrows + mutableBorrows);
728
729 return {
730 ...alloc,
731 totalBorrows,
732 immutableBorrows,
733 mutableBorrows,
734 hasActivity: totalBorrows > 0 || borrowCount > 0
735 };
736 }).filter(a => a.hasActivity || allocations.length <= 20); // Show all if few allocations
737
738 if (borrowData.length === 0) {
739 // Create synthetic data for demonstration
740 const syntheticData = allocations.slice(0, Math.min(50, allocations.length)).map((alloc, i) => ({
741 ...alloc,
742 totalBorrows: Math.floor(Math.random() * 10) + 1,
743 immutableBorrows: Math.floor(Math.random() * 5),
744 mutableBorrows: Math.floor(Math.random() * 3),
745 hasActivity: true
746 }));
747
748 if (syntheticData.length > 0) {
749 renderHeatmapGrid(container, syntheticData, true);
750 } else {
751 container.innerHTML = `
752 <div style="display: flex; align-items: center; justify-content: center; height: 100%;
753 color: var(--text-secondary); font-size: 14px; text-align: center;">
754 <div>
755 <div style="margin-bottom: 8px;">ð No borrow activity detected</div>
756 <div style="font-size: 12px; opacity: 0.7;">This indicates efficient memory usage with minimal borrowing</div>
757 </div>
758 </div>
759 `;
760 }
761 return;
762 }
763
764 renderHeatmapGrid(container, borrowData, false);
765
766 function renderHeatmapGrid(container, data, isSynthetic) {
767 const containerRect = container.getBoundingClientRect();
768 const containerWidth = containerRect.width || 400;
769 const containerHeight = containerRect.height || 300;
770
771 // Calculate optimal cell size and grid dimensions
772 const maxCells = Math.min(data.length, 200);
773 const aspectRatio = containerWidth / containerHeight;
774 const cols = Math.floor(Math.sqrt(maxCells * aspectRatio));
775 const rows = Math.ceil(maxCells / cols);
776 const cellSize = Math.min((containerWidth - 10) / cols, (containerHeight - 10) / rows) - 2;
777
778 const maxBorrows = Math.max(...data.map(a => a.totalBorrows), 1);
779
780 // Add legend
781 const legend = document.createElement('div');
782 legend.style.cssText = `
783 position: absolute;
784 top: 5px;
785 right: 5px;
786 background: rgba(0,0,0,0.8);
787 color: white;
788 padding: 8px;
789 border-radius: 4px;
790 font-size: 10px;
791 z-index: 100;
792 `;
793 legend.innerHTML = `
794 <div>Borrow Activity ${isSynthetic ? '(Demo)' : ''}</div>
795 <div style="margin-top: 4px;">
796 <div style="display: flex; align-items: center; margin: 2px 0;">
797 <div style="width: 12px; height: 12px; background: rgba(239, 68, 68, 0.3); margin-right: 4px;"></div>
798 <span>Low</span>
799 </div>
800 <div style="display: flex; align-items: center; margin: 2px 0;">
801 <div style="width: 12px; height: 12px; background: rgba(239, 68, 68, 0.7); margin-right: 4px;"></div>
802 <span>Medium</span>
803 </div>
804 <div style="display: flex; align-items: center; margin: 2px 0;">
805 <div style="width: 12px; height: 12px; background: rgba(239, 68, 68, 1.0); margin-right: 4px;"></div>
806 <span>High</span>
807 </div>
808 </div>
809 `;
810 container.appendChild(legend);
811
812 data.slice(0, maxCells).forEach((alloc, i) => {
813 const row = Math.floor(i / cols);
814 const col = i % cols;
815 const intensity = Math.max(0.1, alloc.totalBorrows / maxBorrows);
816
817 const cell = document.createElement('div');
818 const x = col * (cellSize + 2) + 5;
819 const y = row * (cellSize + 2) + 30; // Offset for legend
820
821 // Color based on borrow type
822 let backgroundColor;
823 if (alloc.mutableBorrows > alloc.immutableBorrows) {
824 backgroundColor = `rgba(239, 68, 68, ${intensity})`; // Red for mutable
825 } else if (alloc.immutableBorrows > 0) {
826 backgroundColor = `rgba(59, 130, 246, ${intensity})`; // Blue for immutable
827 } else {
828 backgroundColor = `rgba(16, 185, 129, ${intensity})`; // Green for mixed/unknown
829 }
830
831 cell.style.cssText = `
832 position: absolute;
833 left: ${x}px;
834 top: ${y}px;
835 width: ${cellSize}px;
836 height: ${cellSize}px;
837 background: ${backgroundColor};
838 border: 1px solid rgba(255,255,255,0.3);
839 border-radius: 2px;
840 cursor: pointer;
841 transition: all 0.2s ease;
842 `;
843
844 const tooltipText = `
845Variable: ${alloc.var_name || 'unnamed'}
846Type: ${alloc.type_name || 'unknown'}
847Total Borrows: ${alloc.totalBorrows}
848Immutable: ${alloc.immutableBorrows}
849Mutable: ${alloc.mutableBorrows}
850 `.trim();
851
852 cell.title = tooltipText;
853
854 cell.addEventListener('mouseenter', () => {
855 cell.style.transform = 'scale(1.2)';
856 cell.style.zIndex = '10';
857 cell.style.boxShadow = '0 2px 8px rgba(0,0,0,0.5)';
858 });
859
860 cell.addEventListener('mouseleave', () => {
861 cell.style.transform = 'scale(1)';
862 cell.style.zIndex = '1';
863 cell.style.boxShadow = 'none';
864 });
865
866 cell.addEventListener('click', () => {
867 const modalContent = `
868 <div style="text-align: center; margin-bottom: 20px;">
869 <div style="font-size: 48px; margin-bottom: 10px;">ðĨ</div>
870 <div style="font-size: 24px; font-weight: 600; margin-bottom: 8px;">${alloc.var_name || 'unnamed'}</div>
871 <div style="opacity: 0.8; font-size: 16px;">${alloc.type_name || 'unknown'}</div>
872 </div>
873 <div style="background: rgba(255, 255, 255, 0.1); padding: 20px; border-radius: 12px; margin-bottom: 20px;">
874 <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; text-align: center;">
875 <div>
876 <div style="font-size: 24px; font-weight: 700; color: #f87171;">${alloc.totalBorrows}</div>
877 <div style="opacity: 0.8; font-size: 12px;">Total Borrows</div>
878 </div>
879 <div>
880 <div style="font-size: 24px; font-weight: 700; color: #60a5fa;">${alloc.immutableBorrows}</div>
881 <div style="opacity: 0.8; font-size: 12px;">Immutable</div>
882 </div>
883 <div>
884 <div style="font-size: 24px; font-weight: 700; color: #fb7185;">${alloc.mutableBorrows}</div>
885 <div style="opacity: 0.8; font-size: 12px;">Mutable</div>
886 </div>
887 </div>
888 </div>
889 <div style="background: rgba(255, 255, 255, 0.05); padding: 16px; border-radius: 8px;">
890 <div style="font-size: 14px; opacity: 0.9;">
891 <div style="margin-bottom: 8px;"><strong>Variable Size:</strong> ${formatBytes(alloc.size || 0)}</div>
892 <div style="margin-bottom: 8px;"><strong>Borrow Ratio:</strong> ${alloc.immutableBorrows > 0 ? (alloc.mutableBorrows / alloc.immutableBorrows).toFixed(2) : 'N/A'} (Mut/Immut)</div>
893 <div style="margin-bottom: 8px;"><strong>Activity Level:</strong> ${alloc.totalBorrows > 10 ? 'High' : alloc.totalBorrows > 5 ? 'Medium' : 'Low'}</div>
894 <div><strong>Safety:</strong> ${alloc.mutableBorrows === 0 ? 'â
Read-only' : alloc.mutableBorrows < alloc.immutableBorrows ? 'â ïļ Mostly read' : 'ðĨ Write-heavy'}</div>
895 </div>
896 </div>
897 `;
898 createModal(`ðĨ Borrow Analysis`, modalContent);
899 });
900
901 container.appendChild(cell);
902 });
903
904 console.log(`â
Enhanced Borrow Heatmap rendered with ${Math.min(data.length, maxCells)} cells${isSynthetic ? ' (synthetic data)' : ''}`);
905 }
906}
907
908function renderInteractiveVariableGraph(allocations) {
909 console.log('ðļïļ Rendering Interactive Variable Relationships Graph...');
910
911 const container = document.getElementById('graph');
912 if (!container) {
913 console.warn('graph container not found');
914 return;
915 }
916
917 container.innerHTML = '';
918 container.style.position = 'relative';
919 container.style.overflow = 'hidden';
920 container.style.background = 'var(--bg-primary)';
921 container.style.border = '1px solid var(--border-light)';
922 container.style.borderRadius = '8px';
923
924 // Create interactive graph with D3-like functionality
925 const containerRect = container.getBoundingClientRect();
926 const width = containerRect.width || 600;
927 const height = containerRect.height || 400;
928
929 // Graph state
930 let zoomLevel = 1;
931 let panX = 0;
932 let panY = 0;
933 let selectedNode = null;
934 let isDragging = false;
935 let dragTarget = null;
936
937 // Create nodes with relationship analysis
938 const nodes = allocations.slice(0, 100).map((alloc, i) => {
939 const baseSize = Math.sqrt(alloc.size || 100) / 10 + 8;
940 return {
941 id: i,
942 name: alloc.var_name || ('var_' + i),
943 type: alloc.type_name || 'unknown',
944 size: alloc.size || 0,
945 nodeSize: Math.max(baseSize, 12),
946 x: Math.random() * (width - 100) + 50,
947 y: Math.random() * (height - 100) + 50,
948 vx: 0,
949 vy: 0,
950 alloc: alloc,
951 isLeaked: alloc.is_leaked,
952 borrowCount: alloc.borrow_count || 0,
953 cloneInfo: alloc.clone_info,
954 fixed: false
955 };
956 });
957
958 // Create relationships based on various criteria
959 const links = [];
960 for (let i = 0; i < nodes.length; i++) {
961 for (let j = i + 1; j < nodes.length; j++) {
962 const nodeA = nodes[i];
963 const nodeB = nodes[j];
964 let relationship = null;
965 let strength = 0;
966
967 // Check for clone relationships
968 if (nodeA.cloneInfo && nodeB.cloneInfo) {
969 if (nodeA.cloneInfo.original_ptr === nodeB.alloc.ptr ||
970 nodeB.cloneInfo.original_ptr === nodeA.alloc.ptr) {
971 relationship = 'clone';
972 strength = 0.8;
973 }
974 }
975
976 // Check for type similarity
977 if (!relationship && nodeA.type === nodeB.type && nodeA.type !== 'unknown') {
978 relationship = 'type_similar';
979 strength = 0.3;
980 }
981
982 // Check for thread affinity
983 if (!relationship && nodeA.alloc.thread_id === nodeB.alloc.thread_id &&
984 nodeA.alloc.thread_id !== undefined) {
985 relationship = 'thread_affinity';
986 strength = 0.2;
987 }
988
989 // Check for temporal proximity (allocated around same time)
990 if (!relationship && nodeA.alloc.timestamp_alloc && nodeB.alloc.timestamp_alloc) {
991 const timeDiff = Math.abs(nodeA.alloc.timestamp_alloc - nodeB.alloc.timestamp_alloc);
992 if (timeDiff < 1000000) { // Within 1ms
993 relationship = 'temporal';
994 strength = 0.4;
995 }
996 }
997
998 // Add link if relationship found
999 if (relationship && (strength > 0.2 || Math.random() < 0.05)) {
1000 links.push({
1001 source: i,
1002 target: j,
1003 relationship,
1004 strength,
1005 sourceNode: nodeA,
1006 targetNode: nodeB
1007 });
1008 }
1009 }
1010 }
1011
1012 // Add control panel
1013 const controls = document.createElement('div');
1014 controls.style.cssText = `
1015 position: absolute;
1016 top: 10px;
1017 left: 10px;
1018 background: rgba(0,0,0,0.8);
1019 color: white;
1020 padding: 10px;
1021 border-radius: 6px;
1022 font-size: 12px;
1023 z-index: 1000;
1024 user-select: none;
1025 `;
1026 controls.innerHTML = `
1027 <div style="margin-bottom: 8px; font-weight: bold;">ðŪ Graph Controls</div>
1028 <button id="zoom-in" style="margin: 2px; padding: 4px 8px; font-size: 11px;">ð+ Zoom In</button>
1029 <button id="zoom-out" style="margin: 2px; padding: 4px 8px; font-size: 11px;">ð- Zoom Out</button>
1030 <button id="reset-view" style="margin: 2px; padding: 4px 8px; font-size: 11px;">ð Reset</button>
1031 <button id="auto-layout" style="margin: 2px; padding: 4px 8px; font-size: 11px;">ð Layout</button>
1032 <div style="margin-top: 8px; font-size: 10px;">
1033 <div>Nodes: ${nodes.length}</div>
1034 <div>Links: ${links.length}</div>
1035 <div>Zoom: <span id="zoom-display">100%</span></div>
1036 </div>
1037 `;
1038 container.appendChild(controls);
1039
1040 // Add legend
1041 const legend = document.createElement('div');
1042 legend.style.cssText = `
1043 position: absolute;
1044 top: 10px;
1045 right: 10px;
1046 background: rgba(0,0,0,0.8);
1047 color: white;
1048 padding: 10px;
1049 border-radius: 6px;
1050 font-size: 11px;
1051 z-index: 1000;
1052 user-select: none;
1053 `;
1054 legend.innerHTML = `
1055 <div style="font-weight: bold; margin-bottom: 6px;">ð Relationships</div>
1056 <div style="margin: 3px 0;"><span style="color: #ff6b6b;">ââ</span> Clone</div>
1057 <div style="margin: 3px 0;"><span style="color: #4ecdc4;">ââ</span> Type Similar</div>
1058 <div style="margin: 3px 0;"><span style="color: #45b7d1;">ââ</span> Thread Affinity</div>
1059 <div style="margin: 3px 0;"><span style="color: #f9ca24;">ââ</span> Temporal</div>
1060 <div style="margin-top: 8px; font-weight: bold;">ðŊ Nodes</div>
1061 <div style="margin: 3px 0;"><span style="color: #ff6b6b;">â</span> Leaked</div>
1062 <div style="margin: 3px 0;"><span style="color: #6c5ce7;">â</span> High Borrow</div>
1063 <div style="margin: 3px 0;"><span style="color: #a8e6cf;">â</span> Normal</div>
1064 `;
1065 container.appendChild(legend);
1066
1067 // Create info panel for selected node
1068 const infoPanel = document.createElement('div');
1069 infoPanel.style.cssText = `
1070 position: absolute;
1071 bottom: 10px;
1072 left: 10px;
1073 background: rgba(0,0,0,0.9);
1074 color: white;
1075 padding: 12px;
1076 border-radius: 6px;
1077 font-size: 11px;
1078 max-width: 250px;
1079 z-index: 1000;
1080 display: none;
1081 `;
1082 container.appendChild(infoPanel);
1083
1084 // Render function
1085 function render() {
1086 // Clear existing nodes and links
1087 container.querySelectorAll('.graph-node, .graph-link').forEach(el => el.remove());
1088
1089 // Render links first (behind nodes)
1090 links.forEach(link => {
1091 const sourceNode = nodes[link.source];
1092 const targetNode = nodes[link.target];
1093
1094 const linkEl = document.createElement('div');
1095 linkEl.className = 'graph-link';
1096
1097 const dx = (targetNode.x - sourceNode.x) * zoomLevel;
1098 const dy = (targetNode.y - sourceNode.y) * zoomLevel;
1099 const length = Math.sqrt(dx * dx + dy * dy);
1100 const angle = Math.atan2(dy, dx) * 180 / Math.PI;
1101
1102 const x = sourceNode.x * zoomLevel + panX;
1103 const y = sourceNode.y * zoomLevel + panY;
1104
1105 let color;
1106 switch(link.relationship) {
1107 case 'clone': color = '#ff6b6b'; break;
1108 case 'type_similar': color = '#4ecdc4'; break;
1109 case 'thread_affinity': color = '#45b7d1'; break;
1110 case 'temporal': color = '#f9ca24'; break;
1111 default: color = '#666';
1112 }
1113
1114 linkEl.style.cssText = `
1115 position: absolute;
1116 left: ${x}px;
1117 top: ${y}px;
1118 width: ${length}px;
1119 height: ${Math.max(link.strength * 2, 1)}px;
1120 background: linear-gradient(90deg, ${color} 60%, transparent 60%);
1121 background-size: 8px 100%;
1122 opacity: ${0.4 + link.strength * 0.3};
1123 transform-origin: 0 50%;
1124 transform: rotate(${angle}deg);
1125 z-index: 1;
1126 pointer-events: none;
1127 `;
1128
1129 container.appendChild(linkEl);
1130 });
1131
1132 // Render nodes
1133 nodes.forEach((node, i) => {
1134 const nodeEl = document.createElement('div');
1135 nodeEl.className = 'graph-node';
1136 nodeEl.dataset.nodeId = i;
1137
1138 const x = node.x * zoomLevel + panX - (node.nodeSize * zoomLevel) / 2;
1139 const y = node.y * zoomLevel + panY - (node.nodeSize * zoomLevel) / 2;
1140 const size = node.nodeSize * zoomLevel;
1141
1142 // Determine node color based on properties
1143 let color;
1144 if (node.isLeaked) {
1145 color = '#ff6b6b'; // Red for leaked
1146 } else if (node.borrowCount > 5) {
1147 color = '#6c5ce7'; // Purple for high borrow activity
1148 } else {
1149 color = `hsl(${(node.type.length * 47) % 360}, 65%, 60%)`;
1150 }
1151
1152 nodeEl.style.cssText = `
1153 position: absolute;
1154 left: ${x}px;
1155 top: ${y}px;
1156 width: ${size}px;
1157 height: ${size}px;
1158 background: ${color};
1159 border: ${selectedNode === i ? '3px solid #fff' : '2px solid rgba(255,255,255,0.7)'};
1160 border-radius: 50%;
1161 cursor: ${node.fixed ? 'move' : 'pointer'};
1162 transition: none;
1163 z-index: 10;
1164 box-shadow: 0 2px 8px rgba(0,0,0,0.3);
1165 `;
1166
1167 // Add node label for larger nodes
1168 if (size > 20) {
1169 const label = document.createElement('div');
1170 label.style.cssText = `
1171 position: absolute;
1172 top: ${size + 4}px;
1173 left: 50%;
1174 transform: translateX(-50%);
1175 font-size: ${Math.max(zoomLevel * 10, 8)}px;
1176 color: var(--text-primary);
1177 white-space: nowrap;
1178 pointer-events: none;
1179 text-shadow: 1px 1px 2px rgba(255,255,255,0.8);
1180 font-weight: 600;
1181 `;
1182 label.textContent = node.name.length > 8 ? node.name.substring(0, 8) + '...' : node.name;
1183 nodeEl.appendChild(label);
1184 }
1185
1186 // Add event listeners
1187 nodeEl.addEventListener('click', () => selectNode(i));
1188 nodeEl.addEventListener('mousedown', (e) => startDrag(e, i));
1189
1190 container.appendChild(nodeEl);
1191 });
1192
1193 // Update zoom display
1194 document.getElementById('zoom-display').textContent = Math.round(zoomLevel * 100) + '%';
1195 }
1196
1197 // Event handlers
1198 function selectNode(nodeId) {
1199 selectedNode = nodeId;
1200 const node = nodes[nodeId];
1201
1202 // Show info panel
1203 infoPanel.style.display = 'block';
1204 infoPanel.innerHTML = `
1205 <div style="font-weight: bold; margin-bottom: 8px; color: #4ecdc4;">ð ${node.name}</div>
1206 <div><strong>Type:</strong> ${node.type}</div>
1207 <div><strong>Size:</strong> ${formatBytes(node.size)}</div>
1208 <div><strong>Leaked:</strong> ${node.isLeaked ? 'â Yes' : 'â
No'}</div>
1209 <div><strong>Borrows:</strong> ${node.borrowCount}</div>
1210 ${node.cloneInfo ? `<div><strong>Clones:</strong> ${node.cloneInfo.clone_count || 0}</div>` : ''}
1211 <div><strong>Thread:</strong> ${node.alloc.thread_id || 'Unknown'}</div>
1212 <div style="margin-top: 8px; font-size: 10px; opacity: 0.8;">
1213 Click and drag to move âĒ Double-click to pin
1214 </div>
1215 `;
1216
1217 render();
1218 }
1219
1220 function startDrag(e, nodeId) {
1221 e.preventDefault();
1222 e.stopPropagation(); // Prevent container panning
1223 isDragging = true;
1224 dragTarget = nodeId;
1225
1226 const rect = container.getBoundingClientRect();
1227 const startX = e.clientX;
1228 const startY = e.clientY;
1229 const startNodeX = nodes[nodeId].x;
1230 const startNodeY = nodes[nodeId].y;
1231
1232 // Visual feedback
1233 const nodeEl = document.querySelector(`[data-node-id="${nodeId}"]`);
1234 if (nodeEl) {
1235 nodeEl.style.transform = 'scale(1.2)';
1236 nodeEl.style.zIndex = '100';
1237 }
1238
1239 function onMouseMove(e) {
1240 if (!isDragging || dragTarget === null) return;
1241
1242 // Calculate movement in world coordinates
1243 const dx = (e.clientX - startX) / zoomLevel;
1244 const dy = (e.clientY - startY) / zoomLevel;
1245
1246 // Update node position
1247 nodes[dragTarget].x = Math.max(20, Math.min(width - 20, startNodeX + dx));
1248 nodes[dragTarget].y = Math.max(20, Math.min(height - 20, startNodeY + dy));
1249 nodes[dragTarget].fixed = true;
1250
1251 render();
1252 }
1253
1254 function onMouseUp() {
1255 isDragging = false;
1256
1257 // Reset visual feedback
1258 if (nodeEl) {
1259 nodeEl.style.transform = '';
1260 nodeEl.style.zIndex = '10';
1261 }
1262
1263 dragTarget = null;
1264 document.removeEventListener('mousemove', onMouseMove);
1265 document.removeEventListener('mouseup', onMouseUp);
1266 }
1267
1268 document.addEventListener('mousemove', onMouseMove);
1269 document.addEventListener('mouseup', onMouseUp);
1270 }
1271
1272 // Control event listeners
1273 document.getElementById('zoom-in').addEventListener('click', () => {
1274 zoomLevel = Math.min(zoomLevel * 1.2, 3);
1275 render();
1276 });
1277
1278 document.getElementById('zoom-out').addEventListener('click', () => {
1279 zoomLevel = Math.max(zoomLevel / 1.2, 0.3);
1280 render();
1281 });
1282
1283 document.getElementById('reset-view').addEventListener('click', () => {
1284 zoomLevel = 1;
1285 panX = 0;
1286 panY = 0;
1287 selectedNode = null;
1288 infoPanel.style.display = 'none';
1289 nodes.forEach(node => node.fixed = false);
1290 render();
1291 });
1292
1293 document.getElementById('auto-layout').addEventListener('click', () => {
1294 // Simple force-directed layout simulation
1295 for (let iteration = 0; iteration < 50; iteration++) {
1296 // Repulsion between nodes
1297 for (let i = 0; i < nodes.length; i++) {
1298 nodes[i].vx = 0;
1299 nodes[i].vy = 0;
1300
1301 for (let j = 0; j < nodes.length; j++) {
1302 if (i === j) continue;
1303
1304 const dx = nodes[i].x - nodes[j].x;
1305 const dy = nodes[i].y - nodes[j].y;
1306 const distance = Math.sqrt(dx * dx + dy * dy) + 0.1;
1307 const force = 100 / (distance * distance);
1308
1309 nodes[i].vx += (dx / distance) * force;
1310 nodes[i].vy += (dy / distance) * force;
1311 }
1312 }
1313
1314 // Attraction along links
1315 links.forEach(link => {
1316 const source = nodes[link.source];
1317 const target = nodes[link.target];
1318 const dx = target.x - source.x;
1319 const dy = target.y - source.y;
1320 const distance = Math.sqrt(dx * dx + dy * dy) + 0.1;
1321 const force = distance * 0.01 * link.strength;
1322
1323 source.vx += (dx / distance) * force;
1324 source.vy += (dy / distance) * force;
1325 target.vx -= (dx / distance) * force;
1326 target.vy -= (dy / distance) * force;
1327 });
1328
1329 // Apply velocities
1330 nodes.forEach(node => {
1331 if (!node.fixed) {
1332 node.x += node.vx * 0.1;
1333 node.y += node.vy * 0.1;
1334
1335 // Keep within bounds
1336 node.x = Math.max(30, Math.min(width - 30, node.x));
1337 node.y = Math.max(30, Math.min(height - 30, node.y));
1338 }
1339 });
1340 }
1341
1342 render();
1343 });
1344
1345 // Mouse wheel zoom
1346 container.addEventListener('wheel', (e) => {
1347 e.preventDefault();
1348 const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
1349 const rect = container.getBoundingClientRect();
1350 const mouseX = e.clientX - rect.left;
1351 const mouseY = e.clientY - rect.top;
1352
1353 // Zoom towards mouse position
1354 const beforeZoomX = (mouseX - panX) / zoomLevel;
1355 const beforeZoomY = (mouseY - panY) / zoomLevel;
1356
1357 zoomLevel = Math.max(0.3, Math.min(3, zoomLevel * zoomFactor));
1358
1359 // Adjust pan to keep mouse position fixed
1360 panX = mouseX - beforeZoomX * zoomLevel;
1361 panY = mouseY - beforeZoomY * zoomLevel;
1362
1363 render();
1364 });
1365
1366 // Container pan functionality
1367 let isPanning = false;
1368 let panStartX = 0;
1369 let panStartY = 0;
1370 let panStartPanX = 0;
1371 let panStartPanY = 0;
1372
1373 container.addEventListener('mousedown', (e) => {
1374 // Only start panning if not clicking on a node
1375 if (!e.target.classList.contains('graph-node')) {
1376 isPanning = true;
1377 panStartX = e.clientX;
1378 panStartY = e.clientY;
1379 panStartPanX = panX;
1380 panStartPanY = panY;
1381 container.style.cursor = 'grabbing';
1382 }
1383 });
1384
1385 container.addEventListener('mousemove', (e) => {
1386 if (isPanning) {
1387 panX = panStartPanX + (e.clientX - panStartX);
1388 panY = panStartPanY + (e.clientY - panStartY);
1389 render();
1390 }
1391 });
1392
1393 container.addEventListener('mouseup', () => {
1394 isPanning = false;
1395 container.style.cursor = 'default';
1396 });
1397
1398 container.addEventListener('mouseleave', () => {
1399 isPanning = false;
1400 container.style.cursor = 'default';
1401 });
1402
1403 // Initial render
1404 render();
1405
1406 console.log(`â
Interactive Variable Graph rendered with ${nodes.length} nodes and ${links.length} relationships`);
1407}
1408
1409function populateAllocationTable(allocations) {
1410 console.log('ð Populating allocation table...');
1411
1412 const tbody = document.getElementById('allocTable');
1413 if (!tbody) {
1414 console.warn('allocTable not found');
1415 return;
1416 }
1417
1418 tbody.innerHTML = '';
1419
1420 // Show first 100 allocations
1421 allocations.slice(0, 100).forEach(alloc => {
1422 const row = document.createElement('tr');
1423 row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700';
1424
1425 const status = alloc.is_leaked ?
1426 '<span class="status-badge status-leaked">Leaked</span>' :
1427 alloc.timestamp_dealloc ?
1428 '<span class="status-badge status-freed">Freed</span>' :
1429 '<span class="status-badge status-active">Active</span>';
1430
1431 row.innerHTML = `
1432 <td class="px-3 py-2 text-sm font-mono">${alloc.var_name || 'unnamed'}</td>
1433 <td class="px-3 py-2 text-sm">${alloc.type_name || 'unknown'}</td>
1434 <td class="px-3 py-2 text-sm">${formatBytes(alloc.size || 0)}</td>
1435 <td class="px-3 py-2 text-sm">${status}</td>
1436 `;
1437
1438 tbody.appendChild(row);
1439 });
1440
1441 console.log('â
Allocation table populated with', Math.min(allocations.length, 100), 'entries');
1442}
1443
1444// Utility function for formatting bytes
1445function formatBytes(bytes) {
1446 if (bytes === 0) return '0 B';
1447 const k = 1024;
1448 const sizes = ['B', 'KB', 'MB', 'GB'];
1449 const i = Math.floor(Math.log(bytes) / Math.log(k));
1450 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
1451}
1452
1453// Enhanced mode selector for memory analysis
1454document.addEventListener('DOMContentLoaded', function() {
1455 const modeButtons = document.querySelectorAll('.heatmap-mode-btn');
1456 const visualizations = {
1457 heatmap: document.getElementById('memoryHeatmap'),
1458 type: document.getElementById('typeChart'),
1459 distribution: document.getElementById('distributionChart')
1460 };
1461
1462 modeButtons.forEach(btn => {
1463 btn.addEventListener('click', () => {
1464 // Remove active from all buttons
1465 modeButtons.forEach(b => b.classList.remove('active'));
1466 btn.classList.add('active');
1467
1468 // Hide all visualizations
1469 Object.values(visualizations).forEach(viz => {
1470 if (viz) viz.style.display = 'none';
1471 });
1472
1473 // Show selected visualization
1474 const mode = btn.dataset.mode;
1475 if (visualizations[mode]) {
1476 visualizations[mode].style.display = 'block';
1477 }
1478 });
1479 });
1480});
1481
1482console.log('ðĶ Dashboard JavaScript loaded');
1483"#.to_string()
1484}
1485
1486const EMBEDDED_BINARY_DASHBOARD_TEMPLATE: &str = r#"
1488<!DOCTYPE html>
1489<html lang="en">
1490
1491<head>
1492 <meta charset="UTF-8" />
1493 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1494 <title>{{PROJECT_NAME}} - Binary Memory Analysis Dashboard</title>
1495 <script src="https://cdn.tailwindcss.com"></script>
1496 <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet" />
1497 <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
1498 <script src="https://d3js.org/d3.v7.min.js"></script>
1499 <script src="https://unpkg.com/three@0.128.0/build/three.min.js"></script>
1500 <script src="https://unpkg.com/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
1501
1502 <style>
1503 {
1504 {
1505 CSS_CONTENT
1506 }
1507 }
1508
1509 /* Clean, high-contrast layout variables */
1510 :root {
1511 --primary-blue: #2563eb;
1512 --primary-green: #059669;
1513 --primary-red: #dc2626;
1514 --primary-orange: #ea580c;
1515 --text-primary: #1f2937;
1516 --text-secondary: #6b7280;
1517 --bg-primary: #ffffff;
1518 --bg-secondary: #f8fafc;
1519 --border-light: #e5e7eb;
1520 --shadow-light: 0 1px 3px 0 rgb(0 0 0 / 0.1);
1521 }
1522
1523 .dark {
1524 --text-primary: #f9fafb;
1525 --text-secondary: #d1d5db;
1526 --bg-primary: #111827;
1527 --bg-secondary: #1f2937;
1528 --border-light: #374151;
1529 --shadow-light: 0 4px 6px -1px rgb(0 0 0 / 0.3);
1530 }
1531
1532 body {
1533 font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1534 background: var(--bg-secondary);
1535 color: var(--text-primary);
1536 transition: all 0.3s ease;
1537 line-height: 1.6;
1538 margin: 0;
1539 padding: 0;
1540 }
1541
1542 .dashboard-container {
1543 max-width: 1400px;
1544 margin: 0 auto;
1545 padding: 24px;
1546 min-height: 100vh;
1547 }
1548
1549 /* éĄķéĻæ éĒæ */
1550 .header {
1551 display: flex;
1552 justify-content: space-between;
1553 align-items: center;
1554 margin-bottom: 32px;
1555 padding: 20px 0;
1556 border-bottom: 1px solid var(--border-light);
1557 }
1558
1559 .header h1 {
1560 font-size: 2rem;
1561 font-weight: 700;
1562 color: var(--text-primary);
1563 margin: 0;
1564 }
1565
1566 .header .subtitle {
1567 color: var(--text-secondary);
1568 font-size: 0.9rem;
1569 margin-top: 4px;
1570 }
1571
1572 /* äļŧéĒåæĒæéŪ */
1573 .theme-toggle {
1574 background: var(--primary-blue);
1575 color: white;
1576 border: none;
1577 padding: 10px 16px;
1578 border-radius: 8px;
1579 cursor: pointer;
1580 font-size: 14px;
1581 font-weight: 500;
1582 transition: all 0.2s ease;
1583 display: flex;
1584 align-items: center;
1585 gap: 8px;
1586 }
1587
1588 .theme-toggle:hover {
1589 background: #1d4ed8;
1590 transform: translateY(-1px);
1591 }
1592
1593 /* åĄįæ ·åž */
1594 .card {
1595 background: var(--bg-primary);
1596 border: 1px solid var(--border-light);
1597 border-radius: 12px;
1598 padding: 24px;
1599 box-shadow: var(--shadow-light);
1600 transition: all 0.3s ease;
1601 }
1602
1603 .card:hover {
1604 transform: translateY(-2px);
1605 box-shadow: 0 8px 25px -5px rgb(0 0 0 / 0.1);
1606 }
1607
1608 .card h2 {
1609 font-size: 1.25rem;
1610 font-weight: 600;
1611 color: var(--text-primary);
1612 margin: 0 0 16px 0;
1613 border-bottom: 2px solid var(--primary-blue);
1614 padding-bottom: 8px;
1615 }
1616
1617 /* į―æ žåļåą */
1618 .grid {
1619 display: grid;
1620 gap: 24px;
1621 margin-bottom: 32px;
1622 }
1623
1624 .grid-2 {
1625 grid-template-columns: 1fr 1fr;
1626 }
1627
1628 .grid-3 {
1629 grid-template-columns: repeat(3, 1fr);
1630 }
1631
1632 .grid-4 {
1633 grid-template-columns: repeat(4, 1fr);
1634 }
1635
1636 /* KPI */
1637 .kpi-card {
1638 text-align: center;
1639 padding: 20px;
1640 background: linear-gradient(135deg, var(--primary-blue) 0%, #3b82f6 100%);
1641 color: white;
1642 border-radius: 12px;
1643 border: none;
1644 box-shadow: var(--shadow-light);
1645 }
1646
1647 .kpi-value {
1648 font-size: 2rem;
1649 font-weight: 700;
1650 margin-bottom: 4px;
1651 }
1652
1653 .kpi-label {
1654 font-size: 0.875rem;
1655 opacity: 0.9;
1656 font-weight: 500;
1657 }
1658
1659
1660 .chart-container {
1661 height: 300px;
1662 background: var(--bg-primary);
1663 border-radius: 8px;
1664 position: relative;
1665 padding: 16px;
1666 }
1667
1668 table {
1669 width: 100%;
1670 border-collapse: collapse;
1671 margin-top: 16px;
1672 }
1673
1674 th,
1675 td {
1676 padding: 12px;
1677 text-align: left;
1678 border-bottom: 1px solid var(--border-light);
1679 }
1680
1681 th {
1682 background: var(--bg-secondary);
1683 font-weight: 600;
1684 color: var(--text-primary);
1685 }
1686
1687 tr:hover {
1688 background: var(--bg-secondary);
1689 }
1690
1691 @media (max-width: 768px) {
1692
1693 .grid-2,
1694 .grid-3,
1695 .grid-4 {
1696 grid-template-columns: 1fr;
1697 }
1698
1699 .dashboard-container {
1700 padding: 16px;
1701 }
1702
1703 .header {
1704 flex-direction: column;
1705 gap: 16px;
1706 text-align: center;
1707 }
1708 }
1709
1710 .scroll {
1711 max-height: 400px;
1712 overflow: auto;
1713 }
1714
1715 .scroll::-webkit-scrollbar {
1716 width: 6px;
1717 }
1718
1719 .scroll::-webkit-scrollbar-track {
1720 background: var(--bg-secondary);
1721 }
1722
1723 .scroll::-webkit-scrollbar-thumb {
1724 background: var(--border-light);
1725 border-radius: 3px;
1726 }
1727
1728 .status-badge {
1729 padding: 4px 8px;
1730 border-radius: 4px;
1731 font-size: 0.75rem;
1732 font-weight: 500;
1733 }
1734
1735 .status-active {
1736 background: #dcfce7;
1737 color: #166534;
1738 }
1739
1740 .status-leaked {
1741 background: #fee2e2;
1742 color: #dc2626;
1743 }
1744
1745 .status-freed {
1746 background: #e5e7eb;
1747 color: #374151;
1748 }
1749
1750 .dark .status-active {
1751 background: #064e3b;
1752 color: #34d399;
1753 }
1754
1755 .dark .status-leaked {
1756 background: #7f1d1d;
1757 color: #fca5a5;
1758 }
1759
1760 .dark .status-freed {
1761 background: #374151;
1762 color: #d1d5db;
1763 }
1764
1765 .risk-low {
1766 background: #dcfce7;
1767 color: #166534;
1768 }
1769
1770 .risk-medium {
1771 background: #fef3c7;
1772 color: #92400e;
1773 }
1774
1775 .risk-high {
1776 background: #fee2e2;
1777 color: #dc2626;
1778 }
1779
1780 .dark .risk-low {
1781 background: #064e3b;
1782 color: #34d399;
1783 }
1784
1785 .dark .risk-medium {
1786 background: #78350f;
1787 color: #fbbf24;
1788 }
1789
1790 .dark .risk-high {
1791 background: #7f1d1d;
1792 color: #fca5a5;
1793 }
1794
1795 /* Enhanced Lifecycle Visualization Styles */
1796 .allocation-type {
1797 padding: 3px 6px;
1798 border-radius: 3px;
1799 font-size: 0.7rem;
1800 font-weight: 600;
1801 text-transform: uppercase;
1802 display: inline-block;
1803 }
1804
1805 .type-heap {
1806 background: #fef3c7;
1807 color: #92400e;
1808 border: 1px solid #f59e0b;
1809 }
1810
1811 .type-stack {
1812 background: #dbeafe;
1813 color: #1e40af;
1814 border: 1px solid #3b82f6;
1815 }
1816
1817 .type-unknown {
1818 background: #f3f4f6;
1819 color: #6b7280;
1820 border: 1px solid #9ca3af;
1821 }
1822
1823 .dark .type-heap {
1824 background: #78350f;
1825 color: #fbbf24;
1826 }
1827
1828 .dark .type-stack {
1829 background: #1e3a8a;
1830 color: #60a5fa;
1831 }
1832
1833 .dark .type-unknown {
1834 background: #374151;
1835 color: #d1d5db;
1836 }
1837
1838 /* Enhanced progress bar animations */
1839 @keyframes shine {
1840 0% {
1841 left: -100%;
1842 }
1843
1844 50% {
1845 left: 100%;
1846 }
1847
1848 100% {
1849 left: 100%;
1850 }
1851 }
1852
1853 @keyframes pulse {
1854
1855 0%,
1856 100% {
1857 opacity: 1;
1858 }
1859
1860 50% {
1861 opacity: 0.7;
1862 }
1863 }
1864
1865 /* Enhanced lifecycle item styles */
1866 .lifecycle-item.heap {
1867 border-left-color: #ff6b35 !important;
1868 }
1869
1870 .lifecycle-item.stack {
1871 border-left-color: #4dabf7 !important;
1872 }
1873
1874 .lifecycle-item:hover {
1875 animation: pulse 1s ease-in-out;
1876 }
1877
1878 .lifecycle-bar {
1879 height: 16px;
1880 background: var(--bg-secondary);
1881 border-radius: 8px;
1882 position: relative;
1883 overflow: hidden;
1884 margin: 6px 0;
1885 border: 1px solid var(--border-light);
1886 }
1887
1888 .lifecycle-progress {
1889 height: 100%;
1890 background: linear-gradient(90deg, var(--primary-green), var(--primary-blue));
1891 border-radius: 7px;
1892 position: relative;
1893 transition: width 0.3s ease;
1894 }
1895
1896 .lifecycle-item {
1897 margin: 8px 0;
1898 padding: 12px;
1899 background: var(--bg-secondary);
1900 border-radius: 8px;
1901 border-left: 4px solid var(--primary-blue);
1902 transition: all 0.2s ease;
1903 }
1904
1905 .lifecycle-item:hover {
1906 background: var(--bg-primary);
1907 box-shadow: var(--shadow-light);
1908 }
1909
1910 .lifecycle-item.heap {
1911 border-left-color: var(--primary-orange);
1912 }
1913
1914 .lifecycle-item.stack {
1915 border-left-color: var(--primary-blue);
1916 }
1917
1918 .time-info {
1919 font-size: 0.75rem;
1920 color: var(--text-secondary);
1921 margin-top: 6px;
1922 font-family: 'Courier New', monospace;
1923 }
1924
1925 .time-badge {
1926 display: inline-block;
1927 padding: 2px 6px;
1928 background: var(--bg-primary);
1929 border-radius: 3px;
1930 margin-right: 8px;
1931 border: 1px solid var(--border-light);
1932 }
1933
1934 /* Enhanced 3D Memory Layout Styles */
1935 .memory-3d-container {
1936 position: relative;
1937 width: 100%;
1938 height: 400px;
1939 background: var(--bg-secondary);
1940 border: 1px solid var(--border-light);
1941 border-radius: 8px;
1942 overflow: hidden;
1943 }
1944
1945 /* Removed problematic absolute positioning CSS for memory-3d-controls */
1946
1947 .memory-3d-info {
1948 position: absolute;
1949 bottom: 10px;
1950 left: 10px;
1951 z-index: 100;
1952 background: rgba(0, 0, 0, 0.7);
1953 color: white;
1954 padding: 8px 12px;
1955 border-radius: 6px;
1956 font-size: 0.8rem;
1957 font-family: monospace;
1958 }
1959
1960 /* Timeline Playback Styles */
1961 .timeline-container {
1962 background: var(--bg-secondary);
1963 border: 1px solid var(--border-light);
1964 border-radius: 8px;
1965 padding: 16px;
1966 margin: 16px 0;
1967 }
1968
1969 .timeline-slider {
1970 width: 100%;
1971 height: 6px;
1972 background: var(--border-light);
1973 border-radius: 3px;
1974 position: relative;
1975 cursor: pointer;
1976 margin: 12px 0;
1977 }
1978
1979 .timeline-progress {
1980 height: 100%;
1981 background: linear-gradient(90deg, var(--primary-blue), var(--primary-green));
1982 border-radius: 3px;
1983 transition: width 0.1s ease;
1984 }
1985
1986 .timeline-thumb {
1987 position: absolute;
1988 top: -6px;
1989 width: 18px;
1990 height: 18px;
1991 background: var(--primary-blue);
1992 border: 2px solid white;
1993 border-radius: 50%;
1994 cursor: grab;
1995 box-shadow: var(--shadow-light);
1996 }
1997
1998 .timeline-thumb:active {
1999 cursor: grabbing;
2000 transform: scale(1.1);
2001 }
2002
2003 .timeline-controls {
2004 display: flex;
2005 justify-content: center;
2006 gap: 12px;
2007 margin-top: 12px;
2008 }
2009
2010 .timeline-btn {
2011 background: var(--primary-blue);
2012 color: white;
2013 border: none;
2014 padding: 8px 12px;
2015 border-radius: 6px;
2016 cursor: pointer;
2017 font-size: 0.8rem;
2018 transition: all 0.2s ease;
2019 }
2020
2021 .timeline-btn:hover {
2022 background: #1d4ed8;
2023 transform: translateY(-1px);
2024 }
2025
2026 .timeline-btn:disabled {
2027 background: var(--text-secondary);
2028 cursor: not-allowed;
2029 transform: none;
2030 }
2031
2032 /* Memory Heatmap Styles */
2033 .heatmap-container {
2034 position: relative;
2035 width: 100%;
2036 height: 300px;
2037 background: var(--bg-secondary);
2038 border: 1px solid var(--border-light);
2039 border-radius: 8px;
2040 overflow: hidden;
2041 }
2042
2043 .heatmap-legend {
2044 position: absolute;
2045 top: 10px;
2046 right: 10px;
2047 z-index: 100;
2048 background: rgba(255, 255, 255, 0.9);
2049 padding: 8px;
2050 border-radius: 6px;
2051 font-size: 0.7rem;
2052 }
2053
2054 .dark .heatmap-legend {
2055 background: rgba(0, 0, 0, 0.8);
2056 color: white;
2057 }
2058
2059 .heatmap-mode-selector {
2060 display: flex;
2061 gap: 6px;
2062 margin-bottom: 12px;
2063 }
2064
2065 .heatmap-mode-btn {
2066 background: var(--bg-primary);
2067 color: var(--text-primary);
2068 border: 1px solid var(--border-light);
2069 padding: 6px 12px;
2070 border-radius: 4px;
2071 cursor: pointer;
2072 font-size: 0.8rem;
2073 transition: all 0.2s ease;
2074 }
2075
2076 .heatmap-mode-btn.active {
2077 background: var(--primary-blue);
2078 color: white;
2079 border-color: var(--primary-blue);
2080 }
2081
2082 .heatmap-mode-btn:hover {
2083 background: var(--primary-blue);
2084 color: white;
2085 }
2086
2087 /* Memory Block Visualization */
2088 .memory-block {
2089 position: absolute;
2090 border: 1px solid rgba(255, 255, 255, 0.3);
2091 border-radius: 2px;
2092 cursor: pointer;
2093 transition: all 0.2s ease;
2094 }
2095
2096 .memory-block:hover {
2097 border-color: white;
2098 border-width: 2px;
2099 z-index: 10;
2100 }
2101
2102 .memory-block.heap {
2103 background: linear-gradient(45deg, #ff6b35, #f7931e);
2104 }
2105
2106 .memory-block.stack {
2107 background: linear-gradient(45deg, #4dabf7, #339af0);
2108 }
2109
2110 .memory-block.leaked {
2111 background: linear-gradient(45deg, #dc2626, #ef4444);
2112 box-shadow: 0 0 8px rgba(239, 68, 68, 0.5);
2113 }
2114
2115 /* Tooltip Styles */
2116 .memory-tooltip {
2117 position: absolute;
2118 background: rgba(0, 0, 0, 0.9);
2119 color: white;
2120 padding: 8px 12px;
2121 border-radius: 6px;
2122 font-size: 0.8rem;
2123 font-family: monospace;
2124 pointer-events: none;
2125 z-index: 1000;
2126 max-width: 300px;
2127 line-height: 1.4;
2128 }
2129
2130 .memory-tooltip::after {
2131 content: '';
2132 position: absolute;
2133 top: 100%;
2134 left: 50%;
2135 margin-left: -5px;
2136 border-width: 5px;
2137 border-style: solid;
2138 border-color: rgba(0, 0, 0, 0.9) transparent transparent transparent;
2139 }
2140 </style>
2141 <script>
2142 // Global safe update function - must be defined first
2143 function safeUpdateElement(id, value, defaultValue = '-') {
2144 try {
2145 const el = document.getElementById(id);
2146 if (el) {
2147 el.textContent = value;
2148 return true;
2149 } else {
2150 console.warn(`Element with ID '${id}' not found`);
2151 return false;
2152 }
2153 } catch (error) {
2154 console.error(`Error updating element '${id}':`, error);
2155 return false;
2156 }
2157 }
2158
2159 // Global formatBytes function
2160 function formatBytes(bytes) {
2161 if (bytes === 0) return '0 B';
2162 if (typeof bytes !== 'number') return '0 B';
2163
2164 const k = 1024;
2165 const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
2166 const i = Math.floor(Math.log(bytes) / Math.log(k));
2167
2168 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
2169 }
2170
2171 // Beautiful modal dialog system
2172 function createModal(title, content) {
2173 // Remove existing modal if any
2174 const existingModal = document.getElementById('custom-modal');
2175 if (existingModal) {
2176 existingModal.remove();
2177 }
2178
2179 const modal = document.createElement('div');
2180 modal.id = 'custom-modal';
2181 modal.style.cssText = `
2182 position: fixed;
2183 top: 0;
2184 left: 0;
2185 width: 100%;
2186 height: 100%;
2187 background: rgba(0, 0, 0, 0.7);
2188 display: flex;
2189 align-items: center;
2190 justify-content: center;
2191 z-index: 10000;
2192 opacity: 0;
2193 transition: opacity 0.3s ease;
2194 `;
2195
2196 const modalContent = document.createElement('div');
2197 modalContent.style.cssText = `
2198 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2199 border-radius: 16px;
2200 padding: 0;
2201 max-width: 500px;
2202 width: 90%;
2203 max-height: 80%;
2204 overflow: hidden;
2205 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
2206 transform: scale(0.7);
2207 transition: transform 0.3s ease;
2208 border: 2px solid rgba(255, 255, 255, 0.2);
2209 `;
2210
2211 const header = document.createElement('div');
2212 header.style.cssText = `
2213 background: rgba(255, 255, 255, 0.15);
2214 padding: 20px 24px;
2215 border-bottom: 1px solid rgba(255, 255, 255, 0.1);
2216 display: flex;
2217 justify-content: space-between;
2218 align-items: center;
2219 `;
2220
2221 const titleEl = document.createElement('h3');
2222 titleEl.style.cssText = `
2223 margin: 0;
2224 color: white;
2225 font-size: 20px;
2226 font-weight: 600;
2227 text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
2228 `;
2229 titleEl.textContent = title;
2230
2231 const closeBtn = document.createElement('button');
2232 closeBtn.style.cssText = `
2233 background: rgba(255, 255, 255, 0.2);
2234 border: none;
2235 color: white;
2236 width: 32px;
2237 height: 32px;
2238 border-radius: 50%;
2239 cursor: pointer;
2240 display: flex;
2241 align-items: center;
2242 justify-content: center;
2243 font-size: 18px;
2244 font-weight: bold;
2245 transition: background 0.2s ease;
2246 `;
2247 closeBtn.innerHTML = 'Ã';
2248 closeBtn.addEventListener('mouseenter', () => {
2249 closeBtn.style.background = 'rgba(255, 255, 255, 0.3)';
2250 });
2251 closeBtn.addEventListener('mouseleave', () => {
2252 closeBtn.style.background = 'rgba(255, 255, 255, 0.2)';
2253 });
2254
2255 const body = document.createElement('div');
2256 body.style.cssText = `
2257 padding: 24px;
2258 color: white;
2259 line-height: 1.6;
2260 overflow-y: auto;
2261 max-height: 400px;
2262 `;
2263 body.innerHTML = content;
2264
2265 function closeModal() {
2266 modal.style.opacity = '0';
2267 modalContent.style.transform = 'scale(0.7)';
2268 setTimeout(() => {
2269 modal.remove();
2270 }, 300);
2271 }
2272
2273 closeBtn.addEventListener('click', closeModal);
2274 modal.addEventListener('click', (e) => {
2275 if (e.target === modal) closeModal();
2276 });
2277
2278 document.addEventListener('keydown', function escapeHandler(e) {
2279 if (e.key === 'Escape') {
2280 closeModal();
2281 document.removeEventListener('keydown', escapeHandler);
2282 }
2283 });
2284
2285 header.appendChild(titleEl);
2286 header.appendChild(closeBtn);
2287 modalContent.appendChild(header);
2288 modalContent.appendChild(body);
2289 modal.appendChild(modalContent);
2290 document.body.appendChild(modal);
2291
2292 // Animate in
2293 setTimeout(() => {
2294 modal.style.opacity = '1';
2295 modalContent.style.transform = 'scale(1)';
2296 }, 10);
2297
2298 return modal;
2299 }
2300
2301 // Data injection placeholder - will be replaced by build tool
2302 window.analysisData = {{BINARY_DATA}};
2303
2304 // Emergency fallback: Load data directly from JSON files if injection failed
2305 if (!window.analysisData || Object.keys(window.analysisData).length === 0 ||
2306 !window.analysisData.memory_analysis || !window.analysisData.memory_analysis.allocations) {
2307
2308 console.warn('Data injection failed, attempting to load from JSON files...');
2309
2310 // Try to fetch the JSON data directly
2311 fetch('./large_scale_user_memory_analysis.json')
2312 .then(response => response.json())
2313 .then(memoryData => {
2314 console.log('â
Loaded memory analysis data:', memoryData);
2315
2316 // Construct the expected data structure
2317 window.analysisData = {
2318 memory_analysis: memoryData,
2319 lifetime: {},
2320 complex_types: {},
2321 unsafe_ffi: {},
2322 performance: {}
2323 };
2324
2325 // Try to load other JSON files
2326 Promise.all([
2327 fetch('./large_scale_user_lifetime.json').then(r => r.json()).catch(() => ({})),
2328 fetch('./large_scale_user_complex_types.json').then(r => r.json()).catch(() => ({})),
2329 fetch('./large_scale_user_unsafe_ffi.json').then(r => r.json()).catch(() => ({})),
2330 fetch('./large_scale_user_performance.json').then(r => r.json()).catch(() => ({}))
2331 ]).then(([lifetime, complexTypes, unsafeFfi, performance]) => {
2332 window.analysisData.lifetime = lifetime;
2333 window.analysisData.complex_types = complexTypes;
2334 window.analysisData.unsafe_ffi = unsafeFfi;
2335 window.analysisData.performance = performance;
2336
2337 console.log('â
All data loaded, initializing enhanced features...');
2338
2339 // Trigger enhanced features initialization
2340 console.log('ð Triggering enhanced features initialization...');
2341 if (typeof initEnhancedLifecycleVisualization === 'function') {
2342 setTimeout(() => {
2343 console.log('ð Calling initEnhancedLifecycleVisualization...');
2344 initEnhancedLifecycleVisualization();
2345 }, 100);
2346 } else {
2347 console.error('â initEnhancedLifecycleVisualization function not found');
2348 }
2349
2350 // Also trigger the main dashboard initialization if needed
2351 if (typeof initDashboard === 'function') {
2352 setTimeout(() => {
2353 console.log('ð Calling initDashboard...');
2354 initDashboard();
2355 }, 200);
2356 }
2357 });
2358 })
2359 .catch(error => {
2360 console.error('â Failed to load JSON data:', error);
2361
2362 // Last resort: Create dummy data for testing
2363 window.analysisData = {
2364 memory_analysis: {
2365 allocations: [
2366 {
2367 var_name: 'test_var_1',
2368 type_name: 'Arc<String>',
2369 size: 1024,
2370 timestamp_alloc: Date.now() * 1000000,
2371 lifetime_ms: 100.5,
2372 is_leaked: false
2373 },
2374 {
2375 var_name: 'test_var_2',
2376 type_name: 'Vec<i32>',
2377 size: 2048,
2378 timestamp_alloc: Date.now() * 1000000 + 1000000,
2379 lifetime_ms: 250.0,
2380 is_leaked: true
2381 }
2382 ]
2383 }
2384 };
2385 console.log('â ïļ Using dummy data for testing');
2386 });
2387 } else {
2388 console.log('â
Data injection successful');
2389 }
2390
2391 console.log('Final analysisData:', window.analysisData);
2392 console.log('Allocations available:', window.analysisData?.memory_analysis?.allocations?.length || 0);
2393
2394 // Enhanced Memory Visualization Functions
2395 class EnhancedMemoryVisualizer {
2396 constructor() {
2397 this.scene = null;
2398 this.camera = null;
2399 this.renderer = null;
2400 this.controls = null;
2401 this.memoryBlocks = [];
2402 this.timeline = {
2403 isPlaying: false,
2404 currentTime: 0,
2405 totalTime: 0,
2406 speed: 1000, // ms per step
2407 data: []
2408 };
2409 this.heatmapMode = 'density';
2410 this.tooltip = null;
2411 this.initialized = false;
2412 }
2413
2414 init() {
2415 if (this.initialized) return;
2416 console.log('Initializing EnhancedMemoryVisualizer...');
2417
2418 this.initTooltip();
2419 this.init3DVisualization();
2420 this.initTimelineControls();
2421 this.initHeatmap();
2422 this.bindEvents();
2423
2424 this.initialized = true;
2425 console.log('EnhancedMemoryVisualizer initialized successfully');
2426 }
2427
2428 initTooltip() {
2429 this.tooltip = document.createElement('div');
2430 this.tooltip.className = 'memory-tooltip';
2431 this.tooltip.style.display = 'none';
2432 document.body.appendChild(this.tooltip);
2433 }
2434
2435 init3DVisualization() {
2436 const container = document.getElementById('memory3DContainer');
2437 if (!container) return;
2438
2439 // Scene setup with dark gradient background
2440 this.scene = new THREE.Scene();
2441 this.scene.background = new THREE.Color(0x1a1a2e);
2442
2443 // Camera setup - closer view for better data inspection
2444 this.camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
2445 this.camera.position.set(15, 10, 15);
2446
2447 // Renderer setup with enhanced settings
2448 this.renderer = new THREE.WebGLRenderer({
2449 antialias: true,
2450 alpha: true,
2451 powerPreference: "high-performance"
2452 });
2453 this.renderer.setSize(container.clientWidth, container.clientHeight);
2454 this.renderer.shadowMap.enabled = true;
2455 this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
2456 this.renderer.setClearColor(0x1a1a2e, 1);
2457 this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
2458 container.appendChild(this.renderer.domElement);
2459
2460 // Controls setup with enhanced interaction
2461 try {
2462 if (typeof THREE !== 'undefined') {
2463 const OrbitControls = THREE.OrbitControls || window.OrbitControls;
2464 if (OrbitControls) {
2465 this.controls = new OrbitControls(this.camera, this.renderer.domElement);
2466 this.controls.enableDamping = true;
2467 this.controls.dampingFactor = 0.05;
2468 this.controls.enableZoom = true;
2469 this.controls.enablePan = true;
2470 this.controls.enableRotate = true;
2471 this.controls.autoRotate = false;
2472 this.controls.autoRotateSpeed = 0.5;
2473 this.controls.minDistance = 2; // Allow closer inspection
2474 this.controls.maxDistance = 100;
2475 this.controls.maxPolarAngle = Math.PI;
2476 this.controls.minPolarAngle = 0;
2477 } else {
2478 console.warn('OrbitControls not available, setting up manual controls');
2479 this.setupManualControls();
2480 }
2481 }
2482 } catch (error) {
2483 console.warn('Failed to initialize OrbitControls:', error);
2484 }
2485
2486 // Enhanced lighting setup
2487 const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
2488 this.scene.add(ambientLight);
2489
2490 const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
2491 directionalLight.position.set(50, 50, 25);
2492 directionalLight.castShadow = true;
2493 directionalLight.shadow.mapSize.width = 2048;
2494 directionalLight.shadow.mapSize.height = 2048;
2495 directionalLight.shadow.camera.near = 0.5;
2496 directionalLight.shadow.camera.far = 500;
2497 this.scene.add(directionalLight);
2498
2499 // Add subtle rim lighting
2500 const rimLight = new THREE.DirectionalLight(0x4a90e2, 0.3);
2501 rimLight.position.set(-50, 20, -25);
2502 this.scene.add(rimLight);
2503
2504 // Add point light for dynamic effects
2505 this.pointLight = new THREE.PointLight(0x4a90e2, 0.5, 100);
2506 this.pointLight.position.set(0, 20, 0);
2507 this.scene.add(this.pointLight);
2508
2509 // Create subtle floor plane instead of grid
2510 const floorGeometry = new THREE.PlaneGeometry(100, 100);
2511 const floorMaterial = new THREE.MeshLambertMaterial({
2512 color: 0x2a2a3e,
2513 transparent: true,
2514 opacity: 0.3
2515 });
2516 this.floor = new THREE.Mesh(floorGeometry, floorMaterial);
2517 this.floor.rotation.x = -Math.PI / 2;
2518 this.floor.position.y = -0.1;
2519 this.floor.receiveShadow = true;
2520 this.scene.add(this.floor);
2521
2522 // Initialize animation properties
2523 this.animationTime = 0;
2524 this.isAutoRotating = false;
2525
2526 this.animate3D();
2527 }
2528
2529 setupManualControls() {
2530 if (!this.renderer || !this.camera) return;
2531
2532 const canvas = this.renderer.domElement;
2533 let isMouseDown = false;
2534 let mouseX = 0, mouseY = 0;
2535 let cameraDistance = 20;
2536 let cameraAngleX = 0;
2537 let cameraAngleY = 0;
2538
2539 // Mouse controls for rotation
2540 canvas.addEventListener('mousedown', (event) => {
2541 isMouseDown = true;
2542 mouseX = event.clientX;
2543 mouseY = event.clientY;
2544 canvas.style.cursor = 'grabbing';
2545 });
2546
2547 canvas.addEventListener('mousemove', (event) => {
2548 if (!isMouseDown) return;
2549
2550 const deltaX = event.clientX - mouseX;
2551 const deltaY = event.clientY - mouseY;
2552
2553 cameraAngleY += deltaX * 0.01;
2554 cameraAngleX += deltaY * 0.01;
2555
2556 // Limit vertical rotation
2557 cameraAngleX = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, cameraAngleX));
2558
2559 // Update camera position
2560 this.camera.position.x = Math.cos(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
2561 this.camera.position.y = Math.sin(cameraAngleX) * cameraDistance;
2562 this.camera.position.z = Math.sin(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
2563
2564 this.camera.lookAt(0, 0, 0);
2565
2566 mouseX = event.clientX;
2567 mouseY = event.clientY;
2568 });
2569
2570 canvas.addEventListener('mouseup', () => {
2571 isMouseDown = false;
2572 canvas.style.cursor = 'grab';
2573 });
2574
2575 canvas.addEventListener('mouseleave', () => {
2576 isMouseDown = false;
2577 canvas.style.cursor = 'default';
2578 });
2579
2580 // Zoom with mouse wheel
2581 canvas.addEventListener('wheel', (event) => {
2582 event.preventDefault();
2583
2584 const zoomSpeed = 0.1;
2585 cameraDistance += event.deltaY * zoomSpeed;
2586 cameraDistance = Math.max(2, Math.min(100, cameraDistance));
2587
2588 // Update camera position
2589 this.camera.position.x = Math.cos(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
2590 this.camera.position.y = Math.sin(cameraAngleX) * cameraDistance;
2591 this.camera.position.z = Math.sin(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
2592
2593 this.camera.lookAt(0, 0, 0);
2594 });
2595
2596 // Touch controls for mobile
2597 let lastTouchDistance = 0;
2598
2599 canvas.addEventListener('touchstart', (event) => {
2600 if (event.touches.length === 1) {
2601 isMouseDown = true;
2602 mouseX = event.touches[0].clientX;
2603 mouseY = event.touches[0].clientY;
2604 } else if (event.touches.length === 2) {
2605 const touch1 = event.touches[0];
2606 const touch2 = event.touches[1];
2607 lastTouchDistance = Math.sqrt(
2608 Math.pow(touch2.clientX - touch1.clientX, 2) +
2609 Math.pow(touch2.clientY - touch1.clientY, 2)
2610 );
2611 }
2612 });
2613
2614 canvas.addEventListener('touchmove', (event) => {
2615 event.preventDefault();
2616
2617 if (event.touches.length === 1 && isMouseDown) {
2618 const deltaX = event.touches[0].clientX - mouseX;
2619 const deltaY = event.touches[0].clientY - mouseY;
2620
2621 cameraAngleY += deltaX * 0.01;
2622 cameraAngleX += deltaY * 0.01;
2623 cameraAngleX = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, cameraAngleX));
2624
2625 this.camera.position.x = Math.cos(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
2626 this.camera.position.y = Math.sin(cameraAngleX) * cameraDistance;
2627 this.camera.position.z = Math.sin(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
2628
2629 this.camera.lookAt(0, 0, 0);
2630
2631 mouseX = event.touches[0].clientX;
2632 mouseY = event.touches[0].clientY;
2633 } else if (event.touches.length === 2) {
2634 const touch1 = event.touches[0];
2635 const touch2 = event.touches[1];
2636 const touchDistance = Math.sqrt(
2637 Math.pow(touch2.clientX - touch1.clientX, 2) +
2638 Math.pow(touch2.clientY - touch1.clientY, 2)
2639 );
2640
2641 if (lastTouchDistance > 0) {
2642 const zoomDelta = (lastTouchDistance - touchDistance) * 0.01;
2643 cameraDistance += zoomDelta;
2644 cameraDistance = Math.max(2, Math.min(100, cameraDistance));
2645
2646 this.camera.position.x = Math.cos(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
2647 this.camera.position.y = Math.sin(cameraAngleX) * cameraDistance;
2648 this.camera.position.z = Math.sin(cameraAngleY) * Math.cos(cameraAngleX) * cameraDistance;
2649
2650 this.camera.lookAt(0, 0, 0);
2651 }
2652
2653 lastTouchDistance = touchDistance;
2654 }
2655 });
2656
2657 canvas.addEventListener('touchend', () => {
2658 isMouseDown = false;
2659 lastTouchDistance = 0;
2660 });
2661
2662 canvas.style.cursor = 'grab';
2663 console.log('â
Manual 3D controls initialized (mouse drag to rotate, wheel to zoom)');
2664 }
2665
2666 animate3D() {
2667 requestAnimationFrame(() => this.animate3D());
2668
2669 this.animationTime += 0.01;
2670
2671 // Animate point light for dynamic lighting
2672 if (this.pointLight) {
2673 this.pointLight.position.x = Math.sin(this.animationTime) * 30;
2674 this.pointLight.position.z = Math.cos(this.animationTime) * 30;
2675 this.pointLight.intensity = 0.3 + Math.sin(this.animationTime * 2) * 0.2;
2676 }
2677
2678 // Animate memory blocks with subtle floating effect
2679 this.memoryBlocks.forEach((block, index) => {
2680 if (block && block.position) {
2681 const originalY = block.userData.originalY || block.position.y;
2682 block.position.y = originalY + Math.sin(this.animationTime + index * 0.1) * 0.2;
2683
2684 // Add subtle rotation for leaked memory blocks
2685 if (block.userData.is_leaked) {
2686 block.rotation.y += 0.02;
2687 }
2688 }
2689 });
2690
2691 // Update controls
2692 if (this.controls) {
2693 this.controls.update();
2694 }
2695
2696 // Render scene
2697 if (this.renderer && this.scene && this.camera) {
2698 this.renderer.render(this.scene, this.camera);
2699 }
2700 }
2701
2702 create3DMemoryBlocks(allocations) {
2703 if (!this.scene || !allocations) return;
2704
2705 // Clear existing blocks with fade out animation
2706 this.memoryBlocks.forEach(block => {
2707 if (block && block.material) {
2708 // Fade out animation
2709 const fadeOut = () => {
2710 block.material.opacity -= 0.05;
2711 if (block.material.opacity <= 0) {
2712 this.scene.remove(block);
2713 if (block.geometry) block.geometry.dispose();
2714 if (block.material) block.material.dispose();
2715 } else {
2716 requestAnimationFrame(fadeOut);
2717 }
2718 };
2719 fadeOut();
2720 }
2721 });
2722 this.memoryBlocks = [];
2723
2724 // Sort allocations by size for better visual hierarchy
2725 const sortedAllocs = [...allocations].sort((a, b) => (b.size || 0) - (a.size || 0));
2726
2727 const maxBlocksPerRow = 15;
2728 const spacing = 4;
2729
2730 sortedAllocs.forEach((alloc, index) => {
2731 const size = Math.max(alloc.size || 1, 1);
2732 const blockSize = Math.cbrt(size / 50) + 0.8; // Enhanced size calculation
2733
2734 // Spiral positioning for better visual distribution
2735 const angle = index * 0.5;
2736 const radius = Math.sqrt(index) * 2;
2737 const x = Math.cos(angle) * radius;
2738 const z = Math.sin(angle) * radius;
2739 const y = blockSize / 2;
2740
2741 // Enhanced color scheme based on type and size
2742 let color = 0x3b82f6;
2743 let emissive = 0x000000;
2744 const typeName = alloc.type_name || '';
2745
2746 if (typeName.includes('String')) {
2747 color = 0x4a90e2;
2748 emissive = 0x001122;
2749 } else if (typeName.includes('Box')) {
2750 color = 0xe74c3c;
2751 emissive = 0x220011;
2752 } else if (typeName.includes('Rc')) {
2753 color = 0x2ecc71;
2754 emissive = 0x001100;
2755 } else if (typeName.includes('Arc')) {
2756 color = 0x9b59b6;
2757 emissive = 0x110022;
2758 } else if (typeName.includes('Vec')) {
2759 color = 0xf39c12;
2760 emissive = 0x221100;
2761 }
2762
2763 // Create enhanced geometry with rounded edges
2764 const geometry = new THREE.BoxGeometry(blockSize, blockSize, blockSize);
2765
2766 // Enhanced material with better visual effects
2767 const material = new THREE.MeshPhongMaterial({
2768 color: color,
2769 emissive: emissive,
2770 transparent: true,
2771 opacity: alloc.is_leaked ? 0.7 : 0.9,
2772 shininess: 100,
2773 specular: 0x222222
2774 });
2775
2776 const cube = new THREE.Mesh(geometry, material);
2777 cube.position.set(x, y, z);
2778 cube.castShadow = true;
2779 cube.receiveShadow = true;
2780
2781 // Store original position and allocation data
2782 cube.userData = {
2783 ...alloc,
2784 originalY: y,
2785 originalColor: color,
2786 originalEmissive: emissive
2787 };
2788
2789 // Add entrance animation
2790 cube.scale.set(0, 0, 0);
2791 const targetScale = 1;
2792 const animateEntrance = () => {
2793 cube.scale.x += (targetScale - cube.scale.x) * 0.1;
2794 cube.scale.y += (targetScale - cube.scale.y) * 0.1;
2795 cube.scale.z += (targetScale - cube.scale.z) * 0.1;
2796
2797 if (Math.abs(cube.scale.x - targetScale) > 0.01) {
2798 requestAnimationFrame(animateEntrance);
2799 }
2800 };
2801 setTimeout(() => animateEntrance(), index * 50);
2802
2803 // Add hover effects
2804 cube.addEventListener = (event, handler) => {
2805 // Custom event handling for 3D objects
2806 };
2807
2808 this.scene.add(cube);
2809 this.memoryBlocks.push(cube);
2810 });
2811
2812 this.update3DInfo(allocations.length);
2813 this.setupRaycasting();
2814 }
2815
2816 setupRaycasting() {
2817 if (!this.renderer || !this.camera) return;
2818
2819 this.raycaster = new THREE.Raycaster();
2820 this.mouse = new THREE.Vector2();
2821 this.hoveredObject = null;
2822
2823 const canvas = this.renderer.domElement;
2824
2825 canvas.addEventListener('mousemove', (event) => {
2826 const rect = canvas.getBoundingClientRect();
2827 this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
2828 this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
2829
2830 this.raycaster.setFromCamera(this.mouse, this.camera);
2831 const intersects = this.raycaster.intersectObjects(this.memoryBlocks);
2832
2833 // Reset previous hover
2834 if (this.hoveredObject) {
2835 this.hoveredObject.material.emissive.setHex(this.hoveredObject.userData.originalEmissive);
2836 this.hoveredObject.scale.set(1, 1, 1);
2837 }
2838
2839 if (intersects.length > 0) {
2840 this.hoveredObject = intersects[0].object;
2841 // Highlight hovered object
2842 this.hoveredObject.material.emissive.setHex(0x444444);
2843 this.hoveredObject.scale.set(1.2, 1.2, 1.2);
2844
2845 // Show tooltip
2846 this.show3DTooltip(event, this.hoveredObject.userData);
2847 canvas.style.cursor = 'pointer';
2848 } else {
2849 this.hoveredObject = null;
2850 this.hide3DTooltip();
2851 canvas.style.cursor = 'default';
2852 }
2853 });
2854
2855 canvas.addEventListener('click', (event) => {
2856 if (this.hoveredObject) {
2857 this.selectMemoryBlock(this.hoveredObject);
2858 }
2859 });
2860
2861 canvas.addEventListener('mouseleave', () => {
2862 if (this.hoveredObject) {
2863 this.hoveredObject.material.emissive.setHex(this.hoveredObject.userData.originalEmissive);
2864 this.hoveredObject.scale.set(1, 1, 1);
2865 this.hoveredObject = null;
2866 }
2867 this.hide3DTooltip();
2868 canvas.style.cursor = 'default';
2869 });
2870 }
2871
2872 selectMemoryBlock(block) {
2873 // Animate selection
2874 const originalScale = { x: 1, y: 1, z: 1 };
2875 const targetScale = { x: 1.5, y: 1.5, z: 1.5 };
2876
2877 const animateSelection = () => {
2878 block.scale.x += (targetScale.x - block.scale.x) * 0.2;
2879 block.scale.y += (targetScale.y - block.scale.y) * 0.2;
2880 block.scale.z += (targetScale.z - block.scale.z) * 0.2;
2881
2882 if (Math.abs(block.scale.x - targetScale.x) > 0.01) {
2883 requestAnimationFrame(animateSelection);
2884 } else {
2885 // Return to normal size after selection
2886 setTimeout(() => {
2887 const returnToNormal = () => {
2888 block.scale.x += (originalScale.x - block.scale.x) * 0.2;
2889 block.scale.y += (originalScale.y - block.scale.y) * 0.2;
2890 block.scale.z += (originalScale.z - block.scale.z) * 0.2;
2891
2892 if (Math.abs(block.scale.x - originalScale.x) > 0.01) {
2893 requestAnimationFrame(returnToNormal);
2894 }
2895 };
2896 returnToNormal();
2897 }, 1000);
2898 }
2899 };
2900 animateSelection();
2901
2902 // Show detailed info
2903 this.showDetailedInfo(block.userData);
2904 }
2905
2906 show3DTooltip(event, alloc) {
2907 if (!this.tooltip) return;
2908
2909 this.tooltip.innerHTML = `
2910 <div style="font-weight: bold; color: #4a90e2; margin-bottom: 4px;">${alloc.var_name || 'Unknown'}</div>
2911 <div style="margin-bottom: 2px;"><span style="color: #888;">Type:</span> ${alloc.type_name || 'Unknown'}</div>
2912 <div style="margin-bottom: 2px;"><span style="color: #888;">Size:</span> ${this.formatBytes(alloc.size || 0)}</div>
2913 <div style="margin-bottom: 2px;"><span style="color: #888;">Address:</span> 0x${(alloc.ptr || 0).toString(16)}</div>
2914 <div style="margin-bottom: 2px;"><span style="color: #888;">Lifetime:</span> ${(alloc.lifetime_ms || 0).toFixed(2)}ms</div>
2915 <div style="color: ${alloc.is_leaked ? '#e74c3c' : '#2ecc71'};">
2916 ${alloc.is_leaked ? 'â ïļ Leaked' : 'â
Active'}
2917 </div>
2918 `;
2919
2920 this.tooltip.style.display = 'block';
2921 this.tooltip.style.left = `${event.pageX + 15}px`;
2922 this.tooltip.style.top = `${event.pageY - 10}px`;
2923 }
2924
2925 hide3DTooltip() {
2926 if (this.tooltip) {
2927 this.tooltip.style.display = 'none';
2928 }
2929 }
2930
2931 showDetailedInfo(alloc) {
2932 const infoEl = document.getElementById('memory3DInfo');
2933 if (infoEl) {
2934 infoEl.innerHTML = `
2935 <div style="font-weight: bold; color: #4a90e2; margin-bottom: 8px;">Selected: ${alloc.var_name}</div>
2936 <div style="font-size: 0.8rem; line-height: 1.4;">
2937 <div>Type: ${alloc.type_name}</div>
2938 <div>Size: ${this.formatBytes(alloc.size || 0)}</div>
2939 <div>Address: 0x${(alloc.ptr || 0).toString(16)}</div>
2940 <div>Scope: ${alloc.scope_name || 'unknown'}</div>
2941 <div>Lifetime: ${(alloc.lifetime_ms || 0).toFixed(2)}ms</div>
2942 <div>Status: ${alloc.is_leaked ? 'Leaked' : 'Active'}</div>
2943 </div>
2944 `;
2945 }
2946 }
2947
2948 update3DInfo(blockCount) {
2949 const infoEl = document.getElementById('memory3DInfo');
2950 if (infoEl) {
2951 infoEl.innerHTML = `
2952 Memory Blocks: ${blockCount}<br>
2953 Camera: [${this.camera.position.x.toFixed(1)}, ${this.camera.position.y.toFixed(1)}, ${this.camera.position.z.toFixed(1)}]<br>
2954 Use mouse to rotate, zoom, and pan
2955 `;
2956 }
2957 }
2958
2959 initTimelineControls() {
2960 console.log('Initializing timeline controls...');
2961
2962 const playBtn = document.getElementById('timelinePlay');
2963 const pauseBtn = document.getElementById('timelinePause');
2964 const resetBtn = document.getElementById('timelineReset');
2965 const stepBtn = document.getElementById('timelineStep');
2966 const slider = document.getElementById('timelineSlider');
2967 const thumb = document.getElementById('timelineThumb');
2968
2969 console.log('Found timeline buttons:', {
2970 playBtn: !!playBtn,
2971 pauseBtn: !!pauseBtn,
2972 resetBtn: !!resetBtn,
2973 stepBtn: !!stepBtn,
2974 slider: !!slider,
2975 thumb: !!thumb
2976 });
2977
2978 if (playBtn) {
2979 playBtn.addEventListener('click', () => {
2980 console.log('Timeline play button clicked');
2981 this.playTimeline();
2982 });
2983 console.log('Play button event bound');
2984 } else {
2985 console.error('timelinePlay button not found');
2986 }
2987
2988 if (pauseBtn) {
2989 pauseBtn.addEventListener('click', () => {
2990 console.log('Timeline pause button clicked');
2991 this.pauseTimeline();
2992 });
2993 console.log('Pause button event bound');
2994 } else {
2995 console.error('timelinePause button not found');
2996 }
2997
2998 if (resetBtn) {
2999 resetBtn.addEventListener('click', () => {
3000 console.log('Timeline reset button clicked');
3001 this.resetTimeline();
3002 });
3003 console.log('Reset button event bound');
3004 } else {
3005 console.error('timelineReset button not found');
3006 }
3007
3008 if (stepBtn) {
3009 stepBtn.addEventListener('click', () => {
3010 console.log('Timeline step button clicked');
3011 this.stepTimeline();
3012 });
3013 console.log('Step button event bound');
3014 } else {
3015 console.error('timelineStep button not found');
3016 }
3017
3018 if (slider && thumb) {
3019 let isDragging = false;
3020
3021 thumb.addEventListener('mousedown', (e) => {
3022 isDragging = true;
3023 e.preventDefault();
3024 });
3025
3026 document.addEventListener('mousemove', (e) => {
3027 if (!isDragging) return;
3028
3029 const rect = slider.getBoundingClientRect();
3030 const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
3031 const percentage = x / rect.width;
3032
3033 this.setTimelinePosition(percentage);
3034 });
3035
3036 document.addEventListener('mouseup', () => {
3037 isDragging = false;
3038 });
3039
3040 slider.addEventListener('click', (e) => {
3041 if (isDragging) return;
3042
3043 const rect = slider.getBoundingClientRect();
3044 const x = e.clientX - rect.left;
3045 const percentage = x / rect.width;
3046
3047 this.setTimelinePosition(percentage);
3048 });
3049 }
3050 }
3051
3052 formatBytes(bytes) {
3053 if (bytes === 0) return '0 Bytes';
3054 const k = 1024;
3055 const sizes = ['Bytes', 'KB', 'MB', 'GB'];
3056 const i = Math.floor(Math.log(bytes) / Math.log(k));
3057 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
3058 }
3059
3060 prepareTimelineData(allocations) {
3061 if (!allocations || allocations.length === 0) {
3062 this.timeline.data = [];
3063 this.timeline.totalTime = 0;
3064 this.updateTimelineDisplay();
3065 return;
3066 }
3067
3068 // Filter out allocations with invalid timestamps and sort
3069 const validAllocs = allocations.filter(alloc =>
3070 alloc.timestamp_alloc &&
3071 !isNaN(alloc.timestamp_alloc) &&
3072 alloc.timestamp_alloc > 0
3073 );
3074
3075 if (validAllocs.length === 0) {
3076 // If no valid timestamps, create synthetic timeline based on order
3077 this.timeline.data = allocations.map((alloc, index) => ({
3078 ...alloc,
3079 timestamp_alloc: index * 1000000, // 1ms intervals
3080 timestamp_dealloc: alloc.timestamp_dealloc || (index + 1) * 1000000
3081 }));
3082 this.timeline.totalTime = allocations.length * 1000000;
3083 } else {
3084 // Sort by allocation timestamp
3085 const sortedAllocs = [...validAllocs].sort((a, b) => {
3086 const timeA = a.timestamp_alloc || 0;
3087 const timeB = b.timestamp_alloc || 0;
3088 return timeA - timeB;
3089 });
3090
3091 this.timeline.data = sortedAllocs;
3092 const minTime = sortedAllocs[0].timestamp_alloc || 0;
3093 const maxTime = sortedAllocs[sortedAllocs.length - 1].timestamp_alloc || 0;
3094 this.timeline.totalTime = Math.max(maxTime - minTime, 1000000); // At least 1ms
3095 }
3096
3097 this.updateTimelineDisplay();
3098 }
3099
3100 playTimeline() {
3101 console.log('Starting timeline playback...');
3102 if (this.timeline.isPlaying) {
3103 console.log('Timeline already playing');
3104 return;
3105 }
3106
3107 console.log('Timeline data:', {
3108 totalTime: this.timeline.totalTime,
3109 currentTime: this.timeline.currentTime,
3110 dataLength: this.timeline.data.length
3111 });
3112
3113 this.timeline.isPlaying = true;
3114 const playBtn = document.getElementById('timelinePlay');
3115 const pauseBtn = document.getElementById('timelinePause');
3116
3117 if (playBtn) playBtn.disabled = true;
3118 if (pauseBtn) pauseBtn.disabled = false;
3119
3120 console.log('Timeline playback started');
3121
3122 // Get speed from control
3123 const speedSelect = document.getElementById('timelineSpeed');
3124 const speed = speedSelect ? parseFloat(speedSelect.value) : 1.0;
3125
3126 // Use requestAnimationFrame for smoother animation
3127 const animate = () => {
3128 if (!this.timeline.isPlaying) return;
3129
3130 // Calculate time increment based on speed and total time for better visualization
3131 const baseIncrement = Math.max(this.timeline.totalTime * 0.0005, 12500); // 0.05% of total time or 12.5Ξs minimum
3132 this.timeline.currentTime += baseIncrement * speed;
3133
3134 if (this.timeline.currentTime >= this.timeline.totalTime) {
3135 this.timeline.currentTime = this.timeline.totalTime;
3136 this.updateTimelineVisualization();
3137 this.pauseTimeline();
3138 console.log('Timeline playback completed');
3139 return;
3140 }
3141
3142 this.updateTimelineVisualization();
3143
3144 // Continue animation with consistent frame rate
3145 const frameDelay = Math.max(16, 200 / speed); // Slower frame rate for better visualization
3146 setTimeout(() => {
3147 if (this.timeline.isPlaying) {
3148 requestAnimationFrame(animate);
3149 }
3150 }, frameDelay);
3151 };
3152
3153 requestAnimationFrame(animate);
3154 }
3155
3156 pauseTimeline() {
3157 this.timeline.isPlaying = false;
3158
3159 const playBtn = document.getElementById('timelinePlay');
3160 const pauseBtn = document.getElementById('timelinePause');
3161
3162 if (playBtn) playBtn.disabled = false;
3163 if (pauseBtn) pauseBtn.disabled = true;
3164
3165 if (this.timelineInterval) {
3166 clearInterval(this.timelineInterval);
3167 this.timelineInterval = null;
3168 }
3169 }
3170
3171 resetTimeline() {
3172 console.log('Resetting timeline...');
3173 this.pauseTimeline();
3174 this.timeline.currentTime = 0;
3175 this.updateTimelineVisualization();
3176
3177 // Reset 3D visualization to show all allocations
3178 if (window.analysisData && window.analysisData.memory_analysis) {
3179 this.create3DMemoryBlocks(window.analysisData.memory_analysis.allocations || []);
3180 }
3181
3182 console.log('Timeline reset to beginning');
3183 }
3184
3185 stepTimeline() {
3186 // Step forward by 1% of total time or 1ms, whichever is larger
3187 const stepSize = Math.max(this.timeline.totalTime * 0.01, 1000000); // 1ms minimum
3188 this.timeline.currentTime += stepSize;
3189 if (this.timeline.currentTime > this.timeline.totalTime) {
3190 this.timeline.currentTime = this.timeline.totalTime;
3191 }
3192 this.updateTimelineVisualization();
3193 console.log(`Timeline stepped to ${(this.timeline.currentTime / 1000000).toFixed(1)}ms`);
3194 }
3195
3196 setTimelinePosition(percentage) {
3197 this.timeline.currentTime = percentage * this.timeline.totalTime;
3198 this.updateTimelineVisualization();
3199 }
3200
3201 updateTimelineVisualization() {
3202 const percentage = this.timeline.totalTime > 0 ? this.timeline.currentTime / this.timeline.totalTime : 0;
3203
3204 // Cache DOM elements to avoid repeated queries
3205 if (!this.timelineElements) {
3206 this.timelineElements = {
3207 progress: document.getElementById('timelineProgress'),
3208 thumb: document.getElementById('timelineThumb'),
3209 currentTime: document.getElementById('timelineCurrentTime'),
3210 totalTime: document.getElementById('timelineTotalTime'),
3211 activeCount: document.getElementById('timelineActiveCount')
3212 };
3213 }
3214
3215 // Only update if percentage changed significantly (reduce flickering)
3216 const roundedPercentage = Math.round(percentage * 1000) / 10; // Round to 0.1%
3217 if (this.lastPercentage !== roundedPercentage) {
3218 this.lastPercentage = roundedPercentage;
3219
3220 if (this.timelineElements.progress) {
3221 this.timelineElements.progress.style.width = `${roundedPercentage}%`;
3222 }
3223 if (this.timelineElements.thumb) {
3224 this.timelineElements.thumb.style.left = `calc(${roundedPercentage}% - 9px)`;
3225 }
3226 }
3227
3228 // Update time display less frequently
3229 if (!this.lastTimeUpdate || Date.now() - this.lastTimeUpdate > 100) {
3230 this.lastTimeUpdate = Date.now();
3231
3232 const currentTimeMs = isNaN(this.timeline.currentTime) ? 0 : this.timeline.currentTime / 1000000;
3233 const totalTimeMs = isNaN(this.timeline.totalTime) ? 0 : this.timeline.totalTime / 1000000;
3234
3235 if (this.timelineElements.currentTime) {
3236 safeUpdateElement('timelineCurrentTime', `${currentTimeMs.toFixed(1)}ms`);
3237 }
3238 if (this.timelineElements.totalTime) {
3239 safeUpdateElement('timelineTotalTime', `${totalTimeMs.toFixed(1)}ms`);
3240 }
3241 }
3242
3243 // Count active allocations at current time
3244 const activeAllocs = this.timeline.data.filter(alloc => {
3245 const allocTime = alloc.timestamp_alloc || 0;
3246 const deallocTime = alloc.timestamp_dealloc || Infinity;
3247 const currentTime = this.timeline.data[0] ? (this.timeline.data[0].timestamp_alloc || 0) + this.timeline.currentTime : 0;
3248
3249 return allocTime <= currentTime && currentTime < deallocTime;
3250 });
3251
3252 if (this.timelineElements.activeCount) {
3253 safeUpdateElement('timelineActiveCount', activeAllocs.length);
3254 }
3255
3256 // Update 3D visualization with current active allocations
3257 this.create3DMemoryBlocks(activeAllocs);
3258 }
3259
3260 updateTimelineDisplay() {
3261 const totalTimeEl = document.getElementById('timelineTotalTime');
3262 const currentTimeEl = document.getElementById('timelineCurrentTime');
3263
3264 const totalTimeMs = isNaN(this.timeline.totalTime) ? 0 : this.timeline.totalTime / 1000000;
3265 const currentTimeMs = isNaN(this.timeline.currentTime) ? 0 : this.timeline.currentTime / 1000000;
3266
3267 safeUpdateElement('timelineTotalTime', `${totalTimeMs.toFixed(1)}ms`);
3268 safeUpdateElement('timelineCurrentTime', `${currentTimeMs.toFixed(1)}ms`);
3269 }
3270
3271 initHeatmap() {
3272 const container = document.getElementById('memoryHeatmap');
3273 const modeButtons = document.querySelectorAll('.heatmap-mode-btn');
3274
3275 modeButtons.forEach(btn => {
3276 btn.addEventListener('click', (e) => {
3277 modeButtons.forEach(b => b.classList.remove('active'));
3278 e.target.classList.add('active');
3279 this.heatmapMode = e.target.dataset.mode;
3280 this.updateHeatmap();
3281 });
3282 });
3283 }
3284
3285 generateHeatmap(allocations) {
3286 console.log('Generating enhanced heatmap with', allocations?.length || 0, 'allocations');
3287 if (!allocations) return;
3288
3289 const container = document.getElementById('memoryHeatmap');
3290 if (!container) return;
3291
3292 // Clear existing heatmap
3293 container.innerHTML = '<div class="heatmap-legend" id="heatmapLegend"></div>';
3294
3295 const width = container.clientWidth;
3296 const height = container.clientHeight - 40; // Account for legend
3297 const cellSize = 6; // Smaller cells for better resolution
3298 const cols = Math.floor(width / cellSize);
3299 const rows = Math.floor(height / cellSize);
3300
3301 // Create heatmap data based on mode
3302 let heatmapData = [];
3303 let metadata = {};
3304
3305 switch (this.heatmapMode) {
3306 case 'density':
3307 const densityResult = this.calculateDensityHeatmap(allocations, cols, rows);
3308 heatmapData = densityResult.data || densityResult;
3309 metadata = densityResult.metadata || {};
3310 break;
3311 case 'type':
3312 const typeResult = this.calculateTypeHeatmap(allocations, cols, rows);
3313 heatmapData = typeResult.data || typeResult;
3314 metadata = typeResult.metadata || {};
3315 break;
3316 case 'scope':
3317 const scopeResult = this.calculateScopeHeatmap(allocations, cols, rows);
3318 heatmapData = scopeResult.data || scopeResult;
3319 metadata = scopeResult.metadata || {};
3320 break;
3321 case 'activity':
3322 const activityResult = this.calculateActivityHeatmap(allocations, cols, rows);
3323 heatmapData = activityResult.data || activityResult;
3324 metadata = activityResult.metadata || {};
3325 break;
3326 case 'fragmentation':
3327 const fragResult = this.calculateFragmentationHeatmap(allocations, cols, rows);
3328 heatmapData = fragResult.data || [];
3329 metadata = fragResult.metadata || {};
3330 break;
3331 case 'lifetime':
3332 const lifetimeResult = this.calculateLifetimeHeatmap(allocations, cols, rows);
3333 heatmapData = lifetimeResult.data || [];
3334 metadata = lifetimeResult.metadata || {};
3335 break;
3336 }
3337
3338 // Render enhanced heatmap with smooth transitions
3339 const fragment = document.createDocumentFragment();
3340 for (let row = 0; row < rows; row++) {
3341 for (let col = 0; col < cols; col++) {
3342 const index = row * cols + col;
3343 const intensity = heatmapData[index] || 0;
3344
3345 if (intensity > 0.01) { // Only render visible cells for performance
3346 const cell = document.createElement('div');
3347 cell.style.position = 'absolute';
3348 cell.style.left = `${col * cellSize}px`;
3349 cell.style.top = `${row * cellSize + 40}px`;
3350 cell.style.width = `${cellSize}px`;
3351 cell.style.height = `${cellSize}px`;
3352 cell.style.backgroundColor = this.getHeatmapColor(intensity, this.heatmapMode);
3353 cell.style.opacity = Math.max(0.1, intensity);
3354 cell.style.transition = 'all 0.3s ease';
3355 cell.style.borderRadius = '1px';
3356
3357 // Add hover effects for interactivity
3358 cell.addEventListener('mouseenter', (e) => {
3359 e.target.style.transform = 'scale(1.2)';
3360 e.target.style.zIndex = '10';
3361 this.showHeatmapTooltip(e, intensity, metadata, row, col);
3362 });
3363
3364 cell.addEventListener('mouseleave', (e) => {
3365 e.target.style.transform = 'scale(1)';
3366 e.target.style.zIndex = '1';
3367 this.hideHeatmapTooltip();
3368 });
3369
3370 fragment.appendChild(cell);
3371 }
3372 }
3373 }
3374
3375 container.appendChild(fragment);
3376 this.updateHeatmapLegend(metadata);
3377 }
3378
3379 calculateDensityHeatmap(allocations, cols, rows) {
3380 const data = new Array(cols * rows).fill(0);
3381 const maxSize = Math.max(...allocations.map(a => a.size || 0));
3382
3383 allocations.forEach((alloc, index) => {
3384 const x = index % cols;
3385 const y = Math.floor(index / cols) % rows;
3386 const cellIndex = y * cols + x;
3387
3388 if (cellIndex < data.length) {
3389 data[cellIndex] += (alloc.size || 0) / maxSize;
3390 }
3391 });
3392
3393 return data;
3394 }
3395
3396 calculateTypeHeatmap(allocations, cols, rows) {
3397 const data = new Array(cols * rows).fill(0);
3398 const typeMap = new Map();
3399
3400 allocations.forEach(alloc => {
3401 const type = alloc.type_name || 'unknown';
3402 typeMap.set(type, (typeMap.get(type) || 0) + 1);
3403 });
3404
3405 const maxCount = Math.max(...typeMap.values());
3406 let index = 0;
3407
3408 for (const [type, count] of typeMap.entries()) {
3409 const intensity = count / maxCount;
3410 const startIndex = index * Math.floor(data.length / typeMap.size);
3411 const endIndex = Math.min(startIndex + Math.floor(data.length / typeMap.size), data.length);
3412
3413 for (let i = startIndex; i < endIndex; i++) {
3414 data[i] = intensity;
3415 }
3416 index++;
3417 }
3418
3419 return data;
3420 }
3421
3422 calculateScopeHeatmap(allocations, cols, rows) {
3423 const data = new Array(cols * rows).fill(0);
3424 const scopeMap = new Map();
3425
3426 allocations.forEach(alloc => {
3427 const scope = alloc.scope || 'global';
3428 scopeMap.set(scope, (scopeMap.get(scope) || 0) + 1);
3429 });
3430
3431 const maxCount = Math.max(...scopeMap.values());
3432 let index = 0;
3433
3434 for (const [scope, count] of scopeMap.entries()) {
3435 const intensity = count / maxCount;
3436 const startIndex = index * Math.floor(data.length / scopeMap.size);
3437 const endIndex = Math.min(startIndex + Math.floor(data.length / scopeMap.size), data.length);
3438
3439 for (let i = startIndex; i < endIndex; i++) {
3440 data[i] = intensity;
3441 }
3442 index++;
3443 }
3444
3445 return data;
3446 }
3447
3448 calculateActivityHeatmap(allocations, cols, rows) {
3449 const data = new Array(cols * rows).fill(0);
3450
3451 if (allocations.length === 0) return { data, metadata: {} };
3452
3453 const minTime = Math.min(...allocations.map(a => a.timestamp_alloc || 0));
3454 const maxTime = Math.max(...allocations.map(a => a.timestamp_alloc || 0));
3455 const timeRange = maxTime - minTime || 1;
3456
3457 allocations.forEach(alloc => {
3458 const timeRatio = ((alloc.timestamp_alloc || 0) - minTime) / timeRange;
3459 const cellIndex = Math.floor(timeRatio * data.length);
3460
3461 if (cellIndex < data.length) {
3462 data[cellIndex] += 0.1;
3463 }
3464 });
3465
3466 const maxActivity = Math.max(...data);
3467 const normalizedData = maxActivity > 0 ? data.map(d => d / maxActivity) : data;
3468
3469 return {
3470 data: normalizedData,
3471 metadata: {
3472 maxActivity,
3473 totalAllocations: allocations.length,
3474 timeRange: timeRange / 1000000 // Convert to ms
3475 }
3476 };
3477 }
3478
3479 calculateFragmentationHeatmap(allocations, cols, rows) {
3480 const data = new Array(cols * rows).fill(0);
3481
3482 // Sort allocations by memory address
3483 const sortedAllocs = allocations
3484 .filter(a => a.ptr && a.size)
3485 .map(a => ({
3486 address: parseInt(a.ptr.replace('0x', ''), 16),
3487 size: a.size,
3488 ...a
3489 }))
3490 .sort((a, b) => a.address - b.address);
3491
3492 // Calculate fragmentation score for each memory region
3493 for (let i = 0; i < sortedAllocs.length - 1; i++) {
3494 const current = sortedAllocs[i];
3495 const next = sortedAllocs[i + 1];
3496 const gap = next.address - (current.address + current.size);
3497
3498 if (gap > 0) {
3499 // Map to heatmap coordinates
3500 const normalizedAddr = (current.address % (cols * rows * 1000)) / (cols * rows * 1000);
3501 const row = Math.floor(normalizedAddr * rows);
3502 const col = Math.floor((i / sortedAllocs.length) * cols);
3503 const cellIndex = Math.min(row * cols + col, data.length - 1);
3504
3505 // Higher gap = higher fragmentation
3506 data[cellIndex] += Math.min(gap / 1000, 1); // Normalize gap size
3507 }
3508 }
3509
3510 const maxFrag = Math.max(...data);
3511 const normalizedData = maxFrag > 0 ? data.map(d => d / maxFrag) : data;
3512
3513 return {
3514 data: normalizedData,
3515 metadata: {
3516 maxFragmentation: maxFrag,
3517 totalGaps: data.filter(d => d > 0).length,
3518 avgFragmentation: data.reduce((a, b) => a + b, 0) / data.length
3519 }
3520 };
3521 }
3522
3523 calculateLifetimeHeatmap(allocations, cols, rows) {
3524 const data = new Array(cols * rows).fill(0);
3525
3526 allocations.forEach((alloc, index) => {
3527 const allocTime = alloc.timestamp_alloc || 0;
3528 const deallocTime = alloc.timestamp_dealloc || Date.now() * 1000000;
3529 const lifetime = deallocTime - allocTime;
3530
3531 // Map lifetime to heatmap position
3532 const row = Math.floor((index / allocations.length) * rows);
3533 const col = Math.floor((lifetime / 1000000000) * cols) % cols; // Convert to seconds
3534 const cellIndex = Math.min(row * cols + col, data.length - 1);
3535
3536 data[cellIndex] += 1;
3537 });
3538
3539 const maxLifetime = Math.max(...data);
3540 const normalizedData = maxLifetime > 0 ? data.map(d => d / maxLifetime) : data;
3541
3542 return {
3543 data: normalizedData,
3544 metadata: {
3545 maxLifetime,
3546 avgLifetime: data.reduce((a, b) => a + b, 0) / data.length,
3547 activeAllocations: allocations.filter(a => !a.timestamp_dealloc).length
3548 }
3549 };
3550 }
3551
3552 getHeatmapColor(intensity, mode = 'density') {
3553 const scaledIntensity = Math.min(Math.max(intensity, 0), 1);
3554
3555 // Different color schemes for different modes
3556 const colorSchemes = {
3557 density: [
3558 [59, 130, 246], // Blue
3559 [245, 158, 11], // Orange
3560 [220, 38, 38] // Red
3561 ],
3562 type: [
3563 [34, 197, 94], // Green
3564 [168, 85, 247], // Purple
3565 [239, 68, 68] // Red
3566 ],
3567 scope: [
3568 [14, 165, 233], // Sky blue
3569 [251, 191, 36], // Amber
3570 [239, 68, 68] // Red
3571 ],
3572 activity: [
3573 [99, 102, 241], // Indigo
3574 [236, 72, 153], // Pink
3575 [220, 38, 38] // Red
3576 ],
3577 fragmentation: [
3578 [34, 197, 94], // Green (low fragmentation)
3579 [251, 191, 36], // Amber (medium)
3580 [239, 68, 68] // Red (high fragmentation)
3581 ],
3582 lifetime: [
3583 [147, 51, 234], // Purple (short-lived)
3584 [59, 130, 246], // Blue (medium)
3585 [34, 197, 94] // Green (long-lived)
3586 ]
3587 };
3588
3589 const colors = colorSchemes[mode] || colorSchemes.density;
3590 const colorIndex = scaledIntensity * (colors.length - 1);
3591 const lowerIndex = Math.floor(colorIndex);
3592 const upperIndex = Math.ceil(colorIndex);
3593 const ratio = colorIndex - lowerIndex;
3594
3595 if (lowerIndex === upperIndex) {
3596 const [r, g, b] = colors[lowerIndex];
3597 return `rgb(${r}, ${g}, ${b})`;
3598 }
3599
3600 const [r1, g1, b1] = colors[lowerIndex];
3601 const [r2, g2, b2] = colors[upperIndex];
3602
3603 const r = Math.round(r1 + (r2 - r1) * ratio);
3604 const g = Math.round(g1 + (g2 - g1) * ratio);
3605 const b = Math.round(b1 + (b2 - b1) * ratio);
3606
3607 return `rgb(${r}, ${g}, ${b})`;
3608 }
3609
3610 showHeatmapTooltip(event, intensity, metadata, row, col) {
3611 if (!this.tooltip) {
3612 this.tooltip = document.createElement('div');
3613 this.tooltip.style.cssText = `
3614 position: absolute;
3615 background: rgba(0, 0, 0, 0.9);
3616 color: white;
3617 padding: 8px 12px;
3618 border-radius: 6px;
3619 font-size: 12px;
3620 pointer-events: none;
3621 z-index: 1000;
3622 max-width: 200px;
3623 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
3624 `;
3625 document.body.appendChild(this.tooltip);
3626 }
3627
3628 const modeDescriptions = {
3629 density: 'Memory usage density',
3630 type: 'Type distribution',
3631 scope: 'Scope activity level',
3632 activity: 'Allocation activity',
3633 fragmentation: 'Memory fragmentation',
3634 lifetime: 'Allocation lifetime'
3635 };
3636
3637 this.tooltip.innerHTML = `
3638 <div><strong>${modeDescriptions[this.heatmapMode] || 'Intensity'}</strong></div>
3639 <div>Value: ${(intensity * 100).toFixed(1)}%</div>
3640 <div>Position: (${col}, ${row})</div>
3641 ${metadata.maxActivity ? `<div>Max Activity: ${metadata.maxActivity}</div>` : ''}
3642 ${metadata.totalGaps ? `<div>Total Gaps: ${metadata.totalGaps}</div>` : ''}
3643 ${metadata.activeAllocations ? `<div>Active: ${metadata.activeAllocations}</div>` : ''}
3644 `;
3645
3646 this.tooltip.style.left = `${event.pageX + 10}px`;
3647 this.tooltip.style.top = `${event.pageY - 10}px`;
3648 this.tooltip.style.display = 'block';
3649 }
3650
3651 hideHeatmapTooltip() {
3652 if (this.tooltip) {
3653 this.tooltip.style.display = 'none';
3654 }
3655 }
3656
3657 updateHeatmapLegend(metadata = {}) {
3658 const legend = document.getElementById('heatmapLegend');
3659 if (!legend) return;
3660
3661 const modeLabels = {
3662 density: 'Memory Density',
3663 type: 'Type Distribution',
3664 scope: 'Scope Activity',
3665 activity: 'Allocation Activity',
3666 fragmentation: 'Memory Fragmentation',
3667 lifetime: 'Allocation Lifetime'
3668 };
3669
3670 const modeDescriptions = {
3671 density: 'Shows memory usage concentration',
3672 type: 'Shows distribution of data types',
3673 scope: 'Shows activity by scope',
3674 activity: 'Shows allocation frequency over time',
3675 fragmentation: 'Shows memory fragmentation levels',
3676 lifetime: 'Shows allocation lifetime patterns'
3677 };
3678
3679 const currentMode = this.heatmapMode;
3680 const lowColor = this.getHeatmapColor(0.2, currentMode);
3681 const medColor = this.getHeatmapColor(0.5, currentMode);
3682 const highColor = this.getHeatmapColor(0.8, currentMode);
3683
3684 let metadataHtml = '';
3685 if (metadata.maxActivity) {
3686 metadataHtml += `<div style="font-size: 10px; color: rgba(255,255,255,0.8);">Max Activity: ${metadata.maxActivity}</div>`;
3687 }
3688 if (metadata.totalGaps) {
3689 metadataHtml += `<div style="font-size: 10px; color: rgba(255,255,255,0.8);">Gaps: ${metadata.totalGaps}</div>`;
3690 }
3691 if (metadata.activeAllocations) {
3692 metadataHtml += `<div style="font-size: 10px; color: rgba(255,255,255,0.8);">Active: ${metadata.activeAllocations}</div>`;
3693 }
3694
3695 legend.innerHTML = `
3696 <div style="font-weight: 600; margin-bottom: 2px;">${modeLabels[currentMode]}</div>
3697 <div style="font-size: 10px; color: rgba(255,255,255,0.7); margin-bottom: 4px;">${modeDescriptions[currentMode]}</div>
3698 <div style="display: flex; align-items: center; gap: 4px; margin-bottom: 4px;">
3699 <div style="width: 12px; height: 12px; background: ${lowColor}; border-radius: 2px;"></div>
3700 <span style="font-size: 11px;">Low</span>
3701 <div style="width: 12px; height: 12px; background: ${medColor}; border-radius: 2px;"></div>
3702 <span style="font-size: 11px;">Med</span>
3703 <div style="width: 12px; height: 12px; background: ${highColor}; border-radius: 2px;"></div>
3704 <span style="font-size: 11px;">High</span>
3705 </div>
3706 ${metadataHtml}
3707 `;
3708 }
3709
3710 updateHeatmap() {
3711 if (window.analysisData && window.analysisData.memory_analysis && window.analysisData.memory_analysis.allocations) {
3712 this.generateHeatmap(window.analysisData.memory_analysis.allocations);
3713 }
3714 }
3715
3716 bindEvents() {
3717 console.log('ð§ Binding 3D visualization events...');
3718
3719 // Add visual feedback for button interactions
3720 this.addButtonFeedback();
3721
3722 // Wait for DOM to be fully ready
3723 setTimeout(() => {
3724 const toggle3DBtn = document.getElementById('toggle3DView');
3725 const reset3DBtn = document.getElementById('reset3DView');
3726 const autoRotateBtn = document.getElementById('autoRotate3D');
3727 const focusLargestBtn = document.getElementById('focusLargest');
3728
3729 console.log('ð Found buttons:', {
3730 toggle3DBtn: !!toggle3DBtn,
3731 reset3DBtn: !!reset3DBtn,
3732 autoRotateBtn: !!autoRotateBtn,
3733 focusLargestBtn: !!focusLargestBtn
3734 });
3735
3736 if (toggle3DBtn) {
3737 // Remove any existing event listeners
3738 toggle3DBtn.replaceWith(toggle3DBtn.cloneNode(true));
3739 const newToggle3DBtn = document.getElementById('toggle3DView');
3740
3741 newToggle3DBtn.addEventListener('click', (e) => {
3742 e.preventDefault();
3743 console.log('ðŊ Toggle 3D view clicked');
3744 const container = document.getElementById('memory3DContainer');
3745 if (container) {
3746 const isHidden = container.style.display === 'none';
3747 if (isHidden) {
3748 // Show 3D view
3749 container.style.display = 'block';
3750 newToggle3DBtn.innerHTML = '<i class="fa fa-eye-slash"></i><span>Hide 3D</span>';
3751 newToggle3DBtn.style.background = 'var(--primary-red)';
3752 console.log('â
Showing 3D view');
3753
3754 // Reinitialize 3D scene if needed
3755 if (!this.scene) {
3756 console.log('ð Reinitializing 3D scene...');
3757 this.init3DVisualization();
3758 }
3759
3760 // Update 3D visualization with current data
3761 if (window.analysisData && window.analysisData.memory_analysis) {
3762 this.create3DMemoryBlocks(window.analysisData.memory_analysis.allocations || []);
3763 }
3764 } else {
3765 // Hide 3D view
3766 container.style.display = 'none';
3767 newToggle3DBtn.innerHTML = '<i class="fa fa-eye"></i><span>Show 3D</span>';
3768 newToggle3DBtn.style.background = 'var(--primary-green)';
3769 console.log('â
Hiding 3D view');
3770 }
3771 } else {
3772 console.error('â 3D container not found');
3773 }
3774 });
3775 console.log('â
Toggle 3D button event bound');
3776 } else {
3777 console.error('â toggle3DView button not found');
3778 }
3779
3780 if (reset3DBtn) {
3781 // Remove any existing event listeners
3782 reset3DBtn.replaceWith(reset3DBtn.cloneNode(true));
3783 const newReset3DBtn = document.getElementById('reset3DView');
3784
3785 newReset3DBtn.addEventListener('click', (e) => {
3786 e.preventDefault();
3787 console.log('ðŊ Reset 3D view clicked');
3788 this.reset3DView();
3789 });
3790 console.log('â
Reset 3D button event bound');
3791 } else {
3792 console.error('â reset3DView button not found');
3793 }
3794
3795 if (autoRotateBtn) {
3796 // Remove any existing event listeners
3797 autoRotateBtn.replaceWith(autoRotateBtn.cloneNode(true));
3798 const newAutoRotateBtn = document.getElementById('autoRotate3D');
3799
3800 newAutoRotateBtn.addEventListener('click', (e) => {
3801 e.preventDefault();
3802 console.log('ðŊ Auto rotate clicked');
3803 this.toggleAutoRotate();
3804 });
3805 console.log('â
Auto rotate button event bound');
3806 } else {
3807 console.error('â autoRotate3D button not found');
3808 }
3809
3810 if (focusLargestBtn) {
3811 // Remove any existing event listeners
3812 focusLargestBtn.replaceWith(focusLargestBtn.cloneNode(true));
3813 const newFocusLargestBtn = document.getElementById('focusLargest');
3814
3815 newFocusLargestBtn.addEventListener('click', (e) => {
3816 e.preventDefault();
3817 console.log('ðŊ Focus largest clicked');
3818 this.focusOnLargestBlock();
3819 });
3820 console.log('â
Focus largest button event bound');
3821 } else {
3822 console.error('â focusLargest button not found');
3823 }
3824 }, 500); // Wait 500ms for DOM to be ready
3825
3826 // Handle window resize
3827 window.addEventListener('resize', () => {
3828 if (this.camera && this.renderer) {
3829 const container = document.getElementById('memory3DContainer');
3830 if (container) {
3831 this.camera.aspect = container.clientWidth / container.clientHeight;
3832 this.camera.updateProjectionMatrix();
3833 this.renderer.setSize(container.clientWidth, container.clientHeight);
3834 }
3835 }
3836 });
3837 }
3838
3839 addButtonFeedback() {
3840 // Add hover and click effects to all 3D control buttons
3841 const buttonIds = ['toggle3DView', 'reset3DView', 'autoRotate3D', 'focusLargest'];
3842
3843 buttonIds.forEach(id => {
3844 const btn = document.getElementById(id);
3845 if (btn) {
3846 // Add hover effect
3847 btn.addEventListener('mouseenter', () => {
3848 btn.style.transform = 'scale(1.05)';
3849 btn.style.transition = 'all 0.2s ease';
3850 });
3851
3852 btn.addEventListener('mouseleave', () => {
3853 btn.style.transform = 'scale(1)';
3854 });
3855
3856 // Add click effect
3857 btn.addEventListener('mousedown', () => {
3858 btn.style.transform = 'scale(0.95)';
3859 });
3860
3861 btn.addEventListener('mouseup', () => {
3862 btn.style.transform = 'scale(1.05)';
3863 });
3864
3865 console.log(`â
Added feedback effects to ${id}`);
3866 }
3867 });
3868 }
3869
3870 reset3DView() {
3871 console.log('ð Resetting 3D view...');
3872
3873 // Show visual feedback
3874 const resetBtn = document.getElementById('reset3DView');
3875 if (resetBtn) {
3876 resetBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i><span>Resetting...</span>';
3877 resetBtn.style.background = 'var(--primary-yellow)';
3878 }
3879
3880 if (this.camera && this.controls) {
3881 // Reset camera position
3882 this.camera.position.set(15, 10, 15);
3883 this.camera.lookAt(0, 0, 0);
3884
3885 // Reset controls
3886 this.controls.reset();
3887
3888 // Update camera
3889 this.camera.updateProjectionMatrix();
3890
3891 // Restore button
3892 setTimeout(() => {
3893 if (resetBtn) {
3894 resetBtn.innerHTML = '<i class="fa fa-refresh"></i><span>Reset</span>';
3895 resetBtn.style.background = 'var(--primary-orange)';
3896 }
3897 }, 500);
3898
3899 console.log('â
3D view reset complete');
3900 } else {
3901 console.error('â Camera or controls not available for reset');
3902 if (resetBtn) {
3903 resetBtn.innerHTML = '<i class="fa fa-exclamation"></i><span>Error</span>';
3904 resetBtn.style.background = 'var(--primary-red)';
3905 setTimeout(() => {
3906 resetBtn.innerHTML = '<i class="fa fa-refresh"></i><span>Reset</span>';
3907 resetBtn.style.background = 'var(--primary-orange)';
3908 }, 1000);
3909 }
3910 }
3911
3912 // Animation function
3913 const animateReset = () => {
3914 // Animation logic here if needed
3915 };
3916 animateReset();
3917 }
3918
3919 toggleAutoRotate() {
3920 console.log('ð Toggling auto rotate...');
3921 if (this.controls) {
3922 this.controls.autoRotate = !this.controls.autoRotate;
3923 this.controls.autoRotateSpeed = 2.0; // Set rotation speed
3924
3925 const btn = document.getElementById('autoRotate3D');
3926 if (btn) {
3927 if (this.controls.autoRotate) {
3928 btn.innerHTML = '<i class="fa fa-pause"></i><span>Stop Rotate</span>';
3929 btn.style.background = 'var(--primary-red)';
3930 console.log('â
Auto rotate enabled');
3931 } else {
3932 btn.innerHTML = '<i class="fa fa-rotate-right"></i><span>Auto Rotate</span>';
3933 btn.style.background = 'var(--primary-blue)';
3934 console.log('â
Auto rotate disabled');
3935 }
3936 }
3937 } else {
3938 console.error('â Controls not available for auto rotate');
3939 const btn = document.getElementById('autoRotate3D');
3940 if (btn) {
3941 btn.innerHTML = '<i class="fa fa-exclamation"></i><span>Error</span>';
3942 btn.style.background = 'var(--primary-red)';
3943 setTimeout(() => {
3944 btn.innerHTML = '<i class="fa fa-rotate-right"></i><span>Auto Rotate</span>';
3945 btn.style.background = 'var(--primary-blue)';
3946 }, 1000);
3947 }
3948 }
3949 }
3950
3951 focusOnLargestBlock() {
3952 console.log('ðŊ Focusing on largest block...');
3953
3954 // Show visual feedback
3955 const focusBtn = document.getElementById('focusLargest');
3956 if (focusBtn) {
3957 focusBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i><span>Focusing...</span>';
3958 focusBtn.style.background = 'var(--primary-yellow)';
3959 }
3960
3961 if (!this.memoryBlocks || this.memoryBlocks.length === 0) {
3962 console.warn('â No memory blocks to focus on');
3963 if (focusBtn) {
3964 focusBtn.innerHTML = '<i class="fa fa-exclamation"></i><span>No Blocks</span>';
3965 focusBtn.style.background = 'var(--primary-red)';
3966 setTimeout(() => {
3967 focusBtn.innerHTML = '<i class="fa fa-search-plus"></i><span>Focus Largest</span>';
3968 focusBtn.style.background = 'var(--primary-red)';
3969 }, 1500);
3970 }
3971 return;
3972 }
3973
3974 // Find the largest block
3975 let largestBlock = null;
3976 let largestSize = 0;
3977
3978 this.memoryBlocks.forEach(block => {
3979 const size = block.userData?.size || 0;
3980 if (size > largestSize) {
3981 largestSize = size;
3982 largestBlock = block;
3983 }
3984 });
3985
3986 if (largestBlock && this.camera && this.controls) {
3987 // Calculate optimal camera position
3988 const blockPos = largestBlock.position;
3989 const distance = Math.max(5, Math.sqrt(largestSize) / 10);
3990
3991 // Position camera at an angle for better view
3992 const targetPosition = new THREE.Vector3(
3993 blockPos.x + distance,
3994 blockPos.y + distance * 0.7,
3995 blockPos.z + distance
3996 );
3997
3998 // Smooth camera transition
3999 const startPos = this.camera.position.clone();
4000 let progress = 0;
4001
4002 const animateFocus = () => {
4003 progress += 0.05;
4004 if (progress <= 1) {
4005 this.camera.position.lerpVectors(startPos, targetPosition, progress);
4006 this.camera.lookAt(blockPos);
4007 requestAnimationFrame(animateFocus);
4008 } else {
4009 // Animation complete
4010 console.log(`â
Focused on largest block: ${largestBlock.userData?.var_name || 'unknown'} (${this.formatBytes(largestSize)})`);
4011 this.update3DInfo(this.memoryBlocks.length);
4012
4013 // Restore button
4014 if (focusBtn) {
4015 focusBtn.innerHTML = '<i class="fa fa-search-plus"></i><span>Focus Largest</span>';
4016 focusBtn.style.background = 'var(--primary-red)';
4017 }
4018 }
4019 };
4020 animateFocus();
4021 } else {
4022 console.error('â Camera or controls not available for focus');
4023 if (focusBtn) {
4024 focusBtn.innerHTML = '<i class="fa fa-exclamation"></i><span>Error</span>';
4025 focusBtn.style.background = 'var(--primary-red)';
4026 setTimeout(() => {
4027 focusBtn.innerHTML = '<i class="fa fa-search-plus"></i><span>Focus Largest</span>';
4028 focusBtn.style.background = 'var(--primary-red)';
4029 }, 1000);
4030 }
4031 }
4032 }
4033
4034 // Main initialization method
4035 initializeWithData(analysisData) {
4036 console.log('initializeWithData called with:', analysisData);
4037
4038 let allocations = null;
4039
4040 // Try different data structure paths
4041 if (analysisData && analysisData.memory_analysis && analysisData.memory_analysis.allocations) {
4042 allocations = analysisData.memory_analysis.allocations;
4043 console.log('Found allocations in memory_analysis:', allocations.length);
4044 } else if (analysisData && analysisData.allocations) {
4045 allocations = analysisData.allocations;
4046 console.log('Found allocations directly:', allocations.length);
4047 } else {
4048 console.warn('No allocation data found in analysisData');
4049 console.log('Available keys:', Object.keys(analysisData || {}));
4050 return;
4051 }
4052
4053 if (!allocations || allocations.length === 0) {
4054 console.warn('No allocations to visualize');
4055 return;
4056 }
4057
4058 console.log(`Initializing enhanced visualization with ${allocations.length} allocations`);
4059
4060 // Initialize 3D visualization
4061 this.create3DMemoryBlocks(allocations);
4062
4063 // Initialize timeline
4064 this.prepareTimelineData(allocations);
4065
4066 // Initialize heatmap
4067 this.generateHeatmap(allocations);
4068
4069 // Update memory distribution visualization
4070 this.updateMemoryDistribution(allocations);
4071
4072 // Initialize memory fragmentation visualization
4073 this.initializeMemoryFragmentation(allocations);
4074
4075 console.log('Enhanced memory visualization initialized successfully');
4076 }
4077
4078 initializeMemoryFragmentation(allocations) {
4079 console.log('Initializing memory fragmentation with', allocations?.length || 0, 'allocations');
4080 const container = document.getElementById('memoryFragmentation');
4081 if (!container) {
4082 console.error('Memory fragmentation container not found');
4083 return;
4084 }
4085 if (!allocations || allocations.length === 0) {
4086 console.warn('No allocations data for fragmentation analysis');
4087 container.innerHTML = '<div style="text-align: center; color: var(--text-secondary); padding: 40px;">No allocation data available for fragmentation analysis</div>';
4088 return;
4089 }
4090
4091 console.log('Processing', allocations.length, 'allocations for fragmentation analysis');
4092
4093 // Calculate fragmentation metrics
4094 const sortedAllocs = [...allocations].sort((a, b) => {
4095 const addrA = parseInt(a.ptr || '0x0', 16);
4096 const addrB = parseInt(b.ptr || '0x0', 16);
4097 return addrA - addrB;
4098 });
4099
4100 const totalMemory = allocations.reduce((sum, alloc) => sum + (alloc.size || 0), 0);
4101 const addressRanges = [];
4102 let gaps = 0;
4103 let totalGapSize = 0;
4104
4105 // Calculate memory gaps
4106 for (let i = 0; i < sortedAllocs.length - 1; i++) {
4107 const currentAddr = parseInt(sortedAllocs[i].ptr || '0x0', 16);
4108 const currentSize = sortedAllocs[i].size || 0;
4109 const nextAddr = parseInt(sortedAllocs[i + 1].ptr || '0x0', 16);
4110
4111 const currentEnd = currentAddr + currentSize;
4112 const gap = nextAddr - currentEnd;
4113
4114 if (gap > 0) {
4115 gaps++;
4116 totalGapSize += gap;
4117 addressRanges.push({
4118 type: 'gap',
4119 start: currentEnd,
4120 size: gap,
4121 index: i
4122 });
4123 }
4124
4125 addressRanges.push({
4126 type: 'allocation',
4127 start: currentAddr,
4128 size: currentSize,
4129 allocation: sortedAllocs[i],
4130 index: i
4131 });
4132 }
4133
4134 // Calculate fragmentation percentage
4135 const fragmentation = totalMemory > 0 ? (totalGapSize / (totalMemory + totalGapSize)) * 100 : 0;
4136 const efficiency = Math.max(100 - fragmentation, 0);
4137
4138 console.log('Fragmentation metrics:', { gaps, totalGapSize, fragmentation, efficiency });
4139
4140 // Create visualization
4141 container.innerHTML = `
4142 <div style="margin-bottom: 16px;">
4143 <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 16px;">
4144 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
4145 <div style="font-size: 1.4rem; font-weight: 700; color: var(--primary-red);">${fragmentation.toFixed(1)}%</div>
4146 <div style="font-size: 0.7rem; color: var(--text-secondary);">Fragmentation</div>
4147 </div>
4148 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
4149 <div style="font-size: 1.4rem; font-weight: 700; color: var(--primary-orange);">${gaps}</div>
4150 <div style="font-size: 0.7rem; color: var(--text-secondary);">Memory Gaps</div>
4151 </div>
4152 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
4153 <div style="font-size: 1.4rem; font-weight: 700; color: var(--primary-green);">${efficiency.toFixed(1)}%</div>
4154 <div style="font-size: 0.7rem; color: var(--text-secondary);">Efficiency</div>
4155 </div>
4156 </div>
4157
4158 <div style="margin-bottom: 12px;">
4159 <h4 style="margin: 0 0 8px 0; font-size: 0.9rem; color: var(--text-primary);">Memory Layout Visualization</h4>
4160 <div id="fragmentationChart" style="height: 120px; background: var(--bg-secondary); border: 1px solid var(--border-light); border-radius: 6px; position: relative; overflow: hidden;">
4161 <!-- Memory blocks will be inserted here -->
4162 </div>
4163 </div>
4164
4165 <div style="font-size: 0.8rem; color: var(--text-secondary); text-align: center;">
4166 <div style="display: flex; justify-content: center; gap: 16px; margin-top: 8px;">
4167 <div style="display: flex; align-items: center; gap: 4px;">
4168 <div style="width: 12px; height: 12px; background: var(--primary-blue); border-radius: 2px;"></div>
4169 <span>Allocated</span>
4170 </div>
4171 <div style="display: flex; align-items: center; gap: 4px;">
4172 <div style="width: 12px; height: 12px; background: var(--primary-red); border-radius: 2px;"></div>
4173 <span>Gaps</span>
4174 </div>
4175 <div style="display: flex; align-items: center; gap: 4px;">
4176 <div style="width: 12px; height: 12px; background: var(--primary-orange); border-radius: 2px;"></div>
4177 <span>Leaked</span>
4178 </div>
4179 </div>
4180 </div>
4181 </div>
4182 `;
4183
4184 // Draw memory layout visualization
4185 this.drawFragmentationChart(addressRanges, totalMemory + totalGapSize);
4186 }
4187
4188 drawFragmentationChart(addressRanges, totalSize) {
4189 const chartContainer = document.getElementById('fragmentationChart');
4190 if (!chartContainer || addressRanges.length === 0) return;
4191
4192 const width = chartContainer.clientWidth;
4193 const height = chartContainer.clientHeight;
4194
4195 let currentX = 0;
4196
4197 addressRanges.forEach((range, index) => {
4198 const blockWidth = Math.max((range.size / totalSize) * width, 1);
4199
4200 const block = document.createElement('div');
4201 block.style.position = 'absolute';
4202 block.style.left = `${currentX}px`;
4203 block.style.top = '10px';
4204 block.style.width = `${blockWidth}px`;
4205 block.style.height = `${height - 20}px`;
4206 block.style.borderRadius = '2px';
4207 block.style.cursor = 'pointer';
4208 block.style.transition = 'all 0.2s ease';
4209
4210 if (range.type === 'gap') {
4211 block.style.background = 'linear-gradient(45deg, #dc2626, #ef4444)';
4212 block.style.border = '1px solid #b91c1c';
4213 block.title = `Memory Gap: ${this.formatBytes(range.size)}`;
4214 } else if (range.allocation && range.allocation.is_leaked) {
4215 block.style.background = 'linear-gradient(45deg, #ea580c, #f97316)';
4216 block.style.border = '1px solid #c2410c';
4217 block.title = `Leaked: ${range.allocation.var_name} (${this.formatBytes(range.size)})`;
4218 } else {
4219 block.style.background = 'linear-gradient(45deg, #2563eb, #3b82f6)';
4220 block.style.border = '1px solid #1d4ed8';
4221 block.title = range.allocation ?
4222 `${range.allocation.var_name}: ${range.allocation.type_name} (${this.formatBytes(range.size)})` :
4223 `Allocation: ${this.formatBytes(range.size)}`;
4224 }
4225
4226 // Add hover effects
4227 block.addEventListener('mouseenter', () => {
4228 block.style.transform = 'scaleY(1.2)';
4229 block.style.zIndex = '10';
4230 });
4231
4232 block.addEventListener('mouseleave', () => {
4233 block.style.transform = 'scaleY(1)';
4234 block.style.zIndex = '1';
4235 });
4236
4237 chartContainer.appendChild(block);
4238 currentX += blockWidth;
4239 });
4240 }
4241
4242 updateMemoryDistribution(allocations) {
4243 const container = document.getElementById('memoryDistributionViz');
4244 if (!container || !allocations) return;
4245
4246 container.innerHTML = '';
4247
4248 const containerWidth = container.clientWidth;
4249 const containerHeight = container.clientHeight;
4250 const totalMemory = allocations.reduce((sum, alloc) => sum + (alloc.size || 0), 0);
4251
4252 // č·åå―åįžĐæūæŊäū
4253 const scaleSlider = document.getElementById('memoryDistScale');
4254 const scale = scaleSlider ? parseFloat(scaleSlider.value) / 100 : 1;
4255
4256 // čŪĄįŪåŪé
åŊįĻåŪ―åšĶïžččįžĐæūåčūđč·ïž
4257 const padding = 20;
4258 const availableWidth = (containerWidth - padding * 2) * scale;
4259 const availableHeight = containerHeight - padding * 2;
4260
4261 // ååŧšäļäļŠåŊæŧåĻįå
åŪđåŪđåĻ
4262 const contentContainer = document.createElement('div');
4263 contentContainer.style.cssText = `
4264 position: absolute;
4265 top: ${padding}px;
4266 left: ${padding}px;
4267 width: ${Math.max(availableWidth, containerWidth - padding * 2)}px;
4268 height: ${availableHeight}px;
4269 overflow-x: auto;
4270 overflow-y: hidden;
4271 `;
4272
4273 // ååŧšå
åååŪđåĻ
4274 const blocksContainer = document.createElement('div');
4275 blocksContainer.style.cssText = `
4276 position: relative;
4277 width: ${availableWidth}px;
4278 height: 100%;
4279 min-width: ${containerWidth - padding * 2}px;
4280 `;
4281
4282 let currentX = 0;
4283 let fragmentation = 0;
4284 let efficiency = 0;
4285
4286 // Sort by address for fragmentation calculation
4287 const sortedAllocs = [...allocations].sort((a, b) => {
4288 const addrA = parseInt(a.ptr || '0x0', 16);
4289 const addrB = parseInt(b.ptr || '0x0', 16);
4290 return addrA - addrB;
4291 });
4292
4293 // čŪĄįŪæŊäļŠåįæå°åŪ―åšĶåæŧåŪ―åšĶéæą
4294 const minBlockWidth = 3; // æå°ååŪ―åšĶ
4295 const blockGap = 1;
4296 let totalRequiredWidth = 0;
4297
4298 sortedAllocs.forEach((alloc) => {
4299 const proportionalWidth = (alloc.size || 0) / totalMemory * availableWidth;
4300 const blockWidth = Math.max(proportionalWidth, minBlockWidth);
4301 totalRequiredWidth += blockWidth + blockGap;
4302 });
4303
4304 // åĶææŧåŪ―åšĶčķ
čŋåŊįĻåŪ―åšĶïžč°æīåŪđåĻåŪ―åšĶ
4305 const finalContainerWidth = Math.max(totalRequiredWidth, availableWidth);
4306 blocksContainer.style.width = `${finalContainerWidth}px`;
4307
4308 sortedAllocs.forEach((alloc, index) => {
4309 const proportionalWidth = (alloc.size || 0) / totalMemory * availableWidth;
4310 const blockWidth = Math.max(proportionalWidth, minBlockWidth);
4311 const blockHeight = availableHeight * 0.7;
4312
4313 const block = document.createElement('div');
4314 block.className = 'memory-block';
4315 block.style.cssText = `
4316 position: absolute;
4317 left: ${currentX}px;
4318 top: ${(availableHeight - blockHeight) / 2}px;
4319 width: ${blockWidth}px;
4320 height: ${blockHeight}px;
4321 border-radius: 2px;
4322 cursor: pointer;
4323 transition: all 0.2s ease;
4324 border: 1px solid rgba(255, 255, 255, 0.3);
4325 `;
4326
4327 // Determine allocation type and style
4328 if (alloc.is_leaked) {
4329 block.classList.add('leaked');
4330 block.style.background = 'linear-gradient(45deg, #dc2626, #ef4444)';
4331 block.style.boxShadow = '0 0 8px rgba(239, 68, 68, 0.5)';
4332 } else if (alloc.type_name && alloc.type_name.includes('Box')) {
4333 block.classList.add('heap');
4334 block.style.background = 'linear-gradient(45deg, #ff6b35, #f7931e)';
4335 } else {
4336 block.classList.add('stack');
4337 block.style.background = 'linear-gradient(45deg, #4dabf7, #339af0)';
4338 }
4339
4340 // Enhanced hover effects
4341 block.addEventListener('mouseenter', (e) => {
4342 block.style.transform = 'scaleY(1.2) translateY(-2px)';
4343 block.style.zIndex = '10';
4344 block.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
4345 this.showTooltip(e, alloc);
4346 });
4347
4348 block.addEventListener('mouseleave', () => {
4349 block.style.transform = 'scaleY(1) translateY(0)';
4350 block.style.zIndex = '1';
4351 block.style.boxShadow = 'none';
4352 this.hideTooltip();
4353 });
4354
4355 // æ·ŧå įđåŧäšäŧķæūįĪščŊĶįŧäŋĄæŊ
4356 block.addEventListener('click', () => {
4357 this.showBlockDetails(alloc);
4358 });
4359
4360 blocksContainer.appendChild(block);
4361 currentX += blockWidth + blockGap;
4362 });
4363
4364 contentContainer.appendChild(blocksContainer);
4365 container.appendChild(contentContainer);
4366
4367 // Calculate fragmentation and efficiency
4368 fragmentation = totalRequiredWidth > availableWidth ?
4369 ((totalRequiredWidth - availableWidth) / totalRequiredWidth * 100) : 0;
4370 efficiency = Math.max(100 - fragmentation, 0);
4371
4372 // Update metrics
4373 const fragEl = document.getElementById('memoryFragmentation');
4374 const effEl = document.getElementById('memoryEfficiency');
4375
4376 // Use global safe update function (no need to redefine)
4377
4378 safeUpdateElement('memoryFragmentation', `${fragmentation.toFixed(1)}%`);
4379 safeUpdateElement('memoryEfficiency', `${efficiency.toFixed(1)}%`);
4380
4381 // Setup dynamic controls
4382 this.setupMemoryDistributionControls(allocations);
4383
4384 // Update other metrics with real data
4385 this.updateEnhancedMetrics(allocations);
4386 }
4387
4388 setupMemoryDistributionControls(allocations) {
4389 const scaleSlider = document.getElementById('memoryDistScale');
4390 const scaleValue = document.getElementById('memoryDistScaleValue');
4391 const fitBtn = document.getElementById('memoryDistFit');
4392 const resetBtn = document.getElementById('memoryDistReset');
4393
4394 if (scaleSlider && scaleValue) {
4395 scaleSlider.addEventListener('input', (e) => {
4396 const value = e.target.value;
4397 safeUpdateElement('memoryDistScaleValue', `${value}%`);
4398 this.updateMemoryDistribution(allocations);
4399 });
4400 }
4401
4402 if (fitBtn) {
4403 fitBtn.addEventListener('click', () => {
4404 // čŠåĻčŪĄįŪæä―ģįžĐæūæŊäū
4405 const container = document.getElementById('memoryDistributionViz');
4406 if (container && allocations) {
4407 const containerWidth = container.clientWidth - 40; // ååŧpadding
4408 const totalMemory = allocations.reduce((sum, alloc) => sum + (alloc.size || 0), 0);
4409 const minBlockWidth = 3;
4410 const blockGap = 1;
4411
4412 let requiredWidth = 0;
4413 allocations.forEach(() => {
4414 requiredWidth += minBlockWidth + blockGap;
4415 });
4416
4417 const optimalScale = Math.min(200, Math.max(50, (containerWidth / requiredWidth) * 100));
4418
4419 if (scaleSlider) {
4420 scaleSlider.value = optimalScale;
4421 safeUpdateElement('memoryDistScaleValue', `${Math.round(optimalScale)}%`);
4422 this.updateMemoryDistribution(allocations);
4423 }
4424 }
4425 });
4426 }
4427
4428 if (resetBtn) {
4429 resetBtn.addEventListener('click', () => {
4430 if (scaleSlider) {
4431 scaleSlider.value = 100;
4432 safeUpdateElement('memoryDistScaleValue', '100%');
4433 this.updateMemoryDistribution(allocations);
4434 }
4435 });
4436 }
4437 }
4438
4439 showBlockDetails(alloc) {
4440 // ååŧščŊĶįŧäŋĄæŊåžđįŠ
4441 const modal = document.createElement('div');
4442 modal.style.cssText = `
4443 position: fixed;
4444 top: 0;
4445 left: 0;
4446 width: 100%;
4447 height: 100%;
4448 background: rgba(0,0,0,0.5);
4449 z-index: 1000;
4450 display: flex;
4451 align-items: center;
4452 justify-content: center;
4453 `;
4454
4455 const content = document.createElement('div');
4456 content.style.cssText = `
4457 background: var(--bg-primary);
4458 border-radius: 12px;
4459 padding: 24px;
4460 max-width: 400px;
4461 width: 90%;
4462 box-shadow: 0 20px 40px rgba(0,0,0,0.3);
4463 color: var(--text-primary);
4464 `;
4465
4466 content.innerHTML = `
4467 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
4468 <h3 style="margin: 0; color: var(--primary-blue);">Memory Block Details</h3>
4469 <button id="closeModal" style="background: none; border: none; font-size: 20px; cursor: pointer; color: var(--text-secondary);">×</button>
4470 </div>
4471 <div style="line-height: 1.6;">
4472 <div><strong>Variable:</strong> ${alloc.var_name || 'Unknown'}</div>
4473 <div><strong>Type:</strong> ${alloc.type_name || 'Unknown'}</div>
4474 <div><strong>Size:</strong> ${this.formatBytes(alloc.size || 0)}</div>
4475 <div><strong>Address:</strong> ${alloc.ptr || 'N/A'}</div>
4476 <div><strong>Status:</strong> ${alloc.is_leaked ? 'ðĻ Leaked' : 'â
Active'}</div>
4477 <div><strong>Lifetime:</strong> ${alloc.lifetime_ms ? alloc.lifetime_ms.toFixed(2) + 'ms' : 'N/A'}</div>
4478 <div><strong>Scope:</strong> ${alloc.scope_name || 'Unknown'}</div>
4479 </div>
4480 `;
4481
4482 modal.appendChild(content);
4483 document.body.appendChild(modal);
4484
4485 // å
ģéäšäŧķ
4486 const closeModal = () => {
4487 document.body.removeChild(modal);
4488 };
4489
4490 modal.addEventListener('click', (e) => {
4491 if (e.target === modal) closeModal();
4492 });
4493
4494 content.querySelector('#closeModal').addEventListener('click', closeModal);
4495 }
4496
4497 updateEnhancedMetrics(allocations) {
4498 if (!allocations || allocations.length === 0) return;
4499
4500 // Calculate real metrics from data
4501 const totalAllocs = allocations.length;
4502 const totalMemory = allocations.reduce((sum, alloc) => sum + (alloc.size || 0), 0);
4503 const avgLifetime = allocations.reduce((sum, alloc) => sum + (alloc.lifetime_ms || 0), 0) / totalAllocs;
4504 const heapAllocs = allocations.filter(a => a.type_name && (a.type_name.includes('Box') || a.type_name.includes('Vec'))).length;
4505 const stackAllocs = totalAllocs - heapAllocs;
4506 const heapStackRatio = stackAllocs > 0 ? (heapAllocs / stackAllocs).toFixed(2) : heapAllocs.toString();
4507
4508 // Update KPI cards
4509 const totalAllocsEl = document.getElementById('total-allocations');
4510 const activeVarsEl = document.getElementById('active-variables');
4511 const totalMemoryEl = document.getElementById('total-memory');
4512 const avgLifetimeEl = document.getElementById('avg-lifetime');
4513 const peakMemoryEl = document.getElementById('peak-memory');
4514 const allocRateEl = document.getElementById('allocation-rate');
4515 const fragmentationEl = document.getElementById('fragmentation');
4516
4517 safeUpdateElement('total-allocs', totalAllocs);
4518 safeUpdateElement('active-vars', allocations.filter(a => !a.is_leaked).length);
4519 safeUpdateElement('total-memory', this.formatBytes(totalMemory));
4520 safeUpdateElement('avg-lifetime', `${avgLifetime.toFixed(2)}ms`);
4521 safeUpdateElement('peak-memory', this.formatBytes(Math.max(...allocations.map(a => a.size || 0))));
4522
4523 // Calculate allocation rate (allocations per microsecond)
4524 const timeSpan = Math.max(...allocations.map(a => a.timestamp_alloc || 0)) - Math.min(...allocations.map(a => a.timestamp_alloc || 0));
4525 const allocRate = timeSpan > 0 ? ((totalAllocs / (timeSpan / 1000000)).toFixed(2) + '/sec') : '0/sec';
4526 safeUpdateElement('allocation-rate', allocRate);
4527
4528 // Update enhanced statistics
4529 const totalAllocsEnhancedEl = document.getElementById('total-allocs-enhanced');
4530 const heapStackRatioEl = document.getElementById('heap-stack-ratio');
4531 const avgLifetimeEnhancedEl = document.getElementById('avg-lifetime-enhanced');
4532 const memoryEfficiencyEl = document.getElementById('memory-efficiency');
4533
4534 safeUpdateElement('total-allocs-enhanced', totalAllocs);
4535 safeUpdateElement('heap-stack-ratio', heapStackRatio);
4536 safeUpdateElement('avg-lifetime-enhanced', `${avgLifetime.toFixed(1)}ms`);
4537 safeUpdateElement('memory-efficiency', `${((totalMemory / (totalAllocs * 100)) * 100).toFixed(1)}%`);
4538
4539 // Update type counts
4540 safeUpdateElement('arc-count', allocations.filter(a => a.type_name && a.type_name.includes('Arc')).length);
4541 safeUpdateElement('rc-count', allocations.filter(a => a.type_name && a.type_name.includes('Rc')).length);
4542 safeUpdateElement('collections-count', allocations.filter(a => a.type_name && (a.type_name.includes('Vec') || a.type_name.includes('HashMap'))).length);
4543 }
4544
4545 showTooltip(event, alloc) {
4546 if (!this.tooltip) return;
4547
4548 this.tooltip.innerHTML = `
4549 <strong>${alloc.var_name || 'Unknown'}</strong><br>
4550 Type: ${alloc.type_name || 'Unknown'}<br>
4551 Size: ${this.formatBytes(alloc.size || 0)}<br>
4552 Address: ${alloc.ptr || 'N/A'}<br>
4553 Status: ${alloc.is_leaked ? 'Leaked' : 'Active'}<br>
4554 Lifetime: ${alloc.lifetime_ms ? alloc.lifetime_ms.toFixed(2) + 'ms' : 'N/A'}
4555 `;
4556
4557 this.tooltip.style.display = 'block';
4558 this.tooltip.style.left = `${event.pageX + 10}px`;
4559 this.tooltip.style.top = `${event.pageY - 10}px`;
4560 }
4561
4562 hideTooltip() {
4563 if (this.tooltip) {
4564 this.tooltip.style.display = 'none';
4565 }
4566 }
4567
4568 formatBytes(bytes) {
4569 if (bytes === 0) return '0 B';
4570 const k = 1024;
4571 const sizes = ['B', 'KB', 'MB', 'GB'];
4572 const i = Math.floor(Math.log(bytes) / Math.log(k));
4573 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
4574 }
4575 }
4576
4577 // Global instance
4578 window.enhancedVisualizer = new EnhancedMemoryVisualizer();
4579
4580 // Global function to bind 3D controls - can be called from console for debugging
4581 window.bind3DControls = function() {
4582 console.log('ð§ Manually binding 3D controls...');
4583 if (window.enhancedVisualizer) {
4584 window.enhancedVisualizer.bindEvents();
4585 }
4586 };
4587
4588 // Ensure 3D controls are bound when DOM is ready
4589 // Safety Risk Data and Functions
4590 window.safetyRisks = [];
4591
4592 function getRiskAssessment(risk) {
4593 if (risk.risk_level === 'High') {
4594 return 'Critical memory safety issue - immediate attention required';
4595 } else if (risk.risk_level === 'Medium') {
4596 return 'Potential memory issue - review recommended';
4597 } else {
4598 return 'Low risk - monitoring suggested';
4599 }
4600 }
4601
4602 function loadSafetyRisks() {
4603 console.log('ðĄïļ Loading safety risk data...');
4604 const unsafeTable = document.getElementById('unsafeTable');
4605 if (!unsafeTable) {
4606 console.warn('â ïļ unsafeTable not found');
4607 return;
4608 }
4609
4610 const risks = window.safetyRisks || [];
4611 if (risks.length === 0) {
4612 unsafeTable.innerHTML = '<tr><td colspan="3" class="text-center text-gray-500">No safety risks detected</td></tr>';
4613 return;
4614 }
4615
4616 unsafeTable.innerHTML = '';
4617 risks.forEach((risk, index) => {
4618 const row = document.createElement('tr');
4619 row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700';
4620
4621 const riskLevelClass = risk.risk_level === 'High' ? 'text-red-600 font-bold' :
4622 risk.risk_level === 'Medium' ? 'text-yellow-600 font-semibold' :
4623 'text-green-600';
4624
4625 row.innerHTML = `
4626 <td class="px-3 py-2 text-sm">${risk.location || 'Unknown'}</td>
4627 <td class="px-3 py-2 text-sm">${risk.operation || 'Unknown'}</td>
4628 <td class="px-3 py-2 text-sm"><span class="${riskLevelClass}">${risk.risk_level || 'Low'}</span></td>
4629 `;
4630 unsafeTable.appendChild(row);
4631 });
4632
4633 console.log('â
Safety risks loaded:', risks.length, 'items');
4634 }
4635
4636 document.addEventListener('DOMContentLoaded', function() {
4637 console.log('ð DOM loaded, binding 3D controls...');
4638 setTimeout(() => {
4639 if (window.enhancedVisualizer) {
4640 window.enhancedVisualizer.bindEvents();
4641 }
4642 }, 1000);
4643 });
4644
4645 // Enhanced Unsafe Rust & FFI Memory Analysis
4646 // Global variables for unsafe analysis - must be declared at global scope
4647 window.unsafeAnalysisCurrentFilter = 'critical';
4648 window.unsafeAnalysisData = []; // Initialize as empty array at global scope
4649 window.timelineZoomLevel = 1;
4650 window.timelineOffset = 0;
4651
4652 function initializeEnhancedUnsafeAnalysis() {
4653 console.log('ð§ Initializing Enhanced Unsafe Rust & FFI Memory Analysis...');
4654
4655 // Initialize unsafeAnalysisData if not already done
4656 if (!window.unsafeAnalysisData || window.unsafeAnalysisData.length === 0) {
4657 console.log('ð Initializing window.unsafeAnalysisData...');
4658 window.unsafeAnalysisData = [];
4659 }
4660
4661 // Load unsafe/FFI data from multiple sources
4662 const allocations = window.analysisData?.memory_analysis?.allocations || [];
4663 const unsafeFfiData = loadUnsafeFfiSnapshot();
4664
4665 // Transform and merge data for enhanced analysis
4666 try {
4667 window.unsafeAnalysisData = transformUnsafeAnalysisData(allocations, unsafeFfiData);
4668 console.log('â
Successfully transformed unsafe analysis data:', window.unsafeAnalysisData.length, 'items');
4669 } catch (error) {
4670 console.error('â Error transforming unsafe analysis data:', error);
4671 window.unsafeAnalysisData = []; // Fallback to empty array
4672 }
4673
4674 updateUnsafeAnalysisStats(window.unsafeAnalysisData);
4675 setupEnhancedFilterControls();
4676 setupTimelineControls();
4677 setupMemoryPassportModal();
4678
4679 const filteredData = applyUnsafeAnalysisFilter(window.unsafeAnalysisCurrentFilter, window.unsafeAnalysisData);
4680 renderEnhancedUnsafeTimeline(filteredData);
4681
4682 console.log('â
Enhanced Unsafe Analysis initialized with', window.unsafeAnalysisData.length, 'memory objects');
4683 }
4684
4685 function loadUnsafeFfiSnapshot() {
4686 // Load from the JSON data we saw earlier
4687 try {
4688 if (window.unsafeFfiSnapshot) {
4689 return window.unsafeFfiSnapshot;
4690 }
4691 // Fallback to simulated data based on the structure we observed
4692 return generateEnhancedUnsafeData();
4693 } catch (error) {
4694 console.warn('Failed to load unsafe FFI snapshot, using simulated data');
4695 return generateEnhancedUnsafeData();
4696 }
4697 }
4698
4699 function generateEnhancedUnsafeData() {
4700 // Generate realistic unsafe/FFI data based on the JSON structure we analyzed
4701 const data = [];
4702 for (let i = 0; i < 50; i++) {
4703 const ptr = `0x${(0x60000000 + i * 0x40).toString(16)}`;
4704 const source = Math.random() < 0.4 ? 'UnsafeRust' : 'FfiC';
4705 const hasLeaks = Math.random() < 0.15;
4706 const hasBoundaryEvents = Math.random() < 0.25;
4707
4708 data.push({
4709 base: {
4710 ptr: ptr,
4711 size: [40, 256, 1024, 4096][Math.floor(Math.random() * 4)],
4712 timestamp_alloc: Date.now() * 1000000 + i * 100000,
4713 timestamp_dealloc: hasLeaks ? null : Date.now() * 1000000 + i * 100000 + Math.random() * 5000000,
4714 is_leaked: hasLeaks
4715 },
4716 source: source === 'UnsafeRust' ? {
4717 UnsafeRust: {
4718 unsafe_block_location: `src/lib.rs:${42 + i}:13`,
4719 risk_assessment: {
4720 risk_level: ['Low', 'Medium', 'High'][Math.floor(Math.random() * 3)],
4721 confidence_score: 0.7 + Math.random() * 0.3,
4722 risk_factors: [{
4723 factor_type: 'ManualMemoryManagement',
4724 severity: 3 + Math.random() * 7,
4725 description: 'Manual memory management in unsafe block'
4726 }]
4727 }
4728 }
4729 } : {
4730 FfiC: {
4731 resolved_function: {
4732 library_name: 'libc',
4733 function_name: 'malloc',
4734 risk_level: 'Medium'
4735 }
4736 }
4737 },
4738 cross_boundary_events: hasBoundaryEvents ? [{
4739 event_type: Math.random() < 0.5 ? 'FfiToRust' : 'RustToFfi',
4740 timestamp: Date.now() + i * 1000,
4741 from_context: source === 'UnsafeRust' ? 'rust_context' : 'ffi_context',
4742 to_context: source === 'UnsafeRust' ? 'ffi_context' : 'rust_context'
4743 }] : [],
4744 ffi_tracked: source === 'FfiC' || Math.random() < 0.3
4745 });
4746 }
4747 return data;
4748 }
4749
4750 function transformUnsafeAnalysisData(allocations, unsafeFfiData) {
4751 const transformed = [];
4752
4753 // Transform regular allocations to unsafe analysis format
4754 allocations.forEach(alloc => {
4755 if (alloc.type_name && (alloc.type_name.includes('*') || alloc.type_name.includes('unsafe'))) {
4756 transformed.push({
4757 ...alloc,
4758 analysis_type: 'regular_allocation',
4759 risk_level: 'Low',
4760 has_boundary_events: false
4761 });
4762 }
4763 });
4764
4765 // Add enhanced unsafe/FFI data
4766 unsafeFfiData.forEach(unsafeItem => {
4767 transformed.push({
4768 ...unsafeItem,
4769 analysis_type: 'unsafe_ffi',
4770 risk_level: unsafeItem.source?.UnsafeRust?.risk_assessment?.risk_level || 'Medium',
4771 has_boundary_events: unsafeItem.cross_boundary_events && unsafeItem.cross_boundary_events.length > 0
4772 });
4773 });
4774
4775 return transformed;
4776 }
4777
4778 function updateUnsafeAnalysisStats(data) {
4779 const criticalCount = data.filter(d => d.risk_level === 'High' || d.base?.is_leaked).length;
4780 const leakCount = data.filter(d => d.base?.is_leaked).length;
4781 const boundaryCount = data.filter(d => d.has_boundary_events).length;
4782
4783 safeUpdateElement('unsafe-critical-count', criticalCount);
4784 safeUpdateElement('unsafe-leak-count', leakCount);
4785 safeUpdateElement('unsafe-boundary-count', boundaryCount);
4786 safeUpdateElement('unsafe-total-count', data.length);
4787 }
4788
4789 function applyUnsafeAnalysisFilter(filterType, data) {
4790 switch(filterType) {
4791 case 'critical':
4792 return data.filter(d => d.risk_level === 'High' || d.base?.is_leaked);
4793 case 'leaks':
4794 return data.filter(d => d.base?.is_leaked);
4795 case 'cross-boundary':
4796 return data.filter(d => d.has_boundary_events);
4797 case 'risk-assessment':
4798 return data.filter(d => d.source?.UnsafeRust?.risk_assessment);
4799 case 'all':
4800 default:
4801 return data;
4802 }
4803 }
4804
4805 function setupEnhancedFilterControls() {
4806 const filterTabs = document.querySelectorAll('.unsafe-filter-tab');
4807
4808 filterTabs.forEach(tab => {
4809 tab.addEventListener('click', () => {
4810 filterTabs.forEach(t => t.classList.remove('active'));
4811 tab.classList.add('active');
4812
4813 window.unsafeAnalysisCurrentFilter = tab.dataset.filter;
4814
4815 const filteredData = applyUnsafeAnalysisFilter(window.unsafeAnalysisCurrentFilter, window.unsafeAnalysisData);
4816 renderEnhancedUnsafeTimeline(filteredData);
4817 });
4818 });
4819 }
4820
4821 function setupTimelineControls() {
4822 document.getElementById('timelineZoomIn')?.addEventListener('click', () => {
4823 window.timelineZoomLevel *= 1.5;
4824 rerenderTimeline();
4825 });
4826
4827 document.getElementById('timelineZoomOut')?.addEventListener('click', () => {
4828 window.timelineZoomLevel /= 1.5;
4829 rerenderTimeline();
4830 });
4831
4832 document.getElementById('timelineReset')?.addEventListener('click', () => {
4833 window.timelineZoomLevel = 1;
4834 window.timelineOffset = 0;
4835 rerenderTimeline();
4836 });
4837 }
4838
4839 function setupMemoryPassportModal() {
4840 const modal = document.getElementById('memoryPassport');
4841 const closeBtn = modal?.querySelector('.passport-close');
4842
4843 closeBtn?.addEventListener('click', () => {
4844 modal.style.display = 'none';
4845 });
4846
4847 modal?.addEventListener('click', (e) => {
4848 if (e.target === modal) {
4849 modal.style.display = 'none';
4850 }
4851 });
4852 }
4853
4854 function showMemoryPassport(memoryObject) {
4855 const modal = document.getElementById('memoryPassport');
4856 const body = document.getElementById('passportBody');
4857
4858 if (!modal || !body) return;
4859
4860 // Generate passport content based on the memory object
4861 const passportContent = generatePassportContent(memoryObject);
4862 body.innerHTML = passportContent;
4863
4864 modal.style.display = 'flex';
4865 }
4866
4867 function generatePassportContent(memoryObject) {
4868 const ptr = memoryObject.base?.ptr || memoryObject.ptr || 'Unknown';
4869 const size = memoryObject.base?.size || memoryObject.size || 0;
4870 const isLeaked = memoryObject.base?.is_leaked || false;
4871 const riskLevel = memoryObject.risk_level || 'Unknown';
4872
4873 return `
4874 <div class="passport-section">
4875 <h4><i class="fa fa-info-circle"></i> Memory Passport: ${ptr}</h4>
4876 <div class="passport-grid">
4877 <div class="passport-item">
4878 <strong>Size:</strong> ${formatBytes(size)}
4879 </div>
4880 <div class="passport-item">
4881 <strong>Status:</strong>
4882 <span class="status-${isLeaked ? 'leaked' : 'normal'}">
4883 ${isLeaked ? 'ðĻ LEAKED' : 'â
Normal'}
4884 </span>
4885 </div>
4886 <div class="passport-item">
4887 <strong>Risk Level:</strong>
4888 <span class="risk-${riskLevel.toLowerCase()}">${riskLevel}</span>
4889 </div>
4890 <div class="passport-item">
4891 <strong>FFI Tracked:</strong> ${memoryObject.ffi_tracked ? 'â
Yes' : 'â No'}
4892 </div>
4893 </div>
4894 </div>
4895
4896 <div class="passport-section">
4897 <h4><i class="fa fa-timeline"></i> Lifecycle Log</h4>
4898 <div class="lifecycle-events">
4899 ${generateLifecycleEvents(memoryObject)}
4900 </div>
4901 </div>
4902
4903 ${memoryObject.source?.UnsafeRust?.risk_assessment ? `
4904 <div class="passport-section">
4905 <h4><i class="fa fa-exclamation-triangle"></i> Risk Assessment</h4>
4906 <div class="risk-details">
4907 ${generateRiskAssessment(memoryObject.source.UnsafeRust.risk_assessment)}
4908 </div>
4909 </div>
4910 ` : ''}
4911 `;
4912 }
4913
4914 function generateLifecycleEvents(memoryObject) {
4915 let events = '';
4916
4917 // Allocation event
4918 if (memoryObject.base?.timestamp_alloc) {
4919 events += `
4920 <div class="lifecycle-event allocation">
4921 <div class="event-icon">ðĒ</div>
4922 <div class="event-details">
4923 <strong>Allocation</strong><br>
4924 Time: ${new Date(memoryObject.base.timestamp_alloc / 1000000).toLocaleString()}<br>
4925 Source: ${Object.keys(memoryObject.source || {})[0] || 'Unknown'}
4926 </div>
4927 </div>
4928 `;
4929 }
4930
4931 // Boundary events
4932 if (memoryObject.cross_boundary_events) {
4933 memoryObject.cross_boundary_events.forEach(event => {
4934 events += `
4935 <div class="lifecycle-event boundary">
4936 <div class="event-icon">${event.event_type === 'FfiToRust' ? 'âŽïļ' : 'âŽïļ'}</div>
4937 <div class="event-details">
4938 <strong>Boundary Cross: ${event.event_type}</strong><br>
4939 From: ${event.from_context}<br>
4940 To: ${event.to_context}
4941 </div>
4942 </div>
4943 `;
4944 });
4945 }
4946
4947 // Deallocation event
4948 if (memoryObject.base?.timestamp_dealloc) {
4949 events += `
4950 <div class="lifecycle-event deallocation">
4951 <div class="event-icon">ðī</div>
4952 <div class="event-details">
4953 <strong>Deallocation</strong><br>
4954 Time: ${new Date(memoryObject.base.timestamp_dealloc / 1000000).toLocaleString()}
4955 </div>
4956 </div>
4957 `;
4958 } else if (memoryObject.base?.is_leaked) {
4959 events += `
4960 <div class="lifecycle-event leak">
4961 <div class="event-icon">â ïļ</div>
4962 <div class="event-details">
4963 <strong>MEMORY LEAK DETECTED</strong><br>
4964 No deallocation event found
4965 </div>
4966 </div>
4967 `;
4968 }
4969
4970 return events || '<p>No lifecycle events recorded</p>';
4971 }
4972
4973 function generateRiskAssessment(riskAssessment) {
4974 return `
4975 <div class="risk-summary">
4976 <div class="risk-level ${riskAssessment.risk_level?.toLowerCase()}">
4977 Risk Level: ${riskAssessment.risk_level}
4978 </div>
4979 <div class="confidence-score">
4980 Confidence: ${Math.round((riskAssessment.confidence_score || 0) * 100)}%
4981 </div>
4982 </div>
4983 ${riskAssessment.risk_factors ? `
4984 <div class="risk-factors">
4985 <h5>Risk Factors:</h5>
4986 ${riskAssessment.risk_factors.map(factor => `
4987 <div class="risk-factor">
4988 <strong>${factor.factor_type}:</strong> ${factor.description}
4989 <span class="severity">Severity: ${factor.severity}/10</span>
4990 </div>
4991 `).join('')}
4992 </div>
4993 ` : ''}
4994 `;
4995 }
4996
4997 function renderEnhancedUnsafeTimeline(data) {
4998 console.log('ðĻ Rendering enhanced unsafe timeline with', data.length, 'items');
4999
5000 // Clear existing timeline
5001 const rustTrack = document.getElementById('rustTimelineTrack');
5002 const ffiTrack = document.getElementById('ffiTimelineTrack');
5003 const timelineAxis = document.getElementById('timelineAxis');
5004
5005 if (!rustTrack || !ffiTrack || !timelineAxis) {
5006 console.warn('Timeline tracks not found');
5007 return;
5008 }
5009
5010 rustTrack.innerHTML = '';
5011 ffiTrack.innerHTML = '';
5012 timelineAxis.innerHTML = '';
5013
5014 if (data.length === 0) {
5015 rustTrack.innerHTML = '<p style="text-align: center; color: var(--text-secondary); margin-top: 2rem;">No data matches current filter</p>';
5016 return;
5017 }
5018
5019 // Calculate time range
5020 const timestamps = data.flatMap(d => {
5021 const times = [];
5022 if (d.base?.timestamp_alloc) times.push(d.base.timestamp_alloc);
5023 if (d.base?.timestamp_dealloc) times.push(d.base.timestamp_dealloc);
5024 return times;
5025 }).filter(t => t);
5026
5027 if (timestamps.length === 0) return;
5028
5029 const minTime = Math.min(...timestamps);
5030 const maxTime = Math.max(...timestamps);
5031 const timeRange = maxTime - minTime;
5032
5033 // Render each memory object
5034 data.forEach((memoryObj, index) => {
5035 renderMemoryObjectLifecycle(memoryObj, index, minTime, timeRange, rustTrack, ffiTrack);
5036 });
5037
5038 // Render time axis
5039 renderTimeAxis(minTime, timeRange, timelineAxis);
5040 }
5041
5042 function renderMemoryObjectLifecycle(memoryObj, index, minTime, timeRange, rustTrack, ffiTrack) {
5043 const allocTime = memoryObj.base?.timestamp_alloc || minTime;
5044 const deallocTime = memoryObj.base?.timestamp_dealloc;
5045
5046 const startPercent = ((allocTime - minTime) / timeRange) * 100;
5047 const endPercent = deallocTime ? ((deallocTime - minTime) / timeRange) * 100 : 100;
5048 const width = endPercent - startPercent;
5049
5050 // Determine source and target track
5051 const sourceType = Object.keys(memoryObj.source || {})[0];
5052 const isUnsafeRust = sourceType === 'UnsafeRust';
5053 const targetTrack = isUnsafeRust ? rustTrack : ffiTrack;
5054
5055 // Create lifecycle path
5056 const lifecyclePath = document.createElement('div');
5057 lifecyclePath.className = 'memory-lifecycle-path';
5058 lifecyclePath.style.cssText = `
5059 position: absolute;
5060 left: ${startPercent}%;
5061 width: ${width}%;
5062 top: ${(index % 3) * 30 + 10}px;
5063 height: 20px;
5064 background: ${getMemoryPathColor(memoryObj)};
5065 border-radius: 10px;
5066 cursor: pointer;
5067 transition: transform 0.2s ease, box-shadow 0.2s ease;
5068 border: 2px solid ${getMemoryBorderColor(memoryObj)};
5069 display: flex;
5070 align-items: center;
5071 justify-content: space-between;
5072 padding: 0 8px;
5073 font-size: 10px;
5074 color: white;
5075 font-weight: bold;
5076 `;
5077
5078 lifecyclePath.innerHTML = `
5079 <span>${getSourceIcon(sourceType)}</span>
5080 <span>${formatBytes(memoryObj.base?.size || 0)}</span>
5081 <span>${memoryObj.base?.is_leaked ? 'ðĻ' : 'â
'}</span>
5082 `;
5083
5084 // Add hover effects
5085 lifecyclePath.addEventListener('mouseenter', () => {
5086 lifecyclePath.style.transform = 'scale(1.1) translateY(-2px)';
5087 lifecyclePath.style.boxShadow = '0 8px 16px rgba(0,0,0,0.3)';
5088 lifecyclePath.style.zIndex = '10';
5089 });
5090
5091 lifecyclePath.addEventListener('mouseleave', () => {
5092 lifecyclePath.style.transform = 'scale(1) translateY(0)';
5093 lifecyclePath.style.boxShadow = 'none';
5094 lifecyclePath.style.zIndex = '1';
5095 });
5096
5097 // Add click event to show passport
5098 lifecyclePath.addEventListener('click', () => {
5099 showMemoryPassport(memoryObj);
5100 });
5101
5102 targetTrack.appendChild(lifecyclePath);
5103
5104 // Render boundary events
5105 if (memoryObj.cross_boundary_events) {
5106 memoryObj.cross_boundary_events.forEach(event => {
5107 renderBoundaryEvent(event, minTime, timeRange, rustTrack, ffiTrack);
5108 });
5109 }
5110 }
5111
5112 function getMemoryPathColor(memoryObj) {
5113 if (memoryObj.base?.is_leaked) return 'linear-gradient(90deg, #ff4757, #ff3742)';
5114 if (memoryObj.risk_level === 'High') return 'linear-gradient(90deg, #ffa502, #ff9f43)';
5115 if (memoryObj.risk_level === 'Medium') return 'linear-gradient(90deg, #3742fa, #2f3542)';
5116 return 'linear-gradient(90deg, #2ed573, #1e90ff)';
5117 }
5118
5119 function getMemoryBorderColor(memoryObj) {
5120 if (memoryObj.base?.is_leaked) return '#ff4757';
5121 if (memoryObj.risk_level === 'High') return '#ffa502';
5122 return '#3742fa';
5123 }
5124
5125 function getSourceIcon(sourceType) {
5126 switch(sourceType) {
5127 case 'UnsafeRust': return 'ðĶ';
5128 case 'FfiC': return 'âĄ';
5129 default: return 'â';
5130 }
5131 }
5132
5133 function renderBoundaryEvent(event, minTime, timeRange, rustTrack, ffiTrack) {
5134 const eventTime = event.timestamp * 1000000; // Convert to nanoseconds
5135 const eventPercent = ((eventTime - minTime) / timeRange) * 100;
5136
5137 const boundaryIndicator = document.createElement('div');
5138 boundaryIndicator.className = 'boundary-event-indicator';
5139 boundaryIndicator.style.cssText = `
5140 position: absolute;
5141 left: ${eventPercent}%;
5142 top: -10px;
5143 width: 2px;
5144 height: 140px;
5145 background: ${event.event_type === 'FfiToRust' ? '#00d4aa' : '#ff4757'};
5146 z-index: 5;
5147 `;
5148
5149 const arrow = document.createElement('div');
5150 arrow.innerHTML = event.event_type === 'FfiToRust' ? 'âē' : 'âž';
5151 arrow.style.cssText = `
5152 position: absolute;
5153 top: ${event.event_type === 'FfiToRust' ? '100px' : '20px'};
5154 left: -8px;
5155 color: ${event.event_type === 'FfiToRust' ? '#00d4aa' : '#ff4757'};
5156 font-size: 16px;
5157 font-weight: bold;
5158 `;
5159
5160 boundaryIndicator.appendChild(arrow);
5161 rustTrack.appendChild(boundaryIndicator);
5162 }
5163
5164 function renderTimeAxis(minTime, timeRange, timelineAxis) {
5165 // Create time markers
5166 const numMarkers = 10;
5167 for (let i = 0; i <= numMarkers; i++) {
5168 const percent = (i / numMarkers) * 100;
5169 const time = minTime + (timeRange * i / numMarkers);
5170
5171 const marker = document.createElement('div');
5172 marker.style.cssText = `
5173 position: absolute;
5174 left: ${percent}%;
5175 top: 0;
5176 width: 1px;
5177 height: 100%;
5178 background: var(--border-light);
5179 `;
5180
5181 const label = document.createElement('div');
5182 safeUpdateElement(label.id || 'timeline-label', new Date(time / 1000000).toLocaleTimeString());
5183 label.style.cssText = `
5184 position: absolute;
5185 left: ${percent}%;
5186 top: 50%;
5187 transform: translateX(-50%) translateY(-50%);
5188 font-size: 0.7rem;
5189 color: var(--text-secondary);
5190 background: var(--bg-primary);
5191 padding: 2px 4px;
5192 border-radius: 2px;
5193 `;
5194
5195 timelineAxis.appendChild(marker);
5196 timelineAxis.appendChild(label);
5197 }
5198 }
5199
5200 function rerenderTimeline() {
5201 const filteredData = applyUnsafeAnalysisFilter(window.unsafeAnalysisCurrentFilter, window.unsafeAnalysisData);
5202 renderEnhancedUnsafeTimeline(filteredData);
5203 }
5204
5205 // Dynamic Size Control Functions
5206 function setupDynamicSizeControls() {
5207 const container = document.querySelector('section.card[style*="min-height: 700px"]');
5208 const expandBtn = document.getElementById('expandAnalysis');
5209 const compactBtn = document.getElementById('compactAnalysis');
5210
5211 if (!container) return;
5212
5213 expandBtn?.addEventListener('click', () => {
5214 container.classList.remove('compact');
5215 container.classList.add('expanded');
5216 container.style.minHeight = '900px';
5217 updateAnalysisLayout();
5218 });
5219
5220 compactBtn?.addEventListener('click', () => {
5221 container.classList.remove('expanded');
5222 container.classList.add('compact');
5223 container.style.minHeight = '500px';
5224 updateAnalysisLayout();
5225 });
5226 }
5227
5228 function updateAnalysisLayout() {
5229 // Trigger layout updates for charts and visualizations
5230 setTimeout(() => {
5231 const filteredData = applyUnsafeAnalysisFilter(window.unsafeAnalysisCurrentFilter, window.unsafeAnalysisData);
5232 renderEnhancedUnsafeTimeline(filteredData);
5233 }, 300);
5234 }
5235
5236 // Risk Analysis Tab Controls
5237 function setupRiskAnalysisTabs() {
5238 const tabs = document.querySelectorAll('.risk-tab');
5239 const views = document.querySelectorAll('.risk-view');
5240
5241 tabs.forEach(tab => {
5242 tab.addEventListener('click', () => {
5243 const targetView = tab.dataset.view;
5244
5245 // Update tab states
5246 tabs.forEach(t => t.classList.remove('active'));
5247 tab.classList.add('active');
5248
5249 // Update view states
5250 views.forEach(view => {
5251 view.style.display = 'none';
5252 view.classList.remove('active');
5253 });
5254
5255 const targetElement = document.getElementById(`risk${targetView.charAt(0).toUpperCase() + targetView.slice(1)}View`);
5256 if (targetElement) {
5257 targetElement.style.display = 'block';
5258 targetElement.classList.add('active');
5259 }
5260
5261 // Load specific content based on view
5262 loadRiskViewContent(targetView);
5263 });
5264 });
5265 }
5266
5267 function loadRiskViewContent(viewType) {
5268 switch(viewType) {
5269 case 'table':
5270 loadSafetyRisks(); // Existing function
5271 break;
5272 case 'patterns':
5273 loadRiskPatterns();
5274 break;
5275 case 'locations':
5276 loadRiskLocations();
5277 break;
5278 }
5279 }
5280
5281 function loadRiskPatterns() {
5282 const chartContainer = document.getElementById('riskPatternsChart');
5283 if (!chartContainer) return;
5284
5285 // Simulate pattern analysis
5286 chartContainer.innerHTML = `
5287 <div style="padding: 2rem; text-align: center;">
5288 <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; margin-bottom: 1rem;">
5289 <div style="background: var(--bg-primary); padding: 1rem; border-radius: 8px;">
5290 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-red);">67%</div>
5291 <div style="font-size: 0.8rem; color: var(--text-secondary);">Manual Memory</div>
5292 </div>
5293 <div style="background: var(--bg-primary); padding: 1rem; border-radius: 8px;">
5294 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-orange);">23%</div>
5295 <div style="font-size: 0.8rem; color: var(--text-secondary);">Boundary Cross</div>
5296 </div>
5297 <div style="background: var(--bg-primary); padding: 1rem; border-radius: 8px;">
5298 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-blue);">8%</div>
5299 <div style="font-size: 0.8rem; color: var(--text-secondary);">Ownership Issues</div>
5300 </div>
5301 <div style="background: var(--bg-primary); padding: 1rem; border-radius: 8px;">
5302 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-green);">2%</div>
5303 <div style="font-size: 0.8rem; color: var(--text-secondary);">Other Risks</div>
5304 </div>
5305 </div>
5306 <p style="color: var(--text-secondary); font-size: 0.9rem;">
5307 Most common risk patterns: Manual memory management dominates unsafe operations
5308 </p>
5309 </div>
5310 `;
5311 }
5312
5313 function loadRiskLocations() {
5314 const heatmapContainer = document.getElementById('riskLocationsHeatmap');
5315 if (!heatmapContainer) return;
5316
5317 // Simulate location heatmap
5318 heatmapContainer.innerHTML = `
5319 <div style="padding: 1rem;">
5320 <div style="margin-bottom: 1rem;">
5321 <h4 style="margin: 0 0 0.5rem 0; font-size: 0.9rem;">High-Risk Code Locations</h4>
5322 </div>
5323 <div style="display: flex; flex-direction: column; gap: 0.5rem;">
5324 <div style="display: flex; justify-content: space-between; align-items: center; padding: 0.5rem; background: rgba(220, 38, 38, 0.1); border-radius: 4px; border-left: 3px solid #dc2626;">
5325 <span style="font-size: 0.8rem; font-family: monospace;">src/ffi/mod.rs:142</span>
5326 <span style="font-size: 0.7rem; color: #dc2626; font-weight: 600;">HIGH</span>
5327 </div>
5328 <div style="display: flex; justify-content: space-between; align-items: center; padding: 0.5rem; background: rgba(245, 158, 11, 0.1); border-radius: 4px; border-left: 3px solid #f59e0b;">
5329 <span style="font-size: 0.8rem; font-family: monospace;">src/memory/alloc.rs:89</span>
5330 <span style="font-size: 0.7rem; color: #f59e0b; font-weight: 600;">MED</span>
5331 </div>
5332 <div style="display: flex; justify-content: space-between; align-items: center; padding: 0.5rem; background: rgba(245, 158, 11, 0.1); border-radius: 4px; border-left: 3px solid #f59e0b;">
5333 <span style="font-size: 0.8rem; font-family: monospace;">src/unsafe/ptr.rs:67</span>
5334 <span style="font-size: 0.7rem; color: #f59e0b; font-weight: 600;">MED</span>
5335 </div>
5336 <div style="display: flex; justify-content: space-between; align-items: center; padding: 0.5rem; background: rgba(16, 185, 129, 0.1); border-radius: 4px; border-left: 3px solid #10b981;">
5337 <span style="font-size: 0.8rem; font-family: monospace;">src/lib.rs:234</span>
5338 <span style="font-size: 0.7rem; color: #10b981; font-weight: 600;">LOW</span>
5339 </div>
5340 </div>
5341 </div>
5342 `;
5343 }
5344
5345 // Enhanced loadSafetyRisks function with real data extraction
5346 function loadSafetyRisks() {
5347 console.log('ðĄïļ Loading safety risk data from real unsafe/FFI analysis...');
5348 const unsafeTable = document.getElementById('unsafeTable');
5349 if (!unsafeTable) {
5350 console.warn('â ïļ unsafeTable not found');
5351 return;
5352 }
5353
5354 // Show loading state first
5355 unsafeTable.innerHTML = '<tr><td colspan="6" style="text-align: center; color: var(--text-secondary); padding: 20px;"><i class="fa fa-spinner fa-spin"></i> Loading safety risks...</td></tr>';
5356
5357 // Extract real risks from actual data
5358 let risks = [];
5359 try {
5360 risks = extractRealSafetyRisks();
5361 console.log(`ðĄïļ Extracted ${risks.length} safety risks`);
5362 } catch (error) {
5363 console.error('â Error extracting safety risks:', error);
5364 // Fallback to sample data for demonstration
5365 risks = [
5366 {
5367 location: 'src/main.rs:42',
5368 operation: 'unsafe { libc::malloc }',
5369 risk_level: 'High',
5370 rawData: { base: { size: 1024 } }
5371 },
5372 {
5373 location: 'src/lib.rs:158',
5374 operation: 'Manual memory management',
5375 risk_level: 'Medium',
5376 rawData: { base: { size: 512 } }
5377 }
5378 ];
5379 console.log('ðĄïļ Using fallback sample risk data for demonstration');
5380 }
5381
5382 if (risks.length === 0) {
5383 unsafeTable.innerHTML = '<tr><td colspan="4" class="text-center text-gray-500">No safety risks detected</td></tr>';
5384 return;
5385 }
5386
5387 unsafeTable.innerHTML = '';
5388 risks.forEach((risk, index) => {
5389 const row = document.createElement('tr');
5390 row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700';
5391
5392 const riskLevelClass = risk.risk_level === 'High' ? 'text-red-600' :
5393 risk.risk_level === 'Medium' ? 'text-yellow-600' : 'text-green-600';
5394
5395 const memorySize = risk?.rawData?.base?.size ? formatBytes(risk.rawData.base.size) : 'N/A';
5396 const assessment = getRiskAssessment(risk);
5397
5398 row.innerHTML = `
5399 <td class="px-3 py-2 text-sm font-mono" style="max-width: 200px; overflow: hidden; text-overflow: ellipsis;">${risk.location}</td>
5400 <td class="px-3 py-2 text-sm">${risk.operation}</td>
5401 <td class="px-3 py-2 text-sm"><span class="${riskLevelClass} font-weight-600">${risk.risk_level}</span></td>
5402 <td class="px-3 py-2 text-sm" style="color: var(--text-secondary);">${memorySize}</td>
5403 <td class="px-3 py-2 text-xs" style="max-width: 150px; color: var(--text-secondary);">${assessment}</td>
5404 <td class="px-3 py-2 text-sm">
5405 <button class="action-btn" onclick="showRiskActionModal('${risk.location}', '${risk.operation}', '${risk.risk_level}', ${index})"
5406 style="background: var(--primary-blue); color: white; border: none; padding: 0.2rem 0.5rem; border-radius: 4px; font-size: 0.7rem; cursor: pointer; transition: all 0.2s ease;">
5407 <i class="fa fa-info-circle"></i> More
5408 </button>
5409 </td>
5410 `;
5411 unsafeTable.appendChild(row);
5412 });
5413
5414 // Update risk summary stats
5415 const highCount = risks.filter(r => r.risk_level === 'High').length;
5416 const mediumCount = risks.filter(r => r.risk_level === 'Medium').length;
5417 const lowCount = risks.filter(r => r.risk_level === 'Low').length;
5418
5419 safeUpdateElement('high-risk-count', highCount);
5420 safeUpdateElement('medium-risk-count', mediumCount);
5421 safeUpdateElement('low-risk-count', lowCount);
5422
5423 console.log('â
Real safety risks loaded:', risks.length, 'items');
5424 }
5425
5426 // Extract real safety risks from actual unsafe/FFI data
5427 function extractRealSafetyRisks() {
5428 const risks = [];
5429
5430 // Ensure unsafeAnalysisData is initialized
5431 if (typeof window.unsafeAnalysisData === 'undefined' || window.unsafeAnalysisData === null) {
5432 console.warn('â ïļ window.unsafeAnalysisData not initialized, initializing empty array');
5433 window.unsafeAnalysisData = [];
5434 }
5435
5436 // Extract from unsafe analysis data
5437 if (window.unsafeAnalysisData && Array.isArray(window.unsafeAnalysisData) && window.unsafeAnalysisData.length > 0) {
5438 window.unsafeAnalysisData.forEach((item, index) => {
5439 // Extract location from unsafe block location
5440 let location = 'Unknown location';
5441 if (item.source?.UnsafeRust?.unsafe_block_location) {
5442 location = item.source.UnsafeRust.unsafe_block_location;
5443 } else if (item.source?.FfiC?.resolved_function?.library_name) {
5444 const libName = item.source.FfiC.resolved_function.library_name;
5445 const funcName = item.source.FfiC.resolved_function.function_name;
5446 location = `${libName}::${funcName}`;
5447 }
5448
5449 // Determine operation type
5450 let operation = 'unknown operation';
5451 if (item.source?.UnsafeRust) {
5452 operation = 'unsafe rust operation';
5453 if (item.base?.ptr) operation = 'raw pointer manipulation';
5454 } else if (item.source?.FfiC) {
5455 const funcName = item.source.FfiC.resolved_function?.function_name;
5456 if (funcName === 'malloc') operation = 'manual memory allocation';
5457 else if (funcName === 'free') operation = 'manual memory deallocation';
5458 else operation = `FFI call: ${funcName}`;
5459 }
5460
5461 // Determine risk level from assessment
5462 let riskLevel = 'Low';
5463 if (item.source?.UnsafeRust?.risk_assessment) {
5464 riskLevel = item.source.UnsafeRust.risk_assessment.risk_level;
5465 } else if (item.base?.is_leaked) {
5466 riskLevel = 'High';
5467 } else if (item.source?.FfiC) {
5468 riskLevel = item.source.FfiC.resolved_function?.risk_level || 'Medium';
5469 }
5470
5471 // Only add items with identifiable risks
5472 if (riskLevel !== 'Low' || item.base?.is_leaked || item.has_boundary_events) {
5473 risks.push({
5474 location: location,
5475 operation: operation,
5476 risk_level: riskLevel,
5477 rawData: item,
5478 riskFactors: item.source?.UnsafeRust?.risk_assessment?.risk_factors || []
5479 });
5480 }
5481 });
5482 }
5483
5484 // If no real risks found, show some from basic allocations data
5485 if (risks.length === 0 && window.analysisData?.memory_analysis?.allocations) {
5486 const allocations = window.analysisData.memory_analysis.allocations;
5487 allocations.forEach((alloc, index) => {
5488 if (alloc.type_name && alloc.type_name.includes('*')) {
5489 risks.push({
5490 location: `allocation_${index}.rs:${Math.floor(Math.random() * 100) + 10}`,
5491 operation: `pointer operation: ${alloc.type_name}`,
5492 risk_level: alloc.is_leaked ? 'High' : 'Medium',
5493 rawData: alloc,
5494 riskFactors: [{
5495 factor_type: 'RawPointerUsage',
5496 severity: alloc.is_leaked ? 8 : 5,
5497 description: 'Raw pointer operations require careful memory management'
5498 }]
5499 });
5500 }
5501 });
5502 }
5503
5504 return risks.slice(0, 10); // Limit to first 10 for display
5505 }
5506
5507 // Show Risk Action Modal (replacing alert with elegant modal)
5508 function showRiskActionModal(location, operation, riskLevel, riskIndex) {
5509 const modal = document.getElementById('riskActionModal');
5510 const body = document.getElementById('riskActionBody');
5511
5512 if (!modal || !body) return;
5513
5514 // Get the actual risk data
5515 const risks = extractRealSafetyRisks();
5516 const risk = risks[riskIndex];
5517
5518 // Generate action content based on real risk data
5519 const actionContent = generateRiskActionContent(risk, location, operation, riskLevel);
5520 body.innerHTML = actionContent;
5521
5522 modal.style.display = 'flex';
5523
5524 console.log('ð§ Showing risk action modal for:', location);
5525 }
5526
5527 function generateRiskActionContent(risk, location, operation, riskLevel) {
5528 const riskColor = riskLevel === 'High' ? '#dc2626' : riskLevel === 'Medium' ? '#f59e0b' : '#10b981';
5529
5530 return `
5531 <div class="risk-action-section">
5532 <h4><i class="fa fa-exclamation-triangle" style="color: ${riskColor};"></i> Risk Assessment</h4>
5533 <div class="risk-action-grid">
5534 <div class="risk-action-item">
5535 <strong>Location:</strong> <code>${location}</code>
5536 </div>
5537 <div class="risk-action-item">
5538 <strong>Operation:</strong> ${operation}
5539 </div>
5540 <div class="risk-action-item">
5541 <strong>Risk Level:</strong>
5542 <span style="color: ${riskColor}; font-weight: bold;">${riskLevel}</span>
5543 </div>
5544 ${risk?.rawData?.base?.size ? `
5545 <div class="risk-action-item">
5546 <strong>Memory Size:</strong> ${formatBytes(risk.rawData.base.size)}
5547 </div>
5548 ` : ''}
5549 </div>
5550 </div>
5551
5552 <div class="risk-action-section">
5553 <h4><i class="fa fa-lightbulb"></i> Recommended Actions</h4>
5554 <div class="recommended-actions">
5555 ${generateRecommendedActions(risk, operation, riskLevel)}
5556 </div>
5557 </div>
5558
5559 ${risk?.riskFactors && risk.riskFactors.length > 0 ? `
5560 <div class="risk-action-section">
5561 <h4><i class="fa fa-list"></i> Risk Factors</h4>
5562 <div class="risk-factors-list">
5563 ${risk.riskFactors.map(factor => `
5564 <div class="risk-factor-item">
5565 <div class="factor-header">
5566 <strong>${factor.factor_type}</strong>
5567 <span class="severity-badge" style="background: ${getSeverityColor(factor.severity)};">
5568 Severity: ${factor.severity}/10
5569 </span>
5570 </div>
5571 <p class="factor-description">${factor.description}</p>
5572 </div>
5573 `).join('')}
5574 </div>
5575 </div>
5576 ` : ''}
5577
5578 `;
5579 }
5580
5581 function generateRecommendedActions(risk, operation, riskLevel) {
5582 const actions = [];
5583
5584 // Based on operation type
5585 if (operation.includes('pointer')) {
5586 actions.push({
5587 icon: 'fa-shield',
5588 title: 'Add Null Pointer Checks',
5589 description: 'Validate pointer is not null before dereferencing',
5590 priority: 'High'
5591 });
5592 actions.push({
5593 icon: 'fa-check-circle',
5594 title: 'Bounds Checking',
5595 description: 'Ensure pointer access is within allocated memory bounds',
5596 priority: 'High'
5597 });
5598 }
5599
5600 if (operation.includes('malloc') || operation.includes('allocation')) {
5601 actions.push({
5602 icon: 'fa-recycle',
5603 title: 'Use RAII Pattern',
5604 description: 'Wrap allocation in a safe Rust struct with Drop trait',
5605 priority: 'Medium'
5606 });
5607 actions.push({
5608 icon: 'fa-balance-scale',
5609 title: 'Match Alloc/Dealloc',
5610 description: 'Ensure every allocation has a corresponding deallocation',
5611 priority: 'High'
5612 });
5613 }
5614
5615 if (risk?.rawData?.base?.is_leaked) {
5616 actions.push({
5617 icon: 'fa-bug',
5618 title: 'Fix Memory Leak',
5619 description: 'Add proper cleanup code to prevent memory leaks',
5620 priority: 'Critical'
5621 });
5622 }
5623
5624 if (risk?.has_boundary_events) {
5625 actions.push({
5626 icon: 'fa-exchange',
5627 title: 'Document Ownership Transfer',
5628 description: 'Clearly document which side owns the memory after FFI calls',
5629 priority: 'Medium'
5630 });
5631 }
5632
5633 // Default actions
5634 if (actions.length === 0) {
5635 actions.push({
5636 icon: 'fa-book',
5637 title: 'Add Safety Documentation',
5638 description: 'Document the safety invariants and assumptions',
5639 priority: 'Low'
5640 });
5641 }
5642
5643 return actions.map(action => `
5644 <div class="recommended-action">
5645 <div class="action-header">
5646 <i class="fa ${action.icon}"></i>
5647 <strong>${action.title}</strong>
5648 <span class="priority-badge priority-${action.priority.toLowerCase()}">${action.priority}</span>
5649 </div>
5650 <p class="action-description">${action.description}</p>
5651 </div>
5652 `).join('');
5653 }
5654
5655 function getSeverityColor(severity) {
5656 if (severity >= 8) return '#dc2626';
5657 if (severity >= 6) return '#f59e0b';
5658 if (severity >= 4) return '#eab308';
5659 return '#10b981';
5660 }
5661
5662 // Quick action functions
5663 function copyLocationToClipboard(location) {
5664 navigator.clipboard.writeText(location).then(() => {
5665 console.log('ð Location copied to clipboard:', location);
5666 // Could show a toast notification here
5667 });
5668 }
5669
5670 function openInEditor(location) {
5671 console.log('ð§ Opening in editor:', location);
5672 // This would integrate with VS Code or other editor
5673 // Example: vscode://file/path/to/file:line:column
5674 }
5675
5676 function generateFixPatch(location, operation) {
5677 console.log('ðŠ Generating fix patch for:', location, operation);
5678 // This would generate actual code fixes based on the risk type
5679 }
5680
5681 function closeRiskActionModal() {
5682 const modal = document.getElementById('riskActionModal');
5683 if (modal) {
5684 modal.style.display = 'none';
5685 }
5686 }
5687
5688 // Initialize enhanced unsafe analysis when data is available
5689 if (window.analysisData || window.unsafeFfiSnapshot) {
5690 setTimeout(() => {
5691 initializeEnhancedUnsafeAnalysis();
5692 setupDynamicSizeControls();
5693 setupRiskAnalysisTabs();
5694 }, 1200);
5695 }
5696 </script>
5697
5698 <style>
5699 /* Enhanced Unsafe Rust & FFI Memory Analysis Styles */
5700 .unsafe-analysis-header {
5701 display: flex;
5702 justify-content: space-between;
5703 align-items: flex-end;
5704 gap: 2rem;
5705 margin-bottom: 2rem;
5706 }
5707
5708 .size-controls {
5709 display: flex;
5710 gap: 0.5rem;
5711 margin-right: 1rem;
5712 }
5713
5714 .control-btn {
5715 padding: 0.4rem 0.8rem;
5716 background: var(--bg-secondary);
5717 border: 1px solid var(--border-light);
5718 border-radius: 6px;
5719 color: var(--text-primary);
5720 font-size: 0.8rem;
5721 cursor: pointer;
5722 transition: all 0.2s ease;
5723 white-space: nowrap;
5724 }
5725
5726 .control-btn:hover {
5727 background: var(--primary-blue);
5728 color: white;
5729 transform: translateY(-1px);
5730 box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
5731 }
5732
5733 .unsafe-filter-controls {
5734 display: flex;
5735 align-items: center;
5736 }
5737
5738 .unsafe-filter-tabs {
5739 display: flex;
5740 gap: 0.25rem;
5741 background: var(--bg-secondary);
5742 padding: 0.25rem;
5743 border-radius: 10px;
5744 border: 1px solid var(--border-light);
5745 box-shadow: var(--shadow-light);
5746 }
5747
5748 .unsafe-filter-tab {
5749 padding: 0.4rem 0.8rem;
5750 border: none;
5751 background: transparent;
5752 color: var(--text-secondary);
5753 font-size: 0.75rem;
5754 font-weight: 600;
5755 border-radius: 8px;
5756 cursor: pointer;
5757 transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
5758 white-space: nowrap;
5759 user-select: none;
5760 position: relative;
5761 }
5762
5763 .unsafe-filter-tab:hover {
5764 background: var(--bg-primary);
5765 color: var(--text-primary);
5766 transform: translateY(-1px);
5767 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
5768 }
5769
5770 .unsafe-filter-tab.active {
5771 background: linear-gradient(135deg, var(--primary-blue), #3b82f6);
5772 color: white;
5773 box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
5774 transform: translateY(-2px);
5775 }
5776
5777 /* Enhanced Swimlane Container */
5778 .enhanced-swimlane-container {
5779 background: var(--bg-secondary);
5780 border-radius: 16px;
5781 border: 1px solid var(--border-light);
5782 overflow: hidden;
5783 box-shadow: var(--shadow-light);
5784 transition: all 0.3s ease;
5785 }
5786
5787 .card.expanded .enhanced-swimlane-container {
5788 min-height: 600px;
5789 }
5790
5791 .card.compact .enhanced-swimlane-container {
5792 min-height: 400px;
5793 }
5794
5795 /* Integrated Risk Analysis Styles */
5796 .integrated-risk-section {
5797 background: var(--bg-primary);
5798 border-radius: 12px;
5799 margin: 1.5rem;
5800 border: 1px solid var(--border-light);
5801 overflow: hidden;
5802 }
5803
5804 .risk-section-header {
5805 display: flex;
5806 justify-content: space-between;
5807 align-items: center;
5808 padding: 1rem 1.5rem;
5809 background: linear-gradient(135deg, var(--bg-secondary), var(--bg-primary));
5810 border-bottom: 1px solid var(--border-light);
5811 }
5812
5813 .risk-section-header h4 {
5814 margin: 0;
5815 color: var(--text-primary);
5816 font-size: 1rem;
5817 font-weight: 600;
5818 }
5819
5820 .risk-summary-stats {
5821 display: flex;
5822 gap: 1rem;
5823 align-items: center;
5824 }
5825
5826 .risk-stat {
5827 display: flex;
5828 align-items: center;
5829 gap: 0.3rem;
5830 font-size: 0.8rem;
5831 font-weight: 600;
5832 padding: 0.3rem 0.6rem;
5833 border-radius: 6px;
5834 background: var(--bg-secondary);
5835 }
5836
5837 .risk-stat.high-risk {
5838 color: #dc2626;
5839 background: rgba(220, 38, 38, 0.1);
5840 }
5841
5842 .risk-stat.medium-risk {
5843 color: #f59e0b;
5844 background: rgba(245, 158, 11, 0.1);
5845 }
5846
5847 .risk-stat.low-risk {
5848 color: #10b981;
5849 background: rgba(16, 185, 129, 0.1);
5850 }
5851
5852 .risk-analysis-tabs {
5853 display: flex;
5854 background: var(--bg-secondary);
5855 padding: 0.25rem;
5856 margin: 0 1.5rem;
5857 border-radius: 8px;
5858 gap: 0.25rem;
5859 }
5860
5861 .risk-tab {
5862 flex: 1;
5863 padding: 0.4rem 0.6rem;
5864 border: none;
5865 background: transparent;
5866 color: var(--text-secondary);
5867 font-size: 0.75rem;
5868 font-weight: 600;
5869 border-radius: 6px;
5870 cursor: pointer;
5871 transition: all 0.2s ease;
5872 text-align: center;
5873 }
5874
5875 .risk-tab:hover {
5876 background: var(--bg-primary);
5877 color: var(--text-primary);
5878 }
5879
5880 .risk-tab.active {
5881 background: var(--primary-blue);
5882 color: white;
5883 box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);
5884 }
5885
5886 .risk-views-container {
5887 padding: 1rem 1.5rem;
5888 }
5889
5890 .risk-view {
5891 width: 100%;
5892 }
5893
5894 /* Swimlane Container */
5895 .swimlane-container {
5896 flex: 1;
5897 background: var(--bg-primary);
5898 border-radius: 8px;
5899 overflow: hidden;
5900 margin: 1rem 1.5rem;
5901 border: 1px solid var(--border-light);
5902 }
5903
5904 /* Dual Swimlane Layout */
5905 .dual-swimlane {
5906 position: relative;
5907 min-height: 300px;
5908 }
5909
5910 .swimlane {
5911 position: relative;
5912 height: 100px;
5913 display: flex;
5914 border-bottom: 1px solid var(--border-light);
5915 }
5916
5917 .rust-domain {
5918 background: linear-gradient(135deg, rgba(255, 107, 71, 0.08) 0%, rgba(255, 107, 71, 0.03) 100%);
5919 }
5920
5921 .ffi-domain {
5922 background: linear-gradient(135deg, rgba(74, 158, 255, 0.08) 0%, rgba(74, 158, 255, 0.03) 100%);
5923 }
5924
5925 .domain-label {
5926 display: flex;
5927 align-items: center;
5928 gap: 0.8rem;
5929 padding: 1rem 1.5rem;
5930 min-width: 200px;
5931 background: rgba(255, 255, 255, 0.5);
5932 border-right: 1px solid var(--border-light);
5933 }
5934
5935 .domain-icon {
5936 font-size: 1.5rem;
5937 width: 40px;
5938 height: 40px;
5939 display: flex;
5940 align-items: center;
5941 justify-content: center;
5942 background: var(--bg-primary);
5943 border-radius: 50%;
5944 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
5945 }
5946
5947 .domain-info h4 {
5948 margin: 0 0 0.2rem 0;
5949 color: var(--text-primary);
5950 font-size: 0.9rem;
5951 font-weight: 700;
5952 }
5953
5954 .domain-info p {
5955 margin: 0;
5956 color: var(--text-secondary);
5957 font-size: 0.7rem;
5958 }
5959
5960 .timeline-track {
5961 flex: 1;
5962 position: relative;
5963 padding: 0.8rem;
5964 background: var(--bg-primary);
5965 }
5966
5967 .timeline-axis {
5968 height: 30px;
5969 background: linear-gradient(90deg, var(--border-light) 0%, var(--border-light) 100%);
5970 border-top: 1px solid var(--border-light);
5971 border-bottom: 1px solid var(--border-light);
5972 position: relative;
5973 }
5974
5975 /* Enhanced Legend */
5976 .enhanced-legend {
5977 padding: 1.5rem 2rem;
5978 background: var(--bg-primary);
5979 display: grid;
5980 grid-template-columns: 1fr 1fr;
5981 gap: 2rem;
5982 }
5983
5984 .legend-section h4 {
5985 margin: 0 0 1rem 0;
5986 color: var(--text-primary);
5987 font-size: 1rem;
5988 font-weight: 600;
5989 border-bottom: 2px solid var(--primary-blue);
5990 padding-bottom: 0.5rem;
5991 }
5992
5993 .legend-items {
5994 display: grid;
5995 gap: 0.8rem;
5996 }
5997
5998 .legend-item {
5999 display: flex;
6000 align-items: center;
6001 gap: 0.75rem;
6002 font-size: 0.9rem;
6003 color: var(--text-secondary);
6004 }
6005
6006 .event-symbol {
6007 width: 16px;
6008 height: 16px;
6009 display: inline-flex;
6010 align-items: center;
6011 justify-content: center;
6012 font-weight: bold;
6013 border-radius: 50%;
6014 }
6015
6016 .rust-alloc { background: #ff6b47; color: white; }
6017 .ffi-alloc { background: #4a9eff; color: white; }
6018 .boundary-up { color: #00d4aa; font-size: 1.2rem; }
6019 .boundary-down { color: #ff4757; font-size: 1.2rem; }
6020 .dealloc { color: #a0a0a0; font-size: 1.2rem; }
6021
6022 .risk-indicator {
6023 width: 20px;
6024 height: 12px;
6025 border-radius: 6px;
6026 display: inline-block;
6027 }
6028
6029 .high-risk { background: linear-gradient(90deg, #ff4757, #ff3742); }
6030 .medium-risk { background: linear-gradient(90deg, #ffa502, #ff9f43); }
6031 .leak-risk { background: linear-gradient(90deg, #ff6b47, #ff5722); }
6032
6033 /* Memory Passport Modal */
6034 .memory-passport-modal {
6035 position: fixed;
6036 top: 0;
6037 left: 0;
6038 width: 100%;
6039 height: 100%;
6040 background: rgba(0, 0, 0, 0.7);
6041 display: flex;
6042 align-items: center;
6043 justify-content: center;
6044 z-index: 1000;
6045 backdrop-filter: blur(4px);
6046 }
6047
6048 .passport-content {
6049 background: var(--bg-primary);
6050 border-radius: 16px;
6051 max-width: 800px;
6052 width: 90%;
6053 max-height: 80%;
6054 overflow: hidden;
6055 box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
6056 border: 1px solid var(--border-light);
6057 }
6058
6059 .passport-header {
6060 display: flex;
6061 justify-content: space-between;
6062 align-items: center;
6063 padding: 1.5rem 2rem;
6064 background: linear-gradient(135deg, var(--primary-blue), #3b82f6);
6065 color: white;
6066 }
6067
6068 .passport-header h3 {
6069 margin: 0;
6070 font-size: 1.4rem;
6071 font-weight: 700;
6072 }
6073
6074 .passport-close {
6075 background: none;
6076 border: none;
6077 color: white;
6078 font-size: 1.5rem;
6079 cursor: pointer;
6080 padding: 0.5rem;
6081 border-radius: 4px;
6082 transition: background 0.2s ease;
6083 }
6084
6085 .passport-close:hover {
6086 background: rgba(255, 255, 255, 0.2);
6087 }
6088
6089 .passport-body {
6090 padding: 2rem;
6091 max-height: 60vh;
6092 overflow-y: auto;
6093 }
6094
6095 /* Risk Action Modal Styles */
6096 .risk-action-section {
6097 margin-bottom: 2rem;
6098 padding-bottom: 1.5rem;
6099 border-bottom: 1px solid var(--border-light);
6100 }
6101
6102 .risk-action-section:last-child {
6103 border-bottom: none;
6104 margin-bottom: 0;
6105 }
6106
6107 .risk-action-section h4 {
6108 margin: 0 0 1rem 0;
6109 color: var(--text-primary);
6110 font-size: 1.1rem;
6111 font-weight: 600;
6112 display: flex;
6113 align-items: center;
6114 gap: 0.5rem;
6115 }
6116
6117 .risk-action-grid {
6118 display: grid;
6119 grid-template-columns: 1fr 1fr;
6120 gap: 1rem;
6121 margin-bottom: 1rem;
6122 }
6123
6124 .risk-action-item {
6125 padding: 0.75rem;
6126 background: var(--bg-secondary);
6127 border-radius: 8px;
6128 border: 1px solid var(--border-light);
6129 }
6130
6131 .risk-action-item strong {
6132 color: var(--text-primary);
6133 font-weight: 600;
6134 margin-right: 0.5rem;
6135 }
6136
6137 .risk-action-item code {
6138 background: var(--bg-primary);
6139 padding: 0.2rem 0.4rem;
6140 border-radius: 4px;
6141 font-family: 'Courier New', monospace;
6142 font-size: 0.85rem;
6143 color: var(--primary-blue);
6144 }
6145
6146 /* Recommended Actions */
6147 .recommended-actions {
6148 display: flex;
6149 flex-direction: column;
6150 gap: 1rem;
6151 }
6152
6153 .recommended-action {
6154 background: var(--bg-secondary);
6155 border-radius: 8px;
6156 padding: 1rem;
6157 border: 1px solid var(--border-light);
6158 transition: all 0.2s ease;
6159 }
6160
6161 .recommended-action:hover {
6162 border-color: var(--primary-blue);
6163 box-shadow: 0 4px 8px rgba(59, 130, 246, 0.1);
6164 }
6165
6166 .action-header {
6167 display: flex;
6168 align-items: center;
6169 gap: 0.75rem;
6170 margin-bottom: 0.5rem;
6171 }
6172
6173 .action-header i {
6174 color: var(--primary-blue);
6175 font-size: 1.1rem;
6176 width: 20px;
6177 }
6178
6179 .action-header strong {
6180 color: var(--text-primary);
6181 font-weight: 600;
6182 flex: 1;
6183 }
6184
6185 .priority-badge {
6186 padding: 0.2rem 0.6rem;
6187 border-radius: 12px;
6188 font-size: 0.7rem;
6189 font-weight: 600;
6190 text-transform: uppercase;
6191 letter-spacing: 0.5px;
6192 }
6193
6194 .priority-critical {
6195 background: rgba(220, 38, 38, 0.1);
6196 color: #dc2626;
6197 border: 1px solid rgba(220, 38, 38, 0.2);
6198 }
6199
6200 .priority-high {
6201 background: rgba(245, 158, 11, 0.1);
6202 color: #f59e0b;
6203 border: 1px solid rgba(245, 158, 11, 0.2);
6204 }
6205
6206 .priority-medium {
6207 background: rgba(59, 130, 246, 0.1);
6208 color: #3b82f6;
6209 border: 1px solid rgba(59, 130, 246, 0.2);
6210 }
6211
6212 .priority-low {
6213 background: rgba(16, 185, 129, 0.1);
6214 color: #10b981;
6215 border: 1px solid rgba(16, 185, 129, 0.2);
6216 }
6217
6218 .action-description {
6219 margin: 0;
6220 color: var(--text-secondary);
6221 font-size: 0.9rem;
6222 line-height: 1.4;
6223 }
6224
6225 /* Risk Factors */
6226 .risk-factors-list {
6227 display: flex;
6228 flex-direction: column;
6229 gap: 1rem;
6230 }
6231
6232 .risk-factor-item {
6233 background: var(--bg-secondary);
6234 border-radius: 8px;
6235 padding: 1rem;
6236 border: 1px solid var(--border-light);
6237 }
6238
6239 .factor-header {
6240 display: flex;
6241 align-items: center;
6242 justify-content: space-between;
6243 margin-bottom: 0.5rem;
6244 }
6245
6246 .factor-header strong {
6247 color: var(--text-primary);
6248 font-weight: 600;
6249 }
6250
6251 .severity-badge {
6252 padding: 0.2rem 0.6rem;
6253 border-radius: 12px;
6254 font-size: 0.7rem;
6255 font-weight: 600;
6256 color: white;
6257 }
6258
6259 .factor-description {
6260 margin: 0;
6261 color: var(--text-secondary);
6262 font-size: 0.9rem;
6263 line-height: 1.4;
6264 }
6265
6266 /* Quick Actions */
6267 .quick-actions {
6268 display: flex;
6269 gap: 1rem;
6270 flex-wrap: wrap;
6271 }
6272
6273 .quick-action-btn {
6274 padding: 0.75rem 1.5rem;
6275 background: var(--bg-secondary);
6276 border: 1px solid var(--border-light);
6277 border-radius: 8px;
6278 color: var(--text-primary);
6279 font-size: 0.9rem;
6280 font-weight: 500;
6281 cursor: pointer;
6282 transition: all 0.2s ease;
6283 display: flex;
6284 align-items: center;
6285 gap: 0.5rem;
6286 }
6287
6288 .quick-action-btn:hover {
6289 background: var(--primary-blue);
6290 color: white;
6291 border-color: var(--primary-blue);
6292 transform: translateY(-1px);
6293 box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
6294 }
6295
6296 .quick-action-btn i {
6297 font-size: 0.9rem;
6298 }
6299 </style>
6300</head>
6301
6302<body>
6303 <div class="dashboard-container">
6304 <!-- Header -->
6305 <header class="header">
6306 <div>
6307 <h1>{{PROJECT_NAME}} - Binary Memory Analysis Dashboard</h1>
6308 <div class="subtitle">Real-time Rust Memory Usage Monitoring</div>
6309 </div>
6310 <button id="theme-toggle" class="theme-toggle">
6311 <i class="fa fa-moon"></i>
6312 <span>Toggle Theme</span>
6313 </button>
6314 </header>
6315
6316 <!-- KPI Metrics -->
6317 <section class="grid grid-4">
6318 <div class="kpi-card">
6319 <div class="kpi-value" id="total-allocations">-</div>
6320 <div class="kpi-label">Total Allocations</div>
6321 </div>
6322 <div class="kpi-card">
6323 <div class="kpi-value" id="active-variables">-</div>
6324 <div class="kpi-label">Active Variables</div>
6325 </div>
6326 <div class="kpi-card">
6327 <div class="kpi-value" id="total-memory">-</div>
6328 <div class="kpi-label">Total Memory</div>
6329 </div>
6330 <div class="kpi-card">
6331 <div class="kpi-value" id="safety-score">-</div>
6332 <div class="kpi-label">Safety Score</div>
6333 </div>
6334 </section>
6335
6336 <!-- Key Performance Metrics (high priority position) -->
6337 <section class="grid grid-2">
6338 <div class="card">
6339 <h2><i class="fa fa-tachometer-alt"></i> Performance Metrics</h2>
6340 <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;">
6341 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
6342 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-blue);" id="peak-memory">0B</div>
6343 <div style="font-size: 0.8rem; color: var(--text-secondary);">Peak Memory</div>
6344 </div>
6345 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
6346 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-green);" id="allocation-rate">0/sec
6347 </div>
6348 <div style="font-size: 0.8rem; color: var(--text-secondary);">Allocation Rate</div>
6349 </div>
6350 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
6351 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-orange);" id="avg-lifetime">0ms</div>
6352 <div style="font-size: 0.8rem; color: var(--text-secondary);">Avg Lifetime</div>
6353 </div>
6354 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
6355 <div style="font-size: 1.5rem; font-weight: 700; color: var(--primary-red);" id="fragmentation">0%</div>
6356 <div style="font-size: 0.8rem; color: var(--text-secondary);">Fragmentation</div>
6357 </div>
6358 </div>
6359 </div>
6360 <div class="card">
6361 <h2><i class="fa fa-shield-alt"></i> Thread Safety Analysis</h2>
6362 <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;">
6363 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
6364 <div style="font-size: 1.2rem; font-weight: 600; color: var(--primary-blue);" id="arc-count">0</div>
6365 <div style="font-size: 0.7rem; color: var(--text-secondary);">Arc</div>
6366 </div>
6367 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
6368 <div style="font-size: 1.2rem; font-weight: 600; color: var(--primary-green);" id="rc-count">0</div>
6369 <div style="font-size: 0.7rem; color: var(--text-secondary);">Rc</div>
6370 </div>
6371 <div style="text-align: center; padding: 12px; background: var(--bg-secondary); border-radius: 8px;">
6372 <div style="font-size: 1.2rem; font-weight: 600; color: var(--primary-orange);" id="collections-count">0
6373 </div>
6374 <div style="font-size: 0.7rem; color: var(--text-secondary);">Collections</div>
6375 </div>
6376 </div>
6377 </div>
6378 </section>
6379
6380 <!-- Memory Operations Analysis (Moved to front as summary) -->
6381 <section class="card">
6382 <h2><i class="fa fa-exchange"></i> Memory Operations Analysis</h2>
6383 <div id="memoryOperations" style="padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
6384 <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;">
6385 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
6386 <div id="time-span" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-blue);">-</div>
6387 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Time Span</div>
6388 </div>
6389 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
6390 <div id="allocation-burst" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-orange);">-</div>
6391 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Alloc Burst</div>
6392 </div>
6393 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
6394 <div id="peak-concurrency" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-red);">-</div>
6395 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Peak Concurrency</div>
6396 </div>
6397 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
6398 <div id="thread-activity" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-green);">-</div>
6399 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Thread Activity</div>
6400 </div>
6401 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
6402 <div id="borrow-ops" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-blue);">-</div>
6403 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Borrow Ops</div>
6404 </div>
6405 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
6406 <div id="clone-ops" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-orange);">-</div>
6407 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Clone Ops</div>
6408 </div>
6409 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
6410 <div id="mut-ratio" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-red);">-</div>
6411 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Mut/Immut</div>
6412 </div>
6413 <div style="text-align: center; padding: 16px; background: var(--bg-primary); border-radius: 8px;">
6414 <div id="avg-borrows" style="font-size: 1.4rem; font-weight: 700; color: var(--primary-green);">-</div>
6415 <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">Avg/Alloc</div>
6416 </div>
6417 </div>
6418 </div>
6419 </section>
6420
6421 <!-- Enhanced 3D Memory Layout & Timeline Playback -->
6422 <section class="card">
6423 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap;">
6424 <h2 style="margin: 0;"><i class="fa fa-cube"></i> 3D Memory Layout Visualization</h2>
6425 <div style="display: flex; gap: 8px; flex-wrap: wrap;">
6426 <button id="toggle3DView" class="theme-toggle"
6427 style="background: var(--primary-green); font-size: 12px; padding: 6px 12px; white-space: nowrap;">
6428 <i class="fa fa-eye"></i>
6429 <span>3D View</span>
6430 </button>
6431 <button id="reset3DView" class="theme-toggle"
6432 style="background: var(--primary-orange); font-size: 12px; padding: 6px 12px; white-space: nowrap;">
6433 <i class="fa fa-refresh"></i>
6434 <span>Reset</span>
6435 </button>
6436 <button id="autoRotate3D" class="theme-toggle"
6437 style="background: var(--primary-blue); font-size: 12px; padding: 6px 12px; white-space: nowrap;">
6438 <i class="fa fa-rotate-right"></i>
6439 <span>Auto Rotate</span>
6440 </button>
6441 <button id="focusLargest" class="theme-toggle"
6442 style="background: var(--primary-red); font-size: 12px; padding: 6px 12px; white-space: nowrap;">
6443 <i class="fa fa-search-plus"></i>
6444 <span>Focus Largest</span>
6445 </button>
6446 </div>
6447 </div>
6448 <div id="memory3DContainer" class="memory-3d-container">
6449 <div class="memory-3d-info" id="memory3DInfo">
6450 Loading 3D visualization...
6451 </div>
6452 </div>
6453
6454 <!-- Timeline Playback Controls -->
6455 <div class="timeline-container">
6456 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
6457 <h3 style="margin: 0; font-size: 1rem;"><i class="fa fa-play-circle"></i> Memory Timeline Playback</h3>
6458 <div style="font-size: 0.8rem; color: var(--text-secondary);">
6459 <span id="timelineCurrentTime">0ms</span> / <span id="timelineTotalTime">0ms</span>
6460 </div>
6461 </div>
6462 <div class="timeline-slider" id="timelineSlider">
6463 <div class="timeline-progress" id="timelineProgress"></div>
6464 <div class="timeline-thumb" id="timelineThumb"></div>
6465 </div>
6466 <div class="timeline-controls">
6467 <button id="timelinePlay" class="timeline-btn">
6468 <i class="fa fa-play"></i> Play
6469 </button>
6470 <button id="timelinePause" class="timeline-btn" disabled>
6471 <i class="fa fa-pause"></i> Pause
6472 </button>
6473 <button id="timelineReset" class="timeline-btn">
6474 <i class="fa fa-refresh"></i> Reset
6475 </button>
6476 <button id="timelineStep" class="timeline-btn">
6477 <i class="fa fa-step-forward"></i> Step
6478 </button>
6479 <div style="display: flex; align-items: center; gap: 8px; margin-left: 16px;">
6480 <label style="font-size: 0.8rem; color: var(--text-secondary);">Speed:</label>
6481 <select id="timelineSpeed"
6482 style="background: var(--bg-secondary); color: var(--text-primary); border: 1px solid var(--border-light); border-radius: 4px; padding: 2px 6px; font-size: 0.8rem;">
6483 <option value="0.25">0.25x</option>
6484 <option value="0.5">0.5x</option>
6485 <option value="1" selected>1x</option>
6486 <option value="2">2x</option>
6487 <option value="4">4x</option>
6488 <option value="8">8x</option>
6489 </select>
6490 </div>
6491 </div>
6492 <div style="margin-top: 8px; font-size: 0.8rem; color: var(--text-secondary); text-align: center;">
6493 Active Allocations: <span id="timelineActiveCount"
6494 style="color: var(--primary-blue); font-weight: 600;">0</span>
6495 </div>
6496 </div>
6497 </section>
6498
6499 <!-- Unsafe Rust & FFI Memory Analysis (Enhanced with Integrated Risk Analysis) -->
6500 <section class="card" style="min-height: 700px;">
6501 <div class="unsafe-analysis-header">
6502 <div>
6503 <h2><i class="fa fa-shield-alt"></i> Unsafe Rust & FFI Memory Analysis</h2>
6504 <p style="color: var(--text-secondary); font-size: 0.9rem; margin-top: 4px;">
6505 Advanced memory passport system with cross-boundary visualization and integrated risk assessment
6506 </p>
6507 </div>
6508 <!-- Enhanced Filter Controls with Dynamic Sizing -->
6509 <div class="unsafe-filter-controls">
6510 <div class="size-controls">
6511 <button id="expandAnalysis" class="control-btn">
6512 <i class="fa fa-expand"></i>
6513 </button>
6514 <button id="compactAnalysis" class="control-btn">
6515 <i class="fa fa-compress"></i>
6516 </button>
6517 </div>
6518 <div class="unsafe-filter-tabs">
6519 <button class="unsafe-filter-tab active" data-filter="critical">
6520 ðĻ Critical (<span id="unsafe-critical-count">0</span>)
6521 </button>
6522 <button class="unsafe-filter-tab" data-filter="leaks">
6523 ð§ Leaks (<span id="unsafe-leak-count">0</span>)
6524 </button>
6525 <button class="unsafe-filter-tab" data-filter="cross-boundary">
6526 ð Cross-Boundary (<span id="unsafe-boundary-count">0</span>)
6527 </button>
6528 <button class="unsafe-filter-tab" data-filter="risk-assessment">
6529 â ïļ Risk Analysis
6530 </button>
6531 <button class="unsafe-filter-tab" data-filter="all">
6532 ð All Data (<span id="unsafe-total-count">0</span>)
6533 </button>
6534 </div>
6535 </div>
6536 </div>
6537
6538 <!-- Enhanced Cross-Boundary Swimlane Timeline -->
6539 <div class="enhanced-swimlane-container" id="enhancedSwimlaneContainer">
6540 <div class="swimlane-header">
6541 <h3><i class="fa fa-timeline"></i> Memory Passport Timeline</h3>
6542 <div class="timeline-controls">
6543 <button id="timelineZoomIn" class="timeline-control-btn">
6544 <i class="fa fa-search-plus"></i> Zoom In
6545 </button>
6546 <button id="timelineZoomOut" class="timeline-control-btn">
6547 <i class="fa fa-search-minus"></i> Zoom Out
6548 </button>
6549 <button id="timelineReset" class="timeline-control-btn">
6550 <i class="fa fa-refresh"></i> Reset View
6551 </button>
6552 </div>
6553 </div>
6554
6555 <div class="dual-swimlane">
6556 <!-- Rust Safety Domain -->
6557 <div class="swimlane rust-domain">
6558 <div class="domain-label">
6559 <div class="domain-icon">ðĶ</div>
6560 <div class="domain-info">
6561 <h4>Rust Safety Domain</h4>
6562 <p>Ownership & borrow checker controlled</p>
6563 </div>
6564 </div>
6565 <div class="timeline-track" id="rustTimelineTrack"></div>
6566 </div>
6567
6568 <!-- Timeline Axis -->
6569 <div class="timeline-axis" id="timelineAxis"></div>
6570
6571 <!-- C/FFI Domain -->
6572 <div class="swimlane ffi-domain">
6573 <div class="domain-label">
6574 <div class="domain-icon">âĄ</div>
6575 <div class="domain-info">
6576 <h4>C/FFI Domain</h4>
6577 <p>Manual memory management</p>
6578 </div>
6579 </div>
6580 <div class="timeline-track" id="ffiTimelineTrack"></div>
6581 </div>
6582 </div>
6583
6584 <!-- Integrated Risk Analysis Section -->
6585 <div class="integrated-risk-section">
6586 <div class="risk-section-header">
6587 <h4><i class="fa fa-exclamation-triangle"></i> Safety Risk Analysis</h4>
6588 <div class="risk-summary-stats">
6589 <span class="risk-stat high-risk">
6590 <span id="high-risk-count">0</span> High
6591 </span>
6592 <span class="risk-stat medium-risk">
6593 <span id="medium-risk-count">0</span> Medium
6594 </span>
6595 <span class="risk-stat low-risk">
6596 <span id="low-risk-count">0</span> Low
6597 </span>
6598 </div>
6599 </div>
6600
6601 <!-- Risk Analysis Tabs -->
6602 <div class="risk-analysis-tabs">
6603 <button class="risk-tab active" data-view="table">
6604 <i class="fa fa-table"></i> Risk Items
6605 </button>
6606 <button class="risk-tab" data-view="patterns">
6607 <i class="fa fa-chart-pie"></i> Pattern Analysis
6608 </button>
6609 <button class="risk-tab" data-view="locations">
6610 <i class="fa fa-map-marker"></i> Code Locations
6611 </button>
6612 </div>
6613
6614 <!-- Risk Views Container -->
6615 <div class="risk-views-container">
6616 <!-- Risk Items Table View -->
6617 <div id="riskTableView" class="risk-view active">
6618 <div class="scroll" style="max-height: 200px;">
6619 <table>
6620 <thead>
6621 <tr>
6622 <th>Location</th>
6623 <th>Operation</th>
6624 <th>Risk Level</th>
6625 <th>Memory Size</th>
6626 <th>Assessment</th>
6627 <th>Actions</th>
6628 </tr>
6629 </thead>
6630 <tbody id="unsafeTable"></tbody>
6631 </table>
6632 </div>
6633 </div>
6634
6635 <!-- Pattern Analysis View -->
6636 <div id="riskPatternsView" class="risk-view" style="display: none;">
6637 <div id="riskPatternsChart" style="height: 200px; background: var(--bg-secondary); border-radius: 8px;">
6638 <div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">
6639 <i class="fa fa-chart-pie" style="font-size: 1.5rem; opacity: 0.5;"></i>
6640 <span style="margin-left: 1rem;">Risk pattern analysis loading...</span>
6641 </div>
6642 </div>
6643 </div>
6644
6645 <!-- Code Locations View -->
6646 <div id="riskLocationsView" class="risk-view" style="display: none;">
6647 <div id="riskLocationsHeatmap" style="height: 200px; background: var(--bg-secondary); border-radius: 8px;">
6648 <div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary);">
6649 <i class="fa fa-map" style="font-size: 1.5rem; opacity: 0.5;"></i>
6650 <span style="margin-left: 1rem;">Code location heatmap loading...</span>
6651 </div>
6652 </div>
6653 </div>
6654 </div>
6655 </div>
6656
6657 <!-- Enhanced Legend with Pattern Recognition -->
6658 <div class="enhanced-legend">
6659 <div class="legend-section">
6660 <h4>Event Types</h4>
6661 <div class="legend-items">
6662 <div class="legend-item">
6663 <span class="event-symbol rust-alloc">â</span>
6664 <span>Unsafe Rust Allocation</span>
6665 </div>
6666 <div class="legend-item">
6667 <span class="event-symbol ffi-alloc">â</span>
6668 <span>FFI C Allocation</span>
6669 </div>
6670 <div class="legend-item">
6671 <span class="event-symbol boundary-up">âē</span>
6672 <span>FFI â Rust Transfer</span>
6673 </div>
6674 <div class="legend-item">
6675 <span class="event-symbol boundary-down">âž</span>
6676 <span>Rust â FFI Transfer</span>
6677 </div>
6678 <div class="legend-item">
6679 <span class="event-symbol dealloc">â</span>
6680 <span>Memory Deallocation</span>
6681 </div>
6682 </div>
6683 </div>
6684 <div class="legend-section">
6685 <h4>Risk Patterns</h4>
6686 <div class="legend-items">
6687 <div class="legend-item">
6688 <span class="risk-indicator high-risk"></span>
6689 <span>High Risk - Potential double-free</span>
6690 </div>
6691 <div class="legend-item">
6692 <span class="risk-indicator medium-risk"></span>
6693 <span>Medium Risk - Ownership unclear</span>
6694 </div>
6695 <div class="legend-item">
6696 <span class="risk-indicator leak-risk"></span>
6697 <span>Memory Leak - No deallocation</span>
6698 </div>
6699 </div>
6700 </div>
6701 </div>
6702 </div>
6703
6704 <!-- Memory Passport Modal -->
6705 <div id="memoryPassport" class="memory-passport-modal" style="display: none;">
6706 <div class="passport-content">
6707 <div class="passport-header">
6708 <h3><i class="fa fa-id-card"></i> Memory Passport</h3>
6709 <button class="passport-close">×</button>
6710 </div>
6711 <div class="passport-body" id="passportBody">
6712 <!-- Dynamic content populated by JavaScript -->
6713 </div>
6714 </div>
6715 </div>
6716
6717 <!-- Risk Action Modal -->
6718 <div id="riskActionModal" class="memory-passport-modal" style="display: none;">
6719 <div class="passport-content">
6720 <div class="passport-header">
6721 <h3><i class="fa fa-tools"></i> Risk Mitigation Actions</h3>
6722 <button class="passport-close" onclick="closeRiskActionModal()">×</button>
6723 </div>
6724 <div class="passport-body" id="riskActionBody">
6725 <!-- Dynamic content populated by JavaScript -->
6726 </div>
6727 </div>
6728 </div>
6729 </section>
6730
6731 <!-- Memory Analysis Dashboard module removed -->
6732
6733 <div class="card">
6734 <h2>Memory Over Time</h2>
6735 <div class="chart-container">
6736 <canvas id="timelineChart"></canvas>
6737 </div>
6738 <div style="margin-top:8px; font-size:12px; color: var(--text-secondary); display:flex; gap:8px; align-items:center;">
6739 <label style="display:flex; align-items:center; gap:6px; cursor:pointer;">
6740 <input id="toggleGrowthRate" type="checkbox" style="accent-color: var(--primary-green)">
6741 <span>Show Growth Rate</span>
6742 </label>
6743 <span style="opacity:0.8">Left Y: Cumulative memory, Right Y: Growth rate</span>
6744 </div>
6745 <!-- Integrated Key Metrics -->
6746 <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-top: 12px;">
6747 <div style="text-align: center; padding: 8px; background: var(--bg-secondary); border-radius: 6px;">
6748 <div style="font-size: 1rem; font-weight: 700; color: var(--primary-blue);" id="memoryFragmentation">0%</div>
6749 <div style="font-size: 0.7rem; color: var(--text-secondary);">Fragmentation</div>
6750 </div>
6751 <div style="text-align: center; padding: 8px; background: var(--bg-secondary); border-radius: 6px;">
6752 <div style="font-size: 1rem; font-weight: 700; color: var(--primary-green);" id="memoryEfficiency">0%</div>
6753 <div style="font-size: 0.7rem; color: var(--text-secondary);">Efficiency</div>
6754 </div>
6755 </div>
6756 </div>
6757 </section>
6758
6759 <!-- Memory Distribution Visualization -->
6760 <section class="card">
6761 <h2><i class="fa fa-chart-area"></i> Memory Distribution Visualization</h2>
6762 <!-- åĻæå°šåŊļæ§åķ -->
6763 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
6764 <div style="display: flex; gap: 8px; align-items: center;">
6765 <label style="font-size: 0.8rem; color: var(--text-secondary);">Scale:</label>
6766 <input type="range" id="memoryDistScale" min="50" max="200" value="100"
6767 style="width: 80px; accent-color: var(--primary-blue);">
6768 <span id="memoryDistScaleValue" style="font-size: 0.8rem; color: var(--text-secondary); min-width: 30px;">100%</span>
6769 </div>
6770 <div style="display: flex; gap: 8px;">
6771 <button id="memoryDistFit" class="theme-toggle"
6772 style="background: var(--primary-green); font-size: 10px; padding: 4px 8px;">
6773 <i class="fa fa-expand-arrows-alt"></i>
6774 <span>Fit</span>
6775 </button>
6776 <button id="memoryDistReset" class="theme-toggle"
6777 style="background: var(--primary-orange); font-size: 10px; padding: 4px 8px;">
6778 <i class="fa fa-refresh"></i>
6779 <span>Reset</span>
6780 </button>
6781 </div>
6782 </div>
6783
6784 <div id="memoryDistributionViz"
6785 style="height: 200px; background: var(--bg-secondary); border-radius: 8px; position: relative; overflow: hidden; border: 1px solid var(--border-light);">
6786 <!-- Memory blocks visualization will be inserted here -->
6787 </div>
6788 </section>
6789
6790 <!-- Memory Analysis (wider layout) -->
6791 <section class="grid grid-2">
6792 <div class="card">
6793 <h2>Type Treemap</h2>
6794 <div id="treemap" class="chart-container"
6795 style="height: 360px; padding: 0; background: var(--bg-secondary); border: 1px solid var(--border-light); border-radius: 8px; overflow: hidden;">
6796 </div>
6797 </div>
6798 <div class="card">
6799 <h2><i class="fa fa-timeline"></i> Variable Lifecycle Visualization</h2>
6800 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
6801 <div style="display: flex; gap: 6px;">
6802 <button id="filter-heap" class="theme-toggle"
6803 style="background: var(--primary-orange); font-size: 10px; padding: 3px 6px;">
6804 <span>Heap</span>
6805 </button>
6806 <button id="filter-stack" class="theme-toggle"
6807 style="background: var(--primary-blue); font-size: 10px; padding: 3px 6px;">
6808 <span>Stack</span>
6809 </button>
6810 <button id="toggle-lifecycle" class="theme-toggle"
6811 style="background: var(--primary-green); font-size: 10px; padding: 3px 6px;">
6812 <span>All</span>
6813 </button>
6814 </div>
6815 <div style="display: flex; gap: 12px; font-size: 0.75rem; color: var(--text-secondary);">
6816 <span>Heap: <span id="heap-count-mini"
6817 style="color: var(--primary-orange); font-weight: 600;">-</span></span>
6818 <span>Stack: <span id="stack-count-mini"
6819 style="color: var(--primary-blue); font-weight: 600;">-</span></span>
6820 </div>
6821 </div>
6822 <div style="margin-bottom: 8px;">
6823 <button id="manual-init-btn"
6824 style="background: var(--primary-red); color: white; border: none; padding: 6px 12px; border-radius: 4px; font-size: 0.8rem; cursor: pointer;">
6825 ð Manual Initialize
6826 </button>
6827 <span id="init-status" style="margin-left: 8px; font-size: 0.8rem; color: var(--text-secondary);">Waiting for
6828 data...</span>
6829 </div>
6830 <div id="lifecycleVisualizationContainer" class="scroll" style="max-height: 320px; padding: 8px;">
6831 <!-- Variable lifecycle items will be inserted here -->
6832 </div>
6833 </div>
6834 </section>
6835
6836 <!-- Advanced Analysis -->
6837 <section class="grid grid-2">
6838 <div class="card">
6839 <h2><i class="fa fa-puzzle-piece"></i> Memory Fragmentation Analysis</h2>
6840 <div style="padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
6841 <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 16px;">
6842 <div style="text-align: center; padding: 12px; background: var(--bg-primary); border-radius: 6px;">
6843 <div style="font-size: 1.2rem; font-weight: 700; color: var(--primary-blue);" id="fragmentation-level">5.2%</div>
6844 <div style="font-size: 0.7rem; color: var(--text-secondary);">Fragmentation</div>
6845 </div>
6846 <div style="text-align: center; padding: 12px; background: var(--bg-primary); border-radius: 6px;">
6847 <div style="font-size: 1.2rem; font-weight: 700; color: var(--primary-green);" id="memory-utilization">94.8%</div>
6848 <div style="font-size: 0.7rem; color: var(--text-secondary);">Utilization</div>
6849 </div>
6850 </div>
6851 <div id="memoryFragmentation" style="height: 120px; background: var(--bg-primary); border: 1px solid var(--border-light); border-radius: 6px; padding: 8px;">
6852 <!-- Simple fragmentation visualization -->
6853 <div style="display: flex; align-items: center; height: 100%; gap: 2px;">
6854 <div style="height: 100%; width: 15%; background: var(--primary-blue); border-radius: 2px; opacity: 0.8;"></div>
6855 <div style="height: 60%; width: 8%; background: var(--primary-orange); border-radius: 2px; opacity: 0.6;"></div>
6856 <div style="height: 80%; width: 12%; background: var(--primary-blue); border-radius: 2px; opacity: 0.8;"></div>
6857 <div style="height: 30%; width: 5%; background: var(--primary-red); border-radius: 2px; opacity: 0.5;"></div>
6858 <div style="height: 100%; width: 20%; background: var(--primary-blue); border-radius: 2px; opacity: 0.8;"></div>
6859 <div style="height: 40%; width: 6%; background: var(--primary-orange); border-radius: 2px; opacity: 0.6;"></div>
6860 <div style="height: 90%; width: 18%; background: var(--primary-blue); border-radius: 2px; opacity: 0.8;"></div>
6861 <div style="height: 25%; width: 4%; background: var(--primary-red); border-radius: 2px; opacity: 0.5;"></div>
6862 <div style="height: 70%; width: 12%; background: var(--primary-blue); border-radius: 2px; opacity: 0.8;"></div>
6863 </div>
6864 <div style="text-align: center; font-size: 0.7rem; color: var(--text-secondary); margin-top: 4px;">
6865 Memory Layout: <span style="color: var(--primary-blue);">â Allocated</span>
6866 <span style="color: var(--primary-orange);">â Small Gaps</span>
6867 <span style="color: var(--primary-red);">â Large Gaps</span>
6868 </div>
6869 </div>
6870 </div>
6871 </div>
6872 <div class="card">
6873 <h2><i class="fa fa-fire"></i> Borrow Activity Heatmap</h2>
6874 <div id="borrowPatternChart" style="height: 200px;"></div>
6875 </div>
6876 </section>
6877
6878 <!-- Memory Allocation Details (moved from Advanced Analysis) -->
6879 <section class="grid grid-2">
6880 <div class="card">
6881 <h2><i class="fa fa-table"></i> Memory Allocation Details</h2>
6882 <div class="scroll">
6883 <table>
6884 <thead>
6885 <tr>
6886 <th>Variable</th>
6887 <th>Type</th>
6888 <th>Size</th>
6889 <th>Status</th>
6890 </tr>
6891 </thead>
6892 <tbody id="allocTable"></tbody>
6893 </table>
6894 </div>
6895 </div>
6896
6897 <div class="card">
6898 <h2><i class="fa fa-info-circle"></i> System Status</h2>
6899 <div style="padding: 16px; background: var(--bg-secondary); border-radius: 8px;">
6900 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
6901 <div style="text-align: center; padding: 12px; background: var(--bg-primary); border-radius: 6px;">
6902 <div style="font-size: 1.2rem; font-weight: 700; color: var(--primary-blue);" id="dashboard-status">Ready</div>
6903 <div style="font-size: 0.7rem; color: var(--text-secondary);">Dashboard</div>
6904 </div>
6905 <div style="text-align: center; padding: 12px; background: var(--bg-primary); border-radius: 6px;">
6906 <div style="font-size: 1.2rem; font-weight: 700; color: var(--primary-green);" id="data-status">Loading</div>
6907 <div style="font-size: 0.7rem; color: var(--text-secondary);">Data Status</div>
6908 </div>
6909 </div>
6910 </div>
6911 </div>
6912 </section>
6913
6914
6915 <!-- Variable Relationships (Full Width) -->
6916 <section class="card">
6917 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap;">
6918 <h2 style="margin: 0;"><i class="fa fa-share-alt"></i> Variable Relationships Graph</h2>
6919 <div style="display: flex; gap: 8px; flex-wrap: nowrap;">
6920 <button id="reset-zoom" class="theme-toggle"
6921 style="background: var(--primary-orange); font-size: 12px; padding: 6px 12px; white-space: nowrap;">
6922 <i class="fa fa-expand"></i>
6923 <span>Reset View</span>
6924 </button>
6925 <button id="auto-layout" class="theme-toggle"
6926 style="background: var(--primary-green); font-size: 12px; padding: 6px 12px; white-space: nowrap;">
6927 <i class="fa fa-magic"></i>
6928 <span>Auto Layout</span>
6929 </button>
6930 </div>
6931 </div>
6932 <div id="graph"
6933 style="height: 400px; background: var(--bg-secondary); border: 1px solid var(--border-light); border-radius: 8px; position: relative; overflow: hidden;">
6934 </div>
6935 </section>
6936
6937 </div>
6938
6939 <script>
6940 // Global safe update function - must be defined first
6941 function safeUpdateElement(id, value, defaultValue = '-') {
6942 try {
6943 const el = document.getElementById(id);
6944 if (el) {
6945 el.textContent = value;
6946 return true;
6947 } else {
6948 console.warn(`Element with ID '${id}' not found`);
6949 return false;
6950 }
6951 } catch (error) {
6952 console.error(`Error updating element ${id}:`, error);
6953 return false;
6954 }
6955 }
6956
6957 // Enhanced Lifecycle Visualization Functions
6958 function inferAllocationType(typeName) {
6959 if (!typeName) return 'unknown';
6960
6961 const heapTypes = ['Box', 'Vec', 'String', 'HashMap', 'BTreeMap', 'Arc', 'Rc', 'alloc::', 'std::collections'];
6962 const stackTypes = ['i32', 'i64', 'f32', 'f64', 'bool', 'char', 'usize', 'isize', 'u8', 'u16', 'u32', 'u64'];
6963
6964 for (const heapType of heapTypes) {
6965 if (typeName.includes(heapType)) return 'heap';
6966 }
6967
6968 for (const stackType of stackTypes) {
6969 if (typeName.includes(stackType)) return 'stack';
6970 }
6971
6972 if (typeName.includes('*') || typeName.includes('&')) return 'heap';
6973
6974 return 'unknown';
6975 }
6976
6977 function formatTimestamp(timestamp) {
6978 if (!timestamp || isNaN(timestamp) || timestamp <= 0) return 'N/A';
6979
6980 // Handle different timestamp formats
6981 let timeInMs;
6982 if (timestamp > 1e15) {
6983 // Nanoseconds
6984 timeInMs = timestamp / 1000000;
6985 } else if (timestamp > 1e12) {
6986 // Microseconds
6987 timeInMs = timestamp / 1000;
6988 } else {
6989 // Already in milliseconds
6990 timeInMs = timestamp;
6991 }
6992
6993 const date = new Date(timeInMs);
6994 if (isNaN(date.getTime())) return 'N/A';
6995
6996 // Format as relative time if recent, otherwise absolute time
6997 const now = Date.now();
6998 const diffMs = Math.abs(now - timeInMs);
6999
7000 if (diffMs < 1000) {
7001 return `${diffMs.toFixed(0)}ms ago`;
7002 } else if (diffMs < 60000) {
7003 return `${(diffMs / 1000).toFixed(1)}s ago`;
7004 } else {
7005 return date.toLocaleTimeString() + '.' + String(date.getMilliseconds()).padStart(3, '0');
7006 }
7007 }
7008
7009 function formatTimestampSafe(timestamp, fallbackIndex) {
7010 if (!timestamp || isNaN(timestamp) || timestamp <= 0) {
7011 // Return synthetic time based on index
7012 return `T+${(fallbackIndex * 1).toFixed(1)}ms`;
7013 }
7014
7015 // Handle nanosecond timestamps (typical format from Rust)
7016 let timeInMs;
7017 if (timestamp > 1e15) {
7018 // Nanoseconds (typical Rust timestamp format)
7019 timeInMs = timestamp / 1000000;
7020 } else if (timestamp > 1e12) {
7021 // Microseconds
7022 timeInMs = timestamp / 1000;
7023 } else if (timestamp > 1e9) {
7024 // Milliseconds
7025 timeInMs = timestamp;
7026 } else {
7027 // Seconds to milliseconds
7028 timeInMs = timestamp * 1000;
7029 }
7030
7031 // Convert to relative time from program start
7032 // Use the first allocation timestamp as reference if available
7033 const firstTimestamp = window.analysisData?.memory_analysis?.allocations?.[0]?.timestamp_alloc || timestamp;
7034 const relativeTimeMs = (timestamp - firstTimestamp) / 1000000;
7035
7036 if (Math.abs(relativeTimeMs) < 0.001) {
7037 return 'T+0.00ms';
7038 } else if (relativeTimeMs >= 0) {
7039 return `T+${relativeTimeMs.toFixed(2)}ms`;
7040 } else {
7041 return `T${relativeTimeMs.toFixed(2)}ms`;
7042 }
7043 }
7044
7045 function calculateDropTime(allocTime, lifetimeMs) {
7046 if (!allocTime || lifetimeMs === undefined || isNaN(allocTime) || isNaN(lifetimeMs)) return null;
7047 return allocTime + (lifetimeMs * 1000000); // Convert ms to nanoseconds and add
7048 }
7049
7050 function formatLifetime(lifetimeMs) {
7051 if (lifetimeMs === undefined || lifetimeMs === null) return 'N/A';
7052 if (lifetimeMs < 1) return `${(lifetimeMs * 1000).toFixed(1)}Ξs`;
7053 if (lifetimeMs < 1000) return `${lifetimeMs.toFixed(1)}ms`;
7054 return `${(lifetimeMs / 1000).toFixed(2)}s`;
7055 }
7056
7057 function formatBytes(bytes) {
7058 if (bytes === 0) return '0 B';
7059 const k = 1024;
7060 const sizes = ['B', 'KB', 'MB', 'GB'];
7061 const i = Math.floor(Math.log(bytes) / Math.log(k));
7062 return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
7063 }
7064
7065 function initEnhancedLifecycleVisualization() {
7066 // Debug what's available
7067 console.log('Checking data availability...');
7068 console.log('window.analysisData exists:', !!window.analysisData);
7069 console.log('window.analysisData type:', typeof window.analysisData);
7070
7071 if (window.analysisData) {
7072 console.log('window.analysisData keys:', Object.keys(window.analysisData));
7073 }
7074
7075 // Try multiple data structure paths
7076 let allocations = null;
7077
7078 if (window.analysisData) {
7079 // Method 1: Direct allocations (old structure)
7080 if (window.analysisData.allocations && Array.isArray(window.analysisData.allocations)) {
7081 allocations = window.analysisData.allocations;
7082 console.log('â
Found allocations directly:', allocations.length);
7083 }
7084 // Method 2: Memory analysis structure (new structure)
7085 else if (window.analysisData.memory_analysis && window.analysisData.memory_analysis.allocations) {
7086 allocations = window.analysisData.memory_analysis.allocations;
7087 console.log('â
Found allocations in memory_analysis:', allocations.length);
7088 }
7089 // Method 3: Check all keys for allocations
7090 else {
7091 for (const [key, value] of Object.entries(window.analysisData)) {
7092 if (value && typeof value === 'object' && value.allocations && Array.isArray(value.allocations)) {
7093 allocations = value.allocations;
7094 console.log('â
Found allocations in', key + ':', allocations.length);
7095 break;
7096 }
7097 }
7098 }
7099 }
7100
7101 if (!allocations || !Array.isArray(allocations) || allocations.length === 0) {
7102 console.warn('No allocation data found in window.analysisData');
7103
7104 // Fallback: Try to get data from other global variables that might be set by the dashboard
7105 if (typeof getAllocations === 'function') {
7106 try {
7107 allocations = getAllocations();
7108 console.log('â
Got allocations from getAllocations():', allocations.length);
7109 } catch (e) {
7110 console.warn('getAllocations() failed:', e);
7111 }
7112 }
7113
7114 // Another fallback: Check if data is in a different global variable
7115 if (!allocations && window.memoryAnalysisData) {
7116 if (window.memoryAnalysisData.allocations) {
7117 allocations = window.memoryAnalysisData.allocations;
7118 console.log('â
Got allocations from memoryAnalysisData:', allocations.length);
7119 }
7120 }
7121
7122 // Final fallback: Try to extract from existing DOM elements
7123 if (!allocations) {
7124 const existingTable = document.getElementById('allocTable');
7125 if (existingTable && existingTable.children.length > 1) {
7126 console.log('Trying to extract data from existing table...');
7127 // This is a last resort - we'll create dummy data based on table rows
7128 const rows = existingTable.querySelectorAll('tbody tr');
7129 if (rows.length > 0) {
7130 allocations = Array.from(rows).map((row, index) => {
7131 const cells = row.querySelectorAll('td');
7132 return {
7133 var_name: cells[0]?.textContent || `var_${index}`,
7134 type_name: cells[1]?.textContent || 'unknown',
7135 size: parseInt(cells[2]?.textContent) || 0,
7136 timestamp_alloc: Date.now() * 1000000 + index * 1000000,
7137 lifetime_ms: parseFloat(cells[3]?.textContent) || 1.0,
7138 is_leaked: cells[4]?.textContent?.includes('Yes') || false
7139 };
7140 });
7141 console.log('â
Extracted', allocations.length, 'allocations from existing table');
7142 }
7143 }
7144 }
7145
7146 if (!allocations || allocations.length === 0) {
7147 console.error('â No allocation data available from any source');
7148 return;
7149 }
7150 }
7151
7152 if (allocations.length === 0) {
7153 console.warn('No allocation data found');
7154 return;
7155 }
7156
7157 console.log('â
Found', allocations.length, 'allocations for enhanced visualization');
7158 console.log('Sample allocation:', allocations[0]);
7159
7160 // Statistics
7161 let heapCount = 0, stackCount = 0, unknownCount = 0;
7162 let totalLifetime = 0, validLifetimes = 0;
7163 let totalMemory = 0;
7164
7165 allocations.forEach(alloc => {
7166 const type = inferAllocationType(alloc.type_name);
7167 if (type === 'heap') heapCount++;
7168 else if (type === 'stack') stackCount++;
7169 else unknownCount++;
7170
7171 // Check multiple possible lifetime fields and ensure they're valid numbers
7172 const lifetime = alloc.lifetime_ms || alloc.lifetime || 0;
7173 if (lifetime !== undefined && lifetime !== null && !isNaN(lifetime) && lifetime > 0) {
7174 totalLifetime += lifetime;
7175 validLifetimes++;
7176 }
7177
7178 totalMemory += alloc.size || 0;
7179 });
7180
7181 console.log('Statistics calculated:', { heapCount, stackCount, totalLifetime, validLifetimes });
7182
7183 // Update mini counters
7184 const heapCountMini = document.getElementById('heap-count-mini');
7185 const stackCountMini = document.getElementById('stack-count-mini');
7186
7187 if (heapCountMini) {
7188 safeUpdateElement('heap-count-mini', heapCount);
7189 console.log('Updated heap-count-mini:', heapCount);
7190 } else {
7191 console.warn('heap-count-mini element not found');
7192 }
7193
7194 if (stackCountMini) {
7195 safeUpdateElement('stack-count-mini', stackCount);
7196 console.log('Updated stack-count-mini:', stackCount);
7197 } else {
7198 console.warn('stack-count-mini element not found');
7199 }
7200
7201 // Create lifecycle visualization
7202 console.log('Calling createLifecycleVisualization...');
7203 createLifecycleVisualization(allocations);
7204
7205 // Update enhanced statistics
7206 console.log('Calling updateEnhancedStatistics...');
7207 updateEnhancedStatistics(allocations, heapCount, stackCount, validLifetimes, totalLifetime);
7208
7209 // Setup filters
7210 console.log('Calling setupLifecycleFilters...');
7211 setupLifecycleFilters(allocations);
7212
7213 console.log('â
All enhanced features processing completed');
7214 }
7215
7216 function createLifecycleVisualization(allocations) {
7217 console.log('createLifecycleVisualization called with', allocations.length, 'allocations');
7218 const container = document.getElementById('lifecycleVisualizationContainer');
7219 if (!container) {
7220 console.error('â Lifecycle visualization container not found in DOM');
7221 return;
7222 }
7223 console.log('â
Found lifecycleVisualizationContainer, creating visualization...');
7224 container.innerHTML = '';
7225
7226 // Calculate timeline bounds
7227 const timestamps = allocations.map(a => a.timestamp_alloc).filter(t => t);
7228 const minTime = Math.min(...timestamps);
7229 const maxTime = Math.max(...timestamps);
7230 const timeRange = maxTime - minTime || 1;
7231
7232 allocations.forEach((alloc, index) => {
7233 const allocType = inferAllocationType(alloc.type_name);
7234 let startTime = alloc.timestamp_alloc || alloc.timestamp || Date.now() * 1000000;
7235 const lifetime = alloc.lifetime_ms || alloc.lifetime || 0;
7236 let endTime = startTime + (lifetime * 1000000); // Convert ms to nanoseconds
7237
7238 // Calculate lifetime from timestamps if not provided
7239 let calculatedLifetime = 0;
7240 if (alloc.timestamp_dealloc && alloc.timestamp_alloc) {
7241 calculatedLifetime = (alloc.timestamp_dealloc - alloc.timestamp_alloc) / 1000000; // Convert to ms
7242 } else if (alloc.lifetime_ms) {
7243 calculatedLifetime = alloc.lifetime_ms;
7244 } else {
7245 // Default lifetime for active allocations
7246 calculatedLifetime = 10; // 10ms default
7247 }
7248
7249 // Use actual timestamps if available, otherwise create synthetic ones
7250 if (alloc.timestamp_alloc && !isNaN(alloc.timestamp_alloc)) {
7251 startTime = alloc.timestamp_alloc;
7252 endTime = alloc.timestamp_dealloc || (startTime + calculatedLifetime * 1000000);
7253 } else {
7254 // Create synthetic timeline based on allocation order
7255 startTime = minTime + (index * (timeRange / allocations.length));
7256 endTime = startTime + (calculatedLifetime * 1000000);
7257 }
7258
7259 // Debug and validate time data
7260 console.log('Debug allocation:', {
7261 var_name: alloc.var_name,
7262 timestamp_alloc: alloc.timestamp_alloc,
7263 timestamp_dealloc: alloc.timestamp_dealloc,
7264 calculatedLifetime: calculatedLifetime,
7265 startTime: startTime,
7266 endTime: endTime,
7267 isValidStart: !isNaN(startTime) && startTime > 0,
7268 isValidEnd: !isNaN(endTime) && endTime > 0
7269 });
7270
7271 // Calculate positions and widths with bounds checking
7272 let startPercent = ((startTime - minTime) / timeRange) * 100;
7273 let endPercent = ((endTime - minTime) / timeRange) * 100;
7274
7275 // Ensure values are within bounds
7276 startPercent = Math.max(0, Math.min(startPercent, 100));
7277 endPercent = Math.max(startPercent, Math.min(endPercent, 100));
7278
7279 let width = endPercent - startPercent;
7280 width = Math.max(width, 2); // Minimum 2% width
7281 width = Math.min(width, 100 - startPercent); // Don't exceed container
7282
7283 // Create lifecycle item
7284 const item = document.createElement('div');
7285 item.className = `lifecycle-item ${allocType}`;
7286 item.setAttribute('data-type', allocType);
7287
7288 // Enhanced solid colors for better visibility
7289 const barColor = allocType === 'heap' ? '#ff6b35' :
7290 allocType === 'stack' ? '#4dabf7' : '#868e96';
7291 const barGradient = allocType === 'heap' ? '#ff6b35' :
7292 allocType === 'stack' ? '#4dabf7' :
7293 '#868e96';
7294
7295 item.innerHTML = `
7296 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
7297 <div style="display: flex; align-items: center; gap: 8px;">
7298 <span style="font-weight: 600; font-size: 0.9rem; min-width: 120px;">${alloc.var_name || 'unnamed'}</span>
7299 <span class="allocation-type type-${allocType}">${allocType}</span>
7300 </div>
7301 <div style="display: flex; align-items: center; gap: 8px; font-size: 0.75rem; color: var(--text-secondary);">
7302 <span>${formatBytes(alloc.size || 0)}</span>
7303 <span>${formatLifetime(lifetime)}</span>
7304 </div>
7305 </div>
7306
7307 <!-- Enhanced Timeline Progress Bar -->
7308 <div style="position: relative; height: 24px; background: linear-gradient(90deg, #e8eaed, #ddd); border-radius: 12px; margin: 10px 0; border: 1px solid #bbb; box-shadow: inset 0 1px 2px rgba(0,0,0,0.08);">
7309
7310 <!-- Variable active period with enhanced gradient -->
7311 <div style="position: absolute; top: 1px; left: ${startPercent}%; width: ${width}%; height: calc(100% - 2px);
7312 background: ${barGradient};
7313 border-radius: 11px;
7314 box-shadow: 0 2px 8px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.3);
7315 transition: all 0.3s ease;
7316 position: relative;
7317 overflow: hidden;">
7318
7319 <!-- Animated shine effect -->
7320 <div style="position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
7321 background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
7322 animation: shine 2s infinite;"></div>
7323 </div>
7324
7325 ${alloc.is_leaked ? `
7326 <!-- Leaked indicator -->
7327 <div style="position: absolute; top: -3px; right: 2px; width: 20px; height: 30px;
7328 background: linear-gradient(45deg, #ff4757, #ff3742);
7329 border-radius: 2px;
7330 box-shadow: 0 2px 4px rgba(0,0,0,0.3);
7331 display: flex; align-items: center; justify-content: center;
7332 font-size: 10px; color: white; font-weight: bold;">â </div>
7333 ` : ''}
7334 </div>
7335
7336 <!-- Time info -->
7337 <div style="display: flex; justify-content: space-between; font-size: 0.7rem; color: var(--text-secondary); font-family: monospace;">
7338 <span>Start: ${formatTimestampSafe(startTime, index)}</span>
7339 <span>${alloc.is_leaked ? 'LEAKED' : (formatTimestampSafe(endTime, index + 1) !== 'N/A' ? 'End: ' + formatTimestampSafe(endTime, index + 1) : 'Active')}</span>
7340 </div>
7341 `;
7342
7343 // Enhanced hover effect and styling
7344 item.style.cssText += `
7345 margin-bottom: 18px;
7346 padding: 16px;
7347 background: linear-gradient(135deg, var(--bg-primary), #fafbfc);
7348 border-radius: 12px;
7349 border-left: 5px solid ${barColor};
7350 transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
7351 cursor: pointer;
7352 box-shadow: 0 2px 4px rgba(0,0,0,0.05);
7353 `;
7354
7355 item.addEventListener('mouseenter', () => {
7356 item.style.transform = 'translateX(8px) translateY(-2px)';
7357 item.style.boxShadow = '0 8px 25px rgba(0,0,0,0.15)';
7358 item.style.background = `linear-gradient(135deg, var(--bg-primary), ${barColor}08)`;
7359 });
7360
7361 item.addEventListener('mouseleave', () => {
7362 item.style.transform = 'translateX(0) translateY(0)';
7363 item.style.boxShadow = '0 2px 4px rgba(0,0,0,0.05)';
7364 item.style.background = 'linear-gradient(135deg, var(--bg-primary), #fafbfc)';
7365 });
7366
7367 container.appendChild(item);
7368 });
7369 }
7370
7371 function updateEnhancedStatistics(allocations, heapCount, stackCount, validLifetimes, totalLifetime) {
7372 console.log('Updating enhanced statistics...');
7373
7374 // Update Enhanced Memory Statistics
7375 const totalAllocsEnhanced = document.getElementById('total-allocs-enhanced');
7376 const heapStackRatio = document.getElementById('heap-stack-ratio');
7377 const avgLifetimeEnhanced = document.getElementById('avg-lifetime-enhanced');
7378 const memoryEfficiency = document.getElementById('memory-efficiency');
7379
7380 if (totalAllocsEnhanced) {
7381 safeUpdateElement('total-allocs-enhanced', allocations.length);
7382 console.log('Updated total-allocs-enhanced:', allocations.length);
7383 }
7384
7385 // Safe DOM updates with enhanced error handling
7386 if (heapStackRatio) {
7387 try {
7388 const ratio = stackCount > 0 ? (heapCount / stackCount).toFixed(1) : heapCount;
7389 safeUpdateElement('heap-stack-ratio', ratio + ':1');
7390 console.log('Updated heap-stack-ratio:', ratio + ':1');
7391 } catch (error) {
7392 console.error('Error updating heap-stack-ratio:', error);
7393 }
7394 }
7395
7396 if (avgLifetimeEnhanced) {
7397 try {
7398 const avgLifetime = validLifetimes > 0 ? formatLifetime(totalLifetime / validLifetimes) : 'N/A';
7399 safeUpdateElement('avg-lifetime-enhanced', avgLifetime);
7400 console.log('Updated avg-lifetime-enhanced:', avgLifetime);
7401 } catch (error) {
7402 console.error('Error updating avg-lifetime-enhanced:', error);
7403 }
7404 }
7405
7406 const memoryEfficiencyEl = document.getElementById('memory-efficiency');
7407 if (memoryEfficiencyEl) {
7408 try {
7409 const efficiency = allocations.length > 0 ? ((allocations.length - allocations.filter(a => a.is_leaked).length) / allocations.length * 100).toFixed(0) : 0;
7410 safeUpdateElement('memory-efficiency', efficiency + '%');
7411 console.log('Updated memory-efficiency:', efficiency + '%');
7412 } catch (error) {
7413 console.error('Error updating memory-efficiency:', error);
7414 }
7415 }
7416 }
7417
7418
7419 function setupLifecycleFilters(allocations) {
7420 const heapBtn = document.getElementById('filter-heap');
7421 const stackBtn = document.getElementById('filter-stack');
7422 const allBtn = document.getElementById('toggle-lifecycle');
7423
7424 // Check if all buttons exist
7425 if (!heapBtn || !stackBtn || !allBtn) {
7426 console.warn('Some filter buttons not found');
7427 return;
7428 }
7429
7430 let currentFilter = 'all';
7431
7432 function applyFilter(filter) {
7433 currentFilter = filter;
7434 const items = document.querySelectorAll('.lifecycle-item');
7435
7436 // Update button states
7437 [heapBtn, stackBtn, allBtn].forEach(btn => btn.style.opacity = '0.6');
7438
7439 if (filter === 'heap') {
7440 heapBtn.style.opacity = '1';
7441 items.forEach(item => {
7442 item.style.display = item.getAttribute('data-type') === 'heap' ? 'block' : 'none';
7443 });
7444 } else if (filter === 'stack') {
7445 stackBtn.style.opacity = '1';
7446 items.forEach(item => {
7447 item.style.display = item.getAttribute('data-type') === 'stack' ? 'block' : 'none';
7448 });
7449 } else {
7450 allBtn.style.opacity = '1';
7451 items.forEach(item => {
7452 item.style.display = 'block';
7453 });
7454 }
7455 }
7456
7457 heapBtn.addEventListener('click', () => applyFilter('heap'));
7458 stackBtn.addEventListener('click', () => applyFilter('stack'));
7459 allBtn.addEventListener('click', () => applyFilter('all'));
7460
7461 // Initialize
7462 applyFilter('all');
7463 }
7464
7465 // Initialize enhanced features when DOM is loaded
7466 function initEnhancedFeatures() {
7467 try {
7468 initEnhancedLifecycleVisualization();
7469 } catch (error) {
7470 console.error('Error initializing enhanced lifecycle visualization:', error);
7471 }
7472 }
7473
7474 // Safe initialization wrapper with duplicate prevention
7475 let enhancedInitialized = false;
7476 function safeInitEnhanced() {
7477 if (enhancedInitialized) {
7478 return; // Already initialized
7479 }
7480
7481 try {
7482 initEnhancedFeatures();
7483 enhancedInitialized = true;
7484 console.log('â
Enhanced features initialized successfully');
7485 } catch (error) {
7486 console.warn('Enhanced features initialization failed:', error);
7487 }
7488 }
7489
7490 {{JS_CONTENT}}
7491
7492 // Override problematic functions AFTER JS_CONTENT loads
7493 window.updateLifecycleStatistics = function () {
7494 // Safe override - prevent errors from missing DOM elements
7495 try {
7496 // Try to find and update elements safely
7497 const elements = ['active-vars', 'freed-vars', 'leaked-vars', 'avg-lifetime-stat'];
7498 elements.forEach(id => {
7499 const el = document.getElementById(id);
7500 safeUpdateElement(id, '-');
7501 });
7502 } catch (e) {
7503 // Silently ignore errors
7504 }
7505 };
7506
7507 // Override other potential problem functions
7508 window.updateKPIMetrics = function () { return; };
7509 window.populateLifetimeTable = function () { return; };
7510 window.updateMemoryStats = function () { return; };
7511
7512 // Manual initialization function for testing
7513 function manualInitialize() {
7514 const statusEl = document.getElementById('init-status');
7515 safeUpdateElement('init-status', 'Initializing...');
7516
7517 console.log('ð Manual initialization triggered');
7518 console.log('window.analysisData:', window.analysisData);
7519
7520 if (window.analysisData && window.analysisData.memory_analysis && window.analysisData.memory_analysis.allocations) {
7521 console.log('â
Data found, calling initEnhancedLifecycleVisualization...');
7522 initEnhancedLifecycleVisualization();
7523 safeUpdateElement('init-status', 'Initialized successfully!');
7524 } else {
7525 console.warn('â No data found, trying to load...');
7526 safeUpdateElement('init-status', 'Loading data...');
7527
7528 // Try to load data manually
7529 fetch('./large_scale_user_memory_analysis.json')
7530 .then(response => response.json())
7531 .then(memoryData => {
7532 console.log('â
Manually loaded data:', memoryData);
7533 window.analysisData = {
7534 memory_analysis: memoryData
7535 };
7536 initEnhancedLifecycleVisualization();
7537 safeUpdateElement('init-status', 'Data loaded and initialized!');
7538 })
7539 .catch(error => {
7540 console.error('â Failed to load data:', error);
7541 safeUpdateElement('init-status', 'Failed to load data');
7542 });
7543 }
7544 }
7545
7546 // Wait for all scripts to load, then initialize
7547 function waitForDataAndInit() {
7548 if (window.analysisData && window.analysisData.memory_analysis && window.analysisData.memory_analysis.allocations) {
7549 safeUpdateElement('data-status', 'Processing');
7550
7551 try {
7552 safeInitEnhanced();
7553
7554 // Auto-load Safety Risk Analysis when data is ready
7555 setTimeout(() => {
7556 console.log('ðĄïļ Data ready - auto-loading Safety Risk Analysis...');
7557 try {
7558 initializeEnhancedUnsafeAnalysis();
7559 loadSafetyRisks();
7560 console.log('â
Safety Risk Analysis loaded automatically');
7561 } catch (riskError) {
7562 console.error('â Error auto-loading safety risks:', riskError);
7563 }
7564 }, 500);
7565
7566 // Initialize enhanced visualization features
7567 if (window.enhancedVisualizer) {
7568 setTimeout(() => {
7569 console.log('Initializing enhanced visualizer...');
7570 window.enhancedVisualizer.init();
7571 window.enhancedVisualizer.initializeWithData(window.analysisData);
7572 console.log('Enhanced visualizer initialized');
7573 safeUpdateElement('data-status', 'Ready');
7574 }, 1000); // Give more time for DOM elements to be ready
7575 } else {
7576 safeUpdateElement('data-status', 'Ready');
7577 }
7578 } catch (error) {
7579 console.error('â Error during data initialization:', error);
7580 safeUpdateElement('data-status', 'Error');
7581 }
7582 } else {
7583 setTimeout(waitForDataAndInit, 200);
7584 }
7585 }
7586
7587
7588 // Initialize enhanced features after everything loads
7589 // Theme System Functions
7590 function initializeThemeSystem() {
7591 console.log('ðĻ Initializing theme system...');
7592
7593 const themeToggle = document.getElementById('theme-toggle');
7594 const htmlElement = document.documentElement;
7595
7596 // Load saved theme or default to light
7597 const savedTheme = localStorage.getItem('theme') || 'light';
7598 htmlElement.className = savedTheme;
7599
7600 if (themeToggle) {
7601 // Update button text based on current theme
7602 updateThemeButtonText(savedTheme);
7603
7604 themeToggle.addEventListener('click', function() {
7605 const currentTheme = htmlElement.className;
7606 const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
7607
7608 htmlElement.className = newTheme;
7609 localStorage.setItem('theme', newTheme);
7610 updateThemeButtonText(newTheme);
7611
7612 console.log('ðĻ Theme switched to:', newTheme);
7613 });
7614 }
7615 }
7616
7617 function updateThemeButtonText(theme) {
7618 const themeToggle = document.getElementById('theme-toggle');
7619 if (themeToggle) {
7620 themeToggle.innerHTML = theme === 'dark'
7621 ? '<i class="fa fa-sun-o"></i>'
7622 : '<i class="fa fa-moon-o"></i>';
7623 themeToggle.title = theme === 'dark' ? 'Switch to Light Mode' : 'Switch to Dark Mode';
7624 }
7625 }
7626
7627 document.addEventListener('DOMContentLoaded', function () {
7628 console.log('ð Dashboard initialization started');
7629
7630 // Initialize theme system first
7631 initializeThemeSystem();
7632
7633 // Update status indicators
7634 safeUpdateElement('dashboard-status', 'Initializing');
7635 safeUpdateElement('data-status', 'Loading');
7636
7637 try {
7638 // Setup manual initialize button
7639 const manualBtn = document.getElementById('manual-init-btn');
7640 if (manualBtn) {
7641 manualBtn.addEventListener('click', manualInitialize);
7642 }
7643
7644 // Load safety risks after initialization with longer delay
7645 setTimeout(function() {
7646 try {
7647 console.log('ðĄïļ Auto-loading safety risks on dashboard initialization...');
7648 loadSafetyRisks();
7649 // Also ensure unsafe analysis is initialized
7650 if (window.analysisData) {
7651 initializeEnhancedUnsafeAnalysis();
7652 }
7653 } catch (error) {
7654 console.error('â Error loading safety risks:', error);
7655 }
7656 }, 1500); // Increased delay to ensure data is loaded
7657
7658 // Start checking for data immediately
7659 waitForDataAndInit();
7660
7661 // Initialize dashboard functions if data is already available
7662 setTimeout(() => {
7663 if (window.analysisData && window.analysisData.memory_analysis && window.analysisData.memory_analysis.allocations) {
7664 console.log('ðŊ Data ready - calling initDashboard...');
7665 try {
7666 if (typeof initDashboard === 'function') {
7667 initDashboard();
7668 } else {
7669 console.warn('initDashboard function not found, calling individual functions...');
7670 // Call individual functions from our generated JS
7671 const allocations = window.analysisData.memory_analysis.allocations;
7672 updateKPIs(allocations);
7673 renderMemoryOverTime(allocations);
7674 renderTypeTreemap(allocations);
7675 renderBorrowHeatmap(allocations);
7676 renderVariableGraph(allocations);
7677 populateAllocationTable(allocations);
7678 }
7679 } catch (error) {
7680 console.error('â Error calling dashboard functions:', error);
7681 }
7682 }
7683 }, 2000);
7684
7685 safeUpdateElement('dashboard-status', 'Ready');
7686 console.log('â
Dashboard initialization completed');
7687
7688 } catch (error) {
7689 console.error('â Dashboard initialization failed:', error);
7690 safeUpdateElement('dashboard-status', 'Error');
7691 safeUpdateElement('data-status', 'Failed');
7692 }
7693 });
7694 </script>
7695</body>
7696
7697</html>
7698"#;