|
|
@@ -770,6 +770,16 @@ input.input-error { |
|
|
.health-line .val.good { color: var(--green); } |
|
|
.health-line .val.good { color: var(--green); } |
|
|
.health-line .val.warn { color: var(--amber); } |
|
|
.health-line .val.warn { color: var(--amber); } |
|
|
.health-line .val.err { color: var(--accent); } |
|
|
.health-line .val.err { color: var(--accent); } |
|
|
|
|
|
.health-trend { |
|
|
|
|
|
margin-top: 10px; |
|
|
|
|
|
} |
|
|
|
|
|
.health-trend-label { |
|
|
|
|
|
font-size: 10px; |
|
|
|
|
|
text-transform: uppercase; |
|
|
|
|
|
letter-spacing: 1px; |
|
|
|
|
|
color: var(--text-muted); |
|
|
|
|
|
margin-bottom: 6px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
.fault-history { |
|
|
.fault-history { |
|
|
margin-top: 12px; |
|
|
margin-top: 12px; |
|
|
@@ -1170,6 +1180,10 @@ input.input-error { |
|
|
<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">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-label">High Watermark Trend</div> |
|
|
|
|
|
<svg class="spark warn" id="spark-high-watermark" viewBox="0 0 160 34" preserveAspectRatio="none"></svg> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
@@ -1304,6 +1318,7 @@ const state = { |
|
|
audio: [], |
|
|
audio: [], |
|
|
underruns: [], |
|
|
underruns: [], |
|
|
tx: [], |
|
|
tx: [], |
|
|
|
|
|
highWatermark: [], |
|
|
}, |
|
|
}, |
|
|
runtimeTransitions: [], |
|
|
runtimeTransitions: [], |
|
|
freqPresetIndex: 0, |
|
|
freqPresetIndex: 0, |
|
|
@@ -1479,6 +1494,9 @@ function pushHistory(runtime) { |
|
|
const driver = runtime.driver || {}; |
|
|
const driver = runtime.driver || {}; |
|
|
const audio = runtime.audioStream || {}; |
|
|
const audio = runtime.audioStream || {}; |
|
|
pushChart(state.charts.audio, typeof audio.buffered === 'number' ? audio.buffered : 0); |
|
|
pushChart(state.charts.audio, typeof audio.buffered === 'number' ? audio.buffered : 0); |
|
|
|
|
|
const highWatermarkDurationSeconds = Number(audio.highWatermarkDurationSeconds); |
|
|
|
|
|
const normalizedHighWatermark = Number.isFinite(highWatermarkDurationSeconds) ? highWatermarkDurationSeconds : 0; |
|
|
|
|
|
pushChart(state.charts.highWatermark, normalizedHighWatermark); |
|
|
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); |
|
|
@@ -1951,7 +1969,27 @@ function render() { |
|
|
updateTransitionHistory(); |
|
|
updateTransitionHistory(); |
|
|
updateResetHint(engine); |
|
|
updateResetHint(engine); |
|
|
updateMeters(engine, driver, audioStream); |
|
|
updateMeters(engine, driver, audioStream); |
|
|
|
|
|
const highWatermarkDurationSecondsRaw = audioStream?.highWatermarkDurationSeconds; |
|
|
|
|
|
const highWatermarkDurationSeconds = Number(highWatermarkDurationSecondsRaw); |
|
|
|
|
|
const highWatermarkFramesRaw = audioStream?.highWatermark; |
|
|
|
|
|
const highWatermarkFrames = Number.isFinite(Number(highWatermarkFramesRaw)) ? Number(highWatermarkFramesRaw) : 0; |
|
|
|
|
|
const capacityRaw = audioStream?.capacity; |
|
|
|
|
|
const capacity = Number.isFinite(Number(capacityRaw)) ? Number(capacityRaw) : 0; |
|
|
|
|
|
const bufferedDurationSecondsRaw = audioStream?.bufferedDurationSeconds; |
|
|
|
|
|
const bufferedDurationSeconds = Number(bufferedDurationSecondsRaw); |
|
|
|
|
|
const hasBufferedDuration = Number.isFinite(bufferedDurationSeconds); |
|
|
|
|
|
const hasHighWatermarkDuration = Number.isFinite(highWatermarkDurationSeconds); |
|
|
|
|
|
const highWatermarkRatio = capacity > 0 ? Math.min(1, highWatermarkFrames / capacity) : 0; |
|
|
|
|
|
let highWatermarkMode = 'good'; |
|
|
|
|
|
if (highWatermarkRatio >= 0.95) highWatermarkMode = 'err'; |
|
|
|
|
|
else if (highWatermarkRatio >= 0.65) highWatermarkMode = 'warn'; |
|
|
|
|
|
const sparkHighWatermarkMax = Math.max( |
|
|
|
|
|
1, |
|
|
|
|
|
hasHighWatermarkDuration ? highWatermarkDurationSeconds : 0, |
|
|
|
|
|
hasBufferedDuration ? bufferedDurationSeconds : 0 |
|
|
|
|
|
); |
|
|
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-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(); |
|
|
|