Add webrtc metric to OTel (#974)
* stats: Add summery report --------- Co-authored-by: David Baker <dave@matrix.org>
This commit is contained in:
		
					parent
					
						
							
								28196a2e9d
							
						
					
				
			
			
				commit
				
					
						390442a4c3
					
				
			
		
					 7 changed files with 478 additions and 26 deletions
				
			
		|  | @ -53,7 +53,7 @@ | ||||||
|     "i18next-browser-languagedetector": "^6.1.8", |     "i18next-browser-languagedetector": "^6.1.8", | ||||||
|     "i18next-http-backend": "^1.4.4", |     "i18next-http-backend": "^1.4.4", | ||||||
|     "lodash": "^4.17.21", |     "lodash": "^4.17.21", | ||||||
|     "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#042f2ed76c501c10dde98a31732fd92d862e2187", |     "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#fe79a6fa7ca50fc7d078e11826b5539bb0822c45", | ||||||
|     "matrix-widget-api": "^1.3.1", |     "matrix-widget-api": "^1.3.1", | ||||||
|     "mermaid": "^8.13.8", |     "mermaid": "^8.13.8", | ||||||
|     "normalize.css": "^8.0.1", |     "normalize.css": "^8.0.1", | ||||||
|  |  | ||||||
|  | @ -32,9 +32,17 @@ import { | ||||||
|   CallsByUserAndDevice, |   CallsByUserAndDevice, | ||||||
|   GroupCallError, |   GroupCallError, | ||||||
|   GroupCallEvent, |   GroupCallEvent, | ||||||
|  |   GroupCallStatsReport, | ||||||
| } from "matrix-js-sdk/src/webrtc/groupCall"; | } from "matrix-js-sdk/src/webrtc/groupCall"; | ||||||
|  | import { | ||||||
|  |   ConnectionStatsReport, | ||||||
|  |   ByteSentStatsReport, | ||||||
|  |   SummaryStatsReport, | ||||||
|  | } from "matrix-js-sdk/src/webrtc/stats/statsReport"; | ||||||
|  | import { setSpan } from "@opentelemetry/api/build/esm/trace/context-utils"; | ||||||
| 
 | 
 | ||||||
| import { ElementCallOpenTelemetry } from "./otel"; | import { ElementCallOpenTelemetry } from "./otel"; | ||||||
|  | import { ObjectFlattener } from "./ObjectFlattener"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Flattens out an object into a single layer with components |  * Flattens out an object into a single layer with components | ||||||
|  | @ -91,16 +99,26 @@ interface CallTrackingInfo { | ||||||
| export class OTelGroupCallMembership { | export class OTelGroupCallMembership { | ||||||
|   private callMembershipSpan?: Span; |   private callMembershipSpan?: Span; | ||||||
|   private groupCallContext?: Context; |   private groupCallContext?: Context; | ||||||
|   private myUserId: string; |   private myUserId = "unknown"; | ||||||
|   private myDeviceId: string; |   private myDeviceId: string; | ||||||
|   private myMember: RoomMember; |   private myMember?: RoomMember; | ||||||
|   private callsByCallId = new Map<string, CallTrackingInfo>(); |   private callsByCallId = new Map<string, CallTrackingInfo>(); | ||||||
|  |   private statsReportSpan: { | ||||||
|  |     span: Span | undefined; | ||||||
|  |     stats: OTelStatsReportEvent[]; | ||||||
|  |   }; | ||||||
| 
 | 
 | ||||||
|   constructor(private groupCall: GroupCall, client: MatrixClient) { |   constructor(private groupCall: GroupCall, client: MatrixClient) { | ||||||
|     this.myUserId = client.getUserId(); |     const clientId = client.getUserId(); | ||||||
|     this.myDeviceId = client.getDeviceId(); |     if (clientId) { | ||||||
|     this.myMember = groupCall.room.getMember(client.getUserId()); |       this.myUserId = clientId; | ||||||
| 
 |       const myMember = groupCall.room.getMember(clientId); | ||||||
|  |       if (myMember) { | ||||||
|  |         this.myMember = myMember; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     this.myDeviceId = client.getDeviceId() || "unknown"; | ||||||
|  |     this.statsReportSpan = { span: undefined, stats: [] }; | ||||||
|     this.groupCall.on(GroupCallEvent.CallsChanged, this.onCallsChanged); |     this.groupCall.on(GroupCallEvent.CallsChanged, this.onCallsChanged); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -125,7 +143,7 @@ export class OTelGroupCallMembership { | ||||||
|     this.callMembershipSpan.setAttribute("matrix.deviceId", this.myDeviceId); |     this.callMembershipSpan.setAttribute("matrix.deviceId", this.myDeviceId); | ||||||
|     this.callMembershipSpan.setAttribute( |     this.callMembershipSpan.setAttribute( | ||||||
|       "matrix.displayName", |       "matrix.displayName", | ||||||
|       this.myMember.name |       this.myMember ? this.myMember.name : "unknown-name" | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     this.groupCallContext = opentelemetry.trace.setSpan( |     this.groupCallContext = opentelemetry.trace.setSpan( | ||||||
|  | @ -308,4 +326,80 @@ export class OTelGroupCallMembership { | ||||||
|       "sender.userId": event.getSender(), |       "sender.userId": event.getSender(), | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   public onConnectionStatsReport( | ||||||
|  |     statsReport: GroupCallStatsReport<ConnectionStatsReport> | ||||||
|  |   ) { | ||||||
|  |     const type = OTelStatsReportType.ConnectionReport; | ||||||
|  |     const data = | ||||||
|  |       ObjectFlattener.flattenConnectionStatsReportObject(statsReport); | ||||||
|  |     this.buildStatsEventSpan({ type, data }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public onByteSentStatsReport( | ||||||
|  |     statsReport: GroupCallStatsReport<ByteSentStatsReport> | ||||||
|  |   ) { | ||||||
|  |     const type = OTelStatsReportType.ByteSentReport; | ||||||
|  |     const data = ObjectFlattener.flattenByteSentStatsReportObject(statsReport); | ||||||
|  |     this.buildStatsEventSpan({ type, data }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public onSummaryStatsReport( | ||||||
|  |     statsReport: GroupCallStatsReport<SummaryStatsReport> | ||||||
|  |   ) { | ||||||
|  |     const type = OTelStatsReportType.SummaryReport; | ||||||
|  |     const data = ObjectFlattener.flattenSummaryStatsReportObject(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 | ||||||
|  |       ); | ||||||
|  |       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.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: [] }; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface OTelStatsReportEvent { | ||||||
|  |   type: OTelStatsReportType; | ||||||
|  |   data: Attributes; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | enum OTelStatsReportType { | ||||||
|  |   ConnectionReport = "matrix.stats.connection", | ||||||
|  |   ByteSentReport = "matrix.stats.byteSent", | ||||||
|  |   SummaryReport = "matrix.stats.summary", | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										97
									
								
								src/otel/ObjectFlattener.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/otel/ObjectFlattener.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,97 @@ | ||||||
|  | /* | ||||||
|  | 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, | ||||||
|  |   SummaryStatsReport, | ||||||
|  | } from "matrix-js-sdk/src/webrtc/stats/statsReport"; | ||||||
|  | 
 | ||||||
|  | export class ObjectFlattener { | ||||||
|  |   public static flattenConnectionStatsReportObject( | ||||||
|  |     statsReport: GroupCallStatsReport<ConnectionStatsReport> | ||||||
|  |   ): Attributes { | ||||||
|  |     const flatObject = {}; | ||||||
|  |     ObjectFlattener.flattenObjectRecursive( | ||||||
|  |       statsReport.report, | ||||||
|  |       flatObject, | ||||||
|  |       "matrix.stats.conn.", | ||||||
|  |       0 | ||||||
|  |     ); | ||||||
|  |     return flatObject; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public static flattenByteSentStatsReportObject( | ||||||
|  |     statsReport: GroupCallStatsReport<ByteSentStatsReport> | ||||||
|  |   ): Attributes { | ||||||
|  |     const flatObject = {}; | ||||||
|  |     ObjectFlattener.flattenObjectRecursive( | ||||||
|  |       statsReport.report, | ||||||
|  |       flatObject, | ||||||
|  |       "matrix.stats.bytesSent.", | ||||||
|  |       0 | ||||||
|  |     ); | ||||||
|  |     return flatObject; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static flattenSummaryStatsReportObject( | ||||||
|  |     statsReport: GroupCallStatsReport<SummaryStatsReport> | ||||||
|  |   ) { | ||||||
|  |     const flatObject = {}; | ||||||
|  |     ObjectFlattener.flattenObjectRecursive( | ||||||
|  |       statsReport.report, | ||||||
|  |       flatObject, | ||||||
|  |       "matrix.stats.summary.", | ||||||
|  |       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 | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -48,7 +48,7 @@ export class ElementCallOpenTelemetry { | ||||||
|     return sharedInstance; |     return sharedInstance; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   constructor(collectorUrl: string) { |   constructor(collectorUrl: string | undefined) { | ||||||
|     const otlpExporter = new OTLPTraceExporter({ |     const otlpExporter = new OTLPTraceExporter({ | ||||||
|       url: collectorUrl, |       url: collectorUrl, | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | @ -22,12 +22,19 @@ import { | ||||||
|   GroupCallErrorCode, |   GroupCallErrorCode, | ||||||
|   GroupCallUnknownDeviceError, |   GroupCallUnknownDeviceError, | ||||||
|   GroupCallError, |   GroupCallError, | ||||||
|  |   GroupCallStatsReportEvent, | ||||||
|  |   GroupCallStatsReport, | ||||||
| } from "matrix-js-sdk/src/webrtc/groupCall"; | } from "matrix-js-sdk/src/webrtc/groupCall"; | ||||||
| import { CallFeed, CallFeedEvent } from "matrix-js-sdk/src/webrtc/callFeed"; | import { CallFeed, CallFeedEvent } from "matrix-js-sdk/src/webrtc/callFeed"; | ||||||
| import { RoomMember } from "matrix-js-sdk/src/models/room-member"; | import { RoomMember } from "matrix-js-sdk/src/models/room-member"; | ||||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||||
| import { IWidgetApiRequest } from "matrix-widget-api"; | import { IWidgetApiRequest } from "matrix-widget-api"; | ||||||
| import { MatrixClient } from "matrix-js-sdk"; | import { MatrixClient } from "matrix-js-sdk"; | ||||||
|  | import { | ||||||
|  |   ByteSentStatsReport, | ||||||
|  |   ConnectionStatsReport, | ||||||
|  |   SummaryStatsReport, | ||||||
|  | } from "matrix-js-sdk/src/webrtc/stats/statsReport"; | ||||||
| 
 | 
 | ||||||
| import { usePageUnload } from "./usePageUnload"; | import { usePageUnload } from "./usePageUnload"; | ||||||
| import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; | import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; | ||||||
|  | @ -337,6 +344,24 @@ export function useGroupCall( | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     function onConnectionStatsReport( | ||||||
|  |       report: GroupCallStatsReport<ConnectionStatsReport> | ||||||
|  |     ): void { | ||||||
|  |       groupCallOTelMembership?.onConnectionStatsReport(report); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     function onByteSentStatsReport( | ||||||
|  |       report: GroupCallStatsReport<ByteSentStatsReport> | ||||||
|  |     ): void { | ||||||
|  |       groupCallOTelMembership?.onByteSentStatsReport(report); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     function onSummaryStatsReport( | ||||||
|  |       report: GroupCallStatsReport<SummaryStatsReport> | ||||||
|  |     ): void { | ||||||
|  |       groupCallOTelMembership?.onSummaryStatsReport(report); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     groupCall.on(GroupCallEvent.GroupCallStateChanged, onGroupCallStateChanged); |     groupCall.on(GroupCallEvent.GroupCallStateChanged, onGroupCallStateChanged); | ||||||
|     groupCall.on(GroupCallEvent.UserMediaFeedsChanged, onUserMediaFeedsChanged); |     groupCall.on(GroupCallEvent.UserMediaFeedsChanged, onUserMediaFeedsChanged); | ||||||
|     groupCall.on( |     groupCall.on( | ||||||
|  | @ -353,6 +378,18 @@ export function useGroupCall( | ||||||
|     groupCall.on(GroupCallEvent.ParticipantsChanged, onParticipantsChanged); |     groupCall.on(GroupCallEvent.ParticipantsChanged, onParticipantsChanged); | ||||||
|     groupCall.on(GroupCallEvent.Error, onError); |     groupCall.on(GroupCallEvent.Error, onError); | ||||||
| 
 | 
 | ||||||
|  |     groupCall.on( | ||||||
|  |       GroupCallStatsReportEvent.ConnectionStats, | ||||||
|  |       onConnectionStatsReport | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     groupCall.on( | ||||||
|  |       GroupCallStatsReportEvent.ByteSentStats, | ||||||
|  |       onByteSentStatsReport | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     groupCall.on(GroupCallStatsReportEvent.SummaryStats, onSummaryStatsReport); | ||||||
|  | 
 | ||||||
|     updateState({ |     updateState({ | ||||||
|       error: null, |       error: null, | ||||||
|       state: groupCall.state, |       state: groupCall.state, | ||||||
|  | @ -399,6 +436,18 @@ export function useGroupCall( | ||||||
|         onParticipantsChanged |         onParticipantsChanged | ||||||
|       ); |       ); | ||||||
|       groupCall.removeListener(GroupCallEvent.Error, onError); |       groupCall.removeListener(GroupCallEvent.Error, onError); | ||||||
|  |       groupCall.removeListener( | ||||||
|  |         GroupCallStatsReportEvent.ConnectionStats, | ||||||
|  |         onConnectionStatsReport | ||||||
|  |       ); | ||||||
|  |       groupCall.removeListener( | ||||||
|  |         GroupCallStatsReportEvent.ByteSentStats, | ||||||
|  |         onByteSentStatsReport | ||||||
|  |       ); | ||||||
|  |       groupCall.removeListener( | ||||||
|  |         GroupCallStatsReportEvent.SummaryStats, | ||||||
|  |         onSummaryStatsReport | ||||||
|  |       ); | ||||||
|       leaveCall(); |       leaveCall(); | ||||||
|     }; |     }; | ||||||
|   }, [groupCall, updateState, leaveCall]); |   }, [groupCall, updateState, leaveCall]); | ||||||
|  |  | ||||||
							
								
								
									
										215
									
								
								test/otel/ObjectFlattener-test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								test/otel/ObjectFlattener-test.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										31
									
								
								yarn.lock
									
										
									
									
									
								
							
							
						
						
									
										31
									
								
								yarn.lock
									
										
									
									
									
								
							|  | @ -1822,9 +1822,9 @@ | ||||||
|   integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw== |   integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw== | ||||||
| 
 | 
 | ||||||
| "@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.5": | "@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.5": | ||||||
|   version "0.1.0-alpha.5" |   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.5.tgz#60ede2c43b9d808ba8cf46085a3b347b290d9658" |   resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.6.tgz#c0bdb9ab0d30179b8ef744d1b4010b0ad0ab9c3a" | ||||||
|   integrity sha512-2KjAgWNGfuGLNjJwsrs6gGX157vmcTfNrA4u249utgnMPbJl7QwuUqh1bGxQ0PpK06yvZjgPlkna0lTbuwtuQw== |   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": | "@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" |   version "3.2.14" | ||||||
|  | @ -5726,7 +5726,12 @@ content-disposition@0.5.4: | ||||||
|   dependencies: |   dependencies: | ||||||
|     safe-buffer "5.2.1" |     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" |   version "1.0.4" | ||||||
|   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" |   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" | ||||||
|   integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== |   integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== | ||||||
|  | @ -10413,9 +10418,9 @@ log-symbols@^4.1.0: | ||||||
|     is-unicode-supported "^0.1.0" |     is-unicode-supported "^0.1.0" | ||||||
| 
 | 
 | ||||||
| loglevel@^1.7.1: | loglevel@^1.7.1: | ||||||
|   version "1.8.0" |   version "1.8.1" | ||||||
|   resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114" |   resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.1.tgz#5c621f83d5b48c54ae93b6156353f555963377b4" | ||||||
|   integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA== |   integrity sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg== | ||||||
| 
 | 
 | ||||||
| long@^2.4.0: | long@^2.4.0: | ||||||
|   version "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" |   resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" | ||||||
|   integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== |   integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== | ||||||
| 
 | 
 | ||||||
| "matrix-js-sdk@github:matrix-org/matrix-js-sdk#042f2ed76c501c10dde98a31732fd92d862e2187": | "matrix-js-sdk@github:matrix-org/matrix-js-sdk#fe79a6fa7ca50fc7d078e11826b5539bb0822c45": | ||||||
|   version "24.0.0" |   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/fe79a6fa7ca50fc7d078e11826b5539bb0822c45" | ||||||
|   dependencies: |   dependencies: | ||||||
|     "@babel/runtime" "^7.12.5" |     "@babel/runtime" "^7.12.5" | ||||||
|     "@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.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" |     "@types/events" "^3.0.0" | ||||||
|     events "^3.2.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: | md5.js@^1.3.4: | ||||||
|   version "1.3.5" |   version "1.3.5" | ||||||
|   resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" |   resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue