window.rustProxyExtraOverview = (() => { const limit = () => window.rustProxyOverviewSamples?.getLimit?.() ?? 60; 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_cold_queue_memory", "extra_cold_storage", "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", "extra_backends_healthy", "extra_error_share", "extra_rejected_rate", "extra_cert_expiry", ]; const bounded = new Set([ "extra_process_fds", "extra_cache_memory", "extra_cold_queue_memory", "extra_cold_storage", "extra_h1_share", "extra_h2_share", "extra_h3_share", "extra_backends_healthy", "extra_cert_expiry", ]); const histories = new Map(), lastCounter = new Map(), lastValue = new Map(); const lastUnits = new Map(), lastSegments = 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 meter(id, percent) { const track = $(`${id}-meter`), fill = $(`${id}-meter-fill`); if (!track || !fill) return; const valid = positive(percent), boundedPercent = valid === null ? 0 : Math.min(100, valid); fill.style.width = `${boundedPercent}%`; if (valid === null) { track.removeAttribute("aria-valuenow"); track.setAttribute("aria-valuetext", "-"); } else { track.setAttribute("aria-valuenow", String(boundedPercent)); track.setAttribute("aria-valuetext", `${fmt(valid, 1)}%`); } } function donut(prefix, hits, misses) { const hit = positive(hits), miss = positive(misses); const total = hit !== null && miss !== null ? hit + miss : 0; const share = total > 0 ? hit / total * 100 : 0; const hitArc = $(`${prefix}-hit-arc`), missArc = $(`${prefix}-miss-arc`); if (hitArc) hitArc.setAttribute("stroke-dasharray", `${share} ${100 - share}`); if (missArc) { missArc.setAttribute("stroke-dasharray", `${100 - share} ${share}`); missArc.setAttribute("stroke-dashoffset", String(-share)); } const hitCount = $(`${prefix}-hit-count`), missCount = $(`${prefix}-miss-count`); if (hitCount) hitCount.textContent = hit === null ? "-" : fmt(hit); if (missCount) missCount.textContent = miss === null ? "-" : fmt(miss); const graphic = $(`${prefix}-chart`); if (graphic) graphic.setAttribute("aria-label", t("overviewCacheHitsMisses") .replace("{hits}", hit === null ? "-" : fmt(hit)) .replace("{misses}", miss === null ? "-" : fmt(miss))); } function elapsedDelta(id, counters, now) { const result = rustProxyRateSampling.difference(lastCounter.get(id), counters, now, rustProxyRateSampling.cadence("overview_seconds")); lastCounter.set(id, result.next); return result.deltas ? result : null; } function ramBudget(bytes, budget) { const used = positive(bytes), maximum = positive(budget); const percent = maximum > 0 && used !== null ? used / maximum * 100 : null; return { value: percent === null ? used : percent, unit: percent === null ? "bytes" : "percent", meterPercent: percent, detail: used === null ? "" : `${fmtBytes(used)} / ${maximum > 0 ? fmtBytes(maximum) : t("overviewMeterNoLimit")}`, }; } function read(id, status, now, record) { const m = status.metrics || {}, d = status.system || {}, live = status.live_system || d, c = status.cache || {}; const direct = { 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, extra_response_latency: m.mean_duration_ms, extra_active_exchanges: m.active, }; if (Object.hasOwn(direct, id)) { const numeric = positive(direct[id]), fdLimit = positive(d.max_fds); return { value: numeric, unit: id === "extra_process_cpu" ? "percent" : id === "extra_process_rss" ? "bytes" : id === "extra_response_latency" ? "ms" : "count", meterPercent: id === "extra_process_fds" && fdLimit > 0 && numeric !== null ? numeric / fdLimit * 100 : null, detail: id === "extra_process_fds" ? (fdLimit > 0 ? `${t("overviewExtraFdLimit")}: ${fmt(fdLimit)}` : t("overviewMeterNoLimit")) : "", }; } 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_backends_healthy") { const members = Array.isArray(status.backend_pools) ? status.backend_pools.flatMap(pool => (pool.members || []).map(member => ({ pool: pool.id, ...member }))) : null; if (!members) return { value: null, unit: "count", detail: "" }; const monitored = members.filter(member => member.health_check_enabled !== false && member.health_mode !== "off"); const down = monitored.filter(member => member.status === "down" || member.healthy === false); const up = monitored.length - down.length; return { value: monitored.length ? `${fmt(up)} / ${fmt(monitored.length)}` : null, unit: "text", meterPercent: monitored.length ? up / monitored.length * 100 : null, detail: !monitored.length ? t("overviewExtraBackendsNone") : down.length ? `${t("overviewExtraBackendsDown")}: ${down.slice(0, 3).map(member => `${member.pool}/${member.id}`).join(", ")}${down.length > 3 ? " …" : ""}` : t("overviewExtraBackendsAllUp"), }; } if (id === "extra_cert_expiry") { const inventory = typeof certificateInventory !== "undefined" && certificateInventory instanceof Map ? [...certificateInventory.values()] : []; const dated = inventory.filter(cert => Number.isFinite(Number(cert?.days_remaining))) .sort((a, b) => Number(a.days_remaining) - Number(b.days_remaining)); if (!dated.length) return { value: null, unit: "days", detail: t("overviewExtraCertNone") }; const nearest = dated[0], days = Number(nearest.days_remaining); const name = (Array.isArray(nearest.covered_hosts) && nearest.covered_hosts[0]) || (Array.isArray(nearest.hosts) && nearest.hosts[0]) || nearest.id; return { value: days, unit: "days", meterPercent: Math.max(0, Math.min(100, days / 90 * 100)), detail: `${name} · ${fmt(dated.length)} ${t("overviewExtraCertCount")}` }; } if (id === "extra_cache_memory") return ramBudget(c.resident_bytes, c.hot_limit_bytes); if (id === "extra_cold_queue_memory") return ramBudget(c.cold_queue_bytes, c.cold_queue_limit_bytes); if (id === "extra_cold_storage") return ramBudget(c.cold_bytes, c.cold_limit_bytes); 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", "extra_error_share", "extra_rejected_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], extra_error_share: [m.errors, m.requests], extra_rejected_rate: [m.rejected_requests, m.rejected_connections], })[id].map(positive); const unit = ["extra_cache_hit_rate", "extra_h2_share", "extra_h1_share", "extra_h3_share", "extra_error_share"].includes(id) ? "percent" : "rate"; if (fields.some(v => v === null)) { lastCounter.delete(id); return { value: null, unit, detail: "", slices: null }; } if (!record) return { value: lastValue.get(id) ?? null, unit, detail: "", slices: lastSegments.get(id) || null }; const result = elapsedDelta(id, fields, now); if (!result) return { value: null, unit, detail: "", slices: null }; const { deltas, seconds } = result; if (id === "extra_rejected_rate") return { value: (deltas[0] + deltas[1]) / seconds, unit, detail: `${t("overviewExtraRejectedDetail")}: ${fmt(fields[0])} / ${fmt(fields[1])}` }; if (id === "extra_error_share") return { value: deltas[1] > 0 ? Math.min(100, deltas[0] / deltas[1] * 100) : 0, unit, detail: `${t("overviewExtraErrorShareDetail")}: ${fmt(deltas[0])} / ${fmt(deltas[1])}` }; 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 > 0 ? Math.min(100, deltas[0] / sum * 100) : null, unit, detail: "", slices: id === "extra_cache_hit_rate" ? [deltas[0], deltas[1]] : null }; } 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`; if (unit === "days") return `${fmt(value)} ${t("overviewExtraDaysUnit")}`; if (unit === "text") return String(value); return fmt(value); } function points(id, history, unit) { if (!history.length) return "0,55 300,55"; const peak = window.rustProxyChartScale.peak(`extra:${id}`, unit, [history]); return history.map((v, i) => `${i * 300 / Math.max(1, history.length - 1)},${55 - Math.min(v, peak) / 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) { lastCounter.delete(id); continue; } const metric = read(id, status, now, record); const value = metric.value; if (lastUnits.get(id) !== metric.unit) { histories.delete(id); window.rustProxyChartScale.reset(`extra:${id}`); lastUnits.set(id, metric.unit); } if (!bounded.has(id) && id !== "extra_cache_hit_rate") { const history = histories.get(id) || []; if (record && value !== null) { history.push(value); if (history.length > limit()) history.shift(); histories.set(id, history); } $(`overview-extra-${id}-line`)?.setAttribute("points", points(id, history, metric.unit)); window.rustProxyChartScale.axes(`overview-extra-${id}-line`, window.rustProxyChartScale.peak(`extra:${id}`, metric.unit, [history]), metric.unit, history.length, { top: 5 / 60, bottom: 55 / 60 }); } if (record) { lastValue.set(id, value); if (id === "extra_cache_hit_rate") lastSegments.set(id, metric.slices || null); } const field = $(`overview-extra-${id}`); if (field) field.textContent = format(value, metric.unit); if (bounded.has(id)) { meter(`overview-extra-${id}`, metric.meterPercent ?? (metric.unit === "percent" ? value : null)); } else if (id === "extra_cache_hit_rate") { const slices = metric.slices || (record ? null : lastSegments.get(id)); donut("extra-cache-ratio", slices?.[0], slices?.[1]); } const detail = $(`overview-extra-${id}-detail`); if (detail && id !== "extra_cache_hit_rate") { const detailText = metric.detail || ""; detail.textContent = detailText; detail.hidden = !detailText; } } } function reset() { histories.clear(); lastCounter.clear(); lastValue.clear(); lastUnits.clear(); lastSegments.clear(); window.rustProxyChartScale.reset("extra:"); } window.rustProxyOverviewSamples?.onChange?.(() => { for (const history of histories.values()) window.rustProxyOverviewSamples.trim(history); if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") render(latestStatus, false); }); return { render, reset, meter, donut }; })();