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