1111#include " TraceEventSerializer.h"
1212
1313#include < string_view>
14+ #include < unordered_map>
1415
1516namespace 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 ()) {
0 commit comments