xref: /aosp_15_r20/external/cronet/base/debug/allocation_trace_perftest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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