/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include #include #include #include #include namespace facebook::react::jsinspector_modern::tracing { class RuntimeSamplingProfileTraceEventSerializerTest : public ::testing::Test { protected: std::vector notificationEvents_; std::function createNotificationCallback() { return [this](const folly::dynamic& traceEventsChunk) { notificationEvents_.push_back(traceEventsChunk); }; } RuntimeSamplingProfile::SampleCallStackFrame createJSCallFrame( std::string_view functionName, uint32_t scriptId = 1, std::optional url = std::nullopt, std::optional lineNumber = std::nullopt, std::optional columnNumber = std::nullopt) { return RuntimeSamplingProfile::SampleCallStackFrame( RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction, scriptId, functionName, url, lineNumber, columnNumber); } RuntimeSamplingProfile::SampleCallStackFrame createGCCallFrame() { return RuntimeSamplingProfile::SampleCallStackFrame( RuntimeSamplingProfile::SampleCallStackFrame::Kind::GarbageCollector, 0, "(garbage collector)"); } RuntimeSamplingProfile::Sample createSample( uint64_t timestamp, uint64_t threadId, std::vector callStack) { return {timestamp, threadId, std::move(callStack)}; } RuntimeSamplingProfile createEmptyProfile() { return {"TestRuntime", {}, {}}; } RuntimeSamplingProfile createProfileWithSamples( std::vector samples) { return {"TestRuntime", std::move(samples), {}}; } }; TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptyProfile) { // Setup auto notificationCallback = createNotificationCallback(); RuntimeSamplingProfileTraceEventSerializer serializer( PerformanceTracer::getInstance(), notificationCallback, 10); auto profile = createEmptyProfile(); auto tracingStartTime = HighResTimeStamp::now(); // Execute serializer.serializeAndNotify(profile, tracingStartTime); // Nothing should be reported if the profile is empty. EXPECT_TRUE(notificationEvents_.empty()); } TEST_F( RuntimeSamplingProfileTraceEventSerializerTest, SameCallFramesAreMerged) { // Setup auto notificationCallback = createNotificationCallback(); RuntimeSamplingProfileTraceEventSerializer serializer( PerformanceTracer::getInstance(), notificationCallback, 10); // [ foo ] // [ bar ] // [baz][(gc)] std::vector callStack1 = { createJSCallFrame("bar", 1, "test.js", 20, 10), createJSCallFrame("foo", 1, "test.js", 10, 5), }; std::vector callStack2 = { createJSCallFrame("baz", 1, "other.js", 5, 1), createJSCallFrame("bar", 1, "test.js", 20, 10), createJSCallFrame("foo", 1, "test.js", 10, 5), }; std::vector callStack3 = { createGCCallFrame(), createJSCallFrame("bar", 1, "test.js", 20, 10), createJSCallFrame("foo", 1, "test.js", 10, 5), }; uint64_t threadId = 1; uint64_t timestamp1 = 1000000; uint64_t timestamp2 = 2000000; uint64_t timestamp3 = 3000000; auto samples = std::vector{}; samples.emplace_back(createSample(timestamp1, threadId, callStack1)); samples.emplace_back(createSample(timestamp2, threadId, callStack2)); samples.emplace_back(createSample(timestamp3, threadId, callStack3)); auto profile = createProfileWithSamples(std::move(samples)); auto tracingStartTime = HighResTimeStamp::now(); // Execute serializer.serializeAndNotify(profile, tracingStartTime); // Verify ASSERT_EQ(notificationEvents_.size(), 2); // (root), (program), (idle), foo, bar, baz, (garbage collector) ASSERT_EQ( notificationEvents_[1][0]["args"]["data"]["cpuProfile"]["nodes"].size(), 7); } TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptySample) { // Setup auto notificationCallback = createNotificationCallback(); RuntimeSamplingProfileTraceEventSerializer serializer( PerformanceTracer::getInstance(), notificationCallback, 10); // Create an empty sample (no call stack) std::vector emptyCallStack; uint64_t threadId = 1; uint64_t timestamp = 1000000; auto samples = std::vector{}; samples.emplace_back(createSample(timestamp, threadId, emptyCallStack)); auto profile = createProfileWithSamples(std::move(samples)); auto tracingStartTime = HighResTimeStamp::now(); // Mock the performance tracer methods folly::dynamic profileEvent = folly::dynamic::object; folly::dynamic chunkEvent = folly::dynamic::object; // Execute serializer.serializeAndNotify(profile, tracingStartTime); // Verify // [["Profile"], ["ProfileChunk"]] ASSERT_EQ(notificationEvents_.size(), 2); // (root), (program), (idle) ASSERT_EQ( notificationEvents_[1][0]["args"]["data"]["cpuProfile"]["nodes"].size(), 3); } TEST_F( RuntimeSamplingProfileTraceEventSerializerTest, SamplesFromDifferentThreads) { // Setup auto notificationCallback = createNotificationCallback(); RuntimeSamplingProfileTraceEventSerializer serializer( PerformanceTracer::getInstance(), notificationCallback, 10); // Create samples with different thread IDs std::vector callStack = { createJSCallFrame("foo", 1, "test.js", 10, 5)}; uint64_t timestamp = 1000000; uint64_t threadId1 = 1; uint64_t threadId2 = 2; auto samples = std::vector{}; samples.emplace_back(createSample(timestamp, threadId1, callStack)); samples.emplace_back(createSample(timestamp + 1000, threadId2, callStack)); samples.emplace_back(createSample(timestamp + 2000, threadId1, callStack)); auto profile = createProfileWithSamples(std::move(samples)); auto tracingStartTime = HighResTimeStamp::now(); // Execute serializer.serializeAndNotify(profile, tracingStartTime); // [["Profile"], ["ProfileChunk", "ProfileChunk", "ProfileChunk]] // Samples from different thread should never be grouped together in the same // chunk. ASSERT_EQ(notificationEvents_.size(), 2); ASSERT_EQ(notificationEvents_[1].size(), 3); } TEST_F( RuntimeSamplingProfileTraceEventSerializerTest, TraceEventChunkSizeLimit) { // Setup auto notificationCallback = createNotificationCallback(); uint16_t traceEventChunkSize = 2; uint16_t profileChunkSize = 2; RuntimeSamplingProfileTraceEventSerializer serializer( PerformanceTracer::getInstance(), notificationCallback, traceEventChunkSize, profileChunkSize); // Create multiple samples std::vector callStack = { createJSCallFrame("foo", 1, "test.js", 10, 5)}; uint64_t timestamp = 1000000; uint64_t threadId = 1; std::vector samples; samples.reserve(5); for (int i = 0; i < 5; i++) { samples.push_back(createSample(timestamp + i * 1000, threadId, callStack)); } auto profile = createProfileWithSamples(std::move(samples)); auto tracingStartTime = HighResTimeStamp::now(); // Execute serializer.serializeAndNotify(profile, tracingStartTime); // [["Profile"], ["ProfileChunk", "ProfileChunk"], ["ProfileChunk"]] ASSERT_EQ(notificationEvents_.size(), 3); // Check that each chunk has at most traceEventChunkSize events for (size_t i = 1; i < notificationEvents_.size(); i++) { EXPECT_LE(notificationEvents_[i].size(), traceEventChunkSize); } } TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, ProfileChunkSizeLimit) { // Setup auto notificationCallback = createNotificationCallback(); // Set a small profile chunk size to test profile chunking uint16_t traceEventChunkSize = 10; uint16_t profileChunkSize = 2; double samplesCount = 5; RuntimeSamplingProfileTraceEventSerializer serializer( PerformanceTracer::getInstance(), notificationCallback, traceEventChunkSize, profileChunkSize); // Create multiple samples std::vector callStack = { createJSCallFrame("foo", 1, "test.js", 10, 5)}; uint64_t timestamp = 1000000; uint64_t threadId = 1; std::vector samples; samples.reserve(samplesCount); for (int i = 0; i < samplesCount; i++) { samples.push_back(createSample(timestamp + i * 1000, threadId, callStack)); } auto profile = createProfileWithSamples(std::move(samples)); auto tracingStartTime = HighResTimeStamp::now(); // Execute serializer.serializeAndNotify(profile, tracingStartTime); // [["Profile"], ["ProfileChunk", "ProfileChunk", "ProfileChunk"]] ASSERT_EQ(notificationEvents_.size(), 2); ASSERT_EQ( notificationEvents_[1].size(), std::ceil(samplesCount / profileChunkSize)); for (auto& profileChunk : notificationEvents_[1]) { EXPECT_LE( profileChunk["args"]["data"]["cpuProfile"]["samples"].size(), profileChunkSize); } } } // namespace facebook::react::jsinspector_modern::tracing