]> git.sesse.net Git - nageru/blob - metrics.cpp
Update the queue length metric after trimming, not before.
[nageru] / metrics.cpp
1 #include "metrics.h"
2
3 #include <assert.h>
4 #include <math.h>
5
6 #include <algorithm>
7 #include <chrono>
8 #include <locale>
9 #include <sstream>
10
11 using namespace std;
12 using namespace std::chrono;
13
14 Metrics global_metrics;
15
16 double get_timestamp_for_metrics()
17 {
18         return duration<double>(system_clock::now().time_since_epoch()).count();
19 }
20
21 string Metrics::serialize_name(const string &name, const vector<pair<string, string>> &labels)
22 {
23         return "nageru_" + name + serialize_labels(labels);
24 }
25
26 string Metrics::serialize_labels(const vector<pair<string, string>> &labels)
27 {
28         if (labels.empty()) {
29                 return "";
30         }
31
32         string label_str;
33         for (const pair<string, string> &label : labels) {
34                 if (!label_str.empty()) {
35                         label_str += ",";
36                 }
37                 label_str += label.first + "=\"" + label.second + "\"";
38         }
39         return "{" + label_str + "}";
40 }
41
42 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, atomic<int64_t> *location, Metrics::Type type)
43 {
44         Metric metric;
45         metric.data_type = DATA_TYPE_INT64;
46         metric.location_int64 = location;
47
48         lock_guard<mutex> lock(mu);
49         metrics.emplace(MetricKey(name, labels), metric);
50         assert(types.count(name) == 0 || types[name] == type);
51         types[name] = type;
52 }
53
54 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, atomic<double> *location, Metrics::Type type)
55 {
56         Metric metric;
57         metric.data_type = DATA_TYPE_DOUBLE;
58         metric.location_double = location;
59
60         lock_guard<mutex> lock(mu);
61         metrics.emplace(MetricKey(name, labels), metric);
62         assert(types.count(name) == 0 || types[name] == type);
63         types[name] = type;
64 }
65
66 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, Histogram *location, Laziness laziness)
67 {
68         Metric metric;
69         metric.data_type = DATA_TYPE_HISTOGRAM;
70         metric.laziness = laziness;
71         metric.location_histogram = location;
72
73         lock_guard<mutex> lock(mu);
74         metrics.emplace(MetricKey(name, labels), metric);
75         assert(types.count(name) == 0 || types[name] == TYPE_HISTOGRAM);
76         types[name] = TYPE_HISTOGRAM;
77 }
78
79 void Metrics::remove(const string &name, const vector<pair<string, string>> &labels)
80 {
81         lock_guard<mutex> lock(mu);
82
83         auto it = metrics.find(MetricKey(name, labels));
84         assert(it != metrics.end());
85
86         // If this is the last metric with this name, remove the type as well.
87         if (!((it != metrics.begin() && prev(it)->first.name == name) ||
88               (it != metrics.end() && next(it)->first.name == name))) {
89                 types.erase(name);
90         }
91
92         metrics.erase(it);
93 }
94
95 string Metrics::serialize() const
96 {
97         stringstream ss;
98         ss.imbue(locale("C"));
99         ss.precision(20);
100
101         lock_guard<mutex> lock(mu);
102         auto type_it = types.cbegin();
103         for (const auto &key_and_metric : metrics) {
104                 string name = "nageru_" + key_and_metric.first.name + key_and_metric.first.serialized_labels;
105                 const Metric &metric = key_and_metric.second;
106
107                 if (type_it != types.cend() &&
108                     key_and_metric.first.name == type_it->first) {
109                         // It's the first time we print out any metric with this name,
110                         // so add the type header.
111                         if (type_it->second == TYPE_GAUGE) {
112                                 ss << "# TYPE nageru_" << type_it->first << " gauge\n";
113                         } else if (type_it->second == TYPE_HISTOGRAM) {
114                                 ss << "# TYPE nageru_" << type_it->first << " histogram\n";
115                         }
116                         ++type_it;
117                 }
118
119                 if (metric.data_type == DATA_TYPE_INT64) {
120                         ss << name << " " << metric.location_int64->load() << "\n";
121                 } else if (metric.data_type == DATA_TYPE_DOUBLE) {
122                         double val = metric.location_double->load();
123                         if (isnan(val)) {
124                                 // Prometheus can't handle “-nan”.
125                                 ss << name << " NaN\n";
126                         } else {
127                                 ss << name << " " << val << "\n";
128                         }
129                 } else {
130                         ss << metric.location_histogram->serialize(metric.laziness, key_and_metric.first.name, key_and_metric.first.labels);
131                 }
132         }
133
134         return ss.str();
135 }
136
137 void Histogram::init(const vector<double> &bucket_vals)
138 {
139         this->num_buckets = bucket_vals.size();
140         buckets.reset(new Bucket[num_buckets]);
141         for (size_t i = 0; i < num_buckets; ++i) {
142                 buckets[i].val = bucket_vals[i];
143         }
144 }
145
146 void Histogram::init_uniform(size_t num_buckets)
147 {
148         this->num_buckets = num_buckets;
149         buckets.reset(new Bucket[num_buckets]);
150         for (size_t i = 0; i < num_buckets; ++i) {
151                 buckets[i].val = i;
152         }
153 }
154
155 void Histogram::init_geometric(double min, double max, size_t num_buckets)
156 {
157         this->num_buckets = num_buckets;
158         buckets.reset(new Bucket[num_buckets]);
159         for (size_t i = 0; i < num_buckets; ++i) {
160                 buckets[i].val = min * pow(max / min, double(i) / (num_buckets - 1));
161         }
162 }
163
164 void Histogram::count_event(double val)
165 {
166         Bucket ref_bucket;
167         ref_bucket.val = val;
168         auto it = lower_bound(buckets.get(), buckets.get() + num_buckets, ref_bucket,
169                 [](const Bucket &a, const Bucket &b) { return a.val < b.val; });
170         if (it == buckets.get() + num_buckets) {
171                 ++count_after_last_bucket;
172         } else {
173                 ++it->count;
174         }
175         // Non-atomic add, but that's fine, since there are no concurrent writers.
176         sum = sum + val;
177 }
178
179 string Histogram::serialize(Metrics::Laziness laziness, const string &name, const vector<pair<string, string>> &labels) const
180 {
181         // Check if the histogram is empty and should not be serialized.
182         if (laziness == Metrics::PRINT_WHEN_NONEMPTY && count_after_last_bucket.load() == 0) {
183                 bool empty = true;
184                 for (size_t bucket_idx = 0; bucket_idx < num_buckets; ++bucket_idx) {
185                         if (buckets[bucket_idx].count.load() != 0) {
186                                 empty = false;
187                                 break;
188                         }
189                 }
190                 if (empty) {
191                         return "";
192                 }
193         }
194
195         stringstream ss;
196         ss.imbue(locale("C"));
197         ss.precision(20);
198
199         int64_t count = 0;
200         for (size_t bucket_idx = 0; bucket_idx < num_buckets; ++bucket_idx) {
201                 stringstream le_ss;
202                 le_ss.imbue(locale("C"));
203                 le_ss.precision(20);
204                 le_ss << buckets[bucket_idx].val;
205                 vector<pair<string, string>> bucket_labels = labels;
206                 bucket_labels.emplace_back("le", le_ss.str());
207
208                 count += buckets[bucket_idx].count.load();
209                 ss << Metrics::serialize_name(name + "_bucket", bucket_labels) << " " << count << "\n";
210         }
211
212         count += count_after_last_bucket.load();
213
214         ss << Metrics::serialize_name(name + "_sum", labels) << " " << sum.load() << "\n";
215         ss << Metrics::serialize_name(name + "_count", labels) << " " << count << "\n";
216
217         return ss.str();
218 }