v0.32.6_fix2

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-14 11:51:37 +02:00
1 parent e1ac899ec0
commit fc8711d4d8
39 files changed
+695 -232

No files matched your search

+17 -14
View File
@@ -136,12 +136,14 @@ window.rustProxyBackendDashboard = (() => {
let inPerSecond = samples.inbound.at(-1) ?? 0;
let outPerSecond = samples.outbound.at(-1) ?? 0;
const additional = { http5xx: 0, timeouts: 0, network: 0, retries: 0, queue: 0 };
let fraction = null;
let fraction = null, pausedRate = false;
if (recordSample) {
const now = performance.now();
const next = memberCounters(members);
const elapsed = previous ? (now - previous.when) / 1000 : 0;
if (elapsed > 0) {
const valid = rustProxyRateSampling.continuous(previous, now, rustProxyRateSampling.cadence("overview_seconds"));
pausedRate = previous !== null && !valid;
if (valid) {
const elapsed = (now - previous.at) / 1000;
const diff = deltas(next, previous.counters);
requestsPerSecond = diff.requests / elapsed;
errorsPerSecond = diff.errors / elapsed;
@@ -150,23 +152,24 @@ window.rustProxyBackendDashboard = (() => {
fraction = diff.requests ? 100 * diff.errors / diff.requests : null;
for (const name of Object.keys(additional)) additional[name] = diff[name] / elapsed;
} else { requestsPerSecond = 0; errorsPerSecond = 0; inPerSecond = 0; outPerSecond = 0; }
previous = { when: now, counters: next };
previous = { ...rustProxyRateSampling.stamp(now), counters: next };
if (visible("backend_activity")) push("active", active);
if (visible("backend_requests")) push("requests", requestsPerSecond);
if (!pausedRate && visible("backend_requests")) push("requests", requestsPerSecond);
if (visible("backend_capacity") && capacityPercent !== null) push("capacity", capacityPercent);
if (visible("backend_errors")) push("errors", errorsPerSecond);
if (!pausedRate && visible("backend_errors")) push("errors", errorsPerSecond);
if (visible("backend_latency") && latency !== null) push("latency", latency);
if (visible("backend_traffic")) { push("inbound", inPerSecond); push("outbound", outPerSecond); }
for (const name of Object.keys(additional)) if (visible(`backend_${name === "http5xx" ? "http5xx" : name}`)) push(name, additional[name]);
if (!pausedRate && visible("backend_traffic")) { push("inbound", inPerSecond); push("outbound", outPerSecond); }
if (!pausedRate) for (const name of Object.keys(additional)) if (visible(`backend_${name === "http5xx" ? "http5xx" : name}`)) push(name, additional[name]);
}
if (!recordSample) for (const name of Object.keys(additional)) additional[name] = samples[name].at(-1) ?? 0;
const displayRate = value => pausedRate ? "-" : shortRate(value);
if (visible("backend_activity")) {
set("backend-dashboard-activity", fmt(active));
set("backend-dashboard-activity-detail", t("backendWidgetInFlight"));
paintSeries("active", "backend-dashboard-active-line", Math.max(1, ...samples.active));
}
if (visible("backend_requests")) {
set("backend-dashboard-requests", shortRate(requestsPerSecond));
set("backend-dashboard-requests", displayRate(requestsPerSecond));
set("backend-dashboard-requests-detail", `${t("backendWidgetRequestsTotal")}: ${fmt(totalRequests)}`);
paintSeries("requests", "backend-dashboard-request-line", Math.max(1, ...samples.requests));
}
@@ -178,7 +181,7 @@ window.rustProxyBackendDashboard = (() => {
}
if (visible("backend_errors")) {
set("backend-dashboard-errors", fmt(totalErrors));
set("backend-dashboard-errors-detail", `${t("backendWidgetErrorsRate")}: ${shortRate(errorsPerSecond)}${fraction === null ? "" : ` · ${fraction.toFixed(1)}% ${t("backendWidgetErrorPercent")}`}`);
set("backend-dashboard-errors-detail", `${t("backendWidgetErrorsRate")}: ${displayRate(errorsPerSecond)}${fraction === null ? "" : ` · ${fraction.toFixed(1)}% ${t("backendWidgetErrorPercent")}`}`);
paintSeries("errors", "backend-dashboard-error-line", Math.max(1, ...samples.errors));
}
if (visible("backend_latency")) {
@@ -187,9 +190,9 @@ window.rustProxyBackendDashboard = (() => {
paintSeries("latency", "backend-dashboard-latency-line", Math.max(1, ...samples.latency));
}
if (visible("backend_traffic")) {
set("backend-dashboard-traffic", `${bytes(Math.round(inPerSecond + outPerSecond))}/s`);
set("backend-dashboard-in-rate", `${bytes(Math.round(inPerSecond))}/s`);
set("backend-dashboard-out-rate", `${bytes(Math.round(outPerSecond))}/s`);
set("backend-dashboard-traffic", pausedRate ? "-" : `${bytes(Math.round(inPerSecond + outPerSecond))}/s`);
set("backend-dashboard-in-rate", pausedRate ? "-" : `${bytes(Math.round(inPerSecond))}/s`);
set("backend-dashboard-out-rate", pausedRate ? "-" : `${bytes(Math.round(outPerSecond))}/s`);
const peak = Math.max(1, ...samples.inbound, ...samples.outbound);
paintSeries("inbound", "backend-dashboard-in-line", peak);
paintSeries("outbound", "backend-dashboard-out-line", peak);
@@ -207,7 +210,7 @@ window.rustProxyBackendDashboard = (() => {
name === "network" ? ["connect_errors_total", "dns_errors_total", "tls_errors_total", "io_errors_total"].reduce((v, key) => v + counter(member[key]), 0) :
counter(member.queue_timeouts_total) + counter(member.capacity_rejections_total)), 0);
set(field, fmt(total));
let detail = `${t("backendWidgetRateDetail")}: ${shortRate(additional[name])}`;
let detail = `${t("backendWidgetRateDetail")}: ${displayRate(additional[name])}`;
if (name === "retries") detail += ` · ${t("backendWidgetRetriesSuccess")}: ${fmt(members.reduce((n, { member }) => n + counter(member.retry_successes_total), 0))}`;
if (name === "queue") detail += ` · ${t("backendWidgetQueueRejected")}: ${fmt(members.reduce((n, { member }) => n + counter(member.capacity_rejections_total), 0))}`;
if (name === "network") detail += ` · ${t("backendWidgetNetworkBreakdown")}`;
+13 -15
View File
@@ -230,18 +230,17 @@ function renderStatus(s, systemSample = false) {
const now = performance.now();
const transformed = s.transform_metrics || {};
const counters = { total: Number(m.requests) || 0, h1: Number(m.http1_requests) || 0, h2: Number(m.http2_requests) || 0, h3: Number(m.http3_requests) || 0, transform: (Number(transformed.applied) || 0) + (Number(transformed.cache_hits) || 0) };
const rates = { total: 0, h1: 0, h2: 0, h3: 0, transform: 0 };
if (previous) {
const elapsed = Math.max(.001, (now - previous.time) / 1000);
for (const key of ["total", "h1", "h2", "h3", "transform"]) if (counters[key] >= previous[key]) rates[key] = (counters[key] - previous[key]) / elapsed;
const keys = ["total", "h1", "h2", "h3", "transform"]; const rateSample = rustProxyRateSampling.difference(previous, keys.map(key => counters[key]), now, rustProxyRateSampling.cadence("overview_seconds"));
const pausedRate = previous !== null && rateSample.deltas === null; previous = rateSample.next;
const rates = Object.fromEntries(keys.map((key, i) => [key, rateSample.deltas ? rateSample.deltas[i] / rateSample.seconds : 0]));
if (!pausedRate) {
samples.push(rates.total); samples = samples.slice(-30);
for (const key of ["h1", "h2", "h3"]) { protocolSamples[key].push(rates[key]); protocolSamples[key] = protocolSamples[key].slice(-30); }
}
previous = { time: now, ...counters };
samples.push(rates.total); samples = samples.slice(-30);
for (const key of ["h1", "h2", "h3"]) { protocolSamples[key].push(rates[key]); protocolSamples[key] = protocolSamples[key].slice(-30); }
websocketProtocolSamples.h1.push(Number(m.websocket_h1_active) || 0); websocketProtocolSamples.h1 = websocketProtocolSamples.h1.slice(-30);
websocketProtocolSamples.h2.push(Number(m.websocket_h2_active) || 0); websocketProtocolSamples.h2 = websocketProtocolSamples.h2.slice(-30);
websocketProtocolSamples.h3.push(Number(m.websocket_h3_active) || 0); websocketProtocolSamples.h3 = websocketProtocolSamples.h3.slice(-30);
transformSamples.push(rates.transform); transformSamples = transformSamples.slice(-30);
if (!pausedRate) { transformSamples.push(rates.transform); transformSamples = transformSamples.slice(-30); }
const peak = Math.max(1, ...samples, ...transformSamples);
const points = values => values.map((n, i) => `${i * 600 / Math.max(1, values.length - 1)},${120 - n / peak * 105}`).join(" ");
$("sparkline").setAttribute("points", points(samples));
@@ -249,12 +248,11 @@ function renderStatus(s, systemSample = false) {
$("sparkline-h2").setAttribute("points", points(protocolSamples.h2));
$("sparkline-h3").setAttribute("points", points(protocolSamples.h3));
$("sparkline-transform").setAttribute("points", points(transformSamples));
$("request-rate").textContent = `${rates.total.toFixed(1)} req/s`;
$("rate-total").textContent = `${rates.total.toFixed(1)}/s`; $("rate-h1").textContent = `${rates.h1.toFixed(1)}/s`; $("rate-h2").textContent = `${rates.h2.toFixed(1)}/s`; $("rate-h3").textContent = `${rates.h3.toFixed(1)}/s`; $("rate-transform").textContent = `${rates.transform.toFixed(1)}/s`;
$("metric-http-proto-total").textContent = `${rates.total.toFixed(1)}/s`;
$("metric-http-proto-h1").textContent = `${rates.h1.toFixed(1)}/s`;
$("metric-http-proto-h2").textContent = `${rates.h2.toFixed(1)}/s`;
$("metric-http-proto-h3").textContent = `${rates.h3.toFixed(1)}/s`;
const rateText = key => pausedRate ? "-" : `${rates[key].toFixed(1)}/s`;
$("request-rate").textContent = pausedRate ? "- req/s" : `${rates.total.toFixed(1)} req/s`;
$("rate-total").textContent = rateText("total"); $("rate-h1").textContent = rateText("h1"); $("rate-h2").textContent = rateText("h2"); $("rate-h3").textContent = rateText("h3"); $("rate-transform").textContent = rateText("transform");
$("metric-http-proto-total").textContent = rateText("total"); $("metric-http-proto-h1").textContent = rateText("h1");
$("metric-http-proto-h2").textContent = rateText("h2"); $("metric-http-proto-h3").textContent = rateText("h3");
$("http-protocol-h1").setAttribute("points", sparklinePoints(protocolSamples.h1, 300, 60, 5));
$("http-protocol-h2").setAttribute("points", sparklinePoints(protocolSamples.h2, 300, 60, 5));
$("http-protocol-h3").setAttribute("points", sparklinePoints(protocolSamples.h3, 300, 60, 5));
@@ -302,7 +300,7 @@ function stopLive() {
}
function startLive() {
if (!authenticated || document.hidden || typeof EventSource === "undefined") return;
const seconds = window.rustProxyRefresh?.current().overview_seconds || 2;
const seconds = window.rustProxyRefresh?.current().overview_seconds || 3;
if (liveEvents && liveInterval === seconds) return;
stopLive();
liveInterval = seconds;
+6 -7
View File
@@ -16,16 +16,15 @@ window.rustProxyExtraOverview = (() => {
const node = id => document.querySelector(`[data-dashboard-widget="${id}"]`);
function elapsedDelta(id, counters, now) {
const old = lastCounter.get(id);
const next = { values: counters, at: now };
lastCounter.set(id, next);
if (!old || now <= old.at || counters.some((v, i) => v < old.values[i])) return null;
return { deltas: counters.map((v, i) => v - old.values[i]), seconds: (now - old.at) / 1000 };
const result = rustProxyRateSampling.difference(old, counters, now, rustProxyRateSampling.cadence("overview_seconds"));
lastCounter.set(id, result.next);
return result.deltas ? result : null;
}
function read(id, status, now, record) {
const m = status.metrics || {}, d = status.system || {}, c = status.cache || {};
const m = status.metrics || {}, d = status.system || {}, live = status.live_system || d, c = status.cache || {};
const direct = {
extra_process_cpu: d.process_cpu_percent,
extra_process_rss: d.process_rss_bytes,
extra_process_cpu: live.process_cpu_percent,
extra_process_rss: live.process_rss_bytes,
extra_process_fds: d.open_fds,
extra_open_connections: m.active_connections,
extra_websocket_active: m.websocket_active,
+5 -8
View File
@@ -7,11 +7,11 @@ window.rustProxyWideOverview = (() => {
},
wide_cpu: {
unit: "percent", names: ["system", "process"], values: s =>
[s.system?.cpu_percent, s.system?.process_cpu_percent]
[(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.system?.process_rss_bytes, s.cache?.resident_bytes]
[(s.live_system || s.system)?.process_rss_bytes, s.cache?.resident_bytes]
},
wide_cache_activity: {
unit: "rate", names: ["hits", "misses"], counters: s =>
@@ -51,12 +51,9 @@ window.rustProxyWideOverview = (() => {
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 previous = baselines.get(id);
baselines.set(id, { counters, now });
if (!previous || now <= previous.now || counters.some((n, i) => n < previous.counters[i]))
return counters.map(() => null);
const elapsed = (now - previous.now) / 1000;
return counters.map((n, i) => (n - previous.counters[i]) / elapsed);
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;
+32
View File
@@ -0,0 +1,32 @@
// One continuity policy for all browser-side counter-derived rates.
// Background tabs cannot supply a valid instantaneous rate for an interval
// spanning the pause. Keep the plotted history and rebase on the next sample.
const rustProxyRateSampling = (() => {
let generation = 0;
const pause = () => { generation += 1; };
document.addEventListener?.("visibilitychange", () => { if (document.hidden) pause(); });
window.addEventListener?.("pagehide", pause);
function cadence(key) {
const value = window.rustProxyRefresh?.current?.()?.[key];
const fallback = key === "cache_seconds" ? 5 : 3;
return Number.isFinite(value) && value > 0 ? value : fallback;
}
function stamp(at) { return { at, generation }; }
function continuous(previous, at, intervalSeconds = 3) {
const seconds = Number.isFinite(intervalSeconds) && intervalSeconds > 0 ? intervalSeconds : 3;
return Boolean(previous && !document.hidden && previous.generation === generation &&
Number.isFinite(at) && Number.isFinite(previous.at) && at > previous.at &&
at - previous.at <= Math.max(10000, seconds * 3000));
}
function difference(previous, values, at, intervalSeconds = 3) {
const next = { ...stamp(at), values: [...values] };
if (!continuous(previous, at, intervalSeconds) ||
values.some((value, i) => !Number.isFinite(value) || value < 0 ||
!Number.isFinite(previous.values[i]) || value < previous.values[i])) {
return { next, deltas: null, seconds: null };
}
return { next, deltas: values.map((value, i) => value - previous.values[i]),
seconds: (at - previous.at) / 1000 };
}
return { cadence, stamp, continuous, difference };
})();
+2 -2
View File
@@ -1,9 +1,9 @@
const REFRESH_DEFAULTS = Object.freeze({
live_enabled: true, overview_seconds: 2, traffic_seconds: 5,
live_enabled: true, overview_seconds: 3, traffic_seconds: 5,
cache_seconds: 5, system_info_seconds: 60
});
const REFRESH_CHOICES = Object.freeze({
overview_seconds: [1, 2, 5, 10], traffic_seconds: [0, 3, 5, 10, 30],
overview_seconds: [1, 2, 3, 5, 10], traffic_seconds: [0, 3, 5, 10, 30],
cache_seconds: [0, 3, 5, 10, 30], system_info_seconds: [0, 15, 30, 60, 300]
});
function normalizeRefreshPreferences(input = {}) {
+68 -68
View File
@@ -312,30 +312,30 @@ function renderNetworkInterfaceCharts(interfaces, appendSample) {
}
}
function renderDiagnostics(status, appendSample = true, systemSample = appendSample) {
const d = status.system || {}, c = status.cache || {}, m = status.metrics || {};
const d = status.system || {}, live = status.live_system || d, c = status.cache || {}, m = status.metrics || {};
$("metric-h3").textContent = fmt(m.http3_requests);
$("metric-cache-ratio").textContent = c.hit_ratio == null ? "-" : `${fmt(c.hit_ratio * 100, 1)}%`;
$("cache-summary").textContent = `${fmt(c.hits)} HIT / ${fmt(c.misses)} MISS`;
if (d.process_rss_bytes != null) $("metric-rss").textContent = fmtBytes(d.process_rss_bytes);
$("process-summary").hidden = d.process_threads == null;
if (d.process_threads != null) $("process-summary").textContent = `${fmt(d.process_threads)} ${t("threads")}`;
const systemCpuSupported = d.cpu_percent != null;
setOptionalMetric("system-cpu", systemCpuSupported, systemCpuSupported ? `${fmt(d.cpu_percent, 1)}%` : "");
$("system-chart").hidden = !systemCpuSupported;
const sourceSupported = !!d.source && d.source !== "unsupported";
$("system-source").hidden = !sourceSupported;
if (sourceSupported) $("system-source").textContent = t("realSample");
if (systemSample && d.cpu_percent != null) { systemSamples.push(d.cpu_percent); systemSamples = systemSamples.slice(-30); }
// Slow details vs cached live chart telemetry.
if (systemSample) {
if (d.process_rss_bytes != null) $("metric-rss").textContent = fmtBytes(d.process_rss_bytes);
$("process-summary").hidden = d.process_threads == null;
if (d.process_threads != null) $("process-summary").textContent = `${fmt(d.process_threads)} ${t("threads")}`;
setOptionalMetric("system-cpu", d.cpu_percent != null, `${fmt(d.cpu_percent, 1)}%`);
const sourceSupported = !!d.source && d.source !== "unsupported";
$("system-source").hidden = !sourceSupported; if (sourceSupported) $("system-source").textContent = t("realSample");
const systemMemory = knownPair(d.memory_used_bytes, d.memory_total_bytes, value => fmtBytes(value));
setOptionalMetric("system-memory", systemMemory != null, systemMemory);
$("memory-meter").hidden = systemMemory == null;
if (d.memory_used_bytes != null && d.memory_total_bytes > 0) $("memory-meter").value = d.memory_used_bytes / d.memory_total_bytes * 100; else $("memory-meter").removeAttribute("value");
const systemSwap = knownPair(d.system_swap_used_bytes, d.system_swap_total_bytes, value => fmtBytes(value));
setOptionalMetric("system-swap", systemSwap != null, systemSwap);
const loadSupported = Array.isArray(d.load_average) && d.load_average.length > 0;
setOptionalMetric("system-load", loadSupported, loadSupported ? d.load_average.map(n => fmt(n, 2)).join(" / ") : "");
setOptionalMetric("system-uptime", d.system_uptime_seconds != null, d.system_uptime_seconds == null ? "" : uptime(d.system_uptime_seconds));
}
$("system-chart").hidden = live.cpu_percent == null; if (appendSample && live.cpu_percent != null) { systemSamples.push(Number(live.cpu_percent)); systemSamples = systemSamples.slice(-30); }
$("system-sparkline").setAttribute("points", systemSamples.map((n, i) => `${i * 300 / Math.max(1, systemSamples.length - 1)},${95 - Math.max(0, Math.min(100, n)) * .9}`).join(" "));
const systemMemory = knownPair(d.memory_used_bytes, d.memory_total_bytes, value => fmtBytes(value));
setOptionalMetric("system-memory", systemMemory != null, systemMemory);
$("memory-meter").hidden = systemMemory == null;
if (d.memory_used_bytes != null && d.memory_total_bytes > 0) $("memory-meter").value = d.memory_used_bytes / d.memory_total_bytes * 100; else $("memory-meter").removeAttribute("value");
const systemSwap = knownPair(d.system_swap_used_bytes, d.system_swap_total_bytes, value => fmtBytes(value));
setOptionalMetric("system-swap", systemSwap != null, systemSwap);
const loadSupported = Array.isArray(d.load_average) && d.load_average.length > 0;
setOptionalMetric("system-load", loadSupported, loadSupported ? d.load_average.map(n => fmt(n, 2)).join(" / ") : "");
setOptionalMetric("system-uptime", d.system_uptime_seconds != null, d.system_uptime_seconds == null ? "" : uptime(d.system_uptime_seconds));
$("cache-state").textContent = t(envelope?.config.cache?.enabled ? "enabled" : "notEnabled");
$("cache-counters").textContent = [c.hits, c.misses, c.bypasses].map(n => fmt(n)).join(" / ");
$("cache-objects").textContent = `${fmt(c.entries)} / ${fmtBytes(c.resident_bytes)}`;
@@ -357,15 +357,15 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
if (c.cold_bytes != null) { cacheColdStorageSamples.push(Number(c.cold_bytes)); cacheColdStorageSamples = cacheColdStorageSamples.slice(-60); }
const currentCounters = [c.hits, c.misses, c.bypasses].map(Number);
if (currentCounters.every(Number.isFinite)) {
const delta = lastCacheCounters ? currentCounters.map((value, index) => value >= lastCacheCounters[index] ? value - lastCacheCounters[index] : 0) : [0, 0, 0];
cacheHitSamples.push(delta[0]); cacheMissSamples.push(delta[1]); cacheBypassSamples.push(delta[2]);
cacheHitSamples = cacheHitSamples.slice(-60); cacheMissSamples = cacheMissSamples.slice(-60); cacheBypassSamples = cacheBypassSamples.slice(-60);
lastCacheCounters = currentCounters;
const delta = rustProxyRateSampling.difference(lastCacheCounters, currentCounters, performance.now(), rustProxyRateSampling.cadence("overview_seconds"));
if (delta.deltas || !lastCacheCounters) { const values = delta.deltas || [0, 0, 0];
cacheHitSamples.push(values[0]); cacheMissSamples.push(values[1]); cacheBypassSamples.push(values[2]); cacheHitSamples = cacheHitSamples.slice(-60); cacheMissSamples = cacheMissSamples.slice(-60); cacheBypassSamples = cacheBypassSamples.slice(-60);
} lastCacheCounters = delta.next;
}
}
if (systemSample) {
if (d.process_cpu_percent != null) { processCpuSamples.push(Number(d.process_cpu_percent)); processCpuSamples = processCpuSamples.slice(-60); }
if (d.process_rss_bytes != null) { processRssSamples.push(Number(d.process_rss_bytes)); processRssSamples = processRssSamples.slice(-60); }
if (appendSample) {
if (live.process_cpu_percent != null) { processCpuSamples.push(Number(live.process_cpu_percent)); processCpuSamples = processCpuSamples.slice(-60); }
if (live.process_rss_bytes != null) { processRssSamples.push(Number(live.process_rss_bytes)); processRssSamples = processRssSamples.slice(-60); }
}
const cacheOutcomePeak = Math.max(1, ...cacheHitSamples, ...cacheMissSamples, ...cacheBypassSamples);
$("cache-hit-sparkline").setAttribute("points", sparklinePoints(cacheHitSamples, 300, 60, 5, cacheOutcomePeak));
@@ -389,47 +389,49 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
const coldLimit = Number(c.cold_limit_bytes), coldPeak = Number.isFinite(coldLimit) && coldLimit > 0 ? Math.max(coldLimit, ...cacheColdStorageSamples) : null;
$("cache-cold-sparkline").setAttribute("points", sparklinePoints(cacheColdStorageSamples, 300, 60, 5, coldPeak));
setChartText($("cache-cold-chart-value"), coldEnabled ? occupancyText(c.cold_bytes, c.cold_limit_bytes) : t("notEnabled"));
const processPlatform = [d.platform, d.environment].filter(Boolean).join(" / ");
setOptionalMetric("process-platform", !!d.platform && d.platform !== "Unsupported", processPlatform);
setOptionalMetric("process-cpu", d.process_cpu_percent != null, `${fmt(d.process_cpu_percent, 1)}%`);
const fds = knownPair(d.open_fds, d.max_fds, value => fmt(value), t("unlimited"));
setOptionalMetric("process-fds", fds != null, fds);
setOptionalMetric("process-threads", d.process_threads != null, fmt(d.process_threads));
const virtualMemory = knownPair(d.process_virtual_memory_bytes, d.process_virtual_memory_max_bytes, value => fmtBytes(value), t("unlimited"));
setOptionalMetric("process-virtual-memory", virtualMemory != null, virtualMemory);
setOptionalMetric("process-start-time", d.process_start_time_seconds != null, d.process_start_time_seconds == null ? "" : formatDateTime(Number(d.process_start_time_seconds) * 1000));
setOptionalMetric("process-uptime", status.uptime_seconds != null, status.uptime_seconds == null ? "" : uptime(status.uptime_seconds));
setOptionalMetric("process-rss-total", d.process_rss_bytes != null, fmtBytes(d.process_rss_bytes));
const memory = status.memory_accounting || {};
const hotMemory = knownPair(memory.cache_hot_bytes, memory.cache_hot_limit_bytes, value => fmtBytes(value));
const transientMemory = knownPair(memory.cache_transient_bytes, memory.cache_transient_limit_bytes, value => fmtBytes(value));
const transformMemory = knownPair(memory.transform_reserved_bytes, memory.transform_budget_bytes, value => fmtBytes(value));
const transformInflight = knownPair(memory.transform_inflight, memory.transform_max_inflight, value => fmt(value));
setOptionalMetric("memory-cache-hot", hotMemory != null, hotMemory);
setOptionalMetric("memory-cache-transient", transientMemory != null, transientMemory);
setOptionalMetric("memory-transform", transformMemory != null, transformMemory);
setOptionalMetric("memory-transform-inflight", transformInflight != null, transformInflight);
setOptionalMetric("memory-non-cache-rss", memory.non_cache_rss_bytes != null, fmtBytes(memory.non_cache_rss_bytes));
setOptionalMetric("process-rss-anon", d.process_rss_anon_bytes != null, fmtBytes(d.process_rss_anon_bytes));
const rssFile = knownPair(d.process_rss_file_bytes, d.process_rss_shmem_bytes, value => fmtBytes(value));
setOptionalMetric("process-rss-file", rssFile != null, rssFile);
const dataSwap = knownPair(d.process_data_bytes, d.process_swap_bytes, value => fmtBytes(value));
setOptionalMetric("process-data-swap", dataSwap != null, dataSwap);
const cgroupMemory = knownPair(d.cgroup_memory_current_bytes, d.cgroup_memory_max_bytes, value => fmtBytes(value), t("unlimited"));
setOptionalMetric("cgroup-memory", cgroupMemory != null, cgroupMemory);
if (systemSample) {
const processPlatform = [d.platform, d.environment].filter(Boolean).join(" / ");
setOptionalMetric("process-platform", !!d.platform && d.platform !== "Unsupported", processPlatform);
setOptionalMetric("process-cpu", d.process_cpu_percent != null, `${fmt(d.process_cpu_percent, 1)}%`);
const fds = knownPair(d.open_fds, d.max_fds, value => fmt(value), t("unlimited"));
setOptionalMetric("process-fds", fds != null, fds);
setOptionalMetric("process-threads", d.process_threads != null, fmt(d.process_threads));
const virtualMemory = knownPair(d.process_virtual_memory_bytes, d.process_virtual_memory_max_bytes, value => fmtBytes(value), t("unlimited"));
setOptionalMetric("process-virtual-memory", virtualMemory != null, virtualMemory);
setOptionalMetric("process-start-time", d.process_start_time_seconds != null, d.process_start_time_seconds == null ? "" : formatDateTime(Number(d.process_start_time_seconds) * 1000));
setOptionalMetric("process-uptime", status.uptime_seconds != null, status.uptime_seconds == null ? "" : uptime(status.uptime_seconds));
setOptionalMetric("process-rss-total", d.process_rss_bytes != null, fmtBytes(d.process_rss_bytes));
const memory = status.memory_accounting || {};
const hotMemory = knownPair(memory.cache_hot_bytes, memory.cache_hot_limit_bytes, value => fmtBytes(value));
const transientMemory = knownPair(memory.cache_transient_bytes, memory.cache_transient_limit_bytes, value => fmtBytes(value));
const transformMemory = knownPair(memory.transform_reserved_bytes, memory.transform_budget_bytes, value => fmtBytes(value));
const transformInflight = knownPair(memory.transform_inflight, memory.transform_max_inflight, value => fmt(value));
setOptionalMetric("memory-cache-hot", hotMemory != null, hotMemory);
setOptionalMetric("memory-cache-transient", transientMemory != null, transientMemory);
setOptionalMetric("memory-transform", transformMemory != null, transformMemory);
setOptionalMetric("memory-transform-inflight", transformInflight != null, transformInflight);
setOptionalMetric("memory-non-cache-rss", memory.non_cache_rss_bytes != null, fmtBytes(memory.non_cache_rss_bytes));
setOptionalMetric("process-rss-anon", d.process_rss_anon_bytes != null, fmtBytes(d.process_rss_anon_bytes));
const rssFile = knownPair(d.process_rss_file_bytes, d.process_rss_shmem_bytes, value => fmtBytes(value));
setOptionalMetric("process-rss-file", rssFile != null, rssFile);
const dataSwap = knownPair(d.process_data_bytes, d.process_swap_bytes, value => fmtBytes(value));
setOptionalMetric("process-data-swap", dataSwap != null, dataSwap);
const cgroupMemory = knownPair(d.cgroup_memory_current_bytes, d.cgroup_memory_max_bytes, value => fmtBytes(value), t("unlimited"));
setOptionalMetric("cgroup-memory", cgroupMemory != null, cgroupMemory);
}
$("process-cpu-sparkline").setAttribute("points", sparklinePoints(processCpuSamples));
setChartText($("process-cpu-chart-value"), d.process_cpu_percent == null ? "" : `${fmt(d.process_cpu_percent, 1)}%`);
setChartText($("process-cpu-chart-value"), live.process_cpu_percent == null ? "" : `${fmt(live.process_cpu_percent, 1)}%`);
$("process-rss-sparkline").setAttribute("points", sparklinePoints(processRssSamples));
setChartText($("process-rss-chart-value"), d.process_rss_bytes == null ? "" : fmtBytes(d.process_rss_bytes));
$("process-cpu-sparkline").closest(".mini-live-chart").hidden = d.process_cpu_percent == null;
$("process-rss-sparkline").closest(".mini-live-chart").hidden = d.process_rss_bytes == null;
setChartText($("process-rss-chart-value"), live.process_rss_bytes == null ? "" : fmtBytes(live.process_rss_bytes));
$("process-cpu-sparkline").closest(".mini-live-chart").hidden = live.process_cpu_percent == null;
$("process-rss-sparkline").closest(".mini-live-chart").hidden = live.process_rss_bytes == null;
updateCacheMemorySizing();
const networkRateSupported = d.network_rx_bytes_per_second != null && d.network_tx_bytes_per_second != null;
setOptionalMetric("system-network", networkRateSupported, networkRateSupported ? `${fmtBytes(d.network_rx_bytes_per_second)} / ${fmtBytes(d.network_tx_bytes_per_second)}` : "");
const networkRateSupported = live.network_rx_bytes_per_second != null && live.network_tx_bytes_per_second != null;
if (systemSample) setOptionalMetric("system-network", d.network_rx_bytes_per_second != null && d.network_tx_bytes_per_second != null, `${fmtBytes(d.network_rx_bytes_per_second)} / ${fmtBytes(d.network_tx_bytes_per_second)}`);
setOptionalMetric("mean-latency", m.mean_duration_ms != null, m.mean_duration_ms == null ? "" : `${fmt(m.mean_duration_ms, 2)} ms`);
if (appendSample) {
if (d.network_rx_bytes_per_second != null && d.network_tx_bytes_per_second != null) {
networkRxSamples.push(Number(d.network_rx_bytes_per_second)); networkTxSamples.push(Number(d.network_tx_bytes_per_second));
if (networkRateSupported) {
networkRxSamples.push(Number(live.network_rx_bytes_per_second)); networkTxSamples.push(Number(live.network_tx_bytes_per_second));
networkRxSamples = networkRxSamples.slice(-60); networkTxSamples = networkTxSamples.slice(-60);
}
if (m.mean_duration_ms != null) { responseLatencySamples.push(Number(m.mean_duration_ms)); responseLatencySamples = responseLatencySamples.slice(-60); }
@@ -437,12 +439,10 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
const networkPeak = Math.max(1, ...networkRxSamples, ...networkTxSamples);
$("network-rx-sparkline").setAttribute("points", sparklinePoints(networkRxSamples, 300, 60, 5, networkPeak));
$("network-tx-sparkline").setAttribute("points", sparklinePoints(networkTxSamples, 300, 60, 5, networkPeak));
setChartText($("network-chart-value"), networkRateSupported ? `${fmtBytes(d.network_rx_bytes_per_second)} / ${fmtBytes(d.network_tx_bytes_per_second)}` : "");
setChartText($("network-chart-value"), networkRateSupported ? `${fmtBytes(live.network_rx_bytes_per_second)} / ${fmtBytes(live.network_tx_bytes_per_second)}` : "");
$("network-rx-sparkline").closest(".mini-live-chart").hidden = !networkRateSupported;
// Advance interface charts at the live Overview cadence, just like the
// aggregate network chart. Between slower system-info reads, repeat the
// latest observed rates without initiating another system data fetch.
renderNetworkInterfaceCharts(d.network_interfaces || [], appendSample);
// Cache-sampled OS chart rates are independent from slow System details.
renderNetworkInterfaceCharts(live.network_interfaces || [], appendSample);
$("latency-sparkline").setAttribute("points", sparklinePoints(responseLatencySamples));
setChartText($("latency-chart-value"), m.mean_duration_ms == null ? "" : `${fmt(m.mean_duration_ms, 2)} ms`);
$("latency-sparkline").closest(".mini-live-chart").hidden = m.mean_duration_ms == null;
+10 -10
View File
@@ -88,17 +88,17 @@ window.rustProxyStreamDashboard = (() => {
const rxTotal = views.reduce((sum, view) => sum + (Number(view.bytes_in) || 0), 0);
const txTotal = views.reduce((sum, view) => sum + (Number(view.bytes_out) || 0), 0);
const now = performance.now();
let rx = 0, tx = 0;
if (history.last) {
const seconds = Math.max(.001, (now - history.last.when) / 1000);
if (rxTotal >= history.last.rx) rx = (rxTotal - history.last.rx) / seconds;
if (txTotal >= history.last.tx) tx = (txTotal - history.last.tx) / seconds;
const sample = rustProxyRateSampling.difference(history.last, [rxTotal, txTotal], now, rustProxyRateSampling.cadence("overview_seconds"));
const pausedRate = history.last !== null && sample.deltas === null;
history.last = sample.next;
const rx = sample.deltas ? sample.deltas[0] / sample.seconds : 0;
const tx = sample.deltas ? sample.deltas[1] / sample.seconds : 0;
if (!pausedRate) {
history.rx.push(rx); history.tx.push(tx);
if (history.rx.length > 60) { history.rx.shift(); history.tx.shift(); }
}
history.last = { when: now, rx: rxTotal, tx: txTotal };
history.rx.push(rx); history.tx.push(tx);
if (history.rx.length > 60) { history.rx.shift(); history.tx.shift(); }
set("stream-dashboard-traffic", `${bytes(Math.round(rx))}/s`);
trafficRates(`${bytes(Math.round(rx))}/s`, `${bytes(Math.round(tx))}/s`);
set("stream-dashboard-traffic", pausedRate ? "-" : `${bytes(Math.round(rx))}/s`);
trafficRates(pausedRate ? "-" : `${bytes(Math.round(rx))}/s`, pausedRate ? "-" : `${bytes(Math.round(tx))}/s`);
$("stream-dashboard-rx")?.setAttribute("points", line(history.rx));
$("stream-dashboard-tx")?.setAttribute("points", line(history.tx));
}