/Users/deen/code/yugabyte-db/src/yb/util/stopwatch.h
Line | Count | Source (jump to first uncovered line) |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | // |
18 | | // The following only applies to changes made to this file as part of YugaByte development. |
19 | | // |
20 | | // Portions Copyright (c) YugaByte, Inc. |
21 | | // |
22 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
23 | | // in compliance with the License. You may obtain a copy of the License at |
24 | | // |
25 | | // http://www.apache.org/licenses/LICENSE-2.0 |
26 | | // |
27 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
28 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
29 | | // or implied. See the License for the specific language governing permissions and limitations |
30 | | // under the License. |
31 | | // |
32 | | #ifndef YB_UTIL_STOPWATCH_H |
33 | | #define YB_UTIL_STOPWATCH_H |
34 | | |
35 | | #include <glog/logging.h> |
36 | | #include <sys/resource.h> |
37 | | #include <sys/time.h> |
38 | | #include <time.h> |
39 | | #include <string> |
40 | | #if defined(__APPLE__) |
41 | | #include <mach/clock.h> |
42 | | #include <mach/mach.h> |
43 | | #endif // defined(__APPLE__) |
44 | | |
45 | | #include "yb/gutil/macros.h" |
46 | | #include "yb/gutil/stringprintf.h" |
47 | | #include "yb/gutil/walltime.h" |
48 | | |
49 | | namespace yb { |
50 | | |
51 | | // Macro for logging timing of a block. Usage: |
52 | | // LOG_TIMING_PREFIX_IF(INFO, FLAGS_should_record_time, "Tablet X: ", "doing some task") { |
53 | | // ... some task which takes some time |
54 | | // } |
55 | | // If FLAGS_should_record_time is true, yields a log like: |
56 | | // I1102 14:35:51.726186 23082 file.cc:167] Tablet X: Time spent doing some task: |
57 | | // real 3.729s user 3.570s sys 0.150s |
58 | | // The task will always execute regardless of whether the timing information is |
59 | | // printed. |
60 | | #define LOG_TIMING_PREFIX_IF(severity, condition, prefix, description) \ |
61 | 435k | for (yb::sw_internal::LogTiming _l(__FILE__, __LINE__, google::severity, prefix, description, \ |
62 | 870k | -1, (condition)); !_l.HasRun(); _l.MarkHasRun()435k ) |
63 | | |
64 | | // Conditionally log, no prefix. |
65 | | #define LOG_TIMING_IF(severity, condition, description) \ |
66 | 18 | LOG_TIMING_PREFIX_IF(severity, (condition), "", (description)) |
67 | | |
68 | | // Always log, including prefix. |
69 | | #define LOG_TIMING_PREFIX(severity, prefix, description) \ |
70 | 435k | LOG_TIMING_PREFIX_IF(severity, true, (prefix), (description)) |
71 | | |
72 | | // Always log, no prefix. |
73 | | #define LOG_TIMING(severity, description) \ |
74 | 18 | LOG_TIMING_IF(severity, true, (description)) |
75 | | |
76 | | // Macro to log the time spent in the rest of the block. |
77 | | #define SCOPED_LOG_TIMING(severity, description) \ |
78 | 0 | yb::sw_internal::LogTiming VARNAME_LINENUM(_log_timing)(__FILE__, __LINE__, \ |
79 | 0 | google::severity, "", description, -1, true); |
80 | | |
81 | | // Scoped version of LOG_SLOW_EXECUTION() but with a prefix. |
82 | | #define SCOPED_LOG_SLOW_EXECUTION_PREFIX(severity, max_expected_millis, prefix, description) \ |
83 | 6.74M | yb::sw_internal::LogTiming VARNAME_LINENUM(_log_timing)(__FILE__, __LINE__, \ |
84 | 6.74M | google::severity, prefix, description, max_expected_millis, true) |
85 | | |
86 | | // Macro for logging timing of a block. Usage: |
87 | | // LOG_SLOW_EXECUTION(INFO, 5, "doing some task") { |
88 | | // ... some task which takes some time |
89 | | // } |
90 | | // when slower than 5 milliseconds, yields a log like: |
91 | | // I1102 14:35:51.726186 23082 file.cc:167] Time spent doing some task: |
92 | | // real 3.729s user 3.570s sys 0.150s |
93 | | #define LOG_SLOW_EXECUTION(severity, max_expected_millis, description) \ |
94 | 29.0M | for (yb::sw_internal::LogTiming _l(__FILE__, __LINE__, google::severity, "", description, \ |
95 | 57.5M | max_expected_millis, true); !_l.HasRun(); _l.MarkHasRun()28.5M ) |
96 | | |
97 | | // Macro for vlogging timing of a block. The execution happens regardless of the vlog_level, |
98 | | // it's only the logging that's affected. |
99 | | // Usage: |
100 | | // VLOG_TIMING(1, "doing some task") { |
101 | | // ... some task which takes some time |
102 | | // } |
103 | | // Yields a log just like LOG_TIMING's. |
104 | | #define VLOG_TIMING(vlog_level, description) \ |
105 | 2.60k | for (yb::sw_internal::LogTiming _l(__FILE__, __LINE__, google::INFO, "", description, \ |
106 | 2.69k | -1, VLOG_IS_ON(vlog_level)); !_l.HasRun(); _l.MarkHasRun()94 ) |
107 | | |
108 | | // Macro to log the time spent in the rest of the block. |
109 | | #define SCOPED_VLOG_TIMING(vlog_level, description) \ |
110 | | yb::sw_internal::LogTiming VARNAME_LINENUM(_log_timing)(__FILE__, __LINE__, \ |
111 | | google::INFO, "", description, -1, VLOG_IS_ON(vlog_level)); |
112 | | |
113 | 2.53M | #define NANOS_PER_SECOND 1000000000.0 |
114 | 36.2M | #define NANOS_PER_MILLISECOND 1000000.0 |
115 | | |
116 | | class Stopwatch; |
117 | | |
118 | | typedef uint64_t nanosecond_type; |
119 | | |
120 | | // Structure which contains an elapsed amount of wall/user/sys time. |
121 | | struct CpuTimes { |
122 | | nanosecond_type wall; |
123 | | nanosecond_type user; |
124 | | nanosecond_type system; |
125 | | |
126 | 36.3M | void clear() { wall = user = system = 0LL; } |
127 | | |
128 | | // Return a string formatted similar to the output of the "time" shell command. |
129 | 441k | std::string ToString() const { |
130 | 441k | return StringPrintf( |
131 | 441k | "real %.3fs\tuser %.3fs\tsys %.3fs", |
132 | 441k | wall_seconds(), user_cpu_seconds(), system_cpu_seconds()); |
133 | 441k | } |
134 | | |
135 | 36.2M | double wall_millis() const { |
136 | 36.2M | return static_cast<double>(wall) / NANOS_PER_MILLISECOND; |
137 | 36.2M | } |
138 | | |
139 | 1.65M | double wall_seconds() const { |
140 | 1.65M | return static_cast<double>(wall) / NANOS_PER_SECOND; |
141 | 1.65M | } |
142 | | |
143 | 441k | double user_cpu_seconds() const { |
144 | 441k | return static_cast<double>(user) / NANOS_PER_SECOND; |
145 | 441k | } |
146 | | |
147 | 441k | double system_cpu_seconds() const { |
148 | 441k | return static_cast<double>(system) / NANOS_PER_SECOND; |
149 | 441k | } |
150 | | }; |
151 | | |
152 | | // A Stopwatch is a convenient way of timing a given operation. |
153 | | // |
154 | | // Wall clock time is based on a monotonic timer, so can be reliably used for |
155 | | // determining durations. |
156 | | // CPU time is based on the current thread's usage (not the whole process). |
157 | | // |
158 | | // The implementation relies on several syscalls, so should not be used for |
159 | | // hot paths, but is useful for timing anything on the granularity of seconds |
160 | | // or more. |
161 | | class Stopwatch { |
162 | | public: |
163 | | |
164 | | enum Mode { |
165 | | // Collect usage only about the calling thread. |
166 | | // This may not be supported on older versions of Linux. |
167 | | THIS_THREAD, |
168 | | // Collect usage of all threads. |
169 | | ALL_THREADS |
170 | | }; |
171 | | |
172 | | // Construct a new stopwatch. The stopwatch is initially stopped. |
173 | | explicit Stopwatch(Mode mode = THIS_THREAD) |
174 | | : stopped_(true), |
175 | 36.3M | mode_(mode) { |
176 | 36.3M | times_.clear(); |
177 | 36.3M | } |
178 | | |
179 | | // Start counting. If the stopwatch is already counting, then resets the |
180 | | // start point at the current time. |
181 | 36.3M | void start() { |
182 | 36.3M | stopped_ = false; |
183 | 36.3M | GetTimes(×_); |
184 | 36.3M | } |
185 | | |
186 | | // Stop counting. If the stopwatch is already stopped, has no effect. |
187 | 36.2M | void stop() { |
188 | 36.2M | if (stopped_) return0 ; |
189 | 36.2M | stopped_ = true; |
190 | | |
191 | 36.2M | CpuTimes current; |
192 | 36.2M | GetTimes(¤t); |
193 | 36.2M | times_.wall = current.wall - times_.wall; |
194 | 36.2M | times_.user = current.user - times_.user; |
195 | 36.2M | times_.system = current.system - times_.system; |
196 | 36.2M | } |
197 | | |
198 | | // Return the elapsed amount of time. If the stopwatch is running, then returns |
199 | | // the amount of time since it was started. If it is stopped, returns the amount |
200 | | // of time between the most recent start/stop pair. If the stopwatch has never been |
201 | | // started, the elapsed time is considered to be zero. |
202 | 37.4M | CpuTimes elapsed() const { |
203 | 37.4M | if (stopped_) return times_36.2M ; |
204 | | |
205 | 1.20M | CpuTimes current; |
206 | 1.20M | GetTimes(¤t); |
207 | 1.20M | current.wall -= times_.wall; |
208 | 1.20M | current.user -= times_.user; |
209 | 1.20M | current.system -= times_.system; |
210 | 1.20M | return current; |
211 | 37.4M | } |
212 | | |
213 | | // Resume a stopped stopwatch, such that the elapsed time continues to grow from |
214 | | // the point where it was last stopped. |
215 | | // For example: |
216 | | // Stopwatch s; |
217 | | // s.start(); |
218 | | // sleep(1); // elapsed() is now ~1sec |
219 | | // s.stop(); |
220 | | // sleep(1); |
221 | | // s.resume(); |
222 | | // sleep(1); // elapsed() is now ~2sec |
223 | 0 | void resume() { |
224 | 0 | if (!stopped_) return; |
225 | 0 |
|
226 | 0 | CpuTimes current(times_); |
227 | 0 | start(); |
228 | 0 | times_.wall -= current.wall; |
229 | 0 | times_.user -= current.user; |
230 | 0 | times_.system -= current.system; |
231 | 0 | } |
232 | | |
233 | 0 | bool is_stopped() const { |
234 | 0 | return stopped_; |
235 | 0 | } |
236 | | |
237 | | private: |
238 | 73.7M | void GetTimes(CpuTimes *times) const { |
239 | 73.7M | struct rusage usage; |
240 | 73.7M | struct timespec wall; |
241 | | |
242 | 73.7M | MicrosecondsInt64 user_cpu_us = 0; |
243 | 73.7M | MicrosecondsInt64 sys_cpu_us = 0; |
244 | 73.7M | #if defined(__APPLE__) |
245 | 73.8M | if (mode_ == THIS_THREAD73.7M ) { |
246 | 73.8M | GetThreadUserAndSysCpuTimeMicros(&user_cpu_us, &sys_cpu_us); |
247 | 18.4E | } else { |
248 | 18.4E | CHECK_EQ(0, getrusage(RUSAGE_SELF, &usage)); |
249 | 18.4E | user_cpu_us = usage.ru_utime.tv_sec * 1e6L + usage.ru_utime.tv_usec; |
250 | 18.4E | sys_cpu_us = usage.ru_stime.tv_sec * 1e6L + usage.ru_stime.tv_usec; |
251 | 18.4E | } |
252 | | |
253 | 73.7M | mach_timespec_t ts; |
254 | 73.7M | walltime_internal::GetCurrentTime(&ts); |
255 | 73.7M | wall.tv_sec = ts.tv_sec; |
256 | 73.7M | wall.tv_nsec = ts.tv_nsec; |
257 | | #else |
258 | | CHECK_EQ(0, getrusage((mode_ == THIS_THREAD) ? RUSAGE_THREAD : RUSAGE_SELF, &usage)); |
259 | | CHECK_EQ(0, clock_gettime(CLOCK_MONOTONIC, &wall)); |
260 | | user_cpu_us = usage.ru_utime.tv_sec * 1e6L + usage.ru_utime.tv_usec; |
261 | | sys_cpu_us = usage.ru_stime.tv_sec * 1e6L + usage.ru_stime.tv_usec; |
262 | | #endif // defined(__APPLE__) |
263 | | |
264 | 73.7M | times->wall = wall.tv_sec * 1000000000L + wall.tv_nsec; |
265 | 73.7M | times->user = user_cpu_us * 1000; // Convert to nanoseconds. |
266 | 73.7M | times->system = sys_cpu_us * 1000; // Convert to nanoseconds. |
267 | 73.7M | } |
268 | | |
269 | | bool stopped_; |
270 | | |
271 | | CpuTimes times_; |
272 | | Mode mode_; |
273 | | }; |
274 | | |
275 | | |
276 | | namespace sw_internal { |
277 | | |
278 | | // Internal class used by the LOG_TIMING macro. |
279 | | class LogTiming { |
280 | | public: |
281 | | LogTiming(const char *file, int line, google::LogSeverity severity, |
282 | | std::string prefix, std::string description, |
283 | | int64_t max_expected_millis, bool should_print) |
284 | | : file_(file), |
285 | | line_(line), |
286 | | severity_(severity), |
287 | | prefix_(std::move(prefix)), |
288 | | description_(std::move(description)), |
289 | | max_expected_millis_(max_expected_millis), |
290 | | should_print_(should_print), |
291 | 36.2M | has_run_(false) { |
292 | 36.2M | stopwatch_.start(); |
293 | 36.2M | } |
294 | | |
295 | 36.2M | ~LogTiming() { |
296 | 36.2M | if (should_print_) { |
297 | 36.2M | Print(max_expected_millis_); |
298 | 36.2M | } |
299 | 36.2M | } |
300 | | |
301 | | // Allows this object to be used as the loop variable in for-loop macros. |
302 | | // Call HasRun() in the conditional check in the for-loop. |
303 | 58.4M | bool HasRun() { |
304 | 58.4M | return has_run_; |
305 | 58.4M | } |
306 | | |
307 | | // Allows this object to be used as the loop variable in for-loop macros. |
308 | | // Call MarkHasRun() in the "increment" section of the for-loop. |
309 | 28.9M | void MarkHasRun() { |
310 | 28.9M | has_run_ = true; |
311 | 28.9M | } |
312 | | |
313 | | private: |
314 | | Stopwatch stopwatch_; |
315 | | const char *file_; |
316 | | const int line_; |
317 | | const google::LogSeverity severity_; |
318 | | const string prefix_; |
319 | | const std::string description_; |
320 | | const int64_t max_expected_millis_; |
321 | | const bool should_print_; |
322 | | bool has_run_; |
323 | | |
324 | | // Print if the number of expected millis exceeds the max. |
325 | | // Passing a negative number implies "always print". |
326 | 36.2M | void Print(int64_t max_expected_millis) { |
327 | 36.2M | stopwatch_.stop(); |
328 | 36.2M | CpuTimes times = stopwatch_.elapsed(); |
329 | 36.2M | if (times.wall_millis() > max_expected_millis) { |
330 | 440k | google::LogMessage(file_, line_, severity_).stream() |
331 | 440k | << prefix_ << "Time spent " << description_ << ": " |
332 | 440k | << times.ToString(); |
333 | 440k | } |
334 | 36.2M | } |
335 | | |
336 | | }; |
337 | | |
338 | | } // namespace sw_internal |
339 | | } // namespace yb |
340 | | |
341 | | #endif // YB_UTIL_STOPWATCH_H |