From 66c3d05ae936d7dcd56607184cbf2885619f317c Mon Sep 17 00:00:00 2001 From: Enrico Schwendig Date: Tue, 28 Mar 2023 11:51:15 +0200 Subject: [PATCH 01/10] docu: Add webrtc metric to OTel --- package.json | 2 +- src/otel/OTelGroupCallMembership.ts | 92 +++++++++++- src/otel/ObjectFlattener.ts | 83 +++++++++++ src/room/useGroupCall.ts | 36 +++++ test/otel/ObjectFlattene-test.ts | 215 ++++++++++++++++++++++++++++ 5 files changed, 421 insertions(+), 7 deletions(-) create mode 100644 src/otel/ObjectFlattener.ts create mode 100644 test/otel/ObjectFlattene-test.ts diff --git a/package.json b/package.json index a32fd8c9..3f31e74e 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "i18next-browser-languagedetector": "^6.1.8", "i18next-http-backend": "^1.4.4", "lodash": "^4.17.21", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#23837266fca5ee799b51a722f7b8eefb2f5ac140", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#2cd38e91eee1f5b16a9be0caba6ff19486b95f31", "matrix-widget-api": "^1.0.0", "mermaid": "^8.13.8", "normalize.css": "^8.0.1", diff --git a/src/otel/OTelGroupCallMembership.ts b/src/otel/OTelGroupCallMembership.ts index 764249f3..c11b6e01 100644 --- a/src/otel/OTelGroupCallMembership.ts +++ b/src/otel/OTelGroupCallMembership.ts @@ -23,8 +23,15 @@ import { RoomMember, } from "matrix-js-sdk"; import { VoipEvent } from "matrix-js-sdk/src/webrtc/call"; +import { GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall"; +import { + ConnectionStatsReport, + ByteSentStatsReport, +} from "matrix-js-sdk/src/webrtc/stats/statsReport"; +import { setSpan } from "@opentelemetry/api/build/esm/trace/context-utils"; import { ElementCallOpenTelemetry } from "./otel"; +import { ObjectFlattener } from "./ObjectFlattener"; /** * Flattens out an object into a single layer with components @@ -73,12 +80,24 @@ function flattenVoipEventRecursive( */ export class OTelGroupCallMembership { private callMembershipSpan?: Span; - private myUserId: string; - private myMember: RoomMember; + private myUserId = "unknown"; + private myMember?: RoomMember; + private statsReportSpan: { + span: Span | undefined; + stats: OTelStatsReportEvent[]; + }; constructor(private groupCall: GroupCall, client: MatrixClient) { - this.myUserId = client.getUserId(); - this.myMember = groupCall.room.getMember(client.getUserId()); + const clientId = client.getUserId(); + if (clientId) { + this.myUserId = clientId; + const myMember = groupCall.room.getMember(clientId); + if (myMember) { + this.myMember = myMember; + } + } + + this.statsReportSpan = { span: undefined, stats: [] }; ElementCallOpenTelemetry.instance.provider.resource.attributes[ SemanticResourceAttributes.SERVICE_NAME @@ -98,7 +117,7 @@ export class OTelGroupCallMembership { this.callMembershipSpan.setAttribute("matrix.userId", this.myUserId); this.callMembershipSpan.setAttribute( "matrix.displayName", - this.myMember.name + this.myMember ? this.myMember.name : "unknown-name" ); opentelemetry.trace.setSpan( @@ -113,7 +132,7 @@ export class OTelGroupCallMembership { this.callMembershipSpan?.addEvent("matrix.leaveCall"); // and end the main span to indicate we've left - if (this.callMembershipSpan) this.callMembershipSpan.end(); + this.callMembershipSpan?.end(); } public onUpdateRoomState(event: MatrixEvent) { @@ -177,4 +196,65 @@ export class OTelGroupCallMembership { "matrix.screensharing.enabled": newValue, }); } + + public onConnectionStatsReport( + statsReport: GroupCallStatsReport + ) { + const type = OTelStatsReportType.ConnectionStatsReport; + const data = + ObjectFlattener.flattenConnectionStatsReportObject(statsReport); + this.buildStatsEventSpan({ type, data }); + } + + public onByteSentStatsReport( + statsReport: GroupCallStatsReport + ) { + const type = OTelStatsReportType.ByteSentStatsReport; + const data = ObjectFlattener.flattenByteSentStatsReportObject(statsReport); + this.buildStatsEventSpan({ type, data }); + } + + private buildStatsEventSpan(event: OTelStatsReportEvent): void { + if (this.statsReportSpan.span === undefined && this.callMembershipSpan) { + const ctx = setSpan( + opentelemetry.context.active(), + this.callMembershipSpan + ); + this.statsReportSpan.span = + ElementCallOpenTelemetry.instance.tracer.startSpan( + "matrix.groupCallMembership.statsReport", + undefined, + ctx + ); + this.statsReportSpan.span.setAttribute( + "matrix.confId", + this.groupCall.groupCallId + ); + this.statsReportSpan.span.setAttribute("matrix.userId", this.myUserId); + this.statsReportSpan.span.setAttribute( + "matrix.displayName", + this.myMember ? this.myMember.name : "unknown-name" + ); + + this.statsReportSpan.span.addEvent(event.type, event.data); + this.statsReportSpan.stats.push(event); + } else if ( + this.statsReportSpan.span !== undefined && + this.callMembershipSpan + ) { + this.statsReportSpan.span.addEvent(event.type, event.data); + this.statsReportSpan.span.end(); + this.statsReportSpan = { span: undefined, stats: [] }; + } + } +} + +interface OTelStatsReportEvent { + type: OTelStatsReportType; + data: Attributes; +} + +enum OTelStatsReportType { + ConnectionStatsReport = "matrix.stats.connection", + ByteSentStatsReport = "matrix.stats.byteSent", } diff --git a/src/otel/ObjectFlattener.ts b/src/otel/ObjectFlattener.ts new file mode 100644 index 00000000..d45360cd --- /dev/null +++ b/src/otel/ObjectFlattener.ts @@ -0,0 +1,83 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +import { Attributes } from "@opentelemetry/api"; +import { GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall"; +import { + ByteSentStatsReport, + ConnectionStatsReport, +} from "matrix-js-sdk/src/webrtc/stats/statsReport"; + +export class ObjectFlattener { + public static flattenConnectionStatsReportObject( + statsReport: GroupCallStatsReport + ): Attributes { + const flatObject = {}; + ObjectFlattener.flattenObjectRecursive( + statsReport.report, + flatObject, + "matrix.stats.conn.", + 0 + ); + return flatObject; + } + + public static flattenByteSentStatsReportObject( + statsReport: GroupCallStatsReport + ): Attributes { + const flatObject = {}; + ObjectFlattener.flattenObjectRecursive( + statsReport.report, + flatObject, + "matrix.stats.bytesSent.", + 0 + ); + return flatObject; + } + + public static flattenObjectRecursive( + obj: Object, + flatObject: Attributes, + prefix: string, + depth: number + ): void { + if (depth > 10) + throw new Error( + "Depth limit exceeded: aborting VoipEvent recursion. Prefix is " + + prefix + ); + let entries; + if (obj instanceof Map) { + entries = obj.entries(); + } else { + entries = Object.entries(obj); + } + for (const [k, v] of entries) { + if (["string", "number", "boolean"].includes(typeof v) || v === null) { + let value; + value = v === null ? "null" : v; + value = typeof v === "number" && Number.isNaN(v) ? "NaN" : value; + flatObject[prefix + k] = value; + } else if (typeof v === "object") { + ObjectFlattener.flattenObjectRecursive( + v, + flatObject, + prefix + k + ".", + depth + 1 + ); + } + } + } +} diff --git a/src/room/useGroupCall.ts b/src/room/useGroupCall.ts index 0b54c82f..1c691816 100644 --- a/src/room/useGroupCall.ts +++ b/src/room/useGroupCall.ts @@ -22,12 +22,18 @@ import { GroupCallErrorCode, GroupCallUnknownDeviceError, GroupCallError, + GroupCallStatsReportEvent, + GroupCallStatsReport, } from "matrix-js-sdk/src/webrtc/groupCall"; import { CallFeed, CallFeedEvent } from "matrix-js-sdk/src/webrtc/callFeed"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { useTranslation } from "react-i18next"; import { IWidgetApiRequest } from "matrix-widget-api"; import { MatrixClient } from "matrix-js-sdk"; +import { + ByteSentStatsReport, + ConnectionStatsReport, +} from "matrix-js-sdk/src/webrtc/stats/statsReport"; import { usePageUnload } from "./usePageUnload"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; @@ -330,6 +336,18 @@ export function useGroupCall( } } + function onConnectionStatsReport( + report: GroupCallStatsReport + ): void { + groupCallOTelMembership?.onConnectionStatsReport(report); + } + + function onByteSentStatsReport( + report: GroupCallStatsReport + ): void { + groupCallOTelMembership?.onByteSentStatsReport(report); + } + groupCall.on(GroupCallEvent.GroupCallStateChanged, onGroupCallStateChanged); groupCall.on(GroupCallEvent.UserMediaFeedsChanged, onUserMediaFeedsChanged); groupCall.on( @@ -346,6 +364,16 @@ export function useGroupCall( groupCall.on(GroupCallEvent.ParticipantsChanged, onParticipantsChanged); groupCall.on(GroupCallEvent.Error, onError); + groupCall.on( + GroupCallStatsReportEvent.ConnectionStats, + onConnectionStatsReport + ); + + groupCall.on( + GroupCallStatsReportEvent.ByteSentStats, + onByteSentStatsReport + ); + updateState({ error: null, state: groupCall.state, @@ -392,6 +420,14 @@ export function useGroupCall( onParticipantsChanged ); groupCall.removeListener(GroupCallEvent.Error, onError); + groupCall.removeListener( + GroupCallStatsReportEvent.ConnectionStats, + onConnectionStatsReport + ); + groupCall.removeListener( + GroupCallStatsReportEvent.ByteSentStats, + onByteSentStatsReport + ); groupCall.leave(); }; }, [groupCall, updateState]); diff --git a/test/otel/ObjectFlattene-test.ts b/test/otel/ObjectFlattene-test.ts new file mode 100644 index 00000000..a0258c01 --- /dev/null +++ b/test/otel/ObjectFlattene-test.ts @@ -0,0 +1,215 @@ +import { ObjectFlattener } from "../../src/otel/ObjectFlattener"; + +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +describe("ObjectFlattener", () => { + const statsReport = { + report: { + bandwidth: { upload: 426, download: 0 }, + bitrate: { + upload: 426, + download: 0, + audio: { + upload: 124, + download: 0, + }, + video: { + upload: 302, + download: 0, + }, + }, + packetLoss: { + total: 0, + download: 0, + upload: 0, + }, + framerate: { + local: new Map([ + ["LOCAL_AUDIO_TRACK_ID", 0], + ["LOCAL_VIDEO_TRACK_ID", 30], + ]), + remote: new Map([ + ["REMOTE_AUDIO_TRACK_ID", 0], + ["REMOTE_VIDEO_TRACK_ID", 60], + ]), + }, + resolution: { + local: new Map([ + ["LOCAL_AUDIO_TRACK_ID", { height: -1, width: -1 }], + ["LOCAL_VIDEO_TRACK_ID", { height: 460, width: 780 }], + ]), + remote: new Map([ + ["REMOTE_AUDIO_TRACK_ID", { height: -1, width: -1 }], + ["REMOTE_VIDEO_TRACK_ID", { height: 960, width: 1080 }], + ]), + }, + codec: { + local: new Map([ + ["LOCAL_AUDIO_TRACK_ID", "opus"], + ["LOCAL_VIDEO_TRACK_ID", "v8"], + ]), + remote: new Map([ + ["REMOTE_AUDIO_TRACK_ID", "opus"], + ["REMOTE_VIDEO_TRACK_ID", "v9"], + ]), + }, + transport: [ + { + ip: "ff11::5fa:abcd:999c:c5c5:50000", + type: "udp", + localIp: "2aaa:9999:2aaa:999:8888:2aaa:2aaa:7777:50000", + isFocus: true, + localCandidateType: "host", + remoteCandidateType: "host", + networkType: "ethernet", + rtt: NaN, + }, + { + ip: "10.10.10.2:22222", + type: "tcp", + localIp: "10.10.10.100:33333", + isFocus: true, + localCandidateType: "srfx", + remoteCandidateType: "srfx", + networkType: "ethernet", + rtt: null, + }, + ], + }, + }; + describe("on flattenObjectRecursive", () => { + it("should flatter an Map object", () => { + const flatObject = {}; + ObjectFlattener.flattenObjectRecursive( + statsReport.report.resolution, + flatObject, + "matrix.stats.conn.resolution.", + 0 + ); + expect(flatObject).toEqual({ + "matrix.stats.conn.resolution.local.LOCAL_AUDIO_TRACK_ID.height": -1, + "matrix.stats.conn.resolution.local.LOCAL_AUDIO_TRACK_ID.width": -1, + + "matrix.stats.conn.resolution.local.LOCAL_VIDEO_TRACK_ID.height": 460, + "matrix.stats.conn.resolution.local.LOCAL_VIDEO_TRACK_ID.width": 780, + + "matrix.stats.conn.resolution.remote.REMOTE_AUDIO_TRACK_ID.height": -1, + "matrix.stats.conn.resolution.remote.REMOTE_AUDIO_TRACK_ID.width": -1, + + "matrix.stats.conn.resolution.remote.REMOTE_VIDEO_TRACK_ID.height": 960, + "matrix.stats.conn.resolution.remote.REMOTE_VIDEO_TRACK_ID.width": 1080, + }); + }); + it("should flatter an Array object", () => { + const flatObject = {}; + ObjectFlattener.flattenObjectRecursive( + statsReport.report.transport, + flatObject, + "matrix.stats.conn.transport.", + 0 + ); + expect(flatObject).toEqual({ + "matrix.stats.conn.transport.0.ip": "ff11::5fa:abcd:999c:c5c5:50000", + "matrix.stats.conn.transport.0.type": "udp", + "matrix.stats.conn.transport.0.localIp": + "2aaa:9999:2aaa:999:8888:2aaa:2aaa:7777:50000", + "matrix.stats.conn.transport.0.isFocus": true, + "matrix.stats.conn.transport.0.localCandidateType": "host", + "matrix.stats.conn.transport.0.remoteCandidateType": "host", + "matrix.stats.conn.transport.0.networkType": "ethernet", + "matrix.stats.conn.transport.0.rtt": "NaN", + "matrix.stats.conn.transport.1.ip": "10.10.10.2:22222", + "matrix.stats.conn.transport.1.type": "tcp", + "matrix.stats.conn.transport.1.localIp": "10.10.10.100:33333", + "matrix.stats.conn.transport.1.isFocus": true, + "matrix.stats.conn.transport.1.localCandidateType": "srfx", + "matrix.stats.conn.transport.1.remoteCandidateType": "srfx", + "matrix.stats.conn.transport.1.networkType": "ethernet", + "matrix.stats.conn.transport.1.rtt": "null", + }); + }); + }); + + describe("on flattenConnectionStatsReportObject", () => { + it("should flatten a Report to otel Attributes Object", () => { + expect( + ObjectFlattener.flattenConnectionStatsReportObject(statsReport) + ).toEqual({ + "matrix.stats.conn.bandwidth.download": 0, + "matrix.stats.conn.bandwidth.upload": 426, + "matrix.stats.conn.bitrate.audio.download": 0, + "matrix.stats.conn.bitrate.audio.upload": 124, + "matrix.stats.conn.bitrate.download": 0, + "matrix.stats.conn.bitrate.upload": 426, + "matrix.stats.conn.bitrate.video.download": 0, + "matrix.stats.conn.bitrate.video.upload": 302, + "matrix.stats.conn.codec.local.LOCAL_AUDIO_TRACK_ID": "opus", + "matrix.stats.conn.codec.local.LOCAL_VIDEO_TRACK_ID": "v8", + "matrix.stats.conn.codec.remote.REMOTE_AUDIO_TRACK_ID": "opus", + "matrix.stats.conn.codec.remote.REMOTE_VIDEO_TRACK_ID": "v9", + "matrix.stats.conn.framerate.local.LOCAL_AUDIO_TRACK_ID": 0, + "matrix.stats.conn.framerate.local.LOCAL_VIDEO_TRACK_ID": 30, + "matrix.stats.conn.framerate.remote.REMOTE_AUDIO_TRACK_ID": 0, + "matrix.stats.conn.framerate.remote.REMOTE_VIDEO_TRACK_ID": 60, + "matrix.stats.conn.packetLoss.download": 0, + "matrix.stats.conn.packetLoss.total": 0, + "matrix.stats.conn.packetLoss.upload": 0, + "matrix.stats.conn.resolution.local.LOCAL_AUDIO_TRACK_ID.height": -1, + "matrix.stats.conn.resolution.local.LOCAL_AUDIO_TRACK_ID.width": -1, + "matrix.stats.conn.resolution.local.LOCAL_VIDEO_TRACK_ID.height": 460, + "matrix.stats.conn.resolution.local.LOCAL_VIDEO_TRACK_ID.width": 780, + "matrix.stats.conn.resolution.remote.REMOTE_AUDIO_TRACK_ID.height": -1, + "matrix.stats.conn.resolution.remote.REMOTE_AUDIO_TRACK_ID.width": -1, + "matrix.stats.conn.resolution.remote.REMOTE_VIDEO_TRACK_ID.height": 960, + "matrix.stats.conn.resolution.remote.REMOTE_VIDEO_TRACK_ID.width": 1080, + "matrix.stats.conn.transport.0.ip": "ff11::5fa:abcd:999c:c5c5:50000", + "matrix.stats.conn.transport.0.type": "udp", + "matrix.stats.conn.transport.0.localIp": + "2aaa:9999:2aaa:999:8888:2aaa:2aaa:7777:50000", + "matrix.stats.conn.transport.0.isFocus": true, + "matrix.stats.conn.transport.0.localCandidateType": "host", + "matrix.stats.conn.transport.0.remoteCandidateType": "host", + "matrix.stats.conn.transport.0.networkType": "ethernet", + "matrix.stats.conn.transport.0.rtt": "NaN", + "matrix.stats.conn.transport.1.ip": "10.10.10.2:22222", + "matrix.stats.conn.transport.1.type": "tcp", + "matrix.stats.conn.transport.1.localIp": "10.10.10.100:33333", + "matrix.stats.conn.transport.1.isFocus": true, + "matrix.stats.conn.transport.1.localCandidateType": "srfx", + "matrix.stats.conn.transport.1.remoteCandidateType": "srfx", + "matrix.stats.conn.transport.1.networkType": "ethernet", + "matrix.stats.conn.transport.1.rtt": "null", + }); + }); + }); + + describe("on flattenByteSendStatsReportObject", () => { + const byteSent = { + report: new Map([ + ["4aa92608-04c6-428e-8312-93e17602a959", 132093], + ["a08e4237-ee30-4015-a932-b676aec894b1", 913448], + ]), + }; + it("should flatten a Report to otel Attributes Object", () => { + expect( + ObjectFlattener.flattenByteSentStatsReportObject(byteSent) + ).toEqual({ + "matrix.stats.bytesSent.4aa92608-04c6-428e-8312-93e17602a959": 132093, + "matrix.stats.bytesSent.a08e4237-ee30-4015-a932-b676aec894b1": 913448, + }); + }); + }); +}); From dd67a45671d2d318f5bd77d494039a394f34e477 Mon Sep 17 00:00:00 2001 From: Enrico Schwendig Date: Fri, 31 Mar 2023 14:57:56 +0200 Subject: [PATCH 02/10] stats: Add summery report --- config/otel_dev/collector-gateway.yaml | 2 +- package.json | 2 +- src/otel/OTelGroupCallMembership.ts | 56 ++++++++++++++++---------- src/otel/ObjectFlattener.ts | 14 +++++++ src/room/useGroupCall.ts | 21 ++++++++-- yarn.lock | 31 +++++++------- 6 files changed, 82 insertions(+), 44 deletions(-) diff --git a/config/otel_dev/collector-gateway.yaml b/config/otel_dev/collector-gateway.yaml index 9c1a9cd0..f9e3b90f 100644 --- a/config/otel_dev/collector-gateway.yaml +++ b/config/otel_dev/collector-gateway.yaml @@ -8,7 +8,7 @@ receivers: # This can't be '*' because opentelemetry-js uses sendBeacon which always operates # in 'withCredentials' mode, which browsers don't allow with an allow-origin of '*' #- "https://pr976--element-call.netlify.app" - - "https://*" + - "http://*" allowed_headers: - "*" processors: diff --git a/package.json b/package.json index f7fc0e19..2bec81e3 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "i18next-browser-languagedetector": "^6.1.8", "i18next-http-backend": "^1.4.4", "lodash": "^4.17.21", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#042f2ed76c501c10dde98a31732fd92d862e2187", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#6339dc8cae35e501032666da58b842793ae94fad", "matrix-widget-api": "^1.3.1", "mermaid": "^8.13.8", "normalize.css": "^8.0.1", diff --git a/src/otel/OTelGroupCallMembership.ts b/src/otel/OTelGroupCallMembership.ts index 26852096..2c9a0163 100644 --- a/src/otel/OTelGroupCallMembership.ts +++ b/src/otel/OTelGroupCallMembership.ts @@ -37,10 +37,10 @@ import { import { ConnectionStatsReport, ByteSentStatsReport, + SummeryStatsReport, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; import { setSpan } from "@opentelemetry/api/build/esm/trace/context-utils"; - import { ElementCallOpenTelemetry } from "./otel"; import { ObjectFlattener } from "./ObjectFlattener"; @@ -117,7 +117,7 @@ export class OTelGroupCallMembership { this.myMember = myMember; } } - this.myDeviceId = client.getDeviceId(); + this.myDeviceId = client.getDeviceId() || "unknown"; this.statsReportSpan = { span: undefined, stats: [] }; this.groupCall.on(GroupCallEvent.CallsChanged, this.onCallsChanged); } @@ -317,55 +317,68 @@ export class OTelGroupCallMembership { }); } - public onConnectionStatsReport( - statsReport: GroupCallStatsReport + statsReport: GroupCallStatsReport ) { const type = OTelStatsReportType.ConnectionStatsReport; const data = - ObjectFlattener.flattenConnectionStatsReportObject(statsReport); + ObjectFlattener.flattenConnectionStatsReportObject(statsReport); this.buildStatsEventSpan({ type, data }); } public onByteSentStatsReport( - statsReport: GroupCallStatsReport + statsReport: GroupCallStatsReport ) { const type = OTelStatsReportType.ByteSentStatsReport; const data = ObjectFlattener.flattenByteSentStatsReportObject(statsReport); this.buildStatsEventSpan({ type, data }); } + public onSummeryStatsReport( + statsReport: GroupCallStatsReport + ) { + const type = OTelStatsReportType.SummeryStatsReport; + const data = ObjectFlattener.flattenSummeryStatsReportObject(statsReport); + this.buildStatsEventSpan({ type, data }); + } + private buildStatsEventSpan(event: OTelStatsReportEvent): void { + // @ TODO: fix this - Because on multiple calls we receive multiple stats report spans. + // This could be break if stats arrived in same time from different call objects. if (this.statsReportSpan.span === undefined && this.callMembershipSpan) { const ctx = setSpan( - opentelemetry.context.active(), - this.callMembershipSpan + opentelemetry.context.active(), + this.callMembershipSpan ); this.statsReportSpan.span = - ElementCallOpenTelemetry.instance.tracer.startSpan( - "matrix.groupCallMembership.statsReport", - undefined, - ctx - ); + ElementCallOpenTelemetry.instance.tracer.startSpan( + "matrix.groupCallMembership.statsReport", + undefined, + ctx + ); this.statsReportSpan.span.setAttribute( - "matrix.confId", - this.groupCall.groupCallId + "matrix.confId", + this.groupCall.groupCallId ); this.statsReportSpan.span.setAttribute("matrix.userId", this.myUserId); this.statsReportSpan.span.setAttribute( - "matrix.displayName", - this.myMember ? this.myMember.name : "unknown-name" + "matrix.displayName", + this.myMember ? this.myMember.name : "unknown-name" ); this.statsReportSpan.span.addEvent(event.type, event.data); this.statsReportSpan.stats.push(event); } else if ( - this.statsReportSpan.span !== undefined && - this.callMembershipSpan + this.statsReportSpan.span !== undefined && + this.callMembershipSpan ) { this.statsReportSpan.span.addEvent(event.type, event.data); - this.statsReportSpan.span.end(); - this.statsReportSpan = { span: undefined, stats: [] }; + this.statsReportSpan.stats.push(event); + // if received all three types of stats close this + if (this.statsReportSpan.stats.length === 3) { + this.statsReportSpan.span.end(); + this.statsReportSpan = { span: undefined, stats: [] }; + } } } } @@ -378,4 +391,5 @@ interface OTelStatsReportEvent { enum OTelStatsReportType { ConnectionStatsReport = "matrix.stats.connection", ByteSentStatsReport = "matrix.stats.byteSent", + SummeryStatsReport = "matrix.stats.summery", } diff --git a/src/otel/ObjectFlattener.ts b/src/otel/ObjectFlattener.ts index d45360cd..c332aba0 100644 --- a/src/otel/ObjectFlattener.ts +++ b/src/otel/ObjectFlattener.ts @@ -18,6 +18,7 @@ import { GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall"; import { ByteSentStatsReport, ConnectionStatsReport, + SummeryStatsReport, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; export class ObjectFlattener { @@ -47,6 +48,19 @@ export class ObjectFlattener { return flatObject; } + static flattenSummeryStatsReportObject( + statsReport: GroupCallStatsReport + ) { + const flatObject = {}; + ObjectFlattener.flattenObjectRecursive( + statsReport.report, + flatObject, + "matrix.stats.summery.", + 0 + ); + return flatObject; + } + public static flattenObjectRecursive( obj: Object, flatObject: Attributes, diff --git a/src/room/useGroupCall.ts b/src/room/useGroupCall.ts index 030219b9..52fc31bb 100644 --- a/src/room/useGroupCall.ts +++ b/src/room/useGroupCall.ts @@ -33,6 +33,7 @@ import { MatrixClient } from "matrix-js-sdk"; import { ByteSentStatsReport, ConnectionStatsReport, + SummeryStatsReport, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; import { usePageUnload } from "./usePageUnload"; @@ -355,6 +356,12 @@ export function useGroupCall( groupCallOTelMembership?.onByteSentStatsReport(report); } + function onSummeryStatsReport( + report: GroupCallStatsReport + ): void { + groupCallOTelMembership?.onSummeryStatsReport(report); + } + groupCall.on(GroupCallEvent.GroupCallStateChanged, onGroupCallStateChanged); groupCall.on(GroupCallEvent.UserMediaFeedsChanged, onUserMediaFeedsChanged); groupCall.on( @@ -381,6 +388,8 @@ export function useGroupCall( onByteSentStatsReport ); + groupCall.on(GroupCallStatsReportEvent.SummeryStats, onSummeryStatsReport); + updateState({ error: null, state: groupCall.state, @@ -428,12 +437,16 @@ export function useGroupCall( ); groupCall.removeListener(GroupCallEvent.Error, onError); groupCall.removeListener( - GroupCallStatsReportEvent.ConnectionStats, - onConnectionStatsReport + GroupCallStatsReportEvent.ConnectionStats, + onConnectionStatsReport ); groupCall.removeListener( - GroupCallStatsReportEvent.ByteSentStats, - onByteSentStatsReport + GroupCallStatsReportEvent.ByteSentStats, + onByteSentStatsReport + ); + groupCall.removeListener( + GroupCallStatsReportEvent.SummeryStats, + onSummeryStatsReport ); leaveCall(); }; diff --git a/yarn.lock b/yarn.lock index e25096f8..66c1dc99 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1822,9 +1822,9 @@ integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw== "@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.5": - version "0.1.0-alpha.5" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.5.tgz#60ede2c43b9d808ba8cf46085a3b347b290d9658" - integrity sha512-2KjAgWNGfuGLNjJwsrs6gGX157vmcTfNrA4u249utgnMPbJl7QwuUqh1bGxQ0PpK06yvZjgPlkna0lTbuwtuQw== + version "0.1.0-alpha.6" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.6.tgz#c0bdb9ab0d30179b8ef744d1b4010b0ad0ab9c3a" + integrity sha512-7hMffzw7KijxDyyH/eUyTfrLeCQHuyU3kaPOKGhcl3DZ3vx7bCncqjGMGTnxNPoP23I6gosvKSbO+3wYOT24Xg== "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz": version "3.2.14" @@ -5726,7 +5726,12 @@ content-disposition@0.5.4: dependencies: safe-buffer "5.2.1" -content-type@^1.0.4, content-type@~1.0.4: +content-type@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== @@ -10413,9 +10418,9 @@ log-symbols@^4.1.0: is-unicode-supported "^0.1.0" loglevel@^1.7.1: - version "1.8.0" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114" - integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA== + version "1.8.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.1.tgz#5c621f83d5b48c54ae93b6156353f555963377b4" + integrity sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg== long@^2.4.0: version "2.4.0" @@ -10545,9 +10550,9 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#042f2ed76c501c10dde98a31732fd92d862e2187": +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#6339dc8cae35e501032666da58b842793ae94fad": version "24.0.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/042f2ed76c501c10dde98a31732fd92d862e2187" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/6339dc8cae35e501032666da58b842793ae94fad" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.5" @@ -10570,14 +10575,6 @@ matrix-widget-api@^1.3.1: "@types/events" "^3.0.0" events "^3.2.0" -matrix-widget-api@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.3.1.tgz#e38f404c76bb15c113909505c1c1a5b4d781c2f5" - integrity sha512-+rN6vGvnXm+fn0uq9r2KWSL/aPtehD6ObC50jYmUcEfgo8CUpf9eUurmjbRlwZkWq3XHXFuKQBUCI9UzqWg37Q== - dependencies: - "@types/events" "^3.0.0" - events "^3.2.0" - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" From 47e0ca2eda0c4616ed777b498b50b4b2fee2a6cf Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 31 Mar 2023 14:27:50 +0100 Subject: [PATCH 03/10] Put cors header back to https for now To remove that change for the diff --- config/otel_dev/collector-gateway.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/otel_dev/collector-gateway.yaml b/config/otel_dev/collector-gateway.yaml index f9e3b90f..9c1a9cd0 100644 --- a/config/otel_dev/collector-gateway.yaml +++ b/config/otel_dev/collector-gateway.yaml @@ -8,7 +8,7 @@ receivers: # This can't be '*' because opentelemetry-js uses sendBeacon which always operates # in 'withCredentials' mode, which browsers don't allow with an allow-origin of '*' #- "https://pr976--element-call.netlify.app" - - "http://*" + - "https://*" allowed_headers: - "*" processors: From e18c69ec89f60470413f2daf9fc2d08c18960f6b Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 31 Mar 2023 14:29:07 +0100 Subject: [PATCH 04/10] Use latest js-sdk develop --- package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2bec81e3..2e4fa35c 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "i18next-browser-languagedetector": "^6.1.8", "i18next-http-backend": "^1.4.4", "lodash": "^4.17.21", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#6339dc8cae35e501032666da58b842793ae94fad", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#d1cf98b1770d0282224e80bc9303609f6c156d3a", "matrix-widget-api": "^1.3.1", "mermaid": "^8.13.8", "normalize.css": "^8.0.1", diff --git a/yarn.lock b/yarn.lock index 66c1dc99..595cae5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10550,9 +10550,9 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#6339dc8cae35e501032666da58b842793ae94fad": +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#d1cf98b1770d0282224e80bc9303609f6c156d3a": version "24.0.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/6339dc8cae35e501032666da58b842793ae94fad" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/d1cf98b1770d0282224e80bc9303609f6c156d3a" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.5" From cb0ba6d8277506675f9a641f29b0b8c130f5b8d6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 31 Mar 2023 14:30:24 +0100 Subject: [PATCH 05/10] Add missed 'r' --- test/otel/{ObjectFlattene-test.ts => ObjectFlattener-test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/otel/{ObjectFlattene-test.ts => ObjectFlattener-test.ts} (100%) diff --git a/test/otel/ObjectFlattene-test.ts b/test/otel/ObjectFlattener-test.ts similarity index 100% rename from test/otel/ObjectFlattene-test.ts rename to test/otel/ObjectFlattener-test.ts From 889a31489bfb5afa878e57e1f1939d88f76641b1 Mon Sep 17 00:00:00 2001 From: Enrico Schwendig Date: Mon, 3 Apr 2023 12:37:55 +0200 Subject: [PATCH 06/10] stats: fix typo --- package.json | 2 +- src/otel/OTelGroupCallMembership.ts | 12 ++++++------ src/otel/ObjectFlattener.ts | 8 ++++---- src/room/useGroupCall.ts | 14 +++++++------- yarn.lock | 4 ++-- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 2e4fa35c..86a666f7 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "i18next-browser-languagedetector": "^6.1.8", "i18next-http-backend": "^1.4.4", "lodash": "^4.17.21", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#d1cf98b1770d0282224e80bc9303609f6c156d3a", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#fe79a6fa7ca50fc7d078e11826b5539bb0822c45", "matrix-widget-api": "^1.3.1", "mermaid": "^8.13.8", "normalize.css": "^8.0.1", diff --git a/src/otel/OTelGroupCallMembership.ts b/src/otel/OTelGroupCallMembership.ts index 2c9a0163..78f171f0 100644 --- a/src/otel/OTelGroupCallMembership.ts +++ b/src/otel/OTelGroupCallMembership.ts @@ -37,7 +37,7 @@ import { import { ConnectionStatsReport, ByteSentStatsReport, - SummeryStatsReport, + SummaryStatsReport, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; import { setSpan } from "@opentelemetry/api/build/esm/trace/context-utils"; @@ -334,11 +334,11 @@ export class OTelGroupCallMembership { this.buildStatsEventSpan({ type, data }); } - public onSummeryStatsReport( - statsReport: GroupCallStatsReport + public onSummaryStatsReport( + statsReport: GroupCallStatsReport ) { - const type = OTelStatsReportType.SummeryStatsReport; - const data = ObjectFlattener.flattenSummeryStatsReportObject(statsReport); + const type = OTelStatsReportType.SummaryStatsReport; + const data = ObjectFlattener.flattenSummaryStatsReportObject(statsReport); this.buildStatsEventSpan({ type, data }); } @@ -391,5 +391,5 @@ interface OTelStatsReportEvent { enum OTelStatsReportType { ConnectionStatsReport = "matrix.stats.connection", ByteSentStatsReport = "matrix.stats.byteSent", - SummeryStatsReport = "matrix.stats.summery", + SummaryStatsReport = "matrix.stats.summary", } diff --git a/src/otel/ObjectFlattener.ts b/src/otel/ObjectFlattener.ts index c332aba0..dcda0783 100644 --- a/src/otel/ObjectFlattener.ts +++ b/src/otel/ObjectFlattener.ts @@ -18,7 +18,7 @@ import { GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall"; import { ByteSentStatsReport, ConnectionStatsReport, - SummeryStatsReport, + SummaryStatsReport, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; export class ObjectFlattener { @@ -48,14 +48,14 @@ export class ObjectFlattener { return flatObject; } - static flattenSummeryStatsReportObject( - statsReport: GroupCallStatsReport + static flattenSummaryStatsReportObject( + statsReport: GroupCallStatsReport ) { const flatObject = {}; ObjectFlattener.flattenObjectRecursive( statsReport.report, flatObject, - "matrix.stats.summery.", + "matrix.stats.summary.", 0 ); return flatObject; diff --git a/src/room/useGroupCall.ts b/src/room/useGroupCall.ts index 52fc31bb..48b8d8f1 100644 --- a/src/room/useGroupCall.ts +++ b/src/room/useGroupCall.ts @@ -33,7 +33,7 @@ import { MatrixClient } from "matrix-js-sdk"; import { ByteSentStatsReport, ConnectionStatsReport, - SummeryStatsReport, + SummaryStatsReport, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; import { usePageUnload } from "./usePageUnload"; @@ -356,10 +356,10 @@ export function useGroupCall( groupCallOTelMembership?.onByteSentStatsReport(report); } - function onSummeryStatsReport( - report: GroupCallStatsReport + function onSummaryStatsReport( + report: GroupCallStatsReport ): void { - groupCallOTelMembership?.onSummeryStatsReport(report); + groupCallOTelMembership?.onSummaryStatsReport(report); } groupCall.on(GroupCallEvent.GroupCallStateChanged, onGroupCallStateChanged); @@ -388,7 +388,7 @@ export function useGroupCall( onByteSentStatsReport ); - groupCall.on(GroupCallStatsReportEvent.SummeryStats, onSummeryStatsReport); + groupCall.on(GroupCallStatsReportEvent.SummaryStats, onSummaryStatsReport); updateState({ error: null, @@ -445,8 +445,8 @@ export function useGroupCall( onByteSentStatsReport ); groupCall.removeListener( - GroupCallStatsReportEvent.SummeryStats, - onSummeryStatsReport + GroupCallStatsReportEvent.SummaryStats, + onSummaryStatsReport ); leaveCall(); }; diff --git a/yarn.lock b/yarn.lock index 595cae5a..d98a26fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10550,9 +10550,9 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#d1cf98b1770d0282224e80bc9303609f6c156d3a": +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#fe79a6fa7ca50fc7d078e11826b5539bb0822c45": version "24.0.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/d1cf98b1770d0282224e80bc9303609f6c156d3a" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/fe79a6fa7ca50fc7d078e11826b5539bb0822c45" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.5" From 3b06258e40c7e83725520627b474874cc315ada5 Mon Sep 17 00:00:00 2001 From: Enrico Schwendig Date: Mon, 3 Apr 2023 14:07:29 +0200 Subject: [PATCH 07/10] stats: rename enum to avoid shadow values --- src/otel/OTelGroupCallMembership.ts | 12 ++++++------ src/otel/otel.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/otel/OTelGroupCallMembership.ts b/src/otel/OTelGroupCallMembership.ts index 78f171f0..45d5bfdf 100644 --- a/src/otel/OTelGroupCallMembership.ts +++ b/src/otel/OTelGroupCallMembership.ts @@ -320,7 +320,7 @@ export class OTelGroupCallMembership { public onConnectionStatsReport( statsReport: GroupCallStatsReport ) { - const type = OTelStatsReportType.ConnectionStatsReport; + const type = OTelStatsReportType.ConnectionReport; const data = ObjectFlattener.flattenConnectionStatsReportObject(statsReport); this.buildStatsEventSpan({ type, data }); @@ -329,7 +329,7 @@ export class OTelGroupCallMembership { public onByteSentStatsReport( statsReport: GroupCallStatsReport ) { - const type = OTelStatsReportType.ByteSentStatsReport; + const type = OTelStatsReportType.ByteSentReport; const data = ObjectFlattener.flattenByteSentStatsReportObject(statsReport); this.buildStatsEventSpan({ type, data }); } @@ -337,7 +337,7 @@ export class OTelGroupCallMembership { public onSummaryStatsReport( statsReport: GroupCallStatsReport ) { - const type = OTelStatsReportType.SummaryStatsReport; + const type = OTelStatsReportType.SummaryReport; const data = ObjectFlattener.flattenSummaryStatsReportObject(statsReport); this.buildStatsEventSpan({ type, data }); } @@ -389,7 +389,7 @@ interface OTelStatsReportEvent { } enum OTelStatsReportType { - ConnectionStatsReport = "matrix.stats.connection", - ByteSentStatsReport = "matrix.stats.byteSent", - SummaryStatsReport = "matrix.stats.summary", + ConnectionReport = "matrix.stats.connection", + ByteSentReport = "matrix.stats.byteSent", + SummaryReport = "matrix.stats.summary", } diff --git a/src/otel/otel.ts b/src/otel/otel.ts index eac7ce46..d079da41 100644 --- a/src/otel/otel.ts +++ b/src/otel/otel.ts @@ -48,7 +48,7 @@ export class ElementCallOpenTelemetry { return sharedInstance; } - constructor(collectorUrl: string) { + constructor(collectorUrl: string | undefined) { const otlpExporter = new OTLPTraceExporter({ url: collectorUrl, }); From c824ea6f9a229d28bded3929fdeb8763323d4cc2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 4 Apr 2023 18:00:45 +0100 Subject: [PATCH 08/10] Add OpenTelemetry events for PeerConnection state changes / errors Creates a new class to represent individual calls and adds the listeners there. Requires https://github.com/matrix-org/matrix-js-sdk/pull/3251 Based on https://github.com/vector-im/element-call/pull/974 --- src/otel/OTelCall.ts | 103 ++++++++++++++++++++++++++++ src/otel/OTelGroupCallMembership.ts | 20 ++---- 2 files changed, 109 insertions(+), 14 deletions(-) create mode 100644 src/otel/OTelCall.ts diff --git a/src/otel/OTelCall.ts b/src/otel/OTelCall.ts new file mode 100644 index 00000000..3717abfd --- /dev/null +++ b/src/otel/OTelCall.ts @@ -0,0 +1,103 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { Span } from "@opentelemetry/api"; +import { MatrixCall } from "matrix-js-sdk"; +import { CallEvent } from "matrix-js-sdk/src/webrtc/call"; + +import { ObjectFlattener } from "./ObjectFlattener"; + +/** + * Tracks an individual call within a group call, either to a full-mesh peer or a focus + */ +export class OTelCall { + constructor( + public userId: string, + public deviceId: string, + public call: MatrixCall, + public span: Span + ) { + if (call.peerConn) { + this.addCallPeerConnListeners(); + } else { + this.call.once( + CallEvent.PeerConnectionCreated, + this.addCallPeerConnListeners + ); + } + } + + public dispose() { + this.call.peerConn.removeEventListener( + "iceconnectionstatechange", + this.onIceConnectionStateChanged + ); + } + + private addCallPeerConnListeners = (): void => { + this.call.peerConn.addEventListener( + "connectionstatechange", + this.onCallConnectionStateChanged + ); + this.call.peerConn.addEventListener( + "signalingstatechange", + this.onCallSignalingStateChanged + ); + this.call.peerConn.addEventListener( + "iceconnectionstatechange", + this.onIceConnectionStateChanged + ); + this.call.peerConn.addEventListener( + "icegatheringstatechange", + this.onIceGatheringStateChanged + ); + this.call.peerConn.addEventListener( + "icecandidateerror", + this.onIceCandidateError + ); + }; + + public onCallConnectionStateChanged = (): void => { + this.span.addEvent("matrix.call.callConnectionStateChange", { + callConnectionState: this.call.peerConn.connectionState, + }); + }; + + public onCallSignalingStateChanged = (): void => { + this.span.addEvent("matrix.call.callSignalingStateChange", { + callSignalingState: this.call.peerConn.signalingState, + }); + }; + + public onIceConnectionStateChanged = (): void => { + this.span.addEvent("matrix.call.iceConnectionStateChange", { + iceConnectionState: this.call.peerConn.iceConnectionState, + }); + }; + + public onIceGatheringStateChanged = (): void => { + this.span.addEvent("matrix.call.iceGatheringStateChange", { + iceGatheringState: this.call.peerConn.iceGatheringState, + }); + }; + + public onIceCandidateError = (ev: Event): void => { + const flatObject = {}; + ObjectFlattener.flattenObjectRecursive(ev, flatObject, "error.", 0); + + this.span.addEvent("matrix.call.iceCandidateError", flatObject); + }; +} diff --git a/src/otel/OTelGroupCallMembership.ts b/src/otel/OTelGroupCallMembership.ts index 45d5bfdf..6d3d3b75 100644 --- a/src/otel/OTelGroupCallMembership.ts +++ b/src/otel/OTelGroupCallMembership.ts @@ -43,6 +43,7 @@ import { setSpan } from "@opentelemetry/api/build/esm/trace/context-utils"; import { ElementCallOpenTelemetry } from "./otel"; import { ObjectFlattener } from "./ObjectFlattener"; +import { OTelCall } from "./OTelCall"; /** * Flattens out an object into a single layer with components @@ -86,13 +87,6 @@ function flattenVoipEventRecursive( } } -interface CallTrackingInfo { - userId: string; - deviceId: string; - call: MatrixCall; - span: Span; -} - /** * Represent the span of time which we intend to be joined to a group call */ @@ -102,7 +96,7 @@ export class OTelGroupCallMembership { private myUserId = "unknown"; private myDeviceId: string; private myMember?: RoomMember; - private callsByCallId = new Map(); + private callsByCallId = new Map(); private statsReportSpan: { span: Span | undefined; stats: OTelStatsReportEvent[]; @@ -188,12 +182,10 @@ export class OTelGroupCallMembership { // XXX: anonymity span.setAttribute("matrix.call.target.userId", userId); span.setAttribute("matrix.call.target.deviceId", deviceId); - this.callsByCallId.set(call.callId, { - userId, - deviceId, - call, - span, - }); + this.callsByCallId.set( + call.callId, + new OTelCall(userId, deviceId, call, span) + ); } } } From 2435846f66a8abf9a9e7273334865f86107abf04 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 5 Apr 2023 10:00:16 +0100 Subject: [PATCH 09/10] Latest js-sdk develop (with required PR merged) --- package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 86a666f7..a8e5bfc5 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "i18next-browser-languagedetector": "^6.1.8", "i18next-http-backend": "^1.4.4", "lodash": "^4.17.21", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#fe79a6fa7ca50fc7d078e11826b5539bb0822c45", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#e89467c9fbf98182def2088a947155f30fdc7d1f", "matrix-widget-api": "^1.3.1", "mermaid": "^8.13.8", "normalize.css": "^8.0.1", diff --git a/yarn.lock b/yarn.lock index d98a26fe..84a1c952 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10550,9 +10550,9 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#fe79a6fa7ca50fc7d078e11826b5539bb0822c45": +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#e89467c9fbf98182def2088a947155f30fdc7d1f": version "24.0.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/fe79a6fa7ca50fc7d078e11826b5539bb0822c45" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/e89467c9fbf98182def2088a947155f30fdc7d1f" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.5" From b061cbfb2f528943dd0b0a85e92b4ea05f20ea54 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 5 Apr 2023 10:01:58 +0100 Subject: [PATCH 10/10] Remove the other listeners --- src/otel/OTelCall.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/otel/OTelCall.ts b/src/otel/OTelCall.ts index 3717abfd..79cc38d5 100644 --- a/src/otel/OTelCall.ts +++ b/src/otel/OTelCall.ts @@ -41,10 +41,26 @@ export class OTelCall { } public dispose() { + this.call.peerConn.removeEventListener( + "connectionstatechange", + this.onCallConnectionStateChanged + ); + this.call.peerConn.removeEventListener( + "signalingstatechange", + this.onCallSignalingStateChanged + ); this.call.peerConn.removeEventListener( "iceconnectionstatechange", this.onIceConnectionStateChanged ); + this.call.peerConn.removeEventListener( + "icegatheringstatechange", + this.onIceGatheringStateChanged + ); + this.call.peerConn.removeEventListener( + "icecandidateerror", + this.onIceCandidateError + ); } private addCallPeerConnListeners = (): void => {