]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/seastar/include/seastar/testing/perf_tests.hh
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / seastar / include / seastar / testing / perf_tests.hh
index d225310ba5b702577fde9bf43699baadce0fe0fd..60b6fc1e0250900546ab7d07a10f5a99fb063dab 100644 (file)
 
 #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;
 
@@ -38,14 +43,86 @@ struct config;
 
 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 {
@@ -59,7 +136,7 @@ protected:
 
     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)
@@ -86,33 +163,53 @@ class time_measurement {
     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;
     }
 };
 
@@ -162,10 +259,11 @@ protected:
     }
 
     [[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);
@@ -179,9 +277,11 @@ protected:
                 })();
             }).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();
@@ -192,7 +292,9 @@ protected:
                     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:
@@ -234,6 +336,8 @@ void do_not_optimize(const T& v)
 
 // 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
@@ -257,3 +361,23 @@ void do_not_optimize(const T& v)
     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