21 std::ostringstream oss;
22 oss << std::fixed << std::setprecision(6);
25 std::string label_str;
28 std::ostringstream label_oss;
31 for (
const auto& [key, value] : labels)
37 label_oss << key <<
"=\"" << value <<
"\"";
41 label_str = label_oss.str();
45 for (
const auto& [boundary, bucket_count] : buckets)
47 oss << name <<
"_bucket{le=\"";
48 if (boundary == std::numeric_limits<double>::infinity())
59 oss <<
"," << label_str.substr(1, label_str.length() - 2);
61 oss <<
"} " << bucket_count <<
"\n";
65 oss << name <<
"_sum";
70 oss <<
" " << sum <<
"\n";
72 oss << name <<
"_count";
77 oss <<
" " << count <<
"\n";
84 std::ostringstream oss;
85 oss << std::fixed << std::setprecision(6);
88 oss <<
"\"count\":" <<
count <<
",";
89 oss <<
"\"sum\":" <<
sum <<
",";
94 oss <<
"\"percentiles\":{";
102 oss <<
"\"" << p <<
"\":" << v;
108 oss <<
"\"buckets\":[";
110 for (
const auto& [boundary, bucket_count] :
buckets)
117 if (boundary == std::numeric_limits<double>::infinity())
125 oss <<
",\"count\":" << bucket_count <<
"}";
131 oss <<
"\"labels\":{";
133 for (
const auto& [key, value] :
labels)
139 oss <<
"\"" << key <<
"\":\"" << value <<
"\"";
164 boundaries_.push_back(std::numeric_limits<double>::infinity());
176 : boundaries_(std::move(other.boundaries_))
177 , bucket_counts_(std::move(other.bucket_counts_))
178 , bucket_count_(other.bucket_count_)
179 , count_(other.count_.load(std::memory_order_relaxed))
180 , sum_(other.sum_.load(std::memory_order_relaxed))
181 , min_(other.min_.load(std::memory_order_relaxed))
182 , max_(other.max_.load(std::memory_order_relaxed))
184 other.bucket_count_ = 0;
191 boundaries_ = std::move(other.boundaries_);
192 bucket_counts_ = std::move(other.bucket_counts_);
193 bucket_count_ = other.bucket_count_;
194 count_.store(other.count_.load(std::memory_order_relaxed), std::memory_order_relaxed);
195 sum_.store(other.sum_.load(std::memory_order_relaxed), std::memory_order_relaxed);
196 min_.store(other.min_.load(std::memory_order_relaxed), std::memory_order_relaxed);
197 max_.store(other.max_.load(std::memory_order_relaxed), std::memory_order_relaxed);
198 other.bucket_count_ = 0;
206 bucket_counts_[bucket_idx].fetch_add(1, std::memory_order_relaxed);
208 count_.fetch_add(1, std::memory_order_relaxed);
216 return count_.load(std::memory_order_relaxed);
221 return sum_.load(std::memory_order_relaxed);
226 return min_.load(std::memory_order_relaxed);
231 return max_.load(std::memory_order_relaxed);
236 uint64_t c =
count();
241 return sum() /
static_cast<double>(c);
246 std::lock_guard<std::mutex> lock(mutex_);
248 uint64_t total = count_.load(std::memory_order_relaxed);
255 p = std::clamp(p, 0.0, 1.0);
258 double target = p *
static_cast<double>(total);
261 std::vector<uint64_t> cumulative;
262 cumulative.reserve(bucket_count_);
263 uint64_t running_sum = 0;
264 for (
size_t i = 0; i < bucket_count_; ++i)
266 running_sum += bucket_counts_[i].load(std::memory_order_relaxed);
267 cumulative.push_back(running_sum);
271 size_t bucket_idx = 0;
272 for (
size_t i = 0; i < cumulative.size(); ++i)
274 if (
static_cast<double>(cumulative[i]) >= target)
282 double lower_bound = (bucket_idx == 0) ? 0.0 : boundaries_[bucket_idx - 1];
283 double upper_bound = boundaries_[bucket_idx];
286 if (std::isinf(upper_bound))
291 uint64_t lower_count = (bucket_idx == 0) ? 0 : cumulative[bucket_idx - 1];
292 uint64_t upper_count = cumulative[bucket_idx];
294 if (upper_count == lower_count)
299 double fraction = (target -
static_cast<double>(lower_count))
300 / (
static_cast<double>(upper_count) -
static_cast<double>(lower_count));
302 return lower_bound + (fraction * (upper_bound - lower_bound));
307 std::lock_guard<std::mutex> lock(
mutex_);
309 std::vector<std::pair<double, uint64_t>> result;
312 uint64_t cumulative = 0;
326 snap.
count = count();
347 std::lock_guard<std::mutex> lock(
mutex_);
349 count_.store(0, std::memory_order_relaxed);
350 sum_.store(0.0, std::memory_order_relaxed);
351 min_.store(std::numeric_limits<double>::infinity(), std::memory_order_relaxed);
352 max_.store(-std::numeric_limits<double>::infinity(), std::memory_order_relaxed);
362 auto it = std::lower_bound(boundaries_.begin(), boundaries_.end(), value);
363 if (it == boundaries_.end())
365 return boundaries_.size() - 1;
367 return static_cast<size_t>(std::distance(boundaries_.begin(), it));
372 double current =
min_.load(std::memory_order_relaxed);
373 while (value < current && !
min_.compare_exchange_weak(current, value, std::memory_order_relaxed,
374 std::memory_order_relaxed))
382 double current =
max_.load(std::memory_order_relaxed);
383 while (value > current && !
max_.compare_exchange_weak(current, value, std::memory_order_relaxed,
384 std::memory_order_relaxed))
392 double current =
sum_.load(std::memory_order_relaxed);
393 while (!
sum_.compare_exchange_weak(current, current + value, std::memory_order_relaxed,
394 std::memory_order_relaxed))
Thread-safe histogram for capturing value distributions.
auto max() const -> double
Get maximum observed value.
auto find_bucket(double value) const -> size_t
Find bucket index for a value.
void record(double value)
Record a value observation.
std::atomic< double > sum_
auto min() const -> double
Get minimum observed value.
auto operator=(const histogram &) -> histogram &=delete
auto snapshot(const std::map< std::string, std::string > &labels={}) const -> histogram_snapshot
Create immutable snapshot of current state.
void update_max(double value)
Update max value atomically.
std::vector< double > boundaries_
std::unique_ptr< std::atomic< uint64_t >[]> bucket_counts_
auto sum() const -> double
Get sum of all observations.
std::atomic< double > max_
std::atomic< uint64_t > count_
auto mean() const -> double
Get mean of all observations.
histogram(histogram_config cfg=histogram_config::default_latency_config())
Construct histogram with configuration.
auto percentile(double p) const -> double
Calculate percentile value.
void add_to_sum(double value)
Add to sum atomically.
auto count() const -> uint64_t
Get total number of observations.
auto buckets() const -> std::vector< std::pair< double, uint64_t > >
Get all bucket counts.
void reset()
Reset all statistics.
void update_min(double value)
Update min value atomically.
std::atomic< double > min_
Histogram metric implementation for latency distribution tracking.
Configuration for histogram bucket boundaries.
std::vector< double > bucket_boundaries
Explicit bucket boundaries (upper bounds)
static auto default_latency_config() -> histogram_config
Create default configuration for network latencies.
Immutable snapshot of histogram state for export.
std::map< double, double > percentiles
Percentile -> value mapping.
std::map< std::string, std::string > labels
Additional metric labels.
uint64_t count
Total number of observations.
auto to_json() const -> std::string
Export histogram as JSON.
double sum
Sum of all observed values.
double min_value
Minimum observed value.
std::vector< std::pair< double, uint64_t > > buckets
Boundary -> cumulative count.
auto to_prometheus(const std::string &name) const -> std::string
Export histogram in Prometheus format.
double max_value
Maximum observed value.