From c519e13885f72f52c0534f6d85c8af36fb9a7c54 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 15 Mar 2023 16:00:39 +0000 Subject: [PATCH] Version that does at least send some traces --- package.json | 1 - src/otel/OTelGroupCallMembership.ts | 176 ++++++++-------------------- src/otel/otel.ts | 6 +- src/room/GroupCallInspector.tsx | 23 +++- src/room/GroupCallView.tsx | 4 - src/room/useGroupCall.ts | 3 - src/telemetry/otel.ts | 128 -------------------- yarn.lock | 10 -- 8 files changed, 73 insertions(+), 278 deletions(-) delete mode 100644 src/telemetry/otel.ts diff --git a/package.json b/package.json index 0adbe947..a8f6ff0d 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "@opentelemetry/context-zone": "^1.9.1", "@opentelemetry/exporter-jaeger": "^1.9.1", "@opentelemetry/exporter-trace-otlp-http": "^0.35.1", - "@opentelemetry/exporter-zipkin": "^1.9.1", "@opentelemetry/instrumentation-document-load": "^0.31.1", "@opentelemetry/instrumentation-user-interaction": "^0.32.1", "@opentelemetry/sdk-trace-web": "^1.9.1", diff --git a/src/otel/OTelGroupCallMembership.ts b/src/otel/OTelGroupCallMembership.ts index 56a8cf8b..3ea02660 100644 --- a/src/otel/OTelGroupCallMembership.ts +++ b/src/otel/OTelGroupCallMembership.ts @@ -15,18 +15,48 @@ limitations under the License. */ import opentelemetry, { Context, Span } from "@opentelemetry/api"; -import { - ClientEvent, - GroupCall, - MatrixClient, - MatrixEvent, - RoomStateEvent, -} from "matrix-js-sdk"; -import { CallEvent } from "matrix-js-sdk/src/webrtc/call"; -import { useCallback, useEffect, useState } from "react"; +import { GroupCall, MatrixEvent } from "matrix-js-sdk"; +import { VoipEvent } from "matrix-js-sdk/src/webrtc/call"; import { tracer } from "./otel"; +/** + * Recursively sets the contents of a todevice event object as attributes on a span + */ +function setNestedAttributesFromToDeviceEvent(span: Span, event: VoipEvent) { + setSpanEventAttributesRecursive( + span, + event as unknown as Record, // XXX Types + "matrix.", + 0 + ); +} + +function setSpanEventAttributesRecursive( + span: Span, + obj: Record, + prefix: string, + depth: number +) { + if (depth > 10) + throw new Error( + "Depth limit exceeded: aborting VoipEvent recursion. Prefix is " + prefix + ); + + for (const [k, v] of Object.entries(obj)) { + if (["string", "number"].includes(typeof v)) { + span.setAttribute(prefix + k, v as string | number); + } else if (typeof v === "object") { + setSpanEventAttributesRecursive( + span, + v as Record, + prefix + k + ".", + depth + 1 + ); + } + } +} + /** * Represent the span of time which we intend to be joined to a group call */ @@ -34,7 +64,7 @@ export class OTelGroupCallMembership { private context: Context; private callMembershipSpan: Span; - constructor(private groupCall: GroupCall) { + constructor(groupCall: GroupCall) { const callIdContext = opentelemetry.context .active() .setValue(Symbol("confId"), groupCall.groupCallId); @@ -82,125 +112,19 @@ export class OTelGroupCallMembership { public onSendStateEvent(stateEvent: MatrixEvent) {} - public onSendToDeviceEvent(toDeviceEvent: Record) { - const eventType = toDeviceEvent.eventType as string; + public onSendEvent(event: VoipEvent) { + const eventType = event.eventType as string; if (!eventType.startsWith("m.call")) return; - const span = tracer.startSpan( - `otel_sendToDeviceEvent_${toDeviceEvent.eventType}`, - undefined, - this.context - ); + if (event.type === "toDevice") { + const span = tracer.startSpan( + `otel_sendToDeviceEvent_${event.eventType}`, + undefined, + this.context + ); - for (const [k, v] of Object.entries(toDeviceEvent)) { - if (["string", "number"].includes(typeof v)) - span.setAttribute(k, v as string | number); + setNestedAttributesFromToDeviceEvent(span, event); + span.end(); } } } - -export const useCallEventInstrumentation = ( - client: MatrixClient, - groupCall: GroupCall -): void => { - const [groupCallSpan, setGroupCallSpan] = useState(); - const [groupCallId, setGroupCallId] = useState(); - - const startChildSpan = useCallback( - (name: string, groupCallId: string): Span => { - const traceId = "7b78c1f568312cb288e55a9bc3c28cc5"; - const spanId = "7d31f3e430d90882"; - - const ctx = opentelemetry.trace.setSpanContext(context.active(), { - traceId, - spanId, - traceFlags: 1, - isRemote: true, - }); - - console.log("LOG context", ctx); - console.log( - "LOG context valid", - trace.isSpanContextValid(trace.getSpan(ctx).spanContext()) - ); - console.log("LOG parent span", trace.getSpan(ctx)); - - return tracer.startSpan(name, undefined, ctx); - }, - [] - ); - - const onUpdateRoomState = useCallback((event?: MatrixEvent) => { - /*const callStateEvent = groupCall.room.currentState.getStateEvents( - "org.matrix.msc3401.call", - groupCall.groupCallId - );*/ - /*const memberStateEvents = groupCall.room.currentState.getStateEvents( - "org.matrix.msc3401.call.member" - );*/ - }, []); - - const onReceivedVoipEvent = (event: MatrixEvent) => {}; - - const onUndecryptableToDevice = (event: MatrixEvent) => {}; - - const onSendVoipEvent = useCallback( - (event: Record) => { - const span = startChildSpan( - `element-call:send-voip-event:${event.eventType}`, - groupCall.groupCallId - ); - span.setAttribute("groupCallId", groupCall.groupCallId); - - console.log("LOG span", span); - - span.end(); - }, - [groupCall.groupCallId, startChildSpan] - ); - - useEffect(() => { - return; - if (groupCallId === groupCall.groupCallId) return; - - console.log("LOG starting span", groupCall.groupCallId, groupCallId); - - groupCallSpan?.end(); - - const newSpan = tracer.startSpan("element-call:group-call"); - newSpan.setAttribute("groupCallId", groupCall.groupCallId); - setGroupCallSpan(newSpan); - setGroupCallId(groupCall.groupCallId); - }, [groupCallSpan, groupCallId, groupCall.groupCallId]); - - useEffect(() => () => { - console.log("LOG ending span"); - - groupCallSpan?.end(); - }); - - useEffect(() => { - client.on(RoomStateEvent.Events, onUpdateRoomState); - //groupCall.on("calls_changed", onCallsChanged); - groupCall.on(CallEvent.SendVoipEvent, onSendVoipEvent); - //client.on("state", onCallsChanged); - //client.on("hangup", onCallHangup); - client.on(ClientEvent.ReceivedVoipEvent, onReceivedVoipEvent); - client.on(ClientEvent.UndecryptableToDeviceEvent, onUndecryptableToDevice); - - onUpdateRoomState(); - - return () => { - client.removeListener(RoomStateEvent.Events, onUpdateRoomState); - //groupCall.removeListener("calls_changed", onCallsChanged); - groupCall.removeListener(CallEvent.SendVoipEvent, onSendVoipEvent); - //client.removeListener("state", onCallsChanged); - //client.removeListener("hangup", onCallHangup); - client.removeListener(ClientEvent.ReceivedVoipEvent, onReceivedVoipEvent); - client.removeListener( - ClientEvent.UndecryptableToDeviceEvent, - onUndecryptableToDevice - ); - }; - }, [client, groupCall, onSendVoipEvent, onUpdateRoomState]); -}; diff --git a/src/otel/otel.ts b/src/otel/otel.ts index 6f546db3..ecff690c 100644 --- a/src/otel/otel.ts +++ b/src/otel/otel.ts @@ -6,7 +6,6 @@ import { import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { WebTracerProvider } from "@opentelemetry/sdk-trace-web"; import opentelemetry from "@opentelemetry/api"; -import { Context } from "@opentelemetry/api"; import { Resource } from "@opentelemetry/resources"; import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; @@ -29,12 +28,14 @@ const provider = new WebTracerProvider(providerConfig); provider.addSpanProcessor(new SimpleSpanProcessor(otlpExporter)); provider.addSpanProcessor(new SimpleSpanProcessor(posthogExporter)); provider.addSpanProcessor(new SimpleSpanProcessor(consoleExporter)); +opentelemetry.trace.setGlobalTracerProvider(provider); // This is not the serviceName shown in jaeger export const tracer = opentelemetry.trace.getTracer( "my-element-call-otl-tracer" ); +/* class CallTracer { // We create one tracer class for each main context. // Even if differnt tracer classes overlap in time space, we might want to visulaize them seperately. @@ -47,7 +48,7 @@ class CallTracer { public startGroupCall(groupCallId: string) {} - public startCall(callId: string): Context { + public startCall(callId: string) { // The main context will be set when initiating the main/parent span. // Create an initial context with the callId param @@ -94,3 +95,4 @@ class CallTracer { } export const callTracer = new CallTracer(); +*/ diff --git a/src/room/GroupCallInspector.tsx b/src/room/GroupCallInspector.tsx index 648a0a1f..a0b16a9f 100644 --- a/src/room/GroupCallInspector.tsx +++ b/src/room/GroupCallInspector.tsx @@ -31,11 +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 } from "matrix-js-sdk/src/webrtc/call"; +import { CallEvent, VoipEvent } from "matrix-js-sdk/src/webrtc/call"; import styles from "./GroupCallInspector.module.css"; import { SelectInput } from "../input/SelectInput"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; +import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership"; interface InspectorContextState { eventsByUserId?: { [userId: string]: SequenceDiagramMatrixEvent[] }; @@ -235,7 +236,7 @@ function reducer( action: { type?: CallEvent | ClientEvent | RoomStateEvent; event?: MatrixEvent; - rawEvent?: Record; + rawEvent?: VoipEvent; callStateEvent?: MatrixEvent; memberStateEvents?: MatrixEvent[]; } @@ -355,6 +356,18 @@ function useGroupCallState( groupCall: GroupCall, showPollCallStats: boolean ): 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(), @@ -387,8 +400,10 @@ function useGroupCallState( dispatch({ type: ClientEvent.ReceivedVoipEvent, event }); } - function onSendVoipEvent(event: Record) { + function onSendVoipEvent(event: VoipEvent) { dispatch({ type: CallEvent.SendVoipEvent, rawEvent: event }); + + otelMembership.onSendEvent(event); } function onUndecryptableToDevice(event: MatrixEvent) { @@ -422,7 +437,7 @@ function useGroupCallState( onUndecryptableToDevice ); }; - }, [client, groupCall]); + }, [client, groupCall, otelMembership]); return state; } diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 242e0e5e..61e1eb5e 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -35,7 +35,6 @@ import { useLocationNavigation } from "../useLocationNavigation"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; import { useMediaHandler } from "../settings/useMediaHandler"; import { findDeviceByName, getDevices } from "../media-utils"; -import { callTracer } from "../telemetry/otel"; declare global { interface Window { @@ -144,7 +143,6 @@ export function GroupCallView({ ]); await groupCall.enter(); - callTracer.startCall(groupCall.groupCallId); PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date()); PosthogAnalytics.instance.eventCallStarted.track(groupCall.groupCallId); @@ -165,7 +163,6 @@ export function GroupCallView({ if (isEmbedded && !preload) { // In embedded mode, bypass the lobby and just enter the call straight away groupCall.enter(); - callTracer.startCall(groupCall.groupCallId); PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date()); PosthogAnalytics.instance.eventCallStarted.track(groupCall.groupCallId); @@ -189,7 +186,6 @@ export function GroupCallView({ // In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent, // therefore we want the event to be sent instantly without getting queued/batched. - callTracer.endCall(); const sendInstantly = !!widget; PosthogAnalytics.instance.eventCallEnded.track( groupCall.groupCallId, diff --git a/src/room/useGroupCall.ts b/src/room/useGroupCall.ts index a3250736..37484b43 100644 --- a/src/room/useGroupCall.ts +++ b/src/room/useGroupCall.ts @@ -32,7 +32,6 @@ import { usePageUnload } from "./usePageUnload"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; import { TranslatedError, translatedError } from "../TranslatedError"; import { ElementWidgetActions, ScreenshareStartData, widget } from "../widget"; -import { callTracer } from "../telemetry/otel"; export enum ConnectionState { EstablishingCall = "establishing call", // call hasn't been established yet @@ -376,7 +375,6 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType { ) { return; } - callTracer.startCall(groupCall.groupCallId); PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date()); PosthogAnalytics.instance.eventCallStarted.track(groupCall.groupCallId); @@ -401,7 +399,6 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType { const setMicrophoneMuted = useCallback( (setMuted) => { groupCall.setMicrophoneMuted(setMuted); - callTracer.muteMic(setMuted); PosthogAnalytics.instance.eventMuteMicrophone.track( setMuted, groupCall.groupCallId diff --git a/src/telemetry/otel.ts b/src/telemetry/otel.ts deleted file mode 100644 index c8836692..00000000 --- a/src/telemetry/otel.ts +++ /dev/null @@ -1,128 +0,0 @@ -/* document-load.ts|js file - the code is the same for both the languages */ -import { - ConsoleSpanExporter, - SimpleSpanProcessor, -} from "@opentelemetry/sdk-trace-base"; -import { ZipkinExporter } from "@opentelemetry/exporter-zipkin"; -// import { JaegerExporter } from "@opentelemetry/exporter-jaeger"; -import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; -import { WebTracerProvider } from "@opentelemetry/sdk-trace-web"; -import { ZoneContextManager } from "@opentelemetry/context-zone"; -import { registerInstrumentations } from "@opentelemetry/instrumentation"; -import opentelemetry from "@opentelemetry/api"; -import { Resource } from "@opentelemetry/resources"; -import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; - -import { PosthogSpanExporter } from "../analytics/OtelPosthogExporter"; - -const SERVICE_NAME = "element-call"; -// It is really important to set the correct content type here. Otherwise the Jaeger will crash and not accept the zipkin event -// Additionally jaeger needs to be started with zipkin on port 9411 -const optionsZipkin = { - // url: `http://localhost:9411/api/v2/spans`, - // serviceName: SERVICE_NAME, - headers: { - "Content-Type": "application/json", - }, -}; -// We DO NOT use the OTLPTraceExporter. This somehow does not hit the right endpoint and also causes issues with CORS -const collectorOptions = { - // url: `http://localhost:14268/api/v2/spans`, // url is optional and can be omitted - default is http://localhost:4318/v1/traces - headers: { "Access-Control-Allow-Origin": "*" }, // an optional object containing custom headers to be sent with each request - concurrencyLimit: 10, // an optional limit on pending requests -}; -const otlpExporter = new OTLPTraceExporter(collectorOptions); -const consoleExporter = new ConsoleSpanExporter(); -// The zipkin exporter is the actual exporter we need for web based otel applications -const zipkin = new ZipkinExporter(optionsZipkin); -const posthogExporter = new PosthogSpanExporter(); - -// 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, - }), -}; -const provider = new WebTracerProvider(providerConfig); - -provider.addSpanProcessor(new SimpleSpanProcessor(otlpExporter)); -// We can add as many processors and exporters as we want to. The zipkin one is the important one for Jaeger -provider.addSpanProcessor(new SimpleSpanProcessor(posthogExporter)); -provider.addSpanProcessor(new SimpleSpanProcessor(consoleExporter)); -provider.addSpanProcessor(new SimpleSpanProcessor(zipkin)); - -// This is unecassary i think... -provider.register({ - // Changing default contextManager to use ZoneContextManager - supports asynchronous operations - optional - contextManager: new ZoneContextManager(), -}); - -// Registering instrumentations (These are automated span collectors for the Http request during page loading, switching) -registerInstrumentations({ - instrumentations: [ - // new DocumentLoadInstrumentation(), - // new UserInteractionInstrumentation(), - ], -}); - -// This is not the serviceName shown in jaeger -export const tracer = opentelemetry.trace.getTracer( - "my-element-call-otl-tracer" -); - -class CallTracer { - // We create one tracer class for each main context. - // Even if differnt tracer classes overlap in time space, we might want to visulaize them seperately. - // The Call Tracer should only contain spans/events that are relevant to understand the procedure of the individual candidates. - // Another Tracer Class (for example a ConnectionTracer) can contain a very granular list of all steps to connect to a call. - - private callSpan; - private callContext; - private muteSpan?; - public startCall(callId: string) { - // The main context will be set when initiating the main/parent span. - - // Create an initial context with the callId param - const callIdContext = opentelemetry.context - .active() - .setValue(Symbol("callId"), callId); - - // Create the main span that tracks the whole call - this.callSpan = tracer.startSpan("otel_callSpan", undefined, callIdContext); - - // 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.callContext = opentelemetry.trace.setSpan( - opentelemetry.context.active(), - this.callSpan - ); - - // 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 - const startCallSpan = tracer.startSpan( - "otel_startCallSpan", - undefined, - this.callContext - ); - startCallSpan.end(); - } - public muteMic(muteState: boolean) { - if (muteState) { - this.muteSpan = tracer.startSpan( - "otel_muteSpan", - undefined, - this.callContext - ); - } else if (this.muteSpan) { - this.muteSpan.end(); - this.muteSpan = null; - } - } - public endCall() { - this.callSpan?.end(); - } -} - -export const callTracer = new CallTracer(); diff --git a/yarn.lock b/yarn.lock index 8e518376..2662e3df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1956,16 +1956,6 @@ "@opentelemetry/resources" "1.9.1" "@opentelemetry/sdk-trace-base" "1.9.1" -"@opentelemetry/exporter-zipkin@^1.9.1": - version "1.9.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.9.1.tgz#0bcddf2f3bcb1b26b94a090c953996a28087d21f" - integrity sha512-KBgf3w84luP5vWLlrqVFKmbwFK4lXM//t6K7H4nsg576htbz1RpBbQfybADjPdXTjGHqDTtLiC5MC90hxS7Z2w== - dependencies: - "@opentelemetry/core" "1.9.1" - "@opentelemetry/resources" "1.9.1" - "@opentelemetry/sdk-trace-base" "1.9.1" - "@opentelemetry/semantic-conventions" "1.9.1" - "@opentelemetry/instrumentation-document-load@^0.31.1": version "0.31.1" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-document-load/-/instrumentation-document-load-0.31.1.tgz#a535a5d1d71706701d3ff560a700b9dd03e4fb59"