Skip to content

Commit 8eea166

Browse files
hoxyqfacebook-github-bot
authored andcommitted
Add support for recording Runtime Profiles for multiple JavaScript threads (#53535)
Summary: Pull Request resolved: #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
1 parent bae99ef commit 8eea166

2 files changed

Lines changed: 106 additions & 55 deletions

File tree

packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp

Lines changed: 94 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "TraceEventSerializer.h"
1212

1313
#include <string_view>
14+
#include <unordered_map>
1415

1516
namespace facebook::react::jsinspector_modern::tracing {
1617

@@ -227,6 +228,51 @@ void sendBufferedTraceEvents(
227228
dispatchCallback(std::move(traceEventBuffer));
228229
}
229230

231+
// Auxilliary struct that represents the state of the Profile for a single
232+
// thread. We record a single Profile for a single Thread.
233+
struct ThreadProfileState {
234+
// The current chunk that is being built for this thread.
235+
ProfileChunk chunk;
236+
237+
// The id of the Profile that is being built for this thread.
238+
RuntimeProfileId profileId;
239+
240+
ProfileTreeRootNode rootNode;
241+
ProfileTreeNode programNode;
242+
ProfileTreeNode idleNode;
243+
244+
// The timestamp of the last sample that was captured on this thread.
245+
HighResTimeStamp lastCapturedSampleTimestamp;
246+
247+
// IdGenerator for this Profile.
248+
IdGenerator nodeIdGenerator;
249+
250+
ThreadProfileState(
251+
ProcessId processId,
252+
ThreadId threadId,
253+
RuntimeProfileId profileId,
254+
HighResTimeStamp profileTimestamp,
255+
uint16_t chunkSize,
256+
IdGenerator nodeIdGenerator)
257+
: chunk(chunkSize, processId, threadId, profileTimestamp),
258+
profileId(profileId),
259+
rootNode(ProfileTreeRootNode{nodeIdGenerator.getNext()}),
260+
programNode(*rootNode.addChild(
261+
nodeIdGenerator.getNext(),
262+
ProfileTreeNode::CodeType::Other,
263+
createArtificialCallFrame(PROGRAM_FRAME_NAME))),
264+
idleNode(*rootNode.addChild(
265+
nodeIdGenerator.getNext(),
266+
ProfileTreeNode::CodeType::Other,
267+
createArtificialCallFrame(IDLE_FRAME_NAME))),
268+
lastCapturedSampleTimestamp(profileTimestamp),
269+
nodeIdGenerator(nodeIdGenerator) {
270+
chunk.nodes.push_back(rootNode);
271+
chunk.nodes.push_back(programNode);
272+
chunk.nodes.push_back(idleNode);
273+
}
274+
};
275+
230276
} // namespace
231277

232278
/* static */ void
@@ -266,56 +312,49 @@ RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch(
266312
auto traceEventBuffer = folly::dynamic::array();
267313
traceEventBuffer.reserve(traceEventChunkSize);
268314

269-
ThreadId threadId = samples.front().threadId;
270-
HighResTimeStamp previousSampleTimestamp = tracingStartTime;
271-
HighResTimeStamp currentChunkTimestamp = tracingStartTime;
272-
auto profileId = profileIdGenerator.getNext();
273-
274-
sendProfileTraceEvent(
275-
profile.processId,
276-
threadId,
277-
profileId,
278-
tracingStartTime,
279-
dispatchCallback);
280-
281-
// There could be any number of new nodes in this chunk. Empty if all nodes
282-
// are already emitted in previous chunks.
283-
ProfileChunk chunk{
284-
profileChunkSize, profile.processId, threadId, currentChunkTimestamp};
285-
286-
IdGenerator nodeIdGenerator{};
287-
ProfileTreeRootNode rootNode(nodeIdGenerator.getNext());
288-
chunk.nodes.push_back(rootNode);
289-
290-
ProfileTreeNode* programNode = rootNode.addChild(
291-
nodeIdGenerator.getNext(),
292-
ProfileTreeNode::CodeType::Other,
293-
createArtificialCallFrame(PROGRAM_FRAME_NAME));
294-
chunk.nodes.push_back(*programNode);
295-
296-
ProfileTreeNode* idleNode = rootNode.addChild(
297-
nodeIdGenerator.getNext(),
298-
ProfileTreeNode::CodeType::Other,
299-
createArtificialCallFrame(IDLE_FRAME_NAME));
300-
chunk.nodes.push_back(*idleNode);
301-
uint32_t idleNodeId = idleNode->getId();
302-
315+
std::unordered_map<ThreadId, ThreadProfileState> threadProfiles;
303316
for (auto& sample : samples) {
304317
ThreadId currentSampleThreadId = sample.threadId;
305318
auto currentSampleTimestamp = getHighResTimeStampForSample(sample);
306319

307-
// We should not attempt to merge samples from different threads.
308-
// From past observations, this only happens for GC nodes.
309-
// We should group samples by thread id once we support executing JavaScript
310-
// on different threads.
311-
if (currentSampleThreadId != chunk.threadId || chunk.isFull()) {
320+
auto threadProfileStateIterator =
321+
threadProfiles.find(currentSampleThreadId);
322+
if (threadProfileStateIterator == threadProfiles.end()) {
323+
RuntimeProfileId nextProfileId = profileIdGenerator.getNext();
324+
auto profileStartTime =
325+
threadProfiles.empty() ? tracingStartTime : currentSampleTimestamp;
326+
327+
sendProfileTraceEvent(
328+
profile.processId,
329+
currentSampleThreadId,
330+
nextProfileId,
331+
profileStartTime,
332+
dispatchCallback);
333+
334+
auto [emplacedThreadProfileStateIterator, _] = threadProfiles.emplace(
335+
currentSampleThreadId,
336+
ThreadProfileState{
337+
profile.processId,
338+
currentSampleThreadId,
339+
nextProfileId,
340+
profileStartTime,
341+
profileChunkSize,
342+
IdGenerator{}});
343+
threadProfileStateIterator = emplacedThreadProfileStateIterator;
344+
}
345+
auto& threadProfileState = threadProfileStateIterator->second;
346+
347+
if (threadProfileState.chunk.isFull()) {
312348
bufferProfileChunkTraceEvent(
313-
std::move(chunk), profileId, traceEventBuffer);
314-
chunk = ProfileChunk{
349+
std::move(threadProfileState.chunk),
350+
threadProfileState.profileId,
351+
traceEventBuffer);
352+
353+
threadProfileState.chunk = ProfileChunk{
315354
profileChunkSize,
316355
profile.processId,
317356
currentSampleThreadId,
318-
currentChunkTimestamp};
357+
tracingStartTime};
319358
}
320359

321360
if (traceEventBuffer.size() == traceEventChunkSize) {
@@ -327,17 +366,22 @@ RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch(
327366

328367
processCallStack(
329368
std::move(sample.callStack),
330-
chunk,
331-
rootNode,
332-
idleNodeId,
333-
currentSampleTimestamp - previousSampleTimestamp,
334-
nodeIdGenerator);
369+
threadProfileState.chunk,
370+
threadProfileState.rootNode,
371+
threadProfileState.idleNode.getId(),
372+
currentSampleTimestamp - threadProfileState.lastCapturedSampleTimestamp,
373+
threadProfileState.nodeIdGenerator);
335374

336-
previousSampleTimestamp = currentSampleTimestamp;
375+
threadProfileState.lastCapturedSampleTimestamp = currentSampleTimestamp;
337376
}
338377

339-
if (!chunk.isEmpty()) {
340-
bufferProfileChunkTraceEvent(std::move(chunk), profileId, traceEventBuffer);
378+
for (auto& [threadId, threadState] : threadProfiles) {
379+
if (!threadState.chunk.isEmpty()) {
380+
bufferProfileChunkTraceEvent(
381+
std::move(threadState.chunk),
382+
threadState.profileId,
383+
traceEventBuffer);
384+
}
341385
}
342386

343387
if (!traceEventBuffer.empty()) {

packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,18 @@ TEST_F(
206206
notificationCallback,
207207
10);
208208

209-
// [["Profile"], ["ProfileChunk", "ProfileChunk", "ProfileChunk]]
210-
// Samples from different thread should never be grouped together in the same
211-
// chunk.
212-
ASSERT_EQ(notificationEvents_.size(), 2);
213-
ASSERT_EQ(notificationEvents_[1].size(), 3);
209+
/**
210+
* [
211+
* ["Profile"],
212+
* ["Profile"],
213+
* ["ProfileChunk" for threadId1, "ProfileChunk" for threadId2]
214+
* ]
215+
*
216+
* Samples from different thread should never be grouped together in the same
217+
* chunk.
218+
**/
219+
ASSERT_EQ(notificationEvents_.size(), 3);
220+
ASSERT_EQ(notificationEvents_[2].size(), 2);
214221
}
215222

216223
TEST_F(

0 commit comments

Comments
 (0)