Avoid copies when dispatching TraceEvent chunks (#52868)

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

# Changelog: [Internal]

Mainly, 2 changes:
- Callback that emits `Tracing.dataCollected` events will receive chunks as rvalues refs (`&&`), instead of `const &`.
- The RuntimeSamplingProfile will be passed to the serializer as rvalue ref.

Reviewed By: huntie

Differential Revision: D78919223

fbshipit-source-id: 7f65e0627c8839d507e6b2d088fdb0b560906b6a
This commit is contained in:
Ruslan Lesiutin
2025-07-28 07:12:17 -07:00
committed by Facebook GitHub Bot
parent 3cf11a3113
commit 75d6cb1138
6 changed files with 39 additions and 36 deletions
@@ -102,10 +102,10 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) {
// Send response to Tracing.end request.
frontendChannel_(cdp::jsonResult(req.id));
auto dataCollectedCallback = [this](const folly::dynamic& eventsChunk) {
auto dataCollectedCallback = [this](folly::dynamic&& eventsChunk) {
frontendChannel_(cdp::jsonNotification(
"Tracing.dataCollected",
folly::dynamic::object("value", eventsChunk)));
folly::dynamic::object("value", std::move(eventsChunk))));
};
performanceTracer.collectEvents(
dataCollectedCallback, TRACE_EVENT_CHUNK_SIZE);
@@ -114,8 +114,9 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) {
performanceTracer,
dataCollectedCallback,
PROFILE_TRACE_EVENT_CHUNK_SIZE);
auto tracingProfile = instanceAgent_->collectTracingProfile();
serializer.serializeAndNotify(
instanceAgent_->collectTracingProfile().runtimeSamplingProfile,
std::move(tracingProfile.runtimeSamplingProfile),
instanceTracingStartTimestamp_);
frontendChannel_(cdp::jsonNotification(
@@ -83,8 +83,7 @@ bool PerformanceTracer::stopTracing() {
}
void PerformanceTracer::collectEvents(
const std::function<void(const folly::dynamic& eventsChunk)>&
resultCallback,
const std::function<void(folly::dynamic&& eventsChunk)>& resultCallback,
uint16_t chunkSize) {
std::vector<TraceEvent> localBuffer;
{
@@ -102,12 +101,12 @@ void PerformanceTracer::collectEvents(
serializedTraceEvents.push_back(serializeTraceEvent(std::move(event)));
if (serializedTraceEvents.size() == chunkSize) {
resultCallback(serializedTraceEvents);
resultCallback(std::move(serializedTraceEvents));
serializedTraceEvents = folly::dynamic::array();
}
}
if (!serializedTraceEvents.empty()) {
resultCallback(serializedTraceEvents);
resultCallback(std::move(serializedTraceEvents));
}
}
@@ -56,8 +56,7 @@ class PerformanceTracer {
* Flush out buffered CDP Trace Events using the given callback.
*/
void collectEvents(
const std::function<void(const folly::dynamic& eventsChunk)>&
resultCallback,
const std::function<void(folly::dynamic&& eventsChunk)>& resultCallback,
uint16_t chunkSize);
/**
@@ -115,7 +115,7 @@ void RuntimeSamplingProfileTraceEventSerializer::chunkEmptySample(
}
void RuntimeSamplingProfileTraceEventSerializer::bufferProfileChunkTraceEvent(
ProfileChunk& chunk,
ProfileChunk&& chunk,
uint16_t profileId) {
if (chunk.isEmpty()) {
return;
@@ -135,14 +135,17 @@ void RuntimeSamplingProfileTraceEventSerializer::bufferProfileChunkTraceEvent(
TraceEventProfileChunk{
.cpuProfile =
TraceEventProfileChunk::CPUProfile{
traceEventNodes, chunk.samples},
.nodes = std::move(traceEventNodes),
.samples = std::move(chunk.samples)},
.timeDeltas =
TraceEventProfileChunk::TimeDeltas{chunk.timeDeltas},
TraceEventProfileChunk::TimeDeltas{
.deltas = std::move(chunk.timeDeltas),
},
}));
}
void RuntimeSamplingProfileTraceEventSerializer::processCallStack(
const std::vector<RuntimeSamplingProfile::SampleCallStackFrame>& callStack,
std::vector<RuntimeSamplingProfile::SampleCallStackFrame>&& callStack,
ProfileChunk& chunk,
ProfileTreeNode& rootNode,
uint32_t idleNodeId,
@@ -184,14 +187,16 @@ void RuntimeSamplingProfileTraceEventSerializer::processCallStack(
void RuntimeSamplingProfileTraceEventSerializer::
sendBufferedTraceEventsAndClear() {
notificationCallback_(traceEventBuffer_);
notificationCallback_(std::move(traceEventBuffer_));
traceEventBuffer_ = folly::dynamic::array();
traceEventBuffer_.reserve(traceEventChunkSize_);
}
void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify(
const RuntimeSamplingProfile& profile,
RuntimeSamplingProfile&& profile,
HighResTimeStamp tracingStartTime) {
const std::vector<RuntimeSamplingProfile::Sample>& samples = profile.samples;
auto samples = std::move(profile.samples);
if (samples.empty()) {
return;
}
@@ -225,7 +230,7 @@ void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify(
chunk.nodes.push_back(*idleNode);
uint32_t idleNodeId = idleNode->getId();
for (const auto& sample : samples) {
for (auto& sample : samples) {
uint64_t currentSampleThreadId = sample.threadId;
auto currentSampleTimestamp = getHighResTimeStampForSample(sample);
@@ -234,7 +239,7 @@ void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify(
// We should group samples by thread id once we support executing JavaScript
// on different threads.
if (currentSampleThreadId != chunk.threadId || chunk.isFull()) {
bufferProfileChunkTraceEvent(chunk, PROFILE_ID);
bufferProfileChunkTraceEvent(std::move(chunk), PROFILE_ID);
chunk = ProfileChunk{
profileChunkSize_, currentSampleThreadId, currentChunkTimestamp};
}
@@ -244,7 +249,7 @@ void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify(
}
processCallStack(
sample.callStack,
std::move(sample.callStack),
chunk,
rootNode,
idleNodeId,
@@ -255,7 +260,7 @@ void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify(
}
if (!chunk.isEmpty()) {
bufferProfileChunkTraceEvent(chunk, PROFILE_ID);
bufferProfileChunkTraceEvent(std::move(chunk), PROFILE_ID);
}
if (!traceEventBuffer_.empty()) {
@@ -72,7 +72,7 @@ class RuntimeSamplingProfileTraceEventSerializer {
*/
RuntimeSamplingProfileTraceEventSerializer(
PerformanceTracer& performanceTracer,
std::function<void(const folly::dynamic& traceEventsChunk)>
std::function<void(folly::dynamic&& traceEventsChunk)>
notificationCallback,
uint16_t traceEventChunkSize,
uint16_t profileChunkSize = 10)
@@ -90,7 +90,7 @@ class RuntimeSamplingProfileTraceEventSerializer {
* will be used as a starting reference point of JavaScript samples recording.
*/
void serializeAndNotify(
const RuntimeSamplingProfile& profile,
RuntimeSamplingProfile&& profile,
HighResTimeStamp tracingStartTime);
private:
@@ -123,7 +123,7 @@ class RuntimeSamplingProfileTraceEventSerializer {
* \param chunk The chunk that will be buffered.
* \param profileId The id of the Profile.
*/
void bufferProfileChunkTraceEvent(ProfileChunk& chunk, uint16_t profileId);
void bufferProfileChunkTraceEvent(ProfileChunk&& chunk, uint16_t profileId);
/**
* Encapsulates logic for processing the call stack of the sample.
@@ -140,8 +140,7 @@ class RuntimeSamplingProfileTraceEventSerializer {
* generating unique node ids.
*/
void processCallStack(
const std::vector<RuntimeSamplingProfile::SampleCallStackFrame>&
callStack,
std::vector<RuntimeSamplingProfile::SampleCallStackFrame>&& callStack,
ProfileChunk& chunk,
ProfileTreeNode& rootNode,
uint32_t idleNodeId,
@@ -154,10 +153,10 @@ class RuntimeSamplingProfileTraceEventSerializer {
void sendBufferedTraceEventsAndClear();
PerformanceTracer& performanceTracer_;
const std::function<void(const folly::dynamic& traceEventsChunk)>
const std::function<void(folly::dynamic&& traceEventsChunk)>
notificationCallback_;
uint16_t traceEventChunkSize_;
uint16_t profileChunkSize_;
const uint16_t traceEventChunkSize_;
const uint16_t profileChunkSize_;
folly::dynamic traceEventBuffer_;
};
@@ -18,9 +18,9 @@ class RuntimeSamplingProfileTraceEventSerializerTest : public ::testing::Test {
protected:
std::vector<folly::dynamic> notificationEvents_;
std::function<void(const folly::dynamic& traceEventsChunk)>
std::function<void(folly::dynamic&& traceEventsChunk)>
createNotificationCallback() {
return [this](const folly::dynamic& traceEventsChunk) {
return [this](folly::dynamic&& traceEventsChunk) {
notificationEvents_.push_back(traceEventsChunk);
};
}
@@ -74,7 +74,7 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptyProfile) {
auto tracingStartTime = HighResTimeStamp::now();
// Execute
serializer.serializeAndNotify(profile, tracingStartTime);
serializer.serializeAndNotify(std::move(profile), tracingStartTime);
// Nothing should be reported if the profile is empty.
EXPECT_TRUE(notificationEvents_.empty());
@@ -122,7 +122,7 @@ TEST_F(
auto tracingStartTime = HighResTimeStamp::now();
// Execute
serializer.serializeAndNotify(profile, tracingStartTime);
serializer.serializeAndNotify(std::move(profile), tracingStartTime);
// Verify
ASSERT_EQ(notificationEvents_.size(), 2);
@@ -155,7 +155,7 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptySample) {
folly::dynamic chunkEvent = folly::dynamic::object;
// Execute
serializer.serializeAndNotify(profile, tracingStartTime);
serializer.serializeAndNotify(std::move(profile), tracingStartTime);
// Verify
// [["Profile"], ["ProfileChunk"]]
@@ -192,7 +192,7 @@ TEST_F(
auto tracingStartTime = HighResTimeStamp::now();
// Execute
serializer.serializeAndNotify(profile, tracingStartTime);
serializer.serializeAndNotify(std::move(profile), tracingStartTime);
// [["Profile"], ["ProfileChunk", "ProfileChunk", "ProfileChunk]]
// Samples from different thread should never be grouped together in the same
@@ -231,7 +231,7 @@ TEST_F(
auto tracingStartTime = HighResTimeStamp::now();
// Execute
serializer.serializeAndNotify(profile, tracingStartTime);
serializer.serializeAndNotify(std::move(profile), tracingStartTime);
// [["Profile"], ["ProfileChunk", "ProfileChunk"], ["ProfileChunk"]]
ASSERT_EQ(notificationEvents_.size(), 3);
@@ -272,7 +272,7 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, ProfileChunkSizeLimit) {
auto tracingStartTime = HighResTimeStamp::now();
// Execute
serializer.serializeAndNotify(profile, tracingStartTime);
serializer.serializeAndNotify(std::move(profile), tracingStartTime);
// [["Profile"], ["ProfileChunk", "ProfileChunk", "ProfileChunk"]]
ASSERT_EQ(notificationEvents_.size(), 2);