diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp index fc4730a63a4..466d4a6e1e2 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp @@ -11,6 +11,7 @@ #include "TraceEventSerializer.h" #include +#include namespace facebook::react::jsinspector_modern::tracing { @@ -227,6 +228,51 @@ void sendBufferedTraceEvents( dispatchCallback(std::move(traceEventBuffer)); } +// Auxilliary struct that represents the state of the Profile for a single +// thread. We record a single Profile for a single Thread. +struct ThreadProfileState { + // The current chunk that is being built for this thread. + ProfileChunk chunk; + + // The id of the Profile that is being built for this thread. + RuntimeProfileId profileId; + + ProfileTreeRootNode rootNode; + ProfileTreeNode programNode; + ProfileTreeNode idleNode; + + // The timestamp of the last sample that was captured on this thread. + HighResTimeStamp lastCapturedSampleTimestamp; + + // IdGenerator for this Profile. + IdGenerator nodeIdGenerator; + + ThreadProfileState( + ProcessId processId, + ThreadId threadId, + RuntimeProfileId profileId, + HighResTimeStamp profileTimestamp, + uint16_t chunkSize, + IdGenerator nodeIdGenerator) + : chunk(chunkSize, processId, threadId, profileTimestamp), + profileId(profileId), + rootNode(ProfileTreeRootNode{nodeIdGenerator.getNext()}), + programNode(*rootNode.addChild( + nodeIdGenerator.getNext(), + ProfileTreeNode::CodeType::Other, + createArtificialCallFrame(PROGRAM_FRAME_NAME))), + idleNode(*rootNode.addChild( + nodeIdGenerator.getNext(), + ProfileTreeNode::CodeType::Other, + createArtificialCallFrame(IDLE_FRAME_NAME))), + lastCapturedSampleTimestamp(profileTimestamp), + nodeIdGenerator(nodeIdGenerator) { + chunk.nodes.push_back(rootNode); + chunk.nodes.push_back(programNode); + chunk.nodes.push_back(idleNode); + } +}; + } // namespace /* static */ void @@ -266,56 +312,49 @@ RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch( auto traceEventBuffer = folly::dynamic::array(); traceEventBuffer.reserve(traceEventChunkSize); - ThreadId threadId = samples.front().threadId; - HighResTimeStamp previousSampleTimestamp = tracingStartTime; - HighResTimeStamp currentChunkTimestamp = tracingStartTime; - auto profileId = profileIdGenerator.getNext(); - - sendProfileTraceEvent( - profile.processId, - threadId, - profileId, - tracingStartTime, - dispatchCallback); - - // There could be any number of new nodes in this chunk. Empty if all nodes - // are already emitted in previous chunks. - ProfileChunk chunk{ - profileChunkSize, profile.processId, threadId, currentChunkTimestamp}; - - IdGenerator nodeIdGenerator{}; - ProfileTreeRootNode rootNode(nodeIdGenerator.getNext()); - chunk.nodes.push_back(rootNode); - - ProfileTreeNode* programNode = rootNode.addChild( - nodeIdGenerator.getNext(), - ProfileTreeNode::CodeType::Other, - createArtificialCallFrame(PROGRAM_FRAME_NAME)); - chunk.nodes.push_back(*programNode); - - ProfileTreeNode* idleNode = rootNode.addChild( - nodeIdGenerator.getNext(), - ProfileTreeNode::CodeType::Other, - createArtificialCallFrame(IDLE_FRAME_NAME)); - chunk.nodes.push_back(*idleNode); - uint32_t idleNodeId = idleNode->getId(); - + std::unordered_map threadProfiles; for (auto& sample : samples) { ThreadId currentSampleThreadId = sample.threadId; auto currentSampleTimestamp = getHighResTimeStampForSample(sample); - // We should not attempt to merge samples from different threads. - // From past observations, this only happens for GC nodes. - // We should group samples by thread id once we support executing JavaScript - // on different threads. - if (currentSampleThreadId != chunk.threadId || chunk.isFull()) { + auto threadProfileStateIterator = + threadProfiles.find(currentSampleThreadId); + if (threadProfileStateIterator == threadProfiles.end()) { + RuntimeProfileId nextProfileId = profileIdGenerator.getNext(); + auto profileStartTime = + threadProfiles.empty() ? tracingStartTime : currentSampleTimestamp; + + sendProfileTraceEvent( + profile.processId, + currentSampleThreadId, + nextProfileId, + profileStartTime, + dispatchCallback); + + auto [emplacedThreadProfileStateIterator, _] = threadProfiles.emplace( + currentSampleThreadId, + ThreadProfileState{ + profile.processId, + currentSampleThreadId, + nextProfileId, + profileStartTime, + profileChunkSize, + IdGenerator{}}); + threadProfileStateIterator = emplacedThreadProfileStateIterator; + } + auto& threadProfileState = threadProfileStateIterator->second; + + if (threadProfileState.chunk.isFull()) { bufferProfileChunkTraceEvent( - std::move(chunk), profileId, traceEventBuffer); - chunk = ProfileChunk{ + std::move(threadProfileState.chunk), + threadProfileState.profileId, + traceEventBuffer); + + threadProfileState.chunk = ProfileChunk{ profileChunkSize, profile.processId, currentSampleThreadId, - currentChunkTimestamp}; + tracingStartTime}; } if (traceEventBuffer.size() == traceEventChunkSize) { @@ -327,17 +366,22 @@ RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch( processCallStack( std::move(sample.callStack), - chunk, - rootNode, - idleNodeId, - currentSampleTimestamp - previousSampleTimestamp, - nodeIdGenerator); + threadProfileState.chunk, + threadProfileState.rootNode, + threadProfileState.idleNode.getId(), + currentSampleTimestamp - threadProfileState.lastCapturedSampleTimestamp, + threadProfileState.nodeIdGenerator); - previousSampleTimestamp = currentSampleTimestamp; + threadProfileState.lastCapturedSampleTimestamp = currentSampleTimestamp; } - if (!chunk.isEmpty()) { - bufferProfileChunkTraceEvent(std::move(chunk), profileId, traceEventBuffer); + for (auto& [threadId, threadState] : threadProfiles) { + if (!threadState.chunk.isEmpty()) { + bufferProfileChunkTraceEvent( + std::move(threadState.chunk), + threadState.profileId, + traceEventBuffer); + } } if (!traceEventBuffer.empty()) { 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 95162f03693..dae41580a1c 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp @@ -206,11 +206,18 @@ TEST_F( notificationCallback, 10); - // [["Profile"], ["ProfileChunk", "ProfileChunk", "ProfileChunk]] - // Samples from different thread should never be grouped together in the same - // chunk. - ASSERT_EQ(notificationEvents_.size(), 2); - ASSERT_EQ(notificationEvents_[1].size(), 3); + /** + * [ + * ["Profile"], + * ["Profile"], + * ["ProfileChunk" for threadId1, "ProfileChunk" for threadId2] + * ] + * + * Samples from different thread should never be grouped together in the same + * chunk. + **/ + ASSERT_EQ(notificationEvents_.size(), 3); + ASSERT_EQ(notificationEvents_[2].size(), 2); } TEST_F(