diff --git a/package.json b/package.json index a32fd8c..4ecc42d 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#042f2ed76c501c10dde98a31732fd92d862e2187", "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 764249f..4c6d308 100644 --- a/src/otel/OTelGroupCallMembership.ts +++ b/src/otel/OTelGroupCallMembership.ts @@ -14,15 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -import opentelemetry, { Span, Attributes } from "@opentelemetry/api"; -import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; +import opentelemetry, { Span, Attributes, Context } from "@opentelemetry/api"; +import { logger } from "@sentry/utils"; import { GroupCall, MatrixClient, MatrixEvent, RoomMember, } from "matrix-js-sdk"; -import { VoipEvent } from "matrix-js-sdk/src/webrtc/call"; +import { + CallState, + MatrixCall, + VoipEvent, +} from "matrix-js-sdk/src/webrtc/call"; +import { + CallsByUserAndDevice, + GroupCallEvent, +} from "matrix-js-sdk/src/webrtc/groupCall"; import { ElementCallOpenTelemetry } from "./otel"; @@ -68,21 +76,37 @@ 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 */ export class OTelGroupCallMembership { private callMembershipSpan?: Span; + private groupCallContext?: Context; private myUserId: string; + private myDeviceId: string; private myMember: RoomMember; + private callsByCallId = new Map(); constructor(private groupCall: GroupCall, client: MatrixClient) { this.myUserId = client.getUserId(); + this.myDeviceId = client.getDeviceId(); this.myMember = groupCall.room.getMember(client.getUserId()); - ElementCallOpenTelemetry.instance.provider.resource.attributes[ - SemanticResourceAttributes.SERVICE_NAME - ] = `element-call-${this.myUserId}-${client.getDeviceId()}`; + this.groupCall.on(GroupCallEvent.CallsChanged, this.onCallsChanged); + } + + dispose() { + this.groupCall.removeListener( + GroupCallEvent.CallsChanged, + this.onCallsChanged + ); } public onJoinCall() { @@ -96,12 +120,13 @@ export class OTelGroupCallMembership { this.groupCall.groupCallId ); this.callMembershipSpan.setAttribute("matrix.userId", this.myUserId); + this.callMembershipSpan.setAttribute("matrix.deviceId", this.myDeviceId); this.callMembershipSpan.setAttribute( "matrix.displayName", this.myMember.name ); - opentelemetry.trace.setSpan( + this.groupCallContext = opentelemetry.trace.setSpan( opentelemetry.context.active(), this.callMembershipSpan ); @@ -126,12 +151,55 @@ export class OTelGroupCallMembership { } this.callMembershipSpan?.addEvent( - `otel_onRoomStateEvent_${event.getType()}`, + `matrix.roomStateEvent_${event.getType()}`, flattenVoipEvent(event.getContent()) ); } - public onSendEvent(event: VoipEvent) { + public onCallsChanged = (calls: CallsByUserAndDevice) => { + for (const [userId, userCalls] of calls.entries()) { + for (const [deviceId, call] of userCalls.entries()) { + if (!this.callsByCallId.has(call.callId)) { + const span = ElementCallOpenTelemetry.instance.tracer.startSpan( + `matrix.call`, + undefined, + this.groupCallContext + ); + // 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, + }); + } + } + } + + for (const callTrackingInfo of this.callsByCallId.values()) { + const userCalls = calls.get(callTrackingInfo.userId); + if (!userCalls || !userCalls.has(callTrackingInfo.deviceId)) { + callTrackingInfo.span.end(); + this.callsByCallId.delete(callTrackingInfo.call.callId); + } + } + }; + + public onCallStateChange(call: MatrixCall, newState: CallState) { + const callTrackingInfo = this.callsByCallId.get(call.callId); + if (!callTrackingInfo) { + logger.error(`Got call state change for unknown call ID ${call.callId}`); + return; + } + + callTrackingInfo.span.addEvent("matrix.call.stateChange", { + state: newState, + }); + } + + public onSendEvent(call: MatrixCall, event: VoipEvent) { const eventType = event.eventType as string; if (!eventType.startsWith("m.call")) return; diff --git a/src/otel/otel.ts b/src/otel/otel.ts index 25de3ac..5d3e7d3 100644 --- a/src/otel/otel.ts +++ b/src/otel/otel.ts @@ -30,7 +30,7 @@ import { Anonymity } from "../analytics/PosthogAnalytics"; import { Config } from "../config/Config"; import { getSetting, settingsBus } from "../settings/useSetting"; -const SERVICE_NAME_BASE = "element-call"; +const SERVICE_NAME = "element-call"; let sharedInstance: ElementCallOpenTelemetry; @@ -58,7 +58,7 @@ export class ElementCallOpenTelemetry { // This is how we can make Jaeger show a reaonsable service in the dropdown on the left. const providerConfig = { resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: `${SERVICE_NAME_BASE}-unauthenticated`, + [SemanticResourceAttributes.SERVICE_NAME]: SERVICE_NAME, }), }; this._provider = new WebTracerProvider(providerConfig); diff --git a/src/room/GroupCallInspector.tsx b/src/room/GroupCallInspector.tsx index c058297..ad838fb 100644 --- a/src/room/GroupCallInspector.tsx +++ b/src/room/GroupCallInspector.tsx @@ -31,7 +31,12 @@ import { MatrixEvent, IContent } from "matrix-js-sdk/src/models/event"; import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall"; import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client"; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; -import { CallEvent, VoipEvent } from "matrix-js-sdk/src/webrtc/call"; +import { + CallEvent, + CallState, + MatrixCall, + VoipEvent, +} from "matrix-js-sdk/src/webrtc/call"; import styles from "./GroupCallInspector.module.css"; import { SelectInput } from "../input/SelectInput"; @@ -390,10 +395,18 @@ function useGroupCallState( dispatch({ type: ClientEvent.ReceivedVoipEvent, event }); } - function onSendVoipEvent(event: VoipEvent) { + function onSendVoipEvent(event: VoipEvent, call: MatrixCall) { dispatch({ type: CallEvent.SendVoipEvent, rawEvent: event }); - otelGroupCallMembership?.onSendEvent(event); + otelGroupCallMembership?.onSendEvent(call, event); + } + + function onCallStateChange( + newState: CallState, + _: CallState, + call: MatrixCall + ) { + otelGroupCallMembership?.onCallStateChange(call, newState); } function onUndecryptableToDevice(event: MatrixEvent) { @@ -406,8 +419,8 @@ function useGroupCallState( } client.on(RoomStateEvent.Events, onUpdateRoomState); - //groupCall.on("calls_changed", onCallsChanged); groupCall.on(CallEvent.SendVoipEvent, onSendVoipEvent); + groupCall.on(CallEvent.State, onCallStateChange); //client.on("state", onCallsChanged); //client.on("hangup", onCallHangup); client.on(ClientEvent.ReceivedVoipEvent, onReceivedVoipEvent); @@ -417,8 +430,8 @@ function useGroupCallState( return () => { client.removeListener(RoomStateEvent.Events, onUpdateRoomState); - //groupCall.removeListener("calls_changed", onCallsChanged); groupCall.removeListener(CallEvent.SendVoipEvent, onSendVoipEvent); + groupCall.removeListener(CallEvent.State, onCallStateChange); //client.removeListener("state", onCallsChanged); //client.removeListener("hangup", onCallHangup); client.removeListener(ClientEvent.ReceivedVoipEvent, onReceivedVoipEvent); diff --git a/src/room/useGroupCall.ts b/src/room/useGroupCall.ts index 0b54c82..553d418 100644 --- a/src/room/useGroupCall.ts +++ b/src/room/useGroupCall.ts @@ -173,6 +173,8 @@ export function useGroupCall( }); if (groupCallOTelMembershipGroupCallId !== groupCall.groupCallId) { + if (groupCallOTelMembership) groupCallOTelMembership.dispose(); + // If the user disables analytics, this will stay around until they leave the call // so analytics will be disabled once they leave. if (ElementCallOpenTelemetry.instance) { diff --git a/yarn.lock b/yarn.lock index ddb3fc6..58b450a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1821,7 +1821,7 @@ resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0" integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw== -"@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.3": +"@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== @@ -10545,18 +10545,18 @@ 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#23837266fca5ee799b51a722f7b8eefb2f5ac140": - version "23.5.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/23837266fca5ee799b51a722f7b8eefb2f5ac140" +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#042f2ed76c501c10dde98a31732fd92d862e2187": + version "24.0.0" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/042f2ed76c501c10dde98a31732fd92d862e2187" dependencies: "@babel/runtime" "^7.12.5" - "@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.3" + "@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.5" another-json "^0.2.0" bs58 "^5.0.0" content-type "^1.0.4" loglevel "^1.7.1" matrix-events-sdk "0.0.1" - matrix-widget-api "^1.0.0" + matrix-widget-api "^1.3.1" p-retry "4" sdp-transform "^2.14.1" unhomoglyph "^1.0.6" @@ -10570,6 +10570,14 @@ matrix-widget-api@^1.0.0: "@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"