Spaces:
Sleeping
Sleeping
| /** | |
| * Space Fetch - System Performance Dashboard | |
| * Frontend Logic | |
| */ | |
| // Application State | |
| const state = { | |
| systemInfo: null, | |
| benchmarks: { | |
| cpu: null, | |
| memory: null, | |
| disk: null, | |
| gpu: null | |
| }, | |
| status: { | |
| running: {}, | |
| gpu_available: false | |
| } | |
| }; | |
| // Initialization | |
| document.addEventListener('DOMContentLoaded', async () => { | |
| await fetchSystemInfo(); | |
| await checkBenchmarkStatus(); | |
| // Start polling for active benchmarks | |
| setInterval(pollBenchmarkStatus, 2000); | |
| // Start polling for memory status | |
| setInterval(updateMemoryStatus, 1000); | |
| }); | |
| async function fetchSystemInfo() { | |
| try { | |
| const response = await fetch('/api/system'); | |
| const data = await response.json(); | |
| state.systemInfo = data; | |
| renderSystemInfo(data); | |
| renderHardwareInfo(data); | |
| } catch (error) { | |
| console.error('Failed to fetch system info:', error); | |
| } | |
| } | |
| async function checkBenchmarkStatus() { | |
| try { | |
| const response = await fetch('/api/benchmark/status'); | |
| const data = await response.json(); | |
| state.status = data; | |
| // Update GPU card visibility | |
| const gpuCard = document.getElementById('gpu-card'); | |
| const gpuBench = document.getElementById('bench-gpu'); | |
| if (!data.gpu_available) { | |
| if (gpuCard) gpuCard.style.display = 'none'; | |
| if (gpuBench) gpuBench.style.display = 'none'; | |
| } | |
| // Restore any running or cached benchmarks | |
| for (const [type, isCached] of Object.entries(data.cached)) { | |
| if (isCached && !document.getElementById(`${type}-bench-content`).querySelector('.bench-result-item')) { | |
| await fetchBenchmarkResult(type); | |
| } | |
| } | |
| } catch (error) { | |
| console.error('Failed to check status:', error); | |
| } | |
| } | |
| async function pollBenchmarkStatus() { | |
| const response = await fetch('/api/benchmark/status'); | |
| const data = await response.json(); | |
| // Check for completed benchmarks | |
| for (const [type, isRunning] of Object.entries(data.running)) { | |
| if (state.status.running[type] && !isRunning) { | |
| // Benchmark finished, fetch results | |
| await fetchBenchmarkResult(type); | |
| } | |
| // Update button states | |
| const btn = document.getElementById(`run-${type}-btn`); | |
| if (btn) { | |
| if (isRunning) { | |
| btn.disabled = true; | |
| btn.innerHTML = '<span class="loading-spinner"></span> Running...'; | |
| } else { | |
| btn.disabled = false; | |
| btn.innerText = 'Run'; | |
| } | |
| } | |
| } | |
| state.status = data; | |
| } | |
| async function updateMemoryStatus() { | |
| try { | |
| const response = await fetch('/api/monitor/memory'); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| document.getElementById('memory-total').innerText = data.total_formatted; | |
| document.getElementById('memory-usage').style.width = `${data.percent}%`; | |
| document.getElementById('memory-details').innerText = `${data.used_formatted} used / ${data.available_formatted} available`; | |
| } | |
| } catch (error) { | |
| // Silent fail for polling to avoid console spam | |
| } | |
| } | |
| // Rendering Functions | |
| function renderSystemInfo(data) { | |
| const container = document.getElementById('system-info'); | |
| container.innerHTML = ` | |
| <div class="card"> | |
| <h3>Operating System</h3> | |
| <div class="info-row"> | |
| <span class="info-label">Distro</span> | |
| <span class="info-value">${data.os.distro}</span> | |
| </div> | |
| <div class="info-row"> | |
| <span class="info-label">Kernel</span> | |
| <span class="info-value">${data.os.kernel}</span> | |
| </div> | |
| <div class="info-row"> | |
| <span class="info-label">Hostname</span> | |
| <span class="info-value">${data.os.hostname}</span> | |
| </div> | |
| <div class="info-row"> | |
| <span class="info-label">Load Avg</span> | |
| <span class="info-value">${data.os.load_average.join(', ')}</span> | |
| </div> | |
| <div class="info-row"> | |
| <span class="info-label">Uptime</span> | |
| <span class="info-value">${Math.floor(data.os.uptime_seconds / 3600)}h ${Math.floor((data.os.uptime_seconds % 3600) / 60)}m</span> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h3>Python Environment</h3> | |
| <div class="info-row"> | |
| <span class="info-label">Version</span> | |
| <span class="info-value">Python ${data.os.system === 'Linux' ? '3.x' : 'Unknown'}</span> | |
| </div> | |
| <div class="info-row"> | |
| <span class="info-label">Architecture</span> | |
| <span class="info-value">${data.os.architecture}</span> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| function renderHardwareInfo(data) { | |
| // CPU | |
| document.getElementById('cpu-name').innerText = data.cpu.brand; | |
| const cpuDetails = `${data.cpu.cores_physical} Physical Cores / ${data.cpu.cores_logical} Logical Cores @ ${data.cpu.frequency.current_mhz || 'N/A'} MHz`; | |
| document.getElementById('cpu-details').innerText = cpuDetails; | |
| // Memory | |
| document.getElementById('memory-total').innerText = data.memory.total_formatted; | |
| document.getElementById('memory-usage').style.width = `${data.memory.percent}%`; | |
| document.getElementById('memory-details').innerText = `${data.memory.used_formatted} used / ${data.memory.available_formatted} available`; | |
| // Disk (First Partition) | |
| if (data.disk.length > 0) { | |
| const disk = data.disk[0]; | |
| document.getElementById('disk-total').innerText = disk.total_formatted; | |
| document.getElementById('disk-usage').style.width = `${disk.percent}%`; | |
| document.getElementById('disk-details').innerText = `${disk.used_formatted} used / ${disk.free_formatted} free (${disk.mountpoint})`; | |
| } | |
| // GPU | |
| if (data.gpu && data.gpu.length > 0) { | |
| const gpu = data.gpu[0]; | |
| document.getElementById('gpu-name').innerText = gpu.name; | |
| const memoryUsedPercent = (gpu.memory_used_mb / gpu.memory_total_mb) * 100; | |
| document.getElementById('gpu-usage').style.width = `${memoryUsedPercent}%`; | |
| document.getElementById('gpu-details').innerText = `${gpu.memory_used_mb}MB / ${gpu.memory_total_mb}MB VRAM | Driver: ${gpu.driver_version}`; | |
| } else { | |
| const gpuCard = document.getElementById('gpu-card'); | |
| if (gpuCard) gpuCard.style.display = 'none'; | |
| } | |
| } | |
| // Benchmark Actions | |
| async function runBenchmark(type) { | |
| const container = document.getElementById(`${type}-bench-content`); | |
| container.innerHTML = ` | |
| <div class="benchmark-placeholder"> | |
| <span class="loading-spinner"></span><br> | |
| Running benchmark... This may take a moment. | |
| </div> | |
| `; | |
| try { | |
| await fetch(`/api/benchmark/${type}`, { method: 'POST' }); | |
| // Polling will handle the update | |
| } catch (error) { | |
| container.innerHTML = `<div class="benchmark-placeholder" style="color: var(--danger-color)">Error starting benchmark</div>`; | |
| } | |
| } | |
| async function runAllBenchmarks() { | |
| const types = ['cpu', 'memory', 'disk']; | |
| if (state.status.gpu_available) types.push('gpu'); | |
| for (const type of types) { | |
| runBenchmark(type); | |
| } | |
| } | |
| async function fetchBenchmarkResult(type) { | |
| try { | |
| const response = await fetch(`/api/benchmark/${type}`); | |
| const data = await response.json(); | |
| if (data.results) { | |
| renderBenchmarkResult(type, data.results); | |
| } | |
| } catch (error) { | |
| console.error(`Error details for ${type}:`, error); | |
| } | |
| } | |
| function renderBenchmarkResult(type, results) { | |
| const container = document.getElementById(`${type}-bench-content`); | |
| let html = ''; | |
| if (type === 'cpu') { | |
| html += createResultItem('Single Core Integer', `${results.single_core_integer.ops_per_second} ops/s`, results.single_core_integer.score); | |
| html += createResultItem('Multi Core Integer', `${results.multi_core_integer.ops_per_second} ops/s`, results.multi_core_integer.score); | |
| html += createResultItem('Single Core Float', `${results.single_core_float.gflops} GFLOPS`, results.single_core_float.score); | |
| html += createResultItem('Multi Core Float', `${results.multi_core_float.gflops} GFLOPS`, results.multi_core_float.score); | |
| html += createResultItem('SHA256 Hash', `${results.crypto.throughput_mb_per_sec} MB/s`, results.crypto.score); | |
| html += createResultItem('Zlib Compression', `${results.compression.throughput_mb_per_sec} MB/s`, results.compression.score); | |
| html += createResultItem('Context Switches', `${results.stress.wakeups_per_second} /s`, results.stress.score); | |
| } else if (type === 'memory') { | |
| html += createResultItem('Read Bandwidth', `${results.bandwidth.read_bandwidth_gb_s} GB/s`); | |
| html += createResultItem('Write Bandwidth', `${results.bandwidth.write_bandwidth_gb_s} GB/s`); | |
| // New Metric structure | |
| if (results.latency_random) { | |
| html += createResultItem('Random Latency', `${results.latency_random.average_latency_ns} ns`); | |
| } | |
| if (results.latency_sequential) { | |
| html += createResultItem('Seq Latency', `${results.latency_sequential.average_latency_ns} ns`); | |
| } | |
| if (results.alloc_rate) { | |
| html += createResultItem('Alloc Rate', `${(results.alloc_rate.ops_per_sec / 1e6).toFixed(1)} M/s`); | |
| } | |
| if (results.cache_latency && results.cache_latency.levels) { | |
| const l1 = results.cache_latency.levels.L1; | |
| const l2 = results.cache_latency.levels.L2; | |
| const l3 = results.cache_latency.levels.L3; | |
| html += createResultItem('L1 Latency', `${l1.latency_ns} ns`); | |
| html += createResultItem('L2 Latency', `${l2.latency_ns} ns`); | |
| html += createResultItem('L3 Latency', `${l3.latency_ns} ns`); | |
| } | |
| } else if (type === 'disk') { | |
| html += createResultItem('Seq Read', `${results.sequential_read.throughput_mb_s} MB/s`, results.sequential_read.score); | |
| html += createResultItem('Seq Write', `${results.sequential_write.throughput_mb_s} MB/s`, results.sequential_write.score); | |
| html += createResultItem('Random Read', `${results.random_read_iops.iops} IOPS`, results.random_read_iops.score); | |
| html += createResultItem('Random Write', `${results.random_write_iops.iops} IOPS`, results.random_write_iops.score); | |
| } else if (type === 'gpu') { | |
| if (results.memory_bandwidth) html += createResultItem('Memory Bandwidth', `${results.memory_bandwidth.bandwidth_gb_s} GB/s`, results.memory_bandwidth.score); | |
| if (results.fp32) html += createResultItem('FP32 Compute', `${results.fp32.tflops} TFLOPS`, results.fp32.score); | |
| if (results.fp16) html += createResultItem('FP16 Compute', `${results.fp16.tflops} TFLOPS`, results.fp16.score); | |
| if (results.tensor_cores) html += createResultItem('Tensor Cores', `${results.tensor_cores.tflops} TFLOPS`, results.tensor_cores.score); | |
| } | |
| container.innerHTML = html; | |
| } | |
| function createResultItem(label, value, score) { | |
| return ` | |
| <div class="bench-result-item"> | |
| <div class="bench-label"> | |
| <span>${label}</span> | |
| </div> | |
| <div class="progress-bar" style="height: 4px; margin: 2px 0;"> | |
| <div class="progress-fill" style="width: 100%; opacity: 0.5;"></div> | |
| </div> | |
| <div class="bench-value">${value}</div> | |
| </div> | |
| `; | |
| } | |
| async function exportResults() { | |
| try { | |
| const response = await fetch('/api/export'); | |
| const data = await response.json(); | |
| const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `space-fetch-results-${new Date().toISOString().slice(0, 10)}.json`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| } catch (error) { | |
| alert('Failed to export results'); | |
| } | |
| } | |