From 4d2c21254435048a7a63384ecd3d5ec208b07206 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 11 Dec 2023 13:11:33 -0500 Subject: [PATCH] - Dynamically load BrowserPerformanceMeasurement and capture perf measurements if session storage flag is enabled. - Update browser perf doc. --- lib/msal-browser/docs/performance.md | 16 ++++ .../src/telemetry/BrowserPerformanceClient.ts | 79 ++++++++++++++++++- .../BrowserPerformanceMeasurement.ts | 3 - .../src/utils/BrowserConstants.ts | 2 + .../BrowserPerformanceClient.spec.ts | 6 +- .../performance/IPerformanceMeasurement.ts | 3 - 6 files changed, 98 insertions(+), 11 deletions(-) diff --git a/lib/msal-browser/docs/performance.md b/lib/msal-browser/docs/performance.md index 39b7f842ea..d8e90dfce9 100644 --- a/lib/msal-browser/docs/performance.md +++ b/lib/msal-browser/docs/performance.md @@ -109,3 +109,19 @@ const callbackId: string = msalInstance.addPerformanceCallback((events: Performa const removed: boolean = msalInstance.removePerformanceCallback(callbackId); ``` + +### Measuring browser performance + +Browser performance measurements are disabled by default due to significant performance overhead they impose. +Applications that want to enable performance measurements reported to the browser's performance timeline should: + +1. Open browser developer tools + - Edge, Chrome and Firefox browsers: press F12 + - Safari: go into Safari's preferences (`Safari Menu` > `Preferences`), select the `Advanced Tab` and enable `Show features for web developers`. Once that menu is enabled, you will find the developer console by clicking on `Develop` > `Show Javascript Console` +2. Navigate to `Session Storage`: + - [Edge](https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/storage/sessionstorage) + - [Chrome](https://developer.chrome.com/docs/devtools/storage/sessionstorage) + - [Firefox](https://firefox-source-docs.mozilla.org/devtools-user/storage_inspector/local_storage_session_storage) + - Safari: navigate to `Storage` tab and expand `Session Storage` +3. Select target domain +4. Add `msal.browser.performance.enabled` key to `Session Stirage`, set it's value to `1`, refresh the page and check the browser's performance timeline. diff --git a/lib/msal-browser/src/telemetry/BrowserPerformanceClient.ts b/lib/msal-browser/src/telemetry/BrowserPerformanceClient.ts index d0bdc5a05b..b218862800 100644 --- a/lib/msal-browser/src/telemetry/BrowserPerformanceClient.ts +++ b/lib/msal-browser/src/telemetry/BrowserPerformanceClient.ts @@ -12,14 +12,35 @@ import { PerformanceEvent, PerformanceEvents, PreQueueEvent, + SubMeasurement, } from "@azure/msal-common"; import { Configuration } from "../config/Configuration"; import { name, version } from "../packageMetadata"; +import { + BROWSER_PERF_ENABLED_KEY, + BrowserCacheLocation, +} from "../utils/BrowserConstants"; export class BrowserPerformanceClient extends PerformanceClient implements IPerformanceClient { + private static PERF_MEASUREMENT_MODULE = (() => { + let sessionStorage: Storage | undefined; + try { + sessionStorage = window[BrowserCacheLocation.SessionStorage]; + const perfEnabled = sessionStorage?.getItem( + BROWSER_PERF_ENABLED_KEY + ); + if (Number(perfEnabled) === 1) { + return import("./BrowserPerformanceMeasurement"); + } + // Mute errors if it's a non-browser environment or cookies are blocked. + } catch (e) {} + + return undefined; + })(); + constructor(configuration: Configuration, intFields?: Set) { super( configuration.auth.clientId, @@ -47,6 +68,34 @@ export class BrowserPerformanceClient return document.visibilityState?.toString() || null; } + private deleteIncompleteSubMeasurements( + inProgressEvent: InProgressPerformanceEvent + ): void { + void BrowserPerformanceClient.PERF_MEASUREMENT_MODULE?.then( + (module) => { + const rootEvent = this.eventsByCorrelationId.get( + inProgressEvent.event.correlationId + ); + const isRootEvent = + rootEvent && + rootEvent.eventId === inProgressEvent.event.eventId; + const incompleteMeasurements: SubMeasurement[] = []; + if (isRootEvent && rootEvent?.incompleteSubMeasurements) { + rootEvent.incompleteSubMeasurements.forEach( + (subMeasurement: SubMeasurement) => { + incompleteMeasurements.push({ ...subMeasurement }); + } + ); + } + // Clean up remaining marks for incomplete sub-measurements + module.BrowserPerformanceMeasurement.flushMeasurements( + inProgressEvent.event.correlationId, + incompleteMeasurements + ); + } + ); + } + supportsBrowserPerformanceNow(): boolean { return ( typeof window !== "undefined" && @@ -78,17 +127,41 @@ export class BrowserPerformanceClient ? window.performance.now() : undefined; + const browserMeasurement = + BrowserPerformanceClient.PERF_MEASUREMENT_MODULE?.then((module) => { + return new module.BrowserPerformanceMeasurement( + measureName, + inProgressEvent.event.correlationId + ); + }); + void browserMeasurement?.then((measurement) => + measurement.startMeasurement() + ); + return { ...inProgressEvent, end: ( event?: Partial ): PerformanceEvent | null => { - return inProgressEvent.end({ + const res = inProgressEvent.end({ ...event, startPageVisibility, endPageVisibility: this.getPageVisibility(), - durationMs: this.getPerformanceDurationMs(startTime), + durationMs: this.getPerfDurationMs(startTime), }); + void browserMeasurement?.then((measurement) => + measurement.endMeasurement() + ); + this.deleteIncompleteSubMeasurements(inProgressEvent); + + return res; + }, + discard: () => { + inProgressEvent.discard(); + void browserMeasurement?.then((measurement) => + measurement.flushMeasurement() + ); + this.deleteIncompleteSubMeasurements(inProgressEvent); }, }; } @@ -192,7 +265,7 @@ export class BrowserPerformanceClient * @param startTime {DOMHighResTimeStamp | undefined} * @returns {number | undefined} */ - private getPerformanceDurationMs( + private getPerfDurationMs( startTime: DOMHighResTimeStamp | undefined ): number | undefined { if (!startTime || !this.supportsBrowserPerformanceNow()) { diff --git a/lib/msal-browser/src/telemetry/BrowserPerformanceMeasurement.ts b/lib/msal-browser/src/telemetry/BrowserPerformanceMeasurement.ts index 1719fe9966..55b6dc8dca 100644 --- a/lib/msal-browser/src/telemetry/BrowserPerformanceMeasurement.ts +++ b/lib/msal-browser/src/telemetry/BrowserPerformanceMeasurement.ts @@ -5,9 +5,6 @@ import { IPerformanceMeasurement, SubMeasurement } from "@azure/msal-common"; -/** - * @deprecated This class will be removed in a future major version - */ export class BrowserPerformanceMeasurement implements IPerformanceMeasurement { private readonly measureName: string; private readonly correlationId: string; diff --git a/lib/msal-browser/src/utils/BrowserConstants.ts b/lib/msal-browser/src/utils/BrowserConstants.ts index 1f15799a6f..93692956e9 100644 --- a/lib/msal-browser/src/utils/BrowserConstants.ts +++ b/lib/msal-browser/src/utils/BrowserConstants.ts @@ -239,3 +239,5 @@ export const CacheLookupPolicy = { } as const; export type CacheLookupPolicy = (typeof CacheLookupPolicy)[keyof typeof CacheLookupPolicy]; + +export const BROWSER_PERF_ENABLED_KEY = "msal.browser.performance.enabled"; diff --git a/lib/msal-browser/test/telemetry/BrowserPerformanceClient.spec.ts b/lib/msal-browser/test/telemetry/BrowserPerformanceClient.spec.ts index cdd15efef5..659eec9508 100644 --- a/lib/msal-browser/test/telemetry/BrowserPerformanceClient.spec.ts +++ b/lib/msal-browser/test/telemetry/BrowserPerformanceClient.spec.ts @@ -70,9 +70,11 @@ describe("BrowserPerformanceClient.ts", () => { const result = measurement.end(); - console.log(JSON.stringify(result, null, 2)); - expect(result?.durationMs).toBe(50); + expect( + // @ts-ignore + BrowserPerformanceClient.PERF_MEASUREMENT_MODULE + ).toBeUndefined(); }); it("captures page visibilityState", () => { diff --git a/lib/msal-common/src/telemetry/performance/IPerformanceMeasurement.ts b/lib/msal-common/src/telemetry/performance/IPerformanceMeasurement.ts index 2dd701b51b..a912cf08ff 100644 --- a/lib/msal-common/src/telemetry/performance/IPerformanceMeasurement.ts +++ b/lib/msal-common/src/telemetry/performance/IPerformanceMeasurement.ts @@ -3,9 +3,6 @@ * Licensed under the MIT License. */ -/** - * @deprecated This class will be removed in a future major version - */ export interface IPerformanceMeasurement { startMeasurement(): void; endMeasurement(): void;