From 31450219c87636677bcf8d1b5f60431e7cbe3918 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 16 Mar 2023 14:41:55 +0000 Subject: [PATCH] More work on opentelemetry event reporting Moastly a re-org to avoid new contexts over React component unmounts/ remounts. --- src/analytics/OtelPosthogExporter.ts | 16 +++++++++++++ src/otel/OTelGroupCallMembership.ts | 35 ++++++++++++++-------------- src/otel/otel.ts | 17 +++++++++++++- src/room/GroupCallInspector.tsx | 22 +++++------------ src/room/GroupCallView.tsx | 5 +++- src/room/InCallView.tsx | 4 ++++ src/room/PTTCallView.tsx | 4 ++++ src/room/useGroupCall.ts | 28 ++++++++++++++++++++-- 8 files changed, 93 insertions(+), 38 deletions(-) diff --git a/src/analytics/OtelPosthogExporter.ts b/src/analytics/OtelPosthogExporter.ts index ee72155e..d4f8b536 100644 --- a/src/analytics/OtelPosthogExporter.ts +++ b/src/analytics/OtelPosthogExporter.ts @@ -1,3 +1,19 @@ +/* +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 { SpanExporter } from "@opentelemetry/sdk-trace-base"; import { ReadableSpan } from "@opentelemetry/sdk-trace-base"; import { ExportResult, ExportResultCode } from "@opentelemetry/core"; diff --git a/src/otel/OTelGroupCallMembership.ts b/src/otel/OTelGroupCallMembership.ts index 3ea02660..d8570b7b 100644 --- a/src/otel/OTelGroupCallMembership.ts +++ b/src/otel/OTelGroupCallMembership.ts @@ -15,7 +15,7 @@ limitations under the License. */ import opentelemetry, { Context, Span } from "@opentelemetry/api"; -import { GroupCall, MatrixEvent } from "matrix-js-sdk"; +import { GroupCall, MatrixClient, MatrixEvent } from "matrix-js-sdk"; import { VoipEvent } from "matrix-js-sdk/src/webrtc/call"; import { tracer } from "./otel"; @@ -27,7 +27,7 @@ function setNestedAttributesFromToDeviceEvent(span: Span, event: VoipEvent) { setSpanEventAttributesRecursive( span, event as unknown as Record, // XXX Types - "matrix.", + "matrix.event.", 0 ); } @@ -64,28 +64,27 @@ export class OTelGroupCallMembership { private context: Context; private callMembershipSpan: Span; - constructor(groupCall: GroupCall) { - const callIdContext = opentelemetry.context - .active() - .setValue(Symbol("confId"), groupCall.groupCallId); + constructor(groupCall: GroupCall, client: MatrixClient) { + // Create a new call based on the callIdContext. This context also has a span assigned to it. + // Other spans can use this context to extract the parent span. + // (When passing this context to startSpan the started span will use the span set in the context (in this case the callSpan) as the parent) + const myMember = groupCall.room.getMember(client.getUserId()); + this.context = opentelemetry.trace + .setSpan(opentelemetry.context.active(), this.callMembershipSpan) + .setValue(Symbol("confId"), groupCall.groupCallId) + .setValue(Symbol("matrix.userId"), client.getUserId()) + .setValue(Symbol("matrix.displayName"), myMember.name); + } + + public onJoinCall() { // Create the main span that tracks the time we intend to be in the call this.callMembershipSpan = tracer.startSpan( "otel_groupCallMembershipSpan", undefined, - callIdContext + this.context ); - // Create a new call based on the callIdContext. This context also has a span assigned to it. - // Other spans can use this context to extract the parent span. - // (When passing this context to startSpan the started span will use the span set in the context (in this case the callSpan) as the parent) - this.context = opentelemetry.trace.setSpan( - opentelemetry.context.active(), - this.callMembershipSpan - ); - } - - public onJoinCall() { // Here we start a very short span. This is a hack to trigger the posthog exporter. // Only ended spans are processed by the exporter. // We want the exporter to know that a call has started @@ -107,7 +106,7 @@ export class OTelGroupCallMembership { startCallSpan.end(); // and end the main span to indicate we've left - this.callMembershipSpan.end(); + if (this.callMembershipSpan) this.callMembershipSpan.end(); } public onSendStateEvent(stateEvent: MatrixEvent) {} diff --git a/src/otel/otel.ts b/src/otel/otel.ts index ecff690c..f7facbae 100644 --- a/src/otel/otel.ts +++ b/src/otel/otel.ts @@ -1,4 +1,19 @@ -/* document-load.ts|js file - the code is the same for both the languages */ +/* +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 { ConsoleSpanExporter, SimpleSpanProcessor, diff --git a/src/room/GroupCallInspector.tsx b/src/room/GroupCallInspector.tsx index a0b16a9f..18c57520 100644 --- a/src/room/GroupCallInspector.tsx +++ b/src/room/GroupCallInspector.tsx @@ -354,20 +354,8 @@ function reducer( function useGroupCallState( client: MatrixClient, groupCall: GroupCall, - showPollCallStats: boolean + otelGroupCallMembership: OTelGroupCallMembership ): InspectorContextState { - const [otelMembership] = useState( - () => new OTelGroupCallMembership(groupCall) - ); - - useEffect(() => { - otelMembership.onJoinCall(); - - return () => { - otelMembership.onLeaveCall(); - }; - }, [otelMembership]); - const [state, dispatch] = useReducer(reducer, { localUserId: client.getUserId(), localSessionId: client.getSessionId(), @@ -403,7 +391,7 @@ function useGroupCallState( function onSendVoipEvent(event: VoipEvent) { dispatch({ type: CallEvent.SendVoipEvent, rawEvent: event }); - otelMembership.onSendEvent(event); + otelGroupCallMembership.onSendEvent(event); } function onUndecryptableToDevice(event: MatrixEvent) { @@ -437,7 +425,7 @@ function useGroupCallState( onUndecryptableToDevice ); }; - }, [client, groupCall, otelMembership]); + }, [client, groupCall, otelGroupCallMembership]); return state; } @@ -445,17 +433,19 @@ function useGroupCallState( interface GroupCallInspectorProps { client: MatrixClient; groupCall: GroupCall; + otelGroupCallMembership: OTelGroupCallMembership; show: boolean; } export function GroupCallInspector({ client, groupCall, + otelGroupCallMembership, show, }: GroupCallInspectorProps) { const [currentTab, setCurrentTab] = useState("sequence-diagrams"); const [selectedUserId, setSelectedUserId] = useState(); - const state = useGroupCallState(client, groupCall, show); + const state = useGroupCallState(client, groupCall, otelGroupCallMembership); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [_, setState] = useContext(InspectorContext); diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 61e1eb5e..ad011efb 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -81,7 +81,8 @@ export function GroupCallView({ screenshareFeeds, participants, unencryptedEventsFromUsers, - } = useGroupCall(groupCall); + otelGroupCallMembership, + } = useGroupCall(groupCall, client); const { t } = useTranslation(); const { setAudioInput, setVideoInput } = useMediaHandler(); @@ -237,6 +238,7 @@ export function GroupCallView({ onLeave={onLeave} isEmbedded={isEmbedded} hideHeader={hideHeader} + otelGroupCallMembership={otelGroupCallMembership} /> ); } else { @@ -261,6 +263,7 @@ export function GroupCallView({ roomIdOrAlias={roomIdOrAlias} unencryptedEventsFromUsers={unencryptedEventsFromUsers} hideHeader={hideHeader} + otelGroupCallMembership={otelGroupCallMembership} /> ); } diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 48b1cbf1..8639cbae 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -73,6 +73,7 @@ import { TileDescriptor } from "../video-grid/TileDescriptor"; import { AudioSink } from "../video-grid/AudioSink"; import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts"; import { NewVideoGrid } from "../video-grid/NewVideoGrid"; +import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership"; const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); // There is currently a bug in Safari our our code with cloning and sending MediaStreams @@ -100,6 +101,7 @@ interface Props { roomIdOrAlias: string; unencryptedEventsFromUsers: Set; hideHeader: boolean; + otelGroupCallMembership: OTelGroupCallMembership; } export function InCallView({ @@ -122,6 +124,7 @@ export function InCallView({ roomIdOrAlias, unencryptedEventsFromUsers, hideHeader, + otelGroupCallMembership, }: Props) { const { t } = useTranslation(); usePreventScroll(); @@ -429,6 +432,7 @@ export function InCallView({ {rageshakeRequestModalState.isOpen && ( diff --git a/src/room/PTTCallView.tsx b/src/room/PTTCallView.tsx index a25f3371..27816409 100644 --- a/src/room/PTTCallView.tsx +++ b/src/room/PTTCallView.tsx @@ -44,6 +44,7 @@ import { GroupCallInspector } from "./GroupCallInspector"; import { OverflowMenu } from "./OverflowMenu"; import { Size } from "../Avatar"; import { ParticipantInfo } from "./useGroupCall"; +import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership"; function getPromptText( networkWaiting: boolean, @@ -106,6 +107,7 @@ interface Props { onLeave: () => void; isEmbedded: boolean; hideHeader: boolean; + otelGroupCallMembership: OTelGroupCallMembership; } export const PTTCallView: React.FC = ({ @@ -119,6 +121,7 @@ export const PTTCallView: React.FC = ({ onLeave, isEmbedded, hideHeader, + otelGroupCallMembership, }) => { const { t } = useTranslation(); const { modalState: inviteModalState, modalProps: inviteModalProps } = @@ -192,6 +195,7 @@ export const PTTCallView: React.FC = ({ >; hasLocalParticipant: boolean; unencryptedEventsFromUsers: Set; + otelGroupCallMembership: OTelGroupCallMembership; } interface State { @@ -84,6 +87,13 @@ interface State { hasLocalParticipant: boolean; } +// This is a bit of a hack, but we keep the opentelemetry tracker object at the file +// level so that it doesn't pop in & out of existence as react mounts & unmounts +// components. The right solution is probably for this to live in the js-sdk and have +// the same lifetime as groupcalls themselves. +let groupCallOTelMembership: OTelGroupCallMembership; +let groupCallOTelMembershipGroupCallId: string; + function getParticipants( groupCall: GroupCall ): Map> { @@ -112,7 +122,10 @@ function getParticipants( return participants; } -export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType { +export function useGroupCall( + groupCall: GroupCall, + client: MatrixClient +): UseGroupCallReturnType { const [ { state, @@ -146,6 +159,11 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType { hasLocalParticipant: false, }); + if (groupCallOTelMembershipGroupCallId !== groupCall.groupCallId) { + groupCallOTelMembership = new OTelGroupCallMembership(groupCall, client); + groupCallOTelMembershipGroupCallId = groupCall.groupCallId; + } + const [unencryptedEventsFromUsers, addUnencryptedEventUser] = useReducer( (state: Set, newVal: string) => { return new Set(state).add(newVal); @@ -383,9 +401,14 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType { console.error(error); updateState({ error }); }); + + groupCallOTelMembership.onJoinCall(); }, [groupCall, updateState]); - const leave = useCallback(() => groupCall.leave(), [groupCall]); + const leave = useCallback(() => { + groupCallOTelMembership.onLeaveCall(); + groupCall.leave(); + }, [groupCall]); const toggleLocalVideoMuted = useCallback(() => { const toggleToMute = !groupCall.isLocalVideoMuted(); @@ -525,5 +548,6 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType { participants, hasLocalParticipant, unencryptedEventsFromUsers, + otelGroupCallMembership: groupCallOTelMembership, }; }