Create performance monitoring abstraction

pull/21833/head
Germain Souquet 2021-05-14 10:11:59 +01:00
parent 1aa09cf0ae
commit d6a25d493a
3 changed files with 192 additions and 31 deletions

View File

@ -86,6 +86,8 @@ import {RoomUpdateCause} from "../../stores/room-list/models";
import defaultDispatcher from "../../dispatcher/dispatcher"; import defaultDispatcher from "../../dispatcher/dispatcher";
import SecurityCustomisations from "../../customisations/Security"; import SecurityCustomisations from "../../customisations/Security";
import PerformanceMonitor, { PerformanceEntryNames } from "../../performance";
/** constants for MatrixChat.state.view */ /** constants for MatrixChat.state.view */
export enum Views { export enum Views {
// a special initial state which is only used at startup, while we are // a special initial state which is only used at startup, while we are
@ -484,42 +486,20 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
startPageChangeTimer() { startPageChangeTimer() {
// Tor doesn't support performance PerformanceMonitor.start(PerformanceEntryNames.SWITCH_ROOM);
if (!performance || !performance.mark) return null;
// This shouldn't happen because UNSAFE_componentWillUpdate and componentDidUpdate
// are used.
if (this.pageChanging) {
console.warn('MatrixChat.startPageChangeTimer: timer already started');
return;
}
this.pageChanging = true;
performance.mark('element_MatrixChat_page_change_start');
} }
stopPageChangeTimer() { stopPageChangeTimer() {
// Tor doesn't support performance PerformanceMonitor.stop(PerformanceEntryNames.SWITCH_ROOM);
if (!performance || !performance.mark) return null;
if (!this.pageChanging) { const entries = PerformanceMonitor.getEntries({
console.warn('MatrixChat.stopPageChangeTimer: timer not started'); name: PerformanceEntryNames.SWITCH_ROOM,
return; });
} const measurement = entries.pop();
this.pageChanging = false;
performance.mark('element_MatrixChat_page_change_stop');
performance.measure(
'element_MatrixChat_page_change_delta',
'element_MatrixChat_page_change_start',
'element_MatrixChat_page_change_stop',
);
performance.clearMarks('element_MatrixChat_page_change_start');
performance.clearMarks('element_MatrixChat_page_change_stop');
const measurement = performance.getEntriesByName('element_MatrixChat_page_change_delta').pop();
// In practice, sometimes the entries list is empty, so we get no measurement return measurement
if (!measurement) return null; ? measurement.duration
: null;
return measurement.duration;
} }
shouldTrackPageChange(prevState: IState, state: IState) { shouldTrackPageChange(prevState: IState, state: IState) {
@ -1632,11 +1612,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
action: 'start_registration', action: 'start_registration',
params: params, params: params,
}); });
Performance.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,
}); });
Performance.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',
@ -1876,6 +1858,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// returns a promise which resolves to the new MatrixClient // returns a promise which resolves to the new MatrixClient
onRegistered(credentials: IMatrixClientCreds) { onRegistered(credentials: IMatrixClientCreds) {
Performance.stop(PerformanceEntryNames.REGISTER);
return Lifecycle.setLoggedIn(credentials); return Lifecycle.setLoggedIn(credentials);
} }
@ -1965,6 +1948,7 @@ 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();
Performance.stop(PerformanceEntryNames.LOGIN);
}; };
// complete security / e2e setup has finished // complete security / e2e setup has finished

View File

@ -0,0 +1,57 @@
/*
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.
*/
export enum PerformanceEntryNames {
/**
* Application wide
*/
APP_STARTUP = "mx_AppStartup",
PAGE_CHANGE = "mx_PageChange",
/**
* Events
*/
RESEND_EVENT = "mx_ResendEvent",
SEND_E2EE_EVENT = "mx_SendE2EEEvent",
SEND_ATTACHMENT = "mx_SendAttachment",
/**
* Rooms
*/
SWITCH_ROOM = "mx_SwithRoom",
JUMP_TO_ROOM = "mx_JumpToRoom",
JOIN_ROOM = "mx_JoinRoom",
CREATE_DM = "mx_CreateDM",
PEEK_ROOM = "mx_PeekRoom",
/**
* User
*/
VERIFY_E2EE_USER = "mx_VerifyE2EEUser",
LOGIN = "mx_Login",
REGISTER = "mx_Register",
/**
* VoIP
*/
SETUP_VOIP_CALL = "mx_SetupVoIPCall",
}

120
src/performance/index.ts Normal file
View File

@ -0,0 +1,120 @@
/*
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.
*/
import { string } from "prop-types";
import { PerformanceEntryNames } from "./entry-names";
const START_PREFIX = "start:";
const STOP_PREFIX = "stop:";
export {
PerformanceEntryNames,
}
interface GetEntriesOptions {
name?: string,
type?: string,
}
export default class PerformanceMonitor {
/**
* Starts a performance recording
* @param name Name of the recording
* @param id Specify an identifier appended to the measurement name
* @returns {void}
*/
static start(name: string, id?: string): void {
if (!supportsPerformanceApi()) {
return;
}
const key = buildKey(name, id);
if (!performance.getEntriesByName(key).length) {
console.warn(`Recording already started for: ${name}`);
return;
}
performance.mark(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 {void}
*/
static stop(name: string, id?: string): void {
if (!supportsPerformanceApi()) {
return;
}
const key = buildKey(name, id);
if (!performance.getEntriesByName(START_PREFIX + key).length) {
console.warn(`No recording started for: ${name}`);
return;
}
performance.mark(STOP_PREFIX + key);
performance.measure(
key,
START_PREFIX + key,
STOP_PREFIX + key,
);
this.clear(name, id);
}
static clear(name: string, id?: string): void {
if (!supportsPerformanceApi()) {
return;
}
const key = buildKey(name, id);
performance.clearMarks(START_PREFIX + key);
performance.clearMarks(STOP_PREFIX + key);
}
static getEntries({ name, type }: GetEntriesOptions = {}): PerformanceEntry[] {
if (!supportsPerformanceApi()) {
return;
}
if (!name && !type) {
return performance.getEntries();
} else if (!name) {
return performance.getEntriesByType(type);
} else {
return performance.getEntriesByName(name, type);
}
}
}
/**
* Tor browser does not support the Performance API
* @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}` : ''}`;
}