1
0
forked from wrenn/wrenn

Optimize frontend polling: visibility API, range-based intervals, skip redundant redraws

Adds Page Visibility API to StatsPanel, templates, and capsule detail
pages so polling pauses when the browser tab is hidden. Capsule metrics
now use range-appropriate poll intervals (10s for 5m/10m, up to 120s for
24h) instead of a flat 10s. Chart updates are skipped when the data
fingerprint hasn't changed, avoiding unnecessary Canvas redraws.
This commit is contained in:
2026-04-11 06:20:29 +06:00
parent dbad418093
commit 21b82c2283
4 changed files with 77 additions and 10 deletions

View File

@ -11,7 +11,7 @@
import {
fetchSandboxMetrics,
METRIC_RANGES,
METRIC_POLL_INTERVAL,
METRIC_POLL_INTERVALS,
type MetricRange,
type MetricPoint
} from '$lib/api/metrics';
@ -80,6 +80,8 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let ChartJS = $state<any>(null);
let pollInterval: ReturnType<typeof setInterval> | null = null;
let lastDataKey = ''; // fingerprint to skip redundant chart redraws
let visibilityHandler: (() => void) | null = null;
const metricsAvailable = $derived(
capsule?.status === 'running' || capsule?.status === 'paused'
@ -141,6 +143,10 @@
function updateCharts() {
if (!points.length) return;
// Skip redraw if data hasn't changed.
const key = `${points.length}:${points.at(-1)?.timestamp_unix ?? ''}`;
if (key === lastDataKey) return;
lastDataKey = key;
const labels = formatLabels(Array.from(points), range);
const w = smoothWindow(points.length);
if (chartCpu) {
@ -171,15 +177,20 @@
function setRange(r: MetricRange) {
range = r;
lastDataKey = ''; // force chart redraw on range switch
goto(`?range=${r}`, { replaceState: true, noScroll: true, keepFocus: true });
metricsLoading = true;
restartPolling();
}
function stopPolling() {
if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }
}
function restartPolling() {
if (pollInterval) clearInterval(pollInterval);
stopPolling();
loadMetrics();
pollInterval = setInterval(loadMetrics, METRIC_POLL_INTERVAL);
pollInterval = setInterval(loadMetrics, METRIC_POLL_INTERVALS[range]);
}
// Chart design tokens (match StatsPanel.svelte)
@ -342,7 +353,7 @@
});
return () => {
if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }
stopPolling();
chartCpu?.destroy(); chartCpu = null;
chartRam?.destroy(); chartRam = null;
};
@ -367,10 +378,21 @@
const mod = await import('chart.js/auto');
ChartJS = mod.Chart;
// Pause polling when the browser tab is hidden.
visibilityHandler = () => {
if (document.hidden) {
stopPolling();
} else if (activeTab === 'metrics' && metricsAvailable) {
restartPolling();
}
};
document.addEventListener('visibilitychange', visibilityHandler);
});
onDestroy(() => {
if (pollInterval) clearInterval(pollInterval);
stopPolling();
if (visibilityHandler) document.removeEventListener('visibilitychange', visibilityHandler);
chartCpu?.destroy();
chartRam?.destroy();
});