YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/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
256k
  for (yb::sw_internal::LogTiming _l(__FILE__, __LINE__, google::severity, prefix, description, \
62
512k
          -1, (condition)); !_l.HasRun(); _l.MarkHasRun())
63
64
// Conditionally log, no prefix.
65
#define LOG_TIMING_IF(severity, condition, description) \
66
383
  LOG_TIMING_PREFIX_IF(severity, (condition), "", (description))
67
68
// Always log, including prefix.
69
#define LOG_TIMING_PREFIX(severity, prefix, description) \
70
256k
  LOG_TIMING_PREFIX_IF(severity, true, (prefix), (description))
71
72
// Always log, no prefix.
73
#define LOG_TIMING(severity, description) \
74
383
  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
3.06M
  yb::sw_internal::LogTiming VARNAME_LINENUM(_log_timing)(__FILE__, __LINE__, \
84
3.06M
      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
16.2M
  for (yb::sw_internal::LogTiming _l(__FILE__, __LINE__, google::severity, "", description, \
95
31.9M
          max_expected_millis, true); !_l.HasRun(); _l.MarkHasRun())
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.49k
  for (yb::sw_internal::LogTiming _l(__FILE__, __LINE__, google::INFO, "", description, \
106
2.54k
          -1, VLOG_IS_ON(vlog_level)); !_l.HasRun(); _l.MarkHasRun())
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
1.09M
#define NANOS_PER_SECOND 1000000000.0
114
19.5M
#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
19.5M
  void clear() { wall = user = system = 0LL; }
127
128
  // Return a string formatted similar to the output of the "time" shell command.
129
257k
  std::string ToString() const {
130
257k
    return StringPrintf(
131
257k
      "real %.3fs\tuser %.3fs\tsys %.3fs",
132
257k
      wall_seconds(), user_cpu_seconds(), system_cpu_seconds());
133
257k
  }
134
135
19.5M
  double wall_millis() const {
136
19.5M
    return static_cast<double>(wall) / NANOS_PER_MILLISECOND;
137
19.5M
  }
138
139
580k
  double wall_seconds() const {
140
580k
    return static_cast<double>(wall) / NANOS_PER_SECOND;
141
580k
  }
142
143
257k
  double user_cpu_seconds() const {
144
257k
    return static_cast<double>(user) / NANOS_PER_SECOND;
145
257k
  }
146
147
257k
  double system_cpu_seconds() const {
148
257k
    return static_cast<double>(system) / NANOS_PER_SECOND;
149
257k
  }
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
19.5M
      mode_(mode) {
176
19.5M
    times_.clear();
177
19.5M
  }
178
179
  // Start counting. If the stopwatch is already counting, then resets the
180
  // start point at the current time.
181
19.5M
  void start() {
182
19.5M
    stopped_ = false;
183
19.5M
    GetTimes(&times_);
184
19.5M
  }
185
186
  // Stop counting. If the stopwatch is already stopped, has no effect.
187
19.5M
  void stop() {
188
19.5M
    if (stopped_) return;
189
19.5M
    stopped_ = true;
190
191
19.5M
    CpuTimes current;
192
19.5M
    GetTimes(&current);
193
19.5M
    times_.wall = current.wall - times_.wall;
194
19.5M
    times_.user = current.user - times_.user;
195
19.5M
    times_.system = current.system - times_.system;
196
19.5M
  }
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
19.8M
  CpuTimes elapsed() const {
203
19.8M
    if (stopped_) return times_;
204
205
323k
    CpuTimes current;
206
323k
    GetTimes(&current);
207
323k
    current.wall -= times_.wall;
208
323k
    current.user -= times_.user;
209
323k
    current.system -= times_.system;
210
323k
    return current;
211
323k
  }
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
39.4M
  void GetTimes(CpuTimes *times) const {
239
39.4M
    struct rusage usage;
240
39.4M
    struct timespec wall;
241
242
39.4M
    MicrosecondsInt64 user_cpu_us = 0;
243
39.4M
    MicrosecondsInt64 sys_cpu_us = 0;
244
39.4M
#if defined(__APPLE__)
245
39.4M
    if (mode_ == THIS_THREAD) {
246
39.4M
      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
39.4M
    mach_timespec_t ts;
254
39.4M
    walltime_internal::GetCurrentTime(&ts);
255
39.4M
    wall.tv_sec = ts.tv_sec;
256
39.4M
    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
39.4M
    times->wall   = wall.tv_sec * 1000000000L + wall.tv_nsec;
265
39.4M
    times->user   = user_cpu_us * 1000; // Convert to nanoseconds.
266
39.4M
    times->system = sys_cpu_us * 1000;  // Convert to nanoseconds.
267
39.4M
  }
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
19.5M
        has_run_(false) {
292
19.5M
    stopwatch_.start();
293
19.5M
  }
294
295
19.5M
  ~LogTiming() {
296
19.5M
    if (should_print_) {
297
19.5M
      Print(max_expected_millis_);
298
19.5M
    }
299
19.5M
  }
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
32.4M
  bool HasRun() {
304
32.4M
    return has_run_;
305
32.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
16.0M
  void MarkHasRun() {
310
16.0M
    has_run_ = true;
311
16.0M
  }
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
19.5M
  void Print(int64_t max_expected_millis) {
327
19.5M
    stopwatch_.stop();
328
19.5M
    CpuTimes times = stopwatch_.elapsed();
329
19.5M
    if (times.wall_millis() > max_expected_millis) {
330
256k
      google::LogMessage(file_, line_, severity_).stream()
331
256k
        << prefix_ << "Time spent " << description_ << ": "
332
256k
        << times.ToString();
333
256k
    }
334
19.5M
  }
335
336
};
337
338
} // namespace sw_internal
339
} // namespace yb
340
341
#endif  // YB_UTIL_STOPWATCH_H