27#include <fmt/format.h>
29#include <seastar/core/coroutine.hh>
30#include <seastar/core/future.hh>
31#include <seastar/core/loop.hh>
32#include <seastar/testing/linux_perf_event.hh>
41using clock_type = std::chrono::steady_clock;
45 uint64_t allocations = 0;
46 uint64_t tasks_executed = 0;
47 uint64_t instructions_retired = 0;
48 uint64_t cpu_cycles_retired = 0;
51 static uint64_t perf_mallocs();
52 static uint64_t perf_tasks_processed();
56 perf_stats(uint64_t allocations_, uint64_t tasks_executed_, uint64_t instructions_retired_ = 0, uint64_t cpu_cycles_retired_ = 0)
57 : allocations(allocations_)
58 , tasks_executed(tasks_executed_)
59 , instructions_retired(instructions_retired_)
60 , cpu_cycles_retired(cpu_cycles_retired_)
63 : allocations(std::exchange(o.allocations, 0))
64 , tasks_executed(std::exchange(o.tasks_executed, 0))
65 , instructions_retired(std::exchange(o.instructions_retired, 0))
66 , cpu_cycles_retired(std::exchange(o.cpu_cycles_retired, 0))
82 a.allocations += b.allocations;
83 a.tasks_executed += b.tasks_executed;
84 a.instructions_retired += b.instructions_retired;
85 a.cpu_cycles_retired += b.cpu_cycles_retired;
91operator-(perf_stats a, perf_stats b) {
92 a.allocations -= b.allocations;
93 a.tasks_executed -= b.tasks_executed;
94 a.instructions_retired -= b.instructions_retired;
95 a.cpu_cycles_retired -= b.cpu_cycles_retired;
99inline perf_stats& perf_stats::operator+=(perf_stats b) {
100 allocations += b.allocations;
101 tasks_executed += b.tasks_executed;
102 instructions_retired += b.instructions_retired;
103 cpu_cycles_retired += b.cpu_cycles_retired;
107inline perf_stats& perf_stats::operator-=(perf_stats b) {
108 allocations -= b.allocations;
109 tasks_executed -= b.tasks_executed;
110 instructions_retired -= b.instructions_retired;
111 cpu_cycles_retired -= b.cpu_cycles_retired;
116 std::string _test_case;
117 std::string _test_group;
119 uint64_t _single_run_iterations = 0;
120 std::atomic<uint64_t> _max_single_run_iterations;
122 linux_perf_event _instructions_retired_counter = linux_perf_event::user_instructions_retired();
123 linux_perf_event _cpu_cycles_retired_counter = linux_perf_event::user_cpu_cycles_retired();
125 void do_run(
const config&);
128 clock_type::duration duration;
132 [[gnu::always_inline]] [[gnu::hot]]
134 return _single_run_iterations >= _max_single_run_iterations.load(std::memory_order_relaxed);
137 [[gnu::always_inline]] [[gnu::hot]]
138 void next_iteration(
size_t n) {
139 _single_run_iterations += n;
142 virtual void set_up() = 0;
143 virtual void tear_down() noexcept = 0;
144 virtual
future<run_result> do_single_run() = 0;
146 performance_test(const
std::
string& test_case, const
std::
string& test_group)
147 : _test_case(test_case)
148 , _test_group(test_group)
151 virtual ~performance_test() =
default;
153 const std::string& test_case()
const {
return _test_case; }
154 const std::string& test_group()
const {
return _test_group; }
155 std::string name()
const {
return fmt::format(
"{}.{}", test_group(), test_case()); }
157 void run(
const config&);
159 static void register_test(std::unique_ptr<performance_test>);
168 clock_type::time_point _run_start_time;
169 clock_type::time_point _start_time;
170 clock_type::duration _total_time;
179 [[gnu::always_inline]] [[gnu::hot]]
181 _instructions_retired_counter = instructions_retired_counter;
182 _cpu_cycles_retired_counter = cpu_cycles_retired_counter;
188 _start_stats = perf_stats::snapshot(_instructions_retired_counter, _cpu_cycles_retired_counter);
191 [[gnu::always_inline]] [[gnu::hot]]
195 if (_start_time == _run_start_time) {
196 ret.duration = t - _start_time;
197 auto stats = perf_stats::snapshot(_instructions_retired_counter, _cpu_cycles_retired_counter);
198 ret.stats = stats - _start_stats;
200 ret.duration = _total_time;
201 ret.stats = _total_stats;
203 _instructions_retired_counter =
nullptr;
204 _cpu_cycles_retired_counter =
nullptr;
208 [[gnu::always_inline]] [[gnu::hot]]
209 void start_iteration() {
211 _start_stats = perf_stats::snapshot(_instructions_retired_counter, _cpu_cycles_retired_counter);
214 [[gnu::always_inline]] [[gnu::hot]]
217 _total_time += t - _start_time;
219 stats = perf_stats::snapshot(_instructions_retired_counter, _cpu_cycles_retired_counter);
220 _total_stats += stats - _start_stats;
226template<
typename Test>
228 std::optional<Test> _test;
230 using test_ret_type =
decltype(_test->run());
235 static constexpr bool is_iteration_returning = !(std::is_same_v<test_ret_type, future<>> || std::is_void_v<test_ret_type>);
239 virtual void set_up()
override {
243 virtual void tear_down()
noexcept override {
244 _test = std::nullopt;
249 _instructions_retired_counter.enable();
250 _cpu_cycles_retired_counter.enable();
251 measure_time.start_run(&_instructions_retired_counter, &_cpu_cycles_retired_counter);
253 if constexpr (is_async_test) {
254 if constexpr (is_iteration_returning) {
255 auto f = _test->run();
256 next_iteration(f.available() ? std::move(f).get() :
co_await std::move(f));
258 auto f = _test->run();
261 if (!f.available()) {
262 co_await std::move(f);
267 if constexpr (is_iteration_returning) {
268 next_iteration(_test->run());
275 auto ret = measure_time.stop_run();
276 _instructions_retired_counter.disable();
277 _cpu_cycles_retired_counter.disable();
281 using performance_test::performance_test;
284void register_test(std::unique_ptr<performance_test>);
286template<
typename Test>
288 test_registrar(
const std::string& test_group,
const std::string& test_case) {
289 auto test = std::make_unique<concrete_performance_test<Test>>(test_case, test_group);
290 performance_test::register_test(std::move(test));
296[[gnu::always_inline]]
297inline void start_measuring_time()
299 internal::measure_time.start_iteration();
302[[gnu::always_inline]]
303inline void stop_measuring_time()
305 internal::measure_time.stop_iteration();
310void do_not_optimize(
const T& v)
312 asm volatile(
"" : :
"r,m" (v));
329#define PERF_TEST_F(test_group, test_case) \
330 struct test_##test_group##_##test_case : test_group { \
331 [[gnu::always_inline]] inline auto run(); \
333 static ::perf_tests::internal::test_registrar<test_##test_group##_##test_case> \
334 test_##test_group##_##test_case##_registrar(#test_group, #test_case); \
335 [[gnu::always_inline]] auto test_##test_group##_##test_case::run()
337#define PERF_TEST(test_group, test_case) \
338 struct test_##test_group##_##test_case { \
339 [[gnu::always_inline]] inline auto run(); \
341 static ::perf_tests::internal::test_registrar<test_##test_group##_##test_case> \
342 test_##test_group##_##test_case##_registrar(#test_group, #test_case); \
343 [[gnu::always_inline]] auto test_##test_group##_##test_case::run()
346#define PERF_TEST_C(test_group, test_case) \
347 struct test_##test_group##_##test_case : test_group { \
348 inline future<> run(); \
350 static ::perf_tests::internal::test_registrar<test_##test_group##_##test_case> \
351 test_##test_group##_##test_case##_registrar(#test_group, #test_case); \
352 future<> test_##test_group##_##test_case::run()
354#define PERF_TEST_CN(test_group, test_case) \
355 struct test_##test_group##_##test_case : test_group { \
356 inline future<size_t> run(); \
358 static ::perf_tests::internal::test_registrar<test_##test_group##_##test_case> \
359 test_##test_group##_##test_case##_registrar(#test_group, #test_case); \
360 future<size_t> test_##test_group##_##test_case::run()
Definition: linux_perf_event.hh:36
Definition: perf_tests.hh:43
Definition: perf_tests.hh:167
Type-safe boolean.
Definition: bool_class.hh:58
A representation of a possibly not-yet-computed value.
Definition: future.hh:1197
future now()
Returns a ready future.
Definition: later.hh:35
Seastar API namespace.
Definition: abort_on_ebadf.hh:26
Definition: perf_tests.hh:287
Check whether a type is a future.
Definition: future.hh:1010