Files
ThreadNet-Web/src/performance/index.ts
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

167 lines
5.3 KiB
TypeScript
Raw Normal View History

2021-05-19 10:07:02 +01:00
/*
2024-09-09 14:57:16 +01:00
Copyright 2024 New Vector Ltd.
2021-05-19 10:07:02 +01:00
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
2024-09-09 14:57:16 +01:00
Please see LICENSE files in the repository root for full details.
2021-05-19 10:07:02 +01:00
*/
2021-10-15 16:31:29 +02:00
import { logger } from "matrix-js-sdk/src/logger";
2021-10-22 17:23:32 -05:00
import { PerformanceEntryNames } from "./entry-names";
2021-05-19 10:07:02 +01:00
interface GetEntriesOptions {
2021-07-01 23:23:03 +01:00
name?: string;
type?: string;
2021-05-19 10:07:02 +01:00
}
type PerformanceCallbackFunction = (entry: PerformanceEntry[]) => void;
interface PerformanceDataListener {
2021-07-01 23:23:03 +01:00
entryNames?: string[];
callback: PerformanceCallbackFunction;
2021-05-19 10:07:02 +01:00
}
export default class PerformanceMonitor {
private static _instance: PerformanceMonitor;
2021-05-19 10:07:02 +01:00
2021-06-29 13:11:58 +01:00
private START_PREFIX = "start:";
private STOP_PREFIX = "stop:";
2021-05-19 10:07:02 +01:00
2021-06-29 13:11:58 +01:00
private listeners: PerformanceDataListener[] = [];
private entries: PerformanceEntry[] = [];
2021-05-19 10:07:02 +01:00
public static get instance(): PerformanceMonitor {
if (!PerformanceMonitor._instance) {
PerformanceMonitor._instance = new PerformanceMonitor();
}
return PerformanceMonitor._instance;
}
/**
* Starts a performance recording
* @param name Name of the recording
* @param id Specify an identifier appended to the measurement name
* @returns {void}
*/
public start(name: string, id?: string): void {
2021-05-19 10:07:02 +01:00
if (!this.supportsPerformanceApi()) {
return;
}
const key = this.buildKey(name, id);
if (performance.getEntriesByName(this.START_PREFIX + key).length > 0) {
2021-10-15 16:31:29 +02:00
logger.warn(`Recording already started for: ${name}`);
2021-05-19 10:07:02 +01:00
return;
}
performance.mark(this.START_PREFIX + key);
}
/**
* Stops a performance recording and stores delta duration
* with the start marker
* @param name Name of the recording
* @param id Specify an identifier appended to the measurement name
* @returns The measurement
2021-05-19 10:07:02 +01:00
*/
public stop(name: string, id?: string): PerformanceEntry | undefined {
2021-05-19 10:07:02 +01:00
if (!this.supportsPerformanceApi()) {
return;
}
const key = this.buildKey(name, id);
if (performance.getEntriesByName(this.START_PREFIX + key).length === 0) {
2021-10-15 16:31:29 +02:00
logger.warn(`No recording started for: ${name}`);
2021-05-19 10:07:02 +01:00
return;
}
performance.mark(this.STOP_PREFIX + key);
performance.measure(key, this.START_PREFIX + key, this.STOP_PREFIX + key);
this.clear(name, id);
const measurement = performance.getEntriesByName(key).pop()!;
2021-05-19 10:07:02 +01:00
// Keeping a reference to all PerformanceEntry created
// by this abstraction for historical events collection
// when adding a data callback
this.entries.push(measurement);
this.listeners.forEach((listener) => {
if (this.shouldEmit(listener, measurement)) {
2021-06-29 13:11:58 +01:00
listener.callback([measurement]);
2021-05-19 10:07:02 +01:00
}
});
return measurement;
}
public clear(name: string, id?: string): void {
2021-05-19 10:07:02 +01:00
if (!this.supportsPerformanceApi()) {
return;
}
const key = this.buildKey(name, id);
performance.clearMarks(this.START_PREFIX + key);
performance.clearMarks(this.STOP_PREFIX + key);
}
public getEntries({ name, type }: GetEntriesOptions = {}): PerformanceEntry[] {
2021-05-19 10:07:02 +01:00
return this.entries.filter((entry) => {
const satisfiesName = !name || entry.name === name;
const satisfiedType = !type || entry.entryType === type;
return satisfiesName && satisfiedType;
});
}
public addPerformanceDataCallback(listener: PerformanceDataListener, buffer = false): void {
2021-05-19 10:07:02 +01:00
this.listeners.push(listener);
if (buffer) {
const toEmit = this.entries.filter((entry) => this.shouldEmit(listener, entry));
if (toEmit.length > 0) {
listener.callback(toEmit);
}
}
}
public removePerformanceDataCallback(callback?: PerformanceCallbackFunction): void {
2021-05-19 10:07:02 +01:00
if (!callback) {
this.listeners = [];
} else {
this.listeners.splice(
this.listeners.findIndex((listener) => listener.callback === callback),
1,
);
}
}
/**
* Tor browser does not support the Performance API
* @returns {boolean} true if the Performance API is supported
*/
private supportsPerformanceApi(): boolean {
return performance !== undefined && performance.mark !== undefined;
}
private shouldEmit(listener: PerformanceDataListener, entry: PerformanceEntry): boolean {
return !listener.entryNames || listener.entryNames.includes(entry.name);
}
/**
* Internal utility to ensure consistent name for the recording
* @param name Name of the recording
* @param id Specify an identifier appended to the measurement name
* @returns {string} a compound of the name and identifier if present
*/
private buildKey(name: string, id?: string): string {
const suffix = id ? `:${id}` : "";
return `${name}${suffix}`;
2021-05-19 10:07:02 +01:00
}
}
// Convenience exports
export { PerformanceEntryNames };
// Exposing those to the window object to bridge them from tests
window.mxPerformanceMonitor = PerformanceMonitor.instance;
window.mxPerformanceEntryNames = PerformanceEntryNames;