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:
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;
}