blob: c2492ef5cb4f68469b6a372347d50fcba10b95c6 [file] [log] [blame]
André Kempef23c03c2023-04-25 17:57:361// 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 Kasting134ef9af2024-12-28 02:30:095#include "base/debug/allocation_trace.h"
6
André Kempef23c03c2023-04-25 17:57:367#include <thread>
8#include <vector>
9
André Kempe69aec282024-01-03 19:28:4510#include "base/allocator/dispatcher/notification_data.h"
11#include "base/allocator/dispatcher/subsystem.h"
André Kempef23c03c2023-04-25 17:57:3612#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
17namespace base {
18namespace debug {
19namespace {
20// Change kTimeLimit to something higher if you need more time to capture a
21// trace.
22constexpr base::TimeDelta kTimeLimit = base::Seconds(3);
23constexpr int kWarmupRuns = 100;
24constexpr int kTimeCheckInterval = 1000;
25constexpr char kMetricStackTraceDuration[] = ".duration_per_run";
26constexpr char kMetricStackTraceThroughput[] = ".throughput";
27
28enum 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.
33template <HandlerFunctionSelector HandlerFunction>
34struct HandlerFunctionExecutor {
35 void operator()(base::debug::tracer::AllocationTraceRecorder& recorder) const;
36};
37
38template <>
39struct 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é Kempe69aec282024-01-03 19:28:4545 base::allocator::dispatcher::AllocationNotificationData(
46 &recorder, sizeof(recorder), nullptr,
47 base::allocator::dispatcher::AllocationSubsystem::
48 kPartitionAllocator));
André Kempef23c03c2023-04-25 17:57:3649 }
50};
51
52template <>
53struct HandlerFunctionExecutor<HandlerFunctionSelector::OnFree> {
54 void operator()(
55 base::debug::tracer::AllocationTraceRecorder& recorder) const {
André Kempe69aec282024-01-03 19:28:4556 recorder.OnFree(base::allocator::dispatcher::FreeNotificationData(
57 &recorder,
58 base::allocator::dispatcher::AllocationSubsystem::kPartitionAllocator));
André Kempef23c03c2023-04-25 17:57:3659 }
60};
61} // namespace
62
63class 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
105const 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
115perf_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
128AllocationTraceRecorderPerfTest::TestFunction
129AllocationTraceRecorderPerfTest::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
139void 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
181template <HandlerFunctionSelector HandlerFunction>
182void 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
202INSTANTIATE_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
209TEST_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