1 | /* |
2 | * Copyright 2016-present Facebook, Inc. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | #pragma once |
18 | |
19 | #include <chrono> |
20 | #include <stdexcept> |
21 | #include <utility> |
22 | |
23 | #include <folly/Chrono.h> |
24 | #include <folly/portability/Time.h> |
25 | |
26 | namespace folly { |
27 | |
28 | using monotonic_clock = std::chrono::steady_clock; |
29 | |
30 | /** |
31 | * Calculates the duration of time intervals. Prefer this over directly using |
32 | * monotonic clocks. It is very lightweight and provides convenient facilitles |
33 | * to avoid common pitfalls. |
34 | * |
35 | * There are two type aliases that should be preferred over instantiating this |
36 | * class directly: `coarse_stop_watch` and `stop_watch`. |
37 | * |
38 | * Arguments: |
39 | * - Clock: the monotonic clock to use when calculating time intervals |
40 | * - Duration: (optional) the duration to use when reporting elapsed time. |
41 | * Defaults to the clock's duration. |
42 | * |
43 | * Example 1: |
44 | * |
45 | * coarse_stop_watch<std::chrono::seconds> watch; |
46 | * do_something(); |
47 | * std::cout << "time elapsed: " << watch.elapsed().count() << std::endl; |
48 | * |
49 | * auto const ttl = 60_s; |
50 | * if (watch.elapsed(ttl)) { |
51 | * process_expiration(); |
52 | * } |
53 | * |
54 | * Example 2: |
55 | * |
56 | * struct run_every_n_seconds { |
57 | * using callback = std::function<void()>; |
58 | * run_every_n_seconds(std::chrono::seconds period, callback action) |
59 | * period_(period), |
60 | * action_(std::move(action)) |
61 | * { |
62 | * // watch_ is correctly initialized to the current time |
63 | * } |
64 | * |
65 | * void run() { |
66 | * while (true) { |
67 | * if (watch_.lap(period_)) { |
68 | * action_(); |
69 | * } |
70 | * std::this_thread::yield(); |
71 | * } |
72 | * } |
73 | * |
74 | * private: |
75 | * stop_watch<> watch_; |
76 | * std::chrono::seconds period_; |
77 | * callback action_; |
78 | * }; |
79 | * |
80 | * @author: Marcelo Juchem <marcelo@fb.com> |
81 | */ |
82 | template <typename Clock, typename Duration = typename Clock::duration> |
83 | struct custom_stop_watch { |
84 | using clock_type = Clock; |
85 | using duration = Duration; |
86 | using time_point = std::chrono::time_point<clock_type, duration>; |
87 | |
88 | static_assert( |
89 | std::ratio_less_equal< |
90 | typename clock_type::duration::period, |
91 | typename duration::period>::value, |
92 | "clock must be at least as precise as the requested duration" ); |
93 | |
94 | static_assert( |
95 | Clock::is_steady, |
96 | "only monotonic clocks should be used to track time intervals" ); |
97 | |
98 | /** |
99 | * Initializes the stop watch with the current time as its checkpoint. |
100 | * |
101 | * Example: |
102 | * |
103 | * stop_watch<> watch; |
104 | * do_something(); |
105 | * std::cout << "time elapsed: " << watch.elapsed() << std::endl; |
106 | * |
107 | * @author: Marcelo Juchem <marcelo@fb.com> |
108 | */ |
109 | custom_stop_watch() : checkpoint_(clock_type::now()) {} |
110 | |
111 | /** |
112 | * Initializes the stop watch with the given time as its checkpoint. |
113 | * |
114 | * NOTE: this constructor should be seldomly used. It is only provided so |
115 | * that, in the rare occasions it is needed, one does not have to reimplement |
116 | * the `custom_stop_watch` class. |
117 | * |
118 | * Example: |
119 | * |
120 | * custom_stop_watch<monotonic_clock> watch(monotonic_clock::now()); |
121 | * do_something(); |
122 | * std::cout << "time elapsed: " << watch.elapsed() << std::endl; |
123 | * |
124 | * @author: Marcelo Juchem <marcelo@fb.com> |
125 | */ |
126 | explicit custom_stop_watch(typename clock_type::time_point checkpoint) |
127 | : checkpoint_(std::move(checkpoint)) {} |
128 | |
129 | /** |
130 | * Updates the stop watch checkpoint to the current time. |
131 | * |
132 | * Example: |
133 | * |
134 | * struct some_resource { |
135 | * // ... |
136 | * |
137 | * void on_reloaded() { |
138 | * time_alive.reset(); |
139 | * } |
140 | * |
141 | * void report() { |
142 | * std::cout << "resource has been alive for " << time_alive.elapsed(); |
143 | * } |
144 | * |
145 | * private: |
146 | * stop_watch<> time_alive; |
147 | * }; |
148 | * |
149 | * @author: Marcelo Juchem <marcelo@fb.com> |
150 | */ |
151 | void reset() { |
152 | checkpoint_ = clock_type::now(); |
153 | } |
154 | |
155 | /** |
156 | * Tells the elapsed time since the last update. |
157 | * |
158 | * The stop watch's checkpoint remains unchanged. |
159 | * |
160 | * Example: |
161 | * |
162 | * stop_watch<> watch; |
163 | * do_something(); |
164 | * std::cout << "time elapsed: " << watch.elapsed() << std::endl; |
165 | * |
166 | * @author: Marcelo Juchem <marcelo@fb.com> |
167 | */ |
168 | duration elapsed() const { |
169 | return std::chrono::duration_cast<duration>( |
170 | clock_type::now() - checkpoint_); |
171 | } |
172 | |
173 | /** |
174 | * Tells whether the given duration has already elapsed since the last |
175 | * checkpoint. |
176 | * |
177 | * Example: |
178 | * |
179 | * auto const ttl = 60_s; |
180 | * stop_watch<> watch; |
181 | * |
182 | * do_something(); |
183 | * |
184 | * std::cout << "has the TTL expired? " std::boolalpha<< watch.elapsed(ttl); |
185 | * |
186 | * @author: Marcelo Juchem <marcelo@fb.com> |
187 | */ |
188 | template <typename UDuration> |
189 | bool elapsed(UDuration&& amount) const { |
190 | return clock_type::now() - checkpoint_ >= amount; |
191 | } |
192 | |
193 | /** |
194 | * Tells the elapsed time since the last update, and updates the checkpoint |
195 | * to the current time. |
196 | * |
197 | * Example: |
198 | * |
199 | * struct some_resource { |
200 | * // ... |
201 | * |
202 | * void on_reloaded() { |
203 | * auto const alive = time_alive.lap(); |
204 | * std::cout << "resource reloaded after being alive for " << alive; |
205 | * } |
206 | * |
207 | * private: |
208 | * stop_watch<> time_alive; |
209 | * }; |
210 | * |
211 | * @author: Marcelo Juchem <marcelo@fb.com> |
212 | */ |
213 | duration lap() { |
214 | auto lastCheckpoint = checkpoint_; |
215 | |
216 | checkpoint_ = clock_type::now(); |
217 | |
218 | return std::chrono::duration_cast<duration>(checkpoint_ - lastCheckpoint); |
219 | } |
220 | |
221 | /** |
222 | * Tells whether the given duration has already elapsed since the last |
223 | * checkpoint. If so, update the checkpoint to the current time. If not, |
224 | * the checkpoint remains unchanged. |
225 | * |
226 | * Example: |
227 | * |
228 | * void run_every_n_seconds( |
229 | * std::chrono::seconds period, |
230 | * std::function<void()> action |
231 | * ) { |
232 | * for (stop_watch<> watch;; ) { |
233 | * if (watch.lap(period)) { |
234 | * action(); |
235 | * } |
236 | * std::this_thread::yield(); |
237 | * } |
238 | * } |
239 | * |
240 | * @author: Marcelo Juchem <marcelo@fb.com> |
241 | */ |
242 | template <typename UDuration> |
243 | bool lap(UDuration&& amount) { |
244 | auto now = clock_type::now(); |
245 | |
246 | if (now - checkpoint_ < amount) { |
247 | return false; |
248 | } |
249 | |
250 | checkpoint_ = now; |
251 | return true; |
252 | } |
253 | |
254 | /** |
255 | * Returns the current checkpoint |
256 | */ |
257 | typename clock_type::time_point getCheckpoint() const { |
258 | return checkpoint_; |
259 | } |
260 | |
261 | private: |
262 | typename clock_type::time_point checkpoint_; |
263 | }; |
264 | |
265 | /** |
266 | * A type alias for `custom_stop_watch` that uses a coarse monotonic clock as |
267 | * the time source. Refer to the documentation of `custom_stop_watch` for full |
268 | * documentation. |
269 | * |
270 | * Arguments: |
271 | * - Duration: (optional) the duration to use when reporting elapsed time. |
272 | * Defaults to the clock's duration. |
273 | * |
274 | * Example: |
275 | * |
276 | * coarse_stop_watch<std::chrono::seconds> watch; |
277 | * do_something(); |
278 | * std::cout << "time elapsed: " << watch.elapsed().count() << std::endl; |
279 | * |
280 | * @author: Marcelo Juchem <marcelo@fb.com> |
281 | */ |
282 | template <typename Duration = folly::chrono::coarse_steady_clock::duration> |
283 | using coarse_stop_watch = |
284 | custom_stop_watch<folly::chrono::coarse_steady_clock, Duration>; |
285 | |
286 | /** |
287 | * A type alias for `custom_stop_watch` that uses a monotonic clock as the time |
288 | * source. Refer to the documentation of `custom_stop_watch` for full |
289 | * documentation. |
290 | * |
291 | * Arguments: |
292 | * - Duration: (optional) the duration to use when reporting elapsed time. |
293 | * Defaults to the clock's duration. |
294 | * |
295 | * Example: |
296 | * |
297 | * stop_watch<std::chrono::seconds> watch; |
298 | * do_something(); |
299 | * std::cout << "time elapsed: " << watch.elapsed().count() << std::endl; |
300 | * |
301 | * @author: Marcelo Juchem <marcelo@fb.com> |
302 | */ |
303 | template <typename Duration = std::chrono::steady_clock::duration> |
304 | using stop_watch = custom_stop_watch<std::chrono::steady_clock, Duration>; |
305 | } // namespace folly |
306 | |