window.rustProxyWideOverview = (() => { const limit = () => window.rustProxyOverviewSamples?.getLimit?.() ?? 60; const configurations = { wide_protocol_rates: { unit: "rate", names: ["h1", "h2", "h3"], counters: s => [s.metrics?.http1_requests, s.metrics?.http2_requests, s.metrics?.http3_requests] }, wide_cpu: { unit: "percent", names: ["system", "process"], values: s => [(s.live_system || s.system)?.cpu_percent, (s.live_system || s.system)?.process_cpu_percent] }, wide_memory: { unit: "bytes", names: ["process", "cache"], values: s => [(s.live_system || s.system)?.process_rss_bytes, s.cache?.resident_bytes] }, wide_cache_activity: { unit: "rate", names: ["hits", "misses"], counters: s => [s.cache?.hits, s.cache?.misses] }, wide_connections: { unit: "count", names: ["open", "websockets"], values: s => [s.metrics?.active_connections, s.metrics?.websocket_active] }, wide_backend_health: { unit: "count", names: ["up", "down"], values: s => { if (!Array.isArray(s.backend_pools)) return [null, null]; const members = s.backend_pools.flatMap(pool => pool.members || []); return [members.filter(m => m.status === "up").length, members.filter(m => m.status === "down").length]; } }, wide_latency: { unit: "ms", names: ["response", "probe"], values: s => { if (!Array.isArray(s.backend_pools)) return [s.metrics?.mean_duration_ms, null]; const members = s.backend_pools.flatMap(pool => pool.members || []); const probes = members.filter(m => m.status === "up" && m.health_mode !== "off" && finite(m.last_probe_ms) !== null && finite(m.latency_ms) !== null && Number(m.last_probe_ms) > 0); return [s.metrics?.mean_duration_ms, probes.length ? probes.reduce((total, m) => total + Number(m.latency_ms), 0) / probes.length : null]; } }, wide_transform_activity: { unit: "rate", names: ["applied", "cache_hits", "skipped", "errors"], total: true, counters: s => [s.transform_metrics?.applied, s.transform_metrics?.cache_hits, s.transform_metrics?.skipped, s.transform_metrics?.errors] }, wide_compression_activity: { unit: "rate", names: ["gzip", "br", "zstd", "deflate"], total: true, counters: s => [s.metrics?.compression_gzip, s.metrics?.compression_br, s.metrics?.compression_zstd, s.metrics?.compression_deflate] }, }; const history = new Map(), baselines = new Map(), last = new Map(); let revision = null; const finite = value => value != null && Number.isFinite(Number(value)) && Number(value) >= 0 ? Number(value) : null; const format = (n, unit) => n === null ? "-" : unit === "bytes" ? fmtBytes(n) : unit === "percent" ? `${fmt(n, 1)}%` : unit === "ms" ? `${fmt(n, 1)} ms` : unit === "rate" ? `${fmt(n, 1)}/s` : fmt(n); const tile = id => document.querySelector(`[data-dashboard-widget="${id}"]`); function counterRates(id, values, now, record) { const counters = values.map(finite); if (counters.some(n => n === null)) { baselines.delete(id); return counters.map(() => null); } if (!record) return last.get(id) || counters.map(() => null); const result = rustProxyRateSampling.difference(baselines.get(id), counters, now, rustProxyRateSampling.cadence("overview_seconds")); baselines.set(id, result.next); return result.deltas ? result.deltas.map(delta => delta / result.seconds) : counters.map(() => null); } function path(values, peak) { const count = values.length; let result = "", drawing = false; for (let i = 0; i < count; i++) { const value = values[i]; if (value === null) { drawing = false; continue; } const x = Math.round(600 * i / Math.max(1, count - 1)); const y = Math.round(137 - 120 * value / peak); result += `${drawing ? " L" : " M"}${x} ${y}`; drawing = true; } return result.trim(); } function render(status, record = true) { if (!status || currentPanel !== "overview") return; if (revision !== status.revision) { reset(); revision = status.revision; } const now = performance.now(); for (const [id, config] of Object.entries(configurations)) { const node = tile(id); if (!node || node.hidden) { baselines.delete(id); continue; } const values = config.counters ? counterRates(id, config.counters(status), now, record) : config.values(status).map(finite); const series = history.get(id) || config.names.map(() => []); if (record) { values.forEach((value, i) => { series[i].push(value); if (series[i].length > limit()) series[i].shift(); }); history.set(id, series); last.set(id, values); } const peak = Math.max(config.unit === "percent" ? 100 : 1, ...series.flat().filter(n => n !== null)); config.names.forEach((key, i) => { const value = values[i]; const field = $(`overview-wide-${id}-${key}-value`); if (field) field.textContent = format(value, config.unit); $(`overview-wide-${id}-${key}-line`)?.setAttribute("d", path(series[i], peak)); }); const summary = $(`overview-wide-${id}-summary`); if (summary) summary.textContent = config.total ? format(values.every(value => value !== null) ? values.reduce((sum, value) => sum + value, 0) : null, config.unit) : format(values[0], config.unit); } } function reset() { history.clear(); baselines.clear(); last.clear(); revision = null; } window.rustProxyOverviewSamples?.onChange?.(() => { for (const series of history.values()) for (const values of series) window.rustProxyOverviewSamples.trim(values); if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") render(latestStatus, false); }); return { render, reset }; })();