crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

commit 04cb8edfe1ff0c032f4127ff63305ca3b5ca7ca9
parent b3aa640b6539c74cb19e1925dac81eda0fb3b608
Author: Michael Camilleri <[email protected]>
Date:   Mon, 25 May 2026 23:38:11 +0900

Add further instrumentation to WebRTC bridge

Diffstat:
MCrossmate/Services/EngagementHost.html | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 99 insertions(+), 0 deletions(-)

diff --git a/Crossmate/Services/EngagementHost.html b/Crossmate/Services/EngagementHost.html @@ -89,6 +89,8 @@ engagementID, "data channel open label=" + channel.label + " buffered=" + channel.bufferedAmount ); + postStatsDiagnostic(engagementID, "channel-open"); + setTimeout(() => postStatsDiagnostic(engagementID, "channel-open+1s"), 1000); post({ type: "onChannelOpen", engagementID }); }; channel.onclose = () => postClose(engagementID, "dataChannel"); @@ -107,6 +109,7 @@ engagementID, "data channel receive bytes=" + bytes.byteLength + " state=" + channel.readyState ); + postStatsDiagnostic(engagementID, "after-receive"); post({ type: "onChannelMessage", engagementID, @@ -164,6 +167,99 @@ ); } + function candidateSummary(report) { + if (!report) { + return "missing"; + } + return [ + "type=" + (report.candidateType || "unknown"), + "protocol=" + (report.protocol || "unknown"), + "relayProtocol=" + (report.relayProtocol || "none") + ].join("/"); + } + + function numberSummary(value) { + return typeof value === "number" && Number.isFinite(value) ? String(value) : "n/a"; + } + + function decimalSummary(value) { + return typeof value === "number" && Number.isFinite(value) ? value.toFixed(3) : "n/a"; + } + + function selectedCandidatePair(reports) { + for (const report of reports.values()) { + if (report.type === "transport" && report.selectedCandidatePairId) { + return reports.get(report.selectedCandidatePairId); + } + } + let fallback = null; + for (const report of reports.values()) { + if (report.type !== "candidate-pair") { + continue; + } + if (report.selected || (report.nominated && report.state === "succeeded")) { + return report; + } + if (report.state === "succeeded" && !fallback) { + fallback = report; + } + } + return fallback; + } + + function dataChannelStats(reports) { + for (const report of reports.values()) { + if (report.type === "data-channel") { + return report; + } + } + return null; + } + + async function postStatsDiagnostic(engagementID, label) { + try { + const peer = peers.get(engagementID); + if (!peer) { + postDiagnostic(engagementID, "stats " + label + " peer=missing"); + return; + } + const reports = await peer.pc.getStats(); + const pair = selectedCandidatePair(reports); + const localCandidate = pair ? reports.get(pair.localCandidateId) : null; + const remoteCandidate = pair ? reports.get(pair.remoteCandidateId) : null; + const dataChannel = dataChannelStats(reports); + const channel = peer.channel; + postDiagnostic( + engagementID, + [ + "stats " + label, + "pc=" + peer.pc.connectionState, + "ice=" + peer.pc.iceConnectionState, + "pairState=" + (pair ? pair.state : "missing"), + "nominated=" + (pair ? String(Boolean(pair.nominated)) : "missing"), + "local=" + candidateSummary(localCandidate), + "remote=" + candidateSummary(remoteCandidate), + "bytesSent=" + numberSummary(pair ? pair.bytesSent : undefined), + "bytesReceived=" + numberSummary(pair ? pair.bytesReceived : undefined), + "packetsSent=" + numberSummary(pair ? pair.packetsSent : undefined), + "packetsReceived=" + numberSummary(pair ? pair.packetsReceived : undefined), + "rtt=" + decimalSummary(pair ? pair.currentRoundTripTime : undefined), + "dcState=" + (dataChannel ? dataChannel.state : (channel ? channel.readyState : "missing")), + "dcMessagesSent=" + numberSummary(dataChannel ? dataChannel.messagesSent : undefined), + "dcMessagesReceived=" + numberSummary(dataChannel ? dataChannel.messagesReceived : undefined), + "dcBytesSent=" + numberSummary(dataChannel ? dataChannel.bytesSent : undefined), + "dcBytesReceived=" + numberSummary(dataChannel ? dataChannel.bytesReceived : undefined), + "buffered=" + (channel ? channel.bufferedAmount : "missing") + ].join(" ") + ); + } catch (error) { + postDiagnostic( + engagementID, + "stats " + label + " failed=" + (error && error.message ? error.message : String(error)) + ); + } + } + function candidatesFromSDP(sdp) { return sdp .split(/\r?\n/) @@ -197,6 +293,7 @@ if (peer) { peer.closed = true; } + postStatsDiagnostic(engagementID, "before-close-" + (reason || "unknown")); post({ type: "onDiagnostic", engagementID, @@ -280,6 +377,8 @@ "data channel send accepted bytes=" + bytes.byteLength + " buffered=" + peer.channel.bufferedAmount ); + postStatsDiagnostic(engagementID, "after-send"); + setTimeout(() => postStatsDiagnostic(engagementID, "after-send+1s"), 1000); return true; }