2021-05-19 09:07:02 +00:00
|
|
|
/*
|
|
|
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2021-10-15 14:31:29 +00:00
|
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
|
|
|
|
2021-10-22 22:23:32 +00:00
|
|
|
import { PerformanceEntryNames } from "./entry-names";
|
|
|
|
|
2021-05-19 09:07:02 +00:00
|
|
|
interface GetEntriesOptions {
|
2021-07-01 22:23:03 +00:00
|
|
|
name?: string;
|
|
|
|
type?: string;
|
2021-05-19 09:07:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type PerformanceCallbackFunction = (entry: PerformanceEntry[]) => void;
|
|
|
|
|
|
|
|
interface PerformanceDataListener {
|
2021-07-01 22:23:03 +00:00
|
|
|
entryNames?: string[];
|
|
|
|
callback: PerformanceCallbackFunction;
|
2021-05-19 09:07:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export default class PerformanceMonitor {
|
2022-12-16 12:29:59 +00:00
|
|
|
private static _instance: PerformanceMonitor;
|
2021-05-19 09:07:02 +00:00
|
|
|
|
2021-06-29 12:11:58 +00:00
|
|
|
private START_PREFIX = "start:";
|
|
|
|
private STOP_PREFIX = "stop:";
|
2021-05-19 09:07:02 +00:00
|
|
|
|
2021-06-29 12:11:58 +00:00
|
|
|
private listeners: PerformanceDataListener[] = [];
|
|
|
|
private entries: PerformanceEntry[] = [];
|
2021-05-19 09:07:02 +00: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}
|
|
|
|
*/
|
2022-12-16 12:29:59 +00:00
|
|
|
public start(name: string, id?: string): void {
|
2021-05-19 09:07:02 +00:00
|
|
|
if (!this.supportsPerformanceApi()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const key = this.buildKey(name, id);
|
|
|
|
|
|
|
|
if (performance.getEntriesByName(this.START_PREFIX + key).length > 0) {
|
2021-10-15 14:31:29 +00:00
|
|
|
logger.warn(`Recording already started for: ${name}`);
|
2021-05-19 09:07:02 +00: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
|
2022-05-17 14:38:45 +00:00
|
|
|
* @returns The measurement
|
2021-05-19 09:07:02 +00:00
|
|
|
*/
|
2022-12-16 12:29:59 +00:00
|
|
|
public stop(name: string, id?: string): PerformanceEntry {
|
2021-05-19 09:07:02 +00:00
|
|
|
if (!this.supportsPerformanceApi()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const key = this.buildKey(name, id);
|
|
|
|
if (performance.getEntriesByName(this.START_PREFIX + key).length === 0) {
|
2021-10-15 14:31:29 +00:00
|
|
|
logger.warn(`No recording started for: ${name}`);
|
2021-05-19 09:07:02 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
performance.mark(this.STOP_PREFIX + key);
|
2022-12-12 11:24:14 +00:00
|
|
|
performance.measure(key, this.START_PREFIX + key, this.STOP_PREFIX + key);
|
2021-05-19 09:07:02 +00:00
|
|
|
|
|
|
|
this.clear(name, id);
|
|
|
|
|
|
|
|
const measurement = performance.getEntriesByName(key).pop();
|
|
|
|
|
|
|
|
// Keeping a reference to all PerformanceEntry created
|
|
|
|
// by this abstraction for historical events collection
|
|
|
|
// when adding a data callback
|
|
|
|
this.entries.push(measurement);
|
|
|
|
|
2022-12-12 11:24:14 +00:00
|
|
|
this.listeners.forEach((listener) => {
|
2021-05-19 09:07:02 +00:00
|
|
|
if (this.shouldEmit(listener, measurement)) {
|
2021-06-29 12:11:58 +00:00
|
|
|
listener.callback([measurement]);
|
2021-05-19 09:07:02 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return measurement;
|
|
|
|
}
|
|
|
|
|
2022-12-16 12:29:59 +00:00
|
|
|
public clear(name: string, id?: string): void {
|
2021-05-19 09:07:02 +00:00
|
|
|
if (!this.supportsPerformanceApi()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const key = this.buildKey(name, id);
|
|
|
|
performance.clearMarks(this.START_PREFIX + key);
|
|
|
|
performance.clearMarks(this.STOP_PREFIX + key);
|
|
|
|
}
|
|
|
|
|
2022-12-16 12:29:59 +00:00
|
|
|
public getEntries({ name, type }: GetEntriesOptions = {}): PerformanceEntry[] {
|
2022-12-12 11:24:14 +00:00
|
|
|
return this.entries.filter((entry) => {
|
2021-05-19 09:07:02 +00:00
|
|
|
const satisfiesName = !name || entry.name === name;
|
|
|
|
const satisfiedType = !type || entry.entryType === type;
|
|
|
|
return satisfiesName && satisfiedType;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-12-16 12:29:59 +00:00
|
|
|
public addPerformanceDataCallback(listener: PerformanceDataListener, buffer = false) {
|
2021-05-19 09:07:02 +00:00
|
|
|
this.listeners.push(listener);
|
|
|
|
if (buffer) {
|
2022-12-12 11:24:14 +00:00
|
|
|
const toEmit = this.entries.filter((entry) => this.shouldEmit(listener, entry));
|
2021-05-19 09:07:02 +00:00
|
|
|
if (toEmit.length > 0) {
|
|
|
|
listener.callback(toEmit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-16 12:29:59 +00:00
|
|
|
public removePerformanceDataCallback(callback?: PerformanceCallbackFunction) {
|
2021-05-19 09:07:02 +00:00
|
|
|
if (!callback) {
|
|
|
|
this.listeners = [];
|
|
|
|
} else {
|
|
|
|
this.listeners.splice(
|
2022-12-12 11:24:14 +00:00
|
|
|
this.listeners.findIndex((listener) => listener.callback === callback),
|
2021-05-19 09:07:02 +00:00
|
|
|
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 {
|
2022-12-12 11:24:14 +00:00
|
|
|
const suffix = id ? `:${id}` : "";
|
2022-05-03 21:04:37 +00:00
|
|
|
return `${name}${suffix}`;
|
2021-05-19 09:07:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convenience exports
|
2022-12-12 11:24:14 +00:00
|
|
|
export { PerformanceEntryNames };
|
2021-05-19 09:07:02 +00:00
|
|
|
|
|
|
|
// Exposing those to the window object to bridge them from tests
|
|
|
|
window.mxPerformanceMonitor = PerformanceMonitor.instance;
|
|
|
|
window.mxPerformanceEntryNames = PerformanceEntryNames;
|