track maximum cdp proxy messages from device queue size (#49945)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49945

Changelog:
[General][Internal] track maximum cdp messages from device queue size

Reviewed By: robhogan

Differential Revision: D70404011

fbshipit-source-id: 0191301b27f25ab91b2e9549dd14fd9e40bd825e
This commit is contained in:
Vitali Zaidman
2025-03-12 13:43:43 -07:00
committed by Facebook GitHub Bot
parent 2aeccf2b90
commit b899a0013b
3 changed files with 117 additions and 0 deletions
@@ -0,0 +1,72 @@
/**
* 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-local
* @format
* @oncall react_native
*/
// $FlowFixMe[cannot-resolve-module] libdef missing in RN OSS
import type {Timeout} from 'timers';
// $FlowFixMe[cannot-resolve-module] libdef missing in RN OSS
import {setTimeout} from 'timers';
const MAX_QUEUE_TIME_SPAN_MS = 3000;
export default class CDPMessagesQueueLogging {
#messageQueueSize = 0;
#maxMessageQueueSize = 0;
#messagesMemoryUsage = 0;
#maxMessageQueueCombinedStringSize = 0;
#loggingTimeout: Timeout | null = null;
#debounceStartMs = 0;
#onHighMessageQueueSize: (queueSize: number, memoryUsageMiB: number) => void;
constructor(
onHighMessageQueueSize: (queueSize: number, memoryUsageMiB: number) => void,
) {
this.#onHighMessageQueueSize = onHighMessageQueueSize;
}
messageReceived(messageSize: number) {
this.#messageQueueSize++;
this.#messagesMemoryUsage += messageSize;
if (this.#messageQueueSize > this.#maxMessageQueueSize) {
this.#maxMessageQueueSize = this.#messageQueueSize;
this.#maxMessageQueueCombinedStringSize = this.#messagesMemoryUsage;
// we only report when there were no higher queue size reached for MAX_QUEUE_TIME_SPAN_MS
if (this.#loggingTimeout) {
this.#loggingTimeout.refresh();
} else {
this.#debounceStartMs = new Date().getTime();
this.#loggingTimeout = setTimeout(() => {
this.#onHighMessageQueueSize(
this.#maxMessageQueueSize,
// JS uses around 2 bytes per character
this.#maxMessageQueueCombinedStringSize * 2,
);
this.#loggingTimeout = null;
this.#maxMessageQueueSize = 0;
this.#maxMessageQueueCombinedStringSize = 0;
}, MAX_QUEUE_TIME_SPAN_MS).unref();
}
}
}
messageProcessed(messageSize: number) {
this.#messageQueueSize--;
this.#messagesMemoryUsage -= messageSize;
}
}
@@ -28,6 +28,7 @@ import type {
} from './types';
import CDPMessagesLogging from './CDPMessagesLogging';
import CDPMessagesQueueLogging from './CDPMessagesQueueLogging';
import DeviceEventReporter from './DeviceEventReporter';
import * as fs from 'fs';
import invariant from 'invariant';
@@ -37,6 +38,7 @@ import WS from 'ws';
const debug = require('debug')('Metro:InspectorProxy');
const PAGES_POLLING_INTERVAL = 1000;
const MIN_MESSAGE_QUEUE_BYTES_TO_REPORT = 2 * 1024 * 1024; // 2 MiB
// Prefix for script URLs that are alphanumeric IDs. See comment in #processMessageFromDeviceLegacy method for
// more details.
@@ -88,6 +90,9 @@ export default class Device {
// async fetch.
#messageFromDeviceQueue: Promise<void> = Promise.resolve();
// Logging reporting the maximum size of cdp message coming from device in the queue for processing
#messageFromDeviceQueueLogging: ?CDPMessagesQueueLogging;
// Stores socket connection between Inspector Proxy and device.
#deviceSocket: WS;
@@ -130,6 +135,7 @@ export default class Device {
// A base HTTP(S) URL to the server, relative to this server.
#serverRelativeBaseUrl: URL;
// Logging reporting batches of cdp messages
#cdpMessagesLogging: CDPMessagesLogging;
constructor(deviceOptions: DeviceOptions) {
@@ -152,6 +158,35 @@ export default class Device {
this.#id = id;
this.#name = name;
this.#app = app;
this.#messageFromDeviceQueueLogging = new CDPMessagesQueueLogging(
(maxCDPMessageQueueSize: number, maxCDPMessageQueueMemory: number) => {
if (maxCDPMessageQueueMemory > MIN_MESSAGE_QUEUE_BYTES_TO_REPORT) {
debug(
"CDP messages proxy queue reached='%d' messages using at least '%sMiB' coming from device='%s' for app='%s'",
maxCDPMessageQueueSize,
String(maxCDPMessageQueueMemory / 1024 / 1024).slice(0, 6),
name,
app,
);
const debuggerSessionIDs = {
appId: app,
deviceId: id,
deviceName: name,
pageId: null,
};
eventReporter?.logEvent({
type: 'device_high_message_queue',
maxCDPMessageQueueSize,
maxCDPMessageQueueMemory,
...debuggerSessionIDs,
});
}
},
);
this.#deviceSocket = socket;
this.#projectRoot = projectRoot;
this.#serverRelativeBaseUrl = serverRelativeBaseUrl;
@@ -171,6 +206,7 @@ export default class Device {
// $FlowFixMe[incompatible-call]
this.#deviceSocket.on('message', (message: string) => {
this.#messageFromDeviceQueueLogging?.messageReceived(message.length);
this.#messageFromDeviceQueue = this.#messageFromDeviceQueue
.then(async () => {
const parsedMessage = JSON.parse(message);
@@ -199,6 +235,9 @@ export default class Device {
loggingError,
);
}
})
.finally(() => {
this.#messageFromDeviceQueueLogging?.messageProcessed(message.length);
});
});
// Sends 'getPages' request to device every PAGES_POLLING_INTERVAL milliseconds.
@@ -118,6 +118,12 @@ export type ReportableEvent =
maxEventLoopDelayPercent: number,
duration: number,
...DebuggerSessionIDs,
}
| {
type: 'device_high_message_queue',
maxCDPMessageQueueSize: number,
maxCDPMessageQueueMemory: number,
...DebuggerSessionIDs,
};
/**