]> git.sesse.net Git - nageru/blob - metrics.cpp
Store the metrics sorted.
[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         if (labels.empty()) {
24                 return "nageru_" + name;
25         }
26
27         string label_str;
28         for (const pair<string, string> &label : labels) {
29                 if (!label_str.empty()) {
30                         label_str += ",";
31                 }
32                 label_str += label.first + "=\"" + label.second + "\"";
33         }
34         return "nageru_" + name + "{" + label_str + "}";
35 }
36
37 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, atomic<int64_t> *location, Metrics::Type type)
38 {
39         Metric metric;
40         metric.data_type = DATA_TYPE_INT64;
41         metric.location_int64 = location;
42
43         lock_guard<mutex> lock(mu);
44         metrics.emplace(MetricKey(name, labels), metric);
45         assert(types.count(name) == 0 || types[name] == type);
46         types[name] = type;
47 }
48
49 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, atomic<double> *location, Metrics::Type type)
50 {
51         Metric metric;
52         metric.data_type = DATA_TYPE_DOUBLE;
53         metric.location_double = location;
54
55         lock_guard<mutex> lock(mu);
56         metrics.emplace(MetricKey(name, labels), metric);
57         assert(types.count(name) == 0 || types[name] == type);
58         types[name] = type;
59 }
60
61 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, Histogram *location)
62 {
63         Metric metric;
64         metric.data_type = DATA_TYPE_HISTOGRAM;
65         metric.location_histogram = location;
66
67         lock_guard<mutex> lock(mu);
68         metrics.emplace(MetricKey(name, labels), metric);
69         assert(types.count(name) == 0 || types[name] == TYPE_HISTOGRAM);
70         types[name] = TYPE_HISTOGRAM;
71 }
72
73 string Metrics::serialize() const
74 {
75         stringstream ss;
76         ss.imbue(locale("C"));
77         ss.precision(20);
78
79         lock_guard<mutex> lock(mu);
80         auto type_it = types.cbegin();
81         for (const auto &key_and_metric : metrics) {
82                 const string &name = key_and_metric.first.serialized;
83                 const Metric &metric = key_and_metric.second;
84
85                 if (type_it != types.cend() &&
86                     key_and_metric.first.name == type_it->first) {
87                         // It's the first time we print out any metric with this name,
88                         // so add the type header.
89                         if (type_it->second == TYPE_GAUGE) {
90                                 ss << "# TYPE nageru_" << type_it->first << " gauge\n";
91                         } else if (type_it->second == TYPE_HISTOGRAM) {
92                                 ss << "# TYPE nageru_" << type_it->first << " histogram\n";
93                         }
94                         ++type_it;
95                 }
96
97                 if (metric.data_type == DATA_TYPE_INT64) {
98                         ss << name << " " << metric.location_int64->load() << "\n";
99                 } else if (metric.data_type == DATA_TYPE_DOUBLE) {
100                         ss << name << " " << metric.location_double->load() << "\n";
101                 } else {
102                         ss << metric.location_histogram->serialize(key_and_metric.first.name, key_and_metric.first.labels);
103                 }
104         }
105
106         return ss.str();
107 }
108
109 void Histogram::init(const vector<double> &bucket_vals)
110 {
111         this->num_buckets = bucket_vals.size();
112         buckets.reset(new Bucket[num_buckets]);
113         for (size_t i = 0; i < num_buckets; ++i) {
114                 buckets[i].val = bucket_vals[i];
115         }
116 }
117
118 void Histogram::init_uniform(size_t num_buckets)
119 {
120         this->num_buckets = num_buckets;
121         buckets.reset(new Bucket[num_buckets]);
122         for (size_t i = 0; i < num_buckets; ++i) {
123                 buckets[i].val = i;
124         }
125 }
126
127 void Histogram::init_geometric(double min, double max, size_t num_buckets)
128 {
129         this->num_buckets = num_buckets;
130         buckets.reset(new Bucket[num_buckets]);
131         for (size_t i = 0; i < num_buckets; ++i) {
132                 buckets[i].val = min * pow(max / min, double(i) / (num_buckets - 1));
133         }
134 }
135
136 void Histogram::count_event(double val)
137 {
138         Bucket ref_bucket;
139         ref_bucket.val = val;
140         auto it = lower_bound(buckets.get(), buckets.get() + num_buckets, ref_bucket,
141                 [](const Bucket &a, const Bucket &b) { return a.val < b.val; });
142         if (it == buckets.get() + num_buckets) {
143                 ++count_after_last_bucket;
144         } else {
145                 ++it->count;
146         }
147         // Non-atomic add, but that's fine, since there are no concurrent writers.
148         sum = sum + val;
149 }
150
151 string Histogram::serialize(const string &name, const vector<pair<string, string>> &labels) const
152 {
153         stringstream ss;
154         ss.imbue(locale("C"));
155         ss.precision(20);
156
157         int64_t count = 0;
158         for (size_t bucket_idx = 0; bucket_idx < num_buckets; ++bucket_idx) {
159                 stringstream le_ss;
160                 le_ss.imbue(locale("C"));
161                 le_ss.precision(20);
162                 le_ss << buckets[bucket_idx].val;
163                 vector<pair<string, string>> bucket_labels = labels;
164                 bucket_labels.emplace_back("le", le_ss.str());
165
166                 count += buckets[bucket_idx].count.load();
167                 ss << Metrics::serialize_name(name + "_bucket", bucket_labels) << " " << count << "\n";
168         }
169
170         count += count_after_last_bucket.load();
171
172         ss << Metrics::serialize_name(name + "_sum", labels) << " " << sum.load() << "\n";
173         ss << Metrics::serialize_name(name + "_count", labels) << " " << count << "\n";
174
175         return ss.str();
176 }