15 bucket.store(0, std::memory_order_relaxed);
22 std::memory_order_relaxed);
25 std::memory_order_relaxed);
27 std::memory_order_relaxed);
29 std::memory_order_relaxed);
31 std::memory_order_relaxed);
35 for (std::size_t i = 0; i < BUCKET_COUNT; ++i) {
36 buckets_[i].store(other.buckets_[i].load(std::memory_order_relaxed),
37 std::memory_order_relaxed);
38 other.buckets_[i].store(0, std::memory_order_relaxed);
40 total_count_.store(other.total_count_.exchange(0, std::memory_order_relaxed),
41 std::memory_order_relaxed);
42 total_sum_.store(other.total_sum_.exchange(0, std::memory_order_relaxed),
43 std::memory_order_relaxed);
44 min_value_.store(other.min_value_.exchange(
45 std::numeric_limits<std::uint64_t>::max(),
46 std::memory_order_relaxed),
47 std::memory_order_relaxed);
48 max_value_.store(other.max_value_.exchange(0, std::memory_order_relaxed),
49 std::memory_order_relaxed);
56 std::memory_order_relaxed);
59 std::memory_order_relaxed);
61 std::memory_order_relaxed);
63 std::memory_order_relaxed);
65 std::memory_order_relaxed);
72 for (std::size_t i = 0; i < BUCKET_COUNT; ++i) {
73 buckets_[i].store(other.buckets_[i].load(std::memory_order_relaxed),
74 std::memory_order_relaxed);
75 other.buckets_[i].store(0, std::memory_order_relaxed);
77 total_count_.store(other.total_count_.exchange(0, std::memory_order_relaxed),
78 std::memory_order_relaxed);
79 total_sum_.store(other.total_sum_.exchange(0, std::memory_order_relaxed),
80 std::memory_order_relaxed);
81 min_value_.store(other.min_value_.exchange(
82 std::numeric_limits<std::uint64_t>::max(),
83 std::memory_order_relaxed),
84 std::memory_order_relaxed);
85 max_value_.store(other.max_value_.exchange(0, std::memory_order_relaxed),
86 std::memory_order_relaxed);
92 record_ns(
static_cast<std::uint64_t
>(value.count()));
97 buckets_[bucket_index].fetch_add(1, std::memory_order_relaxed);
99 total_sum_.fetch_add(nanoseconds, std::memory_order_relaxed);
102 auto current_min =
min_value_.load(std::memory_order_relaxed);
103 while (nanoseconds < current_min) {
104 if (
min_value_.compare_exchange_weak(current_min, nanoseconds,
105 std::memory_order_relaxed,
106 std::memory_order_relaxed)) {
112 auto current_max =
max_value_.load(std::memory_order_relaxed);
113 while (nanoseconds > current_max) {
114 if (
max_value_.compare_exchange_weak(current_max, nanoseconds,
115 std::memory_order_relaxed,
116 std::memory_order_relaxed)) {
123 const auto total =
total_count_.load(std::memory_order_relaxed);
129 p = std::clamp(p, 0.0, 1.0);
130 const auto target_count =
static_cast<std::uint64_t
>(std::ceil(p * total));
132 std::uint64_t cumulative = 0;
134 cumulative +=
buckets_[i].load(std::memory_order_relaxed);
135 if (cumulative >= target_count) {
137 const auto bucket_count_val =
buckets_[i].load(std::memory_order_relaxed);
138 if (bucket_count_val == 0) {
142 const auto prev_cumulative = cumulative - bucket_count_val;
143 const auto target_in_bucket = target_count - prev_cumulative;
144 const auto fraction =
145 static_cast<double>(target_in_bucket) / bucket_count_val;
150 return lower + fraction * (upper - lower);
159 const auto total =
total_count_.load(std::memory_order_relaxed);
163 return static_cast<double>(
total_sum_.load(std::memory_order_relaxed)) / total;
167 const auto total =
total_count_.load(std::memory_order_relaxed);
172 const auto mean_val =
mean();
173 double sum_squared_diff = 0.0;
176 const auto bucket_count_val =
buckets_[i].load(std::memory_order_relaxed);
177 if (bucket_count_val > 0) {
179 const auto diff = midpoint - mean_val;
180 sum_squared_diff += bucket_count_val * diff * diff;
184 return std::sqrt(sum_squared_diff / (total - 1));
188 const auto total =
total_count_.load(std::memory_order_relaxed);
192 return min_value_.load(std::memory_order_relaxed);
196 const auto total =
total_count_.load(std::memory_order_relaxed);
200 return max_value_.load(std::memory_order_relaxed);
208 return total_sum_.load(std::memory_order_relaxed);
213 bucket.store(0, std::memory_order_relaxed);
216 total_sum_.store(0, std::memory_order_relaxed);
217 min_value_.store(std::numeric_limits<std::uint64_t>::max(),
218 std::memory_order_relaxed);
219 max_value_.store(0, std::memory_order_relaxed);
223 return total_count_.load(std::memory_order_relaxed) == 0;
229 std::memory_order_relaxed);
232 std::memory_order_relaxed);
234 std::memory_order_relaxed);
237 auto other_min = other.
min_value_.load(std::memory_order_relaxed);
238 auto current_min =
min_value_.load(std::memory_order_relaxed);
239 while (other_min < current_min) {
240 if (
min_value_.compare_exchange_weak(current_min, other_min,
241 std::memory_order_relaxed,
242 std::memory_order_relaxed)) {
248 auto other_max = other.
max_value_.load(std::memory_order_relaxed);
249 auto current_max =
max_value_.load(std::memory_order_relaxed);
250 while (other_max > current_max) {
251 if (
max_value_.compare_exchange_weak(current_max, other_max,
252 std::memory_order_relaxed,
253 std::memory_order_relaxed)) {
263 return buckets_[bucket_index].load(std::memory_order_relaxed);
267 if (bucket_index == 0) {
271 return std::numeric_limits<std::uint64_t>::max();
273 return static_cast<std::uint64_t
>(1ULL << (bucket_index - 1));
278 return std::numeric_limits<std::uint64_t>::max();
280 return static_cast<std::uint64_t
>(1ULL << bucket_index);
290#if defined(__GNUC__) || defined(__clang__)
291 const auto leading_zeros = __builtin_clzll(value);
292 const auto highest_bit = 63 - leading_zeros;
293#elif defined(_MSC_VER)
294 unsigned long highest_bit;
295 _BitScanReverse64(&highest_bit, value);
298 std::size_t highest_bit = 0;
306 return std::min(
static_cast<std::size_t
>(highest_bit + 1),
BUCKET_COUNT - 1);
313 if (upper == std::numeric_limits<std::uint64_t>::max()) {
315 return static_cast<double>(lower);
318 return (
static_cast<double>(lower) +
static_cast<double>(upper)) / 2.0;
HDR-style histogram for latency distribution with logarithmic buckets.
LatencyHistogram()
Default constructor - initializes all buckets to zero.
void record(std::chrono::nanoseconds value)
Record a latency value.
double mean() const
Calculate the arithmetic mean.
static constexpr std::size_t BUCKET_COUNT
Number of histogram buckets.
std::uint64_t count() const
Get the total count of recorded values.
std::uint64_t sum() const
Get the sum of all recorded values.
std::uint64_t bucket_count(std::size_t bucket_index) const
Get the bucket count at a specific index.
static std::size_t compute_bucket_index(std::uint64_t value)
Compute the bucket index for a given value.
std::atomic< std::uint64_t > min_value_
Minimum recorded value.
std::atomic< std::uint64_t > total_count_
Total number of recorded values.
std::array< std::atomic< std::uint64_t >, BUCKET_COUNT > buckets_
Histogram buckets using logarithmic distribution.
bool empty() const
Check if the histogram is empty.
static std::uint64_t bucket_lower_bound(std::size_t bucket_index)
Get the lower bound of a bucket.
double stddev() const
Calculate the standard deviation.
double percentile(double p) const
Calculate the value at a given percentile.
std::atomic< std::uint64_t > max_value_
Maximum recorded value.
static std::uint64_t bucket_upper_bound(std::size_t bucket_index)
Get the upper bound of a bucket.
std::uint64_t min() const
Get the minimum recorded value.
std::atomic< std::uint64_t > total_sum_
Sum of all recorded values.
void reset()
Reset all buckets and counters to zero.
static double bucket_midpoint(std::size_t bucket_index)
Get the midpoint value of a bucket for estimation.
void record_ns(std::uint64_t nanoseconds)
Record a raw nanosecond value.
LatencyHistogram & operator=(const LatencyHistogram &other)
Copy assignment operator.
std::uint64_t max() const
Get the maximum recorded value.
void merge(const LatencyHistogram &other)
Merge another histogram into this one.
HDR-style histogram for latency distribution with logarithmic buckets.