1 #include "shared/metrics.h"
12 using namespace std::chrono;
14 Metrics global_metrics;
15 string Metrics::prefix = "nageru";
17 double get_timestamp_for_metrics()
19 return duration<double>(system_clock::now().time_since_epoch()).count();
22 string Metrics::serialize_name(const string &name, const vector<pair<string, string>> &labels)
24 return prefix + "_" + name + serialize_labels(labels);
27 string Metrics::serialize_labels(const vector<pair<string, string>> &labels)
34 for (const pair<string, string> &label : labels) {
35 if (!label_str.empty()) {
38 label_str += label.first + "=\"" + label.second + "\"";
40 return "{" + label_str + "}";
43 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, atomic<int64_t> *location, Metrics::Type type)
46 metric.data_type = DATA_TYPE_INT64;
47 metric.location_int64 = location;
49 lock_guard<mutex> lock(mu);
50 metrics.emplace(MetricKey(name, labels), metric);
51 assert(types.count(name) == 0 || types[name] == type);
55 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, atomic<double> *location, Metrics::Type type)
58 metric.data_type = DATA_TYPE_DOUBLE;
59 metric.location_double = location;
61 lock_guard<mutex> lock(mu);
62 metrics.emplace(MetricKey(name, labels), metric);
63 assert(types.count(name) == 0 || types[name] == type);
67 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, Histogram *location, Laziness laziness)
70 metric.data_type = DATA_TYPE_HISTOGRAM;
71 metric.laziness = laziness;
72 metric.location_histogram = location;
74 lock_guard<mutex> lock(mu);
75 metrics.emplace(MetricKey(name, labels), metric);
76 assert(types.count(name) == 0 || types[name] == TYPE_HISTOGRAM);
77 types[name] = TYPE_HISTOGRAM;
80 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, Summary *location, Laziness laziness)
83 metric.data_type = DATA_TYPE_SUMMARY;
84 metric.laziness = laziness;
85 metric.location_summary = location;
87 lock_guard<mutex> lock(mu);
88 metrics.emplace(MetricKey(name, labels), metric);
89 assert(types.count(name) == 0 || types[name] == TYPE_SUMMARY);
90 types[name] = TYPE_SUMMARY;
93 void Metrics::remove(const string &name, const vector<pair<string, string>> &labels)
95 lock_guard<mutex> lock(mu);
97 auto it = metrics.find(MetricKey(name, labels));
98 assert(it != metrics.end());
100 // If this is the last metric with this name, remove the type as well.
101 if (!((it != metrics.begin() && prev(it)->first.name == name) ||
102 (it != metrics.end() && next(it) != metrics.end() && next(it)->first.name == name))) {
109 void Metrics::remove_if_exists(const string &name, const vector<pair<string, string>> &labels)
111 lock_guard<mutex> lock(mu);
112 auto it = metrics.find(MetricKey(name, labels));
113 if (it == metrics.end()) {
117 // If this is the last metric with this name, remove the type as well.
118 if (!((it != metrics.begin() && prev(it)->first.name == name) ||
119 (it != metrics.end() && next(it) != metrics.end() && next(it)->first.name == name))) {
126 string Metrics::serialize() const
129 ss.imbue(locale("C"));
132 lock_guard<mutex> lock(mu);
133 auto type_it = types.cbegin();
134 for (const auto &key_and_metric : metrics) {
135 string name = prefix + "_" + key_and_metric.first.name + key_and_metric.first.serialized_labels;
136 const Metric &metric = key_and_metric.second;
138 if (type_it != types.cend() &&
139 key_and_metric.first.name == type_it->first) {
140 // It's the first time we print out any metric with this name,
141 // so add the type header.
142 if (type_it->second == TYPE_GAUGE) {
143 ss << "# TYPE " + prefix + "_" << type_it->first << " gauge\n";
144 } else if (type_it->second == TYPE_HISTOGRAM) {
145 ss << "# TYPE " + prefix + "_" << type_it->first << " histogram\n";
146 } else if (type_it->second == TYPE_SUMMARY) {
147 ss << "# TYPE " + prefix + "_" << type_it->first << " summary\n";
152 if (metric.data_type == DATA_TYPE_INT64) {
153 ss << name << " " << metric.location_int64->load() << "\n";
154 } else if (metric.data_type == DATA_TYPE_DOUBLE) {
155 double val = metric.location_double->load();
157 // Prometheus can't handle “-nan”.
158 ss << name << " NaN\n";
160 ss << name << " " << val << "\n";
162 } else if (metric.data_type == DATA_TYPE_HISTOGRAM) {
163 ss << metric.location_histogram->serialize(metric.laziness, key_and_metric.first.name, key_and_metric.first.labels);
165 ss << metric.location_summary->serialize(metric.laziness, key_and_metric.first.name, key_and_metric.first.labels);
172 void Histogram::init(const vector<double> &bucket_vals)
174 this->num_buckets = bucket_vals.size();
175 buckets.reset(new Bucket[num_buckets]);
176 for (size_t i = 0; i < num_buckets; ++i) {
177 buckets[i].val = bucket_vals[i];
181 void Histogram::init_uniform(size_t num_buckets)
183 this->num_buckets = num_buckets;
184 buckets.reset(new Bucket[num_buckets]);
185 for (size_t i = 0; i < num_buckets; ++i) {
190 void Histogram::init_geometric(double min, double max, size_t num_buckets)
192 this->num_buckets = num_buckets;
193 buckets.reset(new Bucket[num_buckets]);
194 for (size_t i = 0; i < num_buckets; ++i) {
195 buckets[i].val = min * pow(max / min, double(i) / (num_buckets - 1));
199 void Histogram::count_event(double val)
202 ref_bucket.val = val;
203 auto it = lower_bound(buckets.get(), buckets.get() + num_buckets, ref_bucket,
204 [](const Bucket &a, const Bucket &b) { return a.val < b.val; });
205 if (it == buckets.get() + num_buckets) {
206 ++count_after_last_bucket;
210 // Non-atomic add, but that's fine, since there are no concurrent writers.
214 string Histogram::serialize(Metrics::Laziness laziness, const string &name, const vector<pair<string, string>> &labels) const
216 // Check if the histogram is empty and should not be serialized.
217 if (laziness == Metrics::PRINT_WHEN_NONEMPTY && count_after_last_bucket.load() == 0) {
219 for (size_t bucket_idx = 0; bucket_idx < num_buckets; ++bucket_idx) {
220 if (buckets[bucket_idx].count.load() != 0) {
231 ss.imbue(locale("C"));
235 for (size_t bucket_idx = 0; bucket_idx < num_buckets; ++bucket_idx) {
237 le_ss.imbue(locale("C"));
239 le_ss << buckets[bucket_idx].val;
240 vector<pair<string, string>> bucket_labels = labels;
241 bucket_labels.emplace_back("le", le_ss.str());
243 count += buckets[bucket_idx].count.load();
244 ss << Metrics::serialize_name(name + "_bucket", bucket_labels) << " " << count << "\n";
247 count += count_after_last_bucket.load();
249 ss << Metrics::serialize_name(name + "_sum", labels) << " " << sum.load() << "\n";
250 ss << Metrics::serialize_name(name + "_count", labels) << " " << count << "\n";
255 void Summary::init(const vector<double> &quantiles, double window_seconds)
257 this->quantiles = quantiles;
258 window = duration<double>(window_seconds);
261 void Summary::count_event(double val)
263 steady_clock::time_point now = steady_clock::now();
264 steady_clock::time_point cutoff = now - duration_cast<steady_clock::duration>(window);
266 lock_guard<mutex> lock(mu);
267 values.emplace_back(now, val);
268 while (!values.empty() && values.front().first < cutoff) {
272 // Non-atomic add, but that's fine, since there are no concurrent writers.
277 string Summary::serialize(Metrics::Laziness laziness, const string &name, const vector<pair<string, string>> &labels)
279 steady_clock::time_point now = steady_clock::now();
280 steady_clock::time_point cutoff = now - duration_cast<steady_clock::duration>(window);
282 vector<double> values_copy;
284 lock_guard<mutex> lock(mu);
285 while (!values.empty() && values.front().first < cutoff) {
288 values_copy.reserve(values.size());
289 for (const auto &time_and_value : values) {
290 values_copy.push_back(time_and_value.second);
294 vector<pair<double, double>> answers;
295 if (values_copy.size() == 0) {
296 if (laziness == Metrics::PRINT_WHEN_NONEMPTY) {
299 for (double quantile : quantiles) {
300 answers.emplace_back(quantile, 0.0 / 0.0);
302 } else if (values_copy.size() == 1) {
303 for (double quantile : quantiles) {
304 answers.emplace_back(quantile, values_copy[0]);
307 // We could probably do repeated nth_element, but the constant factor
308 // gets a bit high, so just sorting probably is about as fast.
309 sort(values_copy.begin(), values_copy.end());
310 for (double quantile : quantiles) {
311 double idx = quantile * (values_copy.size() - 1);
312 size_t idx_floor = size_t(floor(idx));
313 const double v0 = values_copy[idx_floor];
315 if (idx_floor == values_copy.size() - 1) {
316 answers.emplace_back(quantile, values_copy[idx_floor]);
318 // Linear interpolation.
319 double t = idx - idx_floor;
320 const double v1 = values_copy[idx_floor + 1];
321 answers.emplace_back(quantile, v0 + t * (v1 - v0));
327 ss.imbue(locale("C"));
330 for (const auto &quantile_and_value : answers) {
331 stringstream quantile_ss;
332 quantile_ss.imbue(locale("C"));
333 quantile_ss.precision(3);
334 quantile_ss << quantile_and_value.first;
335 vector<pair<string, string>> quantile_labels = labels;
336 quantile_labels.emplace_back("quantile", quantile_ss.str());
338 double val = quantile_and_value.second;;
340 // Prometheus can't handle “-nan”.
341 ss << Metrics::serialize_name(name, quantile_labels) << " NaN\n";
343 ss << Metrics::serialize_name(name, quantile_labels) << " " << val << "\n";
347 ss << Metrics::serialize_name(name + "_sum", labels) << " " << sum.load() << "\n";
348 ss << Metrics::serialize_name(name + "_count", labels) << " " << count.load() << "\n";