From c70c9713bab322a8f91b367027d4d3e2c4e37de4 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Thu, 4 Jun 2026 12:39:55 -0400 Subject: [PATCH] =?UTF-8?q?zebra-spaces:=20more=20telemetry=20=E2=80=94=20?= =?UTF-8?q?lastPacketReceivedTimestamp=20+=20signal=20event=20firehose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two diagnostic adds for chasing the cohost-toggle-breaks-phone bug: 1. inbound-rtp lastPacketReceivedTimestamp on every audio + video receiver, expressed as 'lp=N.Ns ago'. Pkt-delta only shows audio stopped on the NEXT tick (5s later); lp pins the EXACT real-time moment RTP went silent. 'lp=0.1s' = healthy. 'lp=12s' = receiver has been dead for 12s. Lets us tell at a glance whether the receiver is starved or just idle. 2. Signal-event firehose. Every received signal message gets a one- line log with type + epoch + relevant uuid/role/action. Filters out high-rate noise (sdp-from, mic-state, spotlight, state) so the page log stays readable. Now when phone audio breaks at 16:29:07, we can scroll the phone's page log and see exactly which signal events arrived in the seconds leading up. Format: '« role-change e=12 u=ab12 role=speaker action=…' The « marker keeps signal events visually distinct from the · telemetry ticks and free-form logLines. --- web/zebra-spaces.html | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index 0a1b028..df01c8f 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -3524,15 +3524,28 @@ async function dumpTelemetry(){ if (sfuSubPC && typeof sfuSubPC.getStats === 'function'){ try { const stats = await sfuSubPC.getStats(null); + const nowMs = Date.now(); stats.forEach(r => { if (r.type === 'inbound-rtp' && r.kind === 'audio'){ + /* lastPacketReceivedTimestamp pins the EXACT real-time + * moment audio RTP stopped — pkt-delta only shows it after + * the next tick. Express as 'ago' so a frozen flow stands + * out at a glance ('lp=0.1s' = fine, 'lp=12s' = dead). */ + const lp = r.lastPacketReceivedTimestamp + ? ((nowMs - r.lastPacketReceivedTimestamp) / 1000).toFixed(1) + 's' + : '?'; parts.push('aud.recv pkt=' + (r.packetsReceived|0) + ' lost=' + (r.packetsLost|0) + ' bytes=' + (r.bytesReceived|0) + ' jitter=' + (r.jitter || 0).toFixed(4) + - ' level=' + (r.audioLevel || 0).toFixed(3)); + ' level=' + (r.audioLevel || 0).toFixed(3) + + ' lp=' + lp); } if (r.type === 'inbound-rtp' && r.kind === 'video'){ + const lp = r.lastPacketReceivedTimestamp + ? ((nowMs - r.lastPacketReceivedTimestamp) / 1000).toFixed(1) + 's' + : '?'; parts.push('vid.recv pkt=' + (r.packetsReceived|0) + ' lost=' + (r.packetsLost|0) + - ' frames=' + (r.framesDecoded|0)); + ' frames=' + (r.framesDecoded|0) + + ' lp=' + lp); } }); } catch(e){ parts.push('stats.err=' + e.message); } @@ -3637,6 +3650,21 @@ function openSignal(){ async function handleSignal(raw){ let m; try { m = JSON.parse(raw); } catch(_){ return; } + /* Firehose every received signal event with its epoch. Lets us + * correlate phone-side regressions ('audio went silent at 16:29:07') + * against the exact server events the phone received. Skip the + * uninteresting high-rate types so the log stays readable. */ + const noiseTypes = new Set(['sdp-from', 'mic-state', 'spotlight', 'state']); + if (!noiseTypes.has(m.type)){ + const ep = (m.epoch != null) ? ' e=' + m.epoch : ''; + const who = m.uuid ? (' u=' + String(m.uuid).slice(0,4)) + : m.from ? (' from=' + String(m.from).slice(0,4)) + : m.target ? (' tgt=' + String(m.target).slice(0,4)) + : ''; + const role = m.role ? (' role=' + m.role) : ''; + const action = m.action ? (' action=' + m.action) : ''; + logLine('', '« ' + m.type + ep + who + role + action); + } switch (m.type){ case 'welcome': { @@ -5433,8 +5461,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');