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
26namespace folly {
27
28using 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 */
82template <typename Clock, typename Duration = typename Clock::duration>
83struct 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 */
282template <typename Duration = folly::chrono::coarse_steady_clock::duration>
283using 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 */
303template <typename Duration = std::chrono::steady_clock::duration>
304using stop_watch = custom_stop_watch<std::chrono::steady_clock, Duration>;
305} // namespace folly
306