From cd10723b1a9e9f7326187211a3117807db48d78c Mon Sep 17 00:00:00 2001
From: qicosmos
Date: Fri, 10 May 2024 16:27:01 +0800
Subject: [PATCH 01/20] add metric
---
include/ylt/metric/counter.hpp | 131 +++++++++++++
include/ylt/metric/detail/ckms_quantiles.hpp | 174 +++++++++++++++++
.../metric/detail/time_window_quantiles.hpp | 52 +++++
include/ylt/metric/guage.hpp | 36 ++++
include/ylt/metric/histogram.hpp | 123 ++++++++++++
include/ylt/metric/metric.hpp | 123 ++++++++++++
include/ylt/metric/summary.hpp | 58 ++++++
src/metric/tests/CMakeLists.txt | 10 +
src/metric/tests/test_metric.cpp | 178 ++++++++++++++++++
9 files changed, 885 insertions(+)
create mode 100644 include/ylt/metric/counter.hpp
create mode 100644 include/ylt/metric/detail/ckms_quantiles.hpp
create mode 100644 include/ylt/metric/detail/time_window_quantiles.hpp
create mode 100644 include/ylt/metric/guage.hpp
create mode 100644 include/ylt/metric/histogram.hpp
create mode 100644 include/ylt/metric/metric.hpp
create mode 100644 include/ylt/metric/summary.hpp
create mode 100644 src/metric/tests/CMakeLists.txt
create mode 100644 src/metric/tests/test_metric.cpp
diff --git a/include/ylt/metric/counter.hpp b/include/ylt/metric/counter.hpp
new file mode 100644
index 000000000..402e01795
--- /dev/null
+++ b/include/ylt/metric/counter.hpp
@@ -0,0 +1,131 @@
+#pragma once
+#include "metric.hpp"
+
+namespace ylt {
+class counter_t : public metric_t {
+ public:
+ counter_t() = default;
+ counter_t(std::string name, std::string help,
+ std::vector labels_name = {})
+ : metric_t(MetricType::Counter, std::move(name), std::move(help),
+ std::move(labels_name)) {}
+
+ counter_t(const char *name, const char *help,
+ std::vector labels_name = {})
+ : counter_t(
+ std::string(name), std::string(help),
+ std::vector(labels_name.begin(), labels_name.end())) {}
+
+ void inc() {
+ std::lock_guard guard(mtx_);
+ set_value(value_map_[{}], 1, op_type_t::INC);
+ }
+
+ void inc(const std::vector &labels_value, double value = 1) {
+ if (value == 0) {
+ return;
+ }
+ validate(labels_value, value);
+ std::lock_guard guard(mtx_);
+ set_value(value_map_[labels_value], value, op_type_t::INC);
+ }
+
+ void update(const std::vector &labels_value, double value) {
+ if (labels_name_.size() != labels_value.size()) {
+ throw std::invalid_argument(
+ "the number of labels_value name and labels_value is not match");
+ }
+ std::lock_guard guard(mtx_);
+ set_value(value_map_[labels_value], value, op_type_t::SET);
+ }
+
+ void reset() {
+ std::lock_guard guard(mtx_);
+ for (auto &pair : value_map_) {
+ pair.second = {};
+ }
+ }
+
+ std::map, sample_t,
+ std::less>>
+ values(bool need_lock = true) override {
+ if (need_lock) {
+ return value_map_;
+ }
+ std::lock_guard guard(mtx_);
+ return value_map_;
+ }
+
+ void serialize(std::string &str) override {
+ if (value_map_.empty()) {
+ return;
+ }
+ str.append("# HELP ").append(name_).append(" ").append(help_).append("\n");
+ str.append("# TYPE ")
+ .append(name_)
+ .append(" ")
+ .append(metric_name())
+ .append("\n");
+ for (auto &[labels_value, sample] : value_map_) {
+ str.append(name_);
+ if (labels_name_.empty()) {
+ str.append(" ");
+ }
+ else {
+ str.append("{");
+ build_string(str, labels_name_, labels_value);
+ str.append("} ");
+ }
+
+ str.append(std::to_string((int64_t)sample.value));
+ if (enable_timestamp_) {
+ str.append(" ");
+ str.append(std::to_string(sample.timestamp));
+ }
+ str.append("\n");
+ }
+ }
+
+ protected:
+ enum class op_type_t { INC, DEC, SET };
+ void build_string(std::string &str, const std::vector &v1,
+ const std::vector &v2) {
+ for (size_t i = 0; i < v1.size(); i++) {
+ str.append(v1[i]).append("=\"").append(v2[i]).append("\"").append(",");
+ }
+ str.pop_back();
+ }
+
+ void validate(const std::vector &labels_value, double value) {
+ if (value < 0) {
+ throw std::invalid_argument("the value is less than zero");
+ }
+ if (labels_name_.size() != labels_value.size()) {
+ throw std::invalid_argument(
+ "the number of labels_value name and labels_value is not match");
+ }
+ }
+
+ void set_value(sample_t &sample, double value, op_type_t type) {
+ sample.timestamp = std::chrono::duration_cast(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+ switch (type) {
+ case op_type_t::INC:
+ sample.value += value;
+ break;
+ case op_type_t::DEC:
+ sample.value -= value;
+ break;
+ case op_type_t::SET:
+ sample.value = value;
+ break;
+ }
+ }
+
+ std::mutex mtx_;
+ std::map, sample_t,
+ std::less>>
+ value_map_;
+};
+} // namespace ylt
\ No newline at end of file
diff --git a/include/ylt/metric/detail/ckms_quantiles.hpp b/include/ylt/metric/detail/ckms_quantiles.hpp
new file mode 100644
index 000000000..e73c3ce80
--- /dev/null
+++ b/include/ylt/metric/detail/ckms_quantiles.hpp
@@ -0,0 +1,174 @@
+#pragma once
+#include
+#include
+
+// https://github.com/jupp0r/prometheus-cpp/blob/master/core/include/prometheus/detail/ckms_quantiles.h
+
+namespace ylt {
+class CKMSQuantiles {
+ public:
+ struct Quantile {
+ Quantile(double quantile, double error)
+ : quantile(quantile),
+ error(error),
+ u(2.0 * error / (1.0 - quantile)),
+ v(2.0 * error / quantile) {}
+
+ double quantile;
+ double error;
+ double u;
+ double v;
+ };
+
+ private:
+ struct Item {
+ double value;
+ int g;
+ int delta;
+
+ Item(double value, int lower_delta, int delta)
+ : value(value), g(lower_delta), delta(delta) {}
+ };
+
+ public:
+ explicit CKMSQuantiles(const std::vector& quantiles)
+ : quantiles_(quantiles), count_(0), buffer_{}, buffer_count_(0) {}
+
+ void insert(double value) {
+ buffer_[buffer_count_] = value;
+ ++buffer_count_;
+
+ if (buffer_count_ == buffer_.size()) {
+ insertBatch();
+ compress();
+ }
+ }
+
+ double get(double q) {
+ insertBatch();
+ compress();
+
+ if (sample_.empty()) {
+ return std::numeric_limits::quiet_NaN();
+ }
+
+ int rankMin = 0;
+ const auto desired = static_cast(q * count_);
+ const auto bound = desired + (allowableError(desired) / 2);
+
+ auto it = sample_.begin();
+ decltype(it) prev;
+ auto cur = it++;
+
+ while (it != sample_.end()) {
+ prev = cur;
+ cur = it++;
+
+ rankMin += prev->g;
+
+ if (rankMin + cur->g + cur->delta > bound) {
+ return prev->value;
+ }
+ }
+
+ return sample_.back().value;
+ }
+ void reset() {
+ count_ = 0;
+ sample_.clear();
+ buffer_count_ = 0;
+ }
+
+ private:
+ double allowableError(int rank) {
+ auto size = sample_.size();
+ double minError = size + 1;
+
+ for (const auto& q : quantiles_.get()) {
+ double error;
+ if (rank <= q.quantile * size) {
+ error = q.u * (size - rank);
+ }
+ else {
+ error = q.v * rank;
+ }
+ if (error < minError) {
+ minError = error;
+ }
+ }
+ return minError;
+ }
+
+ bool insertBatch() {
+ if (buffer_count_ == 0) {
+ return false;
+ }
+
+ std::sort(buffer_.begin(), buffer_.begin() + buffer_count_);
+
+ std::size_t start = 0;
+ if (sample_.empty()) {
+ sample_.emplace_back(buffer_[0], 1, 0);
+ ++start;
+ ++count_;
+ }
+
+ std::size_t idx = 0;
+ std::size_t item = idx++;
+
+ for (std::size_t i = start; i < buffer_count_; ++i) {
+ double v = buffer_[i];
+ while (idx < sample_.size() && sample_[item].value < v) {
+ item = idx++;
+ }
+
+ if (sample_[item].value > v) {
+ --idx;
+ }
+
+ int delta;
+ if (idx - 1 == 0 || idx + 1 == sample_.size()) {
+ delta = 0;
+ }
+ else {
+ delta = static_cast(std::floor(allowableError(idx + 1))) + 1;
+ }
+
+ sample_.emplace(sample_.begin() + idx, v, 1, delta);
+ count_++;
+ item = idx++;
+ }
+
+ buffer_count_ = 0;
+ return true;
+ }
+ void compress() {
+ if (sample_.size() < 2) {
+ return;
+ }
+
+ std::size_t idx = 0;
+ std::size_t prev;
+ std::size_t next = idx++;
+
+ while (idx < sample_.size()) {
+ prev = next;
+ next = idx++;
+
+ if (sample_[prev].g + sample_[next].g + sample_[next].delta <=
+ allowableError(idx - 1)) {
+ sample_[next].g += sample_[prev].g;
+ sample_.erase(sample_.begin() + prev);
+ }
+ }
+ }
+
+ private:
+ const std::reference_wrapper> quantiles_;
+
+ std::size_t count_;
+ std::vector- sample_;
+ std::array buffer_;
+ std::size_t buffer_count_;
+};
+} // namespace ylt
\ No newline at end of file
diff --git a/include/ylt/metric/detail/time_window_quantiles.hpp b/include/ylt/metric/detail/time_window_quantiles.hpp
new file mode 100644
index 000000000..fd7df105f
--- /dev/null
+++ b/include/ylt/metric/detail/time_window_quantiles.hpp
@@ -0,0 +1,52 @@
+#pragma once
+#include "ckms_quantiles.hpp"
+// https://github.com/jupp0r/prometheus-cpp/blob/master/core/include/prometheus/detail/time_window_quantiles.h
+
+namespace ylt {
+class TimeWindowQuantiles {
+ using Clock = std::chrono::steady_clock;
+
+ public:
+ TimeWindowQuantiles(const std::vector& quantiles,
+ Clock::duration max_age_seconds, int age_buckets)
+ : quantiles_(quantiles),
+ ckms_quantiles_(age_buckets, CKMSQuantiles(quantiles_)),
+ current_bucket_(0),
+ last_rotation_(Clock::now()),
+ rotation_interval_(max_age_seconds / age_buckets) {}
+
+ double get(double q) const {
+ CKMSQuantiles& current_bucket = rotate();
+ return current_bucket.get(q);
+ }
+ void insert(double value) {
+ rotate();
+ for (auto& bucket : ckms_quantiles_) {
+ bucket.insert(value);
+ }
+ }
+
+ private:
+ CKMSQuantiles& rotate() const {
+ auto delta = Clock::now() - last_rotation_;
+ while (delta > rotation_interval_) {
+ ckms_quantiles_[current_bucket_].reset();
+
+ if (++current_bucket_ >= ckms_quantiles_.size()) {
+ current_bucket_ = 0;
+ }
+
+ delta -= rotation_interval_;
+ last_rotation_ += rotation_interval_;
+ }
+ return ckms_quantiles_[current_bucket_];
+ }
+
+ const std::vector& quantiles_;
+ mutable std::vector ckms_quantiles_;
+ mutable std::size_t current_bucket_;
+
+ mutable Clock::time_point last_rotation_;
+ const Clock::duration rotation_interval_;
+};
+} // namespace ylt
\ No newline at end of file
diff --git a/include/ylt/metric/guage.hpp b/include/ylt/metric/guage.hpp
new file mode 100644
index 000000000..5c45d6470
--- /dev/null
+++ b/include/ylt/metric/guage.hpp
@@ -0,0 +1,36 @@
+#pragma once
+#include
+
+#include "counter.hpp"
+
+namespace ylt {
+class guage_t : public counter_t {
+ public:
+ guage_t() = default;
+ guage_t(std::string name, std::string help,
+ std::vector labels_name = {})
+ : counter_t(std::move(name), std::move(help), std::move(labels_name)) {
+ set_metric_type(MetricType::Guage);
+ }
+
+ guage_t(const char* name, const char* help,
+ std::vector labels_name = {})
+ : guage_t(
+ std::string(name), std::string(help),
+ std::vector(labels_name.begin(), labels_name.end())) {}
+
+ void dec() {
+ std::lock_guard guard(mtx_);
+ set_value(value_map_[{}], 1, op_type_t::DEC);
+ }
+
+ void dec(const std::vector& labels_value, double value) {
+ if (value == 0) {
+ return;
+ }
+ validate(labels_value, value);
+ std::lock_guard guard(mtx_);
+ set_value(value_map_[labels_value], value, op_type_t::DEC);
+ }
+};
+} // namespace ylt
\ No newline at end of file
diff --git a/include/ylt/metric/histogram.hpp b/include/ylt/metric/histogram.hpp
new file mode 100644
index 000000000..cf1a2366d
--- /dev/null
+++ b/include/ylt/metric/histogram.hpp
@@ -0,0 +1,123 @@
+
+#pragma once
+#include
+#include
+#include
+#include
+
+#include "counter.hpp"
+#include "metric.hpp"
+
+namespace ylt {
+class histogram_t : public metric_t {
+ public:
+ histogram_t(std::string name, std::string help, std::vector buckets)
+ : bucket_boundaries_(buckets),
+ metric_t(MetricType::Histogram, std::move(name), std::move(help)),
+ sum_(std::make_shared()) {
+ if (!is_strict_sorted(begin(bucket_boundaries_), end(bucket_boundaries_))) {
+ throw std::invalid_argument("Bucket Boundaries must be strictly sorted");
+ }
+
+ for (size_t i = 0; i < buckets.size() + 1; i++) {
+ bucket_counts_.push_back(std::make_shared());
+ }
+ }
+
+ void observe(double value) {
+ const auto bucket_index = static_cast(
+ std::distance(bucket_boundaries_.begin(),
+ std::lower_bound(bucket_boundaries_.begin(),
+ bucket_boundaries_.end(), value)));
+
+ std::lock_guard guard(mtx_);
+ std::lock_guard lock(mutex_);
+ sum_->inc({}, value);
+ bucket_counts_[bucket_index]->inc();
+ }
+
+ void observe(const std::vector& label, double value) {
+ const auto bucket_index = static_cast(
+ std::distance(bucket_boundaries_.begin(),
+ std::lower_bound(bucket_boundaries_.begin(),
+ bucket_boundaries_.end(), value)));
+
+ std::lock_guard guard(mtx_);
+ std::lock_guard lock(mutex_);
+ sum_->inc(label, value);
+ bucket_counts_[bucket_index]->inc(label);
+ }
+
+ void reset() {
+ std::lock_guard guard(mtx_);
+ for (auto& c : bucket_counts_) {
+ c->reset();
+ }
+
+ sum_->reset();
+ }
+
+ auto bucket_counts() {
+ std::lock_guard guard(mtx_);
+ return bucket_counts_;
+ }
+
+ void serialize(std::string& str) override {
+ if (sum_->values(false).empty()) {
+ return;
+ }
+ str.append("# HELP ").append(name_).append(" ").append(help_).append("\n");
+ str.append("# TYPE ")
+ .append(name_)
+ .append(" ")
+ .append(metric_name())
+ .append("\n");
+ double count = 0;
+ for (size_t i = 0; i < bucket_counts_.size(); i++) {
+ auto counter = bucket_counts_[i];
+ auto values = counter->values(false);
+ for (auto& [labels_value, sample] : values) {
+ str.append(name_).append("_bucket{");
+ if (i == bucket_boundaries_.size()) {
+ str.append("le=\"").append("+Inf").append("\"} ");
+ }
+ else {
+ str.append("le=\"")
+ .append(std::to_string(bucket_boundaries_[i]))
+ .append("\"} ");
+ }
+
+ count += sample.value;
+ str.append(std::to_string(count));
+ if (enable_timestamp_) {
+ str.append(" ").append(std::to_string(sample.timestamp));
+ }
+ str.append("\n");
+ }
+ }
+
+ str.append(name_)
+ .append("_sum ")
+ .append(std::to_string((sum_->values(false)[{}].value)))
+ .append("\n");
+
+ str.append(name_)
+ .append("_count ")
+ .append(std::to_string(count))
+ .append("\n");
+ }
+
+ private:
+ template
+ bool is_strict_sorted(ForwardIterator first, ForwardIterator last) {
+ return std::adjacent_find(first, last,
+ std::greater_equal::value_type>()) == last;
+ }
+
+ std::vector bucket_boundaries_;
+ std::mutex mutex_;
+ std::vector> bucket_counts_;
+ std::shared_ptr sum_;
+};
+} // namespace ylt
\ No newline at end of file
diff --git a/include/ylt/metric/metric.hpp b/include/ylt/metric/metric.hpp
new file mode 100644
index 000000000..c6a62fb27
--- /dev/null
+++ b/include/ylt/metric/metric.hpp
@@ -0,0 +1,123 @@
+#pragma once
+#include
+#include
@@ -10,7 +10,7 @@
[English Version](../../en/guide/what_is_yalantinglibs.md)
-yaLanTingLibs 是一个现代C++基础工具库的集合, 现在它包括 struct_pack, struct_json, struct_xml, struct_yaml, struct_pb, easylog, coro_rpc, coro_io, coro_http 和 async_simple, 目前我们正在开发并添加更多的新功能。
+yaLanTingLibs 是一个现代C++基础工具库的集合, 现在它包括 struct_pack, struct_json, struct_xml, struct_yaml, struct_pb, easylog, coro_rpc, coro_io, coro_http, metric 和 async_simple, 目前我们正在开发并添加更多的新功能。
yaLanTingLibs 的目标: 为C++开发者提供高性能,极度易用的现代C++基础工具库, 帮助用户构建高性能的现代C++应用。
@@ -432,6 +432,10 @@ yalantinglibs工程自身支持如下配置项,如果你使用cmake find_packa
- [protobuf](https://protobuf.dev/)
+### metric
+
+无依赖。
+
## 独立子仓库
coro_http 由独立子仓库实现: [cinatra](https://github.com/qicosmos/cinatra)
diff --git a/website/docs/zh/index.md b/website/docs/zh/index.md
index 08db7b745..bf815f29b 100644
--- a/website/docs/zh/index.md
+++ b/website/docs/zh/index.md
@@ -27,5 +27,7 @@ features:
- title: easylog
details: C++17 实现的高性能易用的日志库, 支持cout 流式、sprintf 和 fmt::format/std::format 输出.
- title: struct_xml struct_json struct_yaml
- details: C++17 实现的高性能易用的序列化库, 支持xml, json和yaml 的序列化/反序列化.
+ details: C++17 实现的高性能易用的序列化库, 支持xml, json和yaml 的序列化/反序列化.
+ - title: metric
+ details: metric 介绍
---
diff --git a/website/docs/zh/metric/metrict_introduction.md b/website/docs/zh/metric/metrict_introduction.md
new file mode 100644
index 000000000..9db3a5428
--- /dev/null
+++ b/website/docs/zh/metric/metrict_introduction.md
@@ -0,0 +1,261 @@
+# metric 介绍
+metric 用于统计应用程序的各种指标,这些指标被用于系统见识和警报,常见的指标类型有四种:Counter、Guage、Histogram和Summary,这些指标遵循[Prometheus](https://hulining.gitbook.io/prometheus/introduction)的数据格式。
+
+## Counter 计数器类型
+Counter是一个累计类型的数据指标,它代表单调递增的计数器,其值只能在重新启动时增加或重置为 0。例如,您可以使用计数器来表示已响应的请求数,已完成或出错的任务数。
+
+不要使用计数器来显示可以减小的值。例如,请不要使用计数器表示当前正在运行的进程数;使用 gauge 代替。
+
+## Gauge 数据轨迹类型
+Gauge 是可以任意上下波动数值的指标类型。
+
+Gauge 通常用于测量值,例如温度或当前的内存使用量,还可用于可能上下波动的"计数",例如请求并发数。
+
+如:
+```
+# HELP node_cpu Seconds the cpus spent in each mode.
+# TYPE node_cpu counter
+node_cpu{cpu="cpu0",mode="idle"} 362812.7890625
+# HELP node_load1 1m load average.
+# TYPE node_load1 gauge
+node_load1 3.0703125
+```
+
+## Histogram 直方图类型
+Histogram 对观测值(通常是请求持续时间或响应大小之类的数据)进行采样,并将其计数在可配置的数值区间中。它也提供了所有数据的总和。
+
+基本数据指标名称为的直方图类型数据指标,在数据采集期间会显示多个时间序列:
+
+数值区间的累计计数器,显示为_bucket{le="<数值区间的上边界>"}
+
+所有观测值的总和,显示为_sum
+
+统计到的事件计数,显示为_count(与上述_bucket{le="+Inf"}相同)
+
+如:
+```
+# A histogram, which has a pretty complex representation in the text format:
+# HELP http_request_duration_seconds A histogram of the request duration.
+# TYPE http_request_duration_seconds histogram
+http_request_duration_seconds_bucket{le="0.05"} 24054
+http_request_duration_seconds_bucket{le="0.1"} 33444
+http_request_duration_seconds_bucket{le="0.2"} 100392
+http_request_duration_seconds_bucket{le="+Inf"} 144320
+http_request_duration_seconds_sum 53423
+http_request_duration_seconds_count 144320
+```
+
+## Summary 汇总类型
+类似于 histogram,summary 会采样观察结果(通常是请求持续时间和响应大小之类的数据)。它不仅提供了观测值的总数和所有观测值的总和,还可以计算滑动时间窗口内的可配置分位数。
+
+基本数据指标名称为的 summary 类型数据指标,在数据采集期间会显示多个时间序列:
+
+流观察到的事件的 φ-quantiles(0≤φ≤1),显示为{quantile="<φ>"}
+
+所有观测值的总和,显示为_sum
+
+观察到的事件计数,显示为_count
+
+如:
+```
+# HELP prometheus_tsdb_wal_fsync_duration_seconds Duration of WAL fsync.
+# TYPE prometheus_tsdb_wal_fsync_duration_seconds summary
+prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.5"} 0.012352463
+prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.9"} 0.014458005
+prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.99"} 0.017316173
+prometheus_tsdb_wal_fsync_duration_seconds_sum 2.888716127000002
+prometheus_tsdb_wal_fsync_duration_seconds_count 216
+```
+
+# 如何使用metric功能
+
+## 使用counter指标统计http 请求总数
+http 请求数量随着时间推移是不断增加的,不可能会减少,因此使用counter类型的指标是合适的,如果数量可能会减少则应该使用guage类型的指标。
+
+### 创建counter 对象
+
+counter 的构造函数
+```cpp
+counter_t(std::string name, std::string help,
+ std::vector labels_name = {});
+```
+name: counter 的名称;
+help: counter 的帮助信息;
+labels_name: 标签的名称列表,默认为空。标签是一个键值对,由标签名称和标签值组成,稍后会在例子中介绍。
+
+如果希望创建一个统计http 请求数量的counter,可以这样创建:
+```cpp
+auto c = std::make_shared("request_count", "request count", std::vector{"method", "url"});
+```
+counter 的名称为request_count,帮助信息为request count,标签名为method 和url,标签的值是动态增加的,比如
+```
+{method = "GET", url = "/"} 10
+{method = "POST", url = "/test"} 20
+```
+method的和url的值就是标签的值,这是动态的,标签之后跟着count数量,第一行表示`GET /`请求的数量为10,`GET /test`请求的数量为20。
+
+如果创建counter的时候不设置标签名称,则counter使用空的标签列表为默认标签。
+
+### 增加counter
+创建counter之后需要增加它的值,调用其inc成员函数即可。
+
+```cpp
+void inc(); //#1 如果发生错误会抛异常
+void inc(const std::vector &labels_value, double value = 1); //#2 如果发生错误会抛异常
+```
+#1 重载函数给默认标签的counter增加数量;
+
+#2 重载函数给指定标签值的counter增加数量,注意:如果标签值列表和创建时标签名称列表的数量没有匹配上则会抛异常。
+
+统计http server 请求数量:
+```cpp
+ coro_http_server server(1, 9001);
+ server.set_http_handler(
+ "/get", [&](coro_http_request &req, coro_http_response &resp) {
+ resp.set_status_and_content(status_type::ok, "ok");
+ c->inc({std::string(req.get_method()), std::string(req.get_url())}); //#1
+ });
+ server.sync_start();
+```
+当收到`/get`请求时,代码#1 调用counter的inc来增加请求数量,标签值是请求的method 名和url。
+
+## 注册counter
+一个应用可能需要统计多个指标,这些指标需要放到一个map中便于管理,比如前端需要拉取所有指标的数据则需要遍历map获取每个指标的详细数据。
+
+注册指标调用:
+```cpp
+metric_t::regiter_metric(c);
+```
+
+## 返回统计结果给前端
+前端一般是prometheus 前端,配置它需要访问http server地址,默认会通过`/metrics` url来访问所有的指标数据。所以需要给http server 提供`/metrics`的http handler用于响应prometheus 前端的请求。
+```cpp
+ coro_http_server server(1, 9001);
+ server.set_http_handler(
+ "/get", [&](coro_http_request &req, coro_http_response &resp) {
+ resp.set_status_and_content(status_type::ok, "ok");
+ c->inc({std::string(req.get_method()), std::string(req.get_url())}); //#1
+ });
+
+ server.set_http_handler(
+ "/metrics", [](coro_http_request &req, coro_http_response &resp) {
+ std::string str;
+ auto metrics = metric_t::collect(); //#1 获取所有的指标对象
+ for (auto &m : metrics) {
+ m->serialize(str); // #2 序列化指标
+ }
+
+ resp.set_status_and_content(status_type::ok, std::move(str));
+ });
+ server.sync_start();
+```
+当前端访问`/metrics` 接口时,通过代码#1 `metric_t::collect()`来获取所有的指标对象,代码#2 `serialize(str)` 将指标详情序列化到str,然后返回给前端。
+
+完整的代码:
+```cpp
+void use_counter() {
+ auto c = std::make_shared("request_count", "request count", std::vector{"method", "url"});
+ metric_t::regiter_metric(c);
+ coro_http_server server(1, 9001);
+ server.set_http_handler(
+ "/get", [&](coro_http_request &req, coro_http_response &resp) {
+ resp.set_status_and_content(status_type::ok, "ok");
+ c->inc({std::string(req.get_method()), std::string(req.get_url())}); //#1
+ });
+
+ server.set_http_handler(
+ "/metrics", [](coro_http_request &req, coro_http_response &resp) {
+ std::string str;
+ auto metrics = metric_t::collect();
+ for (auto &m : metrics) {
+ m->serialize(str);
+ }
+
+ resp.set_status_and_content(status_type::ok, std::move(str));
+ });
+ server.sync_start();
+}
+```
+
+## 配置prometheus 前端
+安装[prometheus](https://github.com/prometheus/prometheus)之后,打开其配置文件:prometheus.yml
+
+修改要连接的服务端地址:
+```
+- targets: ["127.0.0.1:9001"]
+```
+然后启动prometheus,prometheus会定时访问`http://127.0.0.1:9001/metrics` 拉取所有指标数据。
+
+在本地浏览器输入:127.0.0.1:9090, 打开prometheus前端,在前端页面的搜索框中输入指标的名称request_count之后就能看到table和graph 结果了。
+
+## 使用guage
+guage和counter的用法几乎一样,guage比counter多了一个dec方法用来减少数量。
+
+创建一个guage:
+```cpp
+auto g = std::make_shared("not_found_request_count",
+ "not found request count",
+ std::vector{"method", "code", "url"});
+metric_t::regiter_metric(g);
+```
+后面根据自己的需要在业务函数中inc或者dec即可。
+
+## 使用Histogram
+创建Histogram时需要指定桶(bucket),采样点统计数据会落到不同的桶中,并且还需要统计采样点数据的累计总和(sum)以及次数的总和(count)。注意bucket 列表必须是有序的,否则构造时会抛异常。
+
+Histogram统计的特点是:数据是累积的,比如由10, 100,两个桶,第一个桶的数据是所有值 <= 10的样本数据存在桶中,第二个桶是所有 <=100 的样本数据存在桶中,其它数据则存放在`+Inf`的桶中。
+
+```cpp
+ auto h = std::make_shared(
+ std::string("test"), std::string("help"), std::vector{10.0, 100.0});
+ metric_t::regiter_metric(h);
+
+ h->observe(5);
+ h->observe(80);
+ h->observe(120);
+
+ std::string str;
+ h.serialize(str);
+ std::cout< distr(1, 100);
+ for (int i = 0; i < 50; i++) {
+ summary.observe(distr(gen));
+ }
+
+ std::string str;
+ summary.serialize(str);
+ std::cout << str;
+```
+输出:
+```
+# HELP test_summary summary help
+# TYPE test_summary summary
+test_summary{quantile="0.500000"} 45.000000
+test_summary{quantile="0.900000"} 83.000000
+test_summary{quantile="0.950000"} 88.000000
+test_summary{quantile="0.990000"} 93.000000
+test_summary_sum 2497.000000
+test_summary_count 50
+```
\ No newline at end of file
From 88b16307ac0d81fbd81e8a74c4fcd89091e3a33a Mon Sep 17 00:00:00 2001
From: qicosmos
Date: Fri, 10 May 2024 17:15:19 +0800
Subject: [PATCH 03/20] rename
---
include/ylt/metric/guage.hpp | 10 +++++-----
include/ylt/metric/histogram.hpp | 4 ++--
src/metric/tests/test_metric.cpp | 8 ++++----
website/docs/zh/metric/metrict_introduction.md | 2 +-
4 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/include/ylt/metric/guage.hpp b/include/ylt/metric/guage.hpp
index 5c45d6470..444c74463 100644
--- a/include/ylt/metric/guage.hpp
+++ b/include/ylt/metric/guage.hpp
@@ -4,18 +4,18 @@
#include "counter.hpp"
namespace ylt {
-class guage_t : public counter_t {
+class gauge_t : public counter_t {
public:
- guage_t() = default;
- guage_t(std::string name, std::string help,
+ gauge_t() = default;
+ gauge_t(std::string name, std::string help,
std::vector labels_name = {})
: counter_t(std::move(name), std::move(help), std::move(labels_name)) {
set_metric_type(MetricType::Guage);
}
- guage_t(const char* name, const char* help,
+ gauge_t(const char* name, const char* help,
std::vector labels_name = {})
- : guage_t(
+ : gauge_t(
std::string(name), std::string(help),
std::vector(labels_name.begin(), labels_name.end())) {}
diff --git a/include/ylt/metric/histogram.hpp b/include/ylt/metric/histogram.hpp
index cf1a2366d..ce20f7876 100644
--- a/include/ylt/metric/histogram.hpp
+++ b/include/ylt/metric/histogram.hpp
@@ -14,7 +14,7 @@ class histogram_t : public metric_t {
histogram_t(std::string name, std::string help, std::vector buckets)
: bucket_boundaries_(buckets),
metric_t(MetricType::Histogram, std::move(name), std::move(help)),
- sum_(std::make_shared()) {
+ sum_(std::make_shared()) {
if (!is_strict_sorted(begin(bucket_boundaries_), end(bucket_boundaries_))) {
throw std::invalid_argument("Bucket Boundaries must be strictly sorted");
}
@@ -118,6 +118,6 @@ class histogram_t : public metric_t {
std::vector bucket_boundaries_;
std::mutex mutex_;
std::vector> bucket_counts_;
- std::shared_ptr sum_;
+ std::shared_ptr sum_;
};
} // namespace ylt
\ No newline at end of file
diff --git a/src/metric/tests/test_metric.cpp b/src/metric/tests/test_metric.cpp
index a119e58bf..322c7d965 100644
--- a/src/metric/tests/test_metric.cpp
+++ b/src/metric/tests/test_metric.cpp
@@ -34,7 +34,7 @@ TEST_CASE("test counter") {
auto c = std::make_shared("get_count", "get counter",
std::vector{"method", "code"});
CHECK(c->name() == "get_count");
- auto g = std::make_shared("get_count", "get counter",
+ auto g = std::make_shared("get_count", "get counter",
std::vector{"method", "code"});
CHECK(g->name() == "get_count");
CHECK(g->metric_name() == "guage");
@@ -67,7 +67,7 @@ TEST_CASE("test counter") {
TEST_CASE("test guage") {
{
- guage_t g("get_count", "get counter");
+ gauge_t g("get_count", "get counter");
CHECK(g.metric_type() == MetricType::Guage);
CHECK(g.labels_name().empty());
g.inc();
@@ -83,7 +83,7 @@ TEST_CASE("test guage") {
}
{
- guage_t g("get_count", "get counter", {"method", "code", "url"});
+ gauge_t g("get_count", "get counter", {"method", "code", "url"});
CHECK(g.labels_name() == std::vector{"method", "code", "url"});
// method, status code, url
g.inc({"GET", "200", "/"}, 1);
@@ -155,7 +155,7 @@ TEST_CASE("test register metric") {
metric_t::regiter_metric(c);
CHECK_THROWS_AS(metric_t::regiter_metric(c), std::invalid_argument);
- auto g = std::make_shared(std::string("get_guage_count"),
+ auto g = std::make_shared(std::string("get_guage_count"),
std::string("get counter"));
metric_t::regiter_metric(g);
diff --git a/website/docs/zh/metric/metrict_introduction.md b/website/docs/zh/metric/metrict_introduction.md
index 9db3a5428..da2e70287 100644
--- a/website/docs/zh/metric/metrict_introduction.md
+++ b/website/docs/zh/metric/metrict_introduction.md
@@ -193,7 +193,7 @@ guage和counter的用法几乎一样,guage比counter多了一个dec方法用
创建一个guage:
```cpp
-auto g = std::make_shared("not_found_request_count",
+auto g = std::make_shared("not_found_request_count",
"not found request count",
std::vector{"method", "code", "url"});
metric_t::regiter_metric(g);
From e63cf1541a0003514b41b643105eead7c3a039d3 Mon Sep 17 00:00:00 2001
From: qicosmos
Date: Fri, 10 May 2024 17:36:12 +0800
Subject: [PATCH 04/20] rename file
---
include/ylt/metric/{guage.hpp => gauge.hpp} | 0
src/metric/tests/test_metric.cpp | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
rename include/ylt/metric/{guage.hpp => gauge.hpp} (100%)
diff --git a/include/ylt/metric/guage.hpp b/include/ylt/metric/gauge.hpp
similarity index 100%
rename from include/ylt/metric/guage.hpp
rename to include/ylt/metric/gauge.hpp
diff --git a/src/metric/tests/test_metric.cpp b/src/metric/tests/test_metric.cpp
index 322c7d965..7cb1a1c34 100644
--- a/src/metric/tests/test_metric.cpp
+++ b/src/metric/tests/test_metric.cpp
@@ -1,4 +1,4 @@
-#include "ylt/metric/guage.hpp"
+#include "ylt/metric/gauge.hpp"
#define DOCTEST_CONFIG_IMPLEMENT
#include
From dde26938ac99137736a3090f5aba784ca5a144f9 Mon Sep 17 00:00:00 2001
From: qicosmos
Date: Thu, 6 Jun 2024 17:53:56 +0800
Subject: [PATCH 05/20] update
---
include/ylt/metric/counter.hpp | 247 ++++++++---
include/ylt/metric/gauge.hpp | 44 +-
include/ylt/metric/histogram.hpp | 86 +---
include/ylt/metric/metric.hpp | 236 +++++++++--
include/ylt/metric/summary.hpp | 121 ++++--
src/metric/tests/test_metric.cpp | 197 ++++++---
.../docs/zh/metric/metrict_introduction.md | 397 +++++++++++++-----
7 files changed, 974 insertions(+), 354 deletions(-)
diff --git a/include/ylt/metric/counter.hpp b/include/ylt/metric/counter.hpp
index 402e01795..a526ef7cd 100644
--- a/include/ylt/metric/counter.hpp
+++ b/include/ylt/metric/counter.hpp
@@ -1,93 +1,196 @@
#pragma once
+#include
+#include
+
#include "metric.hpp"
namespace ylt {
+enum class op_type_t { INC, DEC, SET };
+struct counter_sample {
+ op_type_t op_type;
+ std::vector labels_value;
+ double value;
+};
+
class counter_t : public metric_t {
public:
- counter_t() = default;
+ // default, no labels, only contains an atomic value.
+ counter_t(std::string name, std::string help)
+ : metric_t(MetricType::Counter, std::move(name), std::move(help)) {
+ use_atomic_ = true;
+ }
+
+ // static labels value, contains a map with atomic value.
+ counter_t(std::string name, std::string help,
+ std::map labels)
+ : metric_t(MetricType::Counter, std::move(name), std::move(help)) {
+ for (auto &[k, v] : labels) {
+ labels_name_.push_back(k);
+ labels_value_.push_back(v);
+ }
+
+ atomic_value_map_.emplace(labels_value_, 0);
+ use_atomic_ = true;
+ }
+
+ // dynamic labels value
counter_t(std::string name, std::string help,
- std::vector labels_name = {})
+ std::vector labels_name)
: metric_t(MetricType::Counter, std::move(name), std::move(help),
std::move(labels_name)) {}
- counter_t(const char *name, const char *help,
- std::vector labels_name = {})
- : counter_t(
- std::string(name), std::string(help),
- std::vector(labels_name.begin(), labels_name.end())) {}
+ double value() { return default_lable_value_; }
- void inc() {
- std::lock_guard guard(mtx_);
- set_value(value_map_[{}], 1, op_type_t::INC);
+ double value(const std::vector &labels_value) {
+ if (use_atomic_) {
+ double val = atomic_value_map_[labels_value];
+ return val;
+ }
+ else {
+ std::lock_guard lock(mtx_);
+ return value_map_[labels_value];
+ }
+ }
+
+ std::map, double,
+ std::less>>
+ value_map() {
+ std::map, double,
+ std::less>>
+ map;
+ if (use_atomic_) {
+ map = {atomic_value_map_.begin(), atomic_value_map_.end()};
+ }
+ else {
+ std::lock_guard lock(mtx_);
+ map = value_map_;
+ }
+ return map;
+ }
+
+ void serialize(std::string &str) override {
+ if (labels_name_.empty()) {
+ if (default_lable_value_ == 0) {
+ return;
+ }
+ serialize_head(str);
+ serialize_default_label(str);
+ return;
+ }
+
+ serialize_head(str);
+ std::string s;
+ if (use_atomic_) {
+ serialize_map(atomic_value_map_, s);
+ }
+ else {
+ serialize_map(value_map_, s);
+ }
+
+ if (s.empty()) {
+ str.clear();
+ }
+ else {
+ str.append(s);
+ }
+ }
+
+ void inc(double val = 1) {
+ if (val < 0) {
+ throw std::invalid_argument("the value is less than zero");
+ }
+
+#ifdef __APPLE__
+ mac_os_atomic_fetch_add(&default_lable_value_, val);
+#else
+ default_lable_value_ += val;
+#endif
}
void inc(const std::vector &labels_value, double value = 1) {
if (value == 0) {
return;
}
+
validate(labels_value, value);
- std::lock_guard guard(mtx_);
- set_value(value_map_[labels_value], value, op_type_t::INC);
+ if (use_atomic_) {
+ if (labels_value != labels_value_) {
+ throw std::invalid_argument(
+ "the given labels_value is not match with origin labels_value");
+ }
+ set_value(atomic_value_map_[labels_value], value, op_type_t::INC);
+ }
+ else {
+ std::lock_guard lock(mtx_);
+ set_value(value_map_[labels_value], value, op_type_t::INC);
+ }
}
+ void update(double value) { default_lable_value_ = value; }
+
void update(const std::vector &labels_value, double value) {
- if (labels_name_.size() != labels_value.size()) {
+ if (labels_value.empty() || labels_name_.size() != labels_value.size()) {
throw std::invalid_argument(
"the number of labels_value name and labels_value is not match");
}
- std::lock_guard guard(mtx_);
- set_value(value_map_[labels_value], value, op_type_t::SET);
- }
-
- void reset() {
- std::lock_guard guard(mtx_);
- for (auto &pair : value_map_) {
- pair.second = {};
+ if (use_atomic_) {
+ if (labels_value != labels_value_) {
+ throw std::invalid_argument(
+ "the given labels_value is not match with origin labels_value");
+ }
+ set_value(atomic_value_map_[labels_value], value, op_type_t::SET);
+ }
+ else {
+ std::lock_guard lock(mtx_);
+ set_value(value_map_[labels_value], value, op_type_t::SET);
}
}
- std::map, sample_t,
+ std::map, std::atomic,
std::less>>
- values(bool need_lock = true) override {
- if (need_lock) {
- return value_map_;
- }
- std::lock_guard guard(mtx_);
- return value_map_;
+ &atomic_value_map() {
+ return atomic_value_map_;
}
- void serialize(std::string &str) override {
- if (value_map_.empty()) {
- return;
+ protected:
+ void serialize_default_label(std::string &str) {
+ str.append(name_);
+ if (labels_name_.empty()) {
+ str.append(" ");
+ }
+
+ if (type_ == MetricType::Counter) {
+ str.append(std::to_string((int64_t)default_lable_value_));
+ }
+ else {
+ str.append(std::to_string(default_lable_value_));
}
- str.append("# HELP ").append(name_).append(" ").append(help_).append("\n");
- str.append("# TYPE ")
- .append(name_)
- .append(" ")
- .append(metric_name())
- .append("\n");
- for (auto &[labels_value, sample] : value_map_) {
+
+ str.append("\n");
+ }
+
+ template
+ void serialize_map(T &value_map, std::string &str) {
+ for (auto &[labels_value, value] : value_map) {
+ if (value == 0) {
+ continue;
+ }
str.append(name_);
- if (labels_name_.empty()) {
- str.append(" ");
+ str.append("{");
+ build_string(str, labels_name_, labels_value);
+ str.append("} ");
+
+ if (type_ == MetricType::Counter) {
+ str.append(std::to_string((int64_t)value));
}
else {
- str.append("{");
- build_string(str, labels_name_, labels_value);
- str.append("} ");
+ str.append(std::to_string(value));
}
- str.append(std::to_string((int64_t)sample.value));
- if (enable_timestamp_) {
- str.append(" ");
- str.append(std::to_string(sample.timestamp));
- }
str.append("\n");
}
}
- protected:
- enum class op_type_t { INC, DEC, SET };
void build_string(std::string &str, const std::vector &v1,
const std::vector &v2) {
for (size_t i = 0; i < v1.size(); i++) {
@@ -100,31 +203,53 @@ class counter_t : public metric_t {
if (value < 0) {
throw std::invalid_argument("the value is less than zero");
}
- if (labels_name_.size() != labels_value.size()) {
+ if (labels_value.empty() || labels_name_.size() != labels_value.size()) {
throw std::invalid_argument(
"the number of labels_value name and labels_value is not match");
}
}
- void set_value(sample_t &sample, double value, op_type_t type) {
- sample.timestamp = std::chrono::duration_cast(
- std::chrono::system_clock::now().time_since_epoch())
- .count();
+ template
+ void set_value(T &label_val, double value, op_type_t type) {
switch (type) {
- case op_type_t::INC:
- sample.value += value;
- break;
+ case op_type_t::INC: {
+#ifdef __APPLE__
+ if constexpr (is_atomic) {
+ mac_os_atomic_fetch_add(&label_val, value);
+ }
+ else {
+ label_val += value;
+ }
+#else
+ label_val += value;
+#endif
+ } break;
case op_type_t::DEC:
- sample.value -= value;
+#ifdef __APPLE__
+ if constexpr (is_atomic) {
+ mac_os_atomic_fetch_sub(&label_val, value);
+ }
+ else {
+ label_val -= value;
+ }
+
+#else
+ label_val -= value;
+#endif
break;
case op_type_t::SET:
- sample.value = value;
+ label_val = value;
break;
}
}
+ std::map, std::atomic,
+ std::less>>
+ atomic_value_map_;
+ std::atomic default_lable_value_ = 0;
+
std::mutex mtx_;
- std::map, sample_t,
+ std::map, double,
std::less>>
value_map_;
};
diff --git a/include/ylt/metric/gauge.hpp b/include/ylt/metric/gauge.hpp
index 444c74463..19c4b65c2 100644
--- a/include/ylt/metric/gauge.hpp
+++ b/include/ylt/metric/gauge.hpp
@@ -6,31 +6,47 @@
namespace ylt {
class gauge_t : public counter_t {
public:
- gauge_t() = default;
+ gauge_t(std::string name, std::string help)
+ : counter_t(std::move(name), std::move(help)) {
+ set_metric_type(MetricType::Gauge);
+ }
gauge_t(std::string name, std::string help,
- std::vector labels_name = {})
+ std::vector labels_name)
: counter_t(std::move(name), std::move(help), std::move(labels_name)) {
- set_metric_type(MetricType::Guage);
+ set_metric_type(MetricType::Gauge);
}
- gauge_t(const char* name, const char* help,
- std::vector labels_name = {})
- : gauge_t(
- std::string(name), std::string(help),
- std::vector(labels_name.begin(), labels_name.end())) {}
+ gauge_t(std::string name, std::string help,
+ std::map labels)
+ : counter_t(std::move(name), std::move(help), std::move(labels)) {
+ set_metric_type(MetricType::Gauge);
+ }
- void dec() {
- std::lock_guard guard(mtx_);
- set_value(value_map_[{}], 1, op_type_t::DEC);
+ void dec(double value = 1) {
+#ifdef __APPLE__
+ mac_os_atomic_fetch_sub(&default_lable_value_, value);
+#else
+ default_lable_value_ -= value;
+#endif
}
- void dec(const std::vector& labels_value, double value) {
+ void dec(const std::vector& labels_value, double value = 1) {
if (value == 0) {
return;
}
+
validate(labels_value, value);
- std::lock_guard guard(mtx_);
- set_value(value_map_[labels_value], value, op_type_t::DEC);
+ if (use_atomic_) {
+ if (labels_value != labels_value_) {
+ throw std::invalid_argument(
+ "the given labels_value is not match with origin labels_value");
+ }
+ set_value(atomic_value_map_[labels_value], value, op_type_t::DEC);
+ }
+ else {
+ std::lock_guard lock(mtx_);
+ set_value(value_map_[labels_value], value, op_type_t::DEC);
+ }
}
};
} // namespace ylt
\ No newline at end of file
diff --git a/include/ylt/metric/histogram.hpp b/include/ylt/metric/histogram.hpp
index ce20f7876..dfd2ca131 100644
--- a/include/ylt/metric/histogram.hpp
+++ b/include/ylt/metric/histogram.hpp
@@ -14,14 +14,15 @@ class histogram_t : public metric_t {
histogram_t(std::string name, std::string help, std::vector buckets)
: bucket_boundaries_(buckets),
metric_t(MetricType::Histogram, std::move(name), std::move(help)),
- sum_(std::make_shared()) {
+ sum_(std::make_shared("", "")) {
if (!is_strict_sorted(begin(bucket_boundaries_), end(bucket_boundaries_))) {
throw std::invalid_argument("Bucket Boundaries must be strictly sorted");
}
for (size_t i = 0; i < buckets.size() + 1; i++) {
- bucket_counts_.push_back(std::make_shared());
+ bucket_counts_.push_back(std::make_shared("", ""));
}
+ use_atomic_ = true;
}
void observe(double value) {
@@ -29,76 +30,36 @@ class histogram_t : public metric_t {
std::distance(bucket_boundaries_.begin(),
std::lower_bound(bucket_boundaries_.begin(),
bucket_boundaries_.end(), value)));
-
- std::lock_guard guard(mtx_);
- std::lock_guard lock(mutex_);
- sum_->inc({}, value);
+ sum_->inc(value);
bucket_counts_[bucket_index]->inc();
}
- void observe(const std::vector& label, double value) {
- const auto bucket_index = static_cast(
- std::distance(bucket_boundaries_.begin(),
- std::lower_bound(bucket_boundaries_.begin(),
- bucket_boundaries_.end(), value)));
-
- std::lock_guard guard(mtx_);
- std::lock_guard lock(mutex_);
- sum_->inc(label, value);
- bucket_counts_[bucket_index]->inc(label);
- }
-
- void reset() {
- std::lock_guard guard(mtx_);
- for (auto& c : bucket_counts_) {
- c->reset();
- }
-
- sum_->reset();
- }
-
- auto bucket_counts() {
- std::lock_guard guard(mtx_);
- return bucket_counts_;
- }
+ auto get_bucket_counts() { return bucket_counts_; }
void serialize(std::string& str) override {
- if (sum_->values(false).empty()) {
- return;
- }
- str.append("# HELP ").append(name_).append(" ").append(help_).append("\n");
- str.append("# TYPE ")
- .append(name_)
- .append(" ")
- .append(metric_name())
- .append("\n");
+ serialize_head(str);
double count = 0;
- for (size_t i = 0; i < bucket_counts_.size(); i++) {
- auto counter = bucket_counts_[i];
- auto values = counter->values(false);
- for (auto& [labels_value, sample] : values) {
- str.append(name_).append("_bucket{");
- if (i == bucket_boundaries_.size()) {
- str.append("le=\"").append("+Inf").append("\"} ");
- }
- else {
- str.append("le=\"")
- .append(std::to_string(bucket_boundaries_[i]))
- .append("\"} ");
- }
-
- count += sample.value;
- str.append(std::to_string(count));
- if (enable_timestamp_) {
- str.append(" ").append(std::to_string(sample.timestamp));
- }
- str.append("\n");
+ auto bucket_counts = get_bucket_counts();
+ for (size_t i = 0; i < bucket_counts.size(); i++) {
+ auto counter = bucket_counts[i];
+ str.append(name_).append("_bucket{");
+ if (i == bucket_boundaries_.size()) {
+ str.append("le=\"").append("+Inf").append("\"} ");
}
+ else {
+ str.append("le=\"")
+ .append(std::to_string(bucket_boundaries_[i]))
+ .append("\"} ");
+ }
+
+ count += counter->value();
+ str.append(std::to_string(count));
+ str.append("\n");
}
str.append(name_)
.append("_sum ")
- .append(std::to_string((sum_->values(false)[{}].value)))
+ .append(std::to_string(sum_->value()))
.append("\n");
str.append(name_)
@@ -116,8 +77,7 @@ class histogram_t : public metric_t {
}
std::vector bucket_boundaries_;
- std::mutex mutex_;
- std::vector> bucket_counts_;
+ std::vector> bucket_counts_; // readonly
std::shared_ptr sum_;
};
} // namespace ylt
\ No newline at end of file
diff --git a/include/ylt/metric/metric.hpp b/include/ylt/metric/metric.hpp
index c6a62fb27..5e326a178 100644
--- a/include/ylt/metric/metric.hpp
+++ b/include/ylt/metric/metric.hpp
@@ -1,4 +1,5 @@
#pragma once
+#include
#include
#include