]> git.proxmox.com Git - ceph.git/blame - ceph/src/seastar/include/seastar/testing/perf_tests.hh
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / seastar / include / seastar / testing / perf_tests.hh
CommitLineData
11fdf7f2
TL
1/*
2 * This file is open source software, licensed to you under the terms
3 * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
4 * distributed with this work for additional information regarding copyright
5 * ownership. You may not use this file except in compliance with the License.
6 *
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing,
12 * software distributed under the License is distributed on an
13 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 * KIND, either express or implied. See the License for the
15 * specific language governing permissions and limitations
16 * under the License.
17 */
18/*
19 * Copyright (C) 2018 ScyllaDB Ltd.
20 */
21
22#pragma once
23
24#include <atomic>
25#include <memory>
26
27#include <fmt/format.h>
28
29#include <seastar/core/future.hh>
f67539c2 30#include <seastar/core/loop.hh>
11fdf7f2
TL
31
32using namespace seastar;
33
34namespace perf_tests {
35namespace internal {
36
37struct config;
38
39using clock_type = std::chrono::steady_clock;
40
41class performance_test {
42 std::string _test_case;
43 std::string _test_group;
44
45 uint64_t _single_run_iterations = 0;
46 std::atomic<uint64_t> _max_single_run_iterations;
47private:
48 void do_run(const config&);
49protected:
50 [[gnu::always_inline]] [[gnu::hot]]
51 bool stop_iteration() const {
52 return _single_run_iterations >= _max_single_run_iterations.load(std::memory_order_relaxed);
53 }
54
55 [[gnu::always_inline]] [[gnu::hot]]
9f95a23c
TL
56 void next_iteration(size_t n) {
57 _single_run_iterations += n;
11fdf7f2
TL
58 }
59
60 virtual void set_up() = 0;
61 virtual void tear_down() noexcept = 0;
62 virtual future<clock_type::duration> do_single_run() = 0;
63public:
64 performance_test(const std::string& test_case, const std::string& test_group)
65 : _test_case(test_case)
66 , _test_group(test_group)
67 { }
68
69 virtual ~performance_test() = default;
70
71 const std::string& test_case() const { return _test_case; }
72 const std::string& test_group() const { return _test_group; }
73 std::string name() const { return fmt::format("{}.{}", test_group(), test_case()); }
74
75 void run(const config&);
76public:
77 static void register_test(std::unique_ptr<performance_test>);
78};
79
80// Helper for measuring time.
81// Each microbenchmark can either use the default behaviour which measures
82// only the start and stop time of the whole run or manually invoke
83// start_measuring_time() and stop_measuring_time() in order to measure
84// only parts of each iteration.
85class time_measurement {
86 clock_type::time_point _run_start_time;
87 clock_type::time_point _start_time;
88 clock_type::duration _total_time;
89public:
90 [[gnu::always_inline]] [[gnu::hot]]
91 void start_run() {
92 _total_time = { };
93 auto t = clock_type::now();
94 _run_start_time = t;
95 _start_time = t;
96 }
97
98 [[gnu::always_inline]] [[gnu::hot]]
99 clock_type::duration stop_run() {
100 auto t = clock_type::now();
101 if (_start_time == _run_start_time) {
102 return t - _start_time;
103 }
104 return _total_time;
105 }
106
107 [[gnu::always_inline]] [[gnu::hot]]
108 void start_iteration() {
109 _start_time = clock_type::now();
110 }
9f95a23c 111
11fdf7f2
TL
112 [[gnu::always_inline]] [[gnu::hot]]
113 void stop_iteration() {
114 auto t = clock_type::now();
115 _total_time += t - _start_time;
116 }
117};
118
119extern time_measurement measure_time;
120
121namespace {
122
123template<bool Condition, typename TrueFn, typename FalseFn>
124struct do_if_constexpr_ : FalseFn {
125 do_if_constexpr_(TrueFn, FalseFn false_fn) : FalseFn(std::move(false_fn)) { }
126 decltype(auto) operator()() const {
127 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095
128 return FalseFn::operator()(0);
129 }
130};
131template<typename TrueFn, typename FalseFn>
132struct do_if_constexpr_<true, TrueFn, FalseFn> : TrueFn {
133 do_if_constexpr_(TrueFn true_fn, FalseFn) : TrueFn(std::move(true_fn)) { }
134 decltype(auto) operator()() const { return TrueFn::operator()(0); }
135};
136
137template<bool Condition, typename TrueFn, typename FalseFn>
138do_if_constexpr_<Condition, TrueFn, FalseFn> if_constexpr_(TrueFn&& true_fn, FalseFn&& false_fn)
139{
140 return do_if_constexpr_<Condition, TrueFn, FalseFn>(std::forward<TrueFn>(true_fn),
141 std::forward<FalseFn>(false_fn));
142}
143
144}
145
146template<typename Test>
147class concrete_performance_test final : public performance_test {
f67539c2 148 std::optional<Test> _test;
9f95a23c
TL
149private:
150 template<typename... Args>
151 auto run_test(Args&&...) {
152 return _test->run();
153 }
154
11fdf7f2
TL
155protected:
156 virtual void set_up() override {
157 _test.emplace();
158 }
159
160 virtual void tear_down() noexcept override {
f67539c2 161 _test = std::nullopt;
11fdf7f2
TL
162 }
163
164 [[gnu::hot]]
165 virtual future<clock_type::duration> do_single_run() override {
166 // Redundant 'this->'s courtesy of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61636
167 return if_constexpr_<is_future<decltype(_test->run())>::value>([&] (auto&&...) {
168 measure_time.start_run();
169 return do_until([this] { return this->stop_iteration(); }, [this] {
9f95a23c
TL
170 return if_constexpr_<std::is_same<decltype(_test->run()), future<>>::value>([&] (auto&&...) {
171 this->next_iteration(1);
172 return _test->run();
173 }, [&] (auto&&... dependency) {
174 // We need `dependency` to make sure the compiler won't be able to instantiate anything
175 // (and notice that the code does not compile) if this part of if_constexpr_ is not active.
176 return run_test(dependency...).then([&] (size_t n) {
177 this->next_iteration(n);
178 });
179 })();
11fdf7f2
TL
180 }).then([] {
181 return measure_time.stop_run();
182 });
183 }, [&] (auto&&...) {
184 measure_time.start_run();
185 while (!stop_iteration()) {
9f95a23c
TL
186 if_constexpr_<std::is_void<decltype(_test->run())>::value>([&] (auto&&...) {
187 (void)_test->run();
188 this->next_iteration(1);
189 }, [&] (auto&&... dependency) {
190 // We need `dependency` to make sure the compiler won't be able to instantiate anything
191 // (and notice that the code does not compile) if this part of if_constexpr_ is not active.
192 this->next_iteration(run_test(dependency...));
193 })();
11fdf7f2
TL
194 }
195 return make_ready_future<clock_type::duration>(measure_time.stop_run());
196 })();
197 }
198public:
199 using performance_test::performance_test;
200};
201
202void register_test(std::unique_ptr<performance_test>);
203
204template<typename Test>
205struct test_registrar {
206 test_registrar(const std::string& test_group, const std::string& test_case) {
207 auto test = std::make_unique<concrete_performance_test<Test>>(test_case, test_group);
208 performance_test::register_test(std::move(test));
209 }
210};
211
212}
213
214[[gnu::always_inline]]
215inline void start_measuring_time()
216{
217 internal::measure_time.start_iteration();
218}
219
220[[gnu::always_inline]]
221inline void stop_measuring_time()
222{
223 internal::measure_time.stop_iteration();
224}
225
226
227template<typename T>
228void do_not_optimize(const T& v)
229{
230 asm volatile("" : : "r,m" (v));
231}
232
233}
234
9f95a23c
TL
235// PERF_TEST and PERF_TEST_F support both synchronous and asynchronous functions.
236// The former should return `void`, the latter `future<>`.
237//
238// Test cases may perform multiple operations in a single run, this may be desirable
239// if the cost of an individual operation is very small. This allows measuring either
240// the latency of throughput depending on how the test in written. In such cases,
241// the test function shall return either size_t or future<size_t> for synchronous and
242// asynchronous cases respectively. The returned value shall be the number of iterations
243// done in a single test run.
244
11fdf7f2
TL
245#define PERF_TEST_F(test_group, test_case) \
246 struct test_##test_group##_##test_case : test_group { \
247 [[gnu::always_inline]] inline auto run(); \
248 }; \
249 static ::perf_tests::internal::test_registrar<test_##test_group##_##test_case> \
250 test_##test_group##_##test_case##_registrar(#test_group, #test_case); \
251 [[gnu::always_inline]] auto test_##test_group##_##test_case::run()
252
253#define PERF_TEST(test_group, test_case) \
254 struct test_##test_group##_##test_case { \
255 [[gnu::always_inline]] inline auto run(); \
256 }; \
257 static ::perf_tests::internal::test_registrar<test_##test_group##_##test_case> \
258 test_##test_group##_##test_case##_registrar(#test_group, #test_case); \
259 [[gnu::always_inline]] auto test_##test_group##_##test_case::run()