From 061afb9b1e755a6d08bd942897dabff1274d115a Mon Sep 17 00:00:00 2001 From: qicosmos Date: Mon, 29 Apr 2024 16:50:30 +0800 Subject: [PATCH 01/20] init register metric --- include/cinatra/metric/counter.hpp | 40 +++++++++++++++++++++++ include/cinatra/metric/guage.hpp | 51 ++++++++++++++++++++++++++++++ include/cinatra/metric/metric.hpp | 51 ++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 include/cinatra/metric/counter.hpp create mode 100644 include/cinatra/metric/guage.hpp create mode 100644 include/cinatra/metric/metric.hpp diff --git a/include/cinatra/metric/counter.hpp b/include/cinatra/metric/counter.hpp new file mode 100644 index 00000000..34d0fa3c --- /dev/null +++ b/include/cinatra/metric/counter.hpp @@ -0,0 +1,40 @@ +#pragma once +#include "metric.hpp" + +namespace cinatra { +class counter_t : public metric_t { + public: + counter_t(std::string name, std::string help, + std::pair labels = {}) + : metric_t(MetricType::Counter, std::move(name), std::move(help), + std::move(labels)) {} + + void inc() { + std::lock_guard guard(mtx_); + value_map_[{}]++; + } + + void inc(const std::pair &label, double value) { + assert(value > 0); + std::lock_guard guard(mtx_); + value_map_[label] += value; + } + + void update(const std::pair &label, double value) { + assert(value > 0); + std::lock_guard guard(mtx_); + value_map_[label] = value; + } + + void reset() { + std::lock_guard guard(mtx_); + for (auto &pair : value_map_) { + pair.second = 0; + } + } + + private: + std::mutex mtx_; + std::map, double> value_map_; +}; +} // namespace cinatra \ No newline at end of file diff --git a/include/cinatra/metric/guage.hpp b/include/cinatra/metric/guage.hpp new file mode 100644 index 00000000..9d6df3d4 --- /dev/null +++ b/include/cinatra/metric/guage.hpp @@ -0,0 +1,51 @@ +#pragma once +#include "metric.hpp" + +namespace cinatra { +class guage_t : public metric_t { + public: + guage_t(std::string name, std::string help, + std::pair labels = {}) + : metric_t(MetricType::Counter, std::move(name), std::move(help), + std::move(labels)) {} + + void inc() { + std::lock_guard guard(mtx_); + value_map_[{}]++; + } + + void inc(const std::pair &label, double value) { + assert(value > 0); + std::lock_guard guard(mtx_); + value_map_[label] += value; + } + + void dec() { + std::lock_guard guard(mtx_); + value_map_[{}]--; + } + + void dec(const std::pair &label, double value) { + assert(value > 0); + std::lock_guard guard(mtx_); + value_map_[{}] -= value; + } + + void update(const std::pair &label, double value) { + assert(value > 0); + std::lock_guard guard(mtx_); + value_map_[label] = value; + } + + void reset() { + std::lock_guard guard(mtx_); + for (auto &pair : value_map_) { + pair.second = 0; + } + } + + private: + std::mutex mtx_; + std::map, double> value_map_; +}; +} // namespace cinatra \ No newline at end of file diff --git a/include/cinatra/metric/metric.hpp b/include/cinatra/metric/metric.hpp new file mode 100644 index 00000000..f37b5e98 --- /dev/null +++ b/include/cinatra/metric/metric.hpp @@ -0,0 +1,51 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace cinatra { +enum class MetricType { + Counter, + Guage, + Histogram, + Summary, + Nil, +}; + +class metric_t { + public: + metric_t(MetricType type, std::string name, std::string help, + std::pair labels = {}) + : type_(type), + name_(std::move(name)), + help_(std::move(help)), + label_(std::move(labels)) {} + std::string_view name() { return name_; } + + std::string_view help() { return help_; } + + MetricType metric_type() { return type_; } + + const std::pair &label() { return label_; } + + static void regiter_metric(std::shared_ptr metric) { + std::scoped_lock guard(mtx_); + std::string name(metric->name()); + auto pair = metric_map_.emplace(name, std::move(metric)); + if (!pair.second) { + throw std::invalid_argument("duplicate metric name: " + name); + } + } + + protected: + MetricType type_ = MetricType::Nil; + std::string name_; + std::string help_; + std::pair label_; + static inline std::mutex mtx_; + static inline std::map> metric_map_; +}; +} // namespace cinatra \ No newline at end of file From c21b5f9b6cc870977a4f52061a31a812c851b83d Mon Sep 17 00:00:00 2001 From: qicosmos Date: Mon, 6 May 2024 11:33:45 +0800 Subject: [PATCH 02/20] test counter --- include/cinatra/metric/counter.hpp | 33 ++++++---------- include/cinatra/metric/guage.hpp | 60 +++++++++++++++++++++++++----- include/cinatra/metric/metric.hpp | 1 + tests/CMakeLists.txt | 4 ++ tests/test_metric.cpp | 28 ++++++++++++++ 5 files changed, 95 insertions(+), 31 deletions(-) create mode 100644 tests/test_metric.cpp diff --git a/include/cinatra/metric/counter.hpp b/include/cinatra/metric/counter.hpp index 34d0fa3c..d39a884d 100644 --- a/include/cinatra/metric/counter.hpp +++ b/include/cinatra/metric/counter.hpp @@ -1,40 +1,31 @@ #pragma once +#include "guage.hpp" #include "metric.hpp" namespace cinatra { -class counter_t : public metric_t { +class counter_t { public: counter_t(std::string name, std::string help, std::pair labels = {}) - : metric_t(MetricType::Counter, std::move(name), std::move(help), - std::move(labels)) {} - - void inc() { - std::lock_guard guard(mtx_); - value_map_[{}]++; + : guage_(std::move(name), std::move(help), std::move(labels)) { + guage_.set_metric_type(MetricType::Counter); } + void inc() { guage_.inc(); } + void inc(const std::pair &label, double value) { - assert(value > 0); - std::lock_guard guard(mtx_); - value_map_[label] += value; + guage_.inc(label, value); } void update(const std::pair &label, double value) { - assert(value > 0); - std::lock_guard guard(mtx_); - value_map_[label] = value; + guage_.update(label, value); } - void reset() { - std::lock_guard guard(mtx_); - for (auto &pair : value_map_) { - pair.second = 0; - } - } + void reset() { guage_.reset(); } + + auto values() { return guage_.values(); } private: - std::mutex mtx_; - std::map, double> value_map_; + guage_t guage_; }; } // namespace cinatra \ No newline at end of file diff --git a/include/cinatra/metric/guage.hpp b/include/cinatra/metric/guage.hpp index 9d6df3d4..b4abaf1e 100644 --- a/include/cinatra/metric/guage.hpp +++ b/include/cinatra/metric/guage.hpp @@ -1,7 +1,14 @@ #pragma once +#include + #include "metric.hpp" namespace cinatra { +struct sample_t { + double value; + int64_t timestamp; +}; + class guage_t : public metric_t { public: guage_t(std::string name, std::string help, @@ -11,41 +18,74 @@ class guage_t : public metric_t { void inc() { std::lock_guard guard(mtx_); - value_map_[{}]++; + set_value(value_map_[{}], 1, op_type_t::INC); } void inc(const std::pair &label, double value) { - assert(value > 0); + if (value == 0) { + return; + } + if (value < 0) { + throw std::invalid_argument("the value is less than zero"); + } std::lock_guard guard(mtx_); - value_map_[label] += value; + set_value(value_map_[label], value, op_type_t::INC); } void dec() { std::lock_guard guard(mtx_); - value_map_[{}]--; + set_value(value_map_[{}], 1, op_type_t::DEC); } void dec(const std::pair &label, double value) { - assert(value > 0); + if (value == 0) { + return; + } + if (value < 0) { + throw std::invalid_argument("the value is less than zero"); + } std::lock_guard guard(mtx_); - value_map_[{}] -= value; + set_value(value_map_[label], value, op_type_t::DEC); } void update(const std::pair &label, double value) { - assert(value > 0); std::lock_guard guard(mtx_); - value_map_[label] = value; + set_value(value_map_[label], value, op_type_t::SET); } void reset() { std::lock_guard guard(mtx_); for (auto &pair : value_map_) { - pair.second = 0; + pair.second = {}; } } + auto values() { + std::lock_guard guard(mtx_); + return value_map_; + } + private: + enum class op_type_t { INC, DEC, SET }; + + 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, double> value_map_; + std::map, sample_t> value_map_; }; } // namespace cinatra \ No newline at end of file diff --git a/include/cinatra/metric/metric.hpp b/include/cinatra/metric/metric.hpp index f37b5e98..2c9c871a 100644 --- a/include/cinatra/metric/metric.hpp +++ b/include/cinatra/metric/metric.hpp @@ -28,6 +28,7 @@ class metric_t { std::string_view help() { return help_; } MetricType metric_type() { return type_; } + void set_metric_type(MetricType type) { type_ = type; } const std::pair &label() { return label_; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 381be1cc..65ac13b9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -50,6 +50,10 @@ add_executable(test_corofile test_corofile.cpp ) +add_executable(test_metric + test_metric.cpp + ) + if(ENABLE_FILE_IO_URING) if (UNIX) target_link_libraries(test_corofile PRIVATE uring) diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp new file mode 100644 index 00000000..d553257b --- /dev/null +++ b/tests/test_metric.cpp @@ -0,0 +1,28 @@ +#include +#define DOCTEST_CONFIG_IMPLEMENT +#include "doctest/doctest.h" + +#include "cinatra/metric/counter.hpp" +using namespace cinatra; + +TEST_CASE("test counter") { + counter_t c("get_count", "get counter", {}); + c.inc(); + CHECK(c.values().begin()->second.value==1); + c.inc(); + CHECK(c.values().begin()->second.value==2); + c.inc({}, 0); + CHECK(c.values().begin()->second.value==2); + + CHECK_THROWS_AS(c.inc({}, -2), std::invalid_argument); + + c.update({}, 10); + CHECK(c.values().begin()->second.value==10); + + c.update({}, 0); + CHECK(c.values().begin()->second.value==0); +} + +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) +int main(int argc, char **argv) { return doctest::Context(argc, argv).run(); } +DOCTEST_MSVC_SUPPRESS_WARNING_POP \ No newline at end of file From 4e77eee374845ebde18b711d7a8f89d7041eac33 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Mon, 6 May 2024 11:37:35 +0800 Subject: [PATCH 03/20] format --- tests/test_metric.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index d553257b..872c536b 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -1,26 +1,26 @@ #include #define DOCTEST_CONFIG_IMPLEMENT -#include "doctest/doctest.h" - #include "cinatra/metric/counter.hpp" +#include "doctest/doctest.h" using namespace cinatra; TEST_CASE("test counter") { - counter_t c("get_count", "get counter", {}); - c.inc(); - CHECK(c.values().begin()->second.value==1); - c.inc(); - CHECK(c.values().begin()->second.value==2); - c.inc({}, 0); - CHECK(c.values().begin()->second.value==2); + counter_t c("get_count", "get counter", {}); + c.inc(); + CHECK(c.values().begin()->second.value == 1); + c.inc(); + CHECK(c.values().begin()->second.value == 2); + c.inc({}, 0); + + CHECK(c.values().begin()->second.value == 2); - CHECK_THROWS_AS(c.inc({}, -2), std::invalid_argument); + CHECK_THROWS_AS(c.inc({}, -2), std::invalid_argument); - c.update({}, 10); - CHECK(c.values().begin()->second.value==10); + c.update({}, 10); + CHECK(c.values().begin()->second.value == 10); - c.update({}, 0); - CHECK(c.values().begin()->second.value==0); + c.update({}, 0); + CHECK(c.values().begin()->second.value == 0); } DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) From 08829913e6402d8b179a4b09dd510289f09a3fc3 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Mon, 6 May 2024 11:52:00 +0800 Subject: [PATCH 04/20] test guage --- tests/test_metric.cpp | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index 872c536b..422daedf 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -1,4 +1,4 @@ -#include +#include "cinatra/metric/guage.hpp" #define DOCTEST_CONFIG_IMPLEMENT #include "cinatra/metric/counter.hpp" #include "doctest/doctest.h" @@ -21,6 +21,41 @@ TEST_CASE("test counter") { c.update({}, 0); CHECK(c.values().begin()->second.value == 0); + + c.inc({"GET", "200"}, 1); + CHECK(c.values()[{"GET", "200"}].value == 1); + c.inc({"GET", "200"}, 2); + CHECK(c.values()[{"GET", "200"}].value == 3); + + c.update({"GET", "200"}, 20); + CHECK(c.values()[{"GET", "200"}].value == 20); + c.reset(); + CHECK(c.values()[{"GET", "200"}].value == 0); + CHECK(c.values().begin()->second.value == 0); +} + +TEST_CASE("test guage") { + guage_t g("get_count", "get counter", {}); + g.inc(); + CHECK(g.values().begin()->second.value == 1); + g.inc(); + CHECK(g.values().begin()->second.value == 2); + g.inc({}, 0); + + g.dec(); + CHECK(g.values().begin()->second.value == 1); + g.dec(); + CHECK(g.values().begin()->second.value == 0); + + g.inc({"GET", "200"}, 1); + CHECK(g.values()[{"GET", "200"}].value == 1); + g.inc({"GET", "200"}, 2); + CHECK(g.values()[{"GET", "200"}].value == 3); + + g.dec({"GET", "200"}, 1); + CHECK(g.values()[{"GET", "200"}].value == 2); + g.dec({"GET", "200"}, 2); + CHECK(g.values()[{"GET", "200"}].value == 0); } DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) From 78e091be7c671aa79500679a2eb0ffc0737cdf56 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Mon, 6 May 2024 16:40:25 +0800 Subject: [PATCH 05/20] register metric --- include/cinatra/metric/counter.hpp | 14 +++++++---- include/cinatra/metric/guage.hpp | 9 ++------ include/cinatra/metric/metric.hpp | 37 +++++++++++++++++++++++++++++- tests/test_metric.cpp | 26 +++++++++++++++++++++ 4 files changed, 73 insertions(+), 13 deletions(-) diff --git a/include/cinatra/metric/counter.hpp b/include/cinatra/metric/counter.hpp index d39a884d..a4dbf7c2 100644 --- a/include/cinatra/metric/counter.hpp +++ b/include/cinatra/metric/counter.hpp @@ -3,17 +3,19 @@ #include "metric.hpp" namespace cinatra { -class counter_t { +class counter_t : public metric_t { public: counter_t(std::string name, std::string help, std::pair labels = {}) - : guage_(std::move(name), std::move(help), std::move(labels)) { - guage_.set_metric_type(MetricType::Counter); + : guage_(std::move(name), std::move(help), std::move(labels)), + metric_t(MetricType::Counter, std::move(name), std::move(help), + std::move(labels)) { + // guage_.set_metric_type(MetricType::Counter); } void inc() { guage_.inc(); } - void inc(const std::pair &label, double value) { + void inc(const std::pair &label, double value = 1) { guage_.inc(label, value); } @@ -23,7 +25,9 @@ class counter_t { void reset() { guage_.reset(); } - auto values() { return guage_.values(); } + std::map, sample_t> values() { + return guage_.values(); + } private: guage_t guage_; diff --git a/include/cinatra/metric/guage.hpp b/include/cinatra/metric/guage.hpp index b4abaf1e..f234b062 100644 --- a/include/cinatra/metric/guage.hpp +++ b/include/cinatra/metric/guage.hpp @@ -4,11 +4,6 @@ #include "metric.hpp" namespace cinatra { -struct sample_t { - double value; - int64_t timestamp; -}; - class guage_t : public metric_t { public: guage_t(std::string name, std::string help, @@ -21,7 +16,7 @@ class guage_t : public metric_t { set_value(value_map_[{}], 1, op_type_t::INC); } - void inc(const std::pair &label, double value) { + void inc(const std::pair &label, double value = 1) { if (value == 0) { return; } @@ -60,7 +55,7 @@ class guage_t : public metric_t { } } - auto values() { + std::map, sample_t> values() { std::lock_guard guard(mtx_); return value_map_; } diff --git a/include/cinatra/metric/metric.hpp b/include/cinatra/metric/metric.hpp index 2c9c871a..08b1d3a7 100644 --- a/include/cinatra/metric/metric.hpp +++ b/include/cinatra/metric/metric.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace cinatra { enum class MetricType { @@ -15,6 +16,11 @@ enum class MetricType { Nil, }; +struct sample_t { + double value; + int64_t timestamp; +}; + class metric_t { public: metric_t(MetricType type, std::string name, std::string help, @@ -30,7 +36,9 @@ class metric_t { MetricType metric_type() { return type_; } void set_metric_type(MetricType type) { type_ = type; } - const std::pair &label() { return label_; } + const std::pair& label() { return label_; } + + virtual std::map, sample_t> values() = 0; static void regiter_metric(std::shared_ptr metric) { std::scoped_lock guard(mtx_); @@ -41,6 +49,33 @@ class metric_t { } } + static void remove_metric(std::string name) { + std::scoped_lock guard(mtx_); + metric_map_.erase(name); + } + + static auto collect() { + std::scoped_lock guard(mtx_); + return metric_map_; + } + + static size_t metric_count() { + std::scoped_lock guard(mtx_); + return metric_map_.size(); + } + + static std::vector metric_keys() { + std::vector keys; + { + std::scoped_lock guard(mtx_); + for (auto& pair : metric_map_) { + keys.push_back(pair.first); + } + } + + return keys; + } + protected: MetricType type_ = MetricType::Nil; std::string name_; diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index 422daedf..7fa63b4b 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -58,6 +58,32 @@ TEST_CASE("test guage") { CHECK(g.values()[{"GET", "200"}].value == 0); } +TEST_CASE("test register metric") { + auto c = std::make_shared(std::string("get_count"), + std::string("get counter"), + std::pair{}); + 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"), + std::string("get counter"), + std::pair{}); + metric_t::regiter_metric(g); + + CHECK(metric_t::metric_count() == 2); + CHECK(metric_t::metric_keys().size() == 2); + + c->inc(); + g->inc(); + + auto map = metric_t::collect(); + CHECK(map["get_count"]->values()[{}].value == 1); + CHECK(map["get_guage_count"]->values()[{}].value == 1); + + metric_t::remove_metric("get_count"); + CHECK(metric_t::metric_count() == 1); +} + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) int main(int argc, char **argv) { return doctest::Context(argc, argv).run(); } DOCTEST_MSVC_SUPPRESS_WARNING_POP \ No newline at end of file From 9a1d4d54512c4538a10cf4d5a126d52f0f78bfa4 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Tue, 7 May 2024 10:29:16 +0800 Subject: [PATCH 06/20] histogram --- include/cinatra/metric/counter.hpp | 5 +- include/cinatra/metric/guage.hpp | 1 + include/cinatra/metric/histogram.hpp | 79 ++++++++++++++++++++++++++++ include/cinatra/metric/metric.hpp | 5 +- tests/test_metric.cpp | 16 ++++++ 5 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 include/cinatra/metric/histogram.hpp diff --git a/include/cinatra/metric/counter.hpp b/include/cinatra/metric/counter.hpp index a4dbf7c2..a60afb7c 100644 --- a/include/cinatra/metric/counter.hpp +++ b/include/cinatra/metric/counter.hpp @@ -5,13 +5,12 @@ namespace cinatra { class counter_t : public metric_t { public: + counter_t() = default; counter_t(std::string name, std::string help, std::pair labels = {}) : guage_(std::move(name), std::move(help), std::move(labels)), metric_t(MetricType::Counter, std::move(name), std::move(help), - std::move(labels)) { - // guage_.set_metric_type(MetricType::Counter); - } + std::move(labels)) {} void inc() { guage_.inc(); } diff --git a/include/cinatra/metric/guage.hpp b/include/cinatra/metric/guage.hpp index f234b062..fd043c76 100644 --- a/include/cinatra/metric/guage.hpp +++ b/include/cinatra/metric/guage.hpp @@ -6,6 +6,7 @@ namespace cinatra { class guage_t : public metric_t { public: + guage_t() = default; guage_t(std::string name, std::string help, std::pair labels = {}) : metric_t(MetricType::Counter, std::move(name), std::move(help), diff --git a/include/cinatra/metric/histogram.hpp b/include/cinatra/metric/histogram.hpp new file mode 100644 index 00000000..9aadc66d --- /dev/null +++ b/include/cinatra/metric/histogram.hpp @@ -0,0 +1,79 @@ + +#pragma once +#include +#include +#include +#include + +#include "counter.hpp" +#include "metric.hpp" + +namespace cinatra { +class histogram_t : public metric_t { + public: + histogram_t(std::string name, std::vector buckets, + std::string help = "") + : 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::pair& 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_; + } + + 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 cinatra \ No newline at end of file diff --git a/include/cinatra/metric/metric.hpp b/include/cinatra/metric/metric.hpp index 08b1d3a7..f153d32d 100644 --- a/include/cinatra/metric/metric.hpp +++ b/include/cinatra/metric/metric.hpp @@ -23,6 +23,7 @@ struct sample_t { class metric_t { public: + metric_t() = default; metric_t(MetricType type, std::string name, std::string help, std::pair labels = {}) : type_(type), @@ -38,7 +39,9 @@ class metric_t { const std::pair& label() { return label_; } - virtual std::map, sample_t> values() = 0; + virtual std::map, sample_t> values() { + return {}; + } static void regiter_metric(std::shared_ptr metric) { std::scoped_lock guard(mtx_); diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index 7fa63b4b..c5d68ac1 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -1,6 +1,7 @@ #include "cinatra/metric/guage.hpp" #define DOCTEST_CONFIG_IMPLEMENT #include "cinatra/metric/counter.hpp" +#include "cinatra/metric/histogram.hpp" #include "doctest/doctest.h" using namespace cinatra; @@ -58,6 +59,21 @@ TEST_CASE("test guage") { CHECK(g.values()[{"GET", "200"}].value == 0); } +TEST_CASE("test histogram") { + histogram_t h("test", {5.0, 10.0, 20.0, 50.0, 100.0}); + h.observe(23); + auto counts = h.bucket_counts(); + CHECK(counts[3]->values()[{}].value == 1); + h.observe(42); + CHECK(counts[3]->values()[{}].value == 2); + h.observe(60); + CHECK(counts[4]->values()[{}].value == 1); + h.observe(120); + CHECK(counts[5]->values()[{}].value == 1); + h.observe(1); + CHECK(counts[0]->values()[{}].value == 1); +} + TEST_CASE("test register metric") { auto c = std::make_shared(std::string("get_count"), std::string("get counter"), From 3a549fa6562012e9a1ee4d5fc5bb3a32e4476f17 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Tue, 7 May 2024 10:57:58 +0800 Subject: [PATCH 07/20] labels --- include/cinatra/metric/counter.hpp | 8 +++++--- include/cinatra/metric/guage.hpp | 14 +++++++++----- include/cinatra/metric/histogram.hpp | 2 +- include/cinatra/metric/metric.hpp | 4 +++- tests/test_metric.cpp | 19 +++++++++++-------- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/include/cinatra/metric/counter.hpp b/include/cinatra/metric/counter.hpp index a60afb7c..bb96daa5 100644 --- a/include/cinatra/metric/counter.hpp +++ b/include/cinatra/metric/counter.hpp @@ -14,17 +14,19 @@ class counter_t : public metric_t { void inc() { guage_.inc(); } - void inc(const std::pair &label, double value = 1) { + void inc(const std::vector &label, double value = 1) { guage_.inc(label, value); } - void update(const std::pair &label, double value) { + void update(const std::vector &label, double value) { guage_.update(label, value); } void reset() { guage_.reset(); } - std::map, sample_t> values() { + std::map, sample_t, + std::less>> + values() { return guage_.values(); } diff --git a/include/cinatra/metric/guage.hpp b/include/cinatra/metric/guage.hpp index fd043c76..8c51bb12 100644 --- a/include/cinatra/metric/guage.hpp +++ b/include/cinatra/metric/guage.hpp @@ -17,7 +17,7 @@ class guage_t : public metric_t { set_value(value_map_[{}], 1, op_type_t::INC); } - void inc(const std::pair &label, double value = 1) { + void inc(const std::vector &label, double value = 1) { if (value == 0) { return; } @@ -33,7 +33,7 @@ class guage_t : public metric_t { set_value(value_map_[{}], 1, op_type_t::DEC); } - void dec(const std::pair &label, double value) { + void dec(const std::vector &label, double value) { if (value == 0) { return; } @@ -44,7 +44,7 @@ class guage_t : public metric_t { set_value(value_map_[label], value, op_type_t::DEC); } - void update(const std::pair &label, double value) { + void update(const std::vector &label, double value) { std::lock_guard guard(mtx_); set_value(value_map_[label], value, op_type_t::SET); } @@ -56,7 +56,9 @@ class guage_t : public metric_t { } } - std::map, sample_t> values() { + std::map, sample_t, + std::less>> + values() { std::lock_guard guard(mtx_); return value_map_; } @@ -82,6 +84,8 @@ class guage_t : public metric_t { } std::mutex mtx_; - std::map, sample_t> value_map_; + std::map, sample_t, + std::less>> + value_map_; }; } // namespace cinatra \ No newline at end of file diff --git a/include/cinatra/metric/histogram.hpp b/include/cinatra/metric/histogram.hpp index 9aadc66d..90dc81d5 100644 --- a/include/cinatra/metric/histogram.hpp +++ b/include/cinatra/metric/histogram.hpp @@ -37,7 +37,7 @@ class histogram_t : public metric_t { bucket_counts_[bucket_index]->inc(); } - void observe(const std::pair& label, double value) { + 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(), diff --git a/include/cinatra/metric/metric.hpp b/include/cinatra/metric/metric.hpp index f153d32d..f95197c4 100644 --- a/include/cinatra/metric/metric.hpp +++ b/include/cinatra/metric/metric.hpp @@ -39,7 +39,9 @@ class metric_t { const std::pair& label() { return label_; } - virtual std::map, sample_t> values() { + virtual std::map, sample_t, + std::less>> + values() { return {}; } diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index c5d68ac1..fe99ecd4 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -1,3 +1,5 @@ +#include + #include "cinatra/metric/guage.hpp" #define DOCTEST_CONFIG_IMPLEMENT #include "cinatra/metric/counter.hpp" @@ -48,15 +50,16 @@ TEST_CASE("test guage") { g.dec(); CHECK(g.values().begin()->second.value == 0); - g.inc({"GET", "200"}, 1); - CHECK(g.values()[{"GET", "200"}].value == 1); - g.inc({"GET", "200"}, 2); - CHECK(g.values()[{"GET", "200"}].value == 3); + // method, status code, url + g.inc({"GET", "200", "/"}, 1); + CHECK(g.values()[{"GET", "200", "/"}].value == 1); + g.inc({"GET", "200", "/"}, 2); + CHECK(g.values()[{"GET", "200", "/"}].value == 3); - g.dec({"GET", "200"}, 1); - CHECK(g.values()[{"GET", "200"}].value == 2); - g.dec({"GET", "200"}, 2); - CHECK(g.values()[{"GET", "200"}].value == 0); + g.dec({"GET", "200", "/"}, 1); + CHECK(g.values()[{"GET", "200", "/"}].value == 2); + g.dec({"GET", "200", "/"}, 2); + CHECK(g.values()[{"GET", "200", "/"}].value == 0); } TEST_CASE("test histogram") { From 118bec92e19261093854bc5925d870f163abd24c Mon Sep 17 00:00:00 2001 From: qicosmos Date: Tue, 7 May 2024 14:24:56 +0800 Subject: [PATCH 08/20] fix --- include/cinatra/metric/counter.hpp | 6 +- include/cinatra/metric/guage.hpp | 28 +++++-- include/cinatra/metric/metric.hpp | 8 +- tests/test_metric.cpp | 120 ++++++++++++++++------------- 4 files changed, 92 insertions(+), 70 deletions(-) diff --git a/include/cinatra/metric/counter.hpp b/include/cinatra/metric/counter.hpp index bb96daa5..c2c785e0 100644 --- a/include/cinatra/metric/counter.hpp +++ b/include/cinatra/metric/counter.hpp @@ -7,10 +7,10 @@ class counter_t : public metric_t { public: counter_t() = default; counter_t(std::string name, std::string help, - std::pair labels = {}) - : guage_(std::move(name), std::move(help), std::move(labels)), + std::vector labels_name = {}) + : guage_(std::move(name), std::move(help), labels_name), metric_t(MetricType::Counter, std::move(name), std::move(help), - std::move(labels)) {} + labels_name) {} void inc() { guage_.inc(); } diff --git a/include/cinatra/metric/guage.hpp b/include/cinatra/metric/guage.hpp index 8c51bb12..c98189d3 100644 --- a/include/cinatra/metric/guage.hpp +++ b/include/cinatra/metric/guage.hpp @@ -8,24 +8,28 @@ class guage_t : public metric_t { public: guage_t() = default; guage_t(std::string name, std::string help, - std::pair labels = {}) + std::vector labels_name = {}) : metric_t(MetricType::Counter, std::move(name), std::move(help), - std::move(labels)) {} + std::move(labels_name)) {} void inc() { std::lock_guard guard(mtx_); set_value(value_map_[{}], 1, op_type_t::INC); } - void inc(const std::vector &label, double value = 1) { + void inc(const std::vector &labels_value, double value = 1) { if (value == 0) { return; } 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"); + } std::lock_guard guard(mtx_); - set_value(value_map_[label], value, op_type_t::INC); + set_value(value_map_[labels_value], value, op_type_t::INC); } void dec() { @@ -33,20 +37,28 @@ class guage_t : public metric_t { set_value(value_map_[{}], 1, op_type_t::DEC); } - void dec(const std::vector &label, double value) { + void dec(const std::vector &labels_value, double value) { if (value == 0) { return; } 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"); + } std::lock_guard guard(mtx_); - set_value(value_map_[label], value, op_type_t::DEC); + set_value(value_map_[labels_value], value, op_type_t::DEC); } - void update(const std::vector &label, double value) { + 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_[label], value, op_type_t::SET); + set_value(value_map_[labels_value], value, op_type_t::SET); } void reset() { diff --git a/include/cinatra/metric/metric.hpp b/include/cinatra/metric/metric.hpp index f95197c4..c0e1dda1 100644 --- a/include/cinatra/metric/metric.hpp +++ b/include/cinatra/metric/metric.hpp @@ -25,11 +25,11 @@ class metric_t { public: metric_t() = default; metric_t(MetricType type, std::string name, std::string help, - std::pair labels = {}) + std::vector labels_name = {}) : type_(type), name_(std::move(name)), help_(std::move(help)), - label_(std::move(labels)) {} + labels_name_(std::move(labels_name)) {} std::string_view name() { return name_; } std::string_view help() { return help_; } @@ -37,7 +37,7 @@ class metric_t { MetricType metric_type() { return type_; } void set_metric_type(MetricType type) { type_ = type; } - const std::pair& label() { return label_; } + const std::vector& labels_name() { return labels_name_; } virtual std::map, sample_t, std::less>> @@ -85,7 +85,7 @@ class metric_t { MetricType type_ = MetricType::Nil; std::string name_; std::string help_; - std::pair label_; + std::vector labels_name_; static inline std::mutex mtx_; static inline std::map> metric_map_; }; diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index fe99ecd4..04353667 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -1,5 +1,3 @@ -#include - #include "cinatra/metric/guage.hpp" #define DOCTEST_CONFIG_IMPLEMENT #include "cinatra/metric/counter.hpp" @@ -8,58 +6,72 @@ using namespace cinatra; TEST_CASE("test counter") { - counter_t c("get_count", "get counter", {}); - c.inc(); - CHECK(c.values().begin()->second.value == 1); - c.inc(); - CHECK(c.values().begin()->second.value == 2); - c.inc({}, 0); - - CHECK(c.values().begin()->second.value == 2); - - CHECK_THROWS_AS(c.inc({}, -2), std::invalid_argument); - - c.update({}, 10); - CHECK(c.values().begin()->second.value == 10); - - c.update({}, 0); - CHECK(c.values().begin()->second.value == 0); - - c.inc({"GET", "200"}, 1); - CHECK(c.values()[{"GET", "200"}].value == 1); - c.inc({"GET", "200"}, 2); - CHECK(c.values()[{"GET", "200"}].value == 3); - - c.update({"GET", "200"}, 20); - CHECK(c.values()[{"GET", "200"}].value == 20); - c.reset(); - CHECK(c.values()[{"GET", "200"}].value == 0); - CHECK(c.values().begin()->second.value == 0); + { + counter_t c("get_count", "get counter"); + c.inc(); + CHECK(c.values().begin()->second.value == 1); + c.inc(); + CHECK(c.values().begin()->second.value == 2); + c.inc({}, 0); + + CHECK(c.values().begin()->second.value == 2); + + CHECK_THROWS_AS(c.inc({}, -2), std::invalid_argument); + + c.update({}, 10); + CHECK(c.values().begin()->second.value == 10); + + c.update({}, 0); + CHECK(c.values().begin()->second.value == 0); + } + + { + counter_t c("get_count", "get counter", {"method", "code"}); + c.inc({"GET", "200"}, 1); + CHECK(c.values()[{"GET", "200"}].value == 1); + c.inc({"GET", "200"}, 2); + CHECK(c.values()[{"GET", "200"}].value == 3); + + CHECK_THROWS_AS(c.inc({"GET", "200", "/"}, 2), std::invalid_argument); + + c.update({"GET", "200"}, 20); + CHECK(c.values()[{"GET", "200"}].value == 20); + c.reset(); + CHECK(c.values()[{"GET", "200"}].value == 0); + CHECK(c.values().begin()->second.value == 0); + } } TEST_CASE("test guage") { - guage_t g("get_count", "get counter", {}); - g.inc(); - CHECK(g.values().begin()->second.value == 1); - g.inc(); - CHECK(g.values().begin()->second.value == 2); - g.inc({}, 0); - - g.dec(); - CHECK(g.values().begin()->second.value == 1); - g.dec(); - CHECK(g.values().begin()->second.value == 0); - - // method, status code, url - g.inc({"GET", "200", "/"}, 1); - CHECK(g.values()[{"GET", "200", "/"}].value == 1); - g.inc({"GET", "200", "/"}, 2); - CHECK(g.values()[{"GET", "200", "/"}].value == 3); - - g.dec({"GET", "200", "/"}, 1); - CHECK(g.values()[{"GET", "200", "/"}].value == 2); - g.dec({"GET", "200", "/"}, 2); - CHECK(g.values()[{"GET", "200", "/"}].value == 0); + { + guage_t g("get_count", "get counter"); + g.inc(); + CHECK(g.values().begin()->second.value == 1); + g.inc(); + CHECK(g.values().begin()->second.value == 2); + g.inc({}, 0); + + g.dec(); + CHECK(g.values().begin()->second.value == 1); + g.dec(); + CHECK(g.values().begin()->second.value == 0); + } + + { + guage_t g("get_count", "get counter", {"method", "code", "url"}); + // method, status code, url + g.inc({"GET", "200", "/"}, 1); + CHECK(g.values()[{"GET", "200", "/"}].value == 1); + g.inc({"GET", "200", "/"}, 2); + CHECK(g.values()[{"GET", "200", "/"}].value == 3); + + CHECK_THROWS_AS(g.dec({"GET", "200"}, 1), std::invalid_argument); + + g.dec({"GET", "200", "/"}, 1); + CHECK(g.values()[{"GET", "200", "/"}].value == 2); + g.dec({"GET", "200", "/"}, 2); + CHECK(g.values()[{"GET", "200", "/"}].value == 0); + } } TEST_CASE("test histogram") { @@ -79,14 +91,12 @@ TEST_CASE("test histogram") { TEST_CASE("test register metric") { auto c = std::make_shared(std::string("get_count"), - std::string("get counter"), - std::pair{}); + std::string("get counter")); 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"), - std::string("get counter"), - std::pair{}); + std::string("get counter")); metric_t::regiter_metric(g); CHECK(metric_t::metric_count() == 2); From 910f92af937f9084edd00de7b60a2251858c66f1 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Tue, 7 May 2024 14:38:55 +0800 Subject: [PATCH 09/20] add tests --- include/cinatra/metric/counter.hpp | 75 ++++++++++++++++++++++----- include/cinatra/metric/guage.hpp | 83 ++---------------------------- tests/test_metric.cpp | 6 +++ 3 files changed, 73 insertions(+), 91 deletions(-) diff --git a/include/cinatra/metric/counter.hpp b/include/cinatra/metric/counter.hpp index c2c785e0..cbc36482 100644 --- a/include/cinatra/metric/counter.hpp +++ b/include/cinatra/metric/counter.hpp @@ -1,5 +1,4 @@ #pragma once -#include "guage.hpp" #include "metric.hpp" namespace cinatra { @@ -8,29 +7,79 @@ class counter_t : public metric_t { counter_t() = default; counter_t(std::string name, std::string help, std::vector labels_name = {}) - : guage_(std::move(name), std::move(help), labels_name), - metric_t(MetricType::Counter, std::move(name), std::move(help), - labels_name) {} + : metric_t(MetricType::Counter, std::move(name), std::move(help), + std::move(labels_name)) {} - void inc() { guage_.inc(); } + void inc() { + std::lock_guard guard(mtx_); + set_value(value_map_[{}], 1, op_type_t::INC); + } - void inc(const std::vector &label, double value = 1) { - guage_.inc(label, value); + 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 &label, double value) { - guage_.update(label, value); + 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() { guage_.reset(); } + void reset() { + std::lock_guard guard(mtx_); + for (auto &pair : value_map_) { + pair.second = {}; + } + } std::map, sample_t, std::less>> values() { - return guage_.values(); + std::lock_guard guard(mtx_); + return value_map_; + } + + protected: + enum class op_type_t { INC, DEC, SET }; + + 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"); + } } - private: - guage_t guage_; + 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 cinatra \ No newline at end of file diff --git a/include/cinatra/metric/guage.hpp b/include/cinatra/metric/guage.hpp index c98189d3..a2bff037 100644 --- a/include/cinatra/metric/guage.hpp +++ b/include/cinatra/metric/guage.hpp @@ -1,35 +1,16 @@ #pragma once #include -#include "metric.hpp" +#include "counter.hpp" namespace cinatra { -class guage_t : public metric_t { +class guage_t : public counter_t { public: guage_t() = default; guage_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)) {} - - 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; - } - 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"); - } - std::lock_guard guard(mtx_); - set_value(value_map_[labels_value], value, op_type_t::INC); + : counter_t(std::move(name), std::move(help), std::move(labels_name)) { + set_metric_type(MetricType::Guage); } void dec() { @@ -41,63 +22,9 @@ class guage_t : public metric_t { if (value == 0) { return; } - 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"); - } + validate(labels_value, value); std::lock_guard guard(mtx_); set_value(value_map_[labels_value], value, op_type_t::DEC); } - - 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() { - std::lock_guard guard(mtx_); - return value_map_; - } - - private: - enum class op_type_t { INC, DEC, SET }; - - 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 cinatra \ No newline at end of file diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index 04353667..40ccc4ef 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -8,6 +8,8 @@ using namespace cinatra; TEST_CASE("test counter") { { counter_t c("get_count", "get counter"); + CHECK(c.metric_type() == MetricType::Counter); + CHECK(c.labels_name().empty()); c.inc(); CHECK(c.values().begin()->second.value == 1); c.inc(); @@ -27,6 +29,7 @@ TEST_CASE("test counter") { { counter_t c("get_count", "get counter", {"method", "code"}); + CHECK(c.labels_name() == std::vector{"method", "code"}); c.inc({"GET", "200"}, 1); CHECK(c.values()[{"GET", "200"}].value == 1); c.inc({"GET", "200"}, 2); @@ -45,6 +48,8 @@ TEST_CASE("test counter") { TEST_CASE("test guage") { { guage_t g("get_count", "get counter"); + CHECK(g.metric_type() == MetricType::Guage); + CHECK(g.labels_name().empty()); g.inc(); CHECK(g.values().begin()->second.value == 1); g.inc(); @@ -59,6 +64,7 @@ TEST_CASE("test guage") { { guage_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); CHECK(g.values()[{"GET", "200", "/"}].value == 1); From a3132df43a7e33b79827cedde631c084150f46af Mon Sep 17 00:00:00 2001 From: qicosmos Date: Tue, 7 May 2024 14:59:35 +0800 Subject: [PATCH 10/20] add method --- include/cinatra/metric/counter.hpp | 6 ++++++ include/cinatra/metric/guage.hpp | 8 +++++++- tests/test_metric.cpp | 9 +++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/include/cinatra/metric/counter.hpp b/include/cinatra/metric/counter.hpp index cbc36482..99ba74a3 100644 --- a/include/cinatra/metric/counter.hpp +++ b/include/cinatra/metric/counter.hpp @@ -10,6 +10,12 @@ class counter_t : public metric_t { : 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); diff --git a/include/cinatra/metric/guage.hpp b/include/cinatra/metric/guage.hpp index a2bff037..252741a5 100644 --- a/include/cinatra/metric/guage.hpp +++ b/include/cinatra/metric/guage.hpp @@ -13,12 +13,18 @@ class guage_t : public counter_t { 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) { + void dec(const std::vector& labels_value, double value) { if (value == 0) { return; } diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index 40ccc4ef..12a4edf6 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -27,6 +27,15 @@ TEST_CASE("test counter") { CHECK(c.values().begin()->second.value == 0); } + { + 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", + std::vector{"method", "code"}); + CHECK(g->name() == "get_count"); + } + { counter_t c("get_count", "get counter", {"method", "code"}); CHECK(c.labels_name() == std::vector{"method", "code"}); From c12d392f644312536318261703b01f42e5b4d0e9 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Tue, 7 May 2024 17:41:57 +0800 Subject: [PATCH 11/20] serialize counter and guage --- example/main.cpp | 59 ++++++++++++++++++++++++++++++ include/cinatra/metric/counter.hpp | 34 ++++++++++++++++- include/cinatra/metric/metric.hpp | 20 +++++++++- tests/test_metric.cpp | 1 + 4 files changed, 112 insertions(+), 2 deletions(-) diff --git a/example/main.cpp b/example/main.cpp index 4727aadb..f10ad9a9 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -7,6 +7,7 @@ #include #include "../include/cinatra.hpp" +#include "cinatra/metric/guage.hpp" using namespace cinatra; using namespace std::chrono_literals; @@ -382,7 +383,65 @@ async_simple::coro::Lazy basic_usage() { #endif } +void use_metric() { + auto c = std::make_shared("request_count", "request count", + std::vector{"method", "url"}); + auto failed = std::make_shared("not_found_request_count", + "not found request count", + std::vector{"method", "code", "url"}); + auto total = + std::make_shared("total_request_count", "total request count"); + metric_t::regiter_metric(c); + metric_t::regiter_metric(total); + metric_t::regiter_metric(failed); + + coro_http_server server(1, 9001); + server.set_default_handler( + [&](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + failed->inc({std::string(req.get_method()), + std::to_string((int)status_type::not_found), + std::string(req.get_url())}); + total->inc(); + resp.set_status_and_content(status_type::not_found, "not found"); + co_return; + }); + + 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())}); + total->inc(); + }); + + server.set_http_handler( + "/test", [&](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())}); + total->inc(); + }); + + server.set_http_handler( + "/", [&](coro_http_request &req, coro_http_response &resp) { + resp.set_status_and_content(status_type::ok, "ok"); + total->inc(); + }); + + server.set_http_handler( + "/metrics", [](coro_http_request &req, coro_http_response &resp) { + std::string str; + auto map = metric_t::collect(); + for (auto &[k, m] : map) { + m->serialize(str); + } + std::cout << str; + resp.set_status_and_content(status_type::ok, std::move(str)); + }); + server.sync_start(); +} + int main() { + // use_metric(); async_simple::coro::syncAwait(basic_usage()); async_simple::coro::syncAwait(use_aspects()); async_simple::coro::syncAwait(static_file_server()); diff --git a/include/cinatra/metric/counter.hpp b/include/cinatra/metric/counter.hpp index 99ba74a3..4e13c3ad 100644 --- a/include/cinatra/metric/counter.hpp +++ b/include/cinatra/metric/counter.hpp @@ -48,13 +48,45 @@ class counter_t : public metric_t { std::map, sample_t, std::less>> - values() { + values() override { std::lock_guard guard(mtx_); return value_map_; } + void serialize(std::string &str) override { + 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)); + 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) { diff --git a/include/cinatra/metric/metric.hpp b/include/cinatra/metric/metric.hpp index c0e1dda1..6f24f990 100644 --- a/include/cinatra/metric/metric.hpp +++ b/include/cinatra/metric/metric.hpp @@ -35,7 +35,21 @@ class metric_t { std::string_view help() { return help_; } MetricType metric_type() { return type_; } - void set_metric_type(MetricType type) { type_ = type; } + + std::string_view metric_name() { + switch (type_) { + case MetricType::Counter: + return "counter"; + case MetricType::Guage: + return "guage"; + case MetricType::Histogram: + return "histogram"; + case MetricType::Summary: + return "summary"; + case MetricType::Nil: + return "unknown"; + } + } const std::vector& labels_name() { return labels_name_; } @@ -45,6 +59,8 @@ class metric_t { return {}; } + virtual void serialize(std::string& out) {} + static void regiter_metric(std::shared_ptr metric) { std::scoped_lock guard(mtx_); std::string name(metric->name()); @@ -82,6 +98,8 @@ class metric_t { } protected: + void set_metric_type(MetricType type) { type_ = type; } + MetricType type_ = MetricType::Nil; std::string name_; std::string help_; diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index 12a4edf6..a29c7e28 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -34,6 +34,7 @@ TEST_CASE("test counter") { auto g = std::make_shared("get_count", "get counter", std::vector{"method", "code"}); CHECK(g->name() == "get_count"); + CHECK(g->metric_name() == "guage"); } { From 3e0fa20b859d24f5bb077bc9987bb64c112a3ef7 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Tue, 7 May 2024 17:48:40 +0800 Subject: [PATCH 12/20] modify collect --- example/main.cpp | 4 ++-- include/cinatra/metric/histogram.hpp | 2 ++ include/cinatra/metric/metric.hpp | 11 +++++++++++ tests/test_metric.cpp | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/example/main.cpp b/example/main.cpp index f10ad9a9..f9c8089f 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -430,8 +430,8 @@ void use_metric() { server.set_http_handler( "/metrics", [](coro_http_request &req, coro_http_response &resp) { std::string str; - auto map = metric_t::collect(); - for (auto &[k, m] : map) { + auto metrics = metric_t::collect(); + for (auto &m : metrics) { m->serialize(str); } std::cout << str; diff --git a/include/cinatra/metric/histogram.hpp b/include/cinatra/metric/histogram.hpp index 90dc81d5..4f2dc57c 100644 --- a/include/cinatra/metric/histogram.hpp +++ b/include/cinatra/metric/histogram.hpp @@ -63,6 +63,8 @@ class histogram_t : public metric_t { return bucket_counts_; } + void serialize(std::string& str) override {} + private: template bool is_strict_sorted(ForwardIterator first, ForwardIterator last) { diff --git a/include/cinatra/metric/metric.hpp b/include/cinatra/metric/metric.hpp index 6f24f990..61d10ed0 100644 --- a/include/cinatra/metric/metric.hpp +++ b/include/cinatra/metric/metric.hpp @@ -76,6 +76,17 @@ class metric_t { } static auto collect() { + std::vector> metrics; + { + std::scoped_lock guard(mtx_); + for (auto& pair : metric_map_) { + metrics.push_back(pair.second); + } + } + return metrics; + } + + static auto metric_map() { std::scoped_lock guard(mtx_); return metric_map_; } diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index a29c7e28..a2d0e3cf 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -121,7 +121,7 @@ TEST_CASE("test register metric") { c->inc(); g->inc(); - auto map = metric_t::collect(); + auto map = metric_t::metric_map(); CHECK(map["get_count"]->values()[{}].value == 1); CHECK(map["get_guage_count"]->values()[{}].value == 1); From 57b6be30e3b8f7c53a901420e96309e5bb65487b Mon Sep 17 00:00:00 2001 From: qicosmos Date: Wed, 8 May 2024 10:49:02 +0800 Subject: [PATCH 13/20] serialize histogram --- include/cinatra/metric/counter.hpp | 5 +++- include/cinatra/metric/histogram.hpp | 37 +++++++++++++++++++++++++++- include/cinatra/metric/metric.hpp | 2 +- tests/test_metric.cpp | 21 ++++++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/include/cinatra/metric/counter.hpp b/include/cinatra/metric/counter.hpp index 4e13c3ad..f58b95af 100644 --- a/include/cinatra/metric/counter.hpp +++ b/include/cinatra/metric/counter.hpp @@ -48,7 +48,10 @@ class counter_t : public metric_t { std::map, sample_t, std::less>> - values() override { + values(bool need_lock = true) override { + if (need_lock) { + return value_map_; + } std::lock_guard guard(mtx_); return value_map_; } diff --git a/include/cinatra/metric/histogram.hpp b/include/cinatra/metric/histogram.hpp index 4f2dc57c..02407dff 100644 --- a/include/cinatra/metric/histogram.hpp +++ b/include/cinatra/metric/histogram.hpp @@ -63,7 +63,42 @@ class histogram_t : public metric_t { return bucket_counts_; } - void serialize(std::string& str) override {} + void serialize(std::string& str) override { + 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)); + str.append(" ").append(std::to_string(sample.timestamp)).append("\n"); + } + } + str.append(name_) + .append("_count ") + .append(std::to_string(count)) + .append("\n"); + str.append(name_) + .append("_sum ") + .append(std::to_string((sum_->values()[{}].value))) + .append("\n"); + } private: template diff --git a/include/cinatra/metric/metric.hpp b/include/cinatra/metric/metric.hpp index 61d10ed0..33fa8fcc 100644 --- a/include/cinatra/metric/metric.hpp +++ b/include/cinatra/metric/metric.hpp @@ -55,7 +55,7 @@ class metric_t { virtual std::map, sample_t, std::less>> - values() { + values(bool need_lock = true) { return {}; } diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index a2d0e3cf..fbcfe2dd 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -45,6 +45,13 @@ TEST_CASE("test counter") { c.inc({"GET", "200"}, 2); CHECK(c.values()[{"GET", "200"}].value == 3); + std::string str; + c.serialize(str); + std::cout << str; + CHECK(str.find("# TYPE get_count counter") != std::string::npos); + CHECK(str.find("get_count{method=\"GET\",code=\"200\"} 3") != + std::string::npos); + CHECK_THROWS_AS(c.inc({"GET", "200", "/"}, 2), std::invalid_argument); c.update({"GET", "200"}, 20); @@ -81,6 +88,13 @@ TEST_CASE("test guage") { g.inc({"GET", "200", "/"}, 2); CHECK(g.values()[{"GET", "200", "/"}].value == 3); + std::string str; + g.serialize(str); + std::cout << str; + CHECK(str.find("# TYPE get_count guage") != std::string::npos); + CHECK(str.find("get_count{method=\"GET\",code=\"200\",url=\"/\"} 3") != + std::string::npos); + CHECK_THROWS_AS(g.dec({"GET", "200"}, 1), std::invalid_argument); g.dec({"GET", "200", "/"}, 1); @@ -103,6 +117,13 @@ TEST_CASE("test histogram") { CHECK(counts[5]->values()[{}].value == 1); h.observe(1); CHECK(counts[0]->values()[{}].value == 1); + std::string str; + h.serialize(str); + std::cout << str; + CHECK(str.find("test_count") != std::string::npos); + CHECK(str.find("test_sum") != std::string::npos); + CHECK(str.find("test_bucket{le=\"5") != std::string::npos); + CHECK(str.find("test_bucket{le=\"+Inf\"}") != std::string::npos); } TEST_CASE("test register metric") { From 5eeedfd16736fd3ee305ce3f239f224b7a19f8b8 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Thu, 9 May 2024 11:09:52 +0800 Subject: [PATCH 14/20] fix some --- example/main.cpp | 21 +++++++++++++++++++++ include/cinatra/metric/counter.hpp | 9 +++++++-- include/cinatra/metric/histogram.hpp | 12 +++++++++--- include/cinatra/metric/metric.hpp | 2 ++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/example/main.cpp b/example/main.cpp index f9c8089f..e26215ec 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -8,6 +8,7 @@ #include "../include/cinatra.hpp" #include "cinatra/metric/guage.hpp" +#include "cinatra/metric/histogram.hpp" using namespace cinatra; using namespace std::chrono_literals; @@ -391,9 +392,28 @@ void use_metric() { std::vector{"method", "code", "url"}); auto total = std::make_shared("total_request_count", "total request count"); + + auto h = std::make_shared( + std::string("test"), std::vector{5.0, 10.0, 20.0, 50.0, 100.0}); + h->observe(23); + h->observe(42); + h->observe(60); + h->observe(120); + h->observe(1); + metric_t::regiter_metric(c); metric_t::regiter_metric(total); metric_t::regiter_metric(failed); + metric_t::regiter_metric(h); + + std::thread thd([=] { + while (true) { + c->inc({"GET", "/test"}); + total->inc(); + std::this_thread::sleep_for(1s); + } + }); + thd.detach(); coro_http_server server(1, 9001); server.set_default_handler( @@ -435,6 +455,7 @@ void use_metric() { m->serialize(str); } std::cout << str; + resp.need_date_head(false); resp.set_status_and_content(status_type::ok, std::move(str)); }); server.sync_start(); diff --git a/include/cinatra/metric/counter.hpp b/include/cinatra/metric/counter.hpp index f58b95af..ef3b0b50 100644 --- a/include/cinatra/metric/counter.hpp +++ b/include/cinatra/metric/counter.hpp @@ -57,6 +57,9 @@ class counter_t : public metric_t { } 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_) @@ -75,8 +78,10 @@ class counter_t : public metric_t { } str.append(std::to_string((int64_t)sample.value)); - str.append(" "); - str.append(std::to_string(sample.timestamp)); + if (enable_timestamp_) { + str.append(" "); + str.append(std::to_string(sample.timestamp)); + } str.append("\n"); } } diff --git a/include/cinatra/metric/histogram.hpp b/include/cinatra/metric/histogram.hpp index 02407dff..b9c5909a 100644 --- a/include/cinatra/metric/histogram.hpp +++ b/include/cinatra/metric/histogram.hpp @@ -64,6 +64,9 @@ class histogram_t : public metric_t { } 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_) @@ -86,8 +89,11 @@ class histogram_t : public metric_t { } count += sample.value; - str.append(std::to_string(count)); - str.append(" ").append(std::to_string(sample.timestamp)).append("\n"); + if (enable_timestamp_) { + str.append(std::to_string(count)); + str.append(" ").append(std::to_string(sample.timestamp)); + } + str.append("\n"); } } str.append(name_) @@ -96,7 +102,7 @@ class histogram_t : public metric_t { .append("\n"); str.append(name_) .append("_sum ") - .append(std::to_string((sum_->values()[{}].value))) + .append(std::to_string((sum_->values(false)[{}].value))) .append("\n"); } diff --git a/include/cinatra/metric/metric.hpp b/include/cinatra/metric/metric.hpp index 33fa8fcc..4926c3b7 100644 --- a/include/cinatra/metric/metric.hpp +++ b/include/cinatra/metric/metric.hpp @@ -52,6 +52,7 @@ class metric_t { } const std::vector& labels_name() { return labels_name_; } + void enable_timestamp(bool r) { enable_timestamp_ = r; } virtual std::map, sample_t, std::less>> @@ -115,6 +116,7 @@ class metric_t { std::string name_; std::string help_; std::vector labels_name_; + bool enable_timestamp_ = false; static inline std::mutex mtx_; static inline std::map> metric_map_; }; From 99937fd3c16daad43e523a75ef2233ec40303ef6 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Thu, 9 May 2024 17:43:55 +0800 Subject: [PATCH 15/20] summary --- example/main.cpp | 14 +- .../cinatra/metric/detail/ckms_quantiles.hpp | 171 ++++++++++++++++++ .../metric/detail/time_window_quantiles.hpp | 52 ++++++ include/cinatra/metric/histogram.hpp | 10 +- include/cinatra/metric/summary.hpp | 59 ++++++ tests/test_metric.cpp | 23 +++ 6 files changed, 324 insertions(+), 5 deletions(-) create mode 100644 include/cinatra/metric/detail/ckms_quantiles.hpp create mode 100644 include/cinatra/metric/detail/time_window_quantiles.hpp create mode 100644 include/cinatra/metric/summary.hpp diff --git a/example/main.cpp b/example/main.cpp index e26215ec..1376371c 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -9,6 +9,7 @@ #include "../include/cinatra.hpp" #include "cinatra/metric/guage.hpp" #include "cinatra/metric/histogram.hpp" +#include "cinatra/metric/summary.hpp" using namespace cinatra; using namespace std::chrono_literals; @@ -401,15 +402,26 @@ void use_metric() { h->observe(120); h->observe(1); + auto summary = std::make_shared( + std::string("test_summary"), std::string("summary help"), + summary_t::Quantiles{ + {0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}); + metric_t::regiter_metric(c); metric_t::regiter_metric(total); metric_t::regiter_metric(failed); metric_t::regiter_metric(h); + metric_t::regiter_metric(summary); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distr(1, 100); - std::thread thd([=] { + std::thread thd([&] { while (true) { c->inc({"GET", "/test"}); total->inc(); + summary->observe(distr(gen)); std::this_thread::sleep_for(1s); } }); diff --git a/include/cinatra/metric/detail/ckms_quantiles.hpp b/include/cinatra/metric/detail/ckms_quantiles.hpp new file mode 100644 index 00000000..5d6b02c5 --- /dev/null +++ b/include/cinatra/metric/detail/ckms_quantiles.hpp @@ -0,0 +1,171 @@ +#pragma once +#include +#include + +// https://github.com/jupp0r/prometheus-cpp/blob/master/core/include/prometheus/detail/ckms_quantiles.h + +namespace cinatra { +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; + } + } + } + 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 cinatra \ No newline at end of file diff --git a/include/cinatra/metric/detail/time_window_quantiles.hpp b/include/cinatra/metric/detail/time_window_quantiles.hpp new file mode 100644 index 00000000..68223dc8 --- /dev/null +++ b/include/cinatra/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 cinatra { +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 cinatra \ No newline at end of file diff --git a/include/cinatra/metric/histogram.hpp b/include/cinatra/metric/histogram.hpp index b9c5909a..4b437d43 100644 --- a/include/cinatra/metric/histogram.hpp +++ b/include/cinatra/metric/histogram.hpp @@ -96,14 +96,16 @@ class histogram_t : public metric_t { str.append("\n"); } } - str.append(name_) - .append("_count ") - .append(std::to_string(count)) - .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: diff --git a/include/cinatra/metric/summary.hpp b/include/cinatra/metric/summary.hpp new file mode 100644 index 00000000..5e19311b --- /dev/null +++ b/include/cinatra/metric/summary.hpp @@ -0,0 +1,59 @@ +#pragma once +#include "detail/time_window_quantiles.hpp" +#include "metric.hpp" + +namespace cinatra { +class summary_t : public metric_t { + public: + summary_t() = default; + using Quantiles = std::vector; + summary_t(std::string name, std::string help, Quantiles quantiles, + std::chrono::milliseconds max_age = std::chrono::seconds{60}, + int age_buckets = 5) + : quantiles_{std::move(quantiles)}, + quantile_values_{quantiles_, max_age, age_buckets}, + metric_t(MetricType::Summary, std::move(name), std::move(help)) {} + + void observe(double value) { + std::lock_guard lock(mutex_); + + count_ += 1; + sum_ += value; + quantile_values_.insert(value); + } + + void serialize(std::string& str) override { + if (quantiles_.empty()) { + return; + } + + str.append("# HELP ").append(name_).append(" ").append(help_).append("\n"); + str.append("# TYPE ") + .append(name_) + .append(" ") + .append(metric_name()) + .append("\n"); + + for (const auto& quantile : quantiles_) { + str.append(name_); + str.append("{quantile=\""); + str.append(std::to_string(quantile.quantile)).append("\"} "); + str.append(std::to_string(quantile_values_.get(quantile.quantile))) + .append("\n"); + } + + str.append(name_).append("_sum ").append(std::to_string(sum_)).append("\n"); + str.append(name_) + .append("_count ") + .append(std::to_string(count_)) + .append("\n"); + } + + private: + Quantiles quantiles_; + mutable std::mutex mutex_; + std::uint64_t count_{}; + double sum_{}; + TimeWindowQuantiles quantile_values_; +}; +} // namespace cinatra \ No newline at end of file diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index fbcfe2dd..42d68b6c 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -1,7 +1,10 @@ #include "cinatra/metric/guage.hpp" #define DOCTEST_CONFIG_IMPLEMENT +#include + #include "cinatra/metric/counter.hpp" #include "cinatra/metric/histogram.hpp" +#include "cinatra/metric/summary.hpp" #include "doctest/doctest.h" using namespace cinatra; @@ -126,6 +129,26 @@ TEST_CASE("test histogram") { CHECK(str.find("test_bucket{le=\"+Inf\"}") != std::string::npos); } +TEST_CASE("test summary") { + summary_t summary{"test_summary", + "summary help", + {{0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}}; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distr(1, 100); + for (int i = 0; i < 50; i++) { + summary.observe(distr(gen)); + } + + std::string str; + summary.serialize(str); + std::cout << str; + CHECK(str.find("test_summary") != std::string::npos); + CHECK(str.find("test_summary_count") != std::string::npos); + CHECK(str.find("test_summary_sum") != std::string::npos); + CHECK(str.find("test_summary{quantile=\"") != std::string::npos); +} + TEST_CASE("test register metric") { auto c = std::make_shared(std::string("get_count"), std::string("get counter")); From 16bb9ba9eed750515e63ca7ed7f6170eff7118a6 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Thu, 9 May 2024 18:13:42 +0800 Subject: [PATCH 16/20] fix --- example/main.cpp | 6 +----- include/cinatra/metric/histogram.hpp | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/example/main.cpp b/example/main.cpp index 1376371c..e40863f4 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -396,11 +396,6 @@ void use_metric() { auto h = std::make_shared( std::string("test"), std::vector{5.0, 10.0, 20.0, 50.0, 100.0}); - h->observe(23); - h->observe(42); - h->observe(60); - h->observe(120); - h->observe(1); auto summary = std::make_shared( std::string("test_summary"), std::string("summary help"), @@ -421,6 +416,7 @@ void use_metric() { while (true) { c->inc({"GET", "/test"}); total->inc(); + h->observe(distr(gen)); summary->observe(distr(gen)); std::this_thread::sleep_for(1s); } diff --git a/include/cinatra/metric/histogram.hpp b/include/cinatra/metric/histogram.hpp index 4b437d43..6d7d28ce 100644 --- a/include/cinatra/metric/histogram.hpp +++ b/include/cinatra/metric/histogram.hpp @@ -89,8 +89,8 @@ class histogram_t : public metric_t { } count += sample.value; + str.append(std::to_string(count)); if (enable_timestamp_) { - str.append(std::to_string(count)); str.append(" ").append(std::to_string(sample.timestamp)); } str.append("\n"); From 275c284e5db4d4fa6e7b4fbab746ab806f362a98 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Fri, 10 May 2024 13:39:18 +0800 Subject: [PATCH 17/20] fix compile --- include/cinatra/metric/detail/ckms_quantiles.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/cinatra/metric/detail/ckms_quantiles.hpp b/include/cinatra/metric/detail/ckms_quantiles.hpp index 5d6b02c5..a9c5afc0 100644 --- a/include/cinatra/metric/detail/ckms_quantiles.hpp +++ b/include/cinatra/metric/detail/ckms_quantiles.hpp @@ -43,6 +43,7 @@ class CKMSQuantiles { compress(); } } + double get(double q) { insertBatch(); compress(); @@ -95,7 +96,9 @@ class CKMSQuantiles { minError = error; } } + return minError; } + bool insertBatch() { if (buffer_count_ == 0) { return false; From 7c81a7905bdf01a1eab0f4a9e97e72a3d1378f6d Mon Sep 17 00:00:00 2001 From: qicosmos Date: Fri, 10 May 2024 13:46:35 +0800 Subject: [PATCH 18/20] move path --- example/main.cpp | 6 +++--- include/cinatra/{ => ylt}/metric/counter.hpp | 0 .../cinatra/{ => ylt}/metric/detail/ckms_quantiles.hpp | 0 .../{ => ylt}/metric/detail/time_window_quantiles.hpp | 0 include/cinatra/{ => ylt}/metric/guage.hpp | 0 include/cinatra/{ => ylt}/metric/histogram.hpp | 0 include/cinatra/{ => ylt}/metric/metric.hpp | 0 include/cinatra/{ => ylt}/metric/summary.hpp | 1 - tests/test_metric.cpp | 8 ++++---- 9 files changed, 7 insertions(+), 8 deletions(-) rename include/cinatra/{ => ylt}/metric/counter.hpp (100%) rename include/cinatra/{ => ylt}/metric/detail/ckms_quantiles.hpp (100%) rename include/cinatra/{ => ylt}/metric/detail/time_window_quantiles.hpp (100%) rename include/cinatra/{ => ylt}/metric/guage.hpp (100%) rename include/cinatra/{ => ylt}/metric/histogram.hpp (100%) rename include/cinatra/{ => ylt}/metric/metric.hpp (100%) rename include/cinatra/{ => ylt}/metric/summary.hpp (98%) diff --git a/example/main.cpp b/example/main.cpp index e40863f4..da02baf4 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -7,9 +7,9 @@ #include #include "../include/cinatra.hpp" -#include "cinatra/metric/guage.hpp" -#include "cinatra/metric/histogram.hpp" -#include "cinatra/metric/summary.hpp" +#include "cinatra/ylt/metric/guage.hpp" +#include "cinatra/ylt/metric/histogram.hpp" +#include "cinatra/ylt/metric/summary.hpp" using namespace cinatra; using namespace std::chrono_literals; diff --git a/include/cinatra/metric/counter.hpp b/include/cinatra/ylt/metric/counter.hpp similarity index 100% rename from include/cinatra/metric/counter.hpp rename to include/cinatra/ylt/metric/counter.hpp diff --git a/include/cinatra/metric/detail/ckms_quantiles.hpp b/include/cinatra/ylt/metric/detail/ckms_quantiles.hpp similarity index 100% rename from include/cinatra/metric/detail/ckms_quantiles.hpp rename to include/cinatra/ylt/metric/detail/ckms_quantiles.hpp diff --git a/include/cinatra/metric/detail/time_window_quantiles.hpp b/include/cinatra/ylt/metric/detail/time_window_quantiles.hpp similarity index 100% rename from include/cinatra/metric/detail/time_window_quantiles.hpp rename to include/cinatra/ylt/metric/detail/time_window_quantiles.hpp diff --git a/include/cinatra/metric/guage.hpp b/include/cinatra/ylt/metric/guage.hpp similarity index 100% rename from include/cinatra/metric/guage.hpp rename to include/cinatra/ylt/metric/guage.hpp diff --git a/include/cinatra/metric/histogram.hpp b/include/cinatra/ylt/metric/histogram.hpp similarity index 100% rename from include/cinatra/metric/histogram.hpp rename to include/cinatra/ylt/metric/histogram.hpp diff --git a/include/cinatra/metric/metric.hpp b/include/cinatra/ylt/metric/metric.hpp similarity index 100% rename from include/cinatra/metric/metric.hpp rename to include/cinatra/ylt/metric/metric.hpp diff --git a/include/cinatra/metric/summary.hpp b/include/cinatra/ylt/metric/summary.hpp similarity index 98% rename from include/cinatra/metric/summary.hpp rename to include/cinatra/ylt/metric/summary.hpp index 5e19311b..6cdcd84f 100644 --- a/include/cinatra/metric/summary.hpp +++ b/include/cinatra/ylt/metric/summary.hpp @@ -5,7 +5,6 @@ namespace cinatra { class summary_t : public metric_t { public: - summary_t() = default; using Quantiles = std::vector; summary_t(std::string name, std::string help, Quantiles quantiles, std::chrono::milliseconds max_age = std::chrono::seconds{60}, diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index 42d68b6c..41cbca40 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -1,10 +1,10 @@ -#include "cinatra/metric/guage.hpp" +#include "cinatra/ylt/metric/guage.hpp" #define DOCTEST_CONFIG_IMPLEMENT #include -#include "cinatra/metric/counter.hpp" -#include "cinatra/metric/histogram.hpp" -#include "cinatra/metric/summary.hpp" +#include "cinatra/ylt/metric/counter.hpp" +#include "cinatra/ylt/metric/histogram.hpp" +#include "cinatra/ylt/metric/summary.hpp" #include "doctest/doctest.h" using namespace cinatra; From e8ffb5b56d01f305946969971f04e383a07f8803 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Fri, 10 May 2024 16:08:26 +0800 Subject: [PATCH 19/20] add doc --- example/main.cpp | 5 +- include/cinatra/ylt/metric/histogram.hpp | 3 +- lang/metrict_introduction.md | 261 +++++++++++++++++++++++ tests/test_metric.cpp | 2 +- 4 files changed, 266 insertions(+), 5 deletions(-) create mode 100644 lang/metrict_introduction.md diff --git a/example/main.cpp b/example/main.cpp index da02baf4..79a9b21a 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -394,8 +394,9 @@ void use_metric() { auto total = std::make_shared("total_request_count", "total request count"); - auto h = std::make_shared( - std::string("test"), std::vector{5.0, 10.0, 20.0, 50.0, 100.0}); + auto h = + std::make_shared(std::string("test"), std::string("help"), + std::vector{5.0, 10.0, 20.0, 50.0, 100.0}); auto summary = std::make_shared( std::string("test_summary"), std::string("summary help"), diff --git a/include/cinatra/ylt/metric/histogram.hpp b/include/cinatra/ylt/metric/histogram.hpp index 6d7d28ce..3bf29e0b 100644 --- a/include/cinatra/ylt/metric/histogram.hpp +++ b/include/cinatra/ylt/metric/histogram.hpp @@ -11,8 +11,7 @@ namespace cinatra { class histogram_t : public metric_t { public: - histogram_t(std::string name, std::vector buckets, - std::string help = "") + 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()) { diff --git a/lang/metrict_introduction.md b/lang/metrict_introduction.md new file mode 100644 index 00000000..5f93e5ba --- /dev/null +++ b/lang/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 +``` + +# 如何使用cinatra的 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 diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index 41cbca40..b768949e 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -108,7 +108,7 @@ TEST_CASE("test guage") { } TEST_CASE("test histogram") { - histogram_t h("test", {5.0, 10.0, 20.0, 50.0, 100.0}); + histogram_t h("test", "help", {5.0, 10.0, 20.0, 50.0, 100.0}); h.observe(23); auto counts = h.bucket_counts(); CHECK(counts[3]->values()[{}].value == 1); From b7fa272039846638202f8acc0445f3a476b8069a Mon Sep 17 00:00:00 2001 From: qicosmos Date: Fri, 10 May 2024 17:35:37 +0800 Subject: [PATCH 20/20] rename --- example/main.cpp | 4 ++-- include/cinatra/ylt/metric/{guage.hpp => gauge.hpp} | 10 +++++----- include/cinatra/ylt/metric/histogram.hpp | 4 ++-- lang/metrict_introduction.md | 4 ++-- tests/test_metric.cpp | 10 +++++----- 5 files changed, 16 insertions(+), 16 deletions(-) rename include/cinatra/ylt/metric/{guage.hpp => gauge.hpp} (83%) diff --git a/example/main.cpp b/example/main.cpp index 79a9b21a..fc922a60 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -7,7 +7,7 @@ #include #include "../include/cinatra.hpp" -#include "cinatra/ylt/metric/guage.hpp" +#include "cinatra/ylt/metric/gauge.hpp" #include "cinatra/ylt/metric/histogram.hpp" #include "cinatra/ylt/metric/summary.hpp" @@ -388,7 +388,7 @@ async_simple::coro::Lazy basic_usage() { void use_metric() { auto c = std::make_shared("request_count", "request count", std::vector{"method", "url"}); - auto failed = std::make_shared("not_found_request_count", + auto failed = std::make_shared("not_found_request_count", "not found request count", std::vector{"method", "code", "url"}); auto total = diff --git a/include/cinatra/ylt/metric/guage.hpp b/include/cinatra/ylt/metric/gauge.hpp similarity index 83% rename from include/cinatra/ylt/metric/guage.hpp rename to include/cinatra/ylt/metric/gauge.hpp index 252741a5..94c2ecad 100644 --- a/include/cinatra/ylt/metric/guage.hpp +++ b/include/cinatra/ylt/metric/gauge.hpp @@ -4,18 +4,18 @@ #include "counter.hpp" namespace cinatra { -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/cinatra/ylt/metric/histogram.hpp b/include/cinatra/ylt/metric/histogram.hpp index 3bf29e0b..a0d58339 100644 --- a/include/cinatra/ylt/metric/histogram.hpp +++ b/include/cinatra/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 cinatra \ No newline at end of file diff --git a/lang/metrict_introduction.md b/lang/metrict_introduction.md index 5f93e5ba..da2e7028 100644 --- a/lang/metrict_introduction.md +++ b/lang/metrict_introduction.md @@ -67,7 +67,7 @@ prometheus_tsdb_wal_fsync_duration_seconds_sum 2.888716127000002 prometheus_tsdb_wal_fsync_duration_seconds_count 216 ``` -# 如何使用cinatra的 metric功能 +# 如何使用metric功能 ## 使用counter指标统计http 请求总数 http 请求数量随着时间推移是不断增加的,不可能会减少,因此使用counter类型的指标是合适的,如果数量可能会减少则应该使用guage类型的指标。 @@ -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); diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index b768949e..db0caabb 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -1,4 +1,4 @@ -#include "cinatra/ylt/metric/guage.hpp" +#include "cinatra/ylt/metric/gauge.hpp" #define DOCTEST_CONFIG_IMPLEMENT #include @@ -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);