]> git.sesse.net Git - nageru/blob - shared/metrics.h
Export SRT statistics as Prometheus metrics.
[nageru] / shared / metrics.h
1 #ifndef _METRICS_H
2 #define _METRICS_H 1
3
4 // A simple global class to keep track of metrics export in Prometheus format.
5 // It would be better to use a more full-featured Prometheus client library for this,
6 // but it would introduce a dependency that is not commonly packaged in distributions,
7 // which makes it quite unwieldy. Thus, we'll package our own for the time being.
8
9 #include <atomic>
10 #include <chrono>
11 #include <deque>
12 #include <map>
13 #include <memory>
14 #include <mutex>
15 #include <string>
16 #include <utility>
17 #include <vector>
18
19 class Histogram;
20 class Summary;
21
22 // Prometheus recommends the use of timestamps instead of “time since event”,
23 // so you can use this to get the number of seconds since the epoch.
24 // Note that this will be wrong if your clock changes, so for non-metric use,
25 // you should use std::chrono::steady_clock instead.
26 double get_timestamp_for_metrics();
27
28 class Metrics {
29 public:
30         enum Type {
31                 TYPE_COUNTER,
32                 TYPE_GAUGE,
33                 TYPE_HISTOGRAM,  // Internal use only.
34                 TYPE_SUMMARY,  // Internal use only.
35         };
36         enum Laziness {
37                 PRINT_ALWAYS,
38                 PRINT_WHEN_NONEMPTY,
39         };
40
41         void set_prefix(const std::string &prefix)  // Not thread-safe; must be set before HTTPD starts up.
42         {
43                 this->prefix = prefix;
44         }
45
46         void add(const std::string &name, std::atomic<int64_t> *location, Type type = TYPE_COUNTER)
47         {
48                 add(name, {}, location, type);
49         }
50
51         void add(const std::string &name, std::atomic<double> *location, Type type = TYPE_COUNTER)
52         {
53                 add(name, {}, location, type);
54         }
55
56         void add(const std::string &name, Histogram *location)
57         {
58                 add(name, {}, location);
59         }
60
61         void add(const std::string &name, Summary *location)
62         {
63                 add(name, {}, location);
64         }
65
66         void add(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels, std::atomic<int64_t> *location, Type type = TYPE_COUNTER);
67         void add(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels, std::atomic<double> *location, Type type = TYPE_COUNTER);
68         void add(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels, Histogram *location, Laziness laziness = PRINT_ALWAYS);
69         void add(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels, Summary *location, Laziness laziness = PRINT_ALWAYS);
70
71         void remove(const std::string &name)
72         {
73                 remove(name, {});
74         }
75
76         void remove(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels);
77
78         void remove_if_exists(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels);
79
80         std::string serialize() const;
81
82 private:
83         static std::string serialize_name(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels);
84         static std::string serialize_labels(const std::vector<std::pair<std::string, std::string>> &labels);
85
86         enum DataType {
87                 DATA_TYPE_INT64,
88                 DATA_TYPE_DOUBLE,
89                 DATA_TYPE_HISTOGRAM,
90                 DATA_TYPE_SUMMARY,
91         };
92         struct MetricKey {
93                 MetricKey(const std::string &name, const std::vector<std::pair<std::string, std::string>> labels)
94                         : name(name), labels(labels), serialized_labels(serialize_labels(labels))
95                 {
96                 }
97
98                 bool operator< (const MetricKey &other) const
99                 {
100                         if (name != other.name)
101                                 return name < other.name;
102                         return serialized_labels < other.serialized_labels;
103                 }
104
105                 const std::string name;
106                 const std::vector<std::pair<std::string, std::string>> labels;
107                 const std::string serialized_labels;
108         };
109         struct Metric {
110                 DataType data_type;
111                 Laziness laziness;  // Only for TYPE_HISTOGRAM.
112                 union {
113                         std::atomic<int64_t> *location_int64;
114                         std::atomic<double> *location_double;
115                         Histogram *location_histogram;
116                         Summary *location_summary;
117                 };
118         };
119
120         mutable std::mutex mu;
121         std::map<std::string, Type> types;  // Ordered the same as metrics.
122         std::map<MetricKey, Metric> metrics;
123         static std::string prefix;
124
125         friend class Histogram;
126         friend class Summary;
127 };
128
129 class Histogram {
130 public:
131         void init(const std::vector<double> &bucket_vals);
132         void init_uniform(size_t num_buckets);  // Sets up buckets 0..(N-1).
133         void init_geometric(double min, double max, size_t num_buckets);
134         void count_event(double val);
135         std::string serialize(Metrics::Laziness laziness, const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels) const;
136
137 private:
138         // Bucket <i> counts number of events where val[i - 1] < x <= val[i].
139         // The end histogram ends up being made into a cumulative one,
140         // but that's not how we store it here.
141         struct Bucket {
142                 double val;
143                 std::atomic<int64_t> count{0};
144         };
145         std::unique_ptr<Bucket[]> buckets;
146         size_t num_buckets;
147         std::atomic<double> sum{0.0};
148         std::atomic<int64_t> count_after_last_bucket{0};
149 };
150
151 // This is a pretty dumb streaming quantile class, but it's exact, and we don't have
152 // too many values (typically one per frame, and one-minute interval), so we don't
153 // need anything fancy.
154 class Summary {
155 public:
156         void init(const std::vector<double> &quantiles, double window_seconds);
157         void count_event(double val);
158         std::string serialize(Metrics::Laziness laziness, const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels);
159
160 private:
161         std::vector<double> quantiles;
162         std::chrono::duration<double> window;
163
164         mutable std::mutex mu;
165         std::deque<std::pair<std::chrono::steady_clock::time_point, double>> values;
166         std::atomic<double> sum{0.0};
167         std::atomic<int64_t> count{0};
168 };
169
170 extern Metrics global_metrics;
171
172 #endif  // !defined(_METRICS_H)