From 6804a26e74267925637296a94ea9e2c927f00aa0 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 14 May 2021 11:04:58 +0100 Subject: [PATCH] Add performance data collection mechanism --- src/components/structures/MatrixChat.tsx | 14 ++--- src/performance/index.ts | 65 +++++++++++++++++++----- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 64d205c89c..81381c56d3 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -486,14 +486,14 @@ export default class MatrixChat extends React.PureComponent { } startPageChangeTimer() { - PerformanceMonitor.start(PerformanceEntryNames.SWITCH_ROOM); + PerformanceMonitor.start(PerformanceEntryNames.PAGE_CHANGE); } stopPageChangeTimer() { - PerformanceMonitor.stop(PerformanceEntryNames.SWITCH_ROOM); + PerformanceMonitor.stop(PerformanceEntryNames.PAGE_CHANGE); const entries = PerformanceMonitor.getEntries({ - name: PerformanceEntryNames.SWITCH_ROOM, + name: PerformanceEntryNames.PAGE_CHANGE, }); const measurement = entries.pop(); @@ -1612,13 +1612,13 @@ export default class MatrixChat extends React.PureComponent { action: 'start_registration', params: params, }); - Performance.start(PerformanceEntryNames.REGISTER); + PerformanceMonitor.start(PerformanceEntryNames.REGISTER); } else if (screen === 'login') { dis.dispatch({ action: 'start_login', params: params, }); - Performance.start(PerformanceEntryNames.LOGIN); + PerformanceMonitor.start(PerformanceEntryNames.LOGIN); } else if (screen === 'forgot_password') { dis.dispatch({ action: 'start_password_recovery', @@ -1858,7 +1858,7 @@ export default class MatrixChat extends React.PureComponent { // returns a promise which resolves to the new MatrixClient onRegistered(credentials: IMatrixClientCreds) { - Performance.stop(PerformanceEntryNames.REGISTER); + PerformanceMonitor.stop(PerformanceEntryNames.REGISTER); return Lifecycle.setLoggedIn(credentials); } @@ -1948,7 +1948,7 @@ export default class MatrixChat extends React.PureComponent { // Create and start the client await Lifecycle.setLoggedIn(credentials); await this.postLoginSetup(); - Performance.stop(PerformanceEntryNames.LOGIN); + PerformanceMonitor.stop(PerformanceEntryNames.LOGIN); }; // complete security / e2e setup has finished diff --git a/src/performance/index.ts b/src/performance/index.ts index 4379ba77e3..3d903537a6 100644 --- a/src/performance/index.ts +++ b/src/performance/index.ts @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { string } from "prop-types"; import { PerformanceEntryNames } from "./entry-names"; const START_PREFIX = "start:"; @@ -29,6 +28,16 @@ interface GetEntriesOptions { type?: string, } +type PerformanceCallbackFunction = (entry: PerformanceEntry) => void; + +interface PerformanceDataListener { + entryTypes?: string[], + callback: PerformanceCallbackFunction +} + +let listeners: PerformanceDataListener[] = []; +const entries: PerformanceEntry[] = []; + export default class PerformanceMonitor { /** * Starts a performance recording @@ -42,7 +51,7 @@ export default class PerformanceMonitor { } const key = buildKey(name, id); - if (!performance.getEntriesByName(key).length) { + if (performance.getEntriesByName(key).length > 0) { console.warn(`Recording already started for: ${name}`); return; } @@ -57,12 +66,12 @@ export default class PerformanceMonitor { * @param id Specify an identifier appended to the measurement name * @returns {void} */ - static stop(name: string, id?: string): void { + static stop(name: string, id?: string): PerformanceEntry { if (!supportsPerformanceApi()) { return; } const key = buildKey(name, id); - if (!performance.getEntriesByName(START_PREFIX + key).length) { + if (performance.getEntriesByName(START_PREFIX + key).length === 0) { console.warn(`No recording started for: ${name}`); return; } @@ -75,6 +84,17 @@ export default class PerformanceMonitor { ); 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 + entries.push(measurement); + + listeners.forEach(listener => emitPerformanceData(listener, measurement)); + + return measurement; } static clear(name: string, id?: string): void { @@ -87,18 +107,37 @@ export default class PerformanceMonitor { } static getEntries({ name, type }: GetEntriesOptions = {}): PerformanceEntry[] { - if (!supportsPerformanceApi()) { - return; - } + return entries.filter(entry => { + const satisfiesName = !name || entry.name === name; + const satisfiedType = !type || entry.entryType === type; + return satisfiesName && satisfiedType; + }); + } - if (!name && !type) { - return performance.getEntries(); - } else if (!name) { - return performance.getEntriesByType(type); - } else { - return performance.getEntriesByName(name, type); + static addPerformanceDataCallback(listener: PerformanceDataListener, buffer = false) { + listeners.push(listener); + + if (buffer) { + entries.forEach(entry => emitPerformanceData(listener, entry)); } } + + static removePerformanceDataCallback(callback?: PerformanceCallbackFunction) { + if (!callback) { + listeners = []; + } else { + listeners.splice( + listeners.findIndex(listener => listener.callback === callback), + 1, + ); + } + } +} + +function emitPerformanceData(listener, entry): void { + if (!listener.entryTypes || listener.entryTypes.includes(entry.entryType)) { + listener.callback(entry) + } } /**