// Copyright 2023 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include "base/allocator/dispatcher/notification_data.h" #include "base/allocator/dispatcher/subsystem.h" #include "base/debug/allocation_trace.h" #include "base/strings/stringprintf.h" #include "base/timer/lap_timer.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/perf/perf_result_reporter.h" namespace base { namespace debug { namespace { // Change kTimeLimit to something higher if you need more time to capture a // trace. constexpr base::TimeDelta kTimeLimit = base::Seconds(3); constexpr int kWarmupRuns = 100; constexpr int kTimeCheckInterval = 1000; constexpr char kMetricStackTraceDuration[] = ".duration_per_run"; constexpr char kMetricStackTraceThroughput[] = ".throughput"; enum class HandlerFunctionSelector { OnAllocation, OnFree }; // An executor to perform the actual notification of the recorder. The correct // handler function is selected using template specialization based on the // HandlerFunctionSelector. template struct HandlerFunctionExecutor { void operator()(base::debug::tracer::AllocationTraceRecorder& recorder) const; }; template <> struct HandlerFunctionExecutor { void operator()( base::debug::tracer::AllocationTraceRecorder& recorder) const { // Since the recorder just stores the value, we can use any value for // address and size that we want. recorder.OnAllocation( base::allocator::dispatcher::AllocationNotificationData( &recorder, sizeof(recorder), nullptr, base::allocator::dispatcher::AllocationSubsystem:: kPartitionAllocator)); } }; template <> struct HandlerFunctionExecutor { void operator()( base::debug::tracer::AllocationTraceRecorder& recorder) const { recorder.OnFree(base::allocator::dispatcher::FreeNotificationData( &recorder, base::allocator::dispatcher::AllocationSubsystem::kPartitionAllocator)); } }; } // namespace class AllocationTraceRecorderPerfTest : public testing::TestWithParam< std::tuple> { protected: // The result data of a single thread. From the results of all the single // threads the final results will be calculated. struct ResultData { TimeDelta time_per_lap; float laps_per_second = 0.0; int number_of_laps = 0; }; // The data of a single test thread. struct ThreadRunnerData { std::thread thread; ResultData result_data; }; // Create and setup the result reporter. const char* GetHandlerDescriptor(HandlerFunctionSelector handler_function); perf_test::PerfResultReporter SetUpReporter( HandlerFunctionSelector handler_function, size_t number_of_allocating_threads); // Select the correct test function which shall be used for the current test. using TestFunction = void (*)(base::debug::tracer::AllocationTraceRecorder& recorder, ResultData& result_data); static TestFunction GetTestFunction(HandlerFunctionSelector handler_function); template static void TestFunctionImplementation( base::debug::tracer::AllocationTraceRecorder& recorder, ResultData& result_data); // The test management function. Using the the above auxiliary functions it is // responsible to setup the result reporter, select the correct test function, // spawn the specified number of worker threads and post process the results. void PerformTest(HandlerFunctionSelector handler_function, size_t number_of_allocating_threads); }; const char* AllocationTraceRecorderPerfTest::GetHandlerDescriptor( HandlerFunctionSelector handler_function) { switch (handler_function) { case HandlerFunctionSelector::OnAllocation: return "OnAllocation"; case HandlerFunctionSelector::OnFree: return "OnFree"; } } perf_test::PerfResultReporter AllocationTraceRecorderPerfTest::SetUpReporter( HandlerFunctionSelector handler_function, size_t number_of_allocating_threads) { const std::string story_name = base::StringPrintf( "(%s;%zu-threads)", GetHandlerDescriptor(handler_function), number_of_allocating_threads); perf_test::PerfResultReporter reporter("AllocationRecorderPerf", story_name); reporter.RegisterImportantMetric(kMetricStackTraceDuration, "ns"); reporter.RegisterImportantMetric(kMetricStackTraceThroughput, "runs/s"); return reporter; } AllocationTraceRecorderPerfTest::TestFunction AllocationTraceRecorderPerfTest::GetTestFunction( HandlerFunctionSelector handler_function) { switch (handler_function) { case HandlerFunctionSelector::OnAllocation: return TestFunctionImplementation; case HandlerFunctionSelector::OnFree: return TestFunctionImplementation; } } void AllocationTraceRecorderPerfTest::PerformTest( HandlerFunctionSelector handler_function, size_t number_of_allocating_threads) { perf_test::PerfResultReporter reporter = SetUpReporter(handler_function, number_of_allocating_threads); TestFunction test_function = GetTestFunction(handler_function); base::debug::tracer::AllocationTraceRecorder the_recorder; std::vector notifying_threads; notifying_threads.reserve(number_of_allocating_threads); // Setup the threads. After creation, each thread immediately starts running. // We expect the creation of the threads to be so quick that the delay from // first to last thread is negligible. for (size_t i = 0; i < number_of_allocating_threads; ++i) { auto& last_item = notifying_threads.emplace_back(); last_item.thread = std::thread{test_function, std::ref(the_recorder), std::ref(last_item.result_data)}; } TimeDelta average_time_per_lap; float average_laps_per_second = 0; // Wait for each thread to finish and collect its result data. for (auto& item : notifying_threads) { item.thread.join(); // When finishing, each threads writes its results into result_data. So, // from here we gather its performance statistics. average_time_per_lap += item.result_data.time_per_lap; average_laps_per_second += item.result_data.laps_per_second; } average_time_per_lap /= number_of_allocating_threads; average_laps_per_second /= number_of_allocating_threads; reporter.AddResult(kMetricStackTraceDuration, average_time_per_lap); reporter.AddResult(kMetricStackTraceThroughput, average_laps_per_second); } template void AllocationTraceRecorderPerfTest::TestFunctionImplementation( base::debug::tracer::AllocationTraceRecorder& recorder, ResultData& result_data) { LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval, LapTimer::TimerMethod::kUseTimeTicks); HandlerFunctionExecutor handler_executor; timer.Start(); do { handler_executor(recorder); timer.NextLap(); } while (!timer.HasTimeLimitExpired()); result_data.time_per_lap = timer.TimePerLap(); result_data.laps_per_second = timer.LapsPerSecond(); result_data.number_of_laps = timer.NumLaps(); } INSTANTIATE_TEST_SUITE_P( , AllocationTraceRecorderPerfTest, ::testing::Combine(::testing::Values(HandlerFunctionSelector::OnAllocation, HandlerFunctionSelector::OnFree), ::testing::Values(1, 5, 10, 20, 40, 80))); TEST_P(AllocationTraceRecorderPerfTest, TestNotification) { const auto parameters = GetParam(); const HandlerFunctionSelector handler_function = std::get<0>(parameters); const size_t number_of_threads = std::get<1>(parameters); PerformTest(handler_function, number_of_threads); } } // namespace debug } // namespace base