This commit is contained in:
Mateusz Gruszczyński committed 2026-10-13 21:29:57 +02:00
1 parent bb025dcf44
commit b0f211555a
74 files changed
+6089 -556

No files matched your search

+128
View File
@@ -0,0 +1,128 @@
window.rustProxyExtraOverview = (() => {
const limit = 30;
const names = [
"extra_process_cpu", "extra_process_rss", "extra_process_fds", "extra_open_connections",
"extra_websocket_active", "extra_tls_error_rate", "extra_request_error_rate",
"extra_cache_hit_rate", "extra_cache_memory", "extra_h2_share",
"extra_response_latency", "extra_backend_unhealthy",
"extra_total_rps", "extra_active_exchanges", "extra_h1_share", "extra_h3_share",
"extra_cache_miss_rate", "extra_backend_active",
];
const histories = new Map();
const lastCounter = new Map();
const lastValue = new Map();
const lastUnits = new Map();
const positive = value => value != null && Number.isFinite(Number(value)) && Number(value) >= 0 ? Number(value) : null;
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 };
}
function read(id, status, now, record) {
const m = status.metrics || {}, d = status.system || {}, c = status.cache || {};
const direct = {
extra_process_cpu: d.process_cpu_percent,
extra_process_rss: d.process_rss_bytes,
extra_process_fds: d.open_fds,
extra_open_connections: m.active_connections,
extra_websocket_active: m.websocket_active,
extra_response_latency: m.mean_duration_ms,
extra_active_exchanges: m.active,
};
if (Object.hasOwn(direct, id)) {
const numeric = positive(direct[id]);
return {
value: numeric, unit: id === "extra_process_cpu" ? "percent" : id === "extra_process_rss" ? "bytes" : id === "extra_response_latency" ? "ms" : "count",
detail: id === "extra_process_fds" && positive(d.max_fds) ? `${t("overviewExtraFdLimit")}: ${fmt(d.max_fds)}` : ""
};
}
if (id === "extra_backend_active") {
const members = Array.isArray(status.backend_pools) ? status.backend_pools.flatMap(pool => pool.members || []) : null;
return {
value: members ? members.reduce((sum, member) => sum + (positive(member.active) ?? 0), 0) : null,
unit: "count", detail: ""
};
}
if (id === "extra_backend_unhealthy") {
return {
value: Array.isArray(status.backend_pools) ? status.backend_pools.reduce((sum, pool) =>
sum + (pool.members || []).filter(member => member.status === "down").length, 0) : null, unit: "count", detail: ""
};
}
if (id === "extra_cache_memory") {
const bytes = positive(c.resident_bytes), budget = positive(c.hot_limit_bytes);
const percent = budget && bytes !== null ? bytes / budget * 100 : null;
return {
value: percent === null ? bytes : percent, unit: percent === null ? "bytes" : "percent",
detail: bytes === null ? "" : `${fmtBytes(bytes)}${budget ? ` / ${fmtBytes(budget)}` : ""}`
};
}
if (!["extra_tls_error_rate", "extra_request_error_rate", "extra_cache_hit_rate", "extra_h2_share",
"extra_total_rps", "extra_h1_share", "extra_h3_share", "extra_cache_miss_rate"].includes(id))
return { value: null, unit: "count", detail: "" };
const fields = ({
extra_tls_error_rate: [m.tls_handshake_errors],
extra_request_error_rate: [m.errors],
extra_cache_hit_rate: [c.hits, c.misses],
extra_h2_share: [m.http2_requests, m.requests],
extra_h1_share: [m.http1_requests, m.requests],
extra_h3_share: [m.http3_requests, m.requests],
extra_total_rps: [m.requests],
extra_cache_miss_rate: [c.misses],
})[id].map(positive);
const unit = ["extra_cache_hit_rate", "extra_h2_share", "extra_h1_share", "extra_h3_share"].includes(id)
? "percent" : "rate";
if (fields.some(v => v === null)) { lastCounter.delete(id); return { value: null, unit, detail: "" }; }
if (!record) return { value: lastValue.get(id) ?? null, unit, detail: "" };
const result = elapsedDelta(id, fields, now);
if (!result) return { value: null, unit, detail: "" };
const { deltas, seconds } = result;
if (unit === "rate") return { value: deltas[0] / seconds, unit, detail: "" };
const sum = id === "extra_cache_hit_rate" ? deltas[0] + deltas[1] : deltas[1];
return { value: sum ? Math.min(100, deltas[0] / sum * 100) : null, unit, detail: "" };
}
function format(value, unit) {
if (value == null) return "-";
if (unit === "percent") return `${fmt(value, 1)}%`;
if (unit === "bytes") return fmtBytes(value);
if (unit === "ms") return `${fmt(value, 1)} ms`;
if (unit === "rate") return `${fmt(value, 1)}/s`;
return fmt(value);
}
function points(history, unit) {
if (!history.length) return "0,55 300,55";
const peak = unit === "percent" ? Math.max(100, ...history) : Math.max(1, ...history);
return history.map((v, i) => `${i * 300 / Math.max(1, history.length - 1)},${55 - v / peak * 50}`).join(" ");
}
function render(status, record = true) {
if (!status || currentPanel !== "overview") return;
const now = performance.now();
for (const id of names) {
const tile = node(id);
if (!tile || tile.hidden) { lastCounter.delete(id); continue; }
const metric = read(id, status, now, record);
const value = metric.value;
if (lastUnits.get(id) !== metric.unit) {
histories.delete(id);
lastUnits.set(id, metric.unit);
}
const history = histories.get(id) || [];
if (record && value !== null) {
history.push(value);
if (history.length > limit) history.shift();
histories.set(id, history);
}
if (record) lastValue.set(id, value);
const field = $(`overview-extra-${id}`);
if (field) field.textContent = format(value, metric.unit);
const detail = $(`overview-extra-${id}-detail`);
if (detail) detail.textContent = metric.detail || t("overviewExtraRecentSamples");
$(`overview-extra-${id}-line`)?.setAttribute("points", points(history, metric.unit));
}
}
function reset() { histories.clear(); lastCounter.clear(); lastValue.clear(); lastUnits.clear(); }
return { render, reset };
})();