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