From b899a0013b3b9319ee3fed9bcb166fed94373a2f Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Wed, 12 Mar 2025 13:43:43 -0700 Subject: [PATCH] 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 --- .../CDPMessagesQueueLogging.js | 72 +++++++++++++++++++ .../src/inspector-proxy/Device.js | 39 ++++++++++ .../dev-middleware/src/types/EventReporter.js | 6 ++ 3 files changed, 117 insertions(+) create mode 100644 packages/dev-middleware/src/inspector-proxy/CDPMessagesQueueLogging.js diff --git a/packages/dev-middleware/src/inspector-proxy/CDPMessagesQueueLogging.js b/packages/dev-middleware/src/inspector-proxy/CDPMessagesQueueLogging.js new file mode 100644 index 00000000000..a732f044e3c --- /dev/null +++ b/packages/dev-middleware/src/inspector-proxy/CDPMessagesQueueLogging.js @@ -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; + } +} diff --git a/packages/dev-middleware/src/inspector-proxy/Device.js b/packages/dev-middleware/src/inspector-proxy/Device.js index fc812b44f7c..fe4d7533e8d 100644 --- a/packages/dev-middleware/src/inspector-proxy/Device.js +++ b/packages/dev-middleware/src/inspector-proxy/Device.js @@ -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 = 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. diff --git a/packages/dev-middleware/src/types/EventReporter.js b/packages/dev-middleware/src/types/EventReporter.js index 91d08a28f10..047417461f3 100644 --- a/packages/dev-middleware/src/types/EventReporter.js +++ b/packages/dev-middleware/src/types/EventReporter.js @@ -118,6 +118,12 @@ export type ReportableEvent = maxEventLoopDelayPercent: number, duration: number, ...DebuggerSessionIDs, + } + | { + type: 'device_high_message_queue', + maxCDPMessageQueueSize: number, + maxCDPMessageQueueMemory: number, + ...DebuggerSessionIDs, }; /**