• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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