12 using namespace std::chrono;
14 Metrics global_metrics;
16 double get_timestamp_for_metrics()
18 return duration<double>(system_clock::now().time_since_epoch()).count();
21 string Metrics::serialize_name(const string &name, const vector<pair<string, string>> &labels)
23 return "nageru_" + name + serialize_labels(labels);
26 string Metrics::serialize_labels(const vector<pair<string, string>> &labels)
33 for (const pair<string, string> &label : labels) {
34 if (!label_str.empty()) {
37 label_str += label.first + "=\"" + label.second + "\"";
39 return "{" + label_str + "}";
42 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, atomic<int64_t> *location, Metrics::Type type)
45 metric.data_type = DATA_TYPE_INT64;
46 metric.location_int64 = location;
48 lock_guard<mutex> lock(mu);
49 metrics.emplace(MetricKey(name, labels), metric);
50 assert(types.count(name) == 0 || types[name] == type);
54 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, atomic<double> *location, Metrics::Type type)
57 metric.data_type = DATA_TYPE_DOUBLE;
58 metric.location_double = location;
60 lock_guard<mutex> lock(mu);
61 metrics.emplace(MetricKey(name, labels), metric);
62 assert(types.count(name) == 0 || types[name] == type);
66 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, Histogram *location, Laziness laziness)
69 metric.data_type = DATA_TYPE_HISTOGRAM;
70 metric.laziness = laziness;
71 metric.location_histogram = location;
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;
79 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, Summary *location, Laziness laziness)
82 metric.data_type = DATA_TYPE_SUMMARY;
83 metric.laziness = laziness;
84 metric.location_summary = location;
86 lock_guard<mutex> lock(mu);
87 metrics.emplace(MetricKey(name, labels), metric);
88 assert(types.count(name) == 0 || types[name] == TYPE_SUMMARY);
89 types[name] = TYPE_SUMMARY;
92 void Metrics::remove(const string &name, const vector<pair<string, string>> &labels)
94 lock_guard<mutex> lock(mu);
96 auto it = metrics.find(MetricKey(name, labels));
97 assert(it != metrics.end());
99 // If this is the last metric with this name, remove the type as well.
100 if (!((it != metrics.begin() && prev(it)->first.name == name) ||
101 (it != metrics.end() && next(it)->first.name == name))) {
108 string Metrics::serialize() const
111 ss.imbue(locale("C"));
114 lock_guard<mutex> lock(mu);
115 auto type_it = types.cbegin();
116 for (const auto &key_and_metric : metrics) {
117 string name = "nageru_" + key_and_metric.first.name + key_and_metric.first.serialized_labels;
118 const Metric &metric = key_and_metric.second;
120 if (type_it != types.cend() &&
121 key_and_metric.first.name == type_it->first) {
122 // It's the first time we print out any metric with this name,
123 // so add the type header.
124 if (type_it->second == TYPE_GAUGE) {
125 ss << "# TYPE nageru_" << type_it->first << " gauge\n";
126 } else if (type_it->second == TYPE_HISTOGRAM) {
127 ss << "# TYPE nageru_" << type_it->first << " histogram\n";
128 } else if (type_it->second == TYPE_SUMMARY) {
129 ss << "# TYPE nageru_" << type_it->first << " summary\n";
134 if (metric.data_type == DATA_TYPE_INT64) {
135 ss << name << " " << metric.location_int64->load() << "\n";
136 } else if (metric.data_type == DATA_TYPE_DOUBLE) {
137 double val = metric.location_double->load();
139 // Prometheus can't handle “-nan”.
140 ss << name << " NaN\n";
142 ss << name << " " << val << "\n";
144 } else if (metric.data_type == DATA_TYPE_HISTOGRAM) {
145 ss << metric.location_histogram->serialize(metric.laziness, key_and_metric.first.name, key_and_metric.first.labels);
147 ss << metric.location_summary->serialize(metric.laziness, key_and_metric.first.name, key_and_metric.first.labels);
154 void Histogram::init(const vector<double> &bucket_vals)
156 this->num_buckets = bucket_vals.size();
157 buckets.reset(new Bucket[num_buckets]);
158 for (size_t i = 0; i < num_buckets; ++i) {
159 buckets[i].val = bucket_vals[i];
163 void Histogram::init_uniform(size_t num_buckets)
165 this->num_buckets = num_buckets;
166 buckets.reset(new Bucket[num_buckets]);
167 for (size_t i = 0; i < num_buckets; ++i) {
172 void Histogram::init_geometric(double min, double max, size_t num_buckets)
174 this->num_buckets = num_buckets;
175 buckets.reset(new Bucket[num_buckets]);
176 for (size_t i = 0; i < num_buckets; ++i) {
177 buckets[i].val = min * pow(max / min, double(i) / (num_buckets - 1));
181 void Histogram::count_event(double val)
184 ref_bucket.val = val;
185 auto it = lower_bound(buckets.get(), buckets.get() + num_buckets, ref_bucket,
186 [](const Bucket &a, const Bucket &b) { return a.val < b.val; });
187 if (it == buckets.get() + num_buckets) {
188 ++count_after_last_bucket;
192 // Non-atomic add, but that's fine, since there are no concurrent writers.
196 string Histogram::serialize(Metrics::Laziness laziness, const string &name, const vector<pair<string, string>> &labels) const
198 // Check if the histogram is empty and should not be serialized.
199 if (laziness == Metrics::PRINT_WHEN_NONEMPTY && count_after_last_bucket.load() == 0) {
201 for (size_t bucket_idx = 0; bucket_idx < num_buckets; ++bucket_idx) {
202 if (buckets[bucket_idx].count.load() != 0) {
213 ss.imbue(locale("C"));
217 for (size_t bucket_idx = 0; bucket_idx < num_buckets; ++bucket_idx) {
219 le_ss.imbue(locale("C"));
221 le_ss << buckets[bucket_idx].val;
222 vector<pair<string, string>> bucket_labels = labels;
223 bucket_labels.emplace_back("le", le_ss.str());
225 count += buckets[bucket_idx].count.load();
226 ss << Metrics::serialize_name(name + "_bucket", bucket_labels) << " " << count << "\n";
229 count += count_after_last_bucket.load();
231 ss << Metrics::serialize_name(name + "_sum", labels) << " " << sum.load() << "\n";
232 ss << Metrics::serialize_name(name + "_count", labels) << " " << count << "\n";
237 void Summary::init(const vector<double> &quantiles, double window_seconds)
239 this->quantiles = quantiles;
240 window = duration<double>(window_seconds);
243 void Summary::count_event(double val)
245 steady_clock::time_point now = steady_clock::now();
246 steady_clock::time_point cutoff = now - duration_cast<steady_clock::duration>(window);
248 lock_guard<mutex> lock(mu);
249 values.emplace_back(now, val);
250 while (!values.empty() && values.front().first < cutoff) {
254 // Non-atomic add, but that's fine, since there are no concurrent writers.
259 string Summary::serialize(Metrics::Laziness laziness, const string &name, const vector<pair<string, string>> &labels)
261 steady_clock::time_point now = steady_clock::now();
262 steady_clock::time_point cutoff = now - duration_cast<steady_clock::duration>(window);
264 vector<double> values_copy;
266 lock_guard<mutex> lock(mu);
267 while (!values.empty() && values.front().first < cutoff) {
270 values_copy.reserve(values.size());
271 for (const auto &time_and_value : values) {
272 values_copy.push_back(time_and_value.second);
276 vector<pair<double, double>> answers;
277 if (values_copy.size() == 0) {
278 if (laziness == Metrics::PRINT_WHEN_NONEMPTY) {
281 for (double quantile : quantiles) {
282 answers.emplace_back(quantile, 0.0 / 0.0);
284 } else if (values_copy.size() == 1) {
285 for (double quantile : quantiles) {
286 answers.emplace_back(quantile, values_copy[0]);
289 // We could probably do repeated nth_element, but the constant factor
290 // gets a bit high, so just sorting probably is about as fast.
291 sort(values_copy.begin(), values_copy.end());
292 for (double quantile : quantiles) {
293 double idx = quantile * (values_copy.size() - 1);
294 size_t idx_floor = size_t(floor(idx));
295 const double v0 = values_copy[idx_floor];
297 if (idx_floor == values_copy.size() - 1) {
298 answers.emplace_back(quantile, values_copy[idx_floor]);
300 // Linear interpolation.
301 double t = idx - idx_floor;
302 const double v1 = values_copy[idx_floor + 1];
303 answers.emplace_back(quantile, v0 + t * (v1 - v0));
309 ss.imbue(locale("C"));
312 for (const auto &quantile_and_value : answers) {
313 stringstream quantile_ss;
314 quantile_ss.imbue(locale("C"));
315 quantile_ss.precision(3);
316 quantile_ss << quantile_and_value.first;
317 vector<pair<string, string>> quantile_labels = labels;
318 quantile_labels.emplace_back("quantile", quantile_ss.str());
320 ss << Metrics::serialize_name(name, quantile_labels) << " " << quantile_and_value.second << "\n";
323 ss << Metrics::serialize_name(name + "_sum", labels) << " " << sum.load() << "\n";
324 ss << Metrics::serialize_name(name + "_count", labels) << " " << count.load() << "\n";