Branch data Line data Source code
1 : : #pragma once 2 : : 3 : : #include <mutable/util/fn.hpp> 4 : : #include <mutable/util/macro.hpp> 5 : : #include <algorithm> 6 : : #include <chrono> 7 : : #include <ctime> 8 : : #include <string> 9 : : #include <utility> 10 : : #include <vector> 11 : : 12 : : 13 : : namespace m { 14 : : 15 : : /** Collect timings of events. */ 16 : : struct Timer 17 : : { 18 : : /** A proxy class to record a `Measurement` with a `Timer`. */ 19 : : struct TimingProcess 20 : : { 21 : : friend struct Timer; 22 : : 23 : : private: 24 : : Timer &timer_; ///< the `Timer` instance 25 : : std::size_t id_; ///< the ID of the `Measurement` 26 : : 27 : 2682 : TimingProcess(Timer &timer, std::size_t index) : timer_(timer), id_(index) { } 28 : : 29 : : public: 30 : 2682 : ~TimingProcess() { 31 [ + - ]: 2682 : auto &M = timer_.get(id_); 32 [ + - + - ]: 2682 : if (not M.has_ended()) 33 [ + - ]: 2682 : timer_.stop(id_); 34 : 2682 : } 35 : : 36 : 0 : void stop() { timer_.stop(id_); } 37 : : }; 38 : : 39 : : using clock = std::chrono::high_resolution_clock; 40 : : using duration = clock::duration; 41 : : using time_point = clock::time_point; 42 : : 43 : : struct Measurement 44 : : { 45 : : std::string name; ///< the name of this `Measurement` 46 : : time_point begin, end; ///< the begin and end time points of this `Measurement` 47 : : 48 : 54 : explicit Measurement(std::string name, time_point begin = time_point(), time_point end = time_point()) 49 : 54 : : name(std::move(name)) 50 : 54 : , begin(std::move(begin)) 51 : 54 : , end(std::move(end)) 52 : 54 : { } 53 : : 54 : : /** Clear this `Measurement`, rendering it unused. */ 55 : 1 : void clear() { begin = end = time_point(); } 56 : : 57 : : /** Start this `Measurement` by setting the start time point to *NOW*. */ 58 : 53 : void start() { 59 : 53 : M_insist(is_unused()); 60 : 53 : begin = clock::now(); 61 : 53 : } 62 : : 63 : : /** Stop this `Measurement` by setting the end time point to *NOW*. */ 64 : 2683 : void stop() { 65 : 2683 : M_insist(is_active()); 66 : 2683 : end = clock::now(); 67 : 2683 : } 68 : : 69 : : /** Returns `true` iff the `Measurement` is unused, i.e. has not started (and hence also not finished). */ 70 : 55 : bool is_unused() const { 71 [ - + ]: 55 : M_insist(has_started() or not has_ended(), 72 : : "if the measurement hasn't started it must not have ended"); 73 : 55 : return not has_started(); 74 : : } 75 : : /** Returns `true` iff this `Measurement` has begun. */ 76 : 5444 : bool has_started() const { return begin != time_point(); } 77 : : /** Returns `true` iff this `Measurement` has ended. */ 78 : 10751 : bool has_ended() const { return end != time_point(); } 79 : : /** Returns `true` iff this `Measurement` is currently in process. */ 80 [ + + ]: 5320 : bool is_active() const { return has_started() and not has_ended(); } 81 : : /** Returns `true` iff this `Measurement` has completed. */ 82 [ + + ]: 10 : bool is_finished() const { return has_started() and has_ended(); } 83 : : 84 : : /** Returns the duration of a *finished* `Measurement`. */ 85 : 6 : duration duration() const { 86 : 6 : M_insist(is_finished(), "can only compute duration of finished measurements"); 87 : 6 : return end - begin; 88 : : } 89 : : 90 : : friend std::ostream & operator<<(std::ostream &out, const Measurement &M); 91 : : void dump(std::ostream &out) const; 92 : : void dump() const; 93 : : }; 94 : : 95 : : private: 96 : : std::vector<Measurement> measurements_; 97 : : 98 : : public: 99 : 1 : auto begin() const { return measurements_.cbegin(); } 100 : 1 : auto end() const { return measurements_.cend(); } 101 : : auto cbegin() const { return measurements_.cbegin(); } 102 : : auto cend() const { return measurements_.cend(); } 103 : : 104 : 1 : const std::vector<Measurement> & measurements() const { return measurements_; } 105 : 2685 : const Measurement & get(std::size_t i) const { 106 [ + + ]: 2685 : if (i >= measurements_.size()) 107 [ + - + - : 2 : throw m::out_of_range("index i out of bounds"); - + + - ] 108 : 2683 : return measurements_[i]; 109 : 2 : } 110 : 3 : const Measurement & get(const std::string &name) const { 111 : 6 : auto it = std::find_if(measurements_.begin(), measurements_.end(), 112 : 6 : [&](auto &elem) { return elem.name == name; }); 113 [ + + ]: 3 : if (it == measurements_.end()) 114 [ + - + - : 2 : throw m::out_of_range("a measurement with that name does not exist"); - + + - ] 115 : 1 : return *it; 116 : 2 : } 117 : : 118 : : /** Erase all `Measurement`s from this `Timer`. */ 119 : 1 : void clear() { measurements_.clear(); } 120 : : 121 : : private: 122 : : /** Start a new `Measurement` with the name `name`. Returns the ID assigned to that `Measurement`. */ 123 : 2683 : std::size_t start(std::string name) { 124 : 5366 : auto it = std::find_if(measurements_.begin(), measurements_.end(), 125 : 8449 : [&](auto &elem) { return elem.name == name; }); 126 : : 127 [ + + ]: 2683 : if (it != measurements_.end()) { // overwrite existing, finished measurement 128 [ + + ]: 2631 : if (it->is_active()) 129 [ + - + - : 1 : throw m::invalid_argument("a measurement with that name is already in progress"); - + + - ] 130 : 2630 : const auto idx = std::distance(measurements_.begin(), it); 131 : 2630 : it->end = time_point(); 132 : 2630 : it->begin = clock::now(); 133 : 2630 : return idx; 134 : : } else { // create new measurement 135 : 52 : auto id = measurements_.size(); 136 : 52 : auto &M = measurements_.emplace_back(name); 137 : 52 : M.start(); 138 : 52 : return id; 139 : : } 140 : 2683 : } 141 : : 142 : : /** Stops the `Measurement` with the given ID. */ 143 : 2682 : void stop(std::size_t id) { 144 : 2682 : M_insist(id < measurements_.size(), "id out of bounds"); 145 : 2682 : auto &M = measurements_[id]; 146 : 2682 : M_insist(not M.has_ended(), "cannot stop that measurement because it has already been stopped"); 147 : 2682 : M.stop(); 148 : 2682 : } 149 : : 150 : : public: 151 : : /** Creates a new `TimingProcess` with the given `name`. */ 152 [ + + + - ]: 2683 : TimingProcess create_timing(std::string name) { return TimingProcess(*this, /* ID= */ start(name)); } 153 : : 154 : : M_LCOV_EXCL_START 155 : : /** Print all finished and in-process timings of `timer` to `out`. */ 156 : : friend std::ostream & operator<<(std::ostream &out, const Timer &timer) { 157 : : out << "Timer measurements:\n"; 158 : : for (auto &M : timer) 159 : : out << " " << M << '\n'; 160 : : 161 : : return out; 162 : : } 163 : : 164 : : void dump(std::ostream &out) const; 165 : : void dump() const; 166 : : M_LCOV_EXCL_STOP 167 : : 168 : : /** Computes the total duration of all `Measurement`s. */ 169 : 1 : duration total() const { 170 : 1 : duration d(0); 171 [ + + ]: 3 : for (auto &m : measurements_) 172 : 2 : d += m.duration(); 173 : 1 : return d; 174 : : } 175 : : }; 176 : : 177 : : #define M_TIME_EXPR(EXPR, DESCR, TIMER) \ 178 : : [&]() { auto TP = (TIMER).create_timing((DESCR)); return (EXPR); }(); 179 : : #define M_TIME_BLOCK(DESCR, TIMER, BLOCK) {\ 180 : : auto TP = (TIMER).create_timing((DESCR)); \ 181 : : BLOCK \ 182 : : } 183 : : #define M_TIME_THIS(DESCR, TIMER) \ 184 : : auto M_PASTE(__timer_, __COUNTER__) = (TIMER).create_timing((DESCR)); 185 : : 186 : : }