Coverage Report

Created: 2026-09-28 07:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rocksdb/monitoring/histogram.cc
Line
Count
Source
1
//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2
//  This source code is licensed under both the GPLv2 (found in the
3
//  COPYING file in the root directory) and Apache 2.0 License
4
//  (found in the LICENSE.Apache file in the root directory).
5
//
6
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
7
// Use of this source code is governed by a BSD-style license that can be
8
// found in the LICENSE file. See the AUTHORS file for names of contributors.
9
10
#include "monitoring/histogram.h"
11
12
#include <algorithm>
13
#include <cassert>
14
#include <cinttypes>
15
#include <cmath>
16
#include <cstdio>
17
18
#include "port/port.h"
19
#include "util/cast_util.h"
20
21
namespace ROCKSDB_NAMESPACE {
22
23
4
HistogramBucketMapper::HistogramBucketMapper() {
24
  // If you change this, you also need to change
25
  // size of array buckets_ in HistogramImpl
26
4
  bucketValues_ = {1, 2};
27
4
  double bucket_val = static_cast<double>(bucketValues_.back());
28
432
  while ((bucket_val = 1.5 * bucket_val) <=
29
432
         static_cast<double>(std::numeric_limits<uint64_t>::max())) {
30
428
    bucketValues_.push_back(static_cast<uint64_t>(bucket_val));
31
    // Extracts two most significant digits to make histogram buckets more
32
    // human-readable. E.g., 172 becomes 170.
33
428
    uint64_t pow_of_ten = 1;
34
3.98k
    while (bucketValues_.back() / 10 > 10) {
35
3.55k
      bucketValues_.back() /= 10;
36
3.55k
      pow_of_ten *= 10;
37
3.55k
    }
38
428
    bucketValues_.back() *= pow_of_ten;
39
428
  }
40
4
  maxBucketValue_ = bucketValues_.back();
41
4
  minBucketValue_ = bucketValues_.front();
42
4
}
43
44
0
size_t HistogramBucketMapper::IndexForValue(const uint64_t value) const {
45
0
  auto beg = bucketValues_.begin();
46
0
  auto end = bucketValues_.end();
47
0
  if (value >= maxBucketValue_) {
48
0
    return end - beg - 1;  // bucketValues_.size() - 1
49
0
  } else {
50
0
    return std::lower_bound(beg, end, value) - beg;
51
0
  }
52
0
}
53
54
namespace {
55
const HistogramBucketMapper bucketMapper;
56
}
57
58
853k
HistogramStat::HistogramStat() : num_buckets_(bucketMapper.BucketCount()) {
59
853k
  assert(num_buckets_ == sizeof(buckets_) / sizeof(*buckets_));
60
853k
  Clear();
61
853k
}
62
63
1.70M
void HistogramStat::Clear() {
64
1.70M
  min_.store(bucketMapper.LastValue(), std::memory_order_relaxed);
65
1.70M
  max_.store(0, std::memory_order_relaxed);
66
1.70M
  num_.store(0, std::memory_order_relaxed);
67
1.70M
  sum_.store(0, std::memory_order_relaxed);
68
1.70M
  sum_squares_.store(0, std::memory_order_relaxed);
69
187M
  for (unsigned int b = 0; b < num_buckets_; b++) {
70
186M
    buckets_[b].store(0, std::memory_order_relaxed);
71
186M
  }
72
1.70M
}
73
74
3.44k
bool HistogramStat::Empty() const { return num() == 0; }
75
76
0
void HistogramStat::Add(uint64_t value) {
77
  // This function is designed to be lock free, as it's in the critical path
78
  // of any operation. Each individual value is atomic and the order of updates
79
  // by concurrent threads is tolerable.
80
0
  const size_t index = bucketMapper.IndexForValue(value);
81
0
  assert(index < num_buckets_);
82
0
  buckets_[index].store(buckets_[index].load(std::memory_order_relaxed) + 1,
83
0
                        std::memory_order_relaxed);
84
85
0
  uint64_t old_min = min();
86
0
  if (value < old_min) {
87
0
    min_.store(value, std::memory_order_relaxed);
88
0
  }
89
90
0
  uint64_t old_max = max();
91
0
  if (value > old_max) {
92
0
    max_.store(value, std::memory_order_relaxed);
93
0
  }
94
95
0
  num_.store(num_.load(std::memory_order_relaxed) + 1,
96
0
             std::memory_order_relaxed);
97
0
  sum_.store(sum_.load(std::memory_order_relaxed) + value,
98
0
             std::memory_order_relaxed);
99
0
  sum_squares_.store(
100
0
      sum_squares_.load(std::memory_order_relaxed) + value * value,
101
0
      std::memory_order_relaxed);
102
0
}
103
104
0
void HistogramStat::Merge(const HistogramStat& other) {
105
  // This function needs to be performned with the outer lock acquired
106
  // However, atomic operation on every member is still need, since Add()
107
  // requires no lock and value update can still happen concurrently
108
0
  uint64_t old_min = min();
109
0
  uint64_t other_min = other.min();
110
0
  while (other_min < old_min &&
111
0
         !min_.compare_exchange_weak(old_min, other_min)) {
112
0
  }
113
114
0
  uint64_t old_max = max();
115
0
  uint64_t other_max = other.max();
116
0
  while (other_max > old_max &&
117
0
         !max_.compare_exchange_weak(old_max, other_max)) {
118
0
  }
119
120
0
  num_.fetch_add(other.num(), std::memory_order_relaxed);
121
0
  sum_.fetch_add(other.sum(), std::memory_order_relaxed);
122
0
  sum_squares_.fetch_add(other.sum_squares(), std::memory_order_relaxed);
123
0
  for (unsigned int b = 0; b < num_buckets_; b++) {
124
0
    buckets_[b].fetch_add(other.bucket_at(b), std::memory_order_relaxed);
125
0
  }
126
0
}
127
128
0
double HistogramStat::Median() const { return Percentile(50.0); }
129
130
0
double HistogramStat::Percentile(double p) const {
131
0
  double threshold = num() * (p / 100.0);
132
0
  uint64_t cumulative_sum = 0;
133
0
  for (unsigned int b = 0; b < num_buckets_; b++) {
134
0
    uint64_t bucket_value = bucket_at(b);
135
0
    cumulative_sum += bucket_value;
136
0
    if (cumulative_sum >= threshold) {
137
      // Scale linearly within this bucket
138
0
      uint64_t left_point = (b == 0) ? 0 : bucketMapper.BucketLimit(b - 1);
139
0
      uint64_t right_point = bucketMapper.BucketLimit(b);
140
0
      uint64_t left_sum = cumulative_sum - bucket_value;
141
0
      uint64_t right_sum = cumulative_sum;
142
0
      double pos = 0;
143
0
      uint64_t right_left_diff = right_sum - left_sum;
144
0
      if (right_left_diff != 0) {
145
0
        pos = (threshold - left_sum) / right_left_diff;
146
0
      }
147
0
      double r = left_point + (right_point - left_point) * pos;
148
0
      uint64_t cur_min = min();
149
0
      uint64_t cur_max = max();
150
0
      if (r < cur_min) {
151
0
        r = static_cast<double>(cur_min);
152
0
      }
153
0
      if (r > cur_max) {
154
0
        r = static_cast<double>(cur_max);
155
0
      }
156
0
      return r;
157
0
    }
158
0
  }
159
0
  return static_cast<double>(max());
160
0
}
161
162
0
double HistogramStat::Average() const {
163
0
  uint64_t cur_num = num();
164
0
  uint64_t cur_sum = sum();
165
0
  if (cur_num == 0) {
166
0
    return 0;
167
0
  }
168
0
  return static_cast<double>(cur_sum) / static_cast<double>(cur_num);
169
0
}
170
171
0
double HistogramStat::StandardDeviation() const {
172
0
  double cur_num =
173
0
      static_cast<double>(num());  // Use double to avoid integer overflow
174
0
  double cur_sum = static_cast<double>(sum());
175
0
  double cur_sum_squares = static_cast<double>(sum_squares());
176
0
  if (cur_num == 0.0) {
177
0
    return 0.0;
178
0
  }
179
0
  double variance =
180
0
      (cur_sum_squares * cur_num - cur_sum * cur_sum) / (cur_num * cur_num);
181
0
  return std::sqrt(std::max(variance, 0.0));
182
0
}
183
184
0
std::string HistogramStat::ToString() const {
185
0
  uint64_t cur_num = num();
186
0
  std::string r;
187
0
  char buf[1650];
188
0
  snprintf(buf, sizeof(buf), "Count: %" PRIu64 " Average: %.4f  StdDev: %.2f\n",
189
0
           cur_num, Average(), StandardDeviation());
190
0
  r.append(buf);
191
0
  snprintf(buf, sizeof(buf),
192
0
           "Min: %" PRIu64 "  Median: %.4f  Max: %" PRIu64 "\n",
193
0
           (cur_num == 0 ? 0 : min()), Median(), (cur_num == 0 ? 0 : max()));
194
0
  r.append(buf);
195
0
  snprintf(buf, sizeof(buf),
196
0
           "Percentiles: "
197
0
           "P50: %.2f P75: %.2f P99: %.2f P99.9: %.2f P99.99: %.2f\n",
198
0
           Percentile(50), Percentile(75), Percentile(99), Percentile(99.9),
199
0
           Percentile(99.99));
200
0
  r.append(buf);
201
0
  r.append("------------------------------------------------------\n");
202
0
  if (cur_num == 0) {
203
0
    return r;  // all buckets are empty
204
0
  }
205
0
  const double mult = 100.0 / cur_num;
206
0
  uint64_t cumulative_sum = 0;
207
0
  for (unsigned int b = 0; b < num_buckets_; b++) {
208
0
    uint64_t bucket_value = bucket_at(b);
209
0
    if (bucket_value <= 0.0) {
210
0
      continue;
211
0
    }
212
0
    cumulative_sum += bucket_value;
213
0
    snprintf(buf, sizeof(buf),
214
0
             "%c %7" PRIu64 ", %7" PRIu64 " ] %8" PRIu64 " %7.3f%% %7.3f%% ",
215
0
             (b == 0) ? '[' : '(',
216
0
             (b == 0) ? 0 : bucketMapper.BucketLimit(b - 1),  // left
217
0
             bucketMapper.BucketLimit(b),                     // right
218
0
             bucket_value,                                    // count
219
0
             (mult * bucket_value),                           // percentage
220
0
             (mult * cumulative_sum));  // cumulative percentage
221
0
    r.append(buf);
222
223
    // Add hash marks based on percentage; 20 marks for 100%.
224
0
    size_t marks = static_cast<size_t>(mult * bucket_value / 5 + 0.5);
225
0
    r.append(marks, '#');
226
0
    r.push_back('\n');
227
0
  }
228
0
  return r;
229
0
}
230
231
0
void HistogramStat::Data(HistogramData* const data) const {
232
0
  assert(data);
233
0
  data->median = Median();
234
0
  data->percentile95 = Percentile(95);
235
0
  data->percentile99 = Percentile(99);
236
0
  data->max = static_cast<double>(max());
237
0
  data->average = Average();
238
0
  data->standard_deviation = StandardDeviation();
239
0
  data->count = num();
240
0
  data->sum = sum();
241
0
  data->min = static_cast<double>(min());
242
0
}
243
244
853k
void HistogramImpl::Clear() {
245
853k
  std::lock_guard<std::mutex> lock(mutex_);
246
853k
  stats_.Clear();
247
853k
}
248
249
3.44k
bool HistogramImpl::Empty() const { return stats_.Empty(); }
250
251
0
void HistogramImpl::Add(uint64_t value) { stats_.Add(value); }
252
253
0
void HistogramImpl::Merge(const Histogram& other) {
254
0
  if (strcmp(Name(), other.Name()) == 0) {
255
0
    Merge(*static_cast_with_check<const HistogramImpl>(&other));
256
0
  }
257
0
}
258
259
0
void HistogramImpl::Merge(const HistogramImpl& other) {
260
0
  std::lock_guard<std::mutex> lock(mutex_);
261
0
  stats_.Merge(other.stats_);
262
0
}
263
264
0
double HistogramImpl::Median() const { return stats_.Median(); }
265
266
0
double HistogramImpl::Percentile(double p) const {
267
0
  return stats_.Percentile(p);
268
0
}
269
270
0
double HistogramImpl::Average() const { return stats_.Average(); }
271
272
0
double HistogramImpl::StandardDeviation() const {
273
0
  return stats_.StandardDeviation();
274
0
}
275
276
0
std::string HistogramImpl::ToString() const { return stats_.ToString(); }
277
278
0
void HistogramImpl::Data(HistogramData* const data) const { stats_.Data(data); }
279
280
}  // namespace ROCKSDB_NAMESPACE