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