mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Add support for recording Runtime Profiles for multiple JavaScript threads (#53535)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53535 # Changelog: [Internal] This primarily addressed the case when we have captured a Runtime Profile during the app startup. The Hermes Runtime is created on the main thread, so the first few samples will be recorded there, but then it will be moved to JavaScript thread. Reviewed By: huntie Differential Revision: D81339676 fbshipit-source-id: 8202ca03df54134330aa921a9a0a97816c51cea5
This commit is contained in:
committed by
Facebook GitHub Bot
parent
bae99efc26
commit
8eea1660f4
+94
-50
@@ -11,6 +11,7 @@
|
||||
#include "TraceEventSerializer.h"
|
||||
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
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<ThreadId, ThreadProfileState> 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()) {
|
||||
|
||||
+12
-5
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user