Refactor performance monitor to use instance pattern

This commit is contained in:
Germain Souquet 2021-05-17 09:30:53 +01:00
parent cbf6457857
commit 781c0dca68
3 changed files with 81 additions and 67 deletions

View file

@ -81,7 +81,7 @@ declare global {
mxTypingStore: TypingStore; mxTypingStore: TypingStore;
mxEventIndexPeg: EventIndexPeg; mxEventIndexPeg: EventIndexPeg;
mxPerformanceMonitor: PerformanceMonitor; mxPerformanceMonitor: PerformanceMonitor;
mxPerformanceEntryNames: PerformanceEntryNames; mxPerformanceEntryNames: any;
} }
interface Document { interface Document {

View file

@ -486,13 +486,15 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
startPageChangeTimer() { startPageChangeTimer() {
PerformanceMonitor.start(PerformanceEntryNames.PAGE_CHANGE); PerformanceMonitor.instance.start(PerformanceEntryNames.PAGE_CHANGE);
} }
stopPageChangeTimer() { stopPageChangeTimer() {
PerformanceMonitor.stop(PerformanceEntryNames.PAGE_CHANGE); const perfMonitor = PerformanceMonitor.instance;
const entries = PerformanceMonitor.getEntries({ perfMonitor.stop(PerformanceEntryNames.PAGE_CHANGE);
const entries = perfMonitor.getEntries({
name: PerformanceEntryNames.PAGE_CHANGE, name: PerformanceEntryNames.PAGE_CHANGE,
}); });
const measurement = entries.pop(); const measurement = entries.pop();
@ -1612,13 +1614,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
action: 'start_registration', action: 'start_registration',
params: params, params: params,
}); });
PerformanceMonitor.start(PerformanceEntryNames.REGISTER); PerformanceMonitor.instance.start(PerformanceEntryNames.REGISTER);
} else if (screen === 'login') { } else if (screen === 'login') {
dis.dispatch({ dis.dispatch({
action: 'start_login', action: 'start_login',
params: params, params: params,
}); });
PerformanceMonitor.start(PerformanceEntryNames.LOGIN); PerformanceMonitor.instance.start(PerformanceEntryNames.LOGIN);
} else if (screen === 'forgot_password') { } else if (screen === 'forgot_password') {
dis.dispatch({ dis.dispatch({
action: 'start_password_recovery', action: 'start_password_recovery',
@ -1947,8 +1949,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// Create and start the client // Create and start the client
await Lifecycle.setLoggedIn(credentials); await Lifecycle.setLoggedIn(credentials);
await this.postLoginSetup(); await this.postLoginSetup();
PerformanceMonitor.stop(PerformanceEntryNames.LOGIN); PerformanceMonitor.instance.stop(PerformanceEntryNames.LOGIN);
PerformanceMonitor.stop(PerformanceEntryNames.REGISTER); PerformanceMonitor.instance.stop(PerformanceEntryNames.REGISTER);
}; };
// complete security / e2e setup has finished // complete security / e2e setup has finished

View file

@ -16,13 +16,6 @@ limitations under the License.
import { PerformanceEntryNames } from "./entry-names"; import { PerformanceEntryNames } from "./entry-names";
const START_PREFIX = "start:";
const STOP_PREFIX = "stop:";
export {
PerformanceEntryNames,
}
interface GetEntriesOptions { interface GetEntriesOptions {
name?: string, name?: string,
type?: string, type?: string,
@ -35,28 +28,40 @@ interface PerformanceDataListener {
callback: PerformanceCallbackFunction callback: PerformanceCallbackFunction
} }
let listeners: PerformanceDataListener[] = [];
const entries: PerformanceEntry[] = [];
export default class PerformanceMonitor { export default class PerformanceMonitor {
static _instance: PerformanceMonitor;
private START_PREFIX = "start:"
private STOP_PREFIX = "stop:"
private listeners: PerformanceDataListener[] = []
private entries: PerformanceEntry[] = []
public static get instance(): PerformanceMonitor {
if (!PerformanceMonitor._instance) {
PerformanceMonitor._instance = new PerformanceMonitor();
}
return PerformanceMonitor._instance;
}
/** /**
* Starts a performance recording * Starts a performance recording
* @param name Name of the recording * @param name Name of the recording
* @param id Specify an identifier appended to the measurement name * @param id Specify an identifier appended to the measurement name
* @returns {void} * @returns {void}
*/ */
static start(name: string, id?: string): void { start(name: string, id?: string): void {
if (!supportsPerformanceApi()) { if (!this.supportsPerformanceApi()) {
return; return;
} }
const key = buildKey(name, id); const key = this.buildKey(name, id);
if (performance.getEntriesByName(key).length > 0) { if (performance.getEntriesByName(key).length > 0) {
console.warn(`Recording already started for: ${name}`); console.warn(`Recording already started for: ${name}`);
return; return;
} }
performance.mark(START_PREFIX + key); performance.mark(this.START_PREFIX + key);
} }
/** /**
@ -66,21 +71,21 @@ export default class PerformanceMonitor {
* @param id Specify an identifier appended to the measurement name * @param id Specify an identifier appended to the measurement name
* @returns {void} * @returns {void}
*/ */
static stop(name: string, id?: string): PerformanceEntry { stop(name: string, id?: string): PerformanceEntry {
if (!supportsPerformanceApi()) { if (!this.supportsPerformanceApi()) {
return; return;
} }
const key = buildKey(name, id); const key = this.buildKey(name, id);
if (performance.getEntriesByName(START_PREFIX + key).length === 0) { if (performance.getEntriesByName(this.START_PREFIX + key).length === 0) {
console.warn(`No recording started for: ${name}`); console.warn(`No recording started for: ${name}`);
return; return;
} }
performance.mark(STOP_PREFIX + key); performance.mark(this.STOP_PREFIX + key);
performance.measure( performance.measure(
key, key,
START_PREFIX + key, this.START_PREFIX + key,
STOP_PREFIX + key, this.STOP_PREFIX + key,
); );
this.clear(name, id); this.clear(name, id);
@ -90,10 +95,10 @@ export default class PerformanceMonitor {
// Keeping a reference to all PerformanceEntry created // Keeping a reference to all PerformanceEntry created
// by this abstraction for historical events collection // by this abstraction for historical events collection
// when adding a data callback // when adding a data callback
entries.push(measurement); this.entries.push(measurement);
listeners.forEach(listener => { this.listeners.forEach(listener => {
if (shouldEmit(listener, measurement)) { if (this.shouldEmit(listener, measurement)) {
listener.callback([measurement]) listener.callback([measurement])
} }
}); });
@ -101,66 +106,73 @@ export default class PerformanceMonitor {
return measurement; return measurement;
} }
static clear(name: string, id?: string): void { clear(name: string, id?: string): void {
if (!supportsPerformanceApi()) { if (!this.supportsPerformanceApi()) {
return; return;
} }
const key = buildKey(name, id); const key = this.buildKey(name, id);
performance.clearMarks(START_PREFIX + key); performance.clearMarks(this.START_PREFIX + key);
performance.clearMarks(STOP_PREFIX + key); performance.clearMarks(this.STOP_PREFIX + key);
} }
static getEntries({ name, type }: GetEntriesOptions = {}): PerformanceEntry[] { getEntries({ name, type }: GetEntriesOptions = {}): PerformanceEntry[] {
return entries.filter(entry => { return this.entries.filter(entry => {
const satisfiesName = !name || entry.name === name; const satisfiesName = !name || entry.name === name;
const satisfiedType = !type || entry.entryType === type; const satisfiedType = !type || entry.entryType === type;
return satisfiesName && satisfiedType; return satisfiesName && satisfiedType;
}); });
} }
static addPerformanceDataCallback(listener: PerformanceDataListener, buffer = false) { addPerformanceDataCallback(listener: PerformanceDataListener, buffer = false) {
listeners.push(listener); this.listeners.push(listener);
if (buffer) { if (buffer) {
const toEmit = entries.filter(entry => shouldEmit(listener, entry)); const toEmit = this.entries.filter(entry => this.shouldEmit(listener, entry));
if (toEmit.length > 0) { if (toEmit.length > 0) {
listener.callback(toEmit); listener.callback(toEmit);
} }
} }
} }
static removePerformanceDataCallback(callback?: PerformanceCallbackFunction) { removePerformanceDataCallback(callback?: PerformanceCallbackFunction) {
if (!callback) { if (!callback) {
listeners = []; this.listeners = [];
} else { } else {
listeners.splice( this.listeners.splice(
listeners.findIndex(listener => listener.callback === callback), this.listeners.findIndex(listener => listener.callback === callback),
1, 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 {
return `${name}${id ? `:${id}` : ''}`;
}
} }
function shouldEmit(listener: PerformanceDataListener, entry: PerformanceEntry): boolean {
return !listener.entryNames || listener.entryNames.includes(entry.name); // Convienience exports
export {
PerformanceEntryNames,
} }
/** // Exposing those to the window object to bridge them from tests
* Tor browser does not support the Performance API window.mxPerformanceMonitor = PerformanceMonitor.instance;
* @returns {boolean} true if the Performance API is supported
*/
function supportsPerformanceApi(): boolean {
return performance !== undefined && performance.mark !== undefined;
}
/**
* 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
*/
function buildKey(name: string, id?: string): string {
return `${name}${id ? `:${id}` : ''}`;
}
window.mxPerformanceMonitor = PerformanceMonitor;
window.mxPerformanceEntryNames = PerformanceEntryNames; window.mxPerformanceEntryNames = PerformanceEntryNames;