Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
kcenon::network::metrics::sliding_histogram Class Reference

Time-windowed histogram for tracking recent latency distributions. More...

#include <sliding_histogram.h>

Collaboration diagram for kcenon::network::metrics::sliding_histogram:
Collaboration graph

Classes

struct  time_bucket
 

Public Member Functions

 sliding_histogram (sliding_histogram_config cfg=sliding_histogram_config::default_config())
 Construct sliding histogram with configuration.
 
 ~sliding_histogram ()=default
 Destructor.
 
 sliding_histogram (const sliding_histogram &)=delete
 
auto operator= (const sliding_histogram &) -> sliding_histogram &=delete
 
 sliding_histogram (sliding_histogram &&other) noexcept
 
auto operator= (sliding_histogram &&other) noexcept -> sliding_histogram &
 
void record (double value)
 Record a value observation.
 
auto count () const -> uint64_t
 Get total number of observations in current window.
 
auto sum () const -> double
 Get sum of all observations in current window.
 
auto mean () const -> double
 Get mean of all observations in current window.
 
auto percentile (double p) const -> double
 Calculate percentile value for current window.
 
auto p50 () const -> double
 Get 50th percentile (median) for current window.
 
auto p95 () const -> double
 Get 95th percentile for current window.
 
auto p99 () const -> double
 Get 99th percentile for current window.
 
auto p999 () const -> double
 Get 99.9th percentile for current window.
 
auto snapshot (const std::map< std::string, std::string > &labels={}) const -> histogram_snapshot
 Create snapshot aggregating all time buckets in current window.
 
auto window_duration () const -> std::chrono::seconds
 Get the window duration.
 
void reset ()
 Reset all data.
 

Private Member Functions

void expire_old_buckets ()
 Expire old buckets outside the window.
 
auto get_current_bucket () -> time_bucket &
 Get or create current time bucket.
 
auto aggregate () const -> histogram_snapshot
 Create aggregated histogram from all buckets.
 

Private Attributes

sliding_histogram_config config_
 
std::chrono::milliseconds bucket_duration_
 
std::deque< std::unique_ptr< time_bucket > > buckets_
 
std::mutex mutex_
 

Detailed Description

Time-windowed histogram for tracking recent latency distributions.

This class maintains a sliding window of histogram data, automatically expiring old measurements. Useful for monitoring recent performance without accumulating historical data indefinitely.

Example usage:
sh.record(5.5);
sh.record(10.2);
// Get percentiles for the last 60 seconds
double p99 = sh.p99();
Time-windowed histogram for tracking recent latency distributions.
auto p99() const -> double
Get 99th percentile for current window.
static auto default_config() -> sliding_histogram_config
Create default configuration (60 second window, 6 buckets = 10s each)

Definition at line 66 of file sliding_histogram.h.

Constructor & Destructor Documentation

◆ sliding_histogram() [1/3]

kcenon::network::metrics::sliding_histogram::sliding_histogram ( sliding_histogram_config cfg = sliding_histogram_config::default_config())
explicit

Construct sliding histogram with configuration.

Parameters
cfgConfiguration for the sliding window

Definition at line 13 of file sliding_histogram.cpp.

14 : config_(std::move(cfg))
15 , bucket_duration_(std::chrono::duration_cast<std::chrono::milliseconds>(config_.window_duration)
17{
18 // Ensure at least 1 bucket
19 if (config_.bucket_count == 0)
20 {
22 bucket_duration_ = std::chrono::duration_cast<std::chrono::milliseconds>(config_.window_duration);
23 }
24}
std::chrono::seconds window_duration
Total window duration.

References kcenon::network::metrics::sliding_histogram_config::bucket_count, and config_.

◆ ~sliding_histogram()

kcenon::network::metrics::sliding_histogram::~sliding_histogram ( )
default

Destructor.

◆ sliding_histogram() [2/3]

kcenon::network::metrics::sliding_histogram::sliding_histogram ( const sliding_histogram & )
delete

◆ sliding_histogram() [3/3]

kcenon::network::metrics::sliding_histogram::sliding_histogram ( sliding_histogram && other)
noexcept

Definition at line 26 of file sliding_histogram.cpp.

27 : config_(std::move(other.config_))
28 , bucket_duration_(other.bucket_duration_)
29 , buckets_(std::move(other.buckets_))
30{
31}
std::deque< std::unique_ptr< time_bucket > > buckets_

Member Function Documentation

◆ aggregate()

auto kcenon::network::metrics::sliding_histogram::aggregate ( ) const -> histogram_snapshot
nodiscardprivate

Create aggregated histogram from all buckets.

Returns
Aggregated histogram snapshot

Definition at line 186 of file sliding_histogram.cpp.

187{
188 std::lock_guard<std::mutex> lock(mutex_);
189 const_cast<sliding_histogram*>(this)->expire_old_buckets();
190
191 histogram_snapshot result;
192 result.count = 0;
193 result.sum = 0.0;
194 result.min_value = std::numeric_limits<double>::infinity();
195 result.max_value = -std::numeric_limits<double>::infinity();
196
197 if (buckets_.empty())
198 {
199 return result;
200 }
201
202 // Aggregate statistics
203 std::vector<std::pair<double, uint64_t>> merged_buckets;
204 bool first_snapshot = true;
205
206 for (const auto& bucket : buckets_)
207 {
208 auto snap = bucket->hist.snapshot();
209 result.count += snap.count;
210 result.sum += snap.sum;
211
212 if (snap.min_value < result.min_value)
213 {
214 result.min_value = snap.min_value;
215 }
216 if (snap.max_value > result.max_value)
217 {
218 result.max_value = snap.max_value;
219 }
220
221 // Merge bucket counts
222 if (first_snapshot)
223 {
224 merged_buckets = snap.buckets;
225 first_snapshot = false;
226 }
227 else
228 {
229 // Need to convert from cumulative to individual counts, add, then back to cumulative
230 std::vector<uint64_t> individual_counts(snap.buckets.size(), 0);
231 for (size_t i = 0; i < snap.buckets.size(); ++i)
232 {
233 uint64_t prev = (i == 0) ? 0 : snap.buckets[i - 1].second;
234 individual_counts[i] = snap.buckets[i].second - prev;
235 }
236
237 std::vector<uint64_t> merged_individual(merged_buckets.size(), 0);
238 for (size_t i = 0; i < merged_buckets.size(); ++i)
239 {
240 uint64_t prev = (i == 0) ? 0 : merged_buckets[i - 1].second;
241 merged_individual[i] = merged_buckets[i].second - prev;
242 }
243
244 // Add individual counts
245 for (size_t i = 0; i < merged_buckets.size() && i < individual_counts.size(); ++i)
246 {
247 merged_individual[i] += individual_counts[i];
248 }
249
250 // Convert back to cumulative
251 uint64_t cumulative = 0;
252 for (size_t i = 0; i < merged_buckets.size(); ++i)
253 {
254 cumulative += merged_individual[i];
255 merged_buckets[i].second = cumulative;
256 }
257 }
258 }
259
260 result.buckets = std::move(merged_buckets);
261
262 // Calculate percentiles from merged buckets
263 if (result.count > 0 && !result.buckets.empty())
264 {
265 auto calc_percentile = [&](double p) -> double
266 {
267 double target = p * static_cast<double>(result.count);
268
269 for (size_t i = 0; i < result.buckets.size(); ++i)
270 {
271 if (static_cast<double>(result.buckets[i].second) >= target)
272 {
273 double lower_bound = (i == 0) ? 0.0 : result.buckets[i - 1].first;
274 double upper_bound = result.buckets[i].first;
275
276 if (std::isinf(upper_bound))
277 {
278 return lower_bound;
279 }
280
281 uint64_t lower_count = (i == 0) ? 0 : result.buckets[i - 1].second;
282 uint64_t upper_count = result.buckets[i].second;
283
284 if (upper_count == lower_count)
285 {
286 return lower_bound;
287 }
288
289 double fraction =
290 (target - static_cast<double>(lower_count))
291 / (static_cast<double>(upper_count) - static_cast<double>(lower_count));
292
293 return lower_bound + (fraction * (upper_bound - lower_bound));
294 }
295 }
296 return 0.0;
297 };
298
299 result.percentiles[0.5] = calc_percentile(0.5);
300 result.percentiles[0.9] = calc_percentile(0.9);
301 result.percentiles[0.95] = calc_percentile(0.95);
302 result.percentiles[0.99] = calc_percentile(0.99);
303 result.percentiles[0.999] = calc_percentile(0.999);
304 }
305
306 return result;
307}
void expire_old_buckets()
Expire old buckets outside the window.
sliding_histogram(sliding_histogram_config cfg=sliding_histogram_config::default_config())
Construct sliding histogram with configuration.

References kcenon::network::metrics::histogram_snapshot::buckets, buckets_, kcenon::network::metrics::histogram_snapshot::count, expire_old_buckets(), kcenon::network::metrics::histogram_snapshot::max_value, kcenon::network::metrics::histogram_snapshot::min_value, mutex_, kcenon::network::metrics::histogram_snapshot::percentiles, and kcenon::network::metrics::histogram_snapshot::sum.

Here is the call graph for this function:

◆ count()

auto kcenon::network::metrics::sliding_histogram::count ( ) const -> uint64_t
nodiscard

Get total number of observations in current window.

Returns
Count of recorded values

Definition at line 53 of file sliding_histogram.cpp.

54{
55 std::lock_guard<std::mutex> lock(mutex_);
56 const_cast<sliding_histogram*>(this)->expire_old_buckets();
57
58 uint64_t total = 0;
59 for (const auto& bucket : buckets_)
60 {
61 total += bucket->hist.count();
62 }
63 return total;
64}

References buckets_, expire_old_buckets(), and mutex_.

Referenced by mean().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ expire_old_buckets()

void kcenon::network::metrics::sliding_histogram::expire_old_buckets ( )
private

Expire old buckets outside the window.

Definition at line 154 of file sliding_histogram.cpp.

155{
156 auto now = std::chrono::steady_clock::now();
157 auto cutoff = now - config_.window_duration;
158
159 while (!buckets_.empty() && buckets_.front()->start_time < cutoff)
160 {
161 buckets_.pop_front();
162 }
163}

References buckets_, config_, and kcenon::network::metrics::sliding_histogram_config::window_duration.

Referenced by aggregate(), count(), record(), and sum().

Here is the caller graph for this function:

◆ get_current_bucket()

auto kcenon::network::metrics::sliding_histogram::get_current_bucket ( ) -> time_bucket&
private

Get or create current time bucket.

Returns
Reference to the current bucket

Definition at line 165 of file sliding_histogram.cpp.

166{
167 auto now = std::chrono::steady_clock::now();
168
169 if (buckets_.empty())
170 {
171 buckets_.push_back(std::make_unique<time_bucket>(config_.hist_config));
172 return *buckets_.back();
173 }
174
175 auto& last = buckets_.back();
176 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - last->start_time);
177
178 if (elapsed >= bucket_duration_)
179 {
180 buckets_.push_back(std::make_unique<time_bucket>(config_.hist_config));
181 }
182
183 return *buckets_.back();
184}
histogram_config hist_config
Histogram bucket configuration.

Referenced by record().

Here is the caller graph for this function:

◆ mean()

auto kcenon::network::metrics::sliding_histogram::mean ( ) const -> double
nodiscard

Get mean of all observations in current window.

Returns
Mean value, or 0 if no observations

Definition at line 79 of file sliding_histogram.cpp.

80{
81 uint64_t c = count();
82 if (c == 0)
83 {
84 return 0.0;
85 }
86 return sum() / static_cast<double>(c);
87}
auto sum() const -> double
Get sum of all observations in current window.
auto count() const -> uint64_t
Get total number of observations in current window.

References count(), and sum().

Here is the call graph for this function:

◆ operator=() [1/2]

auto kcenon::network::metrics::sliding_histogram::operator= ( const sliding_histogram & ) -> sliding_histogram &=delete
delete

◆ operator=() [2/2]

auto kcenon::network::metrics::sliding_histogram::operator= ( sliding_histogram && other) -> sliding_histogram&
noexcept

Definition at line 33 of file sliding_histogram.cpp.

34{
35 if (this != &other)
36 {
37 std::lock_guard<std::mutex> lock(mutex_);
38 config_ = std::move(other.config_);
39 bucket_duration_ = other.bucket_duration_;
40 buckets_ = std::move(other.buckets_);
41 }
42 return *this;
43}

◆ p50()

auto kcenon::network::metrics::sliding_histogram::p50 ( ) const -> double
inlinenodiscard

Get 50th percentile (median) for current window.

Returns
Estimated median value

Definition at line 126 of file sliding_histogram.h.

126{ return percentile(0.50); }
auto percentile(double p) const -> double
Calculate percentile value for current window.

References percentile().

Here is the call graph for this function:

◆ p95()

auto kcenon::network::metrics::sliding_histogram::p95 ( ) const -> double
inlinenodiscard

Get 95th percentile for current window.

Returns
Estimated p95 value

Definition at line 132 of file sliding_histogram.h.

132{ return percentile(0.95); }

References percentile().

Here is the call graph for this function:

◆ p99()

auto kcenon::network::metrics::sliding_histogram::p99 ( ) const -> double
inlinenodiscard

Get 99th percentile for current window.

Returns
Estimated p99 value

Definition at line 138 of file sliding_histogram.h.

138{ return percentile(0.99); }

References percentile().

Here is the call graph for this function:

◆ p999()

auto kcenon::network::metrics::sliding_histogram::p999 ( ) const -> double
inlinenodiscard

Get 99.9th percentile for current window.

Returns
Estimated p999 value

Definition at line 144 of file sliding_histogram.h.

144{ return percentile(0.999); }

References percentile().

Here is the call graph for this function:

◆ percentile()

auto kcenon::network::metrics::sliding_histogram::percentile ( double p) const -> double
nodiscard

Calculate percentile value for current window.

Parameters
pPercentile (0.0 to 1.0)
Returns
Estimated value at the given percentile

Definition at line 89 of file sliding_histogram.cpp.

90{
91 auto snap = aggregate();
92 if (snap.count == 0)
93 {
94 return 0.0;
95 }
96
97 // Clamp percentile
98 p = std::clamp(p, 0.0, 1.0);
99
100 // Use pre-calculated percentiles if available
101 auto it = snap.percentiles.find(p);
102 if (it != snap.percentiles.end())
103 {
104 return it->second;
105 }
106
107 // Calculate from buckets
108 double target = p * static_cast<double>(snap.count);
109
110 for (size_t i = 0; i < snap.buckets.size(); ++i)
111 {
112 if (static_cast<double>(snap.buckets[i].second) >= target)
113 {
114 double lower_bound = (i == 0) ? 0.0 : snap.buckets[i - 1].first;
115 double upper_bound = snap.buckets[i].first;
116
117 if (std::isinf(upper_bound))
118 {
119 return lower_bound;
120 }
121
122 uint64_t lower_count = (i == 0) ? 0 : snap.buckets[i - 1].second;
123 uint64_t upper_count = snap.buckets[i].second;
124
125 if (upper_count == lower_count)
126 {
127 return lower_bound;
128 }
129
130 double fraction = (target - static_cast<double>(lower_count))
131 / (static_cast<double>(upper_count) - static_cast<double>(lower_count));
132
133 return lower_bound + (fraction * (upper_bound - lower_bound));
134 }
135 }
136
137 return 0.0;
138}
auto aggregate() const -> histogram_snapshot
Create aggregated histogram from all buckets.

Referenced by p50(), p95(), p99(), and p999().

Here is the caller graph for this function:

◆ record()

void kcenon::network::metrics::sliding_histogram::record ( double value)

Record a value observation.

Parameters
valueThe value to record

Thread-safe. Automatically creates new time buckets as needed and expires old ones.

Definition at line 45 of file sliding_histogram.cpp.

46{
47 std::lock_guard<std::mutex> lock(mutex_);
49 auto& bucket = get_current_bucket();
50 bucket.hist.record(value);
51}
auto get_current_bucket() -> time_bucket &
Get or create current time bucket.

References expire_old_buckets(), get_current_bucket(), and mutex_.

Here is the call graph for this function:

◆ reset()

void kcenon::network::metrics::sliding_histogram::reset ( )

Reset all data.

Thread-safe. Clears all time buckets.

Definition at line 148 of file sliding_histogram.cpp.

149{
150 std::lock_guard<std::mutex> lock(mutex_);
151 buckets_.clear();
152}

References buckets_, and mutex_.

◆ snapshot()

auto kcenon::network::metrics::sliding_histogram::snapshot ( const std::map< std::string, std::string > & labels = {}) const -> histogram_snapshot
nodiscard

Create snapshot aggregating all time buckets in current window.

Parameters
labelsAdditional labels to include in snapshot
Returns
Aggregated snapshot with statistics

Definition at line 140 of file sliding_histogram.cpp.

142{
143 auto snap = aggregate();
144 snap.labels = labels;
145 return snap;
146}

◆ sum()

auto kcenon::network::metrics::sliding_histogram::sum ( ) const -> double
nodiscard

Get sum of all observations in current window.

Returns
Sum of recorded values

Definition at line 66 of file sliding_histogram.cpp.

67{
68 std::lock_guard<std::mutex> lock(mutex_);
69 const_cast<sliding_histogram*>(this)->expire_old_buckets();
70
71 double total = 0.0;
72 for (const auto& bucket : buckets_)
73 {
74 total += bucket->hist.sum();
75 }
76 return total;
77}

References buckets_, expire_old_buckets(), and mutex_.

Referenced by mean().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ window_duration()

auto kcenon::network::metrics::sliding_histogram::window_duration ( ) const -> std::chrono::seconds
inlinenodiscard

Get the window duration.

Returns
Configured window duration

Definition at line 158 of file sliding_histogram.h.

159 {
161 }

References config_, and kcenon::network::metrics::sliding_histogram_config::window_duration.

Member Data Documentation

◆ bucket_duration_

std::chrono::milliseconds kcenon::network::metrics::sliding_histogram::bucket_duration_
private

Definition at line 183 of file sliding_histogram.h.

◆ buckets_

std::deque<std::unique_ptr<time_bucket> > kcenon::network::metrics::sliding_histogram::buckets_
private

Definition at line 184 of file sliding_histogram.h.

Referenced by aggregate(), count(), expire_old_buckets(), reset(), and sum().

◆ config_

sliding_histogram_config kcenon::network::metrics::sliding_histogram::config_
private

Definition at line 182 of file sliding_histogram.h.

Referenced by expire_old_buckets(), sliding_histogram(), and window_duration().

◆ mutex_

std::mutex kcenon::network::metrics::sliding_histogram::mutex_
mutableprivate

Definition at line 185 of file sliding_histogram.h.

Referenced by aggregate(), count(), record(), reset(), and sum().


The documentation for this class was generated from the following files: