X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=metrics.cpp;h=3fadb72907d9afdc5e2574269c57badd539eba6f;hb=1836dccf699779d9092a75755cec96cea1734a2a;hp=a60eb6c6a25d1dec85eb79b2f11aa0dfb1f8f84d;hpb=cc48fb94d8215eaf529825f76c18e8332352175d;p=nageru diff --git a/metrics.cpp b/metrics.cpp index a60eb6c..3fadb72 100644 --- a/metrics.cpp +++ b/metrics.cpp @@ -1,24 +1,52 @@ #include "metrics.h" #include +#include +#include +#include #include #include using namespace std; +using namespace std::chrono; Metrics global_metrics; +double get_timestamp_for_metrics() +{ + return duration(system_clock::now().time_since_epoch()).count(); +} + +string Metrics::serialize_name(const string &name, const vector> &labels) +{ + return "nageru_" + name + serialize_labels(labels); +} + +string Metrics::serialize_labels(const vector> &labels) +{ + if (labels.empty()) { + return ""; + } + + string label_str; + for (const pair &label : labels) { + if (!label_str.empty()) { + label_str += ","; + } + label_str += label.first + "=\"" + label.second + "\""; + } + return "{" + label_str + "}"; +} + void Metrics::add(const string &name, const vector> &labels, atomic *location, Metrics::Type type) { Metric metric; metric.data_type = DATA_TYPE_INT64; - metric.name = name; - metric.labels = labels; metric.location_int64 = location; lock_guard lock(mu); - metrics.push_back(metric); + metrics.emplace(MetricKey(name, labels), metric); assert(types.count(name) == 0 || types[name] == type); types[name] = type; } @@ -27,16 +55,26 @@ void Metrics::add(const string &name, const vector> &labels { Metric metric; metric.data_type = DATA_TYPE_DOUBLE; - metric.name = name; - metric.labels = labels; metric.location_double = location; lock_guard lock(mu); - metrics.push_back(metric); + metrics.emplace(MetricKey(name, labels), metric); assert(types.count(name) == 0 || types[name] == type); types[name] = type; } +void Metrics::add(const string &name, const vector> &labels, Histogram *location) +{ + Metric metric; + metric.data_type = DATA_TYPE_HISTOGRAM; + metric.location_histogram = location; + + lock_guard lock(mu); + metrics.emplace(MetricKey(name, labels), metric); + assert(types.count(name) == 0 || types[name] == TYPE_HISTOGRAM); + types[name] = TYPE_HISTOGRAM; +} + string Metrics::serialize() const { stringstream ss; @@ -44,34 +82,100 @@ string Metrics::serialize() const ss.precision(20); lock_guard lock(mu); - for (const auto &name_and_type : types) { - if (name_and_type.second == TYPE_GAUGE) { - ss << "# TYPE nageru_" << name_and_type.first << " gauge\n"; - } - } - for (const Metric &metric : metrics) { - string name; - if (metric.labels.empty()) { - name = "nageru_" + metric.name; - } else { - name = "nageru_" + metric.name + "{"; - bool first = true; - for (const pair &label : metric.labels) { - if (!first) { - name += ","; - } - first = false; - name += label.first + "=\"" + label.second + "\""; + auto type_it = types.cbegin(); + for (const auto &key_and_metric : metrics) { + string name = "nageru_" + key_and_metric.first.name + key_and_metric.first.serialized_labels; + const Metric &metric = key_and_metric.second; + + if (type_it != types.cend() && + key_and_metric.first.name == type_it->first) { + // It's the first time we print out any metric with this name, + // so add the type header. + if (type_it->second == TYPE_GAUGE) { + ss << "# TYPE nageru_" << type_it->first << " gauge\n"; + } else if (type_it->second == TYPE_HISTOGRAM) { + ss << "# TYPE nageru_" << type_it->first << " histogram\n"; } - name += "}"; + ++type_it; } if (metric.data_type == DATA_TYPE_INT64) { ss << name << " " << metric.location_int64->load() << "\n"; - } else { + } else if (metric.data_type == DATA_TYPE_DOUBLE) { ss << name << " " << metric.location_double->load() << "\n"; + } else { + ss << metric.location_histogram->serialize(key_and_metric.first.name, key_and_metric.first.labels); } } return ss.str(); } + +void Histogram::init(const vector &bucket_vals) +{ + this->num_buckets = bucket_vals.size(); + buckets.reset(new Bucket[num_buckets]); + for (size_t i = 0; i < num_buckets; ++i) { + buckets[i].val = bucket_vals[i]; + } +} + +void Histogram::init_uniform(size_t num_buckets) +{ + this->num_buckets = num_buckets; + buckets.reset(new Bucket[num_buckets]); + for (size_t i = 0; i < num_buckets; ++i) { + buckets[i].val = i; + } +} + +void Histogram::init_geometric(double min, double max, size_t num_buckets) +{ + this->num_buckets = num_buckets; + buckets.reset(new Bucket[num_buckets]); + for (size_t i = 0; i < num_buckets; ++i) { + buckets[i].val = min * pow(max / min, double(i) / (num_buckets - 1)); + } +} + +void Histogram::count_event(double val) +{ + Bucket ref_bucket; + ref_bucket.val = val; + auto it = lower_bound(buckets.get(), buckets.get() + num_buckets, ref_bucket, + [](const Bucket &a, const Bucket &b) { return a.val < b.val; }); + if (it == buckets.get() + num_buckets) { + ++count_after_last_bucket; + } else { + ++it->count; + } + // Non-atomic add, but that's fine, since there are no concurrent writers. + sum = sum + val; +} + +string Histogram::serialize(const string &name, const vector> &labels) const +{ + stringstream ss; + ss.imbue(locale("C")); + ss.precision(20); + + int64_t count = 0; + for (size_t bucket_idx = 0; bucket_idx < num_buckets; ++bucket_idx) { + stringstream le_ss; + le_ss.imbue(locale("C")); + le_ss.precision(20); + le_ss << buckets[bucket_idx].val; + vector> bucket_labels = labels; + bucket_labels.emplace_back("le", le_ss.str()); + + count += buckets[bucket_idx].count.load(); + ss << Metrics::serialize_name(name + "_bucket", bucket_labels) << " " << count << "\n"; + } + + count += count_after_last_bucket.load(); + + ss << Metrics::serialize_name(name + "_sum", labels) << " " << sum.load() << "\n"; + ss << Metrics::serialize_name(name + "_count", labels) << " " << count << "\n"; + + return ss.str(); +}