From f0a6f5919e421fff88c7795fb8d6d3a4e21f4119 Mon Sep 17 00:00:00 2001 From: Enrico Schwendig Date: Tue, 6 Jun 2023 08:28:53 +0200 Subject: [PATCH] move webrtc etc. events from groupCall to matrix.call span (#1080) * add new linked span for connection stats * move stats span under call span and add user attribute * Update matrix-js-sdk --- package.json | 2 +- src/otel/OTelGroupCallMembership.ts | 111 ++++++++------- src/otel/ObjectFlattener.ts | 12 +- test/otel/ObjectFlattener-test.ts | 205 ++++++++++++++++------------ yarn.lock | 4 +- 5 files changed, 184 insertions(+), 150 deletions(-) diff --git a/package.json b/package.json index 8d9773bb..0d5325b5 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#a7b1dcaf9514b2e424a387e266c6f383a5909927", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#e70a1a1effe59e6754f9a10cc2df8eef81638c7d", "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 60ae2b34..a8d93b6a 100644 --- a/src/otel/OTelGroupCallMembership.ts +++ b/src/otel/OTelGroupCallMembership.ts @@ -333,22 +333,70 @@ export class OTelGroupCallMembership { public onConnectionStatsReport( statsReport: GroupCallStatsReport ) { - if (!ElementCallOpenTelemetry.instance) return; - - const type = OTelStatsReportType.ConnectionReport; - const data = - ObjectFlattener.flattenConnectionStatsReportObject(statsReport); - this.buildStatsEventSpan({ type, data }); + this.buildCallStatsSpan( + OTelStatsReportType.ConnectionReport, + statsReport.report + ); } public onByteSentStatsReport( statsReport: GroupCallStatsReport ) { - if (!ElementCallOpenTelemetry.instance) return; + this.buildCallStatsSpan( + OTelStatsReportType.ByteSentReport, + statsReport.report + ); + } - const type = OTelStatsReportType.ByteSentReport; - const data = ObjectFlattener.flattenByteSentStatsReportObject(statsReport); - this.buildStatsEventSpan({ type, data }); + public buildCallStatsSpan( + type: OTelStatsReportType, + report: ByteSentStatsReport | ConnectionStatsReport + ): void { + if (!ElementCallOpenTelemetry.instance) return; + let call: OTelCall | undefined; + const callId = report?.callId; + + if (callId) { + call = this.callsByCallId.get(callId); + } + + if (!call) { + this.callMembershipSpan?.addEvent(type + "_unknown_callid", { + "call.callId": callId, + "call.opponentMemberId": report.opponentMemberId + ? report.opponentMemberId + : "unknown", + }); + logger.error(`Received ${type} with unknown call ID: ${callId}`); + return; + } + const data = ObjectFlattener.flattenReportObject(type, report); + const ctx = opentelemetry.trace.setSpan( + opentelemetry.context.active(), + call.span + ); + + const options = { + links: [ + { + context: call.span.spanContext(), + }, + ], + }; + + const span = ElementCallOpenTelemetry.instance.tracer.startSpan( + type, + options, + ctx + ); + + span.setAttribute("matrix.callId", callId); + span.setAttribute( + "matrix.opponentMemberId", + report.opponentMemberId ? report.opponentMemberId : "unknown" + ); + span.addEvent("matrix.call.connection_stats_event", data); + span.end(); } public onSummaryStatsReport( @@ -381,45 +429,6 @@ export class OTelGroupCallMembership { span.end(); } } - - 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 - ); - this.statsReportSpan.span = - ElementCallOpenTelemetry.instance?.tracer.startSpan( - "matrix.groupCallMembership.statsReport", - undefined, - ctx - ); - if (this.statsReportSpan.span === undefined) { - return; - } - 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 { @@ -428,7 +437,7 @@ interface OTelStatsReportEvent { } enum OTelStatsReportType { - ConnectionReport = "matrix.stats.connection", - ByteSentReport = "matrix.stats.byteSent", + ConnectionReport = "matrix.call.stats.connection", + ByteSentReport = "matrix.call.stats.byteSent", SummaryReport = "matrix.stats.summary", } diff --git a/src/otel/ObjectFlattener.ts b/src/otel/ObjectFlattener.ts index 321c45ee..652f9056 100644 --- a/src/otel/ObjectFlattener.ts +++ b/src/otel/ObjectFlattener.ts @@ -23,16 +23,12 @@ import { } from "matrix-js-sdk/src/webrtc/stats/statsReport"; export class ObjectFlattener { - public static flattenConnectionStatsReportObject( - statsReport: GroupCallStatsReport + public static flattenReportObject( + prefix: string, + report: ConnectionStatsReport | ByteSentStatsReport ): Attributes { const flatObject = {}; - ObjectFlattener.flattenObjectRecursive( - statsReport.report, - flatObject, - "matrix.stats.conn.", - 0 - ); + ObjectFlattener.flattenObjectRecursive(report, flatObject, `${prefix}.`, 0); return flatObject; } diff --git a/test/otel/ObjectFlattener-test.ts b/test/otel/ObjectFlattener-test.ts index e3709d20..b2fdce37 100644 --- a/test/otel/ObjectFlattener-test.ts +++ b/test/otel/ObjectFlattener-test.ts @@ -1,6 +1,7 @@ import { GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall"; import { AudioConcealment, + ByteSentStatsReport, ConnectionStatsReport, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; import { ObjectFlattener } from "../../src/otel/ObjectFlattener"; @@ -28,6 +29,8 @@ describe("ObjectFlattener", () => { const statsReport: GroupCallStatsReport = { report: { + callId: "callId", + opponentMemberId: "opponentMemberId", bandwidth: { upload: 426, download: 0 }, bitrate: { upload: 426, @@ -116,21 +119,25 @@ describe("ObjectFlattener", () => { ObjectFlattener.flattenObjectRecursive( statsReport.report.resolution, flatObject, - "matrix.stats.conn.resolution.", + "matrix.call.stats.connection.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.call.stats.connection.resolution.local.LOCAL_AUDIO_TRACK_ID.height": + -1, + "matrix.call.stats.connection.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.call.stats.connection.resolution.local.LOCAL_VIDEO_TRACK_ID.height": 460, + "matrix.call.stats.connection.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.call.stats.connection.resolution.remote.REMOTE_AUDIO_TRACK_ID.height": + -1, + "matrix.call.stats.connection.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.call.stats.connection.resolution.remote.REMOTE_VIDEO_TRACK_ID.height": 960, + "matrix.call.stats.connection.resolution.remote.REMOTE_VIDEO_TRACK_ID.width": 1080, }); }); it("should flatter an Array object", () => { @@ -138,106 +145,128 @@ describe("ObjectFlattener", () => { ObjectFlattener.flattenObjectRecursive( statsReport.report.transport, flatObject, - "matrix.stats.conn.transport.", + "matrix.call.stats.connection.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": + "matrix.call.stats.connection.transport.0.ip": + "ff11::5fa:abcd:999c:c5c5:50000", + "matrix.call.stats.connection.transport.0.type": "udp", + "matrix.call.stats.connection.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", + "matrix.call.stats.connection.transport.0.isFocus": true, + "matrix.call.stats.connection.transport.0.localCandidateType": "host", + "matrix.call.stats.connection.transport.0.remoteCandidateType": "host", + "matrix.call.stats.connection.transport.0.networkType": "ethernet", + "matrix.call.stats.connection.transport.0.rtt": "NaN", + "matrix.call.stats.connection.transport.1.ip": "10.10.10.2:22222", + "matrix.call.stats.connection.transport.1.type": "tcp", + "matrix.call.stats.connection.transport.1.localIp": + "10.10.10.100:33333", + "matrix.call.stats.connection.transport.1.isFocus": true, + "matrix.call.stats.connection.transport.1.localCandidateType": "srfx", + "matrix.call.stats.connection.transport.1.remoteCandidateType": "srfx", + "matrix.call.stats.connection.transport.1.networkType": "ethernet", + "matrix.call.stats.connection.transport.1.rtt": "null", }); }); }); - describe("on flattenConnectionStatsReportObject", () => { + describe("on flattenReportObject Connection Stats", () => { it("should flatten a Report to otel Attributes Object", () => { expect( - ObjectFlattener.flattenConnectionStatsReportObject(statsReport) + ObjectFlattener.flattenReportObject( + "matrix.call.stats.connection", + statsReport.report + ) ).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.jitter.REMOTE_AUDIO_TRACK_ID": 2, - "matrix.stats.conn.jitter.REMOTE_VIDEO_TRACK_ID": 50, - "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": + "matrix.call.stats.connection.callId": "callId", + "matrix.call.stats.connection.opponentMemberId": "opponentMemberId", + "matrix.call.stats.connection.bandwidth.download": 0, + "matrix.call.stats.connection.bandwidth.upload": 426, + "matrix.call.stats.connection.bitrate.audio.download": 0, + "matrix.call.stats.connection.bitrate.audio.upload": 124, + "matrix.call.stats.connection.bitrate.download": 0, + "matrix.call.stats.connection.bitrate.upload": 426, + "matrix.call.stats.connection.bitrate.video.download": 0, + "matrix.call.stats.connection.bitrate.video.upload": 302, + "matrix.call.stats.connection.codec.local.LOCAL_AUDIO_TRACK_ID": "opus", + "matrix.call.stats.connection.codec.local.LOCAL_VIDEO_TRACK_ID": "v8", + "matrix.call.stats.connection.codec.remote.REMOTE_AUDIO_TRACK_ID": + "opus", + "matrix.call.stats.connection.codec.remote.REMOTE_VIDEO_TRACK_ID": "v9", + "matrix.call.stats.connection.framerate.local.LOCAL_AUDIO_TRACK_ID": 0, + "matrix.call.stats.connection.framerate.local.LOCAL_VIDEO_TRACK_ID": 30, + "matrix.call.stats.connection.framerate.remote.REMOTE_AUDIO_TRACK_ID": 0, + "matrix.call.stats.connection.framerate.remote.REMOTE_VIDEO_TRACK_ID": 60, + "matrix.call.stats.connection.jitter.REMOTE_AUDIO_TRACK_ID": 2, + "matrix.call.stats.connection.jitter.REMOTE_VIDEO_TRACK_ID": 50, + "matrix.call.stats.connection.packetLoss.download": 0, + "matrix.call.stats.connection.packetLoss.total": 0, + "matrix.call.stats.connection.packetLoss.upload": 0, + "matrix.call.stats.connection.resolution.local.LOCAL_AUDIO_TRACK_ID.height": + -1, + "matrix.call.stats.connection.resolution.local.LOCAL_AUDIO_TRACK_ID.width": + -1, + "matrix.call.stats.connection.resolution.local.LOCAL_VIDEO_TRACK_ID.height": 460, + "matrix.call.stats.connection.resolution.local.LOCAL_VIDEO_TRACK_ID.width": 780, + "matrix.call.stats.connection.resolution.remote.REMOTE_AUDIO_TRACK_ID.height": + -1, + "matrix.call.stats.connection.resolution.remote.REMOTE_AUDIO_TRACK_ID.width": + -1, + "matrix.call.stats.connection.resolution.remote.REMOTE_VIDEO_TRACK_ID.height": 960, + "matrix.call.stats.connection.resolution.remote.REMOTE_VIDEO_TRACK_ID.width": 1080, + "matrix.call.stats.connection.transport.0.ip": + "ff11::5fa:abcd:999c:c5c5:50000", + "matrix.call.stats.connection.transport.0.type": "udp", + "matrix.call.stats.connection.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", - "matrix.stats.conn.audioConcealment.REMOTE_AUDIO_TRACK_ID.concealedAudio": 0, - "matrix.stats.conn.audioConcealment.REMOTE_AUDIO_TRACK_ID.totalAudioDuration": 0, - "matrix.stats.conn.audioConcealment.REMOTE_VIDEO_TRACK_ID.concealedAudio": 0, - "matrix.stats.conn.audioConcealment.REMOTE_VIDEO_TRACK_ID.totalAudioDuration": 0, - "matrix.stats.conn.totalAudioConcealment.concealedAudio": 0, - "matrix.stats.conn.totalAudioConcealment.totalAudioDuration": 0, + "matrix.call.stats.connection.transport.0.isFocus": true, + "matrix.call.stats.connection.transport.0.localCandidateType": "host", + "matrix.call.stats.connection.transport.0.remoteCandidateType": "host", + "matrix.call.stats.connection.transport.0.networkType": "ethernet", + "matrix.call.stats.connection.transport.0.rtt": "NaN", + "matrix.call.stats.connection.transport.1.ip": "10.10.10.2:22222", + "matrix.call.stats.connection.transport.1.type": "tcp", + "matrix.call.stats.connection.transport.1.localIp": + "10.10.10.100:33333", + "matrix.call.stats.connection.transport.1.isFocus": true, + "matrix.call.stats.connection.transport.1.localCandidateType": "srfx", + "matrix.call.stats.connection.transport.1.remoteCandidateType": "srfx", + "matrix.call.stats.connection.transport.1.networkType": "ethernet", + "matrix.call.stats.connection.transport.1.rtt": "null", + "matrix.call.stats.connection.audioConcealment.REMOTE_AUDIO_TRACK_ID.concealedAudio": 0, + "matrix.call.stats.connection.audioConcealment.REMOTE_AUDIO_TRACK_ID.totalAudioDuration": 0, + "matrix.call.stats.connection.audioConcealment.REMOTE_VIDEO_TRACK_ID.concealedAudio": 0, + "matrix.call.stats.connection.audioConcealment.REMOTE_VIDEO_TRACK_ID.totalAudioDuration": 0, + "matrix.call.stats.connection.totalAudioConcealment.concealedAudio": 0, + "matrix.call.stats.connection.totalAudioConcealment.totalAudioDuration": 0, }); }); }); describe("on flattenByteSendStatsReportObject", () => { - const byteSent = { - report: new Map([ - ["4aa92608-04c6-428e-8312-93e17602a959", 132093], - ["a08e4237-ee30-4015-a932-b676aec894b1", 913448], - ]), - }; + const byteSentStatsReport = new Map< + string, + number + >() as ByteSentStatsReport; + byteSentStatsReport.callId = "callId"; + byteSentStatsReport.opponentMemberId = "opponentMemberId"; + byteSentStatsReport.set("4aa92608-04c6-428e-8312-93e17602a959", 132093); + byteSentStatsReport.set("a08e4237-ee30-4015-a932-b676aec894b1", 913448); + it("should flatten a Report to otel Attributes Object", () => { expect( - ObjectFlattener.flattenByteSentStatsReportObject(byteSent) + ObjectFlattener.flattenReportObject( + "matrix.call.stats.bytesSend", + byteSentStatsReport + ) ).toEqual({ - "matrix.stats.bytesSent.4aa92608-04c6-428e-8312-93e17602a959": 132093, - "matrix.stats.bytesSent.a08e4237-ee30-4015-a932-b676aec894b1": 913448, + "matrix.call.stats.bytesSend.4aa92608-04c6-428e-8312-93e17602a959": 132093, + "matrix.call.stats.bytesSend.a08e4237-ee30-4015-a932-b676aec894b1": 913448, }); + expect(byteSentStatsReport.callId).toEqual("callId"); + expect(byteSentStatsReport.opponentMemberId).toEqual("opponentMemberId"); }); }); }); diff --git a/yarn.lock b/yarn.lock index 2e8e7b4c..c9c9a44e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10557,9 +10557,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#a7b1dcaf9514b2e424a387e266c6f383a5909927": +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#e70a1a1effe59e6754f9a10cc2df8eef81638c7d": version "25.1.1" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/a7b1dcaf9514b2e424a387e266c6f383a5909927" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/e70a1a1effe59e6754f9a10cc2df8eef81638c7d" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.9"