From e64dce582ad5a902c499ceafdcb49aa0f743eaab Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Mon, 1 Sep 2025 07:12:55 -0700 Subject: [PATCH] Set threshold for a number of unique nodes in ProfileChunk (#53536) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53536 # Changelog: [Internal] For every chunk, we already have a threshold for the number of samples captured in this chunk. There could be really tall call stacks, where we could record hundreds of unique nodes, which makes the chunk already big enough for a CDP traffic on android. We are adding a threshold for a number of unique nodes in a single chunk. If the chunk has a greater number of nodes recorded, it will be dispatched over CDP. Reviewed By: huntie Differential Revision: D81339677 fbshipit-source-id: 388d14c64c4c3f60918a8526025f79d19d397cb4 --- ...imeSamplingProfileTraceEventSerializer.cpp | 12 +++-- ...ntimeSamplingProfileTraceEventSerializer.h | 20 +++++++- ...amplingProfileTraceEventSerializerTest.cpp | 47 +++++++++++++++++++ 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp index 466d4a6e1e2..f95fb225b82 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp @@ -283,7 +283,8 @@ RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch( const std::function& dispatchCallback, uint16_t traceEventChunkSize, - uint16_t profileChunkSize) { + uint16_t profileChunkSize, + uint16_t maxUniqueNodesPerChunk) { for (auto&& profile : profiles) { serializeAndDispatch( std::move(profile), @@ -291,7 +292,8 @@ RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch( tracingStartTime, dispatchCallback, traceEventChunkSize, - profileChunkSize); + profileChunkSize, + maxUniqueNodesPerChunk); } } @@ -303,7 +305,8 @@ RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch( const std::function& dispatchCallback, uint16_t traceEventChunkSize, - uint16_t profileChunkSize) { + uint16_t profileChunkSize, + uint16_t maxUniqueNodesPerChunk) { auto samples = std::move(profile.samples); if (samples.empty()) { return; @@ -344,7 +347,8 @@ RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch( } auto& threadProfileState = threadProfileStateIterator->second; - if (threadProfileState.chunk.isFull()) { + if (threadProfileState.chunk.isFull() || + threadProfileState.chunk.nodes.size() >= maxUniqueNodesPerChunk) { bufferProfileChunkTraceEvent( std::move(threadProfileState.chunk), threadProfileState.profileId, diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h index 05dd20d12b5..0453e1cd289 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h @@ -13,6 +13,20 @@ namespace facebook::react::jsinspector_modern::tracing { +namespace { + +/** + * Maximum number of samples per chunk. + */ +constexpr uint16_t PROFILE_CHUNK_SIZE = 100; + +/** + * Maximum number of unique nodes per chunk. + */ +constexpr uint16_t MAX_UNIQUE_NODES_PER_CHUNK = 50; + +} // namespace + struct IdGenerator { public: uint32_t getNext() { @@ -49,7 +63,8 @@ class RuntimeSamplingProfileTraceEventSerializer { const std::function& dispatchCallback, uint16_t traceEventChunkSize, - uint16_t profileChunkSize = 10); + uint16_t profileChunkSize = PROFILE_CHUNK_SIZE, + uint16_t maxUniqueNodesPerChunk = MAX_UNIQUE_NODES_PER_CHUNK); static void serializeAndDispatch( std::vector&& profiles, @@ -58,7 +73,8 @@ class RuntimeSamplingProfileTraceEventSerializer { const std::function& dispatchCallback, uint16_t traceEventChunkSize, - uint16_t profileChunkSize = 10); + uint16_t profileChunkSize = PROFILE_CHUNK_SIZE, + uint16_t maxUniqueNodesPerChunk = MAX_UNIQUE_NODES_PER_CHUNK); }; } // namespace facebook::react::jsinspector_modern::tracing 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 dae41580a1c..43182daa4d1 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp @@ -310,4 +310,51 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, ProfileChunkSizeLimit) { } } +TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, UniqueNodesThreshold) { + // Setup + auto notificationCallback = createNotificationCallback(); + IdGenerator profileIdGenerator; + uint16_t traceEventChunkSize = 10; + uint16_t profileChunkSize = 10; + uint16_t maxUniqueNodesPerChunk = 3; + + // Create samples with different function names to generate unique nodes + ThreadId threadId = 1; + uint64_t timestamp = 1000000; + + std::vector samples; + + // In total we would have 8 unique nodes, 5 of which are created here. + // Other 3 are (root), (program), (idle). + for (int i = 0; i < 5; i++) { + std::vector callStack = { + createJSCallFrame( + "function" + std::to_string(i), 1, "test.js", 10 + i, 5)}; + samples.push_back(createSample(timestamp + i * 1000, threadId, callStack)); + } + + auto profile = createProfileWithSamples(std::move(samples)); + auto tracingStartTime = HighResTimeStamp::now(); + + // Execute + RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch( + std::move(profile), + profileIdGenerator, + tracingStartTime, + notificationCallback, + traceEventChunkSize, + profileChunkSize, + maxUniqueNodesPerChunk); + + // [["Profile"], ["ProfileChunk", "ProfileChunk", "ProfileChunk"]] + ASSERT_EQ(notificationEvents_.size(), 2); + EXPECT_EQ(notificationEvents_[1].size(), 3); + + // Verify that each chunk respects the unique nodes limit + for (auto& profileChunk : notificationEvents_[1]) { + auto& nodes = profileChunk["args"]["data"]["cpuProfile"]["nodes"]; + EXPECT_LE(nodes.size(), maxUniqueNodesPerChunk); + } +} + } // namespace facebook::react::jsinspector_modern::tracing