| André Kempe | f23c03c | 2023-04-25 17:57:36 | [diff] [blame] | 1 | // Copyright 2023 The Chromium Authors |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 5 | #include "base/debug/allocation_trace.h" |
| 6 | |
| André Kempe | f23c03c | 2023-04-25 17:57:36 | [diff] [blame] | 7 | #include <thread> |
| 8 | #include <vector> |
| 9 | |
| André Kempe | 69aec28 | 2024-01-03 19:28:45 | [diff] [blame] | 10 | #include "base/allocator/dispatcher/notification_data.h" |
| 11 | #include "base/allocator/dispatcher/subsystem.h" |
| André Kempe | f23c03c | 2023-04-25 17:57:36 | [diff] [blame] | 12 | #include "base/strings/stringprintf.h" |
| 13 | #include "base/timer/lap_timer.h" |
| 14 | #include "testing/gtest/include/gtest/gtest.h" |
| 15 | #include "testing/perf/perf_result_reporter.h" |
| 16 | |
| 17 | namespace base { |
| 18 | namespace debug { |
| 19 | namespace { |
| 20 | // Change kTimeLimit to something higher if you need more time to capture a |
| 21 | // trace. |
| 22 | constexpr base::TimeDelta kTimeLimit = base::Seconds(3); |
| 23 | constexpr int kWarmupRuns = 100; |
| 24 | constexpr int kTimeCheckInterval = 1000; |
| 25 | constexpr char kMetricStackTraceDuration[] = ".duration_per_run"; |
| 26 | constexpr char kMetricStackTraceThroughput[] = ".throughput"; |
| 27 | |
| 28 | enum class HandlerFunctionSelector { OnAllocation, OnFree }; |
| 29 | |
| 30 | // An executor to perform the actual notification of the recorder. The correct |
| 31 | // handler function is selected using template specialization based on the |
| 32 | // HandlerFunctionSelector. |
| 33 | template <HandlerFunctionSelector HandlerFunction> |
| 34 | struct HandlerFunctionExecutor { |
| 35 | void operator()(base::debug::tracer::AllocationTraceRecorder& recorder) const; |
| 36 | }; |
| 37 | |
| 38 | template <> |
| 39 | struct HandlerFunctionExecutor<HandlerFunctionSelector::OnAllocation> { |
| 40 | void operator()( |
| 41 | base::debug::tracer::AllocationTraceRecorder& recorder) const { |
| 42 | // Since the recorder just stores the value, we can use any value for |
| 43 | // address and size that we want. |
| 44 | recorder.OnAllocation( |
| André Kempe | 69aec28 | 2024-01-03 19:28:45 | [diff] [blame] | 45 | base::allocator::dispatcher::AllocationNotificationData( |
| 46 | &recorder, sizeof(recorder), nullptr, |
| 47 | base::allocator::dispatcher::AllocationSubsystem:: |
| 48 | kPartitionAllocator)); |
| André Kempe | f23c03c | 2023-04-25 17:57:36 | [diff] [blame] | 49 | } |
| 50 | }; |
| 51 | |
| 52 | template <> |
| 53 | struct HandlerFunctionExecutor<HandlerFunctionSelector::OnFree> { |
| 54 | void operator()( |
| 55 | base::debug::tracer::AllocationTraceRecorder& recorder) const { |
| André Kempe | 69aec28 | 2024-01-03 19:28:45 | [diff] [blame] | 56 | recorder.OnFree(base::allocator::dispatcher::FreeNotificationData( |
| 57 | &recorder, |
| 58 | base::allocator::dispatcher::AllocationSubsystem::kPartitionAllocator)); |
| André Kempe | f23c03c | 2023-04-25 17:57:36 | [diff] [blame] | 59 | } |
| 60 | }; |
| 61 | } // namespace |
| 62 | |
| 63 | class AllocationTraceRecorderPerfTest |
| 64 | : public testing::TestWithParam< |
| 65 | std::tuple<HandlerFunctionSelector, size_t>> { |
| 66 | protected: |
| 67 | // The result data of a single thread. From the results of all the single |
| 68 | // threads the final results will be calculated. |
| 69 | struct ResultData { |
| 70 | TimeDelta time_per_lap; |
| 71 | float laps_per_second = 0.0; |
| 72 | int number_of_laps = 0; |
| 73 | }; |
| 74 | |
| 75 | // The data of a single test thread. |
| 76 | struct ThreadRunnerData { |
| 77 | std::thread thread; |
| 78 | ResultData result_data; |
| 79 | }; |
| 80 | |
| 81 | // Create and setup the result reporter. |
| 82 | const char* GetHandlerDescriptor(HandlerFunctionSelector handler_function); |
| 83 | perf_test::PerfResultReporter SetUpReporter( |
| 84 | HandlerFunctionSelector handler_function, |
| 85 | size_t number_of_allocating_threads); |
| 86 | |
| 87 | // Select the correct test function which shall be used for the current test. |
| 88 | using TestFunction = |
| 89 | void (*)(base::debug::tracer::AllocationTraceRecorder& recorder, |
| 90 | ResultData& result_data); |
| 91 | |
| 92 | static TestFunction GetTestFunction(HandlerFunctionSelector handler_function); |
| 93 | template <HandlerFunctionSelector HandlerFunction> |
| 94 | static void TestFunctionImplementation( |
| 95 | base::debug::tracer::AllocationTraceRecorder& recorder, |
| 96 | ResultData& result_data); |
| 97 | |
| 98 | // The test management function. Using the the above auxiliary functions it is |
| 99 | // responsible to setup the result reporter, select the correct test function, |
| 100 | // spawn the specified number of worker threads and post process the results. |
| 101 | void PerformTest(HandlerFunctionSelector handler_function, |
| 102 | size_t number_of_allocating_threads); |
| 103 | }; |
| 104 | |
| 105 | const char* AllocationTraceRecorderPerfTest::GetHandlerDescriptor( |
| 106 | HandlerFunctionSelector handler_function) { |
| 107 | switch (handler_function) { |
| 108 | case HandlerFunctionSelector::OnAllocation: |
| 109 | return "OnAllocation"; |
| 110 | case HandlerFunctionSelector::OnFree: |
| 111 | return "OnFree"; |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | perf_test::PerfResultReporter AllocationTraceRecorderPerfTest::SetUpReporter( |
| 116 | HandlerFunctionSelector handler_function, |
| 117 | size_t number_of_allocating_threads) { |
| 118 | const std::string story_name = base::StringPrintf( |
| 119 | "(%s;%zu-threads)", GetHandlerDescriptor(handler_function), |
| 120 | number_of_allocating_threads); |
| 121 | |
| 122 | perf_test::PerfResultReporter reporter("AllocationRecorderPerf", story_name); |
| 123 | reporter.RegisterImportantMetric(kMetricStackTraceDuration, "ns"); |
| 124 | reporter.RegisterImportantMetric(kMetricStackTraceThroughput, "runs/s"); |
| 125 | return reporter; |
| 126 | } |
| 127 | |
| 128 | AllocationTraceRecorderPerfTest::TestFunction |
| 129 | AllocationTraceRecorderPerfTest::GetTestFunction( |
| 130 | HandlerFunctionSelector handler_function) { |
| 131 | switch (handler_function) { |
| 132 | case HandlerFunctionSelector::OnAllocation: |
| 133 | return TestFunctionImplementation<HandlerFunctionSelector::OnAllocation>; |
| 134 | case HandlerFunctionSelector::OnFree: |
| 135 | return TestFunctionImplementation<HandlerFunctionSelector::OnFree>; |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | void AllocationTraceRecorderPerfTest::PerformTest( |
| 140 | HandlerFunctionSelector handler_function, |
| 141 | size_t number_of_allocating_threads) { |
| 142 | perf_test::PerfResultReporter reporter = |
| 143 | SetUpReporter(handler_function, number_of_allocating_threads); |
| 144 | |
| 145 | TestFunction test_function = GetTestFunction(handler_function); |
| 146 | |
| 147 | base::debug::tracer::AllocationTraceRecorder the_recorder; |
| 148 | |
| 149 | std::vector<ThreadRunnerData> notifying_threads; |
| 150 | notifying_threads.reserve(number_of_allocating_threads); |
| 151 | |
| 152 | // Setup the threads. After creation, each thread immediately starts running. |
| 153 | // We expect the creation of the threads to be so quick that the delay from |
| 154 | // first to last thread is negligible. |
| 155 | for (size_t i = 0; i < number_of_allocating_threads; ++i) { |
| 156 | auto& last_item = notifying_threads.emplace_back(); |
| 157 | |
| 158 | last_item.thread = std::thread{test_function, std::ref(the_recorder), |
| 159 | std::ref(last_item.result_data)}; |
| 160 | } |
| 161 | |
| 162 | TimeDelta average_time_per_lap; |
| 163 | float average_laps_per_second = 0; |
| 164 | |
| 165 | // Wait for each thread to finish and collect its result data. |
| 166 | for (auto& item : notifying_threads) { |
| 167 | item.thread.join(); |
| 168 | // When finishing, each threads writes its results into result_data. So, |
| 169 | // from here we gather its performance statistics. |
| 170 | average_time_per_lap += item.result_data.time_per_lap; |
| 171 | average_laps_per_second += item.result_data.laps_per_second; |
| 172 | } |
| 173 | |
| 174 | average_time_per_lap /= number_of_allocating_threads; |
| 175 | average_laps_per_second /= number_of_allocating_threads; |
| 176 | |
| 177 | reporter.AddResult(kMetricStackTraceDuration, average_time_per_lap); |
| 178 | reporter.AddResult(kMetricStackTraceThroughput, average_laps_per_second); |
| 179 | } |
| 180 | |
| 181 | template <HandlerFunctionSelector HandlerFunction> |
| 182 | void AllocationTraceRecorderPerfTest::TestFunctionImplementation( |
| 183 | base::debug::tracer::AllocationTraceRecorder& recorder, |
| 184 | ResultData& result_data) { |
| 185 | LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval, |
| 186 | LapTimer::TimerMethod::kUseTimeTicks); |
| 187 | |
| 188 | HandlerFunctionExecutor<HandlerFunction> handler_executor; |
| 189 | |
| 190 | timer.Start(); |
| 191 | do { |
| 192 | handler_executor(recorder); |
| 193 | |
| 194 | timer.NextLap(); |
| 195 | } while (!timer.HasTimeLimitExpired()); |
| 196 | |
| 197 | result_data.time_per_lap = timer.TimePerLap(); |
| 198 | result_data.laps_per_second = timer.LapsPerSecond(); |
| 199 | result_data.number_of_laps = timer.NumLaps(); |
| 200 | } |
| 201 | |
| 202 | INSTANTIATE_TEST_SUITE_P( |
| 203 | , |
| 204 | AllocationTraceRecorderPerfTest, |
| 205 | ::testing::Combine(::testing::Values(HandlerFunctionSelector::OnAllocation, |
| 206 | HandlerFunctionSelector::OnFree), |
| 207 | ::testing::Values(1, 5, 10, 20, 40, 80))); |
| 208 | |
| 209 | TEST_P(AllocationTraceRecorderPerfTest, TestNotification) { |
| 210 | const auto parameters = GetParam(); |
| 211 | const HandlerFunctionSelector handler_function = std::get<0>(parameters); |
| 212 | const size_t number_of_threads = std::get<1>(parameters); |
| 213 | PerformTest(handler_function, number_of_threads); |
| 214 | } |
| 215 | |
| 216 | } // namespace debug |
| 217 | } // namespace base |