From 3e6d502eb565c4c690ab0baa05a9f6dc7cc6e5eb Mon Sep 17 00:00:00 2001 From: qicosmos Date: Fri, 7 Jun 2024 22:35:51 +0800 Subject: [PATCH] [new features]Improve metric (#591) --- example/CMakeLists.txt | 3 + example/main.cpp | 136 ++++++- include/cinatra/coro_http_connection.hpp | 75 +++- include/cinatra/coro_http_server.hpp | 37 +- include/cinatra/metric_conf.hpp | 126 ++++++ .../cinatra/ylt/coro_io/io_context_pool.hpp | 10 + include/cinatra/ylt/metric/counter.hpp | 251 +++++++++--- .../ylt/metric/detail/ckms_quantiles.hpp | 5 +- .../metric/detail/time_window_quantiles.hpp | 4 +- include/cinatra/ylt/metric/gauge.hpp | 48 ++- include/cinatra/ylt/metric/histogram.hpp | 85 ++-- include/cinatra/ylt/metric/metric.hpp | 288 +++++++++++--- include/cinatra/ylt/metric/summary.hpp | 143 +++++-- lang/how_to_use_metrics.md | 373 ++++++++++++++++++ lang/metrict_introduction.md | 2 +- press_tool/CMakeLists.txt | 3 + tests/CMakeLists.txt | 16 +- tests/test_metric.cpp | 204 +++++++--- 18 files changed, 1493 insertions(+), 316 deletions(-) create mode 100644 include/cinatra/metric_conf.hpp create mode 100644 lang/how_to_use_metrics.md diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 17ece08d..e8d17c79 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -5,6 +5,9 @@ set(CINATRA_EXAMPLE main.cpp ) +include_directories(../include) +include_directories(../include/cinatra) + 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 33d36fb8..e864bb8f 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -7,11 +7,10 @@ #include #include "../include/cinatra.hpp" -#include "cinatra/ylt/metric/gauge.hpp" -#include "cinatra/ylt/metric/histogram.hpp" -#include "cinatra/ylt/metric/summary.hpp" +#include "metric_conf.hpp" using namespace cinatra; +using namespace ylt; using namespace std::chrono_literals; void create_file(std::string filename, size_t file_size = 64) { @@ -373,6 +372,8 @@ async_simple::coro::Lazy basic_usage() { response.set_status_and_content(status_type::ok, "ok"); }); + server.use_metrics(); + person_t person{}; server.set_http_handler("/person", &person_t::foo, person); @@ -420,17 +421,21 @@ async_simple::coro::Lazy basic_usage() { // make sure you have install openssl and enable CINATRA_ENABLE_SSL #ifdef CINATRA_ENABLE_SSL coro_http_client client2{}; - result = co_await client2.async_get("https://baidu.com"); - assert(result.status == 200); + + result = client2.post("https://baidu.com", "test", req_content_type::string); + std::cout << result.resp_body << "\n"; + result.net_err.value() assert(result.status == 200); #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"}); + using namespace ylt; + 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"); @@ -443,11 +448,11 @@ void use_metric() { 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); + default_metric_manger::register_metric_dynamic(c); + default_metric_manger::register_metric_dynamic(total); + default_metric_manger::register_metric_dynamic(failed); + default_metric_manger::register_metric_dynamic(h); + default_metric_manger::register_metric_dynamic(summary); std::random_device rd; std::mt19937 gen(rd()); @@ -499,13 +504,112 @@ void use_metric() { server.set_http_handler( "/metrics", [](coro_http_request &req, coro_http_response &resp) { resp.need_date_head(false); - resp.set_status_and_content(status_type::ok, metric_t::serialize()); + resp.set_status_and_content(status_type::ok, ""); + }); + server.sync_start(); +} + +void metrics_example() { + auto get_req_counter = std::make_shared( + "get_req_count", "get req count", + std::map{{"url", "/get"}}); + auto get_req_qps = std::make_shared("get_req_qps", "get req qps"); + // default_metric_manger::register_metric_static(get_req_counter, + // get_req_qps); + int64_t last = 0; + std::thread thd([&] { + while (true) { + std::this_thread::sleep_for(1s); + auto value = get_req_counter->value({"/get"}); + get_req_qps->update(value - last); + last = value; + } + }); + thd.detach(); + + coro_http_server server(1, 9001); + server.set_http_handler( + "/get", [&](coro_http_request &req, coro_http_response &resp) { + // get_req_counter->inc({"/get"}); + resp.set_status_and_content(status_type::ok, "ok"); }); + server.set_http_handler( + "/", [&](coro_http_request &req, coro_http_response &resp) { + resp.set_status_and_content(status_type::ok, "hello world"); + }); + server.use_metrics(); server.sync_start(); } +async_simple::coro::Lazy use_channel() { + coro_http_server server(1, 9001); + server.set_http_handler( + "/", [&](coro_http_request &req, coro_http_response &resp) { + resp.set_status_and_content(status_type::ok, "hello world"); + }); + server.use_metrics(); + server.async_start(); + std::this_thread::sleep_for(100ms); + + auto channel = std::make_shared>( + coro_io::channel::create( + {"127.0.0.1:9001"}, {.lba = coro_io::load_blance_algorithm::random})); + std::string url = "http://127.0.0.1:9001/"; + co_await channel->send_request( + [&url](coro_http_client &client, + std::string_view host) -> async_simple::coro::Lazy { + auto data = co_await client.async_get(url); + std::cout << data.net_err.message() << "\n"; + std::cout << data.resp_body << "\n"; + }); +} + +async_simple::coro::Lazy use_pool() { + coro_http_server server(1, 9001); + server.set_http_handler( + "/", [&](coro_http_request &req, coro_http_response &resp) { + resp.set_status_and_content(status_type::ok, "hello world"); + }); + server.use_metrics(); + server.async_start(); + + auto map = default_metric_manger::metric_map_static(); + for (auto &[k, m] : map) { + std::cout << k << ", "; + std::cout << m->help() << "\n"; + } + + std::string url = "http://127.0.0.1:9001/"; + + auto pool = coro_io::client_pool::create( + url, {std::thread::hardware_concurrency() * 2}); + + std::atomic count = 0; + for (size_t i = 0; i < 10000; i++) { + pool->send_request( + [&](coro_http_client &client) -> async_simple::coro::Lazy { + auto data = co_await client.async_get(url); + std::cout << data.resp_body << "\n"; + }) + .start([&](auto &&) { + count++; + }); + } + + while (count != 10000) { + std::this_thread::sleep_for(5ms); + } + + int size = pool->free_client_count(); + printf("current client count: %d, \n", size); + co_return; +} + int main() { // use_metric(); + // metrics_example(); + async_simple::coro::syncAwait(use_channel()); + async_simple::coro::syncAwait(use_pool()); async_simple::coro::syncAwait(basic_usage()); async_simple::coro::syncAwait(use_aspects()); async_simple::coro::syncAwait(static_file_server()); diff --git a/include/cinatra/coro_http_connection.hpp b/include/cinatra/coro_http_connection.hpp index 356fc456..d28c449a 100644 --- a/include/cinatra/coro_http_connection.hpp +++ b/include/cinatra/coro_http_connection.hpp @@ -21,9 +21,14 @@ #include "sha1.hpp" #include "string_resize.hpp" #include "websocket.hpp" +#include "ylt/metric/counter.hpp" +#include "ylt/metric/gauge.hpp" +#include "ylt/metric/histogram.hpp" +#include "ylt/metric/metric.hpp" #ifdef CINATRA_ENABLE_GZIP #include "gzip.hpp" #endif +#include "metric_conf.hpp" #include "ylt/coro_io/coro_file.hpp" #include "ylt/coro_io/coro_io.hpp" @@ -47,9 +52,14 @@ class coro_http_connection request_(parser_, this), response_(this) { buffers_.reserve(3); + + cinatra_metric_conf::server_total_fd_inc(); } - ~coro_http_connection() { close(); } + ~coro_http_connection() { + cinatra_metric_conf::server_total_fd_dec(); + close(); + } #ifdef CINATRA_ENABLE_SSL bool init_ssl(const std::string &cert_file, const std::string &key_file, @@ -94,6 +104,8 @@ class coro_http_connection #ifdef CINATRA_ENABLE_SSL bool has_shake = false; #endif + std::chrono::system_clock::time_point start{}; + std::chrono::system_clock::time_point mid{}; while (true) { #ifdef CINATRA_ENABLE_SSL if (use_ssl_ && !has_shake) { @@ -113,13 +125,21 @@ class coro_http_connection if (ec != asio::error::eof) { CINATRA_LOG_WARNING << "read http header error: " << ec.message(); } + + cinatra_metric_conf::server_failed_req_inc(); close(); break; } + if (cinatra_metric_conf::enable_metric) { + start = std::chrono::system_clock::now(); + cinatra_metric_conf::server_total_req_inc(); + } + const char *data_ptr = asio::buffer_cast(head_buf_.data()); int head_len = parser_.parse_request(data_ptr, size, 0); if (head_len <= 0) { + cinatra_metric_conf::server_failed_req_inc(); CINATRA_LOG_ERROR << "parse http header error"; close(); break; @@ -133,6 +153,9 @@ class coro_http_connection if (type != content_type::chunked && type != content_type::multipart) { size_t body_len = parser_.body_len(); if (body_len == 0) { + if (cinatra_metric_conf::enable_metric) { + cinatra_metric_conf::server_total_recv_bytes_inc(head_len); + } if (parser_.method() == "GET"sv) { if (request_.is_upgrade()) { #ifdef CINATRA_ENABLE_GZIP @@ -152,6 +175,16 @@ class coro_http_connection } response_.set_delay(true); } + else { + if (cinatra_metric_conf::enable_metric) { + mid = std::chrono::system_clock::now(); + double count = + std::chrono::duration_cast(mid - + start) + .count(); + cinatra_metric_conf::server_read_latency_observe(count); + } + } } } else if (body_len <= head_buf_.size()) { @@ -161,6 +194,7 @@ class coro_http_connection memcpy(body_.data(), data_ptr, body_len); head_buf_.consume(head_buf_.size()); } + cinatra_metric_conf::server_total_recv_bytes_inc(head_len + body_len); } else { size_t part_size = head_buf_.size(); @@ -175,9 +209,22 @@ class coro_http_connection size_to_read); if (ec) { CINATRA_LOG_ERROR << "async_read error: " << ec.message(); + cinatra_metric_conf::server_failed_req_inc(); close(); break; } + else { + if (cinatra_metric_conf::enable_metric) { + cinatra_metric_conf::server_total_recv_bytes_inc(head_len + + body_len); + mid = std::chrono::system_clock::now(); + double count = + std::chrono::duration_cast(mid - + start) + .count(); + cinatra_metric_conf::server_read_latency_observe(count); + } + } } } @@ -362,6 +409,14 @@ class coro_http_connection } } + if (cinatra_metric_conf::enable_metric) { + mid = std::chrono::system_clock::now(); + double count = + std::chrono::duration_cast(mid - start) + .count(); + cinatra_metric_conf::server_req_latency_observe(count); + } + response_.clear(); request_.clear(); buffers_.clear(); @@ -375,18 +430,32 @@ class coro_http_connection } async_simple::coro::Lazy reply(bool need_to_bufffer = true) { + if (response_.status() >= status_type::bad_request) { + if (cinatra_metric_conf::enable_metric) + cinatra_metric_conf::server_failed_req_inc(); + } std::error_code ec; size_t size; if (multi_buf_) { if (need_to_bufffer) { response_.to_buffers(buffers_, chunk_size_str_); } + int64_t send_size = 0; + for (auto &buf : buffers_) { + send_size += buf.size(); + } + if (cinatra_metric_conf::enable_metric) { + cinatra_metric_conf::server_total_send_bytes_inc(send_size); + } std::tie(ec, size) = co_await async_write(buffers_); } else { if (need_to_bufffer) { response_.build_resp_str(resp_str_); } + if (cinatra_metric_conf::enable_metric) { + cinatra_metric_conf::server_total_send_bytes_inc(resp_str_.size()); + } std::tie(ec, size) = co_await async_write(asio::buffer(resp_str_)); } @@ -794,7 +863,7 @@ class coro_http_connection return last_rwtime_; } - auto &get_executor() { return *executor_; } + auto get_executor() { return executor_; } void close(bool need_cb = true) { if (has_closed_) { @@ -884,7 +953,7 @@ class coro_http_connection private: friend class multipart_reader_t; - async_simple::Executor *executor_; + coro_io::ExecutorWrapper<> *executor_; asio::ip::tcp::socket socket_; coro_http_router &router_; asio::streambuf head_buf_; diff --git a/include/cinatra/coro_http_server.hpp b/include/cinatra/coro_http_server.hpp index 7c2aea83..031b1d03 100644 --- a/include/cinatra/coro_http_server.hpp +++ b/include/cinatra/coro_http_server.hpp @@ -11,6 +11,7 @@ #include "ylt/coro_io/coro_file.hpp" #include "ylt/coro_io/coro_io.hpp" #include "ylt/coro_io/io_context_pool.hpp" +#include "ylt/metric/metric.hpp" namespace cinatra { enum class file_resp_format_type { @@ -181,6 +182,16 @@ class coro_http_server { } } + void use_metrics(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_manger::serialize_static()); + res.set_status_and_content(status_type::ok, std::move(str)); + }); + } + template void set_http_proxy_handler(std::string url_path, std::vector hosts, @@ -684,7 +695,7 @@ class coro_http_server { connections_.emplace(conn_id, conn); } - start_one(conn).via(&conn->get_executor()).detach(); + start_one(conn).via(conn->get_executor()).detach(); } } @@ -868,6 +879,7 @@ class coro_http_server { easylog::logger<>::instance(); // init easylog singleton to make sure // server destruct before easylog. #endif + if (size_t pos = address.find(':'); pos != std::string::npos) { auto port_sv = std::string_view(address).substr(pos + 1); @@ -886,6 +898,29 @@ class coro_http_server { address_ = std::move(address); } + private: + void init_metrics() { + using namespace ylt; + + cinatra_metric_conf::enable_metric = true; + default_metric_manger::create_metric_static( + cinatra_metric_conf::server_total_req, ""); + default_metric_manger::create_metric_static( + cinatra_metric_conf::server_failed_req, ""); + default_metric_manger::create_metric_static( + cinatra_metric_conf::server_total_recv_bytes, ""); + default_metric_manger::create_metric_static( + cinatra_metric_conf::server_total_send_bytes, ""); + default_metric_manger::create_metric_static( + cinatra_metric_conf::server_total_fd, ""); + default_metric_manger::create_metric_static( + cinatra_metric_conf::server_req_latency, "", + std::vector{30, 40, 50, 60, 70, 80, 90, 100, 150}); + default_metric_manger::create_metric_static( + cinatra_metric_conf::server_read_latency, "", + std::vector{3, 5, 7, 9, 13, 18, 23, 35, 50}); + } + private: std::unique_ptr pool_; asio::io_context *out_ctx_ = nullptr; diff --git a/include/cinatra/metric_conf.hpp b/include/cinatra/metric_conf.hpp new file mode 100644 index 00000000..03aaf14b --- /dev/null +++ b/include/cinatra/metric_conf.hpp @@ -0,0 +1,126 @@ +#pragma once +#include +#include + +#include "ylt/metric/counter.hpp" +#include "ylt/metric/gauge.hpp" +#include "ylt/metric/histogram.hpp" +#include "ylt/metric/metric.hpp" +#include "ylt/metric/summary.hpp" + +namespace cinatra { +struct cinatra_metric_conf { + inline static std::string server_total_req = "server_total_req"; + inline static std::string server_failed_req = "server_failed_req"; + inline static std::string server_total_fd = "server_total_fd"; + inline static std::string server_total_recv_bytes = "server_total_recv_bytes"; + inline static std::string server_total_send_bytes = "server_total_send_bytes"; + inline static std::string server_req_latency = "server_req_latency"; + inline static std::string server_read_latency = "server_read_latency"; + inline static std::string server_total_thread_num = "server_total_thread_num"; + inline static bool enable_metric = false; + + inline static void server_total_req_inc() { + if (!enable_metric) { + return; + } + + static auto m = + ylt::default_metric_manger::get_metric_static( + server_total_req); + if (m == nullptr) { + return; + } + m->inc(); + } + + inline static void server_failed_req_inc() { + if (!enable_metric) { + return; + } + static auto m = + ylt::default_metric_manger::get_metric_static( + server_failed_req); + if (m == nullptr) { + return; + } + m->inc(); + } + + inline static void server_total_fd_inc() { + if (!enable_metric) { + return; + } + static auto m = ylt::default_metric_manger::get_metric_static( + server_total_fd); + if (m == nullptr) { + return; + } + m->inc(); + } + + inline static void server_total_fd_dec() { + if (!enable_metric) { + return; + } + static auto m = ylt::default_metric_manger::get_metric_static( + server_total_fd); + if (m == nullptr) { + return; + } + m->dec(); + } + + inline static void server_total_recv_bytes_inc(double val) { + if (!enable_metric) { + return; + } + static auto m = + ylt::default_metric_manger::get_metric_static( + server_total_recv_bytes); + if (m == nullptr) { + return; + } + m->inc(val); + } + + inline static void server_total_send_bytes_inc(double val) { + if (!enable_metric) { + return; + } + static auto m = + ylt::default_metric_manger::get_metric_static( + server_total_send_bytes); + if (m == nullptr) { + return; + } + m->inc(val); + } + + inline static void server_req_latency_observe(double val) { + if (!enable_metric) { + return; + } + static auto m = + ylt::default_metric_manger::get_metric_static( + server_req_latency); + if (m == nullptr) { + return; + } + m->observe(val); + } + + inline static void server_read_latency_observe(double val) { + if (!enable_metric) { + return; + } + static auto m = + ylt::default_metric_manger::get_metric_static( + server_read_latency); + if (m == nullptr) { + return; + } + m->observe(val); + } +}; +} // namespace cinatra diff --git a/include/cinatra/ylt/coro_io/io_context_pool.hpp b/include/cinatra/ylt/coro_io/io_context_pool.hpp index 18c8c61d..08018980 100644 --- a/include/cinatra/ylt/coro_io/io_context_pool.hpp +++ b/include/cinatra/ylt/coro_io/io_context_pool.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -117,6 +118,8 @@ class io_context_pool { pool_size = 1; // set default value as 1 } + total_thread_num_ += pool_size; + for (std::size_t i = 0; i < pool_size; ++i) { io_context_ptr io_context(new asio::io_context(1)); work_ptr work(new asio::io_context::work(*io_context)); @@ -204,6 +207,8 @@ class io_context_pool { template friend io_context_pool &g_io_context_pool(); + static size_t get_total_thread_num() { return total_thread_num_; } + private: using io_context_ptr = std::shared_ptr; using work_ptr = std::shared_ptr; @@ -216,8 +221,13 @@ class io_context_pool { std::atomic has_run_or_stop_ = false; std::once_flag flag_; bool cpu_affinity_ = false; + inline static std::atomic total_thread_num_ = 0; }; +inline size_t get_total_thread_num() { + return io_context_pool::get_total_thread_num(); +} + class multithread_context_pool { public: multithread_context_pool(size_t thd_num = std::thread::hardware_concurrency()) diff --git a/include/cinatra/ylt/metric/counter.hpp b/include/cinatra/ylt/metric/counter.hpp index 775a1bcb..a252106d 100644 --- a/include/cinatra/ylt/metric/counter.hpp +++ b/include/cinatra/ylt/metric/counter.hpp @@ -1,91 +1,198 @@ #pragma once +#include +#include + #include "metric.hpp" -namespace cinatra { +namespace ylt { +enum class op_type_t { INC, DEC, SET }; +struct counter_sample { + op_type_t op_type; + std::vector labels_value; + double value; +}; + class counter_t : public metric_t { public: - counter_t() = default; + // default, no labels, only contains an atomic value. + counter_t(std::string name, std::string help) + : metric_t(MetricType::Counter, std::move(name), std::move(help)) { + use_atomic_ = true; + } + + // static labels value, contains a map with atomic value. + counter_t(std::string name, std::string help, + std::map labels) + : metric_t(MetricType::Counter, std::move(name), std::move(help)) { + for (auto &[k, v] : labels) { + labels_name_.push_back(k); + labels_value_.push_back(v); + } + + atomic_value_map_.emplace(labels_value_, 0); + use_atomic_ = true; + } + + // dynamic labels value counter_t(std::string name, std::string help, - std::vector labels_name = {}) + std::vector labels_name) : metric_t(MetricType::Counter, std::move(name), std::move(help), std::move(labels_name)) {} - counter_t(const char *name, const char *help, - std::vector labels_name = {}) - : counter_t( - std::string(name), std::string(help), - std::vector(labels_name.begin(), labels_name.end())) {} + virtual ~counter_t() {} - void inc() { - std::lock_guard guard(mtx_); - set_value(value_map_[{}], 1, op_type_t::INC); + double value() { return default_lable_value_; } + + double value(const std::vector &labels_value) { + if (use_atomic_) { + double val = atomic_value_map_[labels_value]; + return val; + } + else { + std::lock_guard lock(mtx_); + return value_map_[labels_value]; + } + } + + std::map, double, + std::less>> + value_map() { + std::map, double, + std::less>> + map; + if (use_atomic_) { + map = {atomic_value_map_.begin(), atomic_value_map_.end()}; + } + else { + std::lock_guard lock(mtx_); + map = value_map_; + } + return map; + } + + void serialize(std::string &str) override { + if (labels_name_.empty()) { + if (default_lable_value_ == 0) { + return; + } + serialize_head(str); + serialize_default_label(str); + return; + } + + serialize_head(str); + std::string s; + if (use_atomic_) { + serialize_map(atomic_value_map_, s); + } + else { + serialize_map(value_map_, s); + } + + if (s.empty()) { + str.clear(); + } + else { + str.append(s); + } + } + + void inc(double val = 1) { + if (val < 0) { + throw std::invalid_argument("the value is less than zero"); + } + +#ifdef __APPLE__ + mac_os_atomic_fetch_add(&default_lable_value_, val); +#else + default_lable_value_ += val; +#endif } void inc(const std::vector &labels_value, double value = 1) { if (value == 0) { return; } + validate(labels_value, value); - std::lock_guard guard(mtx_); - set_value(value_map_[labels_value], value, op_type_t::INC); + if (use_atomic_) { + if (labels_value != labels_value_) { + throw std::invalid_argument( + "the given labels_value is not match with origin labels_value"); + } + set_value(atomic_value_map_[labels_value], value, op_type_t::INC); + } + else { + std::lock_guard lock(mtx_); + set_value(value_map_[labels_value], value, op_type_t::INC); + } } + void update(double value) { default_lable_value_ = value; } + void update(const std::vector &labels_value, double value) { - if (labels_name_.size() != labels_value.size()) { + if (labels_value.empty() || labels_name_.size() != labels_value.size()) { throw std::invalid_argument( "the number of labels_value name and labels_value is not match"); } - std::lock_guard guard(mtx_); - set_value(value_map_[labels_value], value, op_type_t::SET); - } - - void reset() { - std::lock_guard guard(mtx_); - for (auto &pair : value_map_) { - pair.second = {}; + if (use_atomic_) { + if (labels_value != labels_value_) { + throw std::invalid_argument( + "the given labels_value is not match with origin labels_value"); + } + set_value(atomic_value_map_[labels_value], value, op_type_t::SET); + } + else { + std::lock_guard lock(mtx_); + set_value(value_map_[labels_value], value, op_type_t::SET); } } - std::map, sample_t, + std::map, std::atomic, std::less>> - values() override { - std::lock_guard guard(mtx_); - return value_map_; + &atomic_value_map() { + return atomic_value_map_; } - void serialize(std::string &str) override { - auto value_map = values(); - if (value_map.empty()) { - return; + protected: + void serialize_default_label(std::string &str) { + str.append(name_); + if (labels_name_.empty()) { + str.append(" "); + } + + if (type_ == MetricType::Counter) { + str.append(std::to_string((int64_t)default_lable_value_)); + } + else { + str.append(std::to_string(default_lable_value_)); } - str.append("# HELP ").append(name_).append(" ").append(help_).append("\n"); - str.append("# TYPE ") - .append(name_) - .append(" ") - .append(metric_name()) - .append("\n"); - for (auto &[labels_value, sample] : value_map) { + + str.append("\n"); + } + + template + void serialize_map(T &value_map, std::string &str) { + for (auto &[labels_value, value] : value_map) { + if (value == 0) { + continue; + } str.append(name_); - if (labels_name_.empty()) { - str.append(" "); + str.append("{"); + build_string(str, labels_name_, labels_value); + str.append("} "); + + if (type_ == MetricType::Counter) { + str.append(std::to_string((int64_t)value)); } else { - str.append("{"); - build_string(str, labels_name_, labels_value); - str.append("} "); + str.append(std::to_string(value)); } - str.append(std::to_string((int64_t)sample.value)); - if (enable_timestamp_) { - str.append(" "); - str.append(std::to_string(sample.timestamp)); - } str.append("\n"); } } - protected: - enum class op_type_t { INC, DEC, SET }; void build_string(std::string &str, const std::vector &v1, const std::vector &v2) { for (size_t i = 0; i < v1.size(); i++) { @@ -98,32 +205,54 @@ class counter_t : public metric_t { if (value < 0) { throw std::invalid_argument("the value is less than zero"); } - if (labels_name_.size() != labels_value.size()) { + if (labels_value.empty() || labels_name_.size() != labels_value.size()) { throw std::invalid_argument( "the number of labels_value name and labels_value is not match"); } } - void set_value(sample_t &sample, double value, op_type_t type) { - sample.timestamp = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); + template + void set_value(T &label_val, double value, op_type_t type) { switch (type) { - case op_type_t::INC: - sample.value += value; - break; + case op_type_t::INC: { +#ifdef __APPLE__ + if constexpr (is_atomic) { + mac_os_atomic_fetch_add(&label_val, value); + } + else { + label_val += value; + } +#else + label_val += value; +#endif + } break; case op_type_t::DEC: - sample.value -= value; +#ifdef __APPLE__ + if constexpr (is_atomic) { + mac_os_atomic_fetch_sub(&label_val, value); + } + else { + label_val -= value; + } + +#else + label_val -= value; +#endif break; case op_type_t::SET: - sample.value = value; + label_val = value; break; } } + std::map, std::atomic, + std::less>> + atomic_value_map_; + std::atomic default_lable_value_ = 0; + std::mutex mtx_; - std::map, sample_t, + std::map, double, std::less>> value_map_; }; -} // namespace cinatra \ No newline at end of file +} // namespace ylt \ 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 a9c5afc0..cdaf1db9 100644 --- a/include/cinatra/ylt/metric/detail/ckms_quantiles.hpp +++ b/include/cinatra/ylt/metric/detail/ckms_quantiles.hpp @@ -1,10 +1,11 @@ #pragma once #include +#include #include // https://github.com/jupp0r/prometheus-cpp/blob/master/core/include/prometheus/detail/ckms_quantiles.h -namespace cinatra { +namespace ylt { class CKMSQuantiles { public: struct Quantile { @@ -171,4 +172,4 @@ class CKMSQuantiles { std::array buffer_; std::size_t buffer_count_; }; -} // namespace cinatra \ No newline at end of file +} // namespace ylt \ 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 68223dc8..fd7df105 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 cinatra { +namespace ylt { 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 cinatra \ No newline at end of file +} // namespace ylt \ No newline at end of file diff --git a/include/cinatra/ylt/metric/gauge.hpp b/include/cinatra/ylt/metric/gauge.hpp index 94c2ecad..19c4b65c 100644 --- a/include/cinatra/ylt/metric/gauge.hpp +++ b/include/cinatra/ylt/metric/gauge.hpp @@ -3,34 +3,50 @@ #include "counter.hpp" -namespace cinatra { +namespace ylt { class gauge_t : public counter_t { public: - gauge_t() = default; + gauge_t(std::string name, std::string help) + : counter_t(std::move(name), std::move(help)) { + set_metric_type(MetricType::Gauge); + } gauge_t(std::string name, std::string help, - std::vector labels_name = {}) + std::vector labels_name) : counter_t(std::move(name), std::move(help), std::move(labels_name)) { - set_metric_type(MetricType::Guage); + set_metric_type(MetricType::Gauge); } - gauge_t(const char* name, const char* help, - std::vector labels_name = {}) - : gauge_t( - std::string(name), std::string(help), - std::vector(labels_name.begin(), labels_name.end())) {} + gauge_t(std::string name, std::string help, + std::map labels) + : counter_t(std::move(name), std::move(help), std::move(labels)) { + set_metric_type(MetricType::Gauge); + } - void dec() { - std::lock_guard guard(mtx_); - set_value(value_map_[{}], 1, op_type_t::DEC); + void dec(double value = 1) { +#ifdef __APPLE__ + mac_os_atomic_fetch_sub(&default_lable_value_, value); +#else + default_lable_value_ -= value; +#endif } - void dec(const std::vector& labels_value, double value) { + void dec(const std::vector& labels_value, double value = 1) { if (value == 0) { return; } + validate(labels_value, value); - std::lock_guard guard(mtx_); - set_value(value_map_[labels_value], value, op_type_t::DEC); + if (use_atomic_) { + if (labels_value != labels_value_) { + throw std::invalid_argument( + "the given labels_value is not match with origin labels_value"); + } + set_value(atomic_value_map_[labels_value], value, op_type_t::DEC); + } + else { + std::lock_guard lock(mtx_); + set_value(value_map_[labels_value], value, op_type_t::DEC); + } } }; -} // namespace cinatra \ No newline at end of file +} // namespace ylt \ No newline at end of file diff --git a/include/cinatra/ylt/metric/histogram.hpp b/include/cinatra/ylt/metric/histogram.hpp index 5fcf2802..dfd2ca13 100644 --- a/include/cinatra/ylt/metric/histogram.hpp +++ b/include/cinatra/ylt/metric/histogram.hpp @@ -8,20 +8,21 @@ #include "counter.hpp" #include "metric.hpp" -namespace cinatra { +namespace ylt { class histogram_t : public metric_t { public: histogram_t(std::string name, std::string help, std::vector buckets) : bucket_boundaries_(buckets), metric_t(MetricType::Histogram, std::move(name), std::move(help)), - sum_(std::make_shared()) { + sum_(std::make_shared("", "")) { if (!is_strict_sorted(begin(bucket_boundaries_), end(bucket_boundaries_))) { throw std::invalid_argument("Bucket Boundaries must be strictly sorted"); } for (size_t i = 0; i < buckets.size() + 1; i++) { - bucket_counts_.push_back(std::make_shared()); + bucket_counts_.push_back(std::make_shared("", "")); } + use_atomic_ = true; } void observe(double value) { @@ -29,77 +30,36 @@ class histogram_t : public metric_t { std::distance(bucket_boundaries_.begin(), std::lower_bound(bucket_boundaries_.begin(), bucket_boundaries_.end(), value))); - - std::lock_guard guard(mtx_); - std::lock_guard lock(mutex_); - sum_->inc({}, value); + sum_->inc(value); bucket_counts_[bucket_index]->inc(); } - void observe(const std::vector& label, double value) { - const auto bucket_index = static_cast( - std::distance(bucket_boundaries_.begin(), - std::lower_bound(bucket_boundaries_.begin(), - bucket_boundaries_.end(), value))); - - std::lock_guard guard(mtx_); - std::lock_guard lock(mutex_); - sum_->inc(label, value); - bucket_counts_[bucket_index]->inc(label); - } - - void reset() { - std::lock_guard guard(mtx_); - for (auto& c : bucket_counts_) { - c->reset(); - } - - sum_->reset(); - } - - auto get_bucket_counts() { - std::lock_guard guard(mtx_); - return bucket_counts_; - } + auto get_bucket_counts() { return bucket_counts_; } void serialize(std::string& str) override { - if (sum_->values().empty()) { - return; - } - str.append("# HELP ").append(name_).append(" ").append(help_).append("\n"); - str.append("# TYPE ") - .append(name_) - .append(" ") - .append(metric_name()) - .append("\n"); + serialize_head(str); double count = 0; auto bucket_counts = get_bucket_counts(); for (size_t i = 0; i < bucket_counts.size(); i++) { auto counter = bucket_counts[i]; - auto values = counter->values(); - for (auto& [labels_value, sample] : values) { - str.append(name_).append("_bucket{"); - if (i == bucket_boundaries_.size()) { - str.append("le=\"").append("+Inf").append("\"} "); - } - else { - str.append("le=\"") - .append(std::to_string(bucket_boundaries_[i])) - .append("\"} "); - } - - count += sample.value; - str.append(std::to_string(count)); - if (enable_timestamp_) { - str.append(" ").append(std::to_string(sample.timestamp)); - } - str.append("\n"); + str.append(name_).append("_bucket{"); + if (i == bucket_boundaries_.size()) { + str.append("le=\"").append("+Inf").append("\"} "); } + else { + str.append("le=\"") + .append(std::to_string(bucket_boundaries_[i])) + .append("\"} "); + } + + count += counter->value(); + str.append(std::to_string(count)); + str.append("\n"); } str.append(name_) .append("_sum ") - .append(std::to_string((sum_->values()[{}].value))) + .append(std::to_string(sum_->value())) .append("\n"); str.append(name_) @@ -117,8 +77,7 @@ class histogram_t : public metric_t { } std::vector bucket_boundaries_; - std::mutex mutex_; - std::vector> bucket_counts_; + std::vector> bucket_counts_; // readonly std::shared_ptr sum_; }; -} // namespace cinatra \ No newline at end of file +} // namespace ylt \ No newline at end of file diff --git a/include/cinatra/ylt/metric/metric.hpp b/include/cinatra/ylt/metric/metric.hpp index ceac6e28..7bced5c0 100644 --- a/include/cinatra/ylt/metric/metric.hpp +++ b/include/cinatra/ylt/metric/metric.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -7,20 +8,18 @@ #include #include -namespace cinatra { +#include "async_simple/coro/Lazy.h" +#include "cinatra/cinatra_log_wrapper.hpp" + +namespace ylt { enum class MetricType { Counter, - Guage, + Gauge, Histogram, Summary, Nil, }; -struct sample_t { - double value; - int64_t timestamp; -}; - class metric_t { public: metric_t() = default; @@ -30,6 +29,8 @@ class metric_t { name_(std::move(name)), help_(std::move(help)), labels_name_(std::move(labels_name)) {} + virtual ~metric_t() {} + std::string_view name() { return name_; } std::string_view help() { return help_; } @@ -40,76 +41,224 @@ class metric_t { switch (type_) { case MetricType::Counter: return "counter"; - case MetricType::Guage: - return "guage"; + case MetricType::Gauge: + return "gauge"; case MetricType::Histogram: return "histogram"; case MetricType::Summary: return "summary"; case MetricType::Nil: + default: return "unknown"; } } const std::vector& labels_name() { return labels_name_; } - void enable_timestamp(bool r) { enable_timestamp_ = r; } - virtual std::map, sample_t, - std::less>> - values() { - return {}; + virtual void serialize(std::string& str) {} + + // only for summary + virtual async_simple::coro::Lazy serialize_async(std::string& out) { + co_return; } - virtual void serialize(std::string& out) {} + bool is_atomic() const { return use_atomic_; } - 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); + template + T* as() { + return dynamic_cast(this); + } + + protected: + void set_metric_type(MetricType type) { type_ = type; } + void serialize_head(std::string& str) { + str.append("# HELP ").append(name_).append(" ").append(help_).append("\n"); + str.append("# TYPE ") + .append(name_) + .append(" ") + .append(metric_name()) + .append("\n"); + } + +#ifdef __APPLE__ + double mac_os_atomic_fetch_add(std::atomic* obj, double arg) { + double v; + do { + v = obj->load(); + } while (!std::atomic_compare_exchange_weak(obj, &v, v + arg)); + return v; + } + + double mac_os_atomic_fetch_sub(std::atomic* obj, double arg) { + double v; + do { + v = obj->load(); + } while (!std::atomic_compare_exchange_weak(obj, &v, v - arg)); + return v; + } +#endif + + MetricType type_ = MetricType::Nil; + std::string name_; + std::string help_; + std::vector labels_name_; // read only + std::vector labels_value_; // read only + bool use_atomic_ = false; +}; + +template +struct metric_manager_t { + struct null_mutex_t { + void lock() {} + void unlock() {} + }; + + // create and register metric + template + static std::shared_ptr create_metric_static(const std::string& name, + const std::string& help, + Args&&... args) { + auto m = std::make_shared(name, help, std::forward(args)...); + bool r = register_metric_static(m); + if (!r) { + return nullptr; } + return m; } - static void remove_metric(std::string name) { - std::scoped_lock guard(mtx_); - metric_map_.erase(name); + template + static std::shared_ptr create_metric_dynamic(const std::string& name, + const std::string& help, + Args&&... args) { + auto m = std::make_shared(name, help, std::forward(args)...); + bool r = register_metric_static(m); + if (!r) { + return nullptr; + } + return m; } - static auto collect() { - std::vector> metrics; - { - std::scoped_lock guard(mtx_); - for (auto& pair : metric_map_) { - metrics.push_back(pair.second); - } + 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); + } + + template + static bool register_metric_dynamic(Metrics... metrics) { + bool r = true; + ((void)(r && (r = register_metric_impl(metrics), true)), ...); + return r; + } + + template + static bool register_metric_static(Metrics... metrics) { + bool r = true; + ((void)(r && (r = register_metric_impl(metrics), true)), ...); + return r; + } + + static auto metric_map_static() { return metric_map_impl(); } + static auto metric_map_dynamic() { return metric_map_impl(); } + + static size_t metric_count_static() { return metric_count_impl(); } + + static size_t metric_count_dynamic() { return metric_count_impl(); } + + static std::vector metric_keys_static() { + return metric_keys_impl(); + } + + static std::vector metric_keys_dynamic() { + return metric_keys_impl(); + } + + template + static T* get_metric_static(const std::string& name) { + auto m = get_metric_impl(name); + if (m == nullptr) { + return nullptr; } - return metrics; + return m->template as(); } - static std::string serialize() { - std::string str; - auto metrics = metric_t::collect(); - for (auto& m : metrics) { - m->serialize(str); + template + static T* get_metric_dynamic(const std::string& name) { + auto m = get_metric_impl(name); + if (m == nullptr) { + return nullptr; + } + return m->template as(); + } + + static async_simple::coro::Lazy serialize_static() { + return serialize_impl(); + } + + static async_simple::coro::Lazy serialize_dynamic() { + return serialize_impl(); + } + + private: + template + static void check_lock() { + if (need_lock_ != need_lock) { + std::string str = "need lock "; + std::string s = need_lock_ ? "true" : "false"; + std::string r = need_lock ? "true" : "false"; + str.append(s).append(" but set as ").append(r); + throw std::invalid_argument(str); + } + } + + template + static auto get_lock() { + check_lock(); + if constexpr (need_lock) { + return std::scoped_lock(mtx_); + } + else { + return std::scoped_lock(null_mtx_); } - return str; } - static auto metric_map() { - std::scoped_lock guard(mtx_); + template + static bool register_metric_impl(std::shared_ptr metric) { + // the first time regiter_metric will set metric_manager_t lock or not lock. + // visit metric_manager_t with different lock strategy will cause throw + // exception. + std::call_once(flag_, [] { + need_lock_ = need_lock; + }); + + std::string name(metric->name()); + auto lock = get_lock(); + bool r = metric_map_.emplace(name, std::move(metric)).second; + if (!r) { + CINATRA_LOG_ERROR << "duplicate registered metric name: " << name; + } + return r; + } + + template + static auto metric_map_impl() { + auto lock = get_lock(); return metric_map_; } - static size_t metric_count() { - std::scoped_lock guard(mtx_); + template + static size_t metric_count_impl() { + auto lock = get_lock(); return metric_map_.size(); } - static std::vector metric_keys() { + template + static std::vector metric_keys_impl() { std::vector keys; { - std::scoped_lock guard(mtx_); + auto lock = get_lock(); for (auto& pair : metric_map_) { keys.push_back(pair.first); } @@ -118,15 +267,50 @@ class metric_t { return keys; } - protected: - void set_metric_type(MetricType type) { type_ = type; } + template + static std::shared_ptr get_metric_impl(const std::string& name) { + auto lock = get_lock(); + auto it = metric_map_.find(name); + if (it == metric_map_.end()) { + return nullptr; + } + return it->second; + } + + template + static auto collect() { + std::vector> metrics; + { + auto lock = get_lock(); + for (auto& pair : metric_map_) { + metrics.push_back(pair.second); + } + } + return metrics; + } + + template + static async_simple::coro::Lazy serialize_impl() { + std::string str; + auto metrics = collect(); + for (auto& m : metrics) { + if (m->metric_type() == MetricType::Summary) { + co_await m->serialize_async(str); + } + else { + m->serialize(str); + } + } + co_return str; + } - MetricType type_ = MetricType::Nil; - 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_; + + static inline null_mutex_t null_mtx_; + static inline std::atomic_bool need_lock_ = true; + static inline std::once_flag flag_; }; -} // namespace cinatra \ No newline at end of file + +using default_metric_manger = metric_manager_t<0>; +} // namespace ylt \ No newline at end of file diff --git a/include/cinatra/ylt/metric/summary.hpp b/include/cinatra/ylt/metric/summary.hpp index 2e272193..415d6b32 100644 --- a/include/cinatra/ylt/metric/summary.hpp +++ b/include/cinatra/ylt/metric/summary.hpp @@ -3,8 +3,10 @@ #include "detail/time_window_quantiles.hpp" #include "metric.hpp" +#include "ylt/coro_io/coro_io.hpp" +#include "ylt/util/concurrentqueue.h" -namespace cinatra { +namespace ylt { class summary_t : public metric_t { public: using Quantiles = std::vector; @@ -12,63 +14,126 @@ class summary_t : public metric_t { 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) { - count_ += 1; - std::lock_guard lock(mutex_); - sum_ += value; - quantile_values_.insert(value); + 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(); + block_->quantile_values_ = + std::make_shared(quantiles_, max_age, age_buckets); + start_timer(block_).via(excutor_.get()).start([](auto &&) { + }); } - auto get_quantile_values() { - std::lock_guard lock(mutex_); - return quantile_values_; + ~summary_t() { + block_->stop_ = true; + work_ = nullptr; + thd_.join(); } - auto get_sum() { - std::lock_guard lock(mutex_); - return sum_; + struct block_t { + std::atomic stop_ = false; + moodycamel::ConcurrentQueue sample_queue_; + std::shared_ptr quantile_values_; + std::uint64_t count_; + double sum_; + }; + + void observe(double value) { block_->sample_queue_.enqueue(value); } + + async_simple::coro::Lazy> get_rates(double &sum, + uint64_t &count) { + std::vector vec; + if (quantiles_.empty()) { + 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_return vec; + } + + async_simple::coro::Lazy get_sum() { + auto ret = co_await coro_io::post([this] { + return block_->sum_; + }); + co_return ret.value(); + } + + async_simple::coro::Lazy get_count() { + auto ret = co_await coro_io::post([this] { + return block_->count_; + }); + co_return ret.value(); } - void serialize(std::string& str) override { + size_t size_approx() { return block_->sample_queue_.size_approx(); } + + async_simple::coro::Lazy serialize_async(std::string &str) override { if (quantiles_.empty()) { - return; + co_return; } - auto quantile_values = get_quantile_values(); + serialize_head(str); - str.append("# HELP ").append(name_).append(" ").append(help_).append("\n"); - str.append("# TYPE ") - .append(name_) - .append(" ") - .append(metric_name()) - .append("\n"); + double sum = 0; + uint64_t count = 0; + auto rates = co_await get_rates(sum, count); - for (const auto& quantile : quantiles_) { + for (size_t i = 0; i < quantiles_.size(); i++) { 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(std::to_string(quantiles_[i].quantile)).append("\"} "); + str.append(std::to_string(rates[i])).append("\n"); } - str.append(name_) - .append("_sum ") - .append(std::to_string(get_sum())) - .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(std::to_string((uint64_t)count)) .append("\n"); } private: - Quantiles quantiles_; - mutable std::mutex mutex_; - std::atomic count_{}; - double sum_{}; - TimeWindowQuantiles quantile_values_; + async_simple::coro::Lazy start_timer(std::shared_ptr block) { + double sample; + size_t count = 1000000; + while (!block->stop_) { + size_t index = 0; + while (block->sample_queue_.try_dequeue(sample)) { + block->quantile_values_->insert(sample); + block->count_ += 1; + block->sum_ += sample; + index++; + if (index == count) { + break; + } + } + + co_await async_simple::coro::Yield{}; + + if (block->sample_queue_.size_approx() == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + } + + co_return; + } + + Quantiles quantiles_; // readonly + std::shared_ptr block_; + std::unique_ptr> excutor_ = nullptr; + std::shared_ptr work_; + asio::io_context ctx_; + std::thread thd_; }; -} // namespace cinatra \ No newline at end of file +} // namespace ylt \ No newline at end of file diff --git a/lang/how_to_use_metrics.md b/lang/how_to_use_metrics.md new file mode 100644 index 00000000..d211b9f6 --- /dev/null +++ b/lang/how_to_use_metrics.md @@ -0,0 +1,373 @@ +# 概述 +metric 包括4种指标类型: +- couter:只会增加的指标; +- gauge:可以增加或减少的指标,它派生于counter; +- histogram:直方图,初始化的时候需要设置桶(bucket); +- summary:分位数指标,初始化的时候需要设置桶和误差; + +# label + +label:标签,可选,指标可以没有标签。标签是指一个键值对,标签的键需要在创建指标的时候设置,是静态不可变的。 + +标签的值可以在创建指标的时候设置,这样的label则被称为静态的label。 + +标签的值在运行期动态创建,则label被称为动态的label。 + +动态label的例子: + +```cpp +{"method", "url"} +``` +这个label只有键没有值,所以这个label是动态的label。后续动态生成label对应的值时,这样做: +```cpp +{"GET", "/"} +{"POST", "/test"} +``` +使用的时候填动态的方法名和url就行了: +```cpp +some_counter.inc({std::string(req.method()), req.url()}, 1); +``` +如果传入的标签值数量和创建时的label 键的数量不匹配时则会抛异常。 + +静态label的例子: +```cpp +{{"method", "GET"}, {"url", "/"}} +``` +这个label的键值都确定了,是静态的,后面使用的时候需要显式调用静态的标签值使用: +```cpp +some_counter.inc({"GET", "/"}, 1); +``` + +无标签:创建指标的时候不设置标签,内部只有一个计数。 + +# counter和gauge的使用 + +## 创建没有标签的指标 +```cpp + gauge_t g{"test_gauge", "help"}; + g.inc(); + g.inc(1); + + std::string str; + g.serialize(str); + CHECK(str.find("test_gauge 2") != std::string::npos); + + g.dec(1); + CHECK(g.value() == 1); + g.update(1); + + CHECK_THROWS_AS(g.dec({"test"}, 1), std::invalid_argument); + CHECK_THROWS_AS(g.inc({"test"}, 1), std::invalid_argument); + CHECK_THROWS_AS(g.update({"test"}, 1), std::invalid_argument); + + counter_t c{"test_counter", "help"}; + c.inc(); + c.inc(1); + std::string str1; + c.serialize(str1); + CHECK(str1.find("test_counter 2") != std::string::npos); +``` +## counter/gauge指标的api + +构造函数: +```cpp +// 无标签,调用inc时不带标签,如c.inc() +// name: 指标对象的名称,注册到指标管理器时会使用这个名称 +// help: 指标对象的帮助信息 +counter_t(std::string name, std::string help); + +// labels: 静态标签,构造时需要将标签键值都填完整,如:{{"method", "GET"}, {"url", "/"}} +// 调用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); +counter_t(std::string name, std::string help, + std::vector labels_name); +``` + +基本函数: +```cpp +// 获取无标签指标的计数, +double value(); + +// 根据标签获取指标的计数,参数为动态或者静态标签的值 +double value(const std::vector &labels_value); + +// 无标签指标增加计数,counter的计数只能增加不能减少,如果填入的时负数时会抛异常;如果需要减少计数的指标则应该使用gauge; +void inc(double val = 1); + +// 根据标签增加计数,如果创建的指标是静态标签值且和传入的标签值不匹配时会抛异常;如果标签的值的数量和构造指标时传入的标签数量不相等时会抛异常。 +void inc(const std::vector &labels_value, double value = 1); + +// 序列化,将指标序列化成prometheus 格式的字符串 +void serialize(std::string &str); + +// 返回带标签的指标内部的计数map,map的key是标签的值,值是对应计数,如:{{{"GET", "/"}, 100}, {{"POST", "/test"}, 20}} +std::map, double, + std::less>> + value_map(); +``` + +注意:如果使用动态标签的时候要注意这个动态的标签值是不是无限多的,如果是无限多的话,那么内部的map也会无限增长,应该避免这种情况,动态的标签也应该是有限的才对。 + +gauge 派生于counter,相比counter多了一个减少计数的api +```cpp +// 无标签指标减少计数 +void dec(double value = 1); + +// 根据标签减少计数,如果创建的指标是静态标签值且和传入的标签值不匹配时会抛异常;如果标签的值的数量和构造指标时传入的标签数量不相等时会抛异常。 +void dec(const std::vector& labels_value, double value = 1); +``` + +# 基类公共函数 +所有指标都派生于metric_t 基类,提供了一些公共方法,如获取指标的名称,指标的类型,标签的键名称等等。 + +```cpp +class metric_t { + public: + // 获取指标对象的名称 + std::string_view name(); + + // 获取指标对象的help 信息 + std::string_view help(); + + // 指标类型 + enum class MetricType { + Counter, + Gauge, + Histogram, + Summary, + Nil, + }; + + // 获取指标类型 + MetricType metric_type(); + + // 获取指标类型的名称,比如counter, gauge, histogram和summary + std::string_view metric_name(); + + // 获取标签的键,如{"method", "url"} + const std::vector& labels_name(); + + // 序列化,调用派生类实现序列化 + virtual void serialize(std::string& str); + + // 给summary专用的api,序列化,调用派生类实现序列化 + virtual async_simple::coro::Lazy serialize_async(std::string& out); + + // 将基类指针向下转换到派生类指针,如: + // std::shared_ptr c = std::make_shared("test", "test"); + // counter_t* t = c->as(); + // t->value(); + template + T* as(); +}; +``` + +# 指标管理器 +如果希望集中管理多个指标时,则需要将指标注册到指标管理器,后面则可以多态调用管理器中的多个指标将各自的计数输出出来。 + +**推荐在一开始就创建所有的指标并注册到管理器**,后面就可以无锁方式根据指标对象的名称来获取指标对象了。 + +```cpp +auto c = std::make_shared("qps_count", "qps help"); +auto g = std::make_shared("fd_count", "fd count help"); +default_metric_manger::register_metric_static(c); +default_metric_manger::register_metric_static(g); + +c->inc(); +g->inc(); + +auto m = default_metric_manger::get_metric_static("qps_count"); +CHECK(m->as()->value() == 1); + +auto m1 = default_metric_manger::get_metric_static("fd_count"); +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"); +default_metric_manger::register_metric_dynamic(c); +default_metric_manger::register_metric_dynamic(g); + +c->inc(); +g->inc(); + +auto m = default_metric_manger::get_metric_dynamic("qps_count"); +CHECK(m->as()->value() == 1); + +auto m1 = default_metric_manger::get_metric_dynamic("fd_count"); +CHECK(m1->as()->value() == 1); +``` +注意:一旦注册时使用了static或者dynamic,那么后面调用default_metric_manger时则应该使用相同后缀的接口,比如注册时使用了get_metric_static,那么后面调用根据名称获取指标对象的方法必须是get_metric_static,否则会抛异常。同样,如果注册使用register_metric_dynamic,则后面应该get_metric_dynamic,否则会抛异常。 + +指标管理器的api +```cpp +template +struct metric_manager_t { + // 创建并注册指标,返回注册的指标对象 + template + static std::shared_ptr create_metric_static(const std::string& name, + const std::string& help, + Args&&... args); + template + static std::shared_ptr create_metric_dynamic(const std::string& name, + const std::string& help, + Args&&... args) + // 注册metric + static bool register_metric_static(std::shared_ptr metric); + static bool register_metric_dynamic(std::shared_ptr metric); + + // 获取注册的所有指标对象 + static std::map> metric_map_static(); + static std::map> metric_map_dynamic(); + + // 获取注册的指标对象的总数 + static size_t metric_count_static(); + static size_t metric_count_dynamic(); + + // 获取注册的指标对象的名称 + static std::vector metric_keys_static(); + static std::vector metric_keys_dynamic(); + + // 根据名称获取指标对象,T为具体指标的类型,如 get_metric_static(); + // 如果找不到则返回nullptr + template + static T* get_metric_static(const std::string& name); + template + static T* get_metric_static(const std::string& name); + + static std::shared_ptr get_metric_static(const std::string& name); + static std::shared_ptr get_metric_dynamic(const std::string& name); + + // 序列化 + static async_simple::coro::Lazy serialize_static(); + static async_simple::coro::Lazy serialize_dynamic(); +}; +using default_metric_manger = metric_manager_t<0>; +``` +metric_manager_t默认为default_metric_manger,如果希望有多个metric manager,用户可以自定义新的metric manager,如: + +```cpp +constexpr size_t metric_id = 100; +using my_metric_manager = metric_manager_t; +``` + +# histogram + +## api +```cpp +// +// name: 对象名称,help:帮助信息 +// buckets:桶,如 {1, 3, 7, 11, 23},后面设置的值会自动落到某个桶中并增加该桶的计数; +// 内部还有一个+Inf 默认的桶,当输入的数据不在前面设置这些桶中,则会落到+Inf 默认桶中。 +// 实际上桶的总数为 buckets.size() + 1 +// 每个bucket 实际上对应了一个counter指标 +histogram_t(std::string name, std::string help, std::vector buckets); + +// 往histogram_t 中插入数据,内部会自动增加对应桶的计数 +void observe(double value); + +// 获取所有桶对应的counter指标对象 +std::vector> get_bucket_counts(); + +// 序列化 +void serialize(std::string& str); +``` +## 例子 +```cpp + histogram_t h("test", "help", {5.0, 10.0, 20.0, 50.0, 100.0}); + h.observe(23); + auto counts = h.get_bucket_counts(); + CHECK(counts[3]->value() == 1); + h.observe(42); + CHECK(counts[3]->value() == 2); + h.observe(60); + CHECK(counts[4]->value() == 1); + h.observe(120); + CHECK(counts[5]->value() == 1); + h.observe(1); + CHECK(counts[0]->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); +``` + +# summary +## api + +```cpp +// Quantiles: 百分位和误差, 如:{{0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}} +summary_t(std::string name, std::string help, Quantiles quantiles); + +// 往summary_t插入数据,会自动计算百分位的数量 +void observe(double value); + +// 获取百分位结果 +async_simple::coro::Lazy> get_rates(); + +// 获取总和 +async_simple::coro::Lazy get_sum(); + +// 获取插入数据的个数 +async_simple::coro::Lazy get_count(); + +// 序列化 +async_simple::coro::Lazy serialize_async(std::string &str); +``` + +## 例子 +```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::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::string str; + async_simple::coro::syncAwait(summary.serialize_async(str)); + std::cout << str; + CHECK(async_simple::coro::syncAwait(summary.get_count()) == 50); + CHECK(async_simple::coro::syncAwait(summary.get_sum()) > 0); + 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); +``` +summary 百分位的计算相比其它指标是最耗时的,应该避免在关键路径上使用它以免对性能造成影响。 + +# cinatra http server中启用内置的metric指标 + +http server 内置的指标: +```cpp +server_total_req: server总的请求数; +server_failed_req:server总的失败请求数; +server_total_fd:server使用的总的句柄数; +server_total_recv_bytes:server总共收到的字节数; +server_total_send_bytes:server总共发送的字节数; +server_req_latency:http 请求的延迟,从收到请求到发送响应的时间间隔 +server_read_latency:http 读请求的延迟,读到完整的http数据的时间间隔 +``` + +```cpp +coro_http_server server(1, 9001); +server.use_metrics("/metrics");//这个url默认就是/metrics,可以不填 +``` +在浏览器中输入`http://127.0.0.1:9001/metrics` 即可看到所有的指标。 + +查看当前server的client pool中有多少client,调用`pool.free_client_count()` + +查看当前server内部线程池中有多少线程,调用`coro_io::get_total_thread_num()` \ No newline at end of file diff --git a/lang/metrict_introduction.md b/lang/metrict_introduction.md index da2e7028..4045fb27 100644 --- a/lang/metrict_introduction.md +++ b/lang/metrict_introduction.md @@ -1,5 +1,5 @@ # metric 介绍 -metric 用于统计应用程序的各种指标,这些指标被用于系统见识和警报,常见的指标类型有四种:Counter、Guage、Histogram和Summary,这些指标遵循[Prometheus](https://hulining.gitbook.io/prometheus/introduction)的数据格式。 +metric 用于统计应用程序的各种指标,这些指标被用于系统见识和警报,常见的指标类型有四种:Counter、Gauge、Histogram和Summary,这些指标遵循[Prometheus](https://hulining.gitbook.io/prometheus/introduction)的数据格式。 ## Counter 计数器类型 Counter是一个累计类型的数据指标,它代表单调递增的计数器,其值只能在重新启动时增加或重置为 0。例如,您可以使用计数器来表示已响应的请求数,已完成或出错的任务数。 diff --git a/press_tool/CMakeLists.txt b/press_tool/CMakeLists.txt index a96e319e..a734e5b1 100644 --- a/press_tool/CMakeLists.txt +++ b/press_tool/CMakeLists.txt @@ -7,6 +7,9 @@ set(cinatra_press_tool main.cpp ) +include_directories(../include) +include_directories(../include/cinatra) + add_definitions(-DBENCHMARK_TEST) add_executable(${project_name} ${cinatra_press_tool}) target_compile_definitions(${project_name} PRIVATE ASYNC_SIMPLE_HAS_NOT_AIO) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 65ac13b9..1d0bf56c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,6 +10,7 @@ add_executable(${project_name} target_compile_definitions(${project_name} PRIVATE ASYNC_SIMPLE_HAS_NOT_AIO INJECT_FOR_HTTP_CLIENT_TEST) target_include_directories(${project_name} PRIVATE ${cinatra_SOURCE_DIR}/include + ${cinatra_SOURCE_DIR}/include/cinatra ) option(SKIP_TIME_TEST "skip time tests" OFF) @@ -45,15 +46,12 @@ endif() ## manual import include_directories(${cinatra_SOURCE_DIR}/include) +include_directories(${cinatra_SOURCE_DIR}/include/cinatra) 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) @@ -69,13 +67,17 @@ add_executable(test_time_util ) add_test(NAME test_time_util COMMAND test_time_util) -option(CINATRA_ENABLE_SSL "Enable ssl support" OFF) -if (CINATRA_ENABLE_SSL) +add_executable(test_metric + test_metric.cpp + ) + +if (ENABLE_SSL) message(STATUS "Use SSL") find_package(OpenSSL REQUIRED) add_definitions(-DCINATRA_ENABLE_SSL) target_link_libraries(test_cinatra OpenSSL::SSL OpenSSL::Crypto) - target_link_libraries(test_corofile PRIVATE OpenSSL::SSL OpenSSL::Crypto) + target_link_libraries(test_corofile OpenSSL::SSL OpenSSL::Crypto) + target_link_libraries(test_metric OpenSSL::SSL OpenSSL::Crypto) endif () add_executable(test_http_parse diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index ae37ec7a..34e35bf8 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -1,3 +1,4 @@ +#include #include #include "cinatra/ylt/metric/gauge.hpp" @@ -9,47 +10,111 @@ #include "cinatra/ylt/metric/histogram.hpp" #include "cinatra/ylt/metric/summary.hpp" #include "doctest/doctest.h" -using namespace cinatra; +using namespace ylt; -TEST_CASE("test counter") { +TEST_CASE("test no lable") { + { + gauge_t g{"test_gauge", "help"}; + g.inc(); + g.inc(); + + std::string str; + g.serialize(str); + CHECK(str.find("test_gauge 2") != std::string::npos); + + g.dec(); + CHECK(g.value() == 1); + CHECK_THROWS_AS(g.dec({}, 1), std::invalid_argument); + CHECK_THROWS_AS(g.inc({}, 1), std::invalid_argument); + CHECK_THROWS_AS(g.update({}, 1), std::invalid_argument); + + counter_t c{"test_counter", "help"}; + c.inc(); + c.inc(); + std::string str1; + c.serialize(str1); + CHECK(str1.find("test_counter 2") != std::string::npos); + } { 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); + CHECK(c.value() == 1); c.inc(); - CHECK(c.values().begin()->second.value == 2); - c.inc({}, 0); + CHECK(c.value() == 2); + c.inc(0); - CHECK(c.values().begin()->second.value == 2); + CHECK(c.value() == 2); - CHECK_THROWS_AS(c.inc({}, -2), std::invalid_argument); + CHECK_THROWS_AS(c.inc(-2), std::invalid_argument); + CHECK_THROWS_AS(c.inc({}, 1), std::invalid_argument); + CHECK_THROWS_AS(c.update({}, 1), std::invalid_argument); - c.update({}, 10); - CHECK(c.values().begin()->second.value == 10); + c.update(10); + CHECK(c.value() == 10); - c.update({}, 0); - CHECK(c.values().begin()->second.value == 0); + c.update(0); + CHECK(c.value() == 0); } +} +TEST_CASE("test with atomic") { + counter_t c( + "get_count", "get counter", + std::map{{"method", "GET"}, {"url", "/"}}); + std::vector labels_value{"GET", "/"}; + c.inc(labels_value); + c.inc(labels_value, 2); + CHECK(c.value(labels_value) == 3); + CHECK_THROWS_AS(c.inc({"GET", "/test"}), std::invalid_argument); + CHECK_THROWS_AS(c.inc({"POST", "/"}), std::invalid_argument); + c.update(labels_value, 10); + CHECK(c.value(labels_value) == 10); + + gauge_t g( + "get_qps", "get qps", + std::map{{"method", "GET"}, {"url", "/"}}); + g.inc(labels_value); + g.inc(labels_value, 2); + CHECK(g.value(labels_value) == 3); + CHECK_THROWS_AS(g.inc({"GET", "/test"}), std::invalid_argument); + CHECK_THROWS_AS(g.inc({"POST", "/"}), std::invalid_argument); + g.dec(labels_value); + g.dec(labels_value, 1); + CHECK(g.value(labels_value) == 1); + + std::string str; + c.serialize(str); + std::cout << str; + std::string str1; + g.serialize(str1); + std::cout << str1; + CHECK(str.find("} 10") != std::string::npos); + CHECK(str1.find("} 1") != std::string::npos); +} + +TEST_CASE("test counter with dynamic labels value") { { - auto c = std::make_shared("get_count", "get counter", - std::vector{"method", "code"}); + 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"}); + auto g = std::make_shared( + "get_count", "get counter", std::vector{"method", "code"}); CHECK(g->name() == "get_count"); - CHECK(g->metric_name() == "guage"); + CHECK(g->metric_name() == "gauge"); } { - counter_t c("get_count", "get counter", {"method", "code"}); + counter_t c("get_count", "get counter", + std::vector{"method", "code"}); CHECK(c.labels_name() == std::vector{"method", "code"}); c.inc({"GET", "200"}, 1); - CHECK(c.values()[{"GET", "200"}].value == 1); + auto values = c.value_map(); + CHECK(values[{"GET", "200"}] == 1); c.inc({"GET", "200"}, 2); - CHECK(c.values()[{"GET", "200"}].value == 3); + values = c.value_map(); + CHECK(values[{"GET", "200"}] == 3); std::string str; c.serialize(str); @@ -61,28 +126,27 @@ TEST_CASE("test counter") { 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); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + values = c.value_map(); + CHECK(values[{"GET", "200"}] == 20); } } -TEST_CASE("test guage") { +TEST_CASE("test gauge") { { gauge_t g("get_count", "get counter"); - CHECK(g.metric_type() == MetricType::Guage); + CHECK(g.metric_type() == MetricType::Gauge); CHECK(g.labels_name().empty()); g.inc(); - CHECK(g.values().begin()->second.value == 1); + CHECK(g.value() == 1); g.inc(); - CHECK(g.values().begin()->second.value == 2); - g.inc({}, 0); + CHECK(g.value() == 2); + g.inc(0); g.dec(); - CHECK(g.values().begin()->second.value == 1); + CHECK(g.value() == 1); g.dec(); - CHECK(g.values().begin()->second.value == 0); + CHECK(g.value() == 0); } { @@ -90,23 +154,27 @@ TEST_CASE("test guage") { 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); + auto values = g.value_map(); + CHECK(values[{"GET", "200", "/"}] == 1); g.inc({"GET", "200", "/"}, 2); - CHECK(g.values()[{"GET", "200", "/"}].value == 3); + values = g.value_map(); + CHECK(values[{"GET", "200", "/"}] == 3); std::string str; g.serialize(str); std::cout << str; - CHECK(str.find("# TYPE get_count guage") != std::string::npos); + CHECK(str.find("# TYPE get_count gauge") != 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); - CHECK(g.values()[{"GET", "200", "/"}].value == 2); + values = g.value_map(); + CHECK(values[{"GET", "200", "/"}] == 2); g.dec({"GET", "200", "/"}, 2); - CHECK(g.values()[{"GET", "200", "/"}].value == 0); + values = g.value_map(); + CHECK(values[{"GET", "200", "/"}] == 0); } } @@ -114,15 +182,15 @@ TEST_CASE("test histogram") { histogram_t h("test", "help", {5.0, 10.0, 20.0, 50.0, 100.0}); h.observe(23); auto counts = h.get_bucket_counts(); - CHECK(counts[3]->values()[{}].value == 1); + CHECK(counts[3]->value() == 1); h.observe(42); - CHECK(counts[3]->values()[{}].value == 2); + CHECK(counts[3]->value() == 2); h.observe(60); - CHECK(counts[4]->values()[{}].value == 1); + CHECK(counts[4]->value() == 1); h.observe(120); - CHECK(counts[5]->values()[{}].value == 1); + CHECK(counts[5]->value() == 1); h.observe(1); - CHECK(counts[0]->values()[{}].value == 1); + CHECK(counts[0]->value() == 1); std::string str; h.serialize(str); std::cout << str; @@ -143,9 +211,12 @@ TEST_CASE("test summary") { summary.observe(distr(gen)); } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::string str; - summary.serialize(str); + async_simple::coro::syncAwait(summary.serialize_async(str)); std::cout << str; + CHECK(async_simple::coro::syncAwait(summary.get_count()) == 50); + CHECK(async_simple::coro::syncAwait(summary.get_sum()) > 0); 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); @@ -155,32 +226,59 @@ TEST_CASE("test summary") { TEST_CASE("test register metric") { auto c = std::make_shared(std::string("get_count"), std::string("get counter")); - metric_t::regiter_metric(c); - CHECK_THROWS_AS(metric_t::regiter_metric(c), std::invalid_argument); + default_metric_manger::register_metric_static(c); + CHECK_FALSE(default_metric_manger::register_metric_static(c)); auto g = std::make_shared(std::string("get_guage_count"), std::string("get counter")); - metric_t::regiter_metric(g); + default_metric_manger::register_metric_static(g); - CHECK(metric_t::metric_count() == 2); - CHECK(metric_t::metric_keys().size() == 2); + auto map1 = default_metric_manger::metric_map_static(); + for (auto& [k, v] : map1) { + bool r = k == "get_count" || k == "get_guage_count"; + break; + } + + CHECK(default_metric_manger::metric_count_static() >= 2); + CHECK(default_metric_manger::metric_keys_static().size() >= 2); c->inc(); g->inc(); - auto map = metric_t::metric_map(); - CHECK(map["get_count"]->values()[{}].value == 1); - CHECK(map["get_guage_count"]->values()[{}].value == 1); + auto map = default_metric_manger::metric_map_static(); + CHECK(map["get_count"]->as()->value() == 1); + CHECK(map["get_guage_count"]->as()->value() == 1); - auto s = metric_t::serialize(); + auto s = + async_simple::coro::syncAwait(default_metric_manger::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); - metric_t::remove_metric("get_count"); - CHECK(metric_t::metric_count() == 1); + auto m = default_metric_manger::get_metric_static("get_count"); + CHECK(m->as()->value() == 1); + + auto m1 = + default_metric_manger::get_metric_static("get_guage_count"); + CHECK(m1->as()->value() == 1); + + { + // because the first regiter_metric is set no lock, so visit + // default_metric_manger with lock will throw. + auto c1 = std::make_shared(std::string(""), std::string("")); + CHECK_THROWS_AS(default_metric_manger::register_metric_dynamic(c1), + std::invalid_argument); + CHECK_THROWS_AS(default_metric_manger::metric_count_dynamic(), + std::invalid_argument); + CHECK_THROWS_AS(default_metric_manger::metric_keys_dynamic(), + std::invalid_argument); + CHECK_THROWS_AS(default_metric_manger::metric_map_dynamic(), + std::invalid_argument); + CHECK_THROWS_AS(default_metric_manger::get_metric_dynamic(""), + std::invalid_argument); + } } DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) -int main(int argc, char **argv) { return doctest::Context(argc, argv).run(); } +int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } DOCTEST_MSVC_SUPPRESS_WARNING_POP \ No newline at end of file