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