- Disable Chart.js animations on real-time FPS and perf charts - Dashboard: delta-update profile badges on state changes instead of full DOM rebuild - Dashboard: cache querySelector results in Map for metrics update loop - Dashboard: debounce poll interval slider restart (300ms) - Calibration: debounce ResizeObserver and span drag via requestAnimationFrame - Calibration: batch updateCalibrationPreview canvas render into rAF Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
181 lines
6.2 KiB
JavaScript
181 lines
6.2 KiB
JavaScript
/**
|
|
* 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 `<div class="perf-charts-grid">
|
|
<div class="perf-chart-card">
|
|
<div class="perf-chart-header">
|
|
<span class="perf-chart-label">${t('dashboard.perf.cpu')}</span>
|
|
<span class="perf-chart-value cpu" id="perf-cpu-value">-</span>
|
|
</div>
|
|
<div class="perf-chart-wrap"><canvas id="perf-chart-cpu"></canvas></div>
|
|
</div>
|
|
<div class="perf-chart-card">
|
|
<div class="perf-chart-header">
|
|
<span class="perf-chart-label">${t('dashboard.perf.ram')}</span>
|
|
<span class="perf-chart-value ram" id="perf-ram-value">-</span>
|
|
</div>
|
|
<div class="perf-chart-wrap"><canvas id="perf-chart-ram"></canvas></div>
|
|
</div>
|
|
<div class="perf-chart-card" id="perf-gpu-card">
|
|
<div class="perf-chart-header">
|
|
<span class="perf-chart-label">${t('dashboard.perf.gpu')}</span>
|
|
<span class="perf-chart-value gpu" id="perf-gpu-value">-</span>
|
|
</div>
|
|
<div class="perf-chart-wrap"><canvas id="perf-chart-gpu"></canvas></div>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
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();
|
|
}
|