]> git.sesse.net Git - nageru/blob - shared/metrics.h
Prefix all the Futatabi Prometheus metrics by futatabi_ instead of nageru_.
[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         std::string serialize() const;
79
80 private:
81         static std::string serialize_name(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels);
82         static std::string serialize_labels(const std::vector<std::pair<std::string, std::string>> &labels);
83
84         enum DataType {
85                 DATA_TYPE_INT64,
86                 DATA_TYPE_DOUBLE,
87                 DATA_TYPE_HISTOGRAM,
88                 DATA_TYPE_SUMMARY,
89         };
90         struct MetricKey {
91                 MetricKey(const std::string &name, const std::vector<std::pair<std::string, std::string>> labels)
92                         : name(name), labels(labels), serialized_labels(serialize_labels(labels))
93                 {
94                 }
95
96                 bool operator< (const MetricKey &other) const
97                 {
98                         if (name != other.name)
99                                 return name < other.name;
100                         return serialized_labels < other.serialized_labels;
101                 }
102
103                 const std::string name;
104                 const std::vector<std::pair<std::string, std::string>> labels;
105                 const std::string serialized_labels;
106         };
107         struct Metric {
108                 DataType data_type;
109                 Laziness laziness;  // Only for TYPE_HISTOGRAM.
110                 union {
111                         std::atomic<int64_t> *location_int64;
112                         std::atomic<double> *location_double;
113                         Histogram *location_histogram;
114                         Summary *location_summary;
115                 };
116         };
117
118         mutable std::mutex mu;
119         std::map<std::string, Type> types;  // Ordered the same as metrics.
120         std::map<MetricKey, Metric> metrics;
121         static std::string prefix;
122
123         friend class Histogram;
124         friend class Summary;
125 };
126
127 class Histogram {
128 public:
129         void init(const std::vector<double> &bucket_vals);
130         void init_uniform(size_t num_buckets);  // Sets up buckets 0..(N-1).
131         void init_geometric(double min, double max, size_t num_buckets);
132         void count_event(double val);
133         std::string serialize(Metrics::Laziness laziness, const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels) const;
134
135 private:
136         // Bucket <i> counts number of events where val[i - 1] < x <= val[i].
137         // The end histogram ends up being made into a cumulative one,
138         // but that's not how we store it here.
139         struct Bucket {
140                 double val;
141                 std::atomic<int64_t> count{0};
142         };
143         std::unique_ptr<Bucket[]> buckets;
144         size_t num_buckets;
145         std::atomic<double> sum{0.0};
146         std::atomic<int64_t> count_after_last_bucket{0};
147 };
148
149 // This is a pretty dumb streaming quantile class, but it's exact, and we don't have
150 // too many values (typically one per frame, and one-minute interval), so we don't
151 // need anything fancy.
152 class Summary {
153 public:
154         void init(const std::vector<double> &quantiles, double window_seconds);
155         void count_event(double val);
156         std::string serialize(Metrics::Laziness laziness, const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels);
157
158 private:
159         std::vector<double> quantiles;
160         std::chrono::duration<double> window;
161
162         mutable std::mutex mu;
163         std::deque<std::pair<std::chrono::steady_clock::time_point, double>> values;
164         std::atomic<double> sum{0.0};
165         std::atomic<int64_t> count{0};
166 };
167
168 extern Metrics global_metrics;
169
170 #endif  // !defined(_METRICS_H)