/** * Performance charts — real-time CPU, RAM, GPU usage with Chart.js. */ import { API_BASE, getHeaders } from '../core/api.js'; import { t } from '../core/i18n.js'; import { dashboardPollInterval } from '../core/state.js'; const MAX_SAMPLES = 60; const STORAGE_KEY = 'perf_history'; let _pollTimer = null; let _charts = {}; // { cpu: Chart, ram: Chart, gpu: Chart } let _history = _loadHistory(); let _hasGpu = null; // null = unknown, true/false after first fetch function _loadHistory() { try { const raw = sessionStorage.getItem(STORAGE_KEY); if (raw) { const parsed = JSON.parse(raw); if (parsed.cpu && parsed.ram && parsed.gpu) return parsed; } } catch {} return { cpu: [], ram: [], gpu: [] }; } function _saveHistory() { try { sessionStorage.setItem(STORAGE_KEY, JSON.stringify(_history)); } catch {} } /** Returns the static HTML for the perf section (canvas placeholders). */ export function renderPerfSection() { return `
${t('dashboard.perf.cpu')} -
${t('dashboard.perf.ram')} -
${t('dashboard.perf.gpu')} -
`; } function _createChart(canvasId, color, fillColor) { const ctx = document.getElementById(canvasId); if (!ctx) return null; return new Chart(ctx, { type: 'line', data: { labels: Array(MAX_SAMPLES).fill(''), datasets: [{ data: [], borderColor: color, backgroundColor: fillColor, borderWidth: 1.5, tension: 0.3, fill: true, pointRadius: 0, }], }, options: { responsive: true, maintainAspectRatio: false, animation: false, plugins: { legend: { display: false }, tooltip: { enabled: false } }, scales: { x: { display: false }, y: { min: 0, max: 100, display: false }, }, layout: { padding: 0 }, }, }); } /** Initialize Chart.js instances on the already-mounted canvases. */ export function initPerfCharts() { _destroyCharts(); _charts.cpu = _createChart('perf-chart-cpu', '#2196F3', 'rgba(33,150,243,0.15)'); _charts.ram = _createChart('perf-chart-ram', '#4CAF50', 'rgba(76,175,80,0.15)'); _charts.gpu = _createChart('perf-chart-gpu', '#FF9800', 'rgba(255,152,0,0.15)'); // Restore any existing history data into the freshly created charts for (const key of ['cpu', 'ram', 'gpu']) { if (_charts[key] && _history[key].length > 0) { _charts[key].data.datasets[0].data = [..._history[key]]; _charts[key].data.labels = _history[key].map(() => ''); _charts[key].update(); } } } function _destroyCharts() { for (const key of Object.keys(_charts)) { if (_charts[key]) { _charts[key].destroy(); _charts[key] = null; } } } function _pushSample(key, value) { _history[key].push(value); if (_history[key].length > MAX_SAMPLES) _history[key].shift(); const chart = _charts[key]; if (!chart) return; chart.data.datasets[0].data = [..._history[key]]; chart.data.labels = _history[key].map(() => ''); chart.update(); } async function _fetchPerformance() { try { const resp = await fetch(`${API_BASE}/system/performance`, { headers: getHeaders() }); if (!resp.ok) return; const data = await resp.json(); // CPU _pushSample('cpu', data.cpu_percent); const cpuEl = document.getElementById('perf-cpu-value'); if (cpuEl) cpuEl.textContent = `${data.cpu_percent.toFixed(0)}%`; // RAM _pushSample('ram', data.ram_percent); const ramEl = document.getElementById('perf-ram-value'); if (ramEl) { const usedGb = (data.ram_used_mb / 1024).toFixed(1); const totalGb = (data.ram_total_mb / 1024).toFixed(1); ramEl.textContent = `${usedGb}/${totalGb} GB`; } // GPU if (data.gpu) { _hasGpu = true; _pushSample('gpu', data.gpu.utilization); const gpuEl = document.getElementById('perf-gpu-value'); if (gpuEl) gpuEl.textContent = `${data.gpu.utilization.toFixed(0)}% · ${data.gpu.temperature_c}°C`; } else if (_hasGpu === null) { _hasGpu = false; const card = document.getElementById('perf-gpu-card'); if (card) { const canvas = card.querySelector('canvas'); if (canvas) canvas.style.display = 'none'; const noGpu = document.createElement('div'); noGpu.className = 'perf-chart-unavailable'; noGpu.textContent = t('dashboard.perf.unavailable'); card.appendChild(noGpu); } } _saveHistory(); } catch { // Silently ignore fetch errors (e.g., network issues, tab hidden) } } export function startPerfPolling() { if (_pollTimer) return; _fetchPerformance(); _pollTimer = setInterval(_fetchPerformance, dashboardPollInterval); } export function stopPerfPolling() { if (_pollTimer) { clearInterval(_pollTimer); _pollTimer = null; } _saveHistory(); }