More work on opentelemetry event reporting
Moastly a re-org to avoid new contexts over React component unmounts/ remounts.
This commit is contained in:
parent
22d2404370
commit
31450219c8
8 changed files with 93 additions and 38 deletions
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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) {}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 && (
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue