212 lines
7.6 KiB
C++
212 lines
7.6 KiB
C++
// Copyright 2023 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include "base/debug/allocation_trace.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "base/timer/lap_timer.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "testing/perf/perf_result_reporter.h"
|
|
|
|
namespace base {
|
|
namespace debug {
|
|
namespace {
|
|
// Change kTimeLimit to something higher if you need more time to capture a
|
|
// trace.
|
|
constexpr base::TimeDelta kTimeLimit = base::Seconds(3);
|
|
constexpr int kWarmupRuns = 100;
|
|
constexpr int kTimeCheckInterval = 1000;
|
|
constexpr char kMetricStackTraceDuration[] = ".duration_per_run";
|
|
constexpr char kMetricStackTraceThroughput[] = ".throughput";
|
|
|
|
enum class HandlerFunctionSelector { OnAllocation, OnFree };
|
|
|
|
// An executor to perform the actual notification of the recorder. The correct
|
|
// handler function is selected using template specialization based on the
|
|
// HandlerFunctionSelector.
|
|
template <HandlerFunctionSelector HandlerFunction>
|
|
struct HandlerFunctionExecutor {
|
|
void operator()(base::debug::tracer::AllocationTraceRecorder& recorder) const;
|
|
};
|
|
|
|
template <>
|
|
struct HandlerFunctionExecutor<HandlerFunctionSelector::OnAllocation> {
|
|
void operator()(
|
|
base::debug::tracer::AllocationTraceRecorder& recorder) const {
|
|
// Since the recorder just stores the value, we can use any value for
|
|
// address and size that we want.
|
|
recorder.OnAllocation(
|
|
&recorder, sizeof(recorder),
|
|
base::allocator::dispatcher::AllocationSubsystem::kPartitionAllocator,
|
|
nullptr);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct HandlerFunctionExecutor<HandlerFunctionSelector::OnFree> {
|
|
void operator()(
|
|
base::debug::tracer::AllocationTraceRecorder& recorder) const {
|
|
recorder.OnFree(&recorder);
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
class AllocationTraceRecorderPerfTest
|
|
: public testing::TestWithParam<
|
|
std::tuple<HandlerFunctionSelector, size_t>> {
|
|
protected:
|
|
// The result data of a single thread. From the results of all the single
|
|
// threads the final results will be calculated.
|
|
struct ResultData {
|
|
TimeDelta time_per_lap;
|
|
float laps_per_second = 0.0;
|
|
int number_of_laps = 0;
|
|
};
|
|
|
|
// The data of a single test thread.
|
|
struct ThreadRunnerData {
|
|
std::thread thread;
|
|
ResultData result_data;
|
|
};
|
|
|
|
// Create and setup the result reporter.
|
|
const char* GetHandlerDescriptor(HandlerFunctionSelector handler_function);
|
|
perf_test::PerfResultReporter SetUpReporter(
|
|
HandlerFunctionSelector handler_function,
|
|
size_t number_of_allocating_threads);
|
|
|
|
// Select the correct test function which shall be used for the current test.
|
|
using TestFunction =
|
|
void (*)(base::debug::tracer::AllocationTraceRecorder& recorder,
|
|
ResultData& result_data);
|
|
|
|
static TestFunction GetTestFunction(HandlerFunctionSelector handler_function);
|
|
template <HandlerFunctionSelector HandlerFunction>
|
|
static void TestFunctionImplementation(
|
|
base::debug::tracer::AllocationTraceRecorder& recorder,
|
|
ResultData& result_data);
|
|
|
|
// The test management function. Using the the above auxiliary functions it is
|
|
// responsible to setup the result reporter, select the correct test function,
|
|
// spawn the specified number of worker threads and post process the results.
|
|
void PerformTest(HandlerFunctionSelector handler_function,
|
|
size_t number_of_allocating_threads);
|
|
};
|
|
|
|
const char* AllocationTraceRecorderPerfTest::GetHandlerDescriptor(
|
|
HandlerFunctionSelector handler_function) {
|
|
switch (handler_function) {
|
|
case HandlerFunctionSelector::OnAllocation:
|
|
return "OnAllocation";
|
|
case HandlerFunctionSelector::OnFree:
|
|
return "OnFree";
|
|
}
|
|
}
|
|
|
|
perf_test::PerfResultReporter AllocationTraceRecorderPerfTest::SetUpReporter(
|
|
HandlerFunctionSelector handler_function,
|
|
size_t number_of_allocating_threads) {
|
|
const std::string story_name = base::StringPrintf(
|
|
"(%s;%zu-threads)", GetHandlerDescriptor(handler_function),
|
|
number_of_allocating_threads);
|
|
|
|
perf_test::PerfResultReporter reporter("AllocationRecorderPerf", story_name);
|
|
reporter.RegisterImportantMetric(kMetricStackTraceDuration, "ns");
|
|
reporter.RegisterImportantMetric(kMetricStackTraceThroughput, "runs/s");
|
|
return reporter;
|
|
}
|
|
|
|
AllocationTraceRecorderPerfTest::TestFunction
|
|
AllocationTraceRecorderPerfTest::GetTestFunction(
|
|
HandlerFunctionSelector handler_function) {
|
|
switch (handler_function) {
|
|
case HandlerFunctionSelector::OnAllocation:
|
|
return TestFunctionImplementation<HandlerFunctionSelector::OnAllocation>;
|
|
case HandlerFunctionSelector::OnFree:
|
|
return TestFunctionImplementation<HandlerFunctionSelector::OnFree>;
|
|
}
|
|
}
|
|
|
|
void AllocationTraceRecorderPerfTest::PerformTest(
|
|
HandlerFunctionSelector handler_function,
|
|
size_t number_of_allocating_threads) {
|
|
perf_test::PerfResultReporter reporter =
|
|
SetUpReporter(handler_function, number_of_allocating_threads);
|
|
|
|
TestFunction test_function = GetTestFunction(handler_function);
|
|
|
|
base::debug::tracer::AllocationTraceRecorder the_recorder;
|
|
|
|
std::vector<ThreadRunnerData> notifying_threads;
|
|
notifying_threads.reserve(number_of_allocating_threads);
|
|
|
|
// Setup the threads. After creation, each thread immediately starts running.
|
|
// We expect the creation of the threads to be so quick that the delay from
|
|
// first to last thread is negligible.
|
|
for (size_t i = 0; i < number_of_allocating_threads; ++i) {
|
|
auto& last_item = notifying_threads.emplace_back();
|
|
|
|
last_item.thread = std::thread{test_function, std::ref(the_recorder),
|
|
std::ref(last_item.result_data)};
|
|
}
|
|
|
|
TimeDelta average_time_per_lap;
|
|
float average_laps_per_second = 0;
|
|
|
|
// Wait for each thread to finish and collect its result data.
|
|
for (auto& item : notifying_threads) {
|
|
item.thread.join();
|
|
// When finishing, each threads writes its results into result_data. So,
|
|
// from here we gather its performance statistics.
|
|
average_time_per_lap += item.result_data.time_per_lap;
|
|
average_laps_per_second += item.result_data.laps_per_second;
|
|
}
|
|
|
|
average_time_per_lap /= number_of_allocating_threads;
|
|
average_laps_per_second /= number_of_allocating_threads;
|
|
|
|
reporter.AddResult(kMetricStackTraceDuration, average_time_per_lap);
|
|
reporter.AddResult(kMetricStackTraceThroughput, average_laps_per_second);
|
|
}
|
|
|
|
template <HandlerFunctionSelector HandlerFunction>
|
|
void AllocationTraceRecorderPerfTest::TestFunctionImplementation(
|
|
base::debug::tracer::AllocationTraceRecorder& recorder,
|
|
ResultData& result_data) {
|
|
LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval,
|
|
LapTimer::TimerMethod::kUseTimeTicks);
|
|
|
|
HandlerFunctionExecutor<HandlerFunction> handler_executor;
|
|
|
|
timer.Start();
|
|
do {
|
|
handler_executor(recorder);
|
|
|
|
timer.NextLap();
|
|
} while (!timer.HasTimeLimitExpired());
|
|
|
|
result_data.time_per_lap = timer.TimePerLap();
|
|
result_data.laps_per_second = timer.LapsPerSecond();
|
|
result_data.number_of_laps = timer.NumLaps();
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
,
|
|
AllocationTraceRecorderPerfTest,
|
|
::testing::Combine(::testing::Values(HandlerFunctionSelector::OnAllocation,
|
|
HandlerFunctionSelector::OnFree),
|
|
::testing::Values(1, 5, 10, 20, 40, 80)));
|
|
|
|
TEST_P(AllocationTraceRecorderPerfTest, TestNotification) {
|
|
const auto parameters = GetParam();
|
|
const HandlerFunctionSelector handler_function = std::get<0>(parameters);
|
|
const size_t number_of_threads = std::get<1>(parameters);
|
|
PerformTest(handler_function, number_of_threads);
|
|
}
|
|
|
|
} // namespace debug
|
|
} // namespace base
|