From 4d9895918a105421d832adde411727f223950d8e Mon Sep 17 00:00:00 2001 From: Jan Svabenik Date: Mon, 6 Apr 2026 04:01:57 +0200 Subject: [PATCH] feat: show queue fill telemetry --- docs/pro-runtime-hardening-workboard.md | 2 ++ internal/control/ui.html | 32 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/docs/pro-runtime-hardening-workboard.md b/docs/pro-runtime-hardening-workboard.md index e0f5f3b..ce14fdf 100644 --- a/docs/pro-runtime-hardening-workboard.md +++ b/docs/pro-runtime-hardening-workboard.md @@ -385,11 +385,13 @@ Vollständige Sichtbarkeit auf Runtime, Queue, Writer, Generator, RF-Selbsttests | Datum | Entscheidung | Notiz | | --- | --- | --- | | 2026-04-06 | High-watermark trend sparkline | Captured audio high-watermark duration history and surface it as a new Health-panel sparkline for queue pressure visibility. | +| 2026-04-06 | Queue fill visibility | Added queue fill ratio health line and sparklines to highlight real-time queue pressure alongside high-watermark trends. | ## WS-04 Verifikation | Datum | Fokus | Ergebnis | | --- | --- | --- | | 2026-04-06 | High-watermark trend sparkline | `go test ./...` plus manual UI check confirm the new sparkline updates with runtime audio stats. | +| 2026-04-06 | Queue fill visibility | `go test ./...` plus UI smoke check confirm queue fill stats stay available and the new sparkline/health line react to queue health changes. | --- diff --git a/internal/control/ui.html b/internal/control/ui.html index 8764bff..7ff20f8 100644 --- a/internal/control/ui.html +++ b/internal/control/ui.html @@ -1179,11 +1179,16 @@ input.input-error {
Audio Buffer
--
Buffer Duration
--
High Watermark
--
+
Queue Fill
--
Last Update
--
High Watermark Trend
+
+
Queue Fill Trend
+ +
@@ -1319,6 +1324,7 @@ const state = { underruns: [], tx: [], highWatermark: [], + queueFill: [], }, runtimeTransitions: [], freqPresetIndex: 0, @@ -1497,6 +1503,8 @@ function pushHistory(runtime) { const highWatermarkDurationSeconds = Number(audio.highWatermarkDurationSeconds); const normalizedHighWatermark = Number.isFinite(highWatermarkDurationSeconds) ? highWatermarkDurationSeconds : 0; pushChart(state.charts.highWatermark, normalizedHighWatermark); + const queueFill = Number(engine.queue?.fillLevel ?? 0); + pushChart(state.charts.queueFill, Number.isFinite(queueFill) ? queueFill : 0); pushChart(state.charts.underruns, Number(engine.underruns ?? driver.underruns ?? 0)); const txState = String(engine.state || 'idle').toLowerCase(); pushChart(state.charts.tx, txState === 'running' ? 1 : state.txBusy ? 0.55 : 0.05); @@ -1988,8 +1996,13 @@ function render() { hasHighWatermarkDuration ? highWatermarkDurationSeconds : 0, hasBufferedDuration ? bufferedDurationSeconds : 0 ); + const queueHealthRaw = String(engine.queue?.health || '').toLowerCase(); + let queueSparkMode = 'good'; + if (queueHealthRaw === 'critical') queueSparkMode = 'err'; + else if (queueHealthRaw === 'low') queueSparkMode = 'warn'; drawSparkline('spark-audio', state.charts.audio, 'good', 1); drawSparkline('spark-high-watermark', state.charts.highWatermark, highWatermarkMode, sparkHighWatermarkMax); + drawSparkline('spark-queue-fill', state.charts.queueFill, queueSparkMode, 1); drawSparkline('spark-underruns', state.charts.underruns, underruns > 0 ? 'err' : 'warn'); drawSparkline('spark-tx', state.charts.tx, txStateValue === 'running' ? 'good' : 'warn', 1); applyMobilePanelDefaults(); @@ -2143,6 +2156,25 @@ function updateHealth(engine, audioStream) { } updateText('health-buffer-highwater', highWatermarkLabel); + const queueFill = Number(engine.queue?.fillLevel); + const queueHealthRaw = String(engine.queue?.health || '').toLowerCase(); + const queueHealthLabel = queueHealthRaw ? queueHealthRaw[0].toUpperCase() + queueHealthRaw.slice(1) : ''; + let queueFillLabel = '--'; + if (Number.isFinite(queueFill)) { + queueFillLabel = fmtPercent(queueFill); + if (queueHealthLabel) queueFillLabel += ` · ${queueHealthLabel}`; + } else if (queueHealthLabel) { + queueFillLabel = queueHealthLabel; + } + updateText('health-queue-fill', queueFillLabel); + const queueFillEl = $('health-queue-fill'); + if (queueFillEl) { + let queueFillClass = 'good'; + if (queueHealthRaw === 'critical') queueFillClass = 'err'; + else if (queueHealthRaw === 'low') queueFillClass = 'warn'; + queueFillEl.className = 'val ' + queueFillClass; + } + const last = Math.max(state.server.lastConfigAt || 0, state.server.lastRuntimeAt || 0); updateText('health-last', ageString(last));