#include <seastar/core/future.hh>
#include <seastar/core/loop.hh>
+#include <seastar/testing/linux_perf_event.hh>
+
+#ifdef SEASTAR_COROUTINES_ENABLED
+#include <seastar/core/coroutine.hh>
+#endif
using namespace seastar;
using clock_type = std::chrono::steady_clock;
+class perf_stats {
+public:
+ uint64_t allocations = 0;
+ uint64_t tasks_executed = 0;
+ uint64_t instructions_retired = 0;
+
+private:
+ static uint64_t perf_mallocs();
+ static uint64_t perf_tasks_processed();
+
+public:
+ perf_stats() = default;
+ perf_stats(uint64_t allocations_, uint64_t tasks_executed_, uint64_t instructions_retired_ = 0)
+ : allocations(allocations_)
+ , tasks_executed(tasks_executed_)
+ , instructions_retired(instructions_retired_)
+ {}
+ perf_stats(perf_stats&& o) noexcept
+ : allocations(std::exchange(o.allocations, 0))
+ , tasks_executed(std::exchange(o.tasks_executed, 0))
+ , instructions_retired(std::exchange(o.instructions_retired, 0))
+ {}
+ perf_stats(const perf_stats& o) = default;
+
+ perf_stats& operator=(perf_stats&& o) = default;
+ perf_stats& operator=(const perf_stats& o) = default;
+
+ perf_stats& operator+=(perf_stats b);
+ perf_stats& operator-=(perf_stats b);
+
+ static perf_stats snapshot(linux_perf_event* instructions_retired_counter = nullptr);
+};
+
+inline
+perf_stats
+operator+(perf_stats a, perf_stats b) {
+ a.allocations += b.allocations;
+ a.tasks_executed += b.tasks_executed;
+ a.instructions_retired += b.instructions_retired;
+ return a;
+}
+
+inline
+perf_stats
+operator-(perf_stats a, perf_stats b) {
+ a.allocations -= b.allocations;
+ a.tasks_executed -= b.tasks_executed;
+ a.instructions_retired -= b.instructions_retired;
+ return a;
+}
+
+inline perf_stats& perf_stats::operator+=(perf_stats b) {
+ allocations += b.allocations;
+ tasks_executed += b.tasks_executed;
+ instructions_retired += b.instructions_retired;
+ return *this;
+}
+
+inline perf_stats& perf_stats::operator-=(perf_stats b) {
+ allocations -= b.allocations;
+ tasks_executed -= b.tasks_executed;
+ instructions_retired -= b.instructions_retired;
+ return *this;
+}
+
class performance_test {
std::string _test_case;
std::string _test_group;
uint64_t _single_run_iterations = 0;
std::atomic<uint64_t> _max_single_run_iterations;
+protected:
+ linux_perf_event _instructions_retired_counter = linux_perf_event::user_instructions_retired();
private:
void do_run(const config&);
+public:
+ struct run_result {
+ clock_type::duration duration;
+ perf_stats stats;
+ };
protected:
[[gnu::always_inline]] [[gnu::hot]]
bool stop_iteration() const {
virtual void set_up() = 0;
virtual void tear_down() noexcept = 0;
- virtual future<clock_type::duration> do_single_run() = 0;
+ virtual future<run_result> do_single_run() = 0;
public:
performance_test(const std::string& test_case, const std::string& test_group)
: _test_case(test_case)
clock_type::time_point _run_start_time;
clock_type::time_point _start_time;
clock_type::duration _total_time;
+
+ perf_stats _start_stats;
+ perf_stats _total_stats;
+
+ linux_perf_event* _instructions_retired_counter = nullptr;
+
public:
[[gnu::always_inline]] [[gnu::hot]]
- void start_run() {
+ void start_run(linux_perf_event* instructions_retired_counter = nullptr) {
+ _instructions_retired_counter = instructions_retired_counter;
_total_time = { };
+ _total_stats = {};
auto t = clock_type::now();
_run_start_time = t;
_start_time = t;
+ _start_stats = perf_stats::snapshot(_instructions_retired_counter);
}
[[gnu::always_inline]] [[gnu::hot]]
- clock_type::duration stop_run() {
+ performance_test::run_result stop_run() {
auto t = clock_type::now();
+ performance_test::run_result ret;
if (_start_time == _run_start_time) {
- return t - _start_time;
+ ret.duration = t - _start_time;
+ auto stats = perf_stats::snapshot(_instructions_retired_counter);
+ ret.stats = stats - _start_stats;
+ } else {
+ ret.duration = _total_time;
+ ret.stats = _total_stats;
}
- return _total_time;
+ _instructions_retired_counter = nullptr;
+ return ret;
}
[[gnu::always_inline]] [[gnu::hot]]
void start_iteration() {
_start_time = clock_type::now();
+ _start_stats = perf_stats::snapshot(_instructions_retired_counter);
}
[[gnu::always_inline]] [[gnu::hot]]
void stop_iteration() {
auto t = clock_type::now();
_total_time += t - _start_time;
+ perf_stats stats;
+ stats = perf_stats::snapshot(_instructions_retired_counter);
+ _total_stats += stats - _start_stats;
}
};
}
[[gnu::hot]]
- virtual future<clock_type::duration> do_single_run() override {
+ virtual future<run_result> do_single_run() override {
// Redundant 'this->'s courtesy of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61636
+ _instructions_retired_counter.enable();
return if_constexpr_<is_future<decltype(_test->run())>::value>([&] (auto&&...) {
- measure_time.start_run();
+ measure_time.start_run(&_instructions_retired_counter);
return do_until([this] { return this->stop_iteration(); }, [this] {
return if_constexpr_<std::is_same<decltype(_test->run()), future<>>::value>([&] (auto&&...) {
this->next_iteration(1);
})();
}).then([] {
return measure_time.stop_run();
+ }).finally([this] {
+ _instructions_retired_counter.disable();
});
}, [&] (auto&&...) {
- measure_time.start_run();
+ measure_time.start_run(&_instructions_retired_counter);
while (!stop_iteration()) {
if_constexpr_<std::is_void<decltype(_test->run())>::value>([&] (auto&&...) {
(void)_test->run();
this->next_iteration(run_test(dependency...));
})();
}
- return make_ready_future<clock_type::duration>(measure_time.stop_run());
+ auto ret = measure_time.stop_run();
+ _instructions_retired_counter.disable();
+ return make_ready_future<run_result>(std::move(ret));
})();
}
public:
// PERF_TEST and PERF_TEST_F support both synchronous and asynchronous functions.
// The former should return `void`, the latter `future<>`.
+// PERF_TEST_C executes a coroutine function, if enabled.
+// PERF_TEST_CN executes a coroutine function, if enabled, returning the number of inner-loops.
//
// Test cases may perform multiple operations in a single run, this may be desirable
// if the cost of an individual operation is very small. This allows measuring either
static ::perf_tests::internal::test_registrar<test_##test_group##_##test_case> \
test_##test_group##_##test_case##_registrar(#test_group, #test_case); \
[[gnu::always_inline]] auto test_##test_group##_##test_case::run()
+
+#ifdef SEASTAR_COROUTINES_ENABLED
+
+#define PERF_TEST_C(test_group, test_case) \
+ struct test_##test_group##_##test_case : test_group { \
+ inline future<> run(); \
+ }; \
+ static ::perf_tests::internal::test_registrar<test_##test_group##_##test_case> \
+ test_##test_group##_##test_case##_registrar(#test_group, #test_case); \
+ future<> test_##test_group##_##test_case::run()
+
+#define PERF_TEST_CN(test_group, test_case) \
+ struct test_##test_group##_##test_case : test_group { \
+ inline future<size_t> run(); \
+ }; \
+ static ::perf_tests::internal::test_registrar<test_##test_group##_##test_case> \
+ test_##test_group##_##test_case##_registrar(#test_group, #test_case); \
+ future<size_t> test_##test_group##_##test_case::run()
+
+#endif // SEASTAR_COROUTINES_ENABLED