// MiniProfiler Web Application // Global state let socket = null; let isConnected = false; let isProfiling = false; let flamegraph = null; // Initialize on page load document.addEventListener('DOMContentLoaded', function() { initializeSocket(); initializeFlameGraph(); }); // Socket.IO Connection function initializeSocket() { socket = io(); socket.on('connect', function() { console.log('Connected to server'); updateStatus('Connected to server', false); }); socket.on('disconnect', function() { console.log('Disconnected from server'); updateStatus('Disconnected from server', false); isConnected = false; updateControlButtons(); }); socket.on('connected', function(data) { console.log('Connected to serial:', data); isConnected = true; updateStatus(`Connected to ${data.port}`, true); updateControlButtons(); document.getElementById('metadata-panel').style.display = 'block'; }); socket.on('disconnected', function() { console.log('Disconnected from serial'); isConnected = false; isProfiling = false; updateStatus('Disconnected', false); updateControlButtons(); document.getElementById('metadata-panel').style.display = 'none'; }); socket.on('profiling_started', function() { console.log('Profiling started'); isProfiling = true; updateStatus('Profiling active', true, true); updateControlButtons(); }); socket.on('profiling_stopped', function() { console.log('Profiling stopped'); isProfiling = false; updateStatus('Profiling stopped', true); updateControlButtons(); }); socket.on('metadata', function(data) { console.log('Metadata received:', data); displayMetadata(data); }); socket.on('device_status', function(data) { console.log('Device status:', data); if (data.buffer_overflows > 0) { console.warn(`Buffer overflows detected: ${data.buffer_overflows}`); } }); socket.on('summary_update', function(data) { updateSummary(data); }); socket.on('flamegraph_update', function(data) { updateFlameGraph(data); }); socket.on('statistics_update', function(data) { updateStatistics(data); }); socket.on('timeline_update', function(data) { updateTimeline(data); }); socket.on('error', function(data) { console.error('Error:', data.message); alert('Error: ' + data.message); }); } // Connection Control function toggleConnection() { if (isConnected) { socket.emit('disconnect_serial'); document.getElementById('connect-btn').textContent = 'Connect'; } else { const port = document.getElementById('serial-port').value; const baudrate = parseInt(document.getElementById('baudrate').value); const elfPath = document.getElementById('elf-path').value; if (!port) { alert('Please enter a serial port'); return; } socket.emit('connect_serial', { port: port, baudrate: baudrate, elf_path: elfPath || null }); document.getElementById('connect-btn').textContent = 'Disconnect'; } } function startProfiling() { socket.emit('start_profiling'); } function stopProfiling() { socket.emit('stop_profiling'); } function clearData() { if (confirm('Clear all profiling data?')) { socket.emit('clear_data'); } } function resetBuffers() { socket.emit('reset_buffers'); } // UI Updates function updateStatus(text, connected, profiling = false) { const statusText = document.getElementById('status-text'); const statusIndicator = document.getElementById('connection-status'); statusText.textContent = text; statusIndicator.classList.remove('connected', 'profiling'); if (profiling) { statusIndicator.classList.add('profiling'); } else if (connected) { statusIndicator.classList.add('connected'); } } function updateControlButtons() { document.getElementById('start-btn').disabled = !isConnected || isProfiling; document.getElementById('stop-btn').disabled = !isConnected || !isProfiling; document.getElementById('clear-btn').disabled = !isConnected; document.getElementById('reset-btn').disabled = !isConnected; } function displayMetadata(data) { const content = document.getElementById('metadata-content'); content.innerHTML = `
No profiling data available
'; return; } const totalTimeMs = (data.total_time_us / 1000).toFixed(2); const hottestTimeMs = (data.hottest_time_us / 1000).toFixed(2); content.innerHTML = `No timeline data available
'; return; } // Convert to Plotly timeline format const traces = []; // Group by depth for better visualization const maxDepth = Math.max(...data.map(d => d.depth)); for (let depth = 0; depth <= maxDepth; depth++) { const depthData = data.filter(d => d.depth === depth); if (depthData.length === 0) continue; const trace = { x: depthData.map(d => d.start), y: depthData.map(d => depth), text: depthData.map(d => `${d.name}${stat.address}