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 string Metrics::serialize() const
112 ss.imbue(locale("C"));
115 lock_guard<mutex> lock(mu);
116 auto type_it = types.cbegin();
117 for (const auto &key_and_metric : metrics) {
118 string name = prefix + "_" + key_and_metric.first.name + key_and_metric.first.serialized_labels;
119 const Metric &metric = key_and_metric.second;
121 if (type_it != types.cend() &&
122 key_and_metric.first.name == type_it->first) {
123 // It's the first time we print out any metric with this name,
124 // so add the type header.
125 if (type_it->second == TYPE_GAUGE) {
126 ss << "# TYPE " + prefix + "_" << type_it->first << " gauge\n";
127 } else if (type_it->second == TYPE_HISTOGRAM) {
128 ss << "# TYPE " + prefix + "_" << type_it->first << " histogram\n";
129 } else if (type_it->second == TYPE_SUMMARY) {
130 ss << "# TYPE " + prefix + "_" << type_it->first << " summary\n";
135 if (metric.data_type == DATA_TYPE_INT64) {
136 ss << name << " " << metric.location_int64->load() << "\n";
137 } else if (metric.data_type == DATA_TYPE_DOUBLE) {
138 double val = metric.location_double->load();
140 // Prometheus can't handle “-nan”.
141 ss << name << " NaN\n";
143 ss << name << " " << val << "\n";
145 } else if (metric.data_type == DATA_TYPE_HISTOGRAM) {
146 ss << metric.location_histogram->serialize(metric.laziness, key_and_metric.first.name, key_and_metric.first.labels);
148 ss << metric.location_summary->serialize(metric.laziness, key_and_metric.first.name, key_and_metric.first.labels);
155 void Histogram::init(const vector<double> &bucket_vals)
157 this->num_buckets = bucket_vals.size();
158 buckets.reset(new Bucket[num_buckets]);
159 for (size_t i = 0; i < num_buckets; ++i) {
160 buckets[i].val = bucket_vals[i];
164 void Histogram::init_uniform(size_t num_buckets)
166 this->num_buckets = num_buckets;
167 buckets.reset(new Bucket[num_buckets]);
168 for (size_t i = 0; i < num_buckets; ++i) {
173 void Histogram::init_geometric(double min, double max, size_t num_buckets)
175 this->num_buckets = num_buckets;
176 buckets.reset(new Bucket[num_buckets]);
177 for (size_t i = 0; i < num_buckets; ++i) {
178 buckets[i].val = min * pow(max / min, double(i) / (num_buckets - 1));
182 void Histogram::count_event(double val)
185 ref_bucket.val = val;
186 auto it = lower_bound(buckets.get(), buckets.get() + num_buckets, ref_bucket,
187 [](const Bucket &a, const Bucket &b) { return a.val < b.val; });
188 if (it == buckets.get() + num_buckets) {
189 ++count_after_last_bucket;
193 // Non-atomic add, but that's fine, since there are no concurrent writers.
197 string Histogram::serialize(Metrics::Laziness laziness, const string &name, const vector<pair<string, string>> &labels) const
199 // Check if the histogram is empty and should not be serialized.
200 if (laziness == Metrics::PRINT_WHEN_NONEMPTY && count_after_last_bucket.load() == 0) {
202 for (size_t bucket_idx = 0; bucket_idx < num_buckets; ++bucket_idx) {
203 if (buckets[bucket_idx].count.load() != 0) {
214 ss.imbue(locale("C"));
218 for (size_t bucket_idx = 0; bucket_idx < num_buckets; ++bucket_idx) {
220 le_ss.imbue(locale("C"));
222 le_ss << buckets[bucket_idx].val;
223 vector<pair<string, string>> bucket_labels = labels;
224 bucket_labels.emplace_back("le", le_ss.str());
226 count += buckets[bucket_idx].count.load();
227 ss << Metrics::serialize_name(name + "_bucket", bucket_labels) << " " << count << "\n";
230 count += count_after_last_bucket.load();
232 ss << Metrics::serialize_name(name + "_sum", labels) << " " << sum.load() << "\n";
233 ss << Metrics::serialize_name(name + "_count", labels) << " " << count << "\n";
238 void Summary::init(const vector<double> &quantiles, double window_seconds)
240 this->quantiles = quantiles;
241 window = duration<double>(window_seconds);
244 void Summary::count_event(double val)
246 steady_clock::time_point now = steady_clock::now();
247 steady_clock::time_point cutoff = now - duration_cast<steady_clock::duration>(window);
249 lock_guard<mutex> lock(mu);
250 values.emplace_back(now, val);
251 while (!values.empty() && values.front().first < cutoff) {
255 // Non-atomic add, but that's fine, since there are no concurrent writers.
260 string Summary::serialize(Metrics::Laziness laziness, const string &name, const vector<pair<string, string>> &labels)
262 steady_clock::time_point now = steady_clock::now();
263 steady_clock::time_point cutoff = now - duration_cast<steady_clock::duration>(window);
265 vector<double> values_copy;
267 lock_guard<mutex> lock(mu);
268 while (!values.empty() && values.front().first < cutoff) {
271 values_copy.reserve(values.size());
272 for (const auto &time_and_value : values) {
273 values_copy.push_back(time_and_value.second);
277 vector<pair<double, double>> answers;
278 if (values_copy.size() == 0) {
279 if (laziness == Metrics::PRINT_WHEN_NONEMPTY) {
282 for (double quantile : quantiles) {
283 answers.emplace_back(quantile, 0.0 / 0.0);
285 } else if (values_copy.size() == 1) {
286 for (double quantile : quantiles) {
287 answers.emplace_back(quantile, values_copy[0]);
290 // We could probably do repeated nth_element, but the constant factor
291 // gets a bit high, so just sorting probably is about as fast.
292 sort(values_copy.begin(), values_copy.end());
293 for (double quantile : quantiles) {
294 double idx = quantile * (values_copy.size() - 1);
295 size_t idx_floor = size_t(floor(idx));
296 const double v0 = values_copy[idx_floor];
298 if (idx_floor == values_copy.size() - 1) {
299 answers.emplace_back(quantile, values_copy[idx_floor]);
301 // Linear interpolation.
302 double t = idx - idx_floor;
303 const double v1 = values_copy[idx_floor + 1];
304 answers.emplace_back(quantile, v0 + t * (v1 - v0));
310 ss.imbue(locale("C"));
313 for (const auto &quantile_and_value : answers) {
314 stringstream quantile_ss;
315 quantile_ss.imbue(locale("C"));
316 quantile_ss.precision(3);
317 quantile_ss << quantile_and_value.first;
318 vector<pair<string, string>> quantile_labels = labels;
319 quantile_labels.emplace_back("quantile", quantile_ss.str());
321 double val = quantile_and_value.second;;
323 // Prometheus can't handle “-nan”.
324 ss << Metrics::serialize_name(name, quantile_labels) << " NaN\n";
326 ss << Metrics::serialize_name(name, quantile_labels) << " " << val << "\n";
330 ss << Metrics::serialize_name(name + "_sum", labels) << " " << sum.load() << "\n";
331 ss << Metrics::serialize_name(name + "_count", labels) << " " << count.load() << "\n";