|
|
@@ -1179,11 +1179,16 @@ input.input-error { |
|
|
<div class="health-line"><div class="name">Audio Buffer</div><div class="val" id="health-audio">--</div></div> |
|
|
<div class="health-line"><div class="name">Audio Buffer</div><div class="val" id="health-audio">--</div></div> |
|
|
<div class="health-line"><div class="name">Buffer Duration</div><div class="val" id="health-buffer-duration">--</div></div> |
|
|
<div class="health-line"><div class="name">Buffer Duration</div><div class="val" id="health-buffer-duration">--</div></div> |
|
|
<div class="health-line"><div class="name">High Watermark</div><div class="val" id="health-buffer-highwater">--</div></div> |
|
|
<div class="health-line"><div class="name">High Watermark</div><div class="val" id="health-buffer-highwater">--</div></div> |
|
|
|
|
|
<div class="health-line"><div class="name">Queue Fill</div><div class="val" id="health-queue-fill">--</div></div> |
|
|
<div class="health-line"><div class="name">Last Update</div><div class="val" id="health-last">--</div></div> |
|
|
<div class="health-line"><div class="name">Last Update</div><div class="val" id="health-last">--</div></div> |
|
|
<div class="health-trend"> |
|
|
<div class="health-trend"> |
|
|
<div class="health-trend-label">High Watermark Trend</div> |
|
|
<div class="health-trend-label">High Watermark Trend</div> |
|
|
<svg class="spark warn" id="spark-high-watermark" viewBox="0 0 160 34" preserveAspectRatio="none"></svg> |
|
|
<svg class="spark warn" id="spark-high-watermark" viewBox="0 0 160 34" preserveAspectRatio="none"></svg> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="health-trend"> |
|
|
|
|
|
<div class="health-trend-label">Queue Fill Trend</div> |
|
|
|
|
|
<svg class="spark good" id="spark-queue-fill" viewBox="0 0 160 34" preserveAspectRatio="none"></svg> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
@@ -1319,6 +1324,7 @@ const state = { |
|
|
underruns: [], |
|
|
underruns: [], |
|
|
tx: [], |
|
|
tx: [], |
|
|
highWatermark: [], |
|
|
highWatermark: [], |
|
|
|
|
|
queueFill: [], |
|
|
}, |
|
|
}, |
|
|
runtimeTransitions: [], |
|
|
runtimeTransitions: [], |
|
|
freqPresetIndex: 0, |
|
|
freqPresetIndex: 0, |
|
|
@@ -1497,6 +1503,8 @@ function pushHistory(runtime) { |
|
|
const highWatermarkDurationSeconds = Number(audio.highWatermarkDurationSeconds); |
|
|
const highWatermarkDurationSeconds = Number(audio.highWatermarkDurationSeconds); |
|
|
const normalizedHighWatermark = Number.isFinite(highWatermarkDurationSeconds) ? highWatermarkDurationSeconds : 0; |
|
|
const normalizedHighWatermark = Number.isFinite(highWatermarkDurationSeconds) ? highWatermarkDurationSeconds : 0; |
|
|
pushChart(state.charts.highWatermark, normalizedHighWatermark); |
|
|
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)); |
|
|
pushChart(state.charts.underruns, Number(engine.underruns ?? driver.underruns ?? 0)); |
|
|
const txState = String(engine.state || 'idle').toLowerCase(); |
|
|
const txState = String(engine.state || 'idle').toLowerCase(); |
|
|
pushChart(state.charts.tx, txState === 'running' ? 1 : state.txBusy ? 0.55 : 0.05); |
|
|
pushChart(state.charts.tx, txState === 'running' ? 1 : state.txBusy ? 0.55 : 0.05); |
|
|
@@ -1988,8 +1996,13 @@ function render() { |
|
|
hasHighWatermarkDuration ? highWatermarkDurationSeconds : 0, |
|
|
hasHighWatermarkDuration ? highWatermarkDurationSeconds : 0, |
|
|
hasBufferedDuration ? bufferedDurationSeconds : 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-audio', state.charts.audio, 'good', 1); |
|
|
drawSparkline('spark-high-watermark', state.charts.highWatermark, highWatermarkMode, sparkHighWatermarkMax); |
|
|
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-underruns', state.charts.underruns, underruns > 0 ? 'err' : 'warn'); |
|
|
drawSparkline('spark-tx', state.charts.tx, txStateValue === 'running' ? 'good' : 'warn', 1); |
|
|
drawSparkline('spark-tx', state.charts.tx, txStateValue === 'running' ? 'good' : 'warn', 1); |
|
|
applyMobilePanelDefaults(); |
|
|
applyMobilePanelDefaults(); |
|
|
@@ -2143,6 +2156,25 @@ function updateHealth(engine, audioStream) { |
|
|
} |
|
|
} |
|
|
updateText('health-buffer-highwater', highWatermarkLabel); |
|
|
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); |
|
|
const last = Math.max(state.server.lastConfigAt || 0, state.server.lastRuntimeAt || 0); |
|
|
updateText('health-last', ageString(last)); |
|
|
updateText('health-last', ageString(last)); |
|
|
|
|
|
|
|
|
|