Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
metric_types.h
Go to the documentation of this file.
1#pragma once
2
3// BSD 3-Clause License
4// Copyright (c) 2025, 🍀☀🌕🌥 🌊
5// See the LICENSE file in the project root for full license information.
6
7
16// Prevent Windows min/max macros from conflicting with std::min/std::max
17// and member functions named min()/max()
18#ifdef _WIN32
19#ifndef NOMINMAX
20#define NOMINMAX
21#endif
22// Undefine min/max macros if already defined (e.g., from windows.h included earlier)
23#ifdef min
24#undef min
25#endif
26#ifdef max
27#undef max
28#endif
29#endif
30
32#include "../core/error_codes.h"
33#include <string>
34#include <chrono>
35#include <unordered_map>
36#include <variant>
37#include <vector>
38#include <cstdint>
39#include <algorithm>
40#include <cmath>
41#include <cstdlib>
42#include <limits>
43
44namespace kcenon { namespace monitoring {
45
50enum class metric_type : uint8_t {
51 counter = 0, // Monotonically increasing value
52 gauge, // Instantaneous value
53 histogram, // Distribution of values
54 summary, // Summary statistics
55 timer, // Duration measurements
56 set // Unique value counting
57};
58
62constexpr const char* metric_type_to_string(metric_type type) noexcept {
63 switch (type) {
64 case metric_type::counter: return "counter";
65 case metric_type::gauge: return "gauge";
66 case metric_type::histogram: return "histogram";
67 case metric_type::summary: return "summary";
68 case metric_type::timer: return "timer";
69 case metric_type::set: return "set";
70 default: return "unknown";
71 }
72}
73
79 uint32_t name_hash; // Hashed metric name for fast lookup
80 metric_type type; // Type of metric
81 uint8_t tag_count; // Number of tags (max 255)
82 uint16_t reserved; // Reserved for future use
83
86
87 metric_metadata(uint32_t hash, metric_type mt, uint8_t tags = 0) noexcept
88 : name_hash(hash), type(mt), tag_count(tags), reserved(0) {}
89};
90
96 using value_type = std::variant<
97 double, // Numeric value
98 int64_t, // Integer value
99 std::string // String value (for sets)
100 >;
101
104 uint64_t timestamp_us; // Microseconds since epoch for precision
105
107 : value(0.0), timestamp_us(0) {}
108
109 compact_metric_value(const metric_metadata& meta, double val) noexcept
110 : metadata(meta), value(val) {
111 timestamp_us = std::chrono::duration_cast<std::chrono::microseconds>(
112 std::chrono::system_clock::now().time_since_epoch()).count();
113 }
114
115 compact_metric_value(const metric_metadata& meta, int64_t val) noexcept
116 : metadata(meta), value(val) {
117 timestamp_us = std::chrono::duration_cast<std::chrono::microseconds>(
118 std::chrono::system_clock::now().time_since_epoch()).count();
119 }
120
121 compact_metric_value(const metric_metadata& meta, std::string val) noexcept
122 : metadata(meta), value(std::move(val)) {
123 timestamp_us = std::chrono::duration_cast<std::chrono::microseconds>(
124 std::chrono::system_clock::now().time_since_epoch()).count();
125 }
126
130 double as_double() const {
131 if (std::holds_alternative<double>(value)) {
132 return std::get<double>(value);
133 } else if (std::holds_alternative<int64_t>(value)) {
134 return static_cast<double>(std::get<int64_t>(value));
135 }
136 return 0.0;
137 }
138
142 int64_t as_int64() const {
143 if (std::holds_alternative<int64_t>(value)) {
144 return std::get<int64_t>(value);
145 } else if (std::holds_alternative<double>(value)) {
146 return static_cast<int64_t>(std::get<double>(value));
147 }
148 return 0;
149 }
150
154 std::string as_string() const {
155 if (std::holds_alternative<std::string>(value)) {
156 return std::get<std::string>(value);
157 } else if (std::holds_alternative<double>(value)) {
158 return std::to_string(std::get<double>(value));
159 } else if (std::holds_alternative<int64_t>(value)) {
160 return std::to_string(std::get<int64_t>(value));
161 }
162 return "";
163 }
164
168 std::chrono::system_clock::time_point get_timestamp() const {
169 return std::chrono::system_clock::time_point(
170 std::chrono::microseconds(timestamp_us));
171 }
172
176 bool is_numeric() const noexcept {
177 return std::holds_alternative<double>(value) ||
178 std::holds_alternative<int64_t>(value);
179 }
180
184 size_t memory_footprint() const noexcept {
185 size_t base_size = sizeof(metric_metadata) + sizeof(timestamp_us) + sizeof(value_type);
186 if (std::holds_alternative<std::string>(value)) {
187 base_size += std::get<std::string>(value).capacity();
188 }
189 return base_size;
190 }
191};
192
198 std::vector<compact_metric_value> metrics;
199 std::chrono::system_clock::time_point batch_timestamp;
200 size_t batch_id;
201
202 metric_batch() : batch_timestamp(std::chrono::system_clock::now()), batch_id(0) {}
203
204 explicit metric_batch(size_t id)
205 : batch_timestamp(std::chrono::system_clock::now()), batch_id(id) {}
206
211 metrics.emplace_back(std::move(metric));
212 }
213
217 size_t memory_footprint() const noexcept {
218 size_t total = sizeof(metric_batch);
219 for (const auto& metric : metrics) {
220 total += metric.memory_footprint();
221 }
222 return total;
223 }
224
228 void reserve(size_t count) {
229 metrics.reserve(count);
230 }
231
235 void clear() {
236 metrics.clear();
237 batch_timestamp = std::chrono::system_clock::now();
238 }
239
243 bool empty() const noexcept {
244 return metrics.empty();
245 }
246
250 size_t size() const noexcept {
251 return metrics.size();
252 }
253};
254
261 uint64_t count;
262
263 histogram_bucket(double bound = 0.0, uint64_t cnt = 0) noexcept
264 : upper_bound(bound), count(cnt) {}
265
266 bool operator<(const histogram_bucket& other) const noexcept {
267 return upper_bound < other.upper_bound;
268 }
269};
270
276 std::vector<histogram_bucket> buckets;
277 uint64_t total_count = 0;
278 double sum = 0.0;
279
283 void add_sample(double value) {
284 sum += value;
285 total_count++;
286
287 for (auto& bucket : buckets) {
288 if (value <= bucket.upper_bound) {
289 bucket.count++;
290 }
291 }
292 }
293
297 double mean() const noexcept {
298 return total_count > 0 ? sum / total_count : 0.0;
299 }
300
305 buckets = {
306 {0.005, 0}, {0.01, 0}, {0.025, 0}, {0.05, 0}, {0.075, 0},
307 {0.1, 0}, {0.25, 0}, {0.5, 0}, {0.75, 0}, {1.0, 0},
308 {2.5, 0}, {5.0, 0}, {7.5, 0}, {10.0, 0},
309 {std::numeric_limits<double>::infinity(), 0}
310 };
311 }
312};
313
319 uint64_t count = 0;
320 double sum = 0.0;
321 double min_value = (std::numeric_limits<double>::max)();
322 double max_value = (std::numeric_limits<double>::lowest)();
323
327 void add_sample(double value) {
328 count++;
329 sum += value;
330 min_value = (std::min)(min_value, value);
331 max_value = (std::max)(max_value, value);
332 }
333
337 double mean() const noexcept {
338 return count > 0 ? sum / count : 0.0;
339 }
340
344 void reset() {
345 count = 0;
346 sum = 0.0;
347 min_value = (std::numeric_limits<double>::max)();
348 max_value = (std::numeric_limits<double>::lowest)();
349 }
350};
351
360 static constexpr size_t DEFAULT_RESERVOIR_SIZE = 1024;
361
362 std::vector<double> samples;
364 uint64_t total_count = 0;
365 double sum = 0.0;
366 double min_value = (std::numeric_limits<double>::max)();
367 double max_value = (std::numeric_limits<double>::lowest)();
368 mutable bool sorted = false;
369
376
380 explicit timer_data(size_t reservoir_size)
381 : max_samples(reservoir_size) {
382 samples.reserve(max_samples);
383 }
384
388 void record(double duration_ms) {
389 total_count++;
390 sum += duration_ms;
391 min_value = (std::min)(min_value, duration_ms);
392 max_value = (std::max)(max_value, duration_ms);
393 sorted = false;
394
395 if (samples.size() < max_samples) {
396 samples.push_back(duration_ms);
397 } else {
398 // Reservoir sampling for memory efficiency
399 size_t idx = static_cast<size_t>(rand()) % total_count;
400 if (idx < max_samples) {
401 samples[idx] = duration_ms;
402 }
403 }
404 }
405
409 template<typename Rep, typename Period>
410 void record(std::chrono::duration<Rep, Period> duration) {
411 auto ms = std::chrono::duration_cast<std::chrono::microseconds>(duration).count() / 1000.0;
412 record(ms);
413 }
414
420 double get_percentile(double percentile) const {
421 if (samples.empty()) return 0.0;
422 if (percentile <= 0) return min_value;
423 if (percentile >= 100) return max_value;
424
426
427 double rank = (percentile / 100.0) * (samples.size() - 1);
428 size_t lower_idx = static_cast<size_t>(rank);
429 size_t upper_idx = lower_idx + 1;
430 double fraction = rank - lower_idx;
431
432 if (upper_idx >= samples.size()) {
433 return samples[lower_idx];
434 }
435
436 // Linear interpolation between adjacent values
437 return samples[lower_idx] + fraction * (samples[upper_idx] - samples[lower_idx]);
438 }
439
443 double median() const {
444 return get_percentile(50.0);
445 }
446
450 double p90() const {
451 return get_percentile(90.0);
452 }
453
457 double p95() const {
458 return get_percentile(95.0);
459 }
460
464 double p99() const {
465 return get_percentile(99.0);
466 }
467
471 double p999() const {
472 return get_percentile(99.9);
473 }
474
478 double mean() const noexcept {
479 return total_count > 0 ? sum / total_count : 0.0;
480 }
481
485 uint64_t count() const noexcept {
486 return total_count;
487 }
488
492 double min() const noexcept {
493 return total_count > 0 ? min_value : 0.0;
494 }
495
499 double max() const noexcept {
500 return total_count > 0 ? max_value : 0.0;
501 }
502
506 double stddev() const {
507 if (samples.size() < 2) return 0.0;
508
509 double avg = mean();
510 double variance = 0.0;
511 for (double sample : samples) {
512 double diff = sample - avg;
513 variance += diff * diff;
514 }
515 variance /= samples.size();
516 return std::sqrt(variance);
517 }
518
522 void reset() {
523 samples.clear();
524 samples.reserve(max_samples);
525 total_count = 0;
526 sum = 0.0;
527 min_value = (std::numeric_limits<double>::max)();
528 max_value = (std::numeric_limits<double>::lowest)();
529 sorted = false;
530 }
531
535 struct snapshot {
536 uint64_t count;
537 double mean;
538 double min;
539 double max;
540 double stddev;
541 double p50;
542 double p90;
543 double p95;
544 double p99;
545 double p999;
546 };
547
549 return snapshot{
551 mean(),
552 min(),
553 max(),
554 stddev(),
555 median(),
556 p90(),
557 p95(),
558 p99(),
559 p999()
560 };
561 }
562
563private:
564 void ensure_sorted() const {
565 if (!sorted && !samples.empty()) {
566 auto& mutable_samples = const_cast<std::vector<double>&>(samples);
567 std::sort(mutable_samples.begin(), mutable_samples.end());
568 const_cast<bool&>(sorted) = true;
569 }
570 }
571};
572
580public:
582 : timer_(timer), start_(std::chrono::steady_clock::now()) {}
583
585 auto end = std::chrono::steady_clock::now();
586 timer_.record(end - start_);
587 }
588
589 // Non-copyable, non-movable
590 timer_scope(const timer_scope&) = delete;
594
595private:
597 std::chrono::steady_clock::time_point start_;
598};
599
603inline uint32_t hash_metric_name(const std::string& name) noexcept {
604 // Simple FNV-1a hash for fast metric name hashing
605 uint32_t hash = 2166136261U;
606 for (char c : name) {
607 hash ^= static_cast<uint32_t>(c);
608 hash *= 16777619U;
609 }
610 return hash;
611}
612
616inline metric_metadata create_metric_metadata(const std::string& name,
617 metric_type type,
618 size_t tag_count = 0) {
619 return metric_metadata(
620 hash_metric_name(name),
621 type,
622 static_cast<uint8_t>((std::min)(tag_count, size_t(255)))
623 );
624}
625
626} } // namespace kcenon::monitoring
RAII timer scope for automatic duration recording with timer_data.
timer_scope(timer_scope &&)=delete
std::chrono::steady_clock::time_point start_
timer_scope & operator=(const timer_scope &)=delete
timer_scope(const timer_scope &)=delete
timer_scope & operator=(timer_scope &&)=delete
Monitoring system specific error codes.
metric_type
Metric types supported by exporters.
@ timer
StatsD-specific timer metric.
@ gauge
Instantaneous value that can go up and down.
@ counter
Monotonically increasing counter.
@ summary
Pre-calculated quantiles and count/sum.
@ histogram
Distribution of values with buckets.
uint32_t hash_metric_name(const std::string &name) noexcept
Hash function for metric names.
constexpr const char * metric_type_to_string(metric_type type) noexcept
Convert metric type to string.
metric_metadata create_metric_metadata(const std::string &name, metric_type type, size_t tag_count=0)
Create metric metadata from name and type.
@ gauge
Instantaneous value that can go up and down.
@ counter
Monotonically increasing counter.
@ histogram
Distribution of values with buckets.
Result pattern type definitions for monitoring system.
Memory-efficient metric value storage.
compact_metric_value(const metric_metadata &meta, double val) noexcept
size_t memory_footprint() const noexcept
Get memory footprint in bytes.
std::variant< double, int64_t, std::string > value_type
compact_metric_value(const metric_metadata &meta, std::string val) noexcept
compact_metric_value(const metric_metadata &meta, int64_t val) noexcept
double as_double() const
Get value as double.
std::string as_string() const
Get value as string.
std::chrono::system_clock::time_point get_timestamp() const
Get timestamp as time_point.
bool is_numeric() const noexcept
Check if metric is numeric.
int64_t as_int64() const
Get value as integer.
Bucket for histogram metrics.
histogram_bucket(double bound=0.0, uint64_t cnt=0) noexcept
bool operator<(const histogram_bucket &other) const noexcept
Histogram data with buckets.
double mean() const noexcept
Get mean value.
void init_standard_buckets()
Initialize standard buckets.
std::vector< histogram_bucket > buckets
void add_sample(double value)
Add value to histogram.
Batch of metrics for efficient processing.
void clear()
Clear all metrics.
std::vector< compact_metric_value > metrics
size_t size() const noexcept
Get number of metrics in batch.
void add_metric(compact_metric_value &&metric)
Add metric to batch.
size_t memory_footprint() const noexcept
Get batch size in bytes.
bool empty() const noexcept
Check if batch is empty.
void reserve(size_t count)
Reserve space for metrics.
std::chrono::system_clock::time_point batch_timestamp
Compact metadata for metrics.
metric_metadata(uint32_t hash, metric_type mt, uint8_t tags=0) noexcept
Basic metric structure for interface compatibility.
Summary statistics for metrics.
void add_sample(double value)
Add sample to summary.
double mean() const noexcept
Get mean value.
Get snapshot of current statistics.
Timer data with percentile calculations.
double p99() const
Get p99 value.
double p90() const
Get p90 value.
uint64_t count() const noexcept
Get sample count.
void record(std::chrono::duration< Rep, Period > duration)
Record a duration using chrono duration.
double mean() const noexcept
Get mean value.
double p95() const
Get p95 value.
std::vector< double > samples
double stddev() const
Get standard deviation.
double max() const noexcept
Get maximum recorded value.
double get_percentile(double percentile) const
Get percentile value (0-100)
double median() const
Get median (p50)
double p999() const
Get p999 value (99.9th percentile)
void record(double duration_ms)
Record a duration sample (in milliseconds)
timer_data(size_t reservoir_size)
Construct timer with custom reservoir size.
timer_data()
Construct timer with default reservoir size.
static constexpr size_t DEFAULT_RESERVOIR_SIZE
void reset()
Reset all data.
double min() const noexcept
Get minimum recorded value.