Static generators for Profile Trace Events (#52915)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52915

# Changelog: [Internal]

This removes the use of `PerformanceTracer` instance in a serialization logic.

Reviewed By: rubennorte

Differential Revision: D78919220

fbshipit-source-id: 5c663ea77eb36eb7664623c1595308a9450f7825
This commit is contained in:
Ruslan Lesiutin
2025-07-31 07:43:31 -07:00
committed by Facebook GitHub Bot
parent 3ed6def874
commit a8f6c96bc2
8 changed files with 93 additions and 66 deletions
@@ -5,10 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
#include <variant>
#include "HermesRuntimeSamplingProfileSerializer.h"
#include <oscompat/OSCompat.h>
#include <variant>
namespace facebook::react::jsinspector_modern::tracing {
namespace {
@@ -163,6 +165,10 @@ HermesRuntimeSamplingProfileSerializer::serializeToTracingSamplingProfile(
return RuntimeSamplingProfile{
"Hermes",
// Hermes' Profile should be the source of truth for this,
// but it is safe to reuse the process ID here, since everything runs in
// the same process.
oscompat::getCurrentProcessId(),
std::move(reconciledSamples),
std::make_unique<RawHermesRuntimeProfile>(std::move(hermesProfile))};
}
@@ -111,9 +111,7 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) {
dataCollectedCallback, TRACE_EVENT_CHUNK_SIZE);
tracing::RuntimeSamplingProfileTraceEventSerializer serializer(
performanceTracer,
dataCollectedCallback,
PROFILE_TRACE_EVENT_CHUNK_SIZE);
dataCollectedCallback, PROFILE_TRACE_EVENT_CHUNK_SIZE);
auto tracingProfile = instanceAgent_->collectTracingProfile();
serializer.serializeAndNotify(
std::move(tracingProfile.runtimeSamplingProfile),
@@ -305,46 +305,49 @@ void PerformanceTracer::reportEventLoopMicrotasks(
});
}
folly::dynamic PerformanceTracer::getSerializedRuntimeProfileTraceEvent(
ThreadId threadId,
/* static */ TraceEvent PerformanceTracer::constructRuntimeProfileTraceEvent(
RuntimeProfileId profileId,
ProcessId processId,
ThreadId threadId,
HighResTimeStamp profileTimestamp) {
// CDT prioritizes event timestamp over startTime metadata field.
// https://fburl.com/lo764pf4
return TraceEventSerializer::serialize(TraceEvent{
return TraceEvent{
.id = profileId,
.name = "Profile",
.cat = "disabled-by-default-v8.cpu_profiler",
.ph = 'P',
.ts = profileTimestamp,
.pid = processId_,
.pid = processId,
.tid = threadId,
.args = folly::dynamic::object(
"data",
folly::dynamic::object(
"startTime",
highResTimeStampToTracingClockTimeStamp(profileTimestamp))),
});
};
}
folly::dynamic PerformanceTracer::getSerializedRuntimeProfileChunkTraceEvent(
ThreadId threadId,
/* static */ TraceEvent
PerformanceTracer::constructRuntimeProfileChunkTraceEvent(
RuntimeProfileId profileId,
ProcessId processId,
ProcessId threadId,
HighResTimeStamp chunkTimestamp,
tracing::TraceEventProfileChunk&& traceEventProfileChunk) {
return TraceEventSerializer::serialize(TraceEvent{
TraceEventProfileChunk&& traceEventProfileChunk) {
return TraceEvent{
.id = profileId,
.name = "ProfileChunk",
.cat = "disabled-by-default-v8.cpu_profiler",
.ph = 'P',
.ts = chunkTimestamp,
.pid = processId_,
.pid = processId,
.tid = threadId,
.args = folly::dynamic::object(
"data",
TraceEventSerializer::serializeProfileChunk(
std::move(traceEventProfileChunk))),
});
};
}
} // namespace facebook::react::jsinspector_modern::tracing
@@ -116,21 +116,25 @@ class PerformanceTracer {
void reportEventLoopMicrotasks(HighResTimeStamp start, HighResTimeStamp end);
/**
* Create and serialize Profile Trace Event.
* \return serialized Trace Event that represents a Profile for CDT.
* Creates "Profile" Trace Event.
*
* Can be serialized to JSON with TraceEventSerializer::serialize.
*/
folly::dynamic getSerializedRuntimeProfileTraceEvent(
ThreadId threadId,
static TraceEvent constructRuntimeProfileTraceEvent(
RuntimeProfileId profileId,
ProcessId processId,
ThreadId threadId,
HighResTimeStamp profileTimestamp);
/**
* Create and serialize ProfileChunk Trace Event.
* \return serialized Trace Event that represents a Profile Chunk for CDT.
* Creates "ProfileChunk" Trace Event.
*
* Can be serialized to JSON with TraceEventSerializer::serialize.
*/
folly::dynamic getSerializedRuntimeProfileChunkTraceEvent(
ProcessId threadId,
static TraceEvent constructRuntimeProfileChunkTraceEvent(
RuntimeProfileId profileId,
ProcessId processId,
ProcessId threadId,
HighResTimeStamp chunkTimestamp,
TraceEventProfileChunk&& traceEventProfileChunk);
@@ -91,9 +91,11 @@ struct RuntimeSamplingProfile {
RuntimeSamplingProfile(
std::string runtimeName,
ProcessId processId,
std::vector<Sample> samples,
std::unique_ptr<RawRuntimeProfile> rawRuntimeProfile)
: runtimeName(std::move(runtimeName)),
processId(processId),
samples(std::move(samples)),
rawRuntimeProfile(std::move(rawRuntimeProfile)) {}
@@ -109,6 +111,8 @@ struct RuntimeSamplingProfile {
/// Name of the runtime, where sampling occurred: Hermes, V8, etc.
std::string runtimeName;
/// The ID of the OS-level process where the sampling occurred.
ProcessId processId;
/// List of recorded samples, should be chronologically sorted.
std::vector<Sample> samples;
/// A unique pointer to the original raw runtime profile, collected from the
@@ -5,10 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
#include <string_view>
#include "ProfileTreeNode.h"
#include "RuntimeSamplingProfileTraceEventSerializer.h"
#include "PerformanceTracer.h"
#include "ProfileTreeNode.h"
#include "TraceEventSerializer.h"
#include <string_view>
namespace facebook::react::jsinspector_modern::tracing {
@@ -96,14 +98,16 @@ class ProfileTreeRootNode : public ProfileTreeNode {
} // namespace
void RuntimeSamplingProfileTraceEventSerializer::sendProfileTraceEvent(
ProcessId processId,
ThreadId threadId,
RuntimeProfileId profileId,
HighResTimeStamp profileStartTimestamp) const {
auto traceEvent = PerformanceTracer::constructRuntimeProfileTraceEvent(
profileId, processId, threadId, profileStartTimestamp);
folly::dynamic serializedTraceEvent =
performanceTracer_.getSerializedRuntimeProfileTraceEvent(
threadId, profileId, profileStartTimestamp);
TraceEventSerializer::serialize(std::move(traceEvent));
notificationCallback_(folly::dynamic::array(serializedTraceEvent));
notificationCallback_(folly::dynamic::array(std::move(serializedTraceEvent)));
}
void RuntimeSamplingProfileTraceEventSerializer::chunkEmptySample(
@@ -127,18 +131,22 @@ void RuntimeSamplingProfileTraceEventSerializer::bufferProfileChunkTraceEvent(
traceEventNodes.push_back(convertToTraceEventProfileNode(node));
}
traceEventBuffer_.push_back(
performanceTracer_.getSerializedRuntimeProfileChunkTraceEvent(
profileId,
chunk.threadId,
chunk.timestamp,
TraceEventProfileChunk{
.cpuProfile =
TraceEventProfileChunk::CPUProfile{
.nodes = std::move(traceEventNodes),
.samples = std::move(chunk.samples)},
.timeDeltas = std::move(chunk.timeDeltas),
}));
auto traceEvent = PerformanceTracer::constructRuntimeProfileChunkTraceEvent(
profileId,
chunk.processId,
chunk.threadId,
chunk.timestamp,
TraceEventProfileChunk{
.cpuProfile =
TraceEventProfileChunk::CPUProfile{
.nodes = std::move(traceEventNodes),
.samples = std::move(chunk.samples)},
.timeDeltas = std::move(chunk.timeDeltas),
});
auto serializedTraceEvent =
TraceEventSerializer::serialize(std::move(traceEvent));
traceEventBuffer_.push_back(std::move(serializedTraceEvent));
}
void RuntimeSamplingProfileTraceEventSerializer::processCallStack(
@@ -202,12 +210,16 @@ void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify(
HighResTimeStamp previousSampleTimestamp = tracingStartTime;
HighResTimeStamp currentChunkTimestamp = tracingStartTime;
sendProfileTraceEvent(firstChunkThreadId, PROFILE_ID, tracingStartTime);
sendProfileTraceEvent(
profile.processId, firstChunkThreadId, PROFILE_ID, tracingStartTime);
// There could be any number of new nodes in this chunk. Empty if all nodes
// are already emitted in previous chunks.
ProfileChunk chunk{
profileChunkSize_, firstChunkThreadId, currentChunkTimestamp};
profileChunkSize_,
profile.processId,
firstChunkThreadId,
currentChunkTimestamp};
NodeIdGenerator nodeIdGenerator{};
@@ -238,7 +250,10 @@ void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify(
if (currentSampleThreadId != chunk.threadId || chunk.isFull()) {
bufferProfileChunkTraceEvent(std::move(chunk), PROFILE_ID);
chunk = ProfileChunk{
profileChunkSize_, currentSampleThreadId, currentChunkTimestamp};
profileChunkSize_,
profile.processId,
currentSampleThreadId,
currentChunkTimestamp};
}
if (traceEventBuffer_.size() == traceEventChunkSize_) {
@@ -7,7 +7,6 @@
#pragma once
#include "PerformanceTracer.h"
#include "ProfileTreeNode.h"
#include "RuntimeSamplingProfile.h"
@@ -37,9 +36,13 @@ class RuntimeSamplingProfileTraceEventSerializer {
struct ProfileChunk {
ProfileChunk(
uint16_t chunkSize,
ProcessId chunkProcessId,
ThreadId chunkThreadId,
HighResTimeStamp chunkTimestamp)
: size(chunkSize), threadId(chunkThreadId), timestamp(chunkTimestamp) {
: size(chunkSize),
processId(chunkProcessId),
threadId(chunkThreadId),
timestamp(chunkTimestamp) {
samples.reserve(size);
timeDeltas.reserve(size);
}
@@ -56,13 +59,13 @@ class RuntimeSamplingProfileTraceEventSerializer {
std::vector<uint32_t> samples;
std::vector<HighResDuration> timeDeltas;
uint16_t size;
ProcessId processId;
ThreadId threadId;
HighResTimeStamp timestamp;
};
public:
/**
* \param performanceTracer A reference to PerformanceTracer instance.
* \param notificationCallback A reference to a callback, which is called
* when a chunk of trace events is ready to be sent.
* \param traceEventChunkSize The maximum number of ProfileChunk trace
@@ -71,13 +74,11 @@ class RuntimeSamplingProfileTraceEventSerializer {
* that can be sent in a single ProfileChunk trace event.
*/
RuntimeSamplingProfileTraceEventSerializer(
PerformanceTracer& performanceTracer,
std::function<void(folly::dynamic&& traceEventsChunk)>
notificationCallback,
uint16_t traceEventChunkSize,
uint16_t profileChunkSize = 10)
: performanceTracer_(performanceTracer),
notificationCallback_(std::move(notificationCallback)),
: notificationCallback_(std::move(notificationCallback)),
traceEventChunkSize_(traceEventChunkSize),
profileChunkSize_(profileChunkSize) {
traceEventBuffer_ = folly::dynamic::array();
@@ -96,12 +97,15 @@ class RuntimeSamplingProfileTraceEventSerializer {
private:
/**
* Sends a single "Profile" Trace Event via notificationCallback_.
* \param processId The id of the process, where the Profile was collected.
* \param threadId The id of the thread, where the Profile was collected.
* \param profileId The id of the Profile.
* \param profileStartUnixTimestamp The Unix timestamp of the start of the
* profile.
*/
void sendProfileTraceEvent(
ProcessId processId,
ThreadId threadId,
RuntimeProfileId profileId,
HighResTimeStamp profileStartTimestamp) const;
@@ -154,7 +158,6 @@ class RuntimeSamplingProfileTraceEventSerializer {
*/
void sendBufferedTraceEventsAndClear();
PerformanceTracer& performanceTracer_;
const std::function<void(folly::dynamic&& traceEventsChunk)>
notificationCallback_;
const uint16_t traceEventChunkSize_;
@@ -55,12 +55,12 @@ class RuntimeSamplingProfileTraceEventSerializerTest : public ::testing::Test {
}
RuntimeSamplingProfile createEmptyProfile() {
return {"TestRuntime", {}, {}};
return {"TestRuntime", 1, {}, {}};
}
RuntimeSamplingProfile createProfileWithSamples(
std::vector<RuntimeSamplingProfile::Sample> samples) {
return {"TestRuntime", std::move(samples), {}};
return {"TestRuntime", 1, std::move(samples), {}};
}
};
@@ -68,7 +68,7 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptyProfile) {
// Setup
auto notificationCallback = createNotificationCallback();
RuntimeSamplingProfileTraceEventSerializer serializer(
PerformanceTracer::getInstance(), notificationCallback, 10);
notificationCallback, 10);
auto profile = createEmptyProfile();
auto tracingStartTime = HighResTimeStamp::now();
@@ -86,7 +86,7 @@ TEST_F(
// Setup
auto notificationCallback = createNotificationCallback();
RuntimeSamplingProfileTraceEventSerializer serializer(
PerformanceTracer::getInstance(), notificationCallback, 10);
notificationCallback, 10);
// [ foo ]
// [ bar ]
@@ -136,7 +136,7 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptySample) {
// Setup
auto notificationCallback = createNotificationCallback();
RuntimeSamplingProfileTraceEventSerializer serializer(
PerformanceTracer::getInstance(), notificationCallback, 10);
notificationCallback, 10);
// Create an empty sample (no call stack)
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> emptyCallStack;
@@ -172,7 +172,7 @@ TEST_F(
// Setup
auto notificationCallback = createNotificationCallback();
RuntimeSamplingProfileTraceEventSerializer serializer(
PerformanceTracer::getInstance(), notificationCallback, 10);
notificationCallback, 10);
// Create samples with different thread IDs
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> callStack = {
@@ -209,10 +209,7 @@ TEST_F(
uint16_t traceEventChunkSize = 2;
uint16_t profileChunkSize = 2;
RuntimeSamplingProfileTraceEventSerializer serializer(
PerformanceTracer::getInstance(),
notificationCallback,
traceEventChunkSize,
profileChunkSize);
notificationCallback, traceEventChunkSize, profileChunkSize);
// Create multiple samples
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> callStack = {
@@ -250,10 +247,7 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, ProfileChunkSizeLimit) {
uint16_t profileChunkSize = 2;
double samplesCount = 5;
RuntimeSamplingProfileTraceEventSerializer serializer(
PerformanceTracer::getInstance(),
notificationCallback,
traceEventChunkSize,
profileChunkSize);
notificationCallback, traceEventChunkSize, profileChunkSize);
// Create multiple samples
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> callStack = {