Skip to content

Commit

Permalink
Logging in GlobalPerformanceLogger via WebPerformance API (#36402)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #36402

## Changelog:

[Internal] -

Makes `GlobalPerformanceLogger` (`IPerformanceLogger` API), additionally use the native WebPerformance implementation (`Performance` and `PerformanceObserver` APIs) to log points/timespans as marks/measures correspondingly.

This will ultimately help to converge performance logging facilities across platforms and language boundaries.

In a shorter term, it can serve as a good case study of using the Web Performance API implementation.

Note that this implementation is disabled by default (controlled by the RN feature flag), and will be enabled separately via na experiment.

Reviewed By: rubennorte

Differential Revision: D43873560

fbshipit-source-id: 22e2d787c8f22d2f67556dfe4bf175743eca2caf
  • Loading branch information
rshest authored and facebook-github-bot committed Mar 10, 2023
1 parent 3cd97e4 commit 48c7adc
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 50 deletions.
4 changes: 2 additions & 2 deletions Libraries/Network/__tests__/XMLHttpRequest-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

'use strict';

import createPerformanceLogger from '../../Utilities/createPerformanceLogger';

const createPerformanceLogger =
require('../../Utilities/createPerformanceLogger').default;
const GlobalPerformanceLogger = require('../../Utilities/GlobalPerformanceLogger');
const Platform = require('../../Utilities/Platform');
const XMLHttpRequest = require('../XMLHttpRequest');
Expand Down
2 changes: 1 addition & 1 deletion Libraries/ReactNative/AppRegistry.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import type * as React from 'react';
import type {IPerformanceLogger} from '../Utilities/createPerformanceLogger';
import type {IPerformanceLogger} from '../Utilities/IPerformanceLogger';

type Task = (taskData: any) => Promise<void>;
type TaskProvider = () => Task;
Expand Down
10 changes: 7 additions & 3 deletions Libraries/ReactNative/ReactNativeFeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @flow strict
* @format
*/

'use strict';

export type FeatureFlags = {|
/**
* Function used to enable / disabled Layout Animations in React Native.
Expand Down Expand Up @@ -38,6 +36,11 @@ export type FeatureFlags = {|
* traffic.
*/
animatedShouldUseSingleOp: () => boolean,
/**
* Enables GlobalPerformanceLogger replacement with a WebPerformance API based
* implementation
*/
isGlobalWebPerformanceLoggerEnabled: () => boolean,
|};

const ReactNativeFeatureFlags: FeatureFlags = {
Expand All @@ -46,6 +49,7 @@ const ReactNativeFeatureFlags: FeatureFlags = {
shouldPressibilityUseW3CPointerEventsForHover: () => false,
animatedShouldDebounceQueueFlush: () => false,
animatedShouldUseSingleOp: () => false,
isGlobalWebPerformanceLoggerEnabled: () => false,
};

module.exports = ReactNativeFeatureFlags;
13 changes: 12 additions & 1 deletion Libraries/Utilities/GlobalPerformanceLogger.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,26 @@

import type {IPerformanceLogger} from './createPerformanceLogger';

import ReactNativeFeatureFlags from '../ReactNative/ReactNativeFeatureFlags';
import NativePerformance from '../WebPerformance/NativePerformance';
import createPerformanceLogger from './createPerformanceLogger';

function isLoggingForWebPerformance(): boolean {
return (
NativePerformance != null &&
ReactNativeFeatureFlags.isGlobalWebPerformanceLoggerEnabled()
);
}

/**
* This is a global shared instance of IPerformanceLogger that is created with
* createPerformanceLogger().
* This logger should be used only for global performance metrics like the ones
* that are logged during loading bundle. If you want to log something from your
* React component you should use PerformanceLoggerContext instead.
*/
const GlobalPerformanceLogger: IPerformanceLogger = createPerformanceLogger();
const GlobalPerformanceLogger: IPerformanceLogger = createPerformanceLogger(
isLoggingForWebPerformance(),
);

module.exports = GlobalPerformanceLogger;
File renamed without changes.
49 changes: 49 additions & 0 deletions Libraries/Utilities/IPerformanceLogger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/

export type Timespan = {
startTime: number,
endTime?: number,
totalTime?: number,
startExtras?: Extras,
endExtras?: Extras,
};

// Extra values should be serializable primitives
export type ExtraValue = number | string | boolean;

export type Extras = {[key: string]: ExtraValue};

export interface IPerformanceLogger {
addTimespan(
key: string,
startTime: number,
endTime: number,
startExtras?: Extras,
endExtras?: Extras,
): void;
append(logger: IPerformanceLogger): void;
clear(): void;
clearCompleted(): void;
close(): void;
currentTimestamp(): number;
getExtras(): $ReadOnly<{[key: string]: ?ExtraValue, ...}>;
getPoints(): $ReadOnly<{[key: string]: ?number, ...}>;
getPointExtras(): $ReadOnly<{[key: string]: ?Extras, ...}>;
getTimespans(): $ReadOnly<{[key: string]: ?Timespan, ...}>;
hasTimespan(key: string): boolean;
isClosed(): boolean;
logEverything(): void;
markPoint(key: string, timestamp?: number, extras?: Extras): void;
removeExtra(key: string): ?ExtraValue;
setExtra(key: string, value: ExtraValue): void;
startTimespan(key: string, timestamp?: number, extras?: Extras): void;
stopTimespan(key: string, timestamp?: number, extras?: Extras): void;
}
98 changes: 55 additions & 43 deletions Libraries/Utilities/createPerformanceLogger.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,28 @@
* @format
*/

const Systrace = require('../Performance/Systrace');
const infoLog = require('./infoLog');
import type {
Extras,
ExtraValue,
IPerformanceLogger,
Timespan,
} from './IPerformanceLogger';

export type Timespan = {
startTime: number,
endTime?: number,
totalTime?: number,
startExtras?: Extras,
endExtras?: Extras,
};

// Extra values should be serializable primitives
export type ExtraValue = number | string | boolean;

export type Extras = {[key: string]: ExtraValue};

export interface IPerformanceLogger {
addTimespan(
key: string,
startTime: number,
endTime: number,
startExtras?: Extras,
endExtras?: Extras,
): void;
append(logger: IPerformanceLogger): void;
clear(): void;
clearCompleted(): void;
close(): void;
currentTimestamp(): number;
getExtras(): $ReadOnly<{[key: string]: ?ExtraValue, ...}>;
getPoints(): $ReadOnly<{[key: string]: ?number, ...}>;
getPointExtras(): $ReadOnly<{[key: string]: ?Extras, ...}>;
getTimespans(): $ReadOnly<{[key: string]: ?Timespan, ...}>;
hasTimespan(key: string): boolean;
isClosed(): boolean;
logEverything(): void;
markPoint(key: string, timestamp?: number, extras?: Extras): void;
removeExtra(key: string): ?ExtraValue;
setExtra(key: string, value: ExtraValue): void;
startTimespan(key: string, timestamp?: number, extras?: Extras): void;
stopTimespan(key: string, timestamp?: number, extras?: Extras): void;
}
import * as Systrace from '../Performance/Systrace';
import Performance from '../WebPerformance/Performance';
import infoLog from './infoLog';

const _cookies: {[key: string]: number, ...} = {};

const PRINT_TO_CONSOLE: false = false; // Type as false to prevent accidentally committing `true`;

// This is the prefix for optional logging points/timespans as marks/measures via Performance API,
// used to separate these internally from other marks/measures
const WEB_PERFORMANCE_PREFIX = 'global_perf_';

// TODO: Remove once T143070419 is done
const performance = new Performance();

export const getCurrentTimestamp: () => number =
global.nativeQPLTimestamp ?? global.performance.now.bind(global.performance);

Expand All @@ -64,6 +39,11 @@ class PerformanceLogger implements IPerformanceLogger {
_points: {[key: string]: ?number} = {};
_pointExtras: {[key: string]: ?Extras, ...} = {};
_closed: boolean = false;
_isLoggingForWebPerformance: boolean = false;

constructor(isLoggingForWebPerformance?: boolean) {
this._isLoggingForWebPerformance = isLoggingForWebPerformance === true;
}

addTimespan(
key: string,
Expand Down Expand Up @@ -95,6 +75,13 @@ class PerformanceLogger implements IPerformanceLogger {
startExtras,
endExtras,
};

if (this._isLoggingForWebPerformance) {
performance.measure(`${WEB_PERFORMANCE_PREFIX}_${key}`, {
start: startTime,
end: endTime,
});
}
}

append(performanceLogger: IPerformanceLogger) {
Expand Down Expand Up @@ -209,6 +196,12 @@ class PerformanceLogger implements IPerformanceLogger {
if (extras) {
this._pointExtras[key] = extras;
}

if (this._isLoggingForWebPerformance) {
performance.mark(`${WEB_PERFORMANCE_PREFIX}_${key}`, {
startTime: timestamp,
});
}
}

removeExtra(key: string): ?ExtraValue {
Expand Down Expand Up @@ -270,6 +263,12 @@ class PerformanceLogger implements IPerformanceLogger {
if (PRINT_TO_CONSOLE) {
infoLog('PerformanceLogger.js', 'start: ' + key);
}

if (this._isLoggingForWebPerformance) {
performance.mark(`${WEB_PERFORMANCE_PREFIX}_timespan_start_${key}`, {
startTime: timestamp,
});
}
}

stopTimespan(
Expand Down Expand Up @@ -315,14 +314,27 @@ class PerformanceLogger implements IPerformanceLogger {
Systrace.endAsyncEvent(key, _cookies[key]);
delete _cookies[key];
}

if (this._isLoggingForWebPerformance) {
performance.measure(`${WEB_PERFORMANCE_PREFIX}_${key}`, {
start: `${WEB_PERFORMANCE_PREFIX}_timespan_start_${key}`,
end: timestamp,
});
}
}
}

// Re-exporting for backwards compatibility with all the clients that
// may still import it from this module.
export type {Extras, ExtraValue, IPerformanceLogger, Timespan};

/**
* This function creates performance loggers that can be used to collect and log
* various performance data such as timespans, points and extras.
* The loggers need to have minimal overhead since they're used in production.
*/
export default function createPerformanceLogger(): IPerformanceLogger {
return new PerformanceLogger();
export default function createPerformanceLogger(
isLoggingForWebPerformance?: boolean,
): IPerformanceLogger {
return new PerformanceLogger(isLoggingForWebPerformance);
}

0 comments on commit 48c7adc

Please sign in to comment.