From ebbb45dad952bbeb80de32f5f65b3c2df5432c8e Mon Sep 17 00:00:00 2001 From: qicosmos Date: Fri, 14 Jun 2024 16:55:43 +0800 Subject: [PATCH 1/3] serialize metric to json (#598) --- cmake/develop.cmake | 6 + example/CMakeLists.txt | 3 + example/main.cpp | 6 +- iguana | 2 +- include/cinatra/coro_http_server.hpp | 21 +- include/cinatra/metric_conf.hpp | 40 +- include/cinatra/ylt/metric/counter.hpp | 70 ++- .../ylt/metric/detail/ckms_quantiles.hpp | 4 +- .../metric/detail/time_window_quantiles.hpp | 4 +- include/cinatra/ylt/metric/gauge.hpp | 4 +- include/cinatra/ylt/metric/histogram.hpp | 210 +++++++- include/cinatra/ylt/metric/metric.hpp | 292 +++++++++- include/cinatra/ylt/metric/summary.hpp | 357 +++++++++++- lang/how_to_use_metrics.md | 152 +++++- press_tool/CMakeLists.txt | 3 + tests/CMakeLists.txt | 6 + tests/test_metric.cpp | 508 +++++++++++++++++- 17 files changed, 1572 insertions(+), 116 deletions(-) diff --git a/cmake/develop.cmake b/cmake/develop.cmake index 864fb2b5..c8e1011c 100644 --- a/cmake/develop.cmake +++ b/cmake/develop.cmake @@ -53,6 +53,12 @@ if(ENABLE_SANITIZER AND NOT MSVC) endif() endif() +option(ENABLE_METRIC_JSON "Enable serialize metric to json" OFF) +if(ENABLE_METRIC_JSON) + add_definitions(-DCINATRA_ENABLE_METRIC_JSON) + message(STATUS "Enable serialize metric to json") +endif() + SET(ENABLE_GZIP OFF) SET(ENABLE_SSL OFF) SET(ENABLE_CLIENT_SSL OFF) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index e8d17c79..fdfa250d 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -7,6 +7,9 @@ set(CINATRA_EXAMPLE include_directories(../include) include_directories(../include/cinatra) +if(ENABLE_METRIC_JSON) + include_directories(../iguana) +endif() add_executable(${project_name} ${CINATRA_EXAMPLE}) target_compile_definitions(${project_name} PRIVATE ASYNC_SIMPLE_HAS_NOT_AIO) diff --git a/example/main.cpp b/example/main.cpp index 8af75539..0ae1de08 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -10,7 +10,7 @@ #include "metric_conf.hpp" using namespace cinatra; -using namespace ylt; +using namespace ylt::metric; using namespace std::chrono_literals; void create_file(std::string filename, size_t file_size = 64) { @@ -429,7 +429,7 @@ async_simple::coro::Lazy basic_usage() { } void use_metric() { - using namespace ylt; + using namespace ylt::metric; auto c = std::make_shared("request_count", "request count", std::vector{"method", "url"}); @@ -537,7 +537,7 @@ void metrics_example() { "/", [&](coro_http_request &req, coro_http_response &resp) { resp.set_status_and_content(status_type::ok, "hello world"); }); - server.use_metrics(); + server.use_metrics(true, "/metrics"); server.sync_start(); } diff --git a/iguana b/iguana index 043512e3..90e9f4a8 160000 --- a/iguana +++ b/iguana @@ -1 +1 @@ -Subproject commit 043512e380fa794f09f1d38bf36bfb533c0d47dd +Subproject commit 90e9f4a824aba660bbcaf47a23b7955d4468e3bb diff --git a/include/cinatra/coro_http_server.hpp b/include/cinatra/coro_http_server.hpp index 29031eae..17a057fd 100644 --- a/include/cinatra/coro_http_server.hpp +++ b/include/cinatra/coro_http_server.hpp @@ -182,12 +182,23 @@ class coro_http_server { } } - void use_metrics(std::string url_path = "/metrics") { + void use_metrics(bool enable_json = false, + std::string url_path = "/metrics") { init_metrics(); set_http_handler( - url_path, [](coro_http_request &req, coro_http_response &res) { - std::string str = async_simple::coro::syncAwait( - ylt::default_metric_manager::serialize_static()); + url_path, + [enable_json](coro_http_request &req, coro_http_response &res) { + std::string str; +#ifdef CINATRA_ENABLE_METRIC_JSON + if (enable_json) { + str = + ylt::metric::default_metric_manager::serialize_to_json_static(); + res.set_content_type(); + } + else +#endif + str = ylt::metric::default_metric_manager::serialize_static(); + res.set_status_and_content(status_type::ok, std::move(str)); }); } @@ -900,7 +911,7 @@ class coro_http_server { private: void init_metrics() { - using namespace ylt; + using namespace ylt::metric; cinatra_metric_conf::enable_metric = true; default_metric_manager::create_metric_static( diff --git a/include/cinatra/metric_conf.hpp b/include/cinatra/metric_conf.hpp index 549c93cd..4800481a 100644 --- a/include/cinatra/metric_conf.hpp +++ b/include/cinatra/metric_conf.hpp @@ -25,9 +25,8 @@ struct cinatra_metric_conf { return; } - static auto m = - ylt::default_metric_manager::get_metric_static( - server_total_req); + static auto m = ylt::metric::default_metric_manager::get_metric_static< + ylt::metric::counter_t>(server_total_req); if (m == nullptr) { return; } @@ -38,9 +37,8 @@ struct cinatra_metric_conf { if (!enable_metric) { return; } - static auto m = - ylt::default_metric_manager::get_metric_static( - server_failed_req); + static auto m = ylt::metric::default_metric_manager::get_metric_static< + ylt::metric::counter_t>(server_failed_req); if (m == nullptr) { return; } @@ -51,9 +49,8 @@ struct cinatra_metric_conf { if (!enable_metric) { return; } - static auto m = - ylt::default_metric_manager::get_metric_static( - server_total_fd); + static auto m = ylt::metric::default_metric_manager::get_metric_static< + ylt::metric::gauge_t>(server_total_fd); if (m == nullptr) { return; } @@ -64,9 +61,8 @@ struct cinatra_metric_conf { if (!enable_metric) { return; } - static auto m = - ylt::default_metric_manager::get_metric_static( - server_total_fd); + static auto m = ylt::metric::default_metric_manager::get_metric_static< + ylt::metric::gauge_t>(server_total_fd); if (m == nullptr) { return; } @@ -77,9 +73,8 @@ struct cinatra_metric_conf { if (!enable_metric) { return; } - static auto m = - ylt::default_metric_manager::get_metric_static( - server_total_recv_bytes); + static auto m = ylt::metric::default_metric_manager::get_metric_static< + ylt::metric::counter_t>(server_total_recv_bytes); if (m == nullptr) { return; } @@ -90,9 +85,8 @@ struct cinatra_metric_conf { if (!enable_metric) { return; } - static auto m = - ylt::default_metric_manager::get_metric_static( - server_total_send_bytes); + static auto m = ylt::metric::default_metric_manager::get_metric_static< + ylt::metric::counter_t>(server_total_send_bytes); if (m == nullptr) { return; } @@ -103,9 +97,8 @@ struct cinatra_metric_conf { if (!enable_metric) { return; } - static auto m = - ylt::default_metric_manager::get_metric_static( - server_req_latency); + static auto m = ylt::metric::default_metric_manager::get_metric_static< + ylt::metric::histogram_t>(server_req_latency); if (m == nullptr) { return; } @@ -116,9 +109,8 @@ struct cinatra_metric_conf { if (!enable_metric) { return; } - static auto m = - ylt::default_metric_manager::get_metric_static( - server_read_latency); + static auto m = ylt::metric::default_metric_manager::get_metric_static< + ylt::metric::histogram_t>(server_read_latency); if (m == nullptr) { return; } diff --git a/include/cinatra/ylt/metric/counter.hpp b/include/cinatra/ylt/metric/counter.hpp index a252106d..3c339b07 100644 --- a/include/cinatra/ylt/metric/counter.hpp +++ b/include/cinatra/ylt/metric/counter.hpp @@ -4,13 +4,23 @@ #include "metric.hpp" -namespace ylt { +namespace ylt::metric { enum class op_type_t { INC, DEC, SET }; -struct counter_sample { - op_type_t op_type; - std::vector labels_value; - double value; + +#ifdef CINATRA_ENABLE_METRIC_JSON +struct json_counter_metric_t { + std::unordered_multimap labels; + int64_t value; +}; +REFLECTION(json_counter_metric_t, labels, value); +struct json_counter_t { + std::string name; + std::string help; + std::string type; + std::vector metrics; }; +REFLECTION(json_counter_t, name, help, type, metrics); +#endif class counter_t : public metric_t { public: @@ -23,12 +33,8 @@ class counter_t : public metric_t { // static labels value, contains a map with atomic value. counter_t(std::string name, std::string help, std::map labels) - : metric_t(MetricType::Counter, std::move(name), std::move(help)) { - for (auto &[k, v] : labels) { - labels_name_.push_back(k); - labels_value_.push_back(v); - } - + : metric_t(MetricType::Counter, std::move(name), std::move(help), + std::move(labels)) { atomic_value_map_.emplace(labels_value_, 0); use_atomic_ = true; } @@ -56,7 +62,7 @@ class counter_t : public metric_t { std::map, double, std::less>> - value_map() { + value_map() override { std::map, double, std::less>> map; @@ -97,6 +103,44 @@ class counter_t : public metric_t { } } +#ifdef CINATRA_ENABLE_METRIC_JSON + void serialize_to_json(std::string &str) override { + std::string s; + if (labels_name_.empty()) { + if (default_lable_value_ == 0) { + return; + } + json_counter_t counter{name_, help_, std::string(metric_name())}; + int64_t value = default_lable_value_; + counter.metrics.push_back({{}, value}); + iguana::to_json(counter, str); + return; + } + + json_counter_t counter{name_, help_, std::string(metric_name())}; + if (use_atomic_) { + to_json(counter, atomic_value_map_, str); + } + else { + to_json(counter, value_map_, str); + } + } + + template + void to_json(json_counter_t &counter, T &map, std::string &str) { + for (auto &[k, v] : map) { + json_counter_metric_t metric; + size_t index = 0; + for (auto &label_value : k) { + metric.labels.emplace(labels_name_[index++], label_value); + } + metric.value = (int64_t)v; + counter.metrics.push_back(std::move(metric)); + } + iguana::to_json(counter, str); + } +#endif + void inc(double val = 1) { if (val < 0) { throw std::invalid_argument("the value is less than zero"); @@ -255,4 +299,4 @@ class counter_t : public metric_t { std::less>> value_map_; }; -} // namespace ylt \ No newline at end of file +} // namespace ylt::metric \ No newline at end of file diff --git a/include/cinatra/ylt/metric/detail/ckms_quantiles.hpp b/include/cinatra/ylt/metric/detail/ckms_quantiles.hpp index cdaf1db9..fd42a8c2 100644 --- a/include/cinatra/ylt/metric/detail/ckms_quantiles.hpp +++ b/include/cinatra/ylt/metric/detail/ckms_quantiles.hpp @@ -5,7 +5,7 @@ // https://github.com/jupp0r/prometheus-cpp/blob/master/core/include/prometheus/detail/ckms_quantiles.h -namespace ylt { +namespace ylt::metric { class CKMSQuantiles { public: struct Quantile { @@ -172,4 +172,4 @@ class CKMSQuantiles { std::array buffer_; std::size_t buffer_count_; }; -} // namespace ylt \ No newline at end of file +} // namespace ylt::metric \ No newline at end of file diff --git a/include/cinatra/ylt/metric/detail/time_window_quantiles.hpp b/include/cinatra/ylt/metric/detail/time_window_quantiles.hpp index fd7df105..6b81179f 100644 --- a/include/cinatra/ylt/metric/detail/time_window_quantiles.hpp +++ b/include/cinatra/ylt/metric/detail/time_window_quantiles.hpp @@ -2,7 +2,7 @@ #include "ckms_quantiles.hpp" // https://github.com/jupp0r/prometheus-cpp/blob/master/core/include/prometheus/detail/time_window_quantiles.h -namespace ylt { +namespace ylt::metric { class TimeWindowQuantiles { using Clock = std::chrono::steady_clock; @@ -49,4 +49,4 @@ class TimeWindowQuantiles { mutable Clock::time_point last_rotation_; const Clock::duration rotation_interval_; }; -} // namespace ylt \ No newline at end of file +} // namespace ylt::metric \ No newline at end of file diff --git a/include/cinatra/ylt/metric/gauge.hpp b/include/cinatra/ylt/metric/gauge.hpp index 19c4b65c..18693f1a 100644 --- a/include/cinatra/ylt/metric/gauge.hpp +++ b/include/cinatra/ylt/metric/gauge.hpp @@ -3,7 +3,7 @@ #include "counter.hpp" -namespace ylt { +namespace ylt::metric { class gauge_t : public counter_t { public: gauge_t(std::string name, std::string help) @@ -49,4 +49,4 @@ class gauge_t : public counter_t { } } }; -} // namespace ylt \ No newline at end of file +} // namespace ylt::metric \ No newline at end of file diff --git a/include/cinatra/ylt/metric/histogram.hpp b/include/cinatra/ylt/metric/histogram.hpp index dfd2ca13..d85225a5 100644 --- a/include/cinatra/ylt/metric/histogram.hpp +++ b/include/cinatra/ylt/metric/histogram.hpp @@ -8,7 +8,24 @@ #include "counter.hpp" #include "metric.hpp" -namespace ylt { +namespace ylt::metric { +#ifdef CINATRA_ENABLE_METRIC_JSON +struct json_histogram_metric_t { + std::map labels; + std::map quantiles; + int64_t count; + double sum; +}; +REFLECTION(json_histogram_metric_t, labels, quantiles, count, sum); +struct json_histogram_t { + std::string name; + std::string help; + std::string type; + std::vector metrics; +}; +REFLECTION(json_histogram_t, name, help, type, metrics); +#endif + class histogram_t : public metric_t { public: histogram_t(std::string name, std::string help, std::vector buckets) @@ -25,7 +42,41 @@ class histogram_t : public metric_t { use_atomic_ = true; } + histogram_t(std::string name, std::string help, std::vector buckets, + std::vector labels_name) + : bucket_boundaries_(buckets), + metric_t(MetricType::Histogram, name, help, labels_name), + sum_(std::make_shared(name, help, labels_name)) { + 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(name, help, labels_name)); + } + } + + histogram_t(std::string name, std::string help, std::vector buckets, + std::map labels) + : bucket_boundaries_(buckets), + metric_t(MetricType::Histogram, name, help, labels), + sum_(std::make_shared(name, help, labels)) { + 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(name, help, labels)); + } + use_atomic_ = true; + } + void observe(double value) { + if (!use_atomic_ || !labels_name_.empty()) { + throw std::invalid_argument("not a default label metric"); + } + const auto bucket_index = static_cast( std::distance(bucket_boundaries_.begin(), std::lower_bound(bucket_boundaries_.begin(), @@ -34,9 +85,33 @@ class histogram_t : public metric_t { bucket_counts_[bucket_index]->inc(); } + void observe(const std::vector &labels_value, double value) { + if (sum_->labels_name().empty()) { + throw std::invalid_argument("not a label metric"); + } + + const auto bucket_index = static_cast( + std::distance(bucket_boundaries_.begin(), + std::lower_bound(bucket_boundaries_.begin(), + bucket_boundaries_.end(), value))); + sum_->inc(labels_value, value); + bucket_counts_[bucket_index]->inc(labels_value); + } + auto get_bucket_counts() { return bucket_counts_; } - void serialize(std::string& str) override { + std::map, double, + std::less>> + value_map() override { + return sum_->value_map(); + } + + void serialize(std::string &str) override { + if (!sum_->labels_name().empty()) { + serialize_with_labels(str); + return; + } + serialize_head(str); double count = 0; auto bucket_counts = get_bucket_counts(); @@ -68,6 +143,41 @@ class histogram_t : public metric_t { .append("\n"); } +#ifdef CINATRA_ENABLE_METRIC_JSON + void serialize_to_json(std::string &str) override { + if (!sum_->labels_name().empty()) { + serialize_to_json_with_labels(str); + return; + } + + json_histogram_t hist{name_, help_, std::string(metric_name())}; + + double count = 0; + auto bucket_counts = get_bucket_counts(); + json_histogram_metric_t metric{}; + for (size_t i = 0; i < bucket_counts.size(); i++) { + auto counter = bucket_counts[i]; + + count += counter->value(); + + if (i == bucket_boundaries_.size()) { + metric.quantiles.emplace(std::numeric_limits::max(), + (int64_t)count); + } + else { + metric.quantiles.emplace(bucket_boundaries_[i], + (int64_t)counter->value()); + } + } + metric.count = (int64_t)count; + metric.sum = sum_->value(); + + hist.metrics.push_back(std::move(metric)); + + iguana::to_json(hist, str); + } +#endif + private: template bool is_strict_sorted(ForwardIterator first, ForwardIterator last) { @@ -76,8 +186,102 @@ class histogram_t : public metric_t { ForwardIterator>::value_type>()) == last; } + void serialize_with_labels(std::string &str) { + serialize_head(str); + + auto bucket_counts = get_bucket_counts(); + + auto value_map = sum_->value_map(); + for (auto &[labels_value, value] : value_map) { + if (value == 0) { + continue; + } + + double count = 0; + for (size_t i = 0; i < bucket_counts.size(); i++) { + auto counter = bucket_counts[i]; + str.append(name_).append("_bucket{"); + build_label_string(str, sum_->labels_name(), labels_value); + str.append(","); + + if (i == bucket_boundaries_.size()) { + str.append("le=\"").append("+Inf").append("\"} "); + } + else { + str.append("le=\"") + .append(std::to_string(bucket_boundaries_[i])) + .append("\"} "); + } + + count += counter->value(labels_value); + str.append(std::to_string(count)); + str.append("\n"); + } + + str.append(name_); + str.append("_sum{"); + build_label_string(str, sum_->labels_name(), labels_value); + str.append("} "); + + if (type_ == MetricType::Counter) { + str.append(std::to_string((int64_t)value)); + } + else { + str.append(std::to_string(value)); + } + str.append("\n"); + + str.append(name_).append("_count{"); + build_label_string(str, sum_->labels_name(), labels_value); + str.append("} "); + str.append(std::to_string(count)); + str.append("\n"); + } + } + +#ifdef CINATRA_ENABLE_METRIC_JSON + void serialize_to_json_with_labels(std::string &str) { + json_histogram_t hist{name_, help_, std::string(metric_name())}; + auto bucket_counts = get_bucket_counts(); + + auto value_map = sum_->value_map(); + for (auto &[labels_value, value] : value_map) { + if (value == 0) { + continue; + } + + size_t count = 0; + json_histogram_metric_t metric{}; + for (size_t i = 0; i < bucket_counts.size(); i++) { + auto counter = bucket_counts[i]; + + count += counter->value(labels_value); + + if (i == bucket_boundaries_.size()) { + metric.quantiles.emplace(std::numeric_limits::max(), + (int64_t)count); + } + else { + metric.quantiles.emplace(bucket_boundaries_[i], + (int64_t)counter->value(labels_value)); + } + } + metric.count = (int64_t)count; + metric.sum = sum_->value(labels_value); + + for (size_t i = 0; i < labels_value.size(); i++) { + metric.labels[sum_->labels_name()[i]] = labels_value[i]; + } + + hist.metrics.push_back(std::move(metric)); + } + + iguana::to_json(hist, str); + } +#endif + std::vector bucket_boundaries_; std::vector> bucket_counts_; // readonly std::shared_ptr sum_; }; -} // namespace ylt \ No newline at end of file +} // namespace ylt::metric \ No newline at end of file diff --git a/include/cinatra/ylt/metric/metric.hpp b/include/cinatra/ylt/metric/metric.hpp index 58d0afc5..ea103e59 100644 --- a/include/cinatra/ylt/metric/metric.hpp +++ b/include/cinatra/ylt/metric/metric.hpp @@ -1,17 +1,33 @@ #pragma once +#include #include #include #include #include #include +#include +#include #include #include #include #include "async_simple/coro/Lazy.h" +#include "async_simple/coro/SyncAwait.h" #include "cinatra/cinatra_log_wrapper.hpp" -namespace ylt { +#ifdef CINATRA_ENABLE_METRIC_JSON +namespace iguana { + +template +inline char* to_chars_float(T value, char* buffer) { + return buffer + snprintf(buffer, 65, "%g", value); +} + +} // namespace iguana + +#include +#endif +namespace ylt::metric { enum class MetricType { Counter, Gauge, @@ -20,15 +36,32 @@ enum class MetricType { Nil, }; +struct metric_filter_options { + std::optional name_regex{}; + std::optional label_regex{}; + bool is_white = true; +}; + class metric_t { public: metric_t() = default; + metric_t(MetricType type, std::string name, std::string help) + : type_(type), name_(std::move(name)), help_(std::move(help)) {} metric_t(MetricType type, std::string name, std::string help, - std::vector labels_name = {}) - : type_(type), - name_(std::move(name)), - help_(std::move(help)), - labels_name_(std::move(labels_name)) {} + std::vector labels_name) + : metric_t(type, std::move(name), std::move(help)) { + labels_name_ = std::move(labels_name); + } + + metric_t(MetricType type, std::string name, std::string help, + std::map static_labels) + : metric_t(type, std::move(name), std::move(help)) { + static_labels_ = std::move(static_labels); + for (auto& [k, v] : static_labels_) { + labels_name_.push_back(k); + labels_value_.push_back(v); + } + } virtual ~metric_t() {} std::string_view name() { return name_; } @@ -55,13 +88,35 @@ class metric_t { const std::vector& labels_name() { return labels_name_; } + const std::map& get_static_labels() { + return static_labels_; + } + + virtual std::map, double, + std::less>> + value_map() { + return {}; + } + virtual void serialize(std::string& str) {} +#ifdef CINATRA_ENABLE_METRIC_JSON + virtual void serialize_to_json(std::string& str) {} +#endif + // only for summary virtual async_simple::coro::Lazy serialize_async(std::string& out) { co_return; } +#ifdef CINATRA_ENABLE_METRIC_JSON + // only for summary + virtual async_simple::coro::Lazy serialize_to_json_async( + std::string& out) { + co_return; + } +#endif + bool is_atomic() const { return use_atomic_; } template @@ -80,6 +135,19 @@ class metric_t { .append("\n"); } + void build_label_string(std::string& str, + const std::vector& label_name, + const std::vector& label_value) { + for (size_t i = 0; i < label_name.size(); i++) { + str.append(label_name[i]) + .append("=\"") + .append(label_value[i]) + .append("\"") + .append(","); + } + str.pop_back(); + } + #ifdef __APPLE__ double mac_os_atomic_fetch_add(std::atomic* obj, double arg) { double v; @@ -101,6 +169,7 @@ class metric_t { MetricType type_ = MetricType::Nil; std::string name_; std::string help_; + std::map static_labels_; std::vector labels_name_; // read only std::vector labels_value_; // read only bool use_atomic_ = false; @@ -131,19 +200,27 @@ struct metric_manager_t { const std::string& help, Args&&... args) { auto m = std::make_shared(name, help, std::forward(args)...); - bool r = register_metric_static(m); + bool r = register_metric_dynamic(m); if (!r) { return nullptr; } return m; } + static bool register_metric_static(std::shared_ptr metric) { + return register_metric_impl(metric); + } + static bool register_metric_dynamic(std::shared_ptr metric) { return register_metric_impl(metric); } - static bool register_metric_static(std::shared_ptr metric) { - return register_metric_impl(metric); + static bool remove_metric_static(const std::string& name) { + return remove_metric_impl(name); + } + + static bool remove_metric_dynamic(const std::string& name) { + return remove_metric_impl(name); } template @@ -175,30 +252,144 @@ struct metric_manager_t { return metric_keys_impl(); } + // static labels: {{"method", "GET"}, {"url", "/"}} + static std::vector> get_metric_by_labels_static( + const std::map& labels) { + std::vector> vec; + auto map = metric_map_static(); + for (auto& [name, m] : map) { + const auto& static_labels = m->get_static_labels(); + if (static_labels == labels) { + vec.push_back(m); + } + } + return vec; + } + + // static label: {"method", "GET"} + static std::vector> get_metric_by_label_static( + const std::pair& label) { + std::vector> vec; + auto map = metric_map_static(); + for (auto& [name, t] : map) { + const auto& static_labels = t->get_static_labels(); + for (const auto& pair : static_labels) { + if (pair.first == label.first && pair.second == label.second) { + vec.push_back(t); + } + } + } + return vec; + } + + // labels: {{"method", "POST"}, {"code", "200"}} + static std::vector> get_metric_by_labels_dynamic( + const std::map& labels) { + std::vector> vec; + auto map = metric_map_dynamic(); + for (auto& [name, t] : map) { + auto val_map = t->value_map(); + auto labels_name = t->labels_name(); + + for (auto& [k, v] : labels) { + if (auto it = std::find(labels_name.begin(), labels_name.end(), k); + it != labels_name.end()) { + if (auto it = std::find_if(val_map.begin(), val_map.end(), + [label_val = v](auto& pair) { + auto& key = pair.first; + return std::find(key.begin(), key.end(), + label_val) != key.end(); + }); + it != val_map.end()) { + vec.push_back(t); + } + } + } + } + + return vec; + } + template - static T* get_metric_static(const std::string& name) { + static std::shared_ptr get_metric_static(const std::string& name) { auto m = get_metric_impl(name); if (m == nullptr) { return nullptr; } - return m->template as(); + return std::dynamic_pointer_cast(m); } template - static T* get_metric_dynamic(const std::string& name) { + static std::shared_ptr get_metric_dynamic(const std::string& name) { auto m = get_metric_impl(name); if (m == nullptr) { return nullptr; } - return m->template as(); + return std::dynamic_pointer_cast(m); + } + + static std::string serialize( + const std::vector>& metrics) { + std::string str; + for (auto& m : metrics) { + if (m->metric_type() == MetricType::Summary) { + async_simple::coro::syncAwait(m->serialize_async(str)); + } + else { + m->serialize(str); + } + } + + return str; + } + + static std::string serialize_static() { return serialize(collect()); } + + static std::string serialize_dynamic() { return serialize(collect()); } + +#ifdef CINATRA_ENABLE_METRIC_JSON + static std::string serialize_to_json_static() { + auto metrics = collect(); + return serialize_to_json(metrics); + } + + static std::string serialize_to_json_dynamic() { + auto metrics = collect(); + return serialize_to_json(metrics); + } + + static std::string serialize_to_json( + const std::vector>& metrics) { + if (metrics.empty()) { + return ""; + } + std::string str; + str.append("["); + for (auto& m : metrics) { + size_t start = str.size(); + if (m->metric_type() == MetricType::Summary) { + async_simple::coro::syncAwait(m->serialize_to_json_async(str)); + } + else { + m->serialize_to_json(str); + } + + if (str.size() > start) + str.append(","); + } + str.back() = ']'; + return str; } +#endif - static async_simple::coro::Lazy serialize_static() { - return serialize_impl(); + static std::vector> filter_metrics_static( + const metric_filter_options& options) { + return filter_metrics(options); } - static async_simple::coro::Lazy serialize_dynamic() { - return serialize_impl(); + static std::vector> filter_metrics_dynamic( + const metric_filter_options& options) { + return filter_metrics(options); } private: @@ -242,6 +433,12 @@ struct metric_manager_t { return r; } + template + static size_t remove_metric_impl(const std::string& name) { + auto lock = get_lock(); + return metric_map_.erase(name); + } + template static auto metric_map_impl() { auto lock = get_lock(); @@ -289,19 +486,64 @@ struct metric_manager_t { return metrics; } - template - static async_simple::coro::Lazy serialize_impl() { - std::string str; + static void filter_by_label_name( + std::vector>& filtered_metrics, + std::shared_ptr m, const metric_filter_options& options, + std::vector& indexs, size_t index) { + const auto& labels_name = m->labels_name(); + for (auto& label_name : labels_name) { + if (std::regex_match(label_name, *options.label_regex)) { + if (options.is_white) { + filtered_metrics.push_back(m); + } + else { + indexs.push_back(index); + } + } + } + } + + template + static std::vector> filter_metrics( + const metric_filter_options& options) { auto metrics = collect(); + if (!options.name_regex && !options.label_regex) { + return metrics; + } + + std::vector> filtered_metrics; + std::vector indexs; + size_t index = 0; for (auto& m : metrics) { - if (m->metric_type() == MetricType::Summary) { - co_await m->serialize_async(str); + if (options.name_regex && !options.label_regex) { + if (std::regex_match(std::string(m->name()), *options.name_regex)) { + if (options.is_white) { + filtered_metrics.push_back(m); + } + else { + indexs.push_back(index); + } + } + } + else if (options.label_regex && !options.name_regex) { + filter_by_label_name(filtered_metrics, m, options, indexs, index); } else { - m->serialize(str); + if (std::regex_match(std::string(m->name()), *options.name_regex)) { + filter_by_label_name(filtered_metrics, m, options, indexs, index); + } + } + index++; + } + + if (!options.is_white) { + for (size_t i : indexs) { + metrics.erase(std::next(metrics.begin(), i)); } + return metrics; } - co_return str; + + return filtered_metrics; } static inline std::mutex mtx_; @@ -313,4 +555,4 @@ struct metric_manager_t { }; using default_metric_manager = metric_manager_t<0>; -} // namespace ylt \ No newline at end of file +} // namespace ylt::metric \ No newline at end of file diff --git a/include/cinatra/ylt/metric/summary.hpp b/include/cinatra/ylt/metric/summary.hpp index 415d6b32..bad8a132 100644 --- a/include/cinatra/ylt/metric/summary.hpp +++ b/include/cinatra/ylt/metric/summary.hpp @@ -6,7 +6,29 @@ #include "ylt/coro_io/coro_io.hpp" #include "ylt/util/concurrentqueue.h" -namespace ylt { +namespace ylt::metric { +#ifdef CINATRA_ENABLE_METRIC_JSON +struct json_summary_metric_t { + std::map labels; + std::map quantiles; + int64_t count; + double sum; +}; +REFLECTION(json_summary_metric_t, labels, quantiles, count, sum); +struct json_summary_t { + std::string name; + std::string help; + std::string type; + std::vector metrics; +}; +REFLECTION(json_summary_t, name, help, type, metrics); +#endif + +struct summary_label_sample { + std::vector labels_value; + double value; +}; + class summary_t : public metric_t { public: using Quantiles = std::vector; @@ -14,22 +36,56 @@ class summary_t : public metric_t { std::chrono::milliseconds max_age = std::chrono::seconds{60}, int age_buckets = 5) : quantiles_{std::move(quantiles)}, - metric_t(MetricType::Summary, std::move(name), std::move(help)) { - work_ = std::make_shared(ctx_); - thd_ = std::thread([this] { - ctx_.run(); - }); - excutor_ = - std::make_unique>(ctx_.get_executor()); - block_ = std::make_shared(); + metric_t(MetricType::Summary, std::move(name), std::move(help)), + max_age_(max_age), + age_buckets_(age_buckets) { + init_executor(); + init_block(block_); block_->quantile_values_ = std::make_shared(quantiles_, max_age, age_buckets); - start_timer(block_).via(excutor_.get()).start([](auto &&) { - }); + use_atomic_ = true; + } + + summary_t(std::string name, std::string help, Quantiles quantiles, + std::vector labels_name, + std::chrono::milliseconds max_age = std::chrono::seconds{60}, + int age_buckets = 5) + : quantiles_{std::move(quantiles)}, + metric_t(MetricType::Summary, std::move(name), std::move(help), + std::move(labels_name)), + max_age_(max_age), + age_buckets_(age_buckets) { + init_executor(); + init_block(labels_block_); + } + + summary_t(std::string name, std::string help, Quantiles quantiles, + std::map static_labels, + std::chrono::milliseconds max_age = std::chrono::seconds{60}, + int age_buckets = 5) + : quantiles_{std::move(quantiles)}, + metric_t(MetricType::Summary, std::move(name), std::move(help), + std::move(static_labels)), + max_age_(max_age), + age_buckets_(age_buckets) { + init_executor(); + init_block(labels_block_); + labels_block_->label_quantile_values_[labels_value_] = + std::make_shared(quantiles_, max_age, age_buckets); + labels_block_->label_count_.emplace(labels_value_, 0); + labels_block_->label_sum_.emplace(labels_value_, 0); + use_atomic_ = true; } ~summary_t() { - block_->stop_ = true; + if (block_) { + block_->stop_ = true; + } + + if (labels_block_) { + labels_block_->stop_ = true; + } + work_ = nullptr; thd_.join(); } @@ -42,7 +98,39 @@ class summary_t : public metric_t { double sum_; }; - void observe(double value) { block_->sample_queue_.enqueue(value); } + struct labels_block_t { + std::atomic stop_ = false; + moodycamel::ConcurrentQueue sample_queue_; + + std::map, std::shared_ptr, + std::less>> + label_quantile_values_; + std::map, std::uint64_t, + std::less>> + label_count_; + std::map, double, + std::less>> + label_sum_; + }; + + void observe(double value) { + if (!labels_name_.empty()) { + throw std::invalid_argument("not a default label metric"); + } + block_->sample_queue_.enqueue(value); + } + + void observe(std::vector labels_value, double value) { + if (labels_value.empty()) { + throw std::invalid_argument("not a label metric"); + } + if (use_atomic_) { + if (labels_value != labels_value_) { + throw std::invalid_argument("not equal with static label"); + } + } + labels_block_->sample_queue_.enqueue({std::move(labels_value), value}); + } async_simple::coro::Lazy> get_rates(double &sum, uint64_t &count) { @@ -51,34 +139,86 @@ class summary_t : public metric_t { co_return std::vector{}; } - co_await coro_io::post([this, &vec, &sum, &count] { - sum = block_->sum_; - count = block_->count_; - for (const auto &quantile : quantiles_) { - vec.push_back(block_->quantile_values_->get(quantile.quantile)); + co_await coro_io::post( + [this, &vec, &sum, &count] { + sum = block_->sum_; + count = block_->count_; + for (const auto &quantile : quantiles_) { + vec.push_back(block_->quantile_values_->get(quantile.quantile)); + } + }, + excutor_.get()); + + co_return vec; + } + + async_simple::coro::Lazy> get_rates( + const std::vector &labels_value, double &sum, + uint64_t &count) { + std::vector vec; + if (quantiles_.empty()) { + co_return std::vector{}; + } + + if (use_atomic_) { + if (labels_value != labels_value_) { + throw std::invalid_argument("not equal with static label"); } - }); + } + + co_await coro_io::post( + [this, &vec, &sum, &count, &labels_value] { + auto it = labels_block_->label_quantile_values_.find(labels_value); + if (it == labels_block_->label_quantile_values_.end()) { + return; + } + sum = labels_block_->label_sum_[labels_value]; + count = labels_block_->label_count_[labels_value]; + for (const auto &quantile : quantiles_) { + vec.push_back(it->second->get(quantile.quantile)); + } + }, + excutor_.get()); co_return vec; } + std::map, double, + std::less>> + value_map() override { + auto ret = async_simple::coro::syncAwait(coro_io::post( + [this] { + return labels_block_->label_sum_; + }, + excutor_.get())); + return ret.value(); + } + async_simple::coro::Lazy get_sum() { - auto ret = co_await coro_io::post([this] { - return block_->sum_; - }); + auto ret = co_await coro_io::post( + [this] { + return block_->sum_; + }, + excutor_.get()); co_return ret.value(); } async_simple::coro::Lazy get_count() { - auto ret = co_await coro_io::post([this] { - return block_->count_; - }); + auto ret = co_await coro_io::post( + [this] { + return block_->count_; + }, + excutor_.get()); co_return ret.value(); } size_t size_approx() { return block_->sample_queue_.size_approx(); } async_simple::coro::Lazy serialize_async(std::string &str) override { + if (block_ == nullptr) { + co_await serialize_async_with_label(str); + co_return; + } if (quantiles_.empty()) { co_return; } @@ -103,8 +243,55 @@ class summary_t : public metric_t { .append("\n"); } +#ifdef CINATRA_ENABLE_METRIC_JSON + async_simple::coro::Lazy serialize_to_json_async( + std::string &str) override { + if (block_ == nullptr) { + co_await serialize_to_json_with_label_async(str); + co_return; + } + + if (quantiles_.empty()) { + co_return; + } + + json_summary_t summary{name_, help_, std::string(metric_name())}; + double sum = 0; + uint64_t count = 0; + auto rates = co_await get_rates(sum, count); + + json_summary_metric_t metric; + + for (size_t i = 0; i < quantiles_.size(); i++) { + metric.quantiles.emplace(quantiles_[i].quantile, rates[i]); + } + + metric.sum = sum; + metric.count = count; + + summary.metrics.push_back(std::move(metric)); + + iguana::to_json(summary, str); + } +#endif private: - async_simple::coro::Lazy start_timer(std::shared_ptr block) { + void init_executor() { + work_ = std::make_shared(ctx_); + thd_ = std::thread([this] { + ctx_.run(); + }); + excutor_ = + std::make_unique>(ctx_.get_executor()); + } + + template + void init_block(std::shared_ptr &block) { + block = std::make_shared(); + start(block).via(excutor_.get()).start([](auto &&) { + }); + } + + async_simple::coro::Lazy start(std::shared_ptr block) { double sample; size_t count = 1000000; while (!block->stop_) { @@ -122,18 +309,132 @@ class summary_t : public metric_t { co_await async_simple::coro::Yield{}; if (block->sample_queue_.size_approx() == 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(5)); + co_await coro_io::sleep_for(std::chrono::milliseconds(5), + excutor_.get()); } } co_return; } + async_simple::coro::Lazy start(std::shared_ptr block) { + summary_label_sample sample; + size_t count = 1000000; + while (!block->stop_) { + size_t index = 0; + while (block->sample_queue_.try_dequeue(sample)) { + auto &ptr = block->label_quantile_values_[sample.labels_value]; + + if (ptr == nullptr) { + ptr = std::make_shared(quantiles_, max_age_, + age_buckets_); + } + + ptr->insert(sample.value); + + block->label_count_[sample.labels_value] += 1; + block->label_sum_[sample.labels_value] += sample.value; + index++; + if (index == count) { + break; + } + } + + co_await async_simple::coro::Yield{}; + + if (block->sample_queue_.size_approx() == 0) { + co_await coro_io::sleep_for(std::chrono::milliseconds(5), + excutor_.get()); + } + } + + co_return; + } + + async_simple::coro::Lazy serialize_async_with_label(std::string &str) { + if (quantiles_.empty()) { + co_return; + } + + serialize_head(str); + + auto sum_map = co_await coro_io::post( + [this] { + return labels_block_->label_sum_; + }, + excutor_.get()); + + for (auto &[labels_value, sum_val] : sum_map.value()) { + double sum = 0; + uint64_t count = 0; + auto rates = co_await get_rates(labels_value, sum, count); + for (size_t i = 0; i < quantiles_.size(); i++) { + str.append(name_); + str.append("{"); + build_label_string(str, labels_name_, labels_value); + str.append(","); + str.append("quantile=\""); + str.append(std::to_string(quantiles_[i].quantile)).append("\"} "); + str.append(std::to_string(rates[i])).append("\n"); + } + + str.append(name_).append("_sum "); + str.append("{"); + build_label_string(str, labels_name_, labels_value); + str.append("} "); + str.append(std::to_string(sum)).append("\n"); + + str.append(name_).append("_count "); + str.append("{"); + build_label_string(str, labels_name_, labels_value); + str.append("} "); + str.append(std::to_string((uint64_t)count)).append("\n"); + } + } + +#ifdef CINATRA_ENABLE_METRIC_JSON + async_simple::coro::Lazy serialize_to_json_with_label_async( + std::string &str) { + if (quantiles_.empty()) { + co_return; + } + + auto sum_map = co_await coro_io::post( + [this] { + return labels_block_->label_sum_; + }, + excutor_.get()); + + json_summary_t summary{name_, help_, std::string(metric_name())}; + + for (auto &[labels_value, sum_val] : sum_map.value()) { + json_summary_metric_t metric; + double sum = 0; + uint64_t count = 0; + auto rates = co_await get_rates(labels_value, sum, count); + metric.count = count; + metric.sum = sum; + for (size_t i = 0; i < quantiles_.size(); i++) { + for (size_t i = 0; i < labels_value.size(); i++) { + metric.labels[labels_name_[i]] = labels_value[i]; + } + metric.quantiles.emplace(quantiles_[i].quantile, rates[i]); + } + + summary.metrics.push_back(std::move(metric)); + } + iguana::to_json(summary, str); + } +#endif + Quantiles quantiles_; // readonly std::shared_ptr block_; + std::shared_ptr labels_block_; std::unique_ptr> excutor_ = nullptr; std::shared_ptr work_; asio::io_context ctx_; std::thread thd_; + std::chrono::milliseconds max_age_; + int age_buckets_; }; -} // namespace ylt \ No newline at end of file +} // namespace ylt::metric \ No newline at end of file diff --git a/lang/how_to_use_metrics.md b/lang/how_to_use_metrics.md index ac880bf3..f7f17bfa 100644 --- a/lang/how_to_use_metrics.md +++ b/lang/how_to_use_metrics.md @@ -70,24 +70,28 @@ some_counter.inc({"GET", "/"}, 1); ## counter/gauge指标的api 构造函数: + ```cpp -// 无标签,调用inc时不带标签,如c.inc() +// 无标签,调用inc时不带标签,如c.inc(),调用此函数则metric 为静态标签的metric // name: 指标对象的名称,注册到指标管理器时会使用这个名称 // help: 指标对象的帮助信息 counter_t(std::string name, std::string help); // labels: 静态标签,构造时需要将标签键值都填完整,如:{{"method", "GET"}, {"url", "/"}} +// 调用此函数则metric 为静态标签的metric // 调用inc时必须带静态标签的值,如:c.inc({"GET", "/"}, 1); counter_t(std::string name, std::string help, std::map labels); // labels_name: 动态标签的键名称,因为标签的值是动态的,而键的名称是固定的,所以这里只需要填键名称,如: {"method", "url"} // 调用时inc时必须带动态标签的值,如:c.inc({method, url}, 1); +// 调用此函数则metric 为动态标签的metric counter_t(std::string name, std::string help, std::vector labels_name); ``` 基本函数: + ```cpp // 获取无标签指标的计数, double value(); @@ -113,6 +117,7 @@ std::map, double, 注意:如果使用动态标签的时候要注意这个动态的标签值是不是无限多的,如果是无限多的话,那么内部的map也会无限增长,应该避免这种情况,动态的标签也应该是有限的才对。 gauge 派生于counter,相比counter多了一个减少计数的api + ```cpp // 无标签指标减少计数 void dec(double value = 1); @@ -151,12 +156,18 @@ class metric_t { // 获取标签的键,如{"method", "url"} const std::vector& labels_name(); + // 获取静态标签,如{{"method", "GET"}, {"code", "200"}} + const std::map& get_static_labels(); + // 序列化,调用派生类实现序列化 virtual void serialize(std::string& str); // 给summary专用的api,序列化,调用派生类实现序列化 virtual async_simple::coro::Lazy serialize_async(std::string& out); + // 序列化到json + void serialize_to_json(std::string& str); + // 将基类指针向下转换到派生类指针,如: // std::shared_ptr c = std::make_shared("test", "test"); // counter_t* t = c->as(); @@ -188,6 +199,7 @@ CHECK(m1->as()->value() == 1); ``` 如果希望动态注册的到管理器则应该调用register_metric_dynamic接口,后面根据名称获取指标对象时则调用get_metric_dynamic接口,dynamic接口内部会加锁。 + ```cpp auto c = std::make_shared("qps_count", "qps help"); auto g = std::make_shared("fd_count", "fd count help"); @@ -222,6 +234,10 @@ struct metric_manager_t { static bool register_metric_static(std::shared_ptr metric); static bool register_metric_dynamic(std::shared_ptr metric); + // 根据metric名称删除metric + static bool remove_metric_static(const std::string& name); + static bool remove_metric_dynamic(const std::string& name); + // 获取注册的所有指标对象 static std::map> metric_map_static(); static std::map> metric_map_dynamic(); @@ -244,9 +260,43 @@ struct metric_manager_t { static std::shared_ptr get_metric_static(const std::string& name); static std::shared_ptr get_metric_dynamic(const std::string& name); + // 根据静态标签获取所有的指标, 如{{"method", "GET"}, {"url", "/"}} + static std::vector> get_metric_by_labels_static( + const std::map& labels); + + // 根据标签值获取所有的静态标签的指标, 如{"method", "GET"} + static std::vector> get_metric_by_label_static( + const std::pair& label); + + // 根据标签值获取所有动态标签的指标, 如{"method", "GET"} + static std::vector> get_metric_by_labels_dynamic( + const std::map& labels); + // 序列化 static async_simple::coro::Lazy serialize_static(); static async_simple::coro::Lazy serialize_dynamic(); + + // 序列化静态标签的指标到json + static std::string serialize_to_json_static(); + // 序列化动态标签的指标到json + static std::string serialize_to_json_dynamic(); + // 序列化metric集合到json + static std::string serialize_to_json( + const std::vector>& metrics); + + // 过滤配置选项,如果name_regex和label_regex都设置了,则会检查这两个条件,如果只设置了一个则只检查设置过的条件 + struct metric_filter_options { + std::optional name_regex{}; // metric 名称的过滤正则表达式 + std::optional label_regex{};// metric label名称的过滤正则表达式 + bool is_white = true; //true: 白名单,包括语义;false: 黑名单,排除语义 + }; + + // 过滤静态标签的指标 + static std::vector> filter_metrics_static( + const metric_filter_options& options); + // 过滤动态标签的指标 + static std::vector> filter_metrics_dynamic( + const metric_filter_options& options); }; using default_metric_manager = metric_manager_t<0>; ``` @@ -267,11 +317,23 @@ using my_metric_manager = metric_manager_t; // 内部还有一个+Inf 默认的桶,当输入的数据不在前面设置这些桶中,则会落到+Inf 默认桶中。 // 实际上桶的总数为 buckets.size() + 1 // 每个bucket 实际上对应了一个counter指标 +// 调用此函数,则metric为静态metric指标 histogram_t(std::string name, std::string help, std::vector buckets); +// labels_value: 标签key,后面可以使用动态标签值去observe,调用此函数则metric为动态metric 指标 +histogram_t(std::string name, std::string help, std::vector buckets, + std::vector labels_name); + +// labels: 静态标签,调用此函数则metric为静态metric指标 +histogram_t(std::string name, std::string help, std::vector buckets, + std::map labels); + // 往histogram_t 中插入数据,内部会自动增加对应桶的计数 void observe(double value); +// 根据标签值插入数据,可以是动态标签值也可以是静态标签值。如果是静态标签,会做额外的检车,检查传入的labels_value是否和注册时的静态标签值是否相同,不相同会抛异常; +void observe(const std::vector &labels_value, double value); + // 获取所有桶对应的counter指标对象 std::vector> get_bucket_counts(); @@ -301,18 +363,63 @@ void serialize(std::string& str); CHECK(str.find("test_bucket{le=\"+Inf\"}") != std::string::npos); ``` +创建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< labels_name); + +// static_labels:静态标签,调用此函数则metric为静态metric 指标 +summary_t(std::string name, std::string help, Quantiles quantiles, std::map static_labels); + // 往summary_t插入数据,会自动计算百分位的数量 void observe(double value); -// 获取百分位结果 -async_simple::coro::Lazy> get_rates(); +// 根据标签值(动态或静态的标签值,依据构造函数决定是动态还是静态metric),往summary_t插入数据,会自动计算百分位的数量 +void observe(std::vector labels_value, double value); + +// 获取分位数结果, sum 和count +async_simple::coro::Lazy> get_rates(double &sum, + uint64_t &count) +// 根据标签获取分位数结果, sum 和count +async_simple::coro::Lazy> get_rates( + const std::vector &labels_value, double &sum, + uint64_t &count); // 获取总和 async_simple::coro::Lazy get_sum(); @@ -349,6 +456,45 @@ async_simple::coro::Lazy serialize_async(std::string &str); ``` summary 百分位的计算相比其它指标是最耗时的,应该避免在关键路径上使用它以免对性能造成影响。 +创建Summary时需要指定分位数和误差,分位数在0到1之间,左右都为闭区间,比如p50就是一个中位数,p99指中位数为0.99的分位数。 +```cpp + 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; +``` +输出: +``` +# 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 +``` + +## 配置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 结果了。 + # cinatra http server中启用内置的metric指标 http server 内置的指标: diff --git a/press_tool/CMakeLists.txt b/press_tool/CMakeLists.txt index a734e5b1..04b88a16 100644 --- a/press_tool/CMakeLists.txt +++ b/press_tool/CMakeLists.txt @@ -9,6 +9,9 @@ set(cinatra_press_tool include_directories(../include) include_directories(../include/cinatra) +if(ENABLE_METRIC_JSON) + include_directories(../iguana) +endif() add_definitions(-DBENCHMARK_TEST) add_executable(${project_name} ${cinatra_press_tool}) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1d0bf56c..23ab442a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,6 +12,11 @@ target_include_directories(${project_name} PRIVATE ${cinatra_SOURCE_DIR}/include ${cinatra_SOURCE_DIR}/include/cinatra ) +if(ENABLE_METRIC_JSON) + target_include_directories(${project_name} PRIVATE + ${cinatra_SOURCE_DIR}/iguana + ) +endif() option(SKIP_TIME_TEST "skip time tests" OFF) if(SKIP_TIME_TEST) @@ -47,6 +52,7 @@ endif() ## manual import include_directories(${cinatra_SOURCE_DIR}/include) include_directories(${cinatra_SOURCE_DIR}/include/cinatra) +include_directories(${cinatra_SOURCE_DIR}/iguana) add_executable(test_corofile test_corofile.cpp diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index d5014605..86a6039d 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -11,6 +11,7 @@ #include "cinatra/ylt/metric/summary.hpp" #include "doctest/doctest.h" using namespace ylt; +using namespace ylt::metric; TEST_CASE("test no lable") { { @@ -143,6 +144,13 @@ TEST_CASE("test gauge") { CHECK(g.value() == 2); g.inc(0); +#ifdef CINATRA_ENABLE_METRIC_JSON + std::string str_json; + g.serialize_to_json(str_json); + std::cout << str_json << "\n"; + CHECK(str_json.find("\"value\":2") != std::string::npos); +#endif + g.dec(); CHECK(g.value() == 1); g.dec(); @@ -160,6 +168,15 @@ TEST_CASE("test gauge") { values = g.value_map(); CHECK(values[{"GET", "200", "/"}] == 3); + g.inc({"POST", "200", "/"}, 4); + +#ifdef CINATRA_ENABLE_METRIC_JSON + std::string str_json; + g.serialize_to_json(str_json); + std::cout << str_json << "\n"; + CHECK(str_json.find("\"code\":\"200\"") != std::string::npos); +#endif + std::string str; g.serialize(str); std::cout << str; @@ -179,7 +196,7 @@ TEST_CASE("test gauge") { } TEST_CASE("test histogram") { - histogram_t h("test", "help", {5.0, 10.0, 20.0, 50.0, 100.0}); + histogram_t h("test", "help", {5.23, 10.54, 20.0, 50.0, 100.0}); h.observe(23); auto counts = h.get_bucket_counts(); CHECK(counts[3]->value() == 1); @@ -193,11 +210,18 @@ TEST_CASE("test histogram") { CHECK(counts[0]->value() == 1); std::string str; h.serialize(str); - std::cout << str; + std::cout << str << "\n"; 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=\"5.23") != std::string::npos); CHECK(str.find("test_bucket{le=\"+Inf\"}") != std::string::npos); + +#ifdef CINATRA_ENABLE_METRIC_JSON + std::string str_json; + h.serialize_to_json(str_json); + std::cout << str_json << "\n"; + CHECK(str_json.find("\"5.23\":1") != std::string::npos); +#endif } TEST_CASE("test summary") { @@ -221,6 +245,13 @@ TEST_CASE("test summary") { 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); + +#ifdef CINATRA_ENABLE_METRIC_JSON + std::string str_json; + async_simple::coro::syncAwait(summary.serialize_to_json_async(str_json)); + std::cout << str_json << "\n"; + CHECK(str_json.find("\"0.9\":") != std::string::npos); +#endif } TEST_CASE("test register metric") { @@ -249,8 +280,7 @@ TEST_CASE("test register metric") { CHECK(map["get_count"]->as()->value() == 1); CHECK(map["get_guage_count"]->as()->value() == 1); - auto s = - async_simple::coro::syncAwait(default_metric_manager::serialize_static()); + auto s = default_metric_manager::serialize_static(); std::cout << s << "\n"; CHECK(s.find("get_count 1") != std::string::npos); CHECK(s.find("get_guage_count 1") != std::string::npos); @@ -279,6 +309,474 @@ TEST_CASE("test register metric") { } } +TEST_CASE("test remove metric and serialize metrics") { + using metric_mgr = metric_manager_t<1>; + metric_mgr::create_metric_dynamic("test_counter", ""); + metric_mgr::create_metric_dynamic("test_counter2", ""); + + size_t count = metric_mgr::metric_count_dynamic(); + CHECK(count == 2); + + metric_mgr::remove_metric_dynamic("test_counter"); + count = metric_mgr::metric_count_dynamic(); + CHECK(count == 1); + + metric_mgr::remove_metric_dynamic("test_counter2"); + count = metric_mgr::metric_count_dynamic(); + CHECK(count == 0); + + CHECK_THROWS_AS( + metric_mgr::create_metric_static("test_static_counter", ""), + std::invalid_argument); + + using metric_mgr2 = metric_manager_t<2>; + auto c = + metric_mgr2::create_metric_static("test_static_counter", ""); + auto c2 = + metric_mgr2::create_metric_static("test_static_counter2", ""); + c->inc(); + c2->inc(); + +#ifdef CINATRA_ENABLE_METRIC_JSON + auto s = metric_mgr2::serialize_to_json_static(); + std::cout << s << "\n"; + auto s1 = metric_mgr2::serialize_to_json({c, c2}); + CHECK(s == s1); +#endif + CHECK_THROWS_AS(metric_mgr2::metric_count_dynamic(), std::invalid_argument); + count = metric_mgr2::metric_count_static(); + CHECK(count == 2); + CHECK_THROWS_AS(metric_mgr2::remove_metric_dynamic("test_static_counter"), + std::invalid_argument); + + metric_mgr2::remove_metric_static("test_static_counter"); + count = metric_mgr2::metric_count_static(); + CHECK(count == 1); +} + +TEST_CASE("test filter metrics static") { + using metric_mgr = metric_manager_t<3>; + auto c = metric_mgr::create_metric_static( + "test_static_counter", "", + std::map{{"method", "GET"}}); + auto c2 = metric_mgr::create_metric_static( + "test_static_counter2", "", + std::map{{"url", "/"}}); + c->inc({"GET"}); + c2->inc({"/"}); + + metric_filter_options options; + options.name_regex = ".*counter.*"; + { + auto metrics = metric_mgr::filter_metrics_static(options); + CHECK(metrics.size() == 2); + + auto s = metric_mgr::serialize(metrics); + CHECK(s.find("test_static_counter") != std::string::npos); + std::cout << s << "\n"; + } + + options.label_regex = ".*ur.*"; + { + auto metrics = metric_mgr::filter_metrics_static(options); + CHECK(metrics.size() == 1); + auto s = metric_mgr::serialize(metrics); + CHECK(s.find("test_static_counter2") != std::string::npos); + std::cout << s << "\n"; + } + + options.name_regex = "no_such_name"; + { + auto metrics = metric_mgr::filter_metrics_static(options); + CHECK(metrics.empty()); + auto s = metric_mgr::serialize(metrics); + CHECK(s.empty()); + } + + options = {}; + options.label_regex = "no_such_label"; + { + auto metrics = metric_mgr::filter_metrics_static(options); + CHECK(metrics.empty()); + auto s = metric_mgr::serialize(metrics); + CHECK(s.empty()); + } + + // don't filter + options = {}; + { + auto metrics = metric_mgr::filter_metrics_static(options); + CHECK(metrics.size() == 2); + } + + // black + options.label_regex = ".*ur.*"; + options.is_white = false; + { + auto metrics = metric_mgr::filter_metrics_static(options); + CHECK(metrics.size() == 1); + auto s = metric_mgr::serialize(metrics); + CHECK(s.find("test_static_counter") != std::string::npos); + CHECK(s.find("test_static_counter2") == std::string::npos); + } + + options = {}; + options.label_regex = ".*ur.*"; + options.is_white = false; + { + auto metrics = metric_mgr::filter_metrics_static(options); + CHECK(metrics.size() == 1); + auto s = metric_mgr::serialize(metrics); + CHECK(s.find("test_static_counter") != std::string::npos); + CHECK(s.find("method") != std::string::npos); + CHECK(s.find("test_static_counter2") == std::string::npos); + CHECK(s.find("url") == std::string::npos); + } +} + +TEST_CASE("test filter metrics dynamic") { + using metric_mgr = metric_manager_t<4>; + auto c = metric_mgr::create_metric_dynamic( + "test_dynamic_counter", "", std::vector{{"method"}}); + auto c2 = metric_mgr::create_metric_dynamic( + "test_dynamic_counter2", "", std::vector{{"url"}}); + c->inc({"GET"}); + c->inc({"POST"}); + c2->inc({"/"}); + c2->inc({"/test"}); + + metric_filter_options options; + options.name_regex = ".*counter.*"; + { + auto metrics = metric_mgr::filter_metrics_dynamic(options); + CHECK(metrics.size() == 2); + + auto s = metric_mgr::serialize(metrics); + CHECK(s.find("test_dynamic_counter") != std::string::npos); + std::cout << s << "\n"; + } + + options.label_regex = ".*ur.*"; + { + auto metrics = metric_mgr::filter_metrics_dynamic(options); + CHECK(metrics.size() == 1); + auto s = metric_mgr::serialize(metrics); + CHECK(s.find("test_dynamic_counter2") != std::string::npos); + std::cout << s << "\n"; + } + + options.name_regex = "no_such_name"; + { + auto metrics = metric_mgr::filter_metrics_dynamic(options); + CHECK(metrics.empty()); + auto s = metric_mgr::serialize(metrics); + CHECK(s.empty()); + } + + options = {}; + options.label_regex = "no_such_label"; + { + auto metrics = metric_mgr::filter_metrics_dynamic(options); + CHECK(metrics.empty()); + auto s = metric_mgr::serialize(metrics); + CHECK(s.empty()); + } + + // don't filter + options = {}; + { + auto metrics = metric_mgr::filter_metrics_dynamic(options); + CHECK(metrics.size() == 2); + } + + // black + options.label_regex = ".*ur.*"; + options.is_white = false; + { + auto metrics = metric_mgr::filter_metrics_dynamic(options); + CHECK(metrics.size() == 1); + auto s = metric_mgr::serialize(metrics); + CHECK(s.find("test_dynamic_counter") != std::string::npos); + CHECK(s.find("test_dynamic_counter2") == std::string::npos); + } + + options = {}; + options.label_regex = ".*ur.*"; + options.is_white = false; + { + auto metrics = metric_mgr::filter_metrics_dynamic(options); + CHECK(metrics.size() == 1); + auto s = metric_mgr::serialize(metrics); + CHECK(s.find("test_dynamic_counter") != std::string::npos); + CHECK(s.find("method") != std::string::npos); + CHECK(s.find("test_dynamic_counter2") == std::string::npos); + CHECK(s.find("url") == std::string::npos); + } +} + +TEST_CASE("test get metric by static labels and label") { + using metric_mgr = metric_manager_t<9>; + metric_mgr::create_metric_static( + "http_req_test", "", + std::map{{"method", "GET"}, {"url", "/"}}); + metric_mgr::create_metric_static( + "http_req_test1", "", + std::map{{"method", "POST"}, {"url", "/"}}); + metric_mgr::create_metric_static( + "http_req_test2", "", + std::map{{"method", "GET"}, {"url", "/test"}}); + + auto v = metric_mgr::get_metric_by_labels_static( + std::map{{"method", "GET"}, {"url", "/test"}}); + CHECK(v[0]->name() == "http_req_test2"); + + v = metric_mgr::get_metric_by_labels_static( + std::map{{"method", "GET"}, {"url", "/"}}); + CHECK(v[0]->name() == "http_req_test"); + + auto h1 = metric_mgr::create_metric_static( + "http_req_static_hist", "help", + std::vector{5.23, 10.54, 20.0, 50.0, 100.0}, + std::map{{"method", "GET"}, {"url", "/"}}); + + h1->observe({"GET", "/"}, 23); + + auto s1 = metric_mgr::create_metric_static( + "http_req_static_summary", "help", + summary_t::Quantiles{ + {0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}, + std::map{{"method", "GET"}, {"url", "/"}}); + s1->observe({"GET", "/"}, 23); + + auto vec = metric_mgr::get_metric_by_label_static({"method", "GET"}); + CHECK(vec.size() == 4); + + vec = metric_mgr::get_metric_by_label_static({"url", "/"}); + CHECK(vec.size() == 4); + + vec = metric_mgr::get_metric_by_label_static({"url", "/test"}); + CHECK(vec.size() == 1); + + vec = metric_mgr::get_metric_by_label_static({"method", "POST"}); + CHECK(vec.size() == 1); + + vec = metric_mgr::get_metric_by_labels_static( + std::map{{"method", "HEAD"}, {"url", "/test"}}); + CHECK(vec.empty()); + + vec = metric_mgr::get_metric_by_labels_static( + std::map{{"method", "GET"}}); + CHECK(vec.empty()); + + vec = metric_mgr::get_metric_by_label_static({"url", "/index"}); + CHECK(vec.empty()); +} + +TEST_CASE("test get metric by dynamic labels") { + using metric_mgr = metric_manager_t<10>; + auto c = metric_mgr::create_metric_dynamic( + "http_req_static", "", std::vector{"method", "code"}); + + auto c1 = metric_mgr::create_metric_dynamic( + "http_req_static1", "", std::vector{"method", "code"}); + + auto c2 = metric_mgr::create_metric_dynamic( + "http_req_static2", "", std::vector{"method", "code"}); + + auto c3 = metric_mgr::create_metric_dynamic( + "http_req_static3", "", std::vector{"method", "code"}); + + c->inc({"POST", "200"}); + c1->inc({"GET", "200"}); + c2->inc({"POST", "301"}); + c3->inc({"POST", "400"}); + + auto c4 = metric_mgr::create_metric_dynamic( + "http_req_static4", "", std::vector{"host", "url"}); + + auto c5 = metric_mgr::create_metric_dynamic( + "http_req_static5", "", std::vector{"host", "url"}); + + c4->inc({"shanghai", "/"}); + c5->inc({"shanghai", "/test"}); + + auto vec = metric_mgr::get_metric_by_labels_dynamic({{"method", "POST"}}); + CHECK(vec.size() == 3); + + vec = metric_mgr::get_metric_by_labels_dynamic({{"method", "GET"}}); + CHECK(vec.size() == 1); + + vec = metric_mgr::get_metric_by_labels_dynamic({{"host", "shanghai"}}); + CHECK(vec.size() == 2); + + vec = metric_mgr::get_metric_by_labels_dynamic({{"url", "/"}}); + CHECK(vec.size() == 1); + + vec = metric_mgr::get_metric_by_labels_dynamic({{"url", "/test"}}); + CHECK(vec.size() == 1); + + vec = metric_mgr::get_metric_by_labels_dynamic({{"url", "/none"}}); + CHECK(vec.size() == 0); + + vec = metric_mgr::get_metric_by_labels_dynamic({{"method", "HEAD"}}); + CHECK(vec.size() == 0); + + auto h1 = metric_mgr::create_metric_dynamic( + "http_req_static_hist", "help", + std::vector{5.23, 10.54, 20.0, 50.0, 100.0}, + std::vector{"method", "url"}); + + h1->observe({"GET", "/"}, 23); + + auto s1 = metric_mgr::create_metric_dynamic( + "http_req_static_summary", "help", + summary_t::Quantiles{ + {0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}, + std::vector{"method", "url"}); + s1->observe({"GET", "/"}, 23); + + vec = metric_mgr::get_metric_by_labels_dynamic({{"method", "GET"}}); + CHECK(vec.size() >= 2); + + auto str = metric_mgr::serialize(vec); + std::cout << str; + +#ifdef CINATRA_ENABLE_METRIC_JSON + auto json_str = metric_mgr::serialize_to_json(vec); + std::cout << json_str << "\n"; +#endif +} + +TEST_CASE("test histogram serialize with dynamic labels") { + histogram_t h("test", "help", {5.23, 10.54, 20.0, 50.0, 100.0}, + std::vector{"method", "url"}); + h.observe({"GET", "/"}, 23); + auto counts = h.get_bucket_counts(); + CHECK(counts[3]->value({"GET", "/"}) == 1); + h.observe({"GET", "/"}, 42); + CHECK(counts[3]->value({"GET", "/"}) == 2); + h.observe({"GET", "/"}, 60); + CHECK(counts[4]->value({"GET", "/"}) == 1); + h.observe({"GET", "/"}, 120); + CHECK(counts[5]->value({"GET", "/"}) == 1); + h.observe({"GET", "/"}, 1); + CHECK(counts[0]->value({"GET", "/"}) == 1); + + h.observe({"POST", "/"}, 23); + CHECK(counts[3]->value({"POST", "/"}) == 1); + h.observe({"POST", "/"}, 42); + CHECK(counts[3]->value({"POST", "/"}) == 2); + h.observe({"POST", "/"}, 60); + CHECK(counts[4]->value({"POST", "/"}) == 1); + h.observe({"POST", "/"}, 120); + CHECK(counts[5]->value({"POST", "/"}) == 1); + h.observe({"POST", "/"}, 1); + CHECK(counts[0]->value({"POST", "/"}) == 1); + + std::string str; + h.serialize(str); + std::cout << str; + +#ifdef CINATRA_ENABLE_METRIC_JSON + std::string str_json; + h.serialize_to_json(str_json); + std::cout << str_json << "\n"; +#endif +} + +TEST_CASE("test histogram serialize with static labels") { + histogram_t h( + "test", "help", {5.23, 10.54, 20.0, 50.0, 100.0}, + std::map{{"method", "GET"}, {"url", "/"}}); + h.observe({"GET", "/"}, 23); + auto counts = h.get_bucket_counts(); + CHECK(counts[3]->value({"GET", "/"}) == 1); + h.observe({"GET", "/"}, 42); + CHECK(counts[3]->value({"GET", "/"}) == 2); + h.observe({"GET", "/"}, 60); + CHECK(counts[4]->value({"GET", "/"}) == 1); + h.observe({"GET", "/"}, 120); + CHECK(counts[5]->value({"GET", "/"}) == 1); + h.observe({"GET", "/"}, 1); + CHECK(counts[0]->value({"GET", "/"}) == 1); + + std::string str; + h.serialize(str); + std::cout << str; + +#ifdef CINATRA_ENABLE_METRIC_JSON + std::string str_json; + h.serialize_to_json(str_json); + std::cout << str_json << "\n"; +#endif +} + +TEST_CASE("test summary with dynamic labels") { + summary_t summary{"test_summary", + "summary help", + {{0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}, + std::vector{"method", "url"}}; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distr(1, 100); + for (int i = 0; i < 50; i++) { + summary.observe({"GET", "/"}, distr(gen)); + summary.observe({"POST", "/test"}, distr(gen)); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + double sum; + uint64_t count; + auto rates = async_simple::coro::syncAwait( + summary.get_rates({"GET", "/"}, sum, count)); + std::cout << rates.size() << "\n"; + + std::string str; + async_simple::coro::syncAwait(summary.serialize_async(str)); + std::cout << str; + +#ifdef CINATRA_ENABLE_METRIC_JSON + std::string json_str; + async_simple::coro::syncAwait(summary.serialize_to_json_async(json_str)); + std::cout << json_str << "\n"; +#endif +} + +TEST_CASE("test summary with static labels") { + summary_t summary{ + "test_summary", + "summary help", + {{0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}, + std::map{{"method", "GET"}, {"url", "/"}}}; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distr(1, 100); + for (int i = 0; i < 50; i++) { + summary.observe({"GET", "/"}, distr(gen)); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + CHECK_THROWS_AS(summary.observe({"POST", "/"}, 1), std::invalid_argument); + + double sum; + uint64_t count; + auto rates = async_simple::coro::syncAwait( + summary.get_rates({"GET", "/"}, sum, count)); + std::cout << rates.size() << "\n"; + + std::string str; + async_simple::coro::syncAwait(summary.serialize_async(str)); + std::cout << str; + +#ifdef CINATRA_ENABLE_METRIC_JSON + std::string json_str; + async_simple::coro::syncAwait(summary.serialize_to_json_async(json_str)); + std::cout << json_str << "\n"; +#endif +} + 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 1e180fbe243c9a8dc7d8cf288d253d528da3dc6c Mon Sep 17 00:00:00 2001 From: qicosmos Date: Tue, 18 Jun 2024 14:22:21 +0800 Subject: [PATCH 2/3] improve summary (#602) --- .github/workflows/linux_llvm_cov.yml | 3 +- example/CMakeLists.txt | 1 - example/main.cpp | 2 +- .../cinatra/ylt/coro_io/io_context_pool.hpp | 12 ++ include/cinatra/ylt/metric/counter.hpp | 15 ++- include/cinatra/ylt/metric/metric.hpp | 72 +++++++++++- include/cinatra/ylt/metric/summary.hpp | 107 +++++++++++------- lang/how_to_use_metrics.md | 7 ++ press_tool/CMakeLists.txt | 1 - tests/CMakeLists.txt | 1 - tests/test_metric.cpp | 24 ++++ 11 files changed, 195 insertions(+), 50 deletions(-) diff --git a/.github/workflows/linux_llvm_cov.yml b/.github/workflows/linux_llvm_cov.yml index b712449c..54135d57 100644 --- a/.github/workflows/linux_llvm_cov.yml +++ b/.github/workflows/linux_llvm_cov.yml @@ -40,9 +40,10 @@ jobs: ./test_corofile ./test_http_parse ./test_time_util + ./test_metric llvm-profdata merge -sparse test_cinatra-*.profraw -o test_cinatra.profdata - llvm-cov show test_cinatra -object test_corofile -object test_time_util -object test_http_parse -instr-profile=test_cinatra.profdata -format=html -output-dir=../.coverage_llvm_cov -ignore-filename-regex="example|asio|cmdline|async_simple|tests" -show-instantiations=false + llvm-cov show test_cinatra -object test_corofile -object test_time_util -object test_http_parse -object test_metric -instr-profile=test_cinatra.profdata -format=html -output-dir=../.coverage_llvm_cov -ignore-filename-regex="example|asio|cmdline|async_simple|tests" -show-instantiations=false echo "Done!" - name: Upload Coverage Results diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index fdfa250d..acbf21a5 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -6,7 +6,6 @@ set(CINATRA_EXAMPLE ) include_directories(../include) -include_directories(../include/cinatra) if(ENABLE_METRIC_JSON) include_directories(../iguana) endif() diff --git a/example/main.cpp b/example/main.cpp index 0ae1de08..3a1ed04d 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -7,7 +7,7 @@ #include #include "../include/cinatra.hpp" -#include "metric_conf.hpp" +#include "cinatra/metric_conf.hpp" using namespace cinatra; using namespace ylt::metric; diff --git a/include/cinatra/ylt/coro_io/io_context_pool.hpp b/include/cinatra/ylt/coro_io/io_context_pool.hpp index 08018980..ab1e6bef 100644 --- a/include/cinatra/ylt/coro_io/io_context_pool.hpp +++ b/include/cinatra/ylt/coro_io/io_context_pool.hpp @@ -285,6 +285,18 @@ inline T &g_io_context_pool( return *_g_io_context_pool; } +template +inline std::shared_ptr create_io_context_pool( + unsigned pool_size = std::thread::hardware_concurrency()) { + auto pool = std::make_shared(pool_size); + std::thread thrd{[pool] { + pool->run(); + }}; + thrd.detach(); + + return pool; +} + template inline T &g_block_io_context_pool( unsigned pool_size = std::thread::hardware_concurrency()) { diff --git a/include/cinatra/ylt/metric/counter.hpp b/include/cinatra/ylt/metric/counter.hpp index 3c339b07..19ff9f88 100644 --- a/include/cinatra/ylt/metric/counter.hpp +++ b/include/cinatra/ylt/metric/counter.hpp @@ -267,7 +267,12 @@ class counter_t : public metric_t { label_val += value; } #else - label_val += value; + if constexpr (is_atomic) { + label_val.fetch_add(value, std::memory_order_relaxed); + } + else { + label_val += value; + } #endif } break; case op_type_t::DEC: @@ -278,9 +283,13 @@ class counter_t : public metric_t { else { label_val -= value; } - #else - label_val -= value; + if constexpr (is_atomic) { + label_val.fetch_sub(value, std::memory_order_relaxed); + } + else { + label_val -= value; + } #endif break; case op_type_t::SET: diff --git a/include/cinatra/ylt/metric/metric.hpp b/include/cinatra/ylt/metric/metric.hpp index ea103e59..f818ed7e 100644 --- a/include/cinatra/ylt/metric/metric.hpp +++ b/include/cinatra/ylt/metric/metric.hpp @@ -14,6 +14,11 @@ #include "async_simple/coro/Lazy.h" #include "async_simple/coro/SyncAwait.h" #include "cinatra/cinatra_log_wrapper.hpp" +#if __has_include("ylt/coro_io/coro_io.hpp") +#include "ylt/coro_io/coro_io.hpp" +#else +#include "cinatra/ylt/coro_io/coro_io.hpp" +#endif #ifdef CINATRA_ENABLE_METRIC_JSON namespace iguana { @@ -46,7 +51,10 @@ class metric_t { public: metric_t() = default; metric_t(MetricType type, std::string name, std::string help) - : type_(type), name_(std::move(name)), help_(std::move(help)) {} + : type_(type), + name_(std::move(name)), + help_(std::move(help)), + metric_created_time_(std::chrono::system_clock::now()) {} metric_t(MetricType type, std::string name, std::string help, std::vector labels_name) : metric_t(type, std::move(name), std::move(help)) { @@ -70,6 +78,8 @@ class metric_t { MetricType metric_type() { return type_; } + auto get_created_time() { return metric_created_time_; } + std::string_view metric_name() { switch (type_) { case MetricType::Counter: @@ -173,6 +183,7 @@ class metric_t { std::vector labels_name_; // read only std::vector labels_value_; // read only bool use_atomic_ = false; + std::chrono::system_clock::time_point metric_created_time_{}; }; template @@ -237,6 +248,14 @@ struct metric_manager_t { return r; } + static void set_metric_max_age(std::chrono::steady_clock::duration max_age, + std::chrono::steady_clock::duration + check_duration = std::chrono::minutes(5)) { + metric_max_age_ = max_age; + metric_check_duration_ = check_duration; + start_check(); + } + static auto metric_map_static() { return metric_map_impl(); } static auto metric_map_dynamic() { return metric_map_impl(); } @@ -546,12 +565,63 @@ struct metric_manager_t { return filtered_metrics; } + static void check_impl() { + check_timer_->expires_after(metric_check_duration_); + check_timer_->async_wait([](std::error_code ec) { + if (ec) { + return; + } + + check_clean_metrics(); + check_impl(); + }); + } + + static void start_check() { + if (has_start_check_metric_) { + return; + } + + has_start_check_metric_ = true; + + executor_ = coro_io::create_io_context_pool(1); + + check_timer_ = + std::make_shared(executor_->get_executor()); + + check_impl(); + } + + static void check_clean_metrics() { + auto cur_time = std::chrono::system_clock::now(); + { + auto lock = get_lock(); + for (auto it = metric_map_.begin(); it != metric_map_.end();) { + if (cur_time - it->second->get_created_time() > metric_max_age_) { + metric_map_.erase(it++); + } + else { + ++it; + } + } + } + } + + static inline bool has_start_check_metric_ = false; + static inline std::shared_ptr check_timer_ = nullptr; + static inline std::shared_ptr executor_ = nullptr; + static inline std::mutex mtx_; static inline std::map> metric_map_; static inline null_mutex_t null_mtx_; static inline std::atomic_bool need_lock_ = true; static inline std::once_flag flag_; + + static inline std::chrono::steady_clock::duration metric_max_age_{ + std::chrono::hours(24)}; + static inline std::chrono::steady_clock::duration metric_check_duration_{ + std::chrono::minutes(5)}; }; using default_metric_manager = metric_manager_t<0>; diff --git a/include/cinatra/ylt/metric/summary.hpp b/include/cinatra/ylt/metric/summary.hpp index bad8a132..fcf9f03a 100644 --- a/include/cinatra/ylt/metric/summary.hpp +++ b/include/cinatra/ylt/metric/summary.hpp @@ -3,8 +3,11 @@ #include "detail/time_window_quantiles.hpp" #include "metric.hpp" -#include "ylt/coro_io/coro_io.hpp" +#if __has_include("ylt/util/concurrentqueue.h") #include "ylt/util/concurrentqueue.h" +#else +#include "cinatra/ylt/util/concurrentqueue.h" +#endif namespace ylt::metric { #ifdef CINATRA_ENABLE_METRIC_JSON @@ -39,7 +42,6 @@ class summary_t : public metric_t { metric_t(MetricType::Summary, std::move(name), std::move(help)), max_age_(max_age), age_buckets_(age_buckets) { - init_executor(); init_block(block_); block_->quantile_values_ = std::make_shared(quantiles_, max_age, age_buckets); @@ -55,7 +57,6 @@ class summary_t : public metric_t { std::move(labels_name)), max_age_(max_age), age_buckets_(age_buckets) { - init_executor(); init_block(labels_block_); } @@ -68,7 +69,6 @@ class summary_t : public metric_t { std::move(static_labels)), max_age_(max_age), age_buckets_(age_buckets) { - init_executor(); init_block(labels_block_); labels_block_->label_quantile_values_[labels_value_] = std::make_shared(quantiles_, max_age, age_buckets); @@ -85,9 +85,6 @@ class summary_t : public metric_t { if (labels_block_) { labels_block_->stop_ = true; } - - work_ = nullptr; - thd_.join(); } struct block_t { @@ -117,7 +114,16 @@ class summary_t : public metric_t { if (!labels_name_.empty()) { throw std::invalid_argument("not a default label metric"); } + while (block_->sample_queue_.size_approx() >= 20000000) { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } block_->sample_queue_.enqueue(value); + + bool expected = false; + if (is_coro_started_.compare_exchange_strong(expected, true)) { + start(block_).via(excutor_->get_executor()).start([](auto &&) { + }); + } } void observe(std::vector labels_value, double value) { @@ -129,7 +135,16 @@ class summary_t : public metric_t { throw std::invalid_argument("not equal with static label"); } } + while (labels_block_->sample_queue_.size_approx() >= 20000000) { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } labels_block_->sample_queue_.enqueue({std::move(labels_value), value}); + + bool expected = false; + if (is_coro_started_.compare_exchange_strong(expected, true)) { + start(labels_block_).via(excutor_->get_executor()).start([](auto &&) { + }); + } } async_simple::coro::Lazy> get_rates(double &sum, @@ -147,7 +162,7 @@ class summary_t : public metric_t { vec.push_back(block_->quantile_values_->get(quantile.quantile)); } }, - excutor_.get()); + excutor_->get_executor()); co_return vec; } @@ -178,7 +193,7 @@ class summary_t : public metric_t { vec.push_back(it->second->get(quantile.quantile)); } }, - excutor_.get()); + excutor_->get_executor()); co_return vec; } @@ -190,7 +205,7 @@ class summary_t : public metric_t { [this] { return labels_block_->label_sum_; }, - excutor_.get())); + excutor_->get_executor())); return ret.value(); } @@ -199,7 +214,7 @@ class summary_t : public metric_t { [this] { return block_->sum_; }, - excutor_.get()); + excutor_->get_executor()); co_return ret.value(); } @@ -208,7 +223,7 @@ class summary_t : public metric_t { [this] { return block_->count_; }, - excutor_.get()); + excutor_->get_executor()); co_return ret.value(); } @@ -275,19 +290,10 @@ class summary_t : public metric_t { } #endif private: - void init_executor() { - work_ = std::make_shared(ctx_); - thd_ = std::thread([this] { - ctx_.run(); - }); - excutor_ = - std::make_unique>(ctx_.get_executor()); - } - template void init_block(std::shared_ptr &block) { block = std::make_shared(); - start(block).via(excutor_.get()).start([](auto &&) { + start(block).via(excutor_->get_executor()).start([](auto &&) { }); } @@ -306,24 +312,34 @@ class summary_t : public metric_t { } } - co_await async_simple::coro::Yield{}; - if (block->sample_queue_.size_approx() == 0) { - co_await coro_io::sleep_for(std::chrono::milliseconds(5), - excutor_.get()); + is_coro_started_ = false; + if (block->sample_queue_.size_approx() == 0) { + break; + } + + bool expected = false; + if (!is_coro_started_.compare_exchange_strong(expected, true)) { + break; + } + + continue; } + + co_await async_simple::coro::Yield{}; } co_return; } - async_simple::coro::Lazy start(std::shared_ptr block) { + async_simple::coro::Lazy start( + std::shared_ptr label_block) { summary_label_sample sample; size_t count = 1000000; - while (!block->stop_) { + while (!label_block->stop_) { size_t index = 0; - while (block->sample_queue_.try_dequeue(sample)) { - auto &ptr = block->label_quantile_values_[sample.labels_value]; + while (label_block->sample_queue_.try_dequeue(sample)) { + auto &ptr = label_block->label_quantile_values_[sample.labels_value]; if (ptr == nullptr) { ptr = std::make_shared(quantiles_, max_age_, @@ -332,8 +348,8 @@ class summary_t : public metric_t { ptr->insert(sample.value); - block->label_count_[sample.labels_value] += 1; - block->label_sum_[sample.labels_value] += sample.value; + label_block->label_count_[sample.labels_value] += 1; + label_block->label_sum_[sample.labels_value] += sample.value; index++; if (index == count) { break; @@ -342,10 +358,20 @@ class summary_t : public metric_t { co_await async_simple::coro::Yield{}; - if (block->sample_queue_.size_approx() == 0) { - co_await coro_io::sleep_for(std::chrono::milliseconds(5), - excutor_.get()); + if (label_block->sample_queue_.size_approx() == 0) { + is_coro_started_ = false; + if (label_block->sample_queue_.size_approx() == 0) { + break; + } + + bool expected = false; + if (!is_coro_started_.compare_exchange_strong(expected, true)) { + break; + } + + continue; } + co_await async_simple::coro::Yield{}; } co_return; @@ -362,7 +388,7 @@ class summary_t : public metric_t { [this] { return labels_block_->label_sum_; }, - excutor_.get()); + excutor_->get_executor()); for (auto &[labels_value, sum_val] : sum_map.value()) { double sum = 0; @@ -403,7 +429,7 @@ class summary_t : public metric_t { [this] { return labels_block_->label_sum_; }, - excutor_.get()); + excutor_->get_executor()); json_summary_t summary{name_, help_, std::string(metric_name())}; @@ -430,11 +456,10 @@ class summary_t : public metric_t { Quantiles quantiles_; // readonly std::shared_ptr block_; std::shared_ptr labels_block_; - std::unique_ptr> excutor_ = nullptr; - std::shared_ptr work_; - asio::io_context ctx_; - std::thread thd_; + static inline std::shared_ptr excutor_ = + coro_io::create_io_context_pool(1); std::chrono::milliseconds max_age_; int age_buckets_; + std::atomic is_coro_started_ = false; }; } // namespace ylt::metric \ No newline at end of file diff --git a/lang/how_to_use_metrics.md b/lang/how_to_use_metrics.md index f7f17bfa..3b14ae63 100644 --- a/lang/how_to_use_metrics.md +++ b/lang/how_to_use_metrics.md @@ -234,6 +234,13 @@ struct metric_manager_t { static bool register_metric_static(std::shared_ptr metric); static bool register_metric_dynamic(std::shared_ptr metric); + // 启用metric 定时清理功能,在使用metric之前设置 + // max_age:设置metric的过期时间,过期之后metric会被清理 + // check_duration:设置定期监测metric过期的时间间隔 + static void set_metric_max_age(std::chrono::steady_clock::duration max_age, + std::chrono::steady_clock::duration + check_duration = std::chrono::minutes(5)); + // 根据metric名称删除metric static bool remove_metric_static(const std::string& name); static bool remove_metric_dynamic(const std::string& name); diff --git a/press_tool/CMakeLists.txt b/press_tool/CMakeLists.txt index 04b88a16..87c2122a 100644 --- a/press_tool/CMakeLists.txt +++ b/press_tool/CMakeLists.txt @@ -8,7 +8,6 @@ set(cinatra_press_tool ) include_directories(../include) -include_directories(../include/cinatra) if(ENABLE_METRIC_JSON) include_directories(../iguana) endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 23ab442a..d62d7e7f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -51,7 +51,6 @@ endif() ## manual import include_directories(${cinatra_SOURCE_DIR}/include) -include_directories(${cinatra_SOURCE_DIR}/include/cinatra) include_directories(${cinatra_SOURCE_DIR}/iguana) add_executable(test_corofile diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index 86a6039d..1f78d722 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -777,6 +777,30 @@ TEST_CASE("test summary with static labels") { #endif } +TEST_CASE("check clean metrics") { + using metric_mgr = metric_manager_t<11>; + metric_mgr::create_metric_dynamic("test_counter", ""); + metric_mgr::create_metric_dynamic("test_counter2", ""); + metric_mgr::create_metric_dynamic( + "http_req_static_hist", "help", + std::vector{5.23, 10.54, 20.0, 50.0, 100.0}, + std::vector{"method", "url"}); + + metric_mgr::create_metric_dynamic( + "http_req_static_summary", "help", + summary_t::Quantiles{ + {0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}, + std::vector{"method", "url"}); + + CHECK(metric_mgr::metric_count_dynamic() == 4); + metric_mgr::set_metric_max_age(std::chrono::milliseconds(10), + std::chrono::milliseconds(10)); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + CHECK(metric_mgr::metric_count_dynamic() == 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 235d3ed205d8e82af0605dd06b6f5c07163b8979 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Tue, 18 Jun 2024 18:00:11 +0800 Subject: [PATCH 3/3] [metric]simplify clean (#603) --- include/cinatra/ylt/metric/counter.hpp | 20 ++---- include/cinatra/ylt/metric/histogram.hpp | 6 +- include/cinatra/ylt/metric/metric.hpp | 84 ++++++------------------ include/cinatra/ylt/metric/summary.hpp | 26 +++----- lang/how_to_use_metrics.md | 10 +-- tests/test_metric.cpp | 24 ------- 6 files changed, 37 insertions(+), 133 deletions(-) diff --git a/include/cinatra/ylt/metric/counter.hpp b/include/cinatra/ylt/metric/counter.hpp index 19ff9f88..d801722a 100644 --- a/include/cinatra/ylt/metric/counter.hpp +++ b/include/cinatra/ylt/metric/counter.hpp @@ -60,12 +60,8 @@ class counter_t : public metric_t { } } - std::map, double, - std::less>> - value_map() override { - std::map, double, - std::less>> - map; + metric_hash_map value_map() override { + metric_hash_map map; if (use_atomic_) { map = {atomic_value_map_.begin(), atomic_value_map_.end()}; } @@ -192,9 +188,7 @@ class counter_t : public metric_t { } } - std::map, std::atomic, - std::less>> - &atomic_value_map() { + metric_hash_map> &atomic_value_map() { return atomic_value_map_; } @@ -298,14 +292,10 @@ class counter_t : public metric_t { } } - std::map, std::atomic, - std::less>> - atomic_value_map_; + metric_hash_map> atomic_value_map_; std::atomic default_lable_value_ = 0; std::mutex mtx_; - std::map, double, - std::less>> - value_map_; + metric_hash_map value_map_; }; } // namespace ylt::metric \ No newline at end of file diff --git a/include/cinatra/ylt/metric/histogram.hpp b/include/cinatra/ylt/metric/histogram.hpp index d85225a5..1f371b85 100644 --- a/include/cinatra/ylt/metric/histogram.hpp +++ b/include/cinatra/ylt/metric/histogram.hpp @@ -100,11 +100,7 @@ class histogram_t : public metric_t { auto get_bucket_counts() { return bucket_counts_; } - std::map, double, - std::less>> - value_map() override { - return sum_->value_map(); - } + metric_hash_map value_map() override { return sum_->value_map(); } void serialize(std::string &str) override { if (!sum_->labels_name().empty()) { diff --git a/include/cinatra/ylt/metric/metric.hpp b/include/cinatra/ylt/metric/metric.hpp index f818ed7e..12c25c6b 100644 --- a/include/cinatra/ylt/metric/metric.hpp +++ b/include/cinatra/ylt/metric/metric.hpp @@ -47,6 +47,25 @@ struct metric_filter_options { bool is_white = true; }; +struct vector_hash { + size_t operator()(const std::vector& vec) const { + unsigned int seed = 131; + unsigned int hash = 0; + + for (const auto& str : vec) { + for (auto ch : str) { + hash = hash * seed + ch; + } + } + + return (hash & 0x7FFFFFFF); + } +}; + +template +using metric_hash_map = + std::unordered_map, T, vector_hash>; + class metric_t { public: metric_t() = default; @@ -102,11 +121,7 @@ class metric_t { return static_labels_; } - virtual std::map, double, - std::less>> - value_map() { - return {}; - } + virtual metric_hash_map value_map() { return {}; } virtual void serialize(std::string& str) {} @@ -248,14 +263,6 @@ struct metric_manager_t { return r; } - static void set_metric_max_age(std::chrono::steady_clock::duration max_age, - std::chrono::steady_clock::duration - check_duration = std::chrono::minutes(5)) { - metric_max_age_ = max_age; - metric_check_duration_ = check_duration; - start_check(); - } - static auto metric_map_static() { return metric_map_impl(); } static auto metric_map_dynamic() { return metric_map_impl(); } @@ -565,63 +572,12 @@ struct metric_manager_t { return filtered_metrics; } - static void check_impl() { - check_timer_->expires_after(metric_check_duration_); - check_timer_->async_wait([](std::error_code ec) { - if (ec) { - return; - } - - check_clean_metrics(); - check_impl(); - }); - } - - static void start_check() { - if (has_start_check_metric_) { - return; - } - - has_start_check_metric_ = true; - - executor_ = coro_io::create_io_context_pool(1); - - check_timer_ = - std::make_shared(executor_->get_executor()); - - check_impl(); - } - - static void check_clean_metrics() { - auto cur_time = std::chrono::system_clock::now(); - { - auto lock = get_lock(); - for (auto it = metric_map_.begin(); it != metric_map_.end();) { - if (cur_time - it->second->get_created_time() > metric_max_age_) { - metric_map_.erase(it++); - } - else { - ++it; - } - } - } - } - - static inline bool has_start_check_metric_ = false; - static inline std::shared_ptr check_timer_ = nullptr; - static inline std::shared_ptr executor_ = nullptr; - static inline std::mutex mtx_; static inline std::map> metric_map_; static inline null_mutex_t null_mtx_; static inline std::atomic_bool need_lock_ = true; static inline std::once_flag flag_; - - static inline std::chrono::steady_clock::duration metric_max_age_{ - std::chrono::hours(24)}; - static inline std::chrono::steady_clock::duration metric_check_duration_{ - std::chrono::minutes(5)}; }; using default_metric_manager = metric_manager_t<0>; diff --git a/include/cinatra/ylt/metric/summary.hpp b/include/cinatra/ylt/metric/summary.hpp index fcf9f03a..58957bc7 100644 --- a/include/cinatra/ylt/metric/summary.hpp +++ b/include/cinatra/ylt/metric/summary.hpp @@ -98,24 +98,19 @@ class summary_t : public metric_t { struct labels_block_t { std::atomic stop_ = false; moodycamel::ConcurrentQueue sample_queue_; - - std::map, std::shared_ptr, - std::less>> + metric_hash_map> label_quantile_values_; - std::map, std::uint64_t, - std::less>> - label_count_; - std::map, double, - std::less>> - label_sum_; + metric_hash_map label_count_; + metric_hash_map label_sum_; }; void observe(double value) { if (!labels_name_.empty()) { throw std::invalid_argument("not a default label metric"); } - while (block_->sample_queue_.size_approx() >= 20000000) { - std::this_thread::sleep_for(std::chrono::milliseconds(5)); + if (block_->sample_queue_.size_approx() >= 20000000) { + // TODO: record failed count. + return; } block_->sample_queue_.enqueue(value); @@ -135,8 +130,9 @@ class summary_t : public metric_t { throw std::invalid_argument("not equal with static label"); } } - while (labels_block_->sample_queue_.size_approx() >= 20000000) { - std::this_thread::sleep_for(std::chrono::milliseconds(5)); + if (labels_block_->sample_queue_.size_approx() >= 20000000) { + // TODO: record failed count. + return; } labels_block_->sample_queue_.enqueue({std::move(labels_value), value}); @@ -198,9 +194,7 @@ class summary_t : public metric_t { co_return vec; } - std::map, double, - std::less>> - value_map() override { + metric_hash_map value_map() override { auto ret = async_simple::coro::syncAwait(coro_io::post( [this] { return labels_block_->label_sum_; diff --git a/lang/how_to_use_metrics.md b/lang/how_to_use_metrics.md index 3b14ae63..471a6b5a 100644 --- a/lang/how_to_use_metrics.md +++ b/lang/how_to_use_metrics.md @@ -109,8 +109,7 @@ void inc(const std::vector &labels_value, double value = 1); void serialize(std::string &str); // 返回带标签的指标内部的计数map,map的key是标签的值,值是对应计数,如:{{{"GET", "/"}, 100}, {{"POST", "/test"}, 20}} -std::map, double, - std::less>> +std::unordered_map, double, vector_hash> value_map(); ``` @@ -234,13 +233,6 @@ struct metric_manager_t { static bool register_metric_static(std::shared_ptr metric); static bool register_metric_dynamic(std::shared_ptr metric); - // 启用metric 定时清理功能,在使用metric之前设置 - // max_age:设置metric的过期时间,过期之后metric会被清理 - // check_duration:设置定期监测metric过期的时间间隔 - static void set_metric_max_age(std::chrono::steady_clock::duration max_age, - std::chrono::steady_clock::duration - check_duration = std::chrono::minutes(5)); - // 根据metric名称删除metric static bool remove_metric_static(const std::string& name); static bool remove_metric_dynamic(const std::string& name); diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index 1f78d722..86a6039d 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -777,30 +777,6 @@ TEST_CASE("test summary with static labels") { #endif } -TEST_CASE("check clean metrics") { - using metric_mgr = metric_manager_t<11>; - metric_mgr::create_metric_dynamic("test_counter", ""); - metric_mgr::create_metric_dynamic("test_counter2", ""); - metric_mgr::create_metric_dynamic( - "http_req_static_hist", "help", - std::vector{5.23, 10.54, 20.0, 50.0, 100.0}, - std::vector{"method", "url"}); - - metric_mgr::create_metric_dynamic( - "http_req_static_summary", "help", - summary_t::Quantiles{ - {0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}, - std::vector{"method", "url"}); - - CHECK(metric_mgr::metric_count_dynamic() == 4); - metric_mgr::set_metric_max_age(std::chrono::milliseconds(10), - std::chrono::milliseconds(10)); - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - CHECK(metric_mgr::metric_count_dynamic() == 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