2023-03-16 14:41:55 +00:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2023-03-20 13:30:21 +00:00
|
|
|
import { SpanExporter, ReadableSpan } from "@opentelemetry/sdk-trace-base";
|
2023-03-10 10:33:54 +01:00
|
|
|
import { ExportResult, ExportResultCode } from "@opentelemetry/core";
|
2023-04-03 20:57:03 -04:00
|
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
|
|
|
import { HrTime } from "@opentelemetry/api";
|
2023-03-10 10:33:54 +01:00
|
|
|
|
|
|
|
import { PosthogAnalytics } from "./PosthogAnalytics";
|
2023-03-31 11:10:05 +01:00
|
|
|
|
2023-04-03 20:57:03 -04:00
|
|
|
interface PrevCall {
|
|
|
|
callId: string;
|
|
|
|
hangupTs: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
function hrTimeToMs(time: HrTime): number {
|
|
|
|
return time[0] * 1000 + time[1] * 0.000001;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The maximum time between hanging up and joining the same call that we would
|
|
|
|
* consider a 'rejoin' on the user's part.
|
|
|
|
*/
|
|
|
|
const maxRejoinMs = 2 * 60 * 1000; // 2 minutes
|
|
|
|
|
2023-03-10 10:33:54 +01:00
|
|
|
/**
|
2023-04-03 20:57:03 -04:00
|
|
|
* This is implementation of {@link SpanExporter} that extracts certain metrics
|
|
|
|
* from spans to send to PostHog
|
2023-03-10 10:33:54 +01:00
|
|
|
*/
|
|
|
|
export class PosthogSpanExporter implements SpanExporter {
|
|
|
|
/**
|
|
|
|
* Export spans.
|
|
|
|
* @param spans
|
|
|
|
* @param resultCallback
|
|
|
|
*/
|
|
|
|
async export(
|
|
|
|
spans: ReadableSpan[],
|
|
|
|
resultCallback: (result: ExportResult) => void
|
|
|
|
): Promise<void> {
|
2023-04-03 20:57:03 -04:00
|
|
|
await Promise.all(
|
|
|
|
spans.map((span) => {
|
|
|
|
switch (span.name) {
|
|
|
|
case "matrix.groupCallMembership":
|
|
|
|
return this.exportGroupCallMembershipSpan(span);
|
|
|
|
// TBD if there are other spans that we want to process for export to
|
|
|
|
// PostHog
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
|
|
}
|
|
|
|
|
|
|
|
private get prevCall(): PrevCall | null {
|
|
|
|
// This is stored in localStorage so we can remember the previous call
|
|
|
|
// across app restarts
|
|
|
|
const data = localStorage.getItem("matrix-prev-call");
|
|
|
|
if (data === null) return null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
return JSON.parse(data);
|
|
|
|
} catch (e) {
|
|
|
|
logger.warn("Invalid prev call data", data);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private set prevCall(data: PrevCall | null) {
|
|
|
|
localStorage.setItem("matrix-prev-call", JSON.stringify(data));
|
|
|
|
}
|
|
|
|
|
|
|
|
async exportGroupCallMembershipSpan(span: ReadableSpan): Promise<void> {
|
|
|
|
const prevCall = this.prevCall;
|
|
|
|
const newPrevCall = (this.prevCall = {
|
|
|
|
callId: span.attributes["matrix.confId"] as string,
|
|
|
|
hangupTs: hrTimeToMs(span.endTime),
|
|
|
|
});
|
|
|
|
|
|
|
|
// If the user joined the same call within a short time frame, log this as a
|
|
|
|
// rejoin. This is interesting as a call quality metric, since rejoins may
|
|
|
|
// indicate that users had to intervene to make the product work.
|
|
|
|
if (prevCall !== null && newPrevCall.callId === prevCall.callId) {
|
|
|
|
const duration = hrTimeToMs(span.startTime) - prevCall.hangupTs;
|
|
|
|
if (duration <= maxRejoinMs) {
|
|
|
|
PosthogAnalytics.instance.trackEvent(
|
2023-03-20 13:30:21 +00:00
|
|
|
{
|
2023-04-03 20:57:03 -04:00
|
|
|
eventName: "Rejoin",
|
|
|
|
callId: prevCall.callId,
|
|
|
|
rejoinDuration: duration,
|
2023-03-20 13:30:21 +00:00
|
|
|
},
|
2023-04-03 20:57:03 -04:00
|
|
|
// Send instantly because the window might be closing
|
|
|
|
{ send_instantly: true }
|
2023-03-20 13:30:21 +00:00
|
|
|
);
|
|
|
|
}
|
2023-03-10 10:33:54 +01:00
|
|
|
}
|
|
|
|
}
|
2023-04-03 20:57:03 -04:00
|
|
|
|
2023-03-10 10:33:54 +01:00
|
|
|
/**
|
|
|
|
* Shutdown the exporter.
|
|
|
|
*/
|
|
|
|
shutdown(): Promise<void> {
|
2023-04-03 20:57:03 -04:00
|
|
|
return Promise.resolve();
|
2023-03-10 10:33:54 +01:00
|
|
|
}
|
|
|
|
}
|