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