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