More work on opentelemetry event reporting

Moastly a re-org to avoid new contexts over React component unmounts/
remounts.
This commit is contained in:
David Baker 2023-03-16 14:41:55 +00:00
commit 31450219c8
8 changed files with 93 additions and 38 deletions

View file

@ -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 { SpanExporter } from "@opentelemetry/sdk-trace-base";
import { ReadableSpan } from "@opentelemetry/sdk-trace-base"; import { ReadableSpan } from "@opentelemetry/sdk-trace-base";
import { ExportResult, ExportResultCode } from "@opentelemetry/core"; import { ExportResult, ExportResultCode } from "@opentelemetry/core";

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import opentelemetry, { Context, Span } from "@opentelemetry/api"; 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 { VoipEvent } from "matrix-js-sdk/src/webrtc/call";
import { tracer } from "./otel"; import { tracer } from "./otel";
@ -27,7 +27,7 @@ function setNestedAttributesFromToDeviceEvent(span: Span, event: VoipEvent) {
setSpanEventAttributesRecursive( setSpanEventAttributesRecursive(
span, span,
event as unknown as Record<string, unknown>, // XXX Types event as unknown as Record<string, unknown>, // XXX Types
"matrix.", "matrix.event.",
0 0
); );
} }
@ -64,28 +64,27 @@ export class OTelGroupCallMembership {
private context: Context; private context: Context;
private callMembershipSpan: Span; private callMembershipSpan: Span;
constructor(groupCall: GroupCall) { constructor(groupCall: GroupCall, client: MatrixClient) {
const callIdContext = opentelemetry.context // Create a new call based on the callIdContext. This context also has a span assigned to it.
.active() // Other spans can use this context to extract the parent span.
.setValue(Symbol("confId"), groupCall.groupCallId); // (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 // Create the main span that tracks the time we intend to be in the call
this.callMembershipSpan = tracer.startSpan( this.callMembershipSpan = tracer.startSpan(
"otel_groupCallMembershipSpan", "otel_groupCallMembershipSpan",
undefined, 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. // Here we start a very short span. This is a hack to trigger the posthog exporter.
// Only ended spans are processed by the exporter. // Only ended spans are processed by the exporter.
// We want the exporter to know that a call has started // We want the exporter to know that a call has started
@ -107,7 +106,7 @@ export class OTelGroupCallMembership {
startCallSpan.end(); startCallSpan.end();
// and end the main span to indicate we've left // and end the main span to indicate we've left
this.callMembershipSpan.end(); if (this.callMembershipSpan) this.callMembershipSpan.end();
} }
public onSendStateEvent(stateEvent: MatrixEvent) {} public onSendStateEvent(stateEvent: MatrixEvent) {}

View file

@ -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 { import {
ConsoleSpanExporter, ConsoleSpanExporter,
SimpleSpanProcessor, SimpleSpanProcessor,

View file

@ -354,20 +354,8 @@ function reducer(
function useGroupCallState( function useGroupCallState(
client: MatrixClient, client: MatrixClient,
groupCall: GroupCall, groupCall: GroupCall,
showPollCallStats: boolean otelGroupCallMembership: OTelGroupCallMembership
): InspectorContextState { ): InspectorContextState {
const [otelMembership] = useState(
() => new OTelGroupCallMembership(groupCall)
);
useEffect(() => {
otelMembership.onJoinCall();
return () => {
otelMembership.onLeaveCall();
};
}, [otelMembership]);
const [state, dispatch] = useReducer(reducer, { const [state, dispatch] = useReducer(reducer, {
localUserId: client.getUserId(), localUserId: client.getUserId(),
localSessionId: client.getSessionId(), localSessionId: client.getSessionId(),
@ -403,7 +391,7 @@ function useGroupCallState(
function onSendVoipEvent(event: VoipEvent) { function onSendVoipEvent(event: VoipEvent) {
dispatch({ type: CallEvent.SendVoipEvent, rawEvent: event }); dispatch({ type: CallEvent.SendVoipEvent, rawEvent: event });
otelMembership.onSendEvent(event); otelGroupCallMembership.onSendEvent(event);
} }
function onUndecryptableToDevice(event: MatrixEvent) { function onUndecryptableToDevice(event: MatrixEvent) {
@ -437,7 +425,7 @@ function useGroupCallState(
onUndecryptableToDevice onUndecryptableToDevice
); );
}; };
}, [client, groupCall, otelMembership]); }, [client, groupCall, otelGroupCallMembership]);
return state; return state;
} }
@ -445,17 +433,19 @@ function useGroupCallState(
interface GroupCallInspectorProps { interface GroupCallInspectorProps {
client: MatrixClient; client: MatrixClient;
groupCall: GroupCall; groupCall: GroupCall;
otelGroupCallMembership: OTelGroupCallMembership;
show: boolean; show: boolean;
} }
export function GroupCallInspector({ export function GroupCallInspector({
client, client,
groupCall, groupCall,
otelGroupCallMembership,
show, show,
}: GroupCallInspectorProps) { }: GroupCallInspectorProps) {
const [currentTab, setCurrentTab] = useState("sequence-diagrams"); const [currentTab, setCurrentTab] = useState("sequence-diagrams");
const [selectedUserId, setSelectedUserId] = useState<string>(); const [selectedUserId, setSelectedUserId] = useState<string>();
const state = useGroupCallState(client, groupCall, show); const state = useGroupCallState(client, groupCall, otelGroupCallMembership);
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, setState] = useContext(InspectorContext); const [_, setState] = useContext(InspectorContext);

View file

@ -81,7 +81,8 @@ export function GroupCallView({
screenshareFeeds, screenshareFeeds,
participants, participants,
unencryptedEventsFromUsers, unencryptedEventsFromUsers,
} = useGroupCall(groupCall); otelGroupCallMembership,
} = useGroupCall(groupCall, client);
const { t } = useTranslation(); const { t } = useTranslation();
const { setAudioInput, setVideoInput } = useMediaHandler(); const { setAudioInput, setVideoInput } = useMediaHandler();
@ -237,6 +238,7 @@ export function GroupCallView({
onLeave={onLeave} onLeave={onLeave}
isEmbedded={isEmbedded} isEmbedded={isEmbedded}
hideHeader={hideHeader} hideHeader={hideHeader}
otelGroupCallMembership={otelGroupCallMembership}
/> />
); );
} else { } else {
@ -261,6 +263,7 @@ export function GroupCallView({
roomIdOrAlias={roomIdOrAlias} roomIdOrAlias={roomIdOrAlias}
unencryptedEventsFromUsers={unencryptedEventsFromUsers} unencryptedEventsFromUsers={unencryptedEventsFromUsers}
hideHeader={hideHeader} hideHeader={hideHeader}
otelGroupCallMembership={otelGroupCallMembership}
/> />
); );
} }

View file

@ -73,6 +73,7 @@ import { TileDescriptor } from "../video-grid/TileDescriptor";
import { AudioSink } from "../video-grid/AudioSink"; import { AudioSink } from "../video-grid/AudioSink";
import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts"; import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts";
import { NewVideoGrid } from "../video-grid/NewVideoGrid"; import { NewVideoGrid } from "../video-grid/NewVideoGrid";
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
// There is currently a bug in Safari our our code with cloning and sending MediaStreams // There is currently a bug in Safari our our code with cloning and sending MediaStreams
@ -100,6 +101,7 @@ interface Props {
roomIdOrAlias: string; roomIdOrAlias: string;
unencryptedEventsFromUsers: Set<string>; unencryptedEventsFromUsers: Set<string>;
hideHeader: boolean; hideHeader: boolean;
otelGroupCallMembership: OTelGroupCallMembership;
} }
export function InCallView({ export function InCallView({
@ -122,6 +124,7 @@ export function InCallView({
roomIdOrAlias, roomIdOrAlias,
unencryptedEventsFromUsers, unencryptedEventsFromUsers,
hideHeader, hideHeader,
otelGroupCallMembership,
}: Props) { }: Props) {
const { t } = useTranslation(); const { t } = useTranslation();
usePreventScroll(); usePreventScroll();
@ -429,6 +432,7 @@ export function InCallView({
<GroupCallInspector <GroupCallInspector
client={client} client={client}
groupCall={groupCall} groupCall={groupCall}
otelGroupCallMembership={otelGroupCallMembership}
show={showInspector} show={showInspector}
/> />
{rageshakeRequestModalState.isOpen && ( {rageshakeRequestModalState.isOpen && (

View file

@ -44,6 +44,7 @@ import { GroupCallInspector } from "./GroupCallInspector";
import { OverflowMenu } from "./OverflowMenu"; import { OverflowMenu } from "./OverflowMenu";
import { Size } from "../Avatar"; import { Size } from "../Avatar";
import { ParticipantInfo } from "./useGroupCall"; import { ParticipantInfo } from "./useGroupCall";
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
function getPromptText( function getPromptText(
networkWaiting: boolean, networkWaiting: boolean,
@ -106,6 +107,7 @@ interface Props {
onLeave: () => void; onLeave: () => void;
isEmbedded: boolean; isEmbedded: boolean;
hideHeader: boolean; hideHeader: boolean;
otelGroupCallMembership: OTelGroupCallMembership;
} }
export const PTTCallView: React.FC<Props> = ({ export const PTTCallView: React.FC<Props> = ({
@ -119,6 +121,7 @@ export const PTTCallView: React.FC<Props> = ({
onLeave, onLeave,
isEmbedded, isEmbedded,
hideHeader, hideHeader,
otelGroupCallMembership,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { modalState: inviteModalState, modalProps: inviteModalProps } = const { modalState: inviteModalState, modalProps: inviteModalProps } =
@ -192,6 +195,7 @@ export const PTTCallView: React.FC<Props> = ({
<GroupCallInspector <GroupCallInspector
client={client} client={client}
groupCall={groupCall} groupCall={groupCall}
otelGroupCallMembership={otelGroupCallMembership}
// Never shown in PTT mode, but must be present to collect call state // Never shown in PTT mode, but must be present to collect call state
// https://github.com/vector-im/element-call/issues/328 // https://github.com/vector-im/element-call/issues/328
show={false} show={false}

View file

@ -27,11 +27,13 @@ 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 { usePageUnload } from "./usePageUnload"; import { usePageUnload } from "./usePageUnload";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { TranslatedError, translatedError } from "../TranslatedError"; import { TranslatedError, translatedError } from "../TranslatedError";
import { ElementWidgetActions, ScreenshareStartData, widget } from "../widget"; import { ElementWidgetActions, ScreenshareStartData, widget } from "../widget";
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
export enum ConnectionState { export enum ConnectionState {
EstablishingCall = "establishing call", // call hasn't been established yet EstablishingCall = "establishing call", // call hasn't been established yet
@ -66,6 +68,7 @@ export interface UseGroupCallReturnType {
participants: Map<RoomMember, Map<string, ParticipantInfo>>; participants: Map<RoomMember, Map<string, ParticipantInfo>>;
hasLocalParticipant: boolean; hasLocalParticipant: boolean;
unencryptedEventsFromUsers: Set<string>; unencryptedEventsFromUsers: Set<string>;
otelGroupCallMembership: OTelGroupCallMembership;
} }
interface State { interface State {
@ -84,6 +87,13 @@ interface State {
hasLocalParticipant: boolean; 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( function getParticipants(
groupCall: GroupCall groupCall: GroupCall
): Map<RoomMember, Map<string, ParticipantInfo>> { ): Map<RoomMember, Map<string, ParticipantInfo>> {
@ -112,7 +122,10 @@ function getParticipants(
return participants; return participants;
} }
export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType { export function useGroupCall(
groupCall: GroupCall,
client: MatrixClient
): UseGroupCallReturnType {
const [ const [
{ {
state, state,
@ -146,6 +159,11 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
hasLocalParticipant: false, hasLocalParticipant: false,
}); });
if (groupCallOTelMembershipGroupCallId !== groupCall.groupCallId) {
groupCallOTelMembership = new OTelGroupCallMembership(groupCall, client);
groupCallOTelMembershipGroupCallId = groupCall.groupCallId;
}
const [unencryptedEventsFromUsers, addUnencryptedEventUser] = useReducer( const [unencryptedEventsFromUsers, addUnencryptedEventUser] = useReducer(
(state: Set<string>, newVal: string) => { (state: Set<string>, newVal: string) => {
return new Set(state).add(newVal); return new Set(state).add(newVal);
@ -383,9 +401,14 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
console.error(error); console.error(error);
updateState({ error }); updateState({ error });
}); });
groupCallOTelMembership.onJoinCall();
}, [groupCall, updateState]); }, [groupCall, updateState]);
const leave = useCallback(() => groupCall.leave(), [groupCall]); const leave = useCallback(() => {
groupCallOTelMembership.onLeaveCall();
groupCall.leave();
}, [groupCall]);
const toggleLocalVideoMuted = useCallback(() => { const toggleLocalVideoMuted = useCallback(() => {
const toggleToMute = !groupCall.isLocalVideoMuted(); const toggleToMute = !groupCall.isLocalVideoMuted();
@ -525,5 +548,6 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
participants, participants,
hasLocalParticipant, hasLocalParticipant,
unencryptedEventsFromUsers, unencryptedEventsFromUsers,
otelGroupCallMembership: groupCallOTelMembership,
}; };
} }