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

@ -21,5 +21,14 @@ export async function fetchSandboxMetrics(id: string, range: MetricRange): Promi
export const METRIC_RANGES: MetricRange[] = ['5m', '10m', '1h', '6h', '24h'];
// All ranges poll every 10 seconds.
// Poll interval varies by range — shorter ranges need fresher data.
export const METRIC_POLL_INTERVALS: Record<MetricRange, number> = {
'5m': 10_000,
'10m': 10_000,
'1h': 30_000,
'6h': 60_000,
'24h': 120_000,
};
/** @deprecated Use METRIC_POLL_INTERVALS instead */
export const METRIC_POLL_INTERVAL = 10_000;

View File

@ -26,6 +26,8 @@
let chartRam: any = null;
let pollInterval: ReturnType<typeof setInterval> | null = null;
let lastDataKey = ''; // cheap fingerprint to skip redundant chart redraws
let visibilityHandler: (() => void) | null = null;
async function load() {
const result = await fetchStats(range);
@ -43,6 +45,10 @@
function updateCharts() {
if (!stats) return;
// Skip redraw if data hasn't changed (same length + same last label).
const key = `${stats.series.labels.length}:${stats.series.labels.at(-1) ?? ''}`;
if (key === lastDataKey) return;
lastDataKey = key;
// Use Array.from to pass plain JS arrays to Chart.js — Svelte 5 $state
// wraps arrays in reactive proxies which Chart.js can't iterate reliably.
const labels = formatLabels(Array.from(stats.series.labels), range);
@ -77,14 +83,19 @@
});
}
function stopPolling() {
if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }
}
function restartPolling() {
if (pollInterval) clearInterval(pollInterval);
stopPolling();
load();
pollInterval = setInterval(load, POLL_INTERVALS[range]);
}
function setRange(r: TimeRange) {
range = r;
lastDataKey = ''; // force chart redraw on range switch
goto(`?range=${r}`, { replaceState: true, noScroll: true, keepFocus: true });
restartPolling();
}
@ -237,10 +248,21 @@
updateCharts();
restartPolling();
// Pause polling when the browser tab is hidden to save bandwidth/CPU.
visibilityHandler = () => {
if (document.hidden) {
stopPolling();
} else {
restartPolling();
}
};
document.addEventListener('visibilitychange', visibilityHandler);
});
onDestroy(() => {
if (pollInterval) clearInterval(pollInterval);
stopPolling();
if (visibilityHandler) document.removeEventListener('visibilitychange', visibilityHandler);
chartRunning?.destroy();
chartCpu?.destroy();
chartRam?.destroy();