1#pragma once
2
3#include <time.h>
4#include <atomic>
5#include <common/Types.h>
6#include <port/clock.h>
7
8
9namespace StopWatchDetail
10{
11 inline UInt64 nanoseconds(clockid_t clock_type)
12 {
13 struct timespec ts;
14 clock_gettime(clock_type, &ts);
15 return UInt64(ts.tv_sec * 1000000000LL + ts.tv_nsec);
16 }
17}
18
19
20/** Differs from Poco::Stopwatch only by using 'clock_gettime' instead of 'gettimeofday',
21 * returns nanoseconds instead of microseconds, and also by other minor differencies.
22 */
23class Stopwatch
24{
25public:
26 /** CLOCK_MONOTONIC works relatively efficient (~15 million calls/sec) and doesn't lead to syscall.
27 * Pass CLOCK_MONOTONIC_COARSE, if you need better performance with acceptable cost of several milliseconds of inaccuracy.
28 */
29 Stopwatch(clockid_t clock_type_ = CLOCK_MONOTONIC) : clock_type(clock_type_) { start(); }
30
31 void start() { start_ns = nanoseconds(); is_running = true; }
32 void stop() { stop_ns = nanoseconds(); is_running = false; }
33 void reset() { start_ns = 0; stop_ns = 0; is_running = false; }
34 void restart() { start(); }
35 UInt64 elapsed() const { return elapsedNanoseconds(); }
36 UInt64 elapsedNanoseconds() const { return is_running ? nanoseconds() - start_ns : stop_ns - start_ns; }
37 UInt64 elapsedMicroseconds() const { return elapsedNanoseconds() / 1000U; }
38 UInt64 elapsedMilliseconds() const { return elapsedNanoseconds() / 1000000UL; }
39 double elapsedSeconds() const { return static_cast<double>(elapsedNanoseconds()) / 1000000000ULL; }
40
41private:
42 UInt64 start_ns = 0;
43 UInt64 stop_ns = 0;
44 clockid_t clock_type;
45 bool is_running = false;
46
47 UInt64 nanoseconds() const { return StopWatchDetail::nanoseconds(clock_type); }
48};
49
50
51class AtomicStopwatch
52{
53public:
54 AtomicStopwatch(clockid_t clock_type_ = CLOCK_MONOTONIC) : clock_type(clock_type_) { restart(); }
55
56 void restart() { start_ns = nanoseconds(); }
57 UInt64 elapsed() const { return nanoseconds() - start_ns; }
58 UInt64 elapsedMilliseconds() const { return elapsed() / 1000000UL; }
59 double elapsedSeconds() const { return static_cast<double>(elapsed()) / 1000000000ULL; }
60
61 /** If specified amount of time has passed, then restarts timer and returns true.
62 * Otherwise returns false.
63 * This is done atomically.
64 */
65 bool compareAndRestart(double seconds)
66 {
67 UInt64 threshold = static_cast<UInt64>(seconds * 1000000000.0);
68 UInt64 current_ns = nanoseconds();
69 UInt64 current_start_ns = start_ns;
70
71 while (true)
72 {
73 if (current_ns < current_start_ns + threshold)
74 return false;
75
76 if (start_ns.compare_exchange_weak(current_start_ns, current_ns))
77 return true;
78 }
79 }
80
81 struct Lock
82 {
83 AtomicStopwatch * parent = nullptr;
84
85 Lock() {}
86
87 operator bool() const { return parent != nullptr; }
88
89 Lock(AtomicStopwatch * parent_) : parent(parent_) {}
90
91 Lock(Lock &&) = default;
92
93 ~Lock()
94 {
95 if (parent)
96 parent->restart();
97 }
98 };
99
100 /** If specified amount of time has passed and timer is not locked right now, then returns Lock object,
101 * which locks timer and, on destruction, restarts timer and releases the lock.
102 * Otherwise returns object, that is implicitly casting to false.
103 * This is done atomically.
104 *
105 * Usage:
106 * if (auto lock = timer.compareAndRestartDeferred(1))
107 * /// do some work, that must be done in one thread and not more frequently than each second.
108 */
109 Lock compareAndRestartDeferred(double seconds)
110 {
111 UInt64 threshold = UInt64(seconds * 1000000000.0);
112 UInt64 current_ns = nanoseconds();
113 UInt64 current_start_ns = start_ns;
114
115 while (true)
116 {
117 if ((current_start_ns & 0x8000000000000000ULL))
118 return {};
119
120 if (current_ns < current_start_ns + threshold)
121 return {};
122
123 if (start_ns.compare_exchange_weak(current_start_ns, current_ns | 0x8000000000000000ULL))
124 return Lock(this);
125 }
126 }
127
128private:
129 std::atomic<UInt64> start_ns;
130 std::atomic<bool> lock {false};
131 clockid_t clock_type;
132
133 /// Most significant bit is a lock. When it is set, compareAndRestartDeferred method will return false.
134 UInt64 nanoseconds() const { return StopWatchDetail::nanoseconds(clock_type) & 0x7FFFFFFFFFFFFFFFULL; }
135};
136
137
138/// Like ordinary StopWatch, but uses getrusage() system call
139struct StopwatchRUsage
140{
141 StopwatchRUsage() = default;
142
143 void start() { start_ts = Timestamp::current(); is_running = true; }
144 void stop() { stop_ts = Timestamp::current(); is_running = false; }
145 void reset() { start_ts = Timestamp(); stop_ts = Timestamp(); is_running = false; }
146 void restart() { start(); }
147
148 UInt64 elapsed(bool count_user = true, bool count_sys = true) const
149 {
150 return elapsedNanoseconds(count_user, count_sys);
151 }
152
153 UInt64 elapsedNanoseconds(bool count_user = true, bool count_sys = true) const
154 {
155 return (is_running ? Timestamp::current() : stop_ts).nanoseconds(count_user, count_sys) - start_ts.nanoseconds(count_user, count_sys);
156 }
157
158 UInt64 elapsedMicroseconds(bool count_user = true, bool count_sys = true) const
159 {
160 return elapsedNanoseconds(count_user, count_sys) / 1000UL;
161 }
162
163 UInt64 elapsedMilliseconds(bool count_user = true, bool count_sys = true) const
164 {
165 return elapsedNanoseconds(count_user, count_sys) / 1000000UL;
166 }
167
168 double elapsedSeconds(bool count_user = true, bool count_sys = true) const
169 {
170 return static_cast<double>(elapsedNanoseconds(count_user, count_sys)) / 1000000000.0;
171 }
172
173private:
174
175 struct Timestamp
176 {
177 UInt64 user_ns = 0;
178 UInt64 sys_ns = 0;
179
180 static Timestamp current();
181
182 UInt64 nanoseconds(bool count_user = true, bool count_sys = true) const
183 {
184 return (count_user ? user_ns : 0) + (count_sys ? sys_ns : 0);
185 }
186 };
187
188 Timestamp start_ts;
189 Timestamp stop_ts;
190 bool is_running = false;
191};
192
193
194template <typename TStopwatch>
195class StopwatchGuard : public TStopwatch
196{
197public:
198 explicit StopwatchGuard(UInt64 & elapsed_ns_) : elapsed_ns(elapsed_ns_) {}
199
200 ~StopwatchGuard() { elapsed_ns += TStopwatch::elapsedNanoseconds(); }
201
202private:
203 UInt64 & elapsed_ns;
204};
205