Set threshold for a number of unique nodes in ProfileChunk (#53536)

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

# Changelog: [Internal]

For every chunk, we already have a threshold for the number of samples captured in this chunk.

There could be really tall call stacks, where we could record hundreds of unique nodes, which makes the chunk already big enough for a CDP traffic on android.

We are adding a threshold for a number of unique nodes in a single chunk. If the chunk has a greater number of nodes recorded, it will be dispatched over CDP.

Reviewed By: huntie

Differential Revision: D81339677

fbshipit-source-id: 388d14c64c4c3f60918a8526025f79d19d397cb4
This commit is contained in:
Ruslan Lesiutin
2025-09-01 07:12:55 -07:00
committed by Facebook GitHub Bot
parent 8eea1660f4
commit e64dce582a
3 changed files with 73 additions and 6 deletions
@@ -283,7 +283,8 @@ RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch(
const std::function<void(folly::dynamic&& traceEventsChunk)>&
dispatchCallback,
uint16_t traceEventChunkSize,
uint16_t profileChunkSize) {
uint16_t profileChunkSize,
uint16_t maxUniqueNodesPerChunk) {
for (auto&& profile : profiles) {
serializeAndDispatch(
std::move(profile),
@@ -291,7 +292,8 @@ RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch(
tracingStartTime,
dispatchCallback,
traceEventChunkSize,
profileChunkSize);
profileChunkSize,
maxUniqueNodesPerChunk);
}
}
@@ -303,7 +305,8 @@ RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch(
const std::function<void(folly::dynamic&& traceEventsChunk)>&
dispatchCallback,
uint16_t traceEventChunkSize,
uint16_t profileChunkSize) {
uint16_t profileChunkSize,
uint16_t maxUniqueNodesPerChunk) {
auto samples = std::move(profile.samples);
if (samples.empty()) {
return;
@@ -344,7 +347,8 @@ RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch(
}
auto& threadProfileState = threadProfileStateIterator->second;
if (threadProfileState.chunk.isFull()) {
if (threadProfileState.chunk.isFull() ||
threadProfileState.chunk.nodes.size() >= maxUniqueNodesPerChunk) {
bufferProfileChunkTraceEvent(
std::move(threadProfileState.chunk),
threadProfileState.profileId,
@@ -13,6 +13,20 @@
namespace facebook::react::jsinspector_modern::tracing {
namespace {
/**
* Maximum number of samples per chunk.
*/
constexpr uint16_t PROFILE_CHUNK_SIZE = 100;
/**
* Maximum number of unique nodes per chunk.
*/
constexpr uint16_t MAX_UNIQUE_NODES_PER_CHUNK = 50;
} // namespace
struct IdGenerator {
public:
uint32_t getNext() {
@@ -49,7 +63,8 @@ class RuntimeSamplingProfileTraceEventSerializer {
const std::function<void(folly::dynamic&& traceEventsChunk)>&
dispatchCallback,
uint16_t traceEventChunkSize,
uint16_t profileChunkSize = 10);
uint16_t profileChunkSize = PROFILE_CHUNK_SIZE,
uint16_t maxUniqueNodesPerChunk = MAX_UNIQUE_NODES_PER_CHUNK);
static void serializeAndDispatch(
std::vector<RuntimeSamplingProfile>&& profiles,
@@ -58,7 +73,8 @@ class RuntimeSamplingProfileTraceEventSerializer {
const std::function<void(folly::dynamic&& traceEventsChunk)>&
dispatchCallback,
uint16_t traceEventChunkSize,
uint16_t profileChunkSize = 10);
uint16_t profileChunkSize = PROFILE_CHUNK_SIZE,
uint16_t maxUniqueNodesPerChunk = MAX_UNIQUE_NODES_PER_CHUNK);
};
} // namespace facebook::react::jsinspector_modern::tracing
@@ -310,4 +310,51 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, ProfileChunkSizeLimit) {
}
}
TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, UniqueNodesThreshold) {
// Setup
auto notificationCallback = createNotificationCallback();
IdGenerator profileIdGenerator;
uint16_t traceEventChunkSize = 10;
uint16_t profileChunkSize = 10;
uint16_t maxUniqueNodesPerChunk = 3;
// Create samples with different function names to generate unique nodes
ThreadId threadId = 1;
uint64_t timestamp = 1000000;
std::vector<RuntimeSamplingProfile::Sample> samples;
// In total we would have 8 unique nodes, 5 of which are created here.
// Other 3 are (root), (program), (idle).
for (int i = 0; i < 5; i++) {
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> callStack = {
createJSCallFrame(
"function" + std::to_string(i), 1, "test.js", 10 + i, 5)};
samples.push_back(createSample(timestamp + i * 1000, threadId, callStack));
}
auto profile = createProfileWithSamples(std::move(samples));
auto tracingStartTime = HighResTimeStamp::now();
// Execute
RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch(
std::move(profile),
profileIdGenerator,
tracingStartTime,
notificationCallback,
traceEventChunkSize,
profileChunkSize,
maxUniqueNodesPerChunk);
// [["Profile"], ["ProfileChunk", "ProfileChunk", "ProfileChunk"]]
ASSERT_EQ(notificationEvents_.size(), 2);
EXPECT_EQ(notificationEvents_[1].size(), 3);
// Verify that each chunk respects the unique nodes limit
for (auto& profileChunk : notificationEvents_[1]) {
auto& nodes = profileChunk["args"]["data"]["cpuProfile"]["nodes"];
EXPECT_LE(nodes.size(), maxUniqueNodesPerChunk);
}
}
} // namespace facebook::react::jsinspector_modern::tracing