diff --git a/include/ylt/metric/counter.hpp b/include/ylt/metric/counter.hpp index cf690af7c..750154b22 100644 --- a/include/ylt/metric/counter.hpp +++ b/include/ylt/metric/counter.hpp @@ -107,6 +107,9 @@ class basic_static_counter : public static_metric { } value_type update(value_type value) { + if (!has_change_) [[unlikely]] { + has_change_ = true; + } return default_label_value_.update(value); } @@ -116,7 +119,7 @@ class basic_static_counter : public static_metric { void serialize(std::string &str) override { auto value = default_label_value_.value(); - if (value == 0) { + if (value == 0 && !has_change_) { return; } @@ -164,6 +167,7 @@ class basic_static_counter : public static_metric { thread_local_value default_label_value_; uint32_t dupli_count_ = 2; + bool has_change_ = false; }; template @@ -231,6 +235,8 @@ class basic_dynamic_counter : public dynamic_metric { if (value_map_.size() > ylt_label_capacity) { return value_type{}; } + if (!has_change_) [[unlikely]] + has_change_ = true; auto [it, r] = value_map_.try_emplace( labels_value, thread_local_value(dupli_count_)); lock.unlock(); @@ -266,12 +272,20 @@ class basic_dynamic_counter : public dynamic_metric { dynamic_metric_hash_map, thread_local_value> value_map() { + [[maybe_unused]] bool has_change = false; + return value_map(has_change); + } + + dynamic_metric_hash_map, + thread_local_value> + value_map(bool &has_change) { dynamic_metric_hash_map, thread_local_value> map; { std::lock_guard lock(mtx_); map = value_map_; + has_change = has_change_; } return map; @@ -353,7 +367,8 @@ class basic_dynamic_counter : public dynamic_metric { } bool has_label_value(const std::string &value) override { - auto map = value_map(); + [[maybe_unused]] bool has_change = false; + auto map = value_map(has_change); for (auto &[label_value, _] : map) { if (auto it = std::find(label_value.begin(), label_value.end(), value); it != label_value.end()) { @@ -365,7 +380,8 @@ class basic_dynamic_counter : public dynamic_metric { } bool has_label_value(const std::regex ®ex) override { - auto map = value_map(); + [[maybe_unused]] bool has_change = false; + auto map = value_map(has_change); for (auto &[label_value, _] : map) { if (auto it = std::find_if(label_value.begin(), label_value.end(), [&](auto &val) { @@ -390,13 +406,14 @@ class basic_dynamic_counter : public dynamic_metric { } void serialize(std::string &str) override { - auto map = value_map(); + bool has_change = false; + auto map = value_map(has_change); if (map.empty()) { return; } std::string value_str; - serialize_map(map, value_str); + serialize_map(map, value_str, has_change); if (!value_str.empty()) { serialize_head(str); str.append(value_str); @@ -406,16 +423,18 @@ class basic_dynamic_counter : public dynamic_metric { #ifdef CINATRA_ENABLE_METRIC_JSON void serialize_to_json(std::string &str) override { std::string s; - auto map = value_map(); + bool has_change = false; + auto map = value_map(has_change); json_counter_t counter{name_, help_, std::string(metric_name())}; - to_json(counter, map, str); + to_json(counter, map, str, has_change); } template - void to_json(json_counter_t &counter, T &map, std::string &str) { + void to_json(json_counter_t &counter, T &map, std::string &str, + bool has_change) { for (auto &[k, v] : map) { auto val = v.value(); - if (val == 0) { + if (val == 0 && !has_change) { continue; } json_counter_metric_t metric; @@ -434,10 +453,10 @@ class basic_dynamic_counter : public dynamic_metric { protected: template - void serialize_map(T &value_map, std::string &str) { + void serialize_map(T &value_map, std::string &str, bool has_change) { for (auto &[labels_value, value] : value_map) { auto val = value.value(); - if (val == 0) { + if (val == 0 && !has_change) { continue; } str.append(name_); @@ -477,6 +496,7 @@ class basic_dynamic_counter : public dynamic_metric { thread_local_value> value_map_; size_t dupli_count_ = 2; + bool has_change_ = false; }; using dynamic_counter_1t = basic_dynamic_counter; diff --git a/include/ylt/metric/gauge.hpp b/include/ylt/metric/gauge.hpp index 7deb301e9..50d678c1c 100644 --- a/include/ylt/metric/gauge.hpp +++ b/include/ylt/metric/gauge.hpp @@ -11,6 +11,7 @@ class basic_static_gauge : public basic_static_counter { using basic_static_counter::default_label_value_; using metric_t::labels_value_; using basic_static_counter::dupli_count_; + using basic_static_counter::has_change_; public: basic_static_gauge(std::string name, std::string help, size_t dupli_count = 2) @@ -28,6 +29,9 @@ class basic_static_gauge : public basic_static_counter { } void dec(value_type value = 1) { + if (!has_change_) [[unlikely]] { + has_change_ = true; + } #ifdef __APPLE__ if constexpr (std::is_floating_point_v) { mac_os_atomic_fetch_sub(&default_label_value_.local_value(), value); @@ -49,6 +53,7 @@ class basic_dynamic_gauge : public basic_dynamic_counter { using basic_dynamic_counter::value_map_; using basic_dynamic_counter::mtx_; using basic_dynamic_counter::dupli_count_; + using basic_dynamic_counter::has_change_; public: basic_dynamic_gauge(std::string name, std::string help, @@ -70,6 +75,8 @@ class basic_dynamic_gauge : public basic_dynamic_counter { if (value_map_.size() > ylt_label_capacity) { return; } + if (!has_change_) [[unlikely]] + has_change_ = true; auto [it, r] = value_map_.try_emplace( labels_value, thread_local_value(dupli_count_)); lock.unlock(); diff --git a/include/ylt/metric/summary.hpp b/include/ylt/metric/summary.hpp index d7f9a5b6f..9ac1a980e 100644 --- a/include/ylt/metric/summary.hpp +++ b/include/ylt/metric/summary.hpp @@ -64,6 +64,9 @@ class summary_t : public static_metric { } void observe(double value) { + if (!has_observe_) [[unlikely]] { + has_observe_ = true; + } int64_t max_limit = (std::min)(ylt_label_capacity, (int64_t)1000000); if (block_->sample_queue_.size_approx() >= max_limit) { g_summary_failed_count++; @@ -123,6 +126,10 @@ class summary_t : public static_metric { co_return; } + if (!has_observe_) { + co_return; + } + serialize_head(str); double sum = 0; @@ -156,6 +163,10 @@ class summary_t : public static_metric { co_return; } + if (!has_observe_) { + co_return; + } + json_summary_t summary{name_, help_, std::string(metric_name())}; double sum = 0; uint64_t count = 0; @@ -233,6 +244,7 @@ class summary_t : public static_metric { static inline std::shared_ptr excutor_ = coro_io::create_io_context_pool(1); std::atomic is_coro_started_ = false; + bool has_observe_ = false; }; template @@ -284,6 +296,9 @@ class basic_dynamic_summary : public dynamic_metric { } void observe(std::array labels_value, double value) { + if (!has_observe_) [[unlikely]] { + has_observe_ = true; + } int64_t max_limit = (std::min)(ylt_label_capacity, (int64_t)1000000); if (labels_block_->sample_queue_.size_approx() >= max_limit) { g_summary_failed_count++; @@ -401,6 +416,10 @@ class basic_dynamic_summary : public dynamic_metric { co_return; } + if (!has_observe_) { + co_return; + } + serialize_head(str); auto sum_map = co_await coro_io::post( @@ -444,6 +463,10 @@ class basic_dynamic_summary : public dynamic_metric { co_return; } + if (!has_observe_) { + co_return; + } + auto sum_map = co_await coro_io::post( [this] { return labels_block_->sum_and_count_; @@ -479,6 +502,7 @@ class basic_dynamic_summary : public dynamic_metric { std::chrono::milliseconds max_age_; uint16_t age_buckets_; std::atomic is_coro_started_ = false; + bool has_observe_ = false; }; using dynamic_summary_1 = basic_dynamic_summary<1>; diff --git a/src/metric/tests/test_metric.cpp b/src/metric/tests/test_metric.cpp index a165e2fec..ac6b012b5 100644 --- a/src/metric/tests/test_metric.cpp +++ b/src/metric/tests/test_metric.cpp @@ -1,5 +1,7 @@ #define DOCTEST_CONFIG_IMPLEMENT +#include #include +using namespace std::chrono_literals; #include "doctest.h" #include "ylt/metric.hpp" @@ -11,6 +13,91 @@ struct metrc_tag {}; struct test_tag {}; +TEST_CASE("serialize zero") { + counter_t c("test", ""); + gauge_t g("test1", ""); + std::string str; + c.serialize(str); + CHECK(str.empty()); + g.serialize(str); + CHECK(str.empty()); + c.inc(); + c.serialize(str); + CHECK(!str.empty()); + str.clear(); + g.inc(); + g.serialize(str); + CHECK(!str.empty()); + c.update(0); + c.serialize(str); + CHECK(!str.empty()); + str.clear(); + g.dec(); + g.serialize(str); + CHECK(!str.empty()); + str.clear(); + + dynamic_counter_1t c1("test", "", {"url"}); + c1.serialize(str); + CHECK(str.empty()); + dynamic_gauge_1t g1("test", "", {"url"}); + g1.serialize(str); + CHECK(str.empty()); + c1.inc({"/test"}); + c1.serialize(str); + CHECK(!str.empty()); + str.clear(); + g1.inc({"/test"}); + g1.serialize(str); + CHECK(!str.empty()); + str.clear(); + + c1.update({"/test"}, 0); + c1.serialize(str); + CHECK(!str.empty()); + str.clear(); + + g1.dec({"/test"}); + g1.serialize(str); + CHECK(!str.empty()); + str.clear(); + + c1.serialize_to_json(str); + CHECK(!str.empty()); + str.clear(); + g1.serialize_to_json(str); + CHECK(!str.empty()); + str.clear(); + + histogram_t h("test", "help", {5.23, 10.54, 20.0, 50.0, 100.0}); + h.serialize(str); + CHECK(str.empty()); + h.serialize_to_json(str); + CHECK(str.empty()); + h.observe(23); + h.serialize(str); + CHECK(!str.empty()); + str.clear(); + + std::map customMap = {}; + auto summary = std::make_shared( + "test", "help", + summary_t::Quantiles{ + {0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}, + customMap); + async_simple::coro::syncAwait(summary->serialize_async(str)); + CHECK(str.empty()); + async_simple::coro::syncAwait(summary->serialize_to_json_async(str)); + CHECK(str.empty()); + summary->observe(0); + async_simple::coro::syncAwait(summary->serialize_async(str)); + CHECK(!str.empty()); + str.clear(); + async_simple::coro::syncAwait(summary->serialize_to_json_async(str)); + CHECK(!str.empty()); + str.clear(); +} + TEST_CASE("test metric manager") { auto c = std::make_shared("test1", ""); auto g = std::make_shared("test2", "");