Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 47 additions & 4 deletions front_end/core/host/RNPerfMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class RNPerfMetrics {
#telemetryInfo: Object = {};
// map of panel location to panel name
#currentPanels = new Map<PanelLocation, string>();
#initialResourcesLoadedInfo: null|{count: number, time: number} = null;

isEnabled(): boolean {
return globalThis.enableReactNativePerfMetrics === true;
Expand Down Expand Up @@ -186,6 +187,12 @@ class RNPerfMetrics {
});
}

initialResourcesLoaded(info: {count: number, time: number}): void {
// eslint-disable-next-line no-console
console.info('Initial %d resources are loaded at %sms since launch', info.count, info.time);
this.#initialResourcesLoadedInfo = info;
}

fuseboxSetClientMetadataStarted(): void {
this.sendEvent({eventName: 'FuseboxSetClientMetadataStarted'});
}
Expand All @@ -206,6 +213,32 @@ class RNPerfMetrics {
}
}

tryReportingCdpLowRoundtrip(cdpLowRoundtripStartTime: number): boolean {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the "low roundtrip" naming — not quite descriptive enough.

Maybe

  • tryReportAchievedResponsiveRoundtrip(startTime: number)
  • tryReportBackendRoundtripPerfSettled
  • tryReportStartupLivenessPingSettled (probably best?)
  • tryReportStartupPingSettled

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, let's add a doc comment above this method to describe how we define/measure this metric.

if (this.#initialResourcesLoadedInfo === null) {
return false;
}

// if the roundtrip is fine for a long time, just take the initial resources loading time
// if it got better only after the initial resources were loaded, take the cdp low roundtrip time instead
const startupTime = Math.max(cdpLowRoundtripStartTime, this.#initialResourcesLoadedInfo.time);

// eslint-disable-next-line no-console
console.info('The app had a low CDP roundtrip at %sms since launch', cdpLowRoundtripStartTime);
// eslint-disable-next-line no-console
console.info('Startup time is %sms', startupTime);

this.sendEvent({
eventName: 'StartUpFinished',
params: {
bundleCount: this.#initialResourcesLoadedInfo.count,
duration: startupTime,
initialResourcesLoadedTime: this.#initialResourcesLoadedInfo.time,
cdpLowRoundtripStartTime,
}
});
return true;
}

heapSnapshotStarted(): void {
this.sendEvent({
eventName: 'MemoryPanelActionStarted',
Expand Down Expand Up @@ -489,12 +522,22 @@ export type StackTraceFrameUrlResolutionFailed = Readonly<{
}>,
}>;

export type StartUpFinished = Readonly<{
eventName: 'StartUpFinished',
params: Readonly<{
bundleCount: number,
duration: number,
initialResourcesLoadedTime: number,
cdpLowRoundtripStartTime: number,
}>,
}>;

export type ReactNativeChromeDevToolsEvent =
EntrypointLoadingStartedEvent|EntrypointLoadingFinishedEvent|DebuggerReadyEvent|BrowserVisibilityChangeEvent|
BrowserErrorEvent|RemoteDebuggingTerminatedEvent|DeveloperResourceLoadingStartedEvent|
DeveloperResourceLoadingFinishedEvent|FuseboxSetClientMetadataStartedEvent|FuseboxSetClientMetadataFinishedEvent|
MemoryPanelActionStartedEvent|MemoryPanelActionFinishedEvent|PanelShownEvent|PanelClosedEvent|
StackTraceSymbolicationSucceeded|StackTraceSymbolicationFailed|StackTraceFrameUrlResolutionSucceeded|
StackTraceFrameUrlResolutionFailed;
DeveloperResourceLoadingFinishedEvent|FuseboxSetClientMetadataStartedEvent|
FuseboxSetClientMetadataFinishedEvent|MemoryPanelActionStartedEvent|MemoryPanelActionFinishedEvent|
PanelShownEvent|PanelClosedEvent|StackTraceSymbolicationSucceeded|StackTraceSymbolicationFailed|
StackTraceFrameUrlResolutionSucceeded|StackTraceFrameUrlResolutionFailed|StartUpFinished;

export type DecoratedReactNativeChromeDevToolsEvent = CommonEventFields&ReactNativeChromeDevToolsEvent;
22 changes: 22 additions & 0 deletions front_end/core/sdk/PageResourceLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const UIStrings = {
const str_ = i18n.i18n.registerUIStrings('core/sdk/PageResourceLoader.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

const MS_WAIT_ENSURING_ALL_RESOUCES_ARE_LOADED = 3000;

export interface ExtensionInitiator {
target: null;
frameId: null;
Expand Down Expand Up @@ -82,6 +84,8 @@ interface LoadQueueEntry {
*/
export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
#currentlyLoading = 0;
#initialResourcesLoadedTimeout: number|null = null;
#reportedInitialResourcesLoaded = false;
#currentlyLoadingPerTarget = new Map<Protocol.Target.TargetID|'main', number>();
readonly #maxConcurrentLoads: number;
#pageResources = new Map<string, PageResource>();
Expand Down Expand Up @@ -354,6 +358,24 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
}
Host.rnPerfMetrics.developerResourceLoadingFinished(
parsedURL, Host.UserMetrics.DeveloperResourceLoaded.FALLBACK_AFTER_FAILURE, result);

// Wait for several seconds to ensure no new resources were loaded,
// possibly by the resources that just finished loading
const resourceLoadingTime = performance.now();
if (this.#initialResourcesLoadedTimeout) {
window.clearTimeout(this.#initialResourcesLoadedTimeout);
}
this.#initialResourcesLoadedTimeout = window.setTimeout(() => {
const allResourcesLoaded = this.#currentlyLoading === 0;
if (allResourcesLoaded && !this.#reportedInitialResourcesLoaded) {
Host.rnPerfMetrics.initialResourcesLoaded({
count: this.getNumberOfResources().resources,
time: Math.round(resourceLoadingTime)
});
this.#reportedInitialResourcesLoaded = true;
}
}, MS_WAIT_ENSURING_ALL_RESOUCES_ARE_LOADED);

return result;
}

Expand Down
54 changes: 47 additions & 7 deletions front_end/entrypoints/inspector_main/InspectorMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import * as UI from '../../ui/legacy/legacy.js';

import nodeIconStyles from './nodeIcon.css.js';

const MS_BETWEEN_ROUNDTRIP_MEASUREMENTS = 3000;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious if this should be more frequent, as (IIUC) this will create a very coarse, 3s incremented, metric? What about 1s/500ms?

const MS_MAX_LOW_ROUNDTRIP = 200;

const UIStrings = {
/**
* @description Text that refers to the main target. The main target is the primary webpage that
Expand Down Expand Up @@ -46,6 +49,8 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
let inspectorMainImplInstance: InspectorMainImpl;

export class InspectorMainImpl implements Common.Runnable.Runnable {
#consecutiveLowRoundtrips = 0;

static instance(opts: {
forceNew: boolean|null,
} = {forceNew: null}): InspectorMainImpl {
Expand All @@ -57,6 +62,40 @@ export class InspectorMainImpl implements Common.Runnable.Runnable {
return inspectorMainImplInstance;
}

async #measureMainConnectionRoundtrip(debuggerModel: SDK.DebuggerModel.DebuggerModel): Promise<void> {
if (!debuggerModel.debuggerEnabled()) {
return;
}

const startMs = Date.now();
// Issues and waits for a response from a simple "Debugger.enable" when the debugger is enabled
// which noops and retuns a truthy response:
// https://github.com/facebook/hermes/blob/ae235193b9329867afaa2838183cbffa34aca098/API/hermes/cdp/DebuggerDomainAgent.cpp#L224-L228
// https://github.com/facebook/hermes/blob/ae235193b9329867afaa2838183cbffa34aca098/API/hermes/cdp/DebuggerDomainAgent.cpp#L183-L185
// It measures the round trip time for CDP message after being queued in the CDP queue in each direction.
await debuggerModel.syncDebuggerId();
const roundtripTime = Date.now() - startMs;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we use performance.now()?


if (roundtripTime > MS_MAX_LOW_ROUNDTRIP) {
this.#consecutiveLowRoundtrips = 0;
} else {
this.#consecutiveLowRoundtrips++;
}

let reportedLowRoundrip = false;
if (this.#consecutiveLowRoundtrips >= 2) {
reportedLowRoundrip = Host.rnPerfMetrics.tryReportingCdpLowRoundtrip(
Math.round(performance.now() - ((this.#consecutiveLowRoundtrips - 1) * MS_BETWEEN_ROUNDTRIP_MEASUREMENTS))
);
}

if (!reportedLowRoundrip) {
setTimeout(() => {
void this.#measureMainConnectionRoundtrip(debuggerModel);
}, MS_BETWEEN_ROUNDTRIP_MEASUREMENTS);
}
}

async run(): Promise<void> {
let firstCall = true;
await SDK.Connections.initMainConnection(async () => {
Expand Down Expand Up @@ -94,13 +133,14 @@ export class InspectorMainImpl implements Common.Runnable.Runnable {
}
firstCall = false;

if (waitForDebuggerInPage) {
const debuggerModel = target.model(SDK.DebuggerModel.DebuggerModel);
if (debuggerModel) {
if (!debuggerModel.isReadyToPause()) {
await debuggerModel.once(SDK.DebuggerModel.Events.DebuggerIsReadyToPause);
}
debuggerModel.pause();
const debuggerModel = target.model(SDK.DebuggerModel.DebuggerModel);
if (debuggerModel) {
void this.#measureMainConnectionRoundtrip(debuggerModel);
if (waitForDebuggerInPage) {
if (!debuggerModel.isReadyToPause()) {
await debuggerModel.once(SDK.DebuggerModel.Events.DebuggerIsReadyToPause);
}
debuggerModel.pause();
}
}

Expand Down
Loading