From a8f6c96bc24428fc779a48559d80e53d1c8eed80 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Thu, 31 Jul 2025 07:43:31 -0700 Subject: [PATCH] Static generators for Profile Trace Events (#52915) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52915 # Changelog: [Internal] This removes the use of `PerformanceTracer` instance in a serialization logic. Reviewed By: rubennorte Differential Revision: D78919220 fbshipit-source-id: 5c663ea77eb36eb7664623c1595308a9450f7825 --- ...HermesRuntimeSamplingProfileSerializer.cpp | 10 +++- .../jsinspector-modern/TracingAgent.cpp | 4 +- .../tracing/PerformanceTracer.cpp | 25 ++++---- .../tracing/PerformanceTracer.h | 20 ++++--- .../tracing/RuntimeSamplingProfile.h | 4 ++ ...imeSamplingProfileTraceEventSerializer.cpp | 57 ++++++++++++------- ...ntimeSamplingProfileTraceEventSerializer.h | 17 +++--- ...amplingProfileTraceEventSerializerTest.cpp | 22 +++---- 8 files changed, 93 insertions(+), 66 deletions(-) diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeSamplingProfileSerializer.cpp b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeSamplingProfileSerializer.cpp index 8b6532d2cb6..b81c9b7b6a0 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeSamplingProfileSerializer.cpp +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeSamplingProfileSerializer.cpp @@ -5,10 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -#include - #include "HermesRuntimeSamplingProfileSerializer.h" +#include + +#include + namespace facebook::react::jsinspector_modern::tracing { namespace { @@ -163,6 +165,10 @@ HermesRuntimeSamplingProfileSerializer::serializeToTracingSamplingProfile( return RuntimeSamplingProfile{ "Hermes", + // Hermes' Profile should be the source of truth for this, + // but it is safe to reuse the process ID here, since everything runs in + // the same process. + oscompat::getCurrentProcessId(), std::move(reconciledSamples), std::make_unique(std::move(hermesProfile))}; } diff --git a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp index e74c90c0497..80bade26692 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp @@ -111,9 +111,7 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) { dataCollectedCallback, TRACE_EVENT_CHUNK_SIZE); tracing::RuntimeSamplingProfileTraceEventSerializer serializer( - performanceTracer, - dataCollectedCallback, - PROFILE_TRACE_EVENT_CHUNK_SIZE); + dataCollectedCallback, PROFILE_TRACE_EVENT_CHUNK_SIZE); auto tracingProfile = instanceAgent_->collectTracingProfile(); serializer.serializeAndNotify( std::move(tracingProfile.runtimeSamplingProfile), diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp index b38535888db..16650e2ed41 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp @@ -305,46 +305,49 @@ void PerformanceTracer::reportEventLoopMicrotasks( }); } -folly::dynamic PerformanceTracer::getSerializedRuntimeProfileTraceEvent( - ThreadId threadId, +/* static */ TraceEvent PerformanceTracer::constructRuntimeProfileTraceEvent( RuntimeProfileId profileId, + ProcessId processId, + ThreadId threadId, HighResTimeStamp profileTimestamp) { // CDT prioritizes event timestamp over startTime metadata field. // https://fburl.com/lo764pf4 - return TraceEventSerializer::serialize(TraceEvent{ + return TraceEvent{ .id = profileId, .name = "Profile", .cat = "disabled-by-default-v8.cpu_profiler", .ph = 'P', .ts = profileTimestamp, - .pid = processId_, + .pid = processId, .tid = threadId, .args = folly::dynamic::object( "data", folly::dynamic::object( "startTime", highResTimeStampToTracingClockTimeStamp(profileTimestamp))), - }); + }; } -folly::dynamic PerformanceTracer::getSerializedRuntimeProfileChunkTraceEvent( - ThreadId threadId, +/* static */ TraceEvent +PerformanceTracer::constructRuntimeProfileChunkTraceEvent( RuntimeProfileId profileId, + ProcessId processId, + ProcessId threadId, HighResTimeStamp chunkTimestamp, - tracing::TraceEventProfileChunk&& traceEventProfileChunk) { - return TraceEventSerializer::serialize(TraceEvent{ + TraceEventProfileChunk&& traceEventProfileChunk) { + return TraceEvent{ .id = profileId, .name = "ProfileChunk", .cat = "disabled-by-default-v8.cpu_profiler", .ph = 'P', .ts = chunkTimestamp, - .pid = processId_, + .pid = processId, .tid = threadId, .args = folly::dynamic::object( "data", TraceEventSerializer::serializeProfileChunk( std::move(traceEventProfileChunk))), - }); + }; } } // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h index bcbb3232f4d..a94e548be96 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h @@ -116,21 +116,25 @@ class PerformanceTracer { void reportEventLoopMicrotasks(HighResTimeStamp start, HighResTimeStamp end); /** - * Create and serialize Profile Trace Event. - * \return serialized Trace Event that represents a Profile for CDT. + * Creates "Profile" Trace Event. + * + * Can be serialized to JSON with TraceEventSerializer::serialize. */ - folly::dynamic getSerializedRuntimeProfileTraceEvent( - ThreadId threadId, + static TraceEvent constructRuntimeProfileTraceEvent( RuntimeProfileId profileId, + ProcessId processId, + ThreadId threadId, HighResTimeStamp profileTimestamp); /** - * Create and serialize ProfileChunk Trace Event. - * \return serialized Trace Event that represents a Profile Chunk for CDT. + * Creates "ProfileChunk" Trace Event. + * + * Can be serialized to JSON with TraceEventSerializer::serialize. */ - folly::dynamic getSerializedRuntimeProfileChunkTraceEvent( - ProcessId threadId, + static TraceEvent constructRuntimeProfileChunkTraceEvent( RuntimeProfileId profileId, + ProcessId processId, + ProcessId threadId, HighResTimeStamp chunkTimestamp, TraceEventProfileChunk&& traceEventProfileChunk); diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfile.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfile.h index 7eb9d578c19..a6c593cba2d 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfile.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfile.h @@ -91,9 +91,11 @@ struct RuntimeSamplingProfile { RuntimeSamplingProfile( std::string runtimeName, + ProcessId processId, std::vector samples, std::unique_ptr rawRuntimeProfile) : runtimeName(std::move(runtimeName)), + processId(processId), samples(std::move(samples)), rawRuntimeProfile(std::move(rawRuntimeProfile)) {} @@ -109,6 +111,8 @@ struct RuntimeSamplingProfile { /// Name of the runtime, where sampling occurred: Hermes, V8, etc. std::string runtimeName; + /// The ID of the OS-level process where the sampling occurred. + ProcessId processId; /// List of recorded samples, should be chronologically sorted. std::vector samples; /// A unique pointer to the original raw runtime profile, collected from the diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp index 039b767c701..cff8e412dbd 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp @@ -5,10 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -#include - -#include "ProfileTreeNode.h" #include "RuntimeSamplingProfileTraceEventSerializer.h" +#include "PerformanceTracer.h" +#include "ProfileTreeNode.h" +#include "TraceEventSerializer.h" + +#include namespace facebook::react::jsinspector_modern::tracing { @@ -96,14 +98,16 @@ class ProfileTreeRootNode : public ProfileTreeNode { } // namespace void RuntimeSamplingProfileTraceEventSerializer::sendProfileTraceEvent( + ProcessId processId, ThreadId threadId, RuntimeProfileId profileId, HighResTimeStamp profileStartTimestamp) const { + auto traceEvent = PerformanceTracer::constructRuntimeProfileTraceEvent( + profileId, processId, threadId, profileStartTimestamp); folly::dynamic serializedTraceEvent = - performanceTracer_.getSerializedRuntimeProfileTraceEvent( - threadId, profileId, profileStartTimestamp); + TraceEventSerializer::serialize(std::move(traceEvent)); - notificationCallback_(folly::dynamic::array(serializedTraceEvent)); + notificationCallback_(folly::dynamic::array(std::move(serializedTraceEvent))); } void RuntimeSamplingProfileTraceEventSerializer::chunkEmptySample( @@ -127,18 +131,22 @@ void RuntimeSamplingProfileTraceEventSerializer::bufferProfileChunkTraceEvent( traceEventNodes.push_back(convertToTraceEventProfileNode(node)); } - traceEventBuffer_.push_back( - performanceTracer_.getSerializedRuntimeProfileChunkTraceEvent( - profileId, - chunk.threadId, - chunk.timestamp, - TraceEventProfileChunk{ - .cpuProfile = - TraceEventProfileChunk::CPUProfile{ - .nodes = std::move(traceEventNodes), - .samples = std::move(chunk.samples)}, - .timeDeltas = std::move(chunk.timeDeltas), - })); + auto traceEvent = PerformanceTracer::constructRuntimeProfileChunkTraceEvent( + profileId, + chunk.processId, + chunk.threadId, + chunk.timestamp, + TraceEventProfileChunk{ + .cpuProfile = + TraceEventProfileChunk::CPUProfile{ + .nodes = std::move(traceEventNodes), + .samples = std::move(chunk.samples)}, + .timeDeltas = std::move(chunk.timeDeltas), + }); + auto serializedTraceEvent = + TraceEventSerializer::serialize(std::move(traceEvent)); + + traceEventBuffer_.push_back(std::move(serializedTraceEvent)); } void RuntimeSamplingProfileTraceEventSerializer::processCallStack( @@ -202,12 +210,16 @@ void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify( HighResTimeStamp previousSampleTimestamp = tracingStartTime; HighResTimeStamp currentChunkTimestamp = tracingStartTime; - sendProfileTraceEvent(firstChunkThreadId, PROFILE_ID, tracingStartTime); + sendProfileTraceEvent( + profile.processId, firstChunkThreadId, PROFILE_ID, tracingStartTime); // There could be any number of new nodes in this chunk. Empty if all nodes // are already emitted in previous chunks. ProfileChunk chunk{ - profileChunkSize_, firstChunkThreadId, currentChunkTimestamp}; + profileChunkSize_, + profile.processId, + firstChunkThreadId, + currentChunkTimestamp}; NodeIdGenerator nodeIdGenerator{}; @@ -238,7 +250,10 @@ void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify( if (currentSampleThreadId != chunk.threadId || chunk.isFull()) { bufferProfileChunkTraceEvent(std::move(chunk), PROFILE_ID); chunk = ProfileChunk{ - profileChunkSize_, currentSampleThreadId, currentChunkTimestamp}; + profileChunkSize_, + profile.processId, + currentSampleThreadId, + currentChunkTimestamp}; } if (traceEventBuffer_.size() == traceEventChunkSize_) { diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h index 8b0bc3b5228..0f26efd5669 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h @@ -7,7 +7,6 @@ #pragma once -#include "PerformanceTracer.h" #include "ProfileTreeNode.h" #include "RuntimeSamplingProfile.h" @@ -37,9 +36,13 @@ class RuntimeSamplingProfileTraceEventSerializer { struct ProfileChunk { ProfileChunk( uint16_t chunkSize, + ProcessId chunkProcessId, ThreadId chunkThreadId, HighResTimeStamp chunkTimestamp) - : size(chunkSize), threadId(chunkThreadId), timestamp(chunkTimestamp) { + : size(chunkSize), + processId(chunkProcessId), + threadId(chunkThreadId), + timestamp(chunkTimestamp) { samples.reserve(size); timeDeltas.reserve(size); } @@ -56,13 +59,13 @@ class RuntimeSamplingProfileTraceEventSerializer { std::vector samples; std::vector timeDeltas; uint16_t size; + ProcessId processId; ThreadId threadId; HighResTimeStamp timestamp; }; public: /** - * \param performanceTracer A reference to PerformanceTracer instance. * \param notificationCallback A reference to a callback, which is called * when a chunk of trace events is ready to be sent. * \param traceEventChunkSize The maximum number of ProfileChunk trace @@ -71,13 +74,11 @@ class RuntimeSamplingProfileTraceEventSerializer { * that can be sent in a single ProfileChunk trace event. */ RuntimeSamplingProfileTraceEventSerializer( - PerformanceTracer& performanceTracer, std::function notificationCallback, uint16_t traceEventChunkSize, uint16_t profileChunkSize = 10) - : performanceTracer_(performanceTracer), - notificationCallback_(std::move(notificationCallback)), + : notificationCallback_(std::move(notificationCallback)), traceEventChunkSize_(traceEventChunkSize), profileChunkSize_(profileChunkSize) { traceEventBuffer_ = folly::dynamic::array(); @@ -96,12 +97,15 @@ class RuntimeSamplingProfileTraceEventSerializer { private: /** * Sends a single "Profile" Trace Event via notificationCallback_. + + * \param processId The id of the process, where the Profile was collected. * \param threadId The id of the thread, where the Profile was collected. * \param profileId The id of the Profile. * \param profileStartUnixTimestamp The Unix timestamp of the start of the * profile. */ void sendProfileTraceEvent( + ProcessId processId, ThreadId threadId, RuntimeProfileId profileId, HighResTimeStamp profileStartTimestamp) const; @@ -154,7 +158,6 @@ class RuntimeSamplingProfileTraceEventSerializer { */ void sendBufferedTraceEventsAndClear(); - PerformanceTracer& performanceTracer_; const std::function notificationCallback_; const uint16_t traceEventChunkSize_; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp index 3b44a41e7d7..45489c5cd3b 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp @@ -55,12 +55,12 @@ class RuntimeSamplingProfileTraceEventSerializerTest : public ::testing::Test { } RuntimeSamplingProfile createEmptyProfile() { - return {"TestRuntime", {}, {}}; + return {"TestRuntime", 1, {}, {}}; } RuntimeSamplingProfile createProfileWithSamples( std::vector samples) { - return {"TestRuntime", std::move(samples), {}}; + return {"TestRuntime", 1, std::move(samples), {}}; } }; @@ -68,7 +68,7 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptyProfile) { // Setup auto notificationCallback = createNotificationCallback(); RuntimeSamplingProfileTraceEventSerializer serializer( - PerformanceTracer::getInstance(), notificationCallback, 10); + notificationCallback, 10); auto profile = createEmptyProfile(); auto tracingStartTime = HighResTimeStamp::now(); @@ -86,7 +86,7 @@ TEST_F( // Setup auto notificationCallback = createNotificationCallback(); RuntimeSamplingProfileTraceEventSerializer serializer( - PerformanceTracer::getInstance(), notificationCallback, 10); + notificationCallback, 10); // [ foo ] // [ bar ] @@ -136,7 +136,7 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptySample) { // Setup auto notificationCallback = createNotificationCallback(); RuntimeSamplingProfileTraceEventSerializer serializer( - PerformanceTracer::getInstance(), notificationCallback, 10); + notificationCallback, 10); // Create an empty sample (no call stack) std::vector emptyCallStack; @@ -172,7 +172,7 @@ TEST_F( // Setup auto notificationCallback = createNotificationCallback(); RuntimeSamplingProfileTraceEventSerializer serializer( - PerformanceTracer::getInstance(), notificationCallback, 10); + notificationCallback, 10); // Create samples with different thread IDs std::vector callStack = { @@ -209,10 +209,7 @@ TEST_F( uint16_t traceEventChunkSize = 2; uint16_t profileChunkSize = 2; RuntimeSamplingProfileTraceEventSerializer serializer( - PerformanceTracer::getInstance(), - notificationCallback, - traceEventChunkSize, - profileChunkSize); + notificationCallback, traceEventChunkSize, profileChunkSize); // Create multiple samples std::vector callStack = { @@ -250,10 +247,7 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, ProfileChunkSizeLimit) { uint16_t profileChunkSize = 2; double samplesCount = 5; RuntimeSamplingProfileTraceEventSerializer serializer( - PerformanceTracer::getInstance(), - notificationCallback, - traceEventChunkSize, - profileChunkSize); + notificationCallback, traceEventChunkSize, profileChunkSize); // Create multiple samples std::vector callStack = {