diff --git a/example/main.cpp b/example/main.cpp index f9cc8e75..54763bdd 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -440,10 +440,9 @@ void use_metric() { std::make_shared(std::string("test"), std::string("help"), std::vector{5.0, 10.0, 20.0, 50.0, 100.0}); - auto summary = std::make_shared( - std::string("test_summary"), std::string("summary help"), - summary_t::Quantiles{ - {0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}); + auto summary = std::make_shared(std::string("test_summary"), + std::string("summary help"), + std::vector{0.5, 0.9, 0.95, 0.99}); default_static_metric_manager::instance().register_metric(c); default_static_metric_manager::instance().register_metric(total); diff --git a/include/cinatra/coro_http_client.hpp b/include/cinatra/coro_http_client.hpp index 00d31efc..e4413485 100644 --- a/include/cinatra/coro_http_client.hpp +++ b/include/cinatra/coro_http_client.hpp @@ -2445,7 +2445,7 @@ class coro_http_client : public std::enable_shared_from_this { bool enable_follow_redirect_ = false; bool enable_timeout_ = false; std::chrono::steady_clock::duration conn_timeout_duration_ = - std::chrono::seconds(30); + std::chrono::seconds(8); std::chrono::steady_clock::duration req_timeout_duration_ = std::chrono::seconds(60); bool enable_tcp_no_delay_ = true; diff --git a/include/cinatra/ylt/metric/counter.hpp b/include/cinatra/ylt/metric/counter.hpp index 750154b2..43dc40b3 100644 --- a/include/cinatra/ylt/metric/counter.hpp +++ b/include/cinatra/ylt/metric/counter.hpp @@ -1,11 +1,13 @@ #pragma once + #include #include #include -#include +#include +#include #include -#include "metric.hpp" +#include "dynamic_metric.hpp" #include "thread_local_value.hpp" namespace ylt::metric { @@ -13,97 +15,46 @@ enum class op_type_t { INC, DEC, SET }; #ifdef CINATRA_ENABLE_METRIC_JSON struct json_counter_metric_t { - std::map labels; + std::vector labels; std::variant value; }; -REFLECTION(json_counter_metric_t, labels, value); +YLT_REFL(json_counter_metric_t, labels, value); struct json_counter_t { - std::string name; - std::string help; - std::string type; + std::string_view name; + std::string_view help; + std::string_view type; + std::vector labels_name; std::vector metrics; }; -REFLECTION(json_counter_t, name, help, type, metrics); +YLT_REFL(json_counter_t, name, help, type, labels_name, metrics); #endif -template -inline void set_value(T &label_val, value_type value, op_type_t type) { - switch (type) { - case op_type_t::INC: { -#ifdef __APPLE__ - if constexpr (std::is_floating_point_v) { - mac_os_atomic_fetch_add(&label_val, value); - } - else { - label_val += value; - } -#else - label_val += value; -#endif - } break; - case op_type_t::DEC: -#ifdef __APPLE__ - if constexpr (std::is_floating_point_v) { - mac_os_atomic_fetch_sub(&label_val, value); - } - else { - label_val -= value; - } -#else - label_val -= value; -#endif - break; - case op_type_t::SET: - label_val = value; - break; - } -} - template class basic_static_counter : public static_metric { public: // static counter, no labels, only contains an atomic value. basic_static_counter(std::string name, std::string help, - size_t dupli_count = 2) - : static_metric(MetricType::Counter, std::move(name), std::move(help)) { - init_thread_local(dupli_count); - } + uint32_t dupli_count = (std::min)( + 128u, std::thread::hardware_concurrency())) + : static_metric(MetricType::Counter, std::move(name), std::move(help)), + dupli_count_((std::max)(1u, dupli_count)), + default_label_value_(dupli_count_) {} // static counter, contains a static labels with atomic value. basic_static_counter(std::string name, std::string help, std::map labels, - uint32_t dupli_count = 2) + uint32_t dupli_count = (std::min)( + 128u, std::thread::hardware_concurrency())) : static_metric(MetricType::Counter, std::move(name), std::move(help), - std::move(labels)) { - init_thread_local(dupli_count); - } - - void init_thread_local(uint32_t dupli_count) { - if (dupli_count > 0) { - dupli_count_ = dupli_count; - default_label_value_ = {dupli_count}; - } - - g_user_metric_count++; - } - - virtual ~basic_static_counter() { g_user_metric_count--; } + std::move(labels)), + dupli_count_((std::max)(1u, dupli_count)), + default_label_value_(dupli_count_) {} void inc(value_type val = 1) { if (val <= 0) { return; } - -#ifdef __APPLE__ - if constexpr (std::is_floating_point_v) { - mac_os_atomic_fetch_add(&default_label_value_.local_value(), val); - } - else { - default_label_value_.inc(val); - } -#else default_label_value_.inc(val); -#endif } value_type update(value_type value) { @@ -123,23 +74,33 @@ class basic_static_counter : public static_metric { return; } - serialize_head(str); + metric_t::serialize_head(str); serialize_default_label(str, value); } #ifdef CINATRA_ENABLE_METRIC_JSON void serialize_to_json(std::string &str) override { - if (default_label_value_.value() == 0) { + auto value = default_label_value_.value(); + if (value == 0 && !has_change_) { return; } - json_counter_t counter{name_, help_, std::string(metric_name())}; - auto value = default_label_value_.value(); - counter.metrics.push_back({static_labels_, value}); + json_counter_t counter{name_, help_, metric_name()}; + + counter.labels_name.reserve(static_labels_.size()); + for (auto &[k, _] : static_labels_) { + counter.labels_name.emplace_back(k); + } + counter.metrics.resize(1); + counter.metrics[0].labels.reserve(static_labels_.size()); + for (auto &[k, _] : static_labels_) { + counter.metrics[0].labels.emplace_back(k); + } + counter.metrics[0].value = value; iguana::to_json(counter, str); } #endif - + private: protected: void serialize_default_label(std::string &str, value_type value) { str.append(name_); @@ -165,159 +126,48 @@ class basic_static_counter : public static_metric { str.pop_back(); } - thread_local_value default_label_value_; - uint32_t dupli_count_ = 2; bool has_change_ = false; -}; - -template -struct array_hash { - size_t operator()(const Key &arr) const { - unsigned int seed = 131; - unsigned int hash = 0; - - for (const auto &str : arr) { - for (auto ch : str) { - hash = hash * seed + ch; - } - } - - return (hash & 0x7FFFFFFF); - } + uint32_t dupli_count_; + thread_local_value default_label_value_; }; using counter_t = basic_static_counter; using counter_d = basic_static_counter; -template -using dynamic_metric_hash_map = std::unordered_map>; - template -class basic_dynamic_counter : public dynamic_metric { +class basic_dynamic_counter + : public dynamic_metric_impl, N> { + using Base = dynamic_metric_impl, N>; + public: // dynamic labels value basic_dynamic_counter(std::string name, std::string help, - std::array labels_name, - size_t dupli_count = 2) - : dynamic_metric(MetricType::Counter, std::move(name), std::move(help), - std::move(labels_name)), - dupli_count_(dupli_count) { - g_user_metric_count++; - } - - virtual ~basic_dynamic_counter() { g_user_metric_count--; } - - void inc(const std::array &labels_value, - value_type value = 1) { - if (value == 0) { - return; - } - - std::unique_lock lock(mtx_); - if (value_map_.size() > ylt_label_capacity) { - return; - } - auto [it, r] = value_map_.try_emplace( - labels_value, thread_local_value(dupli_count_)); - lock.unlock(); - if (r) { - g_user_metric_label_count->local_value()++; - if (ylt_label_max_age.count()) { - it->second.set_created_time(std::chrono::system_clock::now()); - } - } - set_value(it->second.local_value(), value, op_type_t::INC); + std::array labels_name) + : Base(MetricType::Counter, std::move(name), std::move(help), + std::move(labels_name)) {} + using label_key_type = const std::array &; + void inc(label_key_type labels_value, value_type value = 1) { + detail::inc_impl(Base::try_emplace(labels_value).first->value, value); } - value_type update(const std::array &labels_value, - value_type value) { - std::unique_lock lock(mtx_); - 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(); - if (r) { - g_user_metric_label_count->local_value()++; - if (ylt_label_max_age.count()) { - it->second.set_created_time(std::chrono::system_clock::now()); - } - } - return it->second.update(value); + value_type update(label_key_type labels_value, value_type value) { + return Base::try_emplace(labels_value) + .first->value.exchange(value, std::memory_order::relaxed); } - value_type value(const std::array &labels_value) { - std::lock_guard lock(mtx_); - if (auto it = value_map_.find(labels_value); it != value_map_.end()) { - return it->second.value(); + value_type value(label_key_type labels_value) { + if (auto ptr = Base::find(labels_value); ptr != nullptr) { + return ptr->value.load(std::memory_order::relaxed); } - - return value_type{}; - } - - value_type reset() { - value_type val = {}; - - std::lock_guard lock(mtx_); - for (auto &[key, t] : value_map_) { - val += t.reset(); - } - - return val; - } - - 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; - } - - size_t label_value_count() override { - std::lock_guard lock(mtx_); - return value_map_.size(); - } - - void clean_expired_label() override { - if (ylt_label_max_age.count() == 0) { - return; + else { + return value_type{}; } - - auto now = std::chrono::system_clock::now(); - std::lock_guard lock(mtx_); - std::erase_if(value_map_, [&now](auto &pair) mutable { - bool r = std::chrono::duration_cast( - now - pair.second.get_created_time()) - .count() >= ylt_label_max_age.count(); - return r; - }); } void remove_label_value( const std::map &labels) override { - { - std::lock_guard lock(mtx_); - if (value_map_.empty()) { - return; - } + if (Base::empty()) { + return; } const auto &labels_name = this->labels_name(); @@ -325,51 +175,41 @@ class basic_dynamic_counter : public dynamic_metric { return; } - if (labels.size() == labels_name.size()) { - std::vector label_value; - for (auto &lb_name : labels_name) { - if (auto i = labels.find(lb_name); i != labels.end()) { - label_value.push_back(i->second); - } - } + // if (labels.size() == labels_name.size()) { // TODO: speed up for this + // case - std::lock_guard lock(mtx_); - std::erase_if(value_map_, [&, this](auto &pair) { - return equal(label_value, pair.first); - }); - return; - } - else { - std::vector vec; - for (auto &lb_name : labels_name) { - if (auto i = labels.find(lb_name); i != labels.end()) { - vec.push_back(i->second); - } - else { - vec.push_back(""); - } + // } + // else { + std::vector vec; + for (auto &lb_name : labels_name) { + if (auto i = labels.find(lb_name); i != labels.end()) { + vec.push_back(i->second); } - if (vec.empty()) { - return; + else { + vec.push_back(""); } - - std::lock_guard lock(mtx_); - std::erase_if(value_map_, [&](auto &pair) { - auto &[arr, _] = pair; + } + if (vec.empty()) { + return; + } + Base::erase_if([&](auto &pair) { + auto &[arr, _] = pair; + if constexpr (N > 0) { for (size_t i = 0; i < vec.size(); i++) { if (!vec[i].empty() && vec[i] != arr[i]) { return false; } } - return true; - }); - } + } + return true; + }); + //} } bool has_label_value(const std::string &value) override { - [[maybe_unused]] bool has_change = false; - auto map = value_map(has_change); - for (auto &[label_value, _] : map) { + auto map = Base::copy(); + for (auto &e : map) { + auto &label_value = e->label; if (auto it = std::find(label_value.begin(), label_value.end(), value); it != label_value.end()) { return true; @@ -380,9 +220,9 @@ class basic_dynamic_counter : public dynamic_metric { } bool has_label_value(const std::regex ®ex) override { - [[maybe_unused]] bool has_change = false; - auto map = value_map(has_change); - for (auto &[label_value, _] : map) { + auto map = Base::copy(); + for (auto &e : map) { + auto &label_value = e->label; if (auto it = std::find_if(label_value.begin(), label_value.end(), [&](auto &val) { return std::regex_match(val, regex); @@ -401,48 +241,46 @@ class basic_dynamic_counter : public dynamic_metric { for (size_t i = 0; i < size; i++) { arr[i] = label_value[i]; } - std::lock_guard lock(mtx_); - return value_map_.contains(arr); + return Base::find(arr) != nullptr; } void serialize(std::string &str) override { - bool has_change = false; - auto map = value_map(has_change); + auto map = Base::copy(); if (map.empty()) { return; } std::string value_str; - serialize_map(map, value_str, has_change); + serialize_map(map, value_str); if (!value_str.empty()) { - serialize_head(str); + Base::serialize_head(str); str.append(value_str); } } #ifdef CINATRA_ENABLE_METRIC_JSON void serialize_to_json(std::string &str) override { - std::string s; - 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, has_change); + auto map = Base::copy(); + json_counter_t counter{Base::name_, Base::help_, Base::metric_name()}; + counter.labels_name.reserve(Base::labels_name().size()); + for (auto &e : Base::labels_name()) { + counter.labels_name.emplace_back(e); + } + to_json(counter, map, str); } template - 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 && !has_change) { - continue; - } + void to_json(json_counter_t &counter, T &map, std::string &str) { + for (auto &e : map) { + auto &k = e->label; + auto &val = e->value; json_counter_metric_t metric; size_t index = 0; + metric.labels.reserve(k.size()); for (auto &label_value : k) { - metric.labels.emplace(labels_name_[index++], label_value); + metric.labels.emplace_back(label_value); } - metric.value = (int64_t)val; + metric.value = val.load(std::memory_order::relaxed); counter.metrics.push_back(std::move(metric)); } if (!counter.metrics.empty()) { @@ -453,19 +291,17 @@ class basic_dynamic_counter : public dynamic_metric { protected: template - 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 && !has_change) { - continue; - } - str.append(name_); - if (labels_name_.empty()) { + void serialize_map(T &value_map, std::string &str) { + for (auto &e : value_map) { + auto &labels_value = e->label; + auto val = e->value.load(std::memory_order::relaxed); + str.append(Base::name_); + if (Base::labels_name_.empty()) { str.append(" "); } else { str.append("{"); - build_string(str, labels_name_, labels_value); + build_string(str, Base::labels_name_, labels_value); str.append("} "); } @@ -490,13 +326,6 @@ class basic_dynamic_counter : public dynamic_metric { } str.pop_back(); } - - std::mutex mtx_; - dynamic_metric_hash_map, - 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/cinatra/ylt/metric/detail/ckms_quantiles.hpp b/include/cinatra/ylt/metric/detail/ckms_quantiles.hpp deleted file mode 100644 index b56b6167..00000000 --- a/include/cinatra/ylt/metric/detail/ckms_quantiles.hpp +++ /dev/null @@ -1,175 +0,0 @@ -#pragma once -#include -#include -#include - -// https://github.com/jupp0r/prometheus-cpp/blob/master/core/include/prometheus/detail/ckms_quantiles.h - -namespace ylt::metric { -class CKMSQuantiles { - public: - struct Quantile { - Quantile(double quantile, double error) - : quantile(quantile), - error(error), - u(2.0 * error / (1.0 - quantile)), - v(2.0 * error / quantile) {} - - double quantile; - double error; - double u; - double v; - }; - - private: - struct Item { - double value; - int g; - int delta; - - Item(double value, int lower_delta, int delta) - : value(value), g(lower_delta), delta(delta) {} - }; - - public: - explicit CKMSQuantiles(const std::vector& quantiles) - : quantiles_(quantiles), count_(0), buffer_{}, buffer_count_(0) {} - - void insert(double value) { - buffer_[buffer_count_] = value; - ++buffer_count_; - - if (buffer_count_ == buffer_.size()) { - insertBatch(); - compress(); - } - } - - double get(double q) { - insertBatch(); - compress(); - - if (sample_.empty()) { - return std::numeric_limits::quiet_NaN(); - } - - int rankMin = 0; - const auto desired = static_cast(q * count_); - const auto bound = desired + (allowableError(desired) / 2); - - auto it = sample_.begin(); - decltype(it) prev; - auto cur = it++; - - while (it != sample_.end()) { - prev = cur; - cur = it++; - - rankMin += prev->g; - - if (rankMin + cur->g + cur->delta > bound) { - return prev->value; - } - } - - return sample_.back().value; - } - void reset() { - count_ = 0; - sample_.clear(); - buffer_count_ = 0; - } - - private: - double allowableError(int rank) { - auto size = sample_.size(); - double minError = size + 1; - - for (const auto& q : quantiles_.get()) { - double error; - if (rank <= q.quantile * size) { - error = q.u * (size - rank); - } - else { - error = q.v * rank; - } - if (error < minError) { - minError = error; - } - } - return minError; - } - - bool insertBatch() { - if (buffer_count_ == 0) { - return false; - } - - std::sort(buffer_.begin(), buffer_.begin() + buffer_count_); - - std::size_t start = 0; - if (sample_.empty()) { - sample_.emplace_back(buffer_[0], 1, 0); - ++start; - ++count_; - } - - std::size_t idx = 0; - std::size_t item = idx++; - - for (uint16_t i = start; i < buffer_count_; ++i) { - double v = buffer_[i]; - while (idx < sample_.size() && sample_[item].value < v) { - item = idx++; - } - - if (sample_[item].value > v) { - --idx; - } - - int delta; - if (idx - 1 == 0 || idx + 1 == sample_.size()) { - delta = 0; - } - else { - delta = static_cast(std::floor(allowableError(idx + 1))) + 1; - } - - sample_.emplace(sample_.begin() + idx, v, 1, delta); - count_++; - item = idx++; - } - - buffer_count_ = 0; - return true; - } - void compress() { - if (sample_.size() < 2) { - return; - } - - std::size_t idx = 0; - std::size_t prev; - std::size_t next = idx++; - - while (idx < sample_.size()) { - prev = next; - next = idx++; - - if (sample_[prev].g + sample_[next].g + sample_[next].delta <= - allowableError(idx - 1)) { - sample_[next].g += sample_[prev].g; - sample_.erase(sample_.begin() + prev); - } - } - } - - private: - const std::reference_wrapper> quantiles_; - - uint16_t count_; - std::vector sample_; - std::array buffer_; - uint16_t buffer_count_; -}; -} // 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 deleted file mode 100644 index 07debac1..00000000 --- a/include/cinatra/ylt/metric/detail/time_window_quantiles.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once -#include "ckms_quantiles.hpp" -// https://github.com/jupp0r/prometheus-cpp/blob/master/core/include/prometheus/detail/time_window_quantiles.h - -namespace ylt::metric { -class TimeWindowQuantiles { - using Clock = std::chrono::steady_clock; - - public: - TimeWindowQuantiles(const std::vector& quantiles, - Clock::duration max_age_seconds, uint16_t age_buckets) - : quantiles_(quantiles), - ckms_quantiles_(age_buckets, CKMSQuantiles(quantiles_)), - current_bucket_(0), - last_rotation_(Clock::now()), - rotation_interval_(max_age_seconds / age_buckets) {} - - double get(double q) const { - CKMSQuantiles& current_bucket = rotate(); - return current_bucket.get(q); - } - void insert(double value) { - rotate(); - for (auto& bucket : ckms_quantiles_) { - bucket.insert(value); - } - } - - private: - CKMSQuantiles& rotate() const { - auto delta = Clock::now() - last_rotation_; - while (delta > rotation_interval_) { - ckms_quantiles_[current_bucket_].reset(); - - if (++current_bucket_ >= ckms_quantiles_.size()) { - current_bucket_ = 0; - } - - delta -= rotation_interval_; - last_rotation_ += rotation_interval_; - } - return ckms_quantiles_[current_bucket_]; - } - - const std::vector& quantiles_; - mutable std::vector ckms_quantiles_; - mutable uint16_t current_bucket_; - - mutable Clock::time_point last_rotation_; - const Clock::duration rotation_interval_; -}; -} // namespace ylt::metric \ No newline at end of file diff --git a/include/cinatra/ylt/metric/dynamic_metric.hpp b/include/cinatra/ylt/metric/dynamic_metric.hpp new file mode 100644 index 00000000..02533bbe --- /dev/null +++ b/include/cinatra/ylt/metric/dynamic_metric.hpp @@ -0,0 +1,131 @@ +#pragma once +#include + +#include "metric.hpp" +#if __has_include("ylt/util/type_traits.h") +#include "ylt/util/map_sharded.hpp" +#else +#include "../util/map_sharded.hpp" +#endif + +namespace ylt::metric { + +class dynamic_metric : public metric_t { + public: + using metric_t::metric_t; +}; + +template +class dynamic_metric_impl : public dynamic_metric { + template + struct my_hash { + using is_transparent = void; + std::size_t operator()( + const std::span& s) const noexcept { + unsigned int hash = 0; + for (const auto& str : s) { + for (auto ch : str) { + hash = hash * seed + ch; + } + } + return hash; + } + std::size_t operator()( + const std::span& s) const noexcept { + unsigned int hash = 0; + for (const auto& str : s) { + for (auto ch : str) { + hash = hash * seed + ch; + } + } + return hash; + } + }; + struct my_equal { + bool operator()(const std::span& s1, + const std::span& s2) const noexcept { + if constexpr (N > 0) { + for (int i = 0; i < N; ++i) { + if (s1[i] != s2[i]) { + return false; + } + } + } + return true; + } + }; + using key_type = std::array; + struct metric_pair { + public: + key_type label; + core_type value; + template + metric_pair(T&& first, Args&&... args) + : label(std::forward(first)), value(std::forward(args)...) { + g_user_metric_label_count->inc(); + if (ylt_label_max_age.count()) { + tp = std::chrono::steady_clock::now(); + } + } + std::chrono::steady_clock::time_point get_created_time() const { + return tp; + } + + private: + std::chrono::steady_clock::time_point tp; + }; + + struct value_type : public std::shared_ptr { + value_type() : std::shared_ptr(nullptr) {} + template + value_type(Args&&... args) + : std::shared_ptr( + std::make_shared(std::forward(args)...)){}; + }; + + public: + using dynamic_metric::dynamic_metric; + size_t size() const { return map_.size(); } + size_t empty() const { return !size(); } + size_t label_value_count() const { return size(); } + + std::vector> copy() const { + return map_.template copy>(); + } + + protected: + template + std::pair, bool> try_emplace(Key&& key, + Args&&... args) { + std::span view = key; + return map_.try_emplace_with_op( + view, + [](auto result) { + if (result.second) { + *const_cast*>( + &result.first->first) = result.first->second->label; + } + }, + std::forward(key), std::forward(args)...); + } + void clean_expired_label() override { + erase_if([now = std::chrono::steady_clock::now()](auto& pair) mutable { + bool r = std::chrono::duration_cast( + now - pair.second->get_created_time()) + .count() >= ylt_label_max_age.count(); + return r; + }); + } + std::shared_ptr find(std::span key) const { + return map_.find(key); + } + size_t erase(std::span key) { return map_.erase(key); } + size_t erase_if(auto&& op) { return map_.erase_if(op); } + + private: + util::map_sharded_t, + value_type, my_hash<131>, my_equal>, + my_hash<137>> + map_{std::min(128u, std::thread::hardware_concurrency())}; +}; +} // 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 50d678c1..b783ffa1 100644 --- a/include/cinatra/ylt/metric/gauge.hpp +++ b/include/cinatra/ylt/metric/gauge.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include "counter.hpp" @@ -32,16 +33,7 @@ class basic_static_gauge : public basic_static_counter { 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); - } - else { - default_label_value_.dec(value); - } -#else default_label_value_.dec(value); -#endif } }; using gauge_t = basic_static_gauge; @@ -50,44 +42,18 @@ using gauge_d = basic_static_gauge; template class basic_dynamic_gauge : public basic_dynamic_counter { using metric_t::set_metric_type; - using basic_dynamic_counter::value_map_; - using basic_dynamic_counter::mtx_; - using basic_dynamic_counter::dupli_count_; - using basic_dynamic_counter::has_change_; + using Base = basic_dynamic_counter; public: basic_dynamic_gauge(std::string name, std::string help, - std::array labels_name, - size_t dupli_count = 2) - : basic_dynamic_counter(std::move(name), std::move(help), - std::move(labels_name), - dupli_count) { + std::array labels_name) + : Base(std::move(name), std::move(help), std::move(labels_name)) { set_metric_type(MetricType::Gauge); } void dec(const std::array& labels_value, value_type value = 1) { - if (value == 0) { - return; - } - - std::unique_lock lock(mtx_); - 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(); - if (r) { - g_user_metric_label_count->local_value()++; - if (ylt_label_max_age.count()) { - it->second.set_created_time(std::chrono::system_clock::now()); - } - } - - set_value(it->second.local_value(), value, op_type_t::DEC); + detail::dec_impl(Base::try_emplace(labels_value).first->value, value); } }; diff --git a/include/cinatra/ylt/metric/histogram.hpp b/include/cinatra/ylt/metric/histogram.hpp index 4b4fe140..5e3e58f5 100644 --- a/include/cinatra/ylt/metric/histogram.hpp +++ b/include/cinatra/ylt/metric/histogram.hpp @@ -6,7 +6,8 @@ #include #include "counter.hpp" -#include "metric.hpp" +#include "dynamic_metric.hpp" +#include "gauge.hpp" namespace ylt::metric { #ifdef CINATRA_ENABLE_METRIC_JSON @@ -16,14 +17,14 @@ struct json_histogram_metric_t { int64_t count; double sum; }; -REFLECTION(json_histogram_metric_t, labels, quantiles, count, sum); +YLT_REFL(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); +YLT_REFL(json_histogram_t, name, help, type, metrics); #endif template @@ -140,8 +141,6 @@ class basic_static_histogram : public static_metric { private: void init_bucket_counter(size_t dupli_count, size_t bucket_size) { - g_user_metric_count++; - for (size_t i = 0; i < bucket_size + 1; i++) { bucket_counts_.push_back( std::make_shared("", "", dupli_count)); @@ -167,18 +166,15 @@ class basic_dynamic_histogram : public dynamic_metric { public: basic_dynamic_histogram(std::string name, std::string help, std::vector buckets, - std::array labels_name, - size_t dupli_count = 2) + std::array labels_name) : bucket_boundaries_(buckets), dynamic_metric(MetricType::Histogram, name, help, labels_name), sum_(std::make_shared>( - name, help, labels_name, dupli_count)) { - g_user_metric_count++; - + name, help, labels_name)) { for (size_t i = 0; i < buckets.size() + 1; i++) { bucket_counts_.push_back( - std::make_shared>( - name, help, labels_name, dupli_count)); + std::make_shared>(name, help, + labels_name)); } } @@ -207,7 +203,7 @@ class basic_dynamic_histogram : public dynamic_metric { } void serialize(std::string &str) override { - auto value_map = sum_->value_map(); + auto value_map = sum_->copy(); if (value_map.empty()) { return; } @@ -216,8 +212,10 @@ class basic_dynamic_histogram : public dynamic_metric { std::string value_str; auto bucket_counts = get_bucket_counts(); - for (auto &[labels_value, value] : value_map) { - if (value.value() == 0) { + for (auto &e : value_map) { + auto &labels_value = e->label; + auto &value = e->value; + if (value == 0) { continue; } @@ -255,7 +253,7 @@ class basic_dynamic_histogram : public dynamic_metric { build_label_string(str, sum_->labels_name(), labels_value); str.append("} "); - str.append(std::to_string(value.value())); + str.append(std::to_string(value)); str.append("\n"); str.append(name_).append("_count{"); @@ -268,7 +266,7 @@ class basic_dynamic_histogram : public dynamic_metric { #ifdef CINATRA_ENABLE_METRIC_JSON void serialize_to_json(std::string &str) override { - auto value_map = sum_->value_map(); + auto value_map = sum_->copy(); if (value_map.empty()) { return; } @@ -276,8 +274,10 @@ class basic_dynamic_histogram : public dynamic_metric { json_histogram_t hist{name_, help_, std::string(metric_name())}; auto bucket_counts = get_bucket_counts(); - for (auto &[labels_value, value] : value_map) { - if (value.value() == 0) { + for (auto &e : value_map) { + auto &labels_value = e->label; + auto &value = e->value; + if (value == 0) { continue; } diff --git a/include/cinatra/ylt/metric/metric.hpp b/include/cinatra/ylt/metric/metric.hpp index f37de8ca..cd395268 100644 --- a/include/cinatra/ylt/metric/metric.hpp +++ b/include/cinatra/ylt/metric/metric.hpp @@ -1,15 +1,14 @@ #pragma once #include #include -#include +#include +#include #include -#include -#include #include #include -#include +#include #include -#include +#include #include #include "async_simple/coro/Lazy.h" @@ -34,6 +33,7 @@ inline char* to_chars_float(T value, char* buffer) { #include #endif + namespace ylt::metric { enum class MetricType { Counter, @@ -50,32 +50,19 @@ struct metric_filter_options { bool is_white = true; }; -#ifdef __APPLE__ -inline 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; -} - -inline 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 - class metric_t { public: + static inline std::atomic g_user_metric_count = 0; + static inline auto g_user_metric_label_count = + new thread_local_value(std::thread::hardware_concurrency()); metric_t() = default; metric_t(MetricType type, std::string name, std::string help) : type_(type), name_(std::move(name)), help_(std::move(help)), - metric_created_time_(std::chrono::system_clock::now()) {} + metric_created_time_(std::chrono::system_clock::now()) { + g_user_metric_count.fetch_add(1, std::memory_order::relaxed); + } template metric_t(MetricType type, std::string name, std::string help, @@ -95,7 +82,9 @@ class metric_t { labels_value_.push_back(v); } } - virtual ~metric_t() {} + virtual ~metric_t() { + g_user_metric_count.fetch_sub(1, std::memory_order::relaxed); + } std::string_view name() { return name_; } @@ -129,8 +118,6 @@ class metric_t { return static_labels_; } - virtual size_t label_value_count() { return 0; } - virtual bool has_label_value(const std::string& label_value) { return std::find(labels_value_.begin(), labels_value_.end(), label_value) != labels_value_.end(); @@ -166,19 +153,6 @@ class metric_t { 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 - template T* as() { return dynamic_cast(this); @@ -221,20 +195,12 @@ class static_metric : public metric_t { using metric_t::metric_t; }; -class dynamic_metric : public metric_t { - using metric_t::metric_t; -}; - -inline auto g_user_metric_label_count = new thread_local_value(2); -inline std::atomic g_summary_failed_count = 0; -inline std::atomic g_user_metric_count = 0; +inline std::chrono::seconds ylt_label_max_age{0}; +inline std::chrono::seconds ylt_label_check_expire_duration{60}; inline std::atomic ylt_metric_capacity = 10000000; inline int64_t ylt_label_capacity = 20000000; -inline std::chrono::seconds ylt_label_max_age{0}; -inline std::chrono::seconds ylt_label_check_expire_duration{0}; - inline void set_metric_capacity(int64_t max_count) { ylt_metric_capacity = max_count; } @@ -245,7 +211,7 @@ inline void set_label_capacity(int64_t max_label_count) { inline void set_label_max_age( std::chrono::seconds max_age, - std::chrono::seconds check_duration = std::chrono::seconds(60 * 10)) { + std::chrono::seconds check_duration = std::chrono::seconds{60}) { ylt_label_max_age = max_age; ylt_label_check_expire_duration = check_duration; } diff --git a/include/cinatra/ylt/metric/metric_manager.hpp b/include/cinatra/ylt/metric/metric_manager.hpp index 8a48d5f1..ff6011d1 100644 --- a/include/cinatra/ylt/metric/metric_manager.hpp +++ b/include/cinatra/ylt/metric/metric_manager.hpp @@ -9,12 +9,12 @@ namespace ylt::metric { class manager_helper { public: static bool register_metric(auto& metric_map, auto metric) { - if (g_user_metric_count > ylt_metric_capacity) { + if (metric::metric_t::g_user_metric_count > ylt_metric_capacity) { CINATRA_LOG_ERROR << "metric count at capacity size: " - << g_user_metric_count; + << metric::metric_t::g_user_metric_count; return false; } - auto [it, r] = metric_map.try_emplace(metric->str_name(), metric); + auto&& [it, r] = metric_map.try_emplace(metric->str_name(), metric); if (!r) { CINATRA_LOG_ERROR << "duplicate registered metric name: " << metric->str_name(); @@ -28,14 +28,8 @@ class manager_helper { 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); - } + m->serialize(str); } - return str; } @@ -49,13 +43,7 @@ class manager_helper { 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); - } - + m->serialize_to_json(str); if (str.size() > start) str.append(","); } @@ -297,7 +285,6 @@ class dynamic_metric_manager { } bool register_metric(std::shared_ptr metric) { - std::unique_lock lock(mtx_); return manager_helper::register_metric(metric_map_, metric); } @@ -328,7 +315,6 @@ class dynamic_metric_manager { #endif bool remove_metric(const std::string& name) { - std::unique_lock lock(mtx_); return metric_map_.erase(name); } @@ -336,7 +322,6 @@ class dynamic_metric_manager { if (metric == nullptr) { return false; } - return remove_metric(metric->str_name()); } @@ -344,7 +329,6 @@ class dynamic_metric_manager { if (names.empty()) { return; } - for (auto& name : names) { remove_metric(name); } @@ -361,20 +345,19 @@ class dynamic_metric_manager { } void remove_label_value(const std::map& labels) { - std::unique_lock lock(mtx_); - for (auto& [_, m] : metric_map_) { - m->remove_label_value(labels); - } + metric_map_.for_each([&](auto& m) { + auto&& [_, metric] = m; + metric->remove_label_value(labels); + }); } void remove_metric_by_label( const std::map& labels) { - std::unique_lock lock(mtx_); - for (auto it = metric_map_.begin(); it != metric_map_.end();) { - auto& m = it->second; + metric_map_.erase_if([&](auto& metric) { + auto&& [_, m] = metric; const auto& labels_name = m->labels_name(); if (labels.size() > labels_name.size()) { - continue; + return false; } if (labels.size() == labels_name.size()) { @@ -384,99 +367,56 @@ class dynamic_metric_manager { label_value.push_back(i->second); } } - - std::erase_if(metric_map_, [&](auto& pair) { - return pair.second->has_label_value(label_value); - }); - if (m->has_label_value(label_value)) { - metric_map_.erase(it); - } - break; + return m->has_label_value(label_value); } else { - bool need_erase = false; - for (auto& lb_name : labels_name) { - if (auto i = labels.find(lb_name); i != labels.end()) { - if (m->has_label_value(i->second)) { - it = metric_map_.erase(it); - need_erase = true; - break; + for (auto& label : labels) { + if (auto i = std::find(labels_name.begin(), labels_name.end(), + label.first); + i != labels_name.end()) { + if (!m->has_label_value(label.second)) { + return false; } } + else { + return false; + } } - - if (!need_erase) - ++it; + return true; } - } + }); } void remove_metric_by_label_name( const std::vector& labels_name) { - std::unique_lock lock(mtx_); - for (auto& [name, m] : metric_map_) { - if (m->labels_name() == labels_name) { - metric_map_.erase(name); - break; - } - } + metric_map_.erase_one([&](auto& m) { + auto&& [name, metric] = m; + return metric->labels_name() == labels_name; + }); } void remove_metric_by_label_name(std::string_view labels_name) { - std::unique_lock lock(mtx_); - for (auto it = metric_map_.cbegin(); it != metric_map_.cend();) { - auto& names = it->second->labels_name(); - if (auto sit = std::find(names.begin(), names.end(), labels_name); - sit != names.end()) { - metric_map_.erase(it++); - } - else { - ++it; - } - } - } - - size_t metric_count() { - std::unique_lock lock(mtx_); - return metric_map_.size(); + metric_map_.erase_if([&](auto& m) { + auto&& [_, metric] = m; + auto& names = metric->labels_name(); + return std::find(names.begin(), names.end(), labels_name) != names.end(); + }); } - auto metric_map() { - std::unique_lock lock(mtx_); - return metric_map_; - } - - auto collect() { - std::vector> metrics; - { - std::unique_lock lock(mtx_); - for (auto& pair : metric_map_) { - metrics.push_back(pair.second); - } - } - return metrics; + size_t metric_count() { return metric_map_.size(); } + std::vector> collect() const { + return metric_map_.template copy>(); } template std::shared_ptr get_metric_dynamic(const std::string& name) { static_assert(std::is_base_of_v, "must be dynamic metric"); - auto map = metric_map(); - auto it = map.find(name); - if (it == map.end()) { - return nullptr; - } - return std::dynamic_pointer_cast(it->second); + return std::dynamic_pointer_cast(metric_map_.find(name)); } std::shared_ptr get_metric_by_name(std::string_view name) { - auto map = metric_map(); - auto it = map.find(name); - if (it == map.end()) { - return nullptr; - } - - return it->second; + return metric_map_.find(name); } std::vector> get_metric_by_label( @@ -491,14 +431,10 @@ class dynamic_metric_manager { std::vector> get_metric_by_label_name( const std::vector& labels_name) { - auto map = metric_map(); - std::vector> vec; - for (auto& [name, m] : map) { - if (m->labels_name() == labels_name) { - vec.push_back(m); - } - } - return vec; + return metric_map_.template copy>( + [&](auto& m) { + return m->labels_name() == labels_name; + }); } std::vector> filter_metrics_dynamic( @@ -530,21 +466,20 @@ class dynamic_metric_manager { if (timer == nullptr) { co_return; } - timer->expires_after(ylt_label_check_expire_duration); bool r = co_await timer->async_await(); if (!r) { co_return; } - - std::unique_lock lock(mtx_); - for (auto& [_, m] : metric_map_) { - m->clean_expired_label(); - } + metric_map_.for_each([](auto& metric) { + metric.second->clean_expired_label(); + }); } } - dynamic_metric_manager() { + dynamic_metric_manager() + : metric_map_( + std::min(std::thread::hardware_concurrency(), 128u)) { if (ylt_label_max_age.count() > 0) { clean_label_expired(); } @@ -552,29 +487,35 @@ class dynamic_metric_manager { std::vector> get_metric_by_label_value( const std::vector& label_value) { - auto map = metric_map(); - std::vector> vec; - for (auto& [name, m] : map) { - if (m->has_label_value(label_value)) { - vec.push_back(m); - } - } - return vec; + return metric_map_.template copy>( + [&label_value](auto& metric) { + return metric->has_label_value(label_value); + }); } void remove_metric_by_label_value( const std::vector& label_value) { - std::unique_lock lock(mtx_); - for (auto& [name, m] : metric_map_) { - if (m->has_label_value(label_value)) { - metric_map_.erase(name); - break; + metric_map_.erase_if([&](auto& metric) { + return metric.second->has_label_value(label_value); + }); + } + + template + struct my_hash { + using is_transparent = void; + std::size_t operator()(std::string_view s) const noexcept { + unsigned int hash = 0; + for (auto ch : s) { + hash = hash * seed + ch; } + return hash; } - } + }; - std::shared_mutex mtx_; - std::unordered_map> metric_map_; + util::map_sharded_t< + std::unordered_map>, + my_hash<>> + metric_map_; std::shared_ptr timer_ = nullptr; std::shared_ptr executor_ = nullptr; }; diff --git a/include/cinatra/ylt/metric/summary.hpp b/include/cinatra/ylt/metric/summary.hpp index d5a5d452..7d36c86e 100644 --- a/include/cinatra/ylt/metric/summary.hpp +++ b/include/cinatra/ylt/metric/summary.hpp @@ -1,9 +1,13 @@ #pragma once #include #include +#include +#include +#include -#include "detail/time_window_quantiles.hpp" -#include "metric.hpp" +#include "counter.hpp" +#include "dynamic_metric.hpp" +#include "summary_impl.hpp" #if __has_include("ylt/util/concurrentqueue.h") #include "ylt/util/concurrentqueue.h" #else @@ -13,129 +17,78 @@ namespace ylt::metric { #ifdef CINATRA_ENABLE_METRIC_JSON struct json_summary_metric_t { - std::map labels; - std::map quantiles; - int64_t count; + std::vector labels; + std::vector quantiles_value; + uint64_t count; double sum; }; -REFLECTION(json_summary_metric_t, labels, quantiles, count, sum); +YLT_REFL(json_summary_metric_t, labels, quantiles_value, count, sum); struct json_summary_t { - std::string name; - std::string help; - std::string type; + std::string_view name; + std::string_view help; + std::string_view type; + const std::vector& labels_name; + const std::vector& quantiles_key; std::vector metrics; }; -REFLECTION(json_summary_t, name, help, type, metrics); +YLT_REFL(json_summary_t, name, help, type, labels_name, quantiles_key, metrics); #endif -struct block_t { - std::atomic is_coro_started_ = false; - std::atomic stop_ = false; - ylt::detail::moodycamel::ConcurrentQueue sample_queue_; - std::shared_ptr quantile_values_; - std::uint64_t count_; - double sum_; -}; - class summary_t : public static_metric { public: - using Quantiles = std::vector; - summary_t(std::string name, std::string help, Quantiles quantiles, - std::chrono::milliseconds max_age = std::chrono::seconds{60}, - uint16_t age_buckets = 5) - : quantiles_{std::move(quantiles)}, - static_metric(MetricType::Summary, std::move(name), std::move(help)) { - init_no_label(max_age, age_buckets); + summary_t(std::string name, std::string help, std::vector quantiles, + std::chrono::seconds max_age = std::chrono::seconds{60}) + : static_metric(MetricType::Summary, std::move(name), std::move(help)), + quantiles_(std::move(quantiles)), + impl_(quantiles_, + std::chrono::duration_cast(max_age)) { + if (!std::is_sorted(quantiles_.begin(), quantiles_.end())) + std::sort(quantiles_.begin(), quantiles_.end()); } - summary_t(std::string name, std::string help, Quantiles quantiles, + summary_t(std::string name, std::string help, std::vector quantiles, std::map static_labels, - std::chrono::milliseconds max_age = std::chrono::seconds{60}, - uint16_t age_buckets = 5) - : quantiles_{std::move(quantiles)}, - static_metric(MetricType::Summary, std::move(name), std::move(help), - std::move(static_labels)) { - init_no_label(max_age, age_buckets); + std::chrono::seconds max_age = std::chrono::seconds{60}) + : static_metric(MetricType::Summary, std::move(name), std::move(help), + std::move(static_labels)), + quantiles_(std::move(quantiles)), + impl_(quantiles_, + std::chrono::duration_cast(max_age)) { + if (!std::is_sorted(quantiles_.begin(), quantiles_.end())) + std::sort(quantiles_.begin(), quantiles_.end()); } - ~summary_t() { - if (block_) { - block_->stop_ = true; - } - } + void observe(float value) { impl_.insert(value); } - 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++; - return; - } - block_->sample_queue_.enqueue(value); - - bool expected = false; - if (block_->is_coro_started_.compare_exchange_strong(expected, true)) { - start(block_).via(excutor_->get_executor()).start([](auto &&) { - }); - } + std::vector get_rates() { + uint64_t count; + double sum; + return get_rates(sum, count); } - - 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)); - } - }, - excutor_->get_executor()); - - co_return vec; + std::vector get_rates(uint64_t& count) { + double sum; + return get_rates(sum, count); } - - async_simple::coro::Lazy get_sum() { - auto ret = co_await coro_io::post( - [this] { - return block_->sum_; - }, - excutor_->get_executor()); - co_return ret.value(); + std::vector get_rates(double& sum) { + uint64_t count; + return get_rates(sum, count); } - async_simple::coro::Lazy get_count() { - auto ret = co_await coro_io::post( - [this] { - return block_->count_; - }, - excutor_->get_executor()); - co_return ret.value(); + std::vector get_rates(double& sum, uint64_t& count) { + return impl_.stat(sum, count); } - size_t size_approx() { return block_->sample_queue_.size_approx(); } - - async_simple::coro::Lazy serialize_async(std::string &str) override { + virtual void serialize(std::string& str) override { if (quantiles_.empty()) { - co_return; - } - - if (!has_observe_) { - co_return; + return; } - - serialize_head(str); - double sum = 0; uint64_t count = 0; - auto rates = co_await get_rates(sum, count); + auto rates = get_rates(sum, count); + if (count == 0) { + return; + } + serialize_head(str); for (size_t i = 0; i < quantiles_.size(); i++) { str.append(name_); @@ -146,7 +99,7 @@ class summary_t : public static_metric { } str.append("quantile=\""); - str.append(std::to_string(quantiles_[i].quantile)).append("\"} "); + str.append(std::to_string(quantiles_[i])).append("\"} "); str.append(std::to_string(rates[i])).append("\n"); } @@ -158,353 +111,144 @@ class summary_t : public static_metric { } #ifdef CINATRA_ENABLE_METRIC_JSON - async_simple::coro::Lazy serialize_to_json_async( - std::string &str) override { + virtual void serialize_to_json(std::string& str) override { if (quantiles_.empty()) { - co_return; - } - - if (!has_observe_) { - co_return; + 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_t summary{name_, help_, metric_name(), labels_name(), + quantiles_}; json_summary_metric_t metric; - for (size_t i = 0; i < quantiles_.size(); i++) { - for (size_t i = 0; i < labels_name_.size(); i++) { - metric.labels[labels_name_[i]] = labels_value_[i]; - } - metric.quantiles.emplace(quantiles_[i].quantile, rates[i]); + metric.quantiles_value = get_rates(metric.sum, metric.count); + if (metric.count == 0) { + return; } - - metric.sum = sum; - metric.count = count; - + metric.labels.reserve(labels_value_.size()); + for (auto& e : labels_value_) metric.labels.emplace_back(e); summary.metrics.push_back(std::move(metric)); - iguana::to_json(summary, str); } #endif - private: - template - void init_block(std::shared_ptr &block) { - block = std::make_shared(); - start(block).via(excutor_->get_executor()).start([](auto &&) { - }); - } - - void init_no_label(std::chrono::milliseconds max_age, uint16_t age_buckets) { - init_block(block_); - block_->quantile_values_ = - std::make_shared(quantiles_, max_age, age_buckets); - g_user_metric_count++; - } - - async_simple::coro::Lazy start(std::shared_ptr block) { - double sample; - size_t count = 100000; - 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; - } - } - - if (block->sample_queue_.size_approx() == 0) { - block_->is_coro_started_ = false; - if (block->sample_queue_.size_approx() == 0) { - break; - } - bool expected = false; - if (!block_->is_coro_started_.compare_exchange_strong(expected, true)) { - break; - } - - continue; - } - - co_await async_simple::coro::Yield{}; - } - - co_return; - } - - Quantiles quantiles_; // readonly - std::shared_ptr block_; - static inline std::shared_ptr excutor_ = - coro_io::create_io_context_pool(1); - bool has_observe_ = false; -}; - -template -struct summary_label_sample { - std::array labels_value; - double value; -}; - -struct sum_and_count_t { - double sum; - uint64_t count; + private: + std::vector quantiles_; + ylt::metric::detail::summary_impl<> impl_; }; -template -struct labels_block_t { - summary_t::Quantiles quantiles_; // readonly - std::chrono::milliseconds max_age_; - uint16_t age_buckets_; - std::atomic is_coro_started_ = false; - std::atomic stop_ = false; - ylt::detail::moodycamel::ConcurrentQueue> - sample_queue_; - dynamic_metric_hash_map, - std::shared_ptr> - label_quantile_values_; - dynamic_metric_hash_map, sum_and_count_t> - sum_and_count_; -}; +template +class basic_dynamic_summary + : public dynamic_metric_impl, N> { + private: + using Base = dynamic_metric_impl, N>; -template -class basic_dynamic_summary : public dynamic_metric { public: - using Quantiles = std::vector; - basic_dynamic_summary( - std::string name, std::string help, Quantiles quantiles, + std::string name, std::string help, std::vector quantiles, std::array labels_name, - std::chrono::milliseconds max_age = std::chrono::seconds{60}, - uint16_t age_buckets = 5) - : dynamic_metric(MetricType::Summary, std::move(name), std::move(help), - std::move(labels_name)) { - labels_block_ = std::make_shared>(); - labels_block_->quantiles_ = std::move(quantiles); - labels_block_->max_age_ = max_age; - labels_block_->age_buckets_ = age_buckets; - - start(labels_block_).via(excutor_->get_executor()).start([](auto &&) { - }); - - g_user_metric_count++; + std::chrono::milliseconds max_age = std::chrono::seconds{60}) + : Base(MetricType::Summary, std::move(name), std::move(help), + std::move(labels_name)), + quantiles_(std::move(quantiles)), + max_age_(max_age) { + if (!std::is_sorted(quantiles_.begin(), quantiles_.end())) + std::sort(quantiles_.begin(), quantiles_.end()); } - ~basic_dynamic_summary() { - if (labels_block_) { - labels_block_->stop_ = true; - } + void observe(const std::array& labels_value, float value) { + Base::try_emplace(labels_value, quantiles_).first->value.insert(value); } - 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++; - return; - } - labels_block_->sample_queue_.enqueue({std::move(labels_value), value}); - - bool expected = false; - if (labels_block_->is_coro_started_.compare_exchange_strong(expected, - true)) { - start(labels_block_).via(excutor_->get_executor()).start([](auto &&) { - }); - } + std::vector get_rates(const std::array& labels_value) { + double sum; + uint64_t count; + return Base::try_emplace(labels_value, quantiles_) + .first->value.get_rates(sum, count); } - size_t size_approx() { return labels_block_->sample_queue_.size_approx(); } - - size_t label_value_count() override { - auto block = labels_block_; - return async_simple::coro::syncAwait(coro_io::post([block] { - return block->sum_and_count_.size(); - })) - .value(); + std::vector get_rates(const std::array& labels_value, + uint64_t& count) { + double sum; + return Base::try_emplace(labels_value, quantiles_) + .first->value.get_rates(sum, count); } - async_simple::coro::Lazy> get_rates( - const std::array &labels_value, double &sum, - uint64_t &count) { - std::vector vec; - if (labels_block_->quantiles_.empty()) { - co_return std::vector{}; - } - - 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_->sum_and_count_[labels_value].sum; - count = labels_block_->sum_and_count_[labels_value].count; - for (const auto &quantile : labels_block_->quantiles_) { - vec.push_back(it->second->get(quantile.quantile)); - } - }, - excutor_->get_executor()); - - co_return vec; - } - - async_simple::coro::Lazy serialize_async(std::string &str) override { - co_await serialize_async_with_label(str); - } - -#ifdef CINATRA_ENABLE_METRIC_JSON - async_simple::coro::Lazy serialize_to_json_async( - std::string &str) override { - co_await serialize_to_json_with_label_async(str); + std::vector get_rates(const std::array& labels_value, + double& sum) { + uint64_t count; + return Base::try_emplace(labels_value, quantiles_) + .first->value.get_rates(sum, count); } -#endif - private: - async_simple::coro::Lazy start( - std::shared_ptr> label_block) { - summary_label_sample sample; - size_t count = 100000; - while (!label_block->stop_) { - size_t index = 0; - 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( - label_block->quantiles_, label_block->max_age_, - label_block->age_buckets_); - } - - ptr->insert(sample.value); - - label_block->sum_and_count_[sample.labels_value].count += 1; - label_block->sum_and_count_[sample.labels_value].sum += sample.value; - index++; - if (index == count) { - break; - } - } - - co_await async_simple::coro::Yield{}; - if (label_block->sample_queue_.size_approx() == 0) { - label_block->is_coro_started_ = false; - if (label_block->sample_queue_.size_approx() == 0) { - break; - } - - bool expected = false; - if (!label_block->is_coro_started_.compare_exchange_strong(expected, - true)) { - break; - } - - continue; - } - co_await async_simple::coro::Yield{}; - } - - co_return; + std::vector get_rates(const std::array& labels_value, + double& sum, uint64_t& count) { + return Base::try_emplace(labels_value, quantiles_) + .first->value.stat(sum, count); } - async_simple::coro::Lazy serialize_async_with_label(std::string &str) { - if (labels_block_->quantiles_.empty()) { - co_return; - } - - if (!has_observe_) { - co_return; - } - - serialize_head(str); - - auto sum_map = co_await coro_io::post( - [this] { - return labels_block_->sum_and_count_; - }, - excutor_->get_executor()); - - for (auto &[labels_value, _] : 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 < labels_block_->quantiles_.size(); i++) { - str.append(name_); + virtual void serialize(std::string& str) override { + double sum = 0; + uint64_t count = 0; + auto map = Base::copy(); + for (auto& e : map) { + auto& labels_value = e->label; + auto& summary_value = e->value; + auto rates = summary_value.stat(sum, count); + for (size_t i = 0; i < quantiles_.size(); i++) { + str.append(Base::name_); str.append("{"); - build_label_string(str, labels_name_, labels_value); + Base::build_label_string(str, Base::labels_name_, labels_value); str.append(","); str.append("quantile=\""); - str.append(std::to_string(labels_block_->quantiles_[i].quantile)) - .append("\"} "); + str.append(std::to_string(quantiles_[i])).append("\"} "); str.append(std::to_string(rates[i])).append("\n"); } - - str.append(name_).append("_sum "); + str.append(Base::name_).append("_sum "); str.append("{"); - build_label_string(str, labels_name_, labels_value); + Base::build_label_string(str, Base::labels_name_, labels_value); str.append("} "); str.append(std::to_string(sum)).append("\n"); - str.append(name_).append("_count "); + str.append(Base::name_).append("_count "); str.append("{"); - build_label_string(str, labels_name_, labels_value); + Base::build_label_string(str, Base::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 (labels_block_->quantiles_.empty()) { - co_return; - } - - if (!has_observe_) { - co_return; + virtual void serialize_to_json(std::string& str) override { + auto map = Base::copy(); + if (map.empty()) { + return; } - - auto sum_map = co_await coro_io::post( - [this] { - return labels_block_->sum_and_count_; - }, - excutor_->get_executor()); - - json_summary_t summary{name_, help_, std::string(metric_name())}; - - for (auto &[labels_value, _] : sum_map.value()) { - json_summary_metric_t metric; + json_summary_t summary{Base::name_, Base::help_, Base::metric_name(), + Base::labels_name(), quantiles_}; + summary.metrics.reserve(map.size()); + for (size_t i = 0; i < map.size(); ++i) { + auto& labels_value = map[i]->label; + auto& summary_value = map[i]->value; double sum = 0; uint64_t count = 0; - auto rates = co_await get_rates(labels_value, sum, count); + auto rates = summary_value.stat(sum, count); + if (count == 0) + continue; + summary.metrics.emplace_back(); + json_summary_metric_t& metric = summary.metrics.back(); metric.count = count; metric.sum = sum; - for (size_t i = 0; i < labels_block_->quantiles_.size(); i++) { - for (size_t i = 0; i < labels_value.size(); i++) { - metric.labels[labels_name_[i]] = labels_value[i]; - } - metric.quantiles.emplace(labels_block_->quantiles_[i].quantile, - rates[i]); - } - - summary.metrics.push_back(std::move(metric)); + metric.quantiles_value = std::move(rates); + metric.labels.reserve(labels_value.size()); + for (auto& e : labels_value) metric.labels.emplace_back(e); } iguana::to_json(summary, str); } #endif - std::shared_ptr> labels_block_; - static inline std::shared_ptr excutor_ = - coro_io::create_io_context_pool(1); - bool has_observe_ = false; + private: + std::vector quantiles_; + std::chrono::milliseconds max_age_; }; using dynamic_summary_1 = basic_dynamic_summary<1>; diff --git a/include/cinatra/ylt/metric/summary_impl.hpp b/include/cinatra/ylt/metric/summary_impl.hpp new file mode 100644 index 00000000..a6935c78 --- /dev/null +++ b/include/cinatra/ylt/metric/summary_impl.hpp @@ -0,0 +1,347 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ylt::metric::detail { + +template +class summary_impl { + constexpr static uint32_t decode_impl(uint16_t float16_value) { + float16_value <<= (8 - frac_bit); + uint32_t sign = float16_value >> 15; + uint32_t exponent = (float16_value >> 8) & 0x7F; + uint32_t fraction = (float16_value & 0xFF); + uint32_t float32_value; + if (exponent == 0) { + /*discard Denormals, in encode they may not correct so we just decode it + * as zero */ + float32_value = (sign << 31); + } + else if (exponent == 0x7F) { + /* Inf or NaN */ + /* we just use return it as value 2^64 */ + float32_value = (sign << 31) | ((127 + (127 - 63)) << 23); + } + else { + /* ordinary number */ + float32_value = + (sign << 31) | ((exponent + (127 - 63)) << 23) | (fraction << 15); + } + return float32_value; + } + + constexpr static auto generate_decode_table() { + constexpr size_t bucket_size = + 1 << (frac_bit + 1 /*sign bit*/ + 7 /*exp bit*/); + std::array table{}; + for (uint16_t i = 0; i < bucket_size; ++i) { + table[i] = decode_impl(i); + } + return table; + }; + + static auto& get_decode_table() { + static constexpr auto table = generate_decode_table(); + return table; + }; + + /*my float16: | 1bit positive/negative flag | 6bit exp | 9bit frac |*/ + static_assert(frac_bit < 8); + static constexpr float float16_max = (1ull << 63) * 2.0f; // 2^64 + + static uint16_t encode(float flt) { + unsigned int& fltInt32 = *(unsigned int*)&flt; + if (std::abs(flt) >= float16_max || std::isnan(flt)) { + flt = (fltInt32 & 0x8000'0000) ? (-float16_max) : (float16_max); + } + unsigned short fltInt16; + fltInt16 = (fltInt32 >> 31) << 7; /*float32 flag: 1bit*/ + unsigned short tmp = (fltInt32 >> 23) & 0xff; /*float32 exp: 8bit*/ + + tmp = (tmp - 0x40) & ((unsigned int)((int)(0x40 - tmp) >> 6) >> 25); + fltInt16 = (fltInt16 | tmp) << 8; + + // this step cause error denormals for flt<2^-63, but we decode it as zero + // later + fltInt16 |= (fltInt32 >> 15) & 0xff; + + auto i = fltInt16 >> (8 - frac_bit); + auto j = decode_impl(i); + return i; + } + + static float decode(uint16_t float16_value) { + static_assert(frac_bit < 8); + return *(float*)&(get_decode_table()[float16_value]); + } + + static constexpr inline size_t bucket_size = + 1 << (frac_bit + 1 /*sign bit*/ + 7 /*exp bit*/); + + static constexpr size_t piece_cnt = 1 << 7; + + struct data_t { + static constexpr size_t piece_size = bucket_size / piece_cnt; + using piece_t = std::array, piece_size>; + + std::atomic& operator[](std::size_t index) { + piece_t* piece = arr[index / piece_size]; + if (piece == nullptr) { + auto ptr = new piece_t{}; + if (!arr[index / piece_size].compare_exchange_strong(piece, ptr)) { + delete ptr; + } + return (*arr[index / piece_size].load())[index % piece_size]; + } + else { + return (*piece)[index % piece_size]; + } + } + void refresh() { + for (auto& piece_ptr : arr) { + if (piece_ptr) { + for (auto& e : *piece_ptr) { + e.store(0, std::memory_order::relaxed); + } + } + } + } + static uint16_t get_ordered_index(int16_t raw_index) { + return (raw_index >= bucket_size / 2) ? (bucket_size / 2 - 1 - raw_index) + : (raw_index); + } + static uint16_t get_raw_index(int16_t ordered_index) { + return (ordered_index < 0) ? (bucket_size / 2 - 1 - ordered_index) + : (ordered_index); + } + template + void stat_impl(uint64_t& count, + std::vector>& result, int i) { + auto piece = arr[i].load(std::memory_order_relaxed); + if (piece) { + if constexpr (inc_order) { + for (int j = 0; j < piece->size(); ++j) { + auto value = (*piece)[j].load(std::memory_order_relaxed); + if (value) { + result.emplace_back(get_ordered_index(i * piece_size + j), value); + count += value; + } + } + } + else { + for (int j = piece->size() - 1; j >= 0; --j) { + auto value = (*piece)[j].load(std::memory_order_relaxed); + if (value) { + result.emplace_back(get_ordered_index(i * piece_size + j), value); + count += value; + } + } + } + } + } + void stat(uint64_t& count, + std::vector>& result) { + for (int i = piece_cnt - 1; i >= piece_cnt / 2; --i) { + stat_impl(count, result, i); + } + for (int i = 0; i < piece_cnt / 2; ++i) { + stat_impl(count, result, i); + } + } + + ~data_t() { + for (auto& e : arr) { + delete e; + } + } + + std::array, piece_cnt> arr; + // fixed_thread_local_value cnt; + }; + + data_t& get_data() { + data_t* data = data_[frontend_data_index_]; + if (data == nullptr) [[unlikely]] { + auto pointer = new data_t{}; + if (!data_[frontend_data_index_].compare_exchange_strong(data, pointer)) { + delete pointer; + } + return *data_[frontend_data_index_]; + } + else { + return *data; + } + } + + static inline const unsigned long ms_count = + std::chrono::steady_clock::duration{std::chrono::milliseconds{1}}.count(); + + constexpr static unsigned int near_uint32_max = 4290000000U; + + void increase(data_t& arr, uint16_t pos) { + if (arr[pos].fetch_add(1, std::memory_order::relaxed) > + near_uint32_max) /*no overflow*/ [[likely]] { + arr[pos].fetch_sub(1, std::memory_order::relaxed); + int upper = (pos < bucket_size / 2) ? (bucket_size / 2) : (bucket_size); + int lower = (pos < bucket_size / 2) ? (0) : (bucket_size / 2); + for (int delta = 1, lim = (std::max)(upper - pos, pos - lower + 1); + delta < lim; ++delta) { + if (pos + delta < upper) { + if (arr[pos + delta].fetch_add(1, std::memory_order::relaxed) <= + near_uint32_max) { + break; + } + arr[pos + delta].fetch_sub(1, std::memory_order::relaxed); + } + if (pos - delta >= lower) { + if (arr[pos - delta].fetch_add(1, std::memory_order::relaxed) <= + near_uint32_max) { + break; + } + arr[pos - delta].fetch_sub(1, std::memory_order::relaxed); + } + } + } + } + + struct data_copy_t { + std::vector> arr[2]; + int index[2] = {}, smaller_one; + void init() { + if (arr[0][0] <= arr[1][0]) { + smaller_one = 0; + } + else { + smaller_one = 1; + } + } + void inc() { + index[smaller_one]++; + if (arr[0][index[0]] <= arr[1][index[1]]) { + smaller_one = 0; + } + else { + smaller_one = 1; + } + } + int16_t value() { return arr[smaller_one][index[smaller_one]].first; } + uint32_t count() { return arr[smaller_one][index[smaller_one]].second; } + }; + + public: + void refresh() { + if (refresh_time_.count() <= 0) { + return; + } + uint64_t old_tp = tp_; + auto new_tp = std::chrono::steady_clock::now().time_since_epoch().count(); + auto ms = (new_tp - old_tp) / ms_count; + if (; ms >= refresh_time_.count()) [[unlikely]] { + if (tp_.compare_exchange_strong(old_tp, new_tp)) { + if (ms >= 2 * refresh_time_.count()) { + for (auto& data : data_) { + if (data != nullptr) { + data.load()->refresh(); + } + } + } + else { + auto pos = frontend_data_index_ ^ 1; + if (auto data = data_[pos].load(); data != nullptr) { + data->refresh(); + } + frontend_data_index_ = pos; + } + } + } + } + void insert(float value) { + refresh(); + auto& data = get_data(); + increase(data, encode(value)); + return; + } + + std::vector stat(double& sum, uint64_t& count) { + refresh(); + count = 0; + sum = 0; + data_copy_t data_copy; + { + data_t* ar[2] = {data_[0], data_[1]}; + if (ar[0] == nullptr && ar[1] == nullptr) [[unlikely]] { + return std::vector(rate_.size(), 0.0f); + } + if (ar[0]) { + ar[0]->stat(count, data_copy.arr[0]); + } + if (ar[1]) { + ar[1]->stat(count, data_copy.arr[1]); + } + } + if (count == 0) { + return std::vector(rate_.size(), 0); + } + uint64_t count_now = 0; + data_copy.arr[0].emplace_back(bucket_size / 2, 0); + data_copy.arr[1].emplace_back(bucket_size / 2, 0); + data_copy.init(); + std::vector result; + result.reserve(rate_.size()); + float v = -float16_max; + for (double e : rate_) { + if (std::isnan(e) || e < 0) { + result.push_back(v); + continue; + } + else if (e > 1) [[unlikely]] { + e = 1; + } + auto target_count = std::min(e * count, count); + while (true) { + if (target_count <= count_now) [[unlikely]] { + result.push_back(v); + break; + } + auto tmp = data_copy.count(); + count_now += tmp; + v = decode(data_t::get_raw_index(data_copy.value())); + sum += v * tmp; + data_copy.inc(); + } + } + while (data_copy.value() < bucket_size / 2) { + sum += + decode(data_t::get_raw_index(data_copy.value())) * data_copy.count(); + data_copy.inc(); + } + return result; + } + + summary_impl(std::vector& rate, + std::chrono::seconds refresh_time = std::chrono::seconds{0}) + : rate_(rate), + refresh_time_(refresh_time.count() * 1000 / 2), + tp_(std::chrono::steady_clock::now().time_since_epoch().count()){}; + + ~summary_impl() { + for (auto& data : data_) { + delete data; + } + } + + private: + const std::chrono::milliseconds refresh_time_; + std::atomic tp_; + std::vector& rate_; + std::array, 2> data_; + std::atomic frontend_data_index_; +}; +} // namespace ylt::metric::detail diff --git a/include/cinatra/ylt/metric/system_metric.hpp b/include/cinatra/ylt/metric/system_metric.hpp index 088e6e60..97e1c26d 100644 --- a/include/cinatra/ylt/metric/system_metric.hpp +++ b/include/cinatra/ylt/metric/system_metric.hpp @@ -2,16 +2,6 @@ #if defined(__GNUC__) #include #include -#endif - -#if defined(WIN32) -#include -#include -#include - -// Link with Psapi.lib -#pragma comment(lib, "Psapi.lib") -#endif #include #include @@ -83,175 +73,6 @@ inline int read_command_output_through_popen(std::ostream& os, } #endif -#if defined(WIN32) -typedef struct timeval { - long tv_sec; - long tv_usec; -} timeval; - -inline int gettimeofday(struct timeval* tp, struct timezone* tzp) { - // Note: some broken versions only have 8 trailing zero's, the correct epoch - // has 9 trailing zero's This magic number is the number of 100 nanosecond - // intervals since January 1, 1601 (UTC) until 00:00:00 January 1, 1970 - static const uint64_t epoch = ((uint64_t)116444736000000000ULL); - - SYSTEMTIME system_time; - FILETIME file_time; - uint64_t time; - - GetSystemTime(&system_time); - SystemTimeToFileTime(&system_time, &file_time); - time = ((uint64_t)file_time.dwLowDateTime); - time += ((uint64_t)file_time.dwHighDateTime) << 32; - - tp->tv_sec = (long)((time - epoch) / 10000000L); - tp->tv_usec = (long)(system_time.wMilliseconds * 1000); - return 0; -} - -#define RUSAGE_SELF 0 -#define RUSAGE_CHILDREN (-1) - -struct rusage { - struct timeval ru_utime; /* user time used */ - struct timeval ru_stime; /* system time used */ -}; - -inline int getrusage(int who, struct rusage* rusage) { - FILETIME starttime; - FILETIME exittime; - FILETIME kerneltime; - FILETIME usertime; - ULARGE_INTEGER li; - - if (who != RUSAGE_SELF) { - /* Only RUSAGE_SELF is supported in this implementation for now */ - errno = EINVAL; - return -1; - } - - if (rusage == (struct rusage*)NULL) { - errno = EFAULT; - return -1; - } - memset(rusage, 0, sizeof(struct rusage)); - if (GetProcessTimes(GetCurrentProcess(), &starttime, &exittime, &kerneltime, - &usertime) == 0) { - return -1; - } - - /* Convert FILETIMEs (0.1 us) to struct timeval */ - memcpy(&li, &kerneltime, sizeof(FILETIME)); - li.QuadPart /= 10L; /* Convert to microseconds */ - rusage->ru_stime.tv_sec = li.QuadPart / 1000000L; - rusage->ru_stime.tv_usec = li.QuadPart % 1000000L; - - memcpy(&li, &usertime, sizeof(FILETIME)); - li.QuadPart /= 10L; /* Convert to microseconds */ - rusage->ru_utime.tv_sec = li.QuadPart / 1000000L; - rusage->ru_utime.tv_usec = li.QuadPart % 1000000L; - - return 0; -} - -inline SIZE_T get_shared_memory_size(HANDLE h_process) { - MEMORY_BASIC_INFORMATION mbi; - SIZE_T base_address = 0; - SIZE_T shared_memory_size = 0; - - while (VirtualQueryEx(h_process, (LPCVOID)base_address, &mbi, sizeof(mbi))) { - if (mbi.State == MEM_COMMIT) { - if (mbi.Type == MEM_MAPPED || mbi.Type == MEM_IMAGE) { - shared_memory_size += mbi.RegionSize; - } - } - base_address = (SIZE_T)mbi.BaseAddress + mbi.RegionSize; - } - - return shared_memory_size; -} - -inline DWORD getppid() { - HANDLE h_snapshot; - PROCESSENTRY32 pe32; - DWORD ppid = 0, pid = GetCurrentProcessId(); - - h_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - try { - if (h_snapshot == INVALID_HANDLE_VALUE) - return ppid; - - ZeroMemory(&pe32, sizeof(pe32)); - pe32.dwSize = sizeof(pe32); - if (!Process32First(h_snapshot, &pe32)) - return ppid; - - do { - if (pe32.th32ProcessID == pid) { - ppid = pe32.th32ParentProcessID; - break; - } - } while (Process32Next(h_snapshot, &pe32)); - - } catch (...) { - if (h_snapshot != INVALID_HANDLE_VALUE) - CloseHandle(h_snapshot); - } - - if (h_snapshot != INVALID_HANDLE_VALUE) - CloseHandle(h_snapshot); - - return ppid; -} - -inline DWORD get_thread_number(DWORD processId) { - DWORD thread_count = 0; - HANDLE snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - - if (snapshot_handle == INVALID_HANDLE_VALUE) { - std::cerr << "Failed to create snapshot. Error code: " << GetLastError() - << std::endl; - return 0; - } - - THREADENTRY32 threadEntry; - threadEntry.dwSize = sizeof(THREADENTRY32); - - if (Thread32First(snapshot_handle, &threadEntry)) { - do { - if (threadEntry.th32OwnerProcessID == processId) { - ++thread_count; - } - } while (Thread32Next(snapshot_handle, &threadEntry)); - } - else { - std::cerr << "Failed to retrieve thread information. Error code: " - << GetLastError() << std::endl; - } - - CloseHandle(snapshot_handle); - return thread_count; -} - -inline DWORD get_process_group(HANDLE process_handle) { - DWORD_PTR process_affinity_mask; - DWORD_PTR system_affinity_mask; - - if (GetProcessAffinityMask(process_handle, &process_affinity_mask, - &system_affinity_mask)) { - // Output the processor group information - // Process Affinity Mask - DWORD grop_id = process_affinity_mask; - return grop_id; - } - else { - std::cerr << "Failed to get process affinity mask. Error code: " - << GetLastError() << std::endl; - return 0; - } -} -#endif - inline int64_t last_time_us = 0; inline int64_t last_sys_time_us = 0; inline int64_t last_user_time_us = 0; @@ -324,9 +145,7 @@ inline void stat_memory() { long virtual_size = 0; long resident = 0; long share = 0; -#if defined(__GNUC__) static long page_size = sysconf(_SC_PAGE_SIZE); -#endif #if defined(__APPLE__) static pid_t pid = getpid(); @@ -350,33 +169,9 @@ inline void stat_memory() { file >> virtual_size >> resident >> share; #endif -#if defined(WIN32) - DWORD current_process = GetCurrentProcessId(); - // open process - HANDLE h_process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, - FALSE, current_process); - if (h_process == NULL) { - virtual_size = 0; - resident = 0; - share = 0; - } - PROCESS_MEMORY_COUNTERS pmc; - if (GetProcessMemoryInfo(h_process, &pmc, sizeof(pmc))) { - virtual_size = pmc.PagefileUsage; - resident = pmc.WorkingSetSize; - } - share = get_shared_memory_size(h_process); - - CloseHandle(h_process); - - process_memory_virtual->update(virtual_size); - process_memory_resident->update(resident); - process_memory_shared->update(share); -#else process_memory_virtual->update(virtual_size * page_size); process_memory_resident->update(resident * page_size); process_memory_shared->update(share * page_size); -#endif } struct ProcIO { @@ -404,9 +199,10 @@ inline void stat_io() { "ylt_process_io_write_second"); ProcIO s{}; -#if defined(__GUNC__) +#if defined(__APPLE__) +#else auto stream_file = - std::shared_ptr(fopen("/proc/self/io", "r"), [](FILE* ptr) { + std::shared_ptr(fopen("/proc/self/io", "r"), [](FILE *ptr) { fclose(ptr); }); if (stream_file == nullptr) { @@ -421,30 +217,6 @@ inline void stat_io() { } #endif -#if defined(WIN32) - DWORD current_process_id = GetCurrentProcessId(); - // open process - HANDLE h_process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, - FALSE, current_process_id); - if (h_process == NULL) { - s.rchar = 0; - s.wchar = 0; - s.syscr = 0; - s.syscw = 0; - } - else { - IO_COUNTERS io_counters = {0}; - if (GetProcessIoCounters(h_process, &io_counters)) { - s.rchar = io_counters.ReadOperationCount; - s.wchar = io_counters.WriteOperationCount; - s.syscr = io_counters.ReadOperationCount; - s.syscw = io_counters.WriteOperationCount; - } - } - - CloseHandle(h_process); -#endif - process_io_read_bytes_second->update(s.rchar); process_io_write_bytes_second->update(s.wchar); process_io_read_second->update(s.syscr); @@ -564,7 +336,7 @@ inline void process_status() { if (read_command_output_through_popen(oss, cmdbuf) != 0) { return; } - const std::string& result = oss.str(); + const std::string &result = oss.str(); if (sscanf(result.c_str(), "%d %d %d %d" "%d %u %ld %ld", @@ -572,16 +344,6 @@ inline void process_status() { &stat.flags, &stat.priority, &stat.nice) != 8) { return; } -#elif defined(WIN32) - stat.pid = GetCurrentProcessId(); - stat.ppid = getppid(); - - HANDLE h_process = - OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, stat.pid); - stat.priority = GetPriorityClass(h_process); - stat.num_threads = get_thread_number(stat.pid); - stat.pgrp = get_process_group(h_process); - CloseHandle(h_process); #endif process_uptime->inc(); process_priority->update(stat.priority); @@ -595,17 +357,12 @@ inline void stat_metric() { static auto user_metric_count = system_metric_manager::instance().get_metric_static( "ylt_user_metric_count"); - user_metric_count->update(g_user_metric_count); + user_metric_count->update(metric::metric_t::g_user_metric_count); static auto user_metric_label_count = system_metric_manager::instance().get_metric_static( "ylt_user_metric_labels"); - user_metric_label_count->update(g_user_metric_label_count->value()); - - static auto ylt_summary_failed_count = - system_metric_manager::instance().get_metric_static( - "ylt_summary_failed_count"); - ylt_summary_failed_count->update(g_summary_failed_count); + user_metric_label_count->update(metric_t::g_user_metric_label_count->value()); } inline void ylt_stat() { @@ -693,4 +450,5 @@ inline bool start_system_metric() { return true; } -} // namespace ylt::metric \ No newline at end of file +} // namespace ylt::metric +#endif \ No newline at end of file diff --git a/include/cinatra/ylt/metric/thread_local_value.hpp b/include/cinatra/ylt/metric/thread_local_value.hpp index b73789dd..8aeb0036 100644 --- a/include/cinatra/ylt/metric/thread_local_value.hpp +++ b/include/cinatra/ylt/metric/thread_local_value.hpp @@ -11,8 +11,42 @@ inline uint32_t get_round_index(uint32_t size) { static thread_local uint32_t index = round++; return index % size; } + +namespace detail { +template +static value_type inc_impl(std::atomic &obj, value_type value) { + if constexpr (!requires { + std::atomic{}.fetch_add(value_type{}); + }) { + value_type v = obj.load(std::memory_order::relaxed); + while (!std::atomic_compare_exchange_weak(&obj, &v, v + value)) + ; + return v; + } + else { + return obj.fetch_add(value, std::memory_order::relaxed); + } +} +template +static value_type dec_impl(std::atomic &obj, value_type value) { + if constexpr (!requires { + std::atomic{}.fetch_add(value_type{}); + }) { + value_type v = obj.load(std::memory_order::relaxed); + while (!std::atomic_compare_exchange_weak(&obj, &v, v - value)) + ; + return v; + } + else { + return obj.fetch_sub(value, std::memory_order::relaxed); + } +} +} // namespace detail + template class thread_local_value { + friend class metric_t; + public: thread_local_value(uint32_t dupli_count = std::thread::hardware_concurrency()) : duplicates_(dupli_count) {} @@ -56,15 +90,15 @@ class thread_local_value { return *this; } - void inc(value_type value = 1) { local_value() += value; } + void inc(value_type value = 1) { detail::inc_impl(local_value(), value); } - void dec(value_type value = 1) { local_value() -= value; } + void dec(value_type value = 1) { detail::dec_impl(local_value(), value); } value_type update(value_type value = 1) { - value_type val = get_value(0).exchange(value); + value_type val = get_value(0).exchange(value, std::memory_order::relaxed); for (size_t i = 1; i < duplicates_.size(); i++) { if (duplicates_[i]) { - val += duplicates_[i].load()->exchange(0); + val += duplicates_[i].load()->exchange(0, std::memory_order::relaxed); } } return val; @@ -89,7 +123,7 @@ class thread_local_value { return *duplicates_[index]; } - value_type value() { + value_type value() const { value_type val = 0; for (auto &t : duplicates_) { if (t) { @@ -99,14 +133,8 @@ class thread_local_value { return val; } - void set_created_time(std::chrono::system_clock::time_point tm) { - created_time_ = tm; - } - - auto get_created_time() { return created_time_; } - private: std::vector *>> duplicates_; std::chrono::system_clock::time_point created_time_{}; }; -} // namespace ylt::metric +} // namespace ylt::metric \ No newline at end of file diff --git a/include/cinatra/ylt/util/map_sharded.hpp b/include/cinatra/ylt/util/map_sharded.hpp new file mode 100644 index 00000000..c1c255a3 --- /dev/null +++ b/include/cinatra/ylt/util/map_sharded.hpp @@ -0,0 +1,225 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ylt::util { +namespace internal { +template +class map_lock_t { + public: + using key_type = typename Map::key_type; + using value_type = typename Map::value_type; + using mapped_type = typename Map::mapped_type; + map_lock_t() : mtx_(std::make_unique()) {} + + std::shared_ptr find( + const key_type& key) const { + std::lock_guard lock(*mtx_); + if (!map_) [[unlikely]] { + return nullptr; + } + auto it = map_->find(key); + return it->second; + } + + template + std::pair, bool> + try_emplace_with_op(const key_type& key, Op&& op, Args&&... args) { + std::lock_guard lock(*mtx_); + auto result = visit_map().try_emplace(key, std::forward(args)...); + op(result); + return {result.first->second, result.second}; + } + + size_t erase(const key_type& key) { + std::lock_guard lock(*mtx_); + if (!map_) [[unlikely]] { + return 0; + } + return map_->erase(key); + } + + template + size_t erase_if(Func&& op) { + std::lock_guard guard(*mtx_); + if (!map_) [[unlikely]] { + return 0; + } + return std::erase_if(*map_, std::forward(op)); + } + + template + bool for_each(Func&& op) { + std::lock_guard guard(*mtx_); + if (!map_) [[unlikely]] { + return true; + } + for (auto& e : *map_) { + if constexpr (requires { op(e) == true; }) { + if (!op(e)) { + break; + return false; + } + } + else { + op(e); + } + } + return true; + } + + template + bool for_each(Func&& op) const { + std::lock_guard guard(*mtx_); + if (!map_) [[unlikely]] { + return true; + } + for (const auto& e : *map_) { + if constexpr (requires { op(e) == true; }) { + if (!op(e)) { + break; + return false; + } + } + else { + op(e); + } + } + return true; + } + + private: + Map& visit_map() { + if (!map_) [[unlikely]] { + map_ = std::make_unique(); + } + return *map_; + } + + std::unique_ptr mtx_; + std::unique_ptr map_; +}; +} // namespace internal + +template +class map_sharded_t { + public: + using key_type = typename Map::key_type; + using value_type = typename Map::value_type; + using mapped_type = typename Map::mapped_type; + map_sharded_t(size_t shard_num) : shards_(shard_num) {} + + template + std::pair, bool> + try_emplace(KeyType&& key, Args&&... args) { + return try_emplace_with_op( + std::forward(key), + [](auto&&) { + }, + std::forward(args)...); + } + + template + std::pair, bool> + try_emplace_with_op(const key_type& key, Op&& func, Args&&... args) { + auto ret = get_sharded(Hash{}(key)) + .try_emplace_with_op(key, std::forward(func), + std::forward(args)...); + if (ret.second) { + size_.fetch_add(1); + } + return ret; + } + + size_t size() const { // this value is approx + int64_t val = size_.load(); + if (val < 0) [[unlikely]] { // may happen when insert & deleted frequently + val = 0; + } + return val; + } + + std::shared_ptr find( + const key_type& key) const { + return get_sharded(Hash{}(key)).find(key); + } + + size_t erase(const key_type& key) { + auto result = get_sharded(Hash{}(key)).erase(key); + if (result) { + size_.fetch_sub(result); + } + return result; + } + + template + size_t erase_if(Func&& op) { + auto total = 0; + for (auto& map : shards_) { + auto result = map.erase_if(std::forward(op)); + total += result; + size_.fetch_sub(result); + } + return total; + } + + template + size_t erase_one(Func&& op) { + auto total = 0; + for (auto& map : shards_) { + auto result = map.erase_if(std::forward(op)); + if (result) { + total += result; + size_.fetch_sub(result); + break; + } + } + return total; + } + + template + void for_each(Func&& op) { + for (auto& map : shards_) { + if (!map.for_each(op)) + break; + } + } + + template + std::vector copy(auto&& op) const { + std::vector ret; + ret.reserve(size()); + for (auto& map : shards_) { + map.for_each([&ret, &op](auto& e) { + if (op(e.second)) { + ret.push_back(e.second); + } + }); + } + return ret; + } + template + std::vector copy() const { + return copy([](auto&) { + return true; + }); + } + + private: + internal::map_lock_t& get_sharded(size_t hash) { + return shards_[hash % shards_.size()]; + } + const internal::map_lock_t& get_sharded(size_t hash) const { + return shards_[hash % shards_.size()]; + } + + std::vector> shards_; + std::atomic size_; +}; +} // namespace ylt::util \ No newline at end of file diff --git a/tests/test_metric.cpp b/tests/test_metric.cpp index 52750427..4e5757bd 100644 --- a/tests/test_metric.cpp +++ b/tests/test_metric.cpp @@ -91,22 +91,19 @@ TEST_CASE("serialize zero") { 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)); + "test", "help", std::vector{0.5, 0.9, 0.95, 0.99}, customMap); + summary->serialize(str); CHECK(str.empty()); #ifdef CINATRA_ENABLE_METRIC_JSON - async_simple::coro::syncAwait(summary->serialize_to_json_async(str)); + summary->serialize_to_json(str); CHECK(str.empty()); #endif summary->observe(0); - async_simple::coro::syncAwait(summary->serialize_async(str)); + summary->serialize(str); CHECK(!str.empty()); str.clear(); #ifdef CINATRA_ENABLE_METRIC_JSON - async_simple::coro::syncAwait(summary->serialize_to_json_async(str)); + summary->serialize_to_json(str); CHECK(!str.empty()); str.clear(); #endif @@ -488,10 +485,7 @@ TEST_CASE("test no lable") { { 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); + "test", "help", std::vector{0.5, 0.9, 0.95, 0.99}, customMap); summary->observe(100); } auto g_counter = g_pair.second; @@ -609,11 +603,11 @@ TEST_CASE("test counter with dynamic labels value") { std::array{"method", "code"}); CHECK(c.labels_name() == std::vector{"method", "code"}); c.inc({"GET", "200"}, 1); - auto values = c.value_map(); - CHECK(values[{"GET", "200"}].value() == 1); + + CHECK(c.value({"GET", "200"}) == 1); c.inc({"GET", "200"}, 2); - values = c.value_map(); - CHECK(values[{"GET", "200"}].value() == 3); + + CHECK(c.value({"GET", "200"}) == 3); std::string str; c.serialize(str); @@ -624,8 +618,7 @@ TEST_CASE("test counter with dynamic labels value") { c.update({"GET", "200"}, 20); std::this_thread::sleep_for(std::chrono::milliseconds(10)); - values = c.value_map(); - CHECK(values[{"GET", "200"}].value() == 20); + CHECK(c.value({"GET", "200"}) == 20); } } @@ -658,11 +651,11 @@ TEST_CASE("test gauge") { CHECK(g.labels_name() == std::vector{"method", "code", "url"}); // method, status code, url g.inc({"GET", "200", "/"}, 1); - auto values = g.value_map(); - CHECK(values[{"GET", "200", "/"}].value() == 1); + + CHECK(g.value({"GET", "200", "/"}) == 1); g.inc({"GET", "200", "/"}, 2); - values = g.value_map(); - CHECK(values[{"GET", "200", "/"}].value() == 3); + + CHECK(g.value({"GET", "200", "/"}) == 3); g.inc({"POST", "200", "/"}, 4); @@ -681,11 +674,11 @@ TEST_CASE("test gauge") { std::string::npos); g.dec({"GET", "200", "/"}, 1); - values = g.value_map(); - CHECK(values[{"GET", "200", "/"}].value() == 2); + + CHECK(g.value({"GET", "200", "/"}) == 2); g.dec({"GET", "200", "/"}, 2); - values = g.value_map(); - CHECK(values[{"GET", "200", "/"}].value() == 0); + + CHECK(g.value({"GET", "200", "/"}) == 0); } } @@ -719,9 +712,8 @@ TEST_CASE("test histogram") { } TEST_CASE("test summary") { - summary_t summary{"test_summary", - "summary help", - {{0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}}; + summary_t summary{"test_summary", "summary help", + std::vector{0.5, 0.9, 0.95, 0.99}}; std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> distr(1, 100); @@ -731,10 +723,13 @@ TEST_CASE("test summary") { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::string str; - async_simple::coro::syncAwait(summary.serialize_async(str)); + summary.serialize(str); std::cout << str; - CHECK(async_simple::coro::syncAwait(summary.get_count()) == 50); - CHECK(async_simple::coro::syncAwait(summary.get_sum()) > 0); + double sum; + uint64_t cnt; + summary.get_rates(sum, cnt); + CHECK(cnt == 50); + CHECK(sum < 1e99); 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); @@ -742,7 +737,7 @@ TEST_CASE("test summary") { #ifdef CINATRA_ENABLE_METRIC_JSON std::string str_json; - async_simple::coro::syncAwait(summary.serialize_to_json_async(str_json)); + summary.serialize_to_json(str_json); std::cout << str_json << "\n"; CHECK(str_json.find("\"0.9\":") != std::string::npos); #endif @@ -1024,9 +1019,7 @@ TEST_CASE("test get metric by static labels and label") { auto map = std::map{{"method", "GET"}, {"url", "/"}}; auto [ec1, s1] = metric_mgr::instance().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}}, + "http_req_static_summary", "help", std::vector{0.5, 0.9, 0.95, 0.99}, std::map{{"method", "GET"}, {"url", "/"}}); s1->observe(23); @@ -1036,9 +1029,7 @@ TEST_CASE("test get metric by static labels and label") { { using metric_mgr2 = static_metric_manager>; auto [ec, s2] = metric_mgr2::instance().create_metric_static( - "http_req_static_summary2", "help", - summary_t::Quantiles{ - {0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}, + "http_req_static_summary2", "help", std::vector{0.5, 0.9, 0.95, 0.99}, map); s2->observe(23); @@ -1117,9 +1108,7 @@ TEST_CASE("test get metric by dynamic labels") { auto [ec7, s1] = metric_mgr::instance().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}}, + "http_req_static_summary", "help", std::vector{0.5, 0.9, 0.95, 0.99}, std::array{"method", "url"}); s1->observe({"GET", "/"}, 23); @@ -1217,11 +1206,10 @@ TEST_CASE("test histogram serialize with static labels") { } TEST_CASE("test summary with dynamic labels") { - basic_dynamic_summary<2> summary{ - "test_summary", - "summary help", - {{0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}, - {"method", "url"}}; + basic_dynamic_summary<2> summary{"test_summary", + "summary help", + std::vector{0.5, 0.9, 0.95, 0.99}, + {"method", "url"}}; std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> distr(1, 100); @@ -1234,26 +1222,23 @@ TEST_CASE("test summary with dynamic labels") { double sum; uint64_t count; - auto rates = async_simple::coro::syncAwait( - summary.get_rates({"GET", "/"}, sum, count)); + auto rates = summary.get_rates({"GET", "/"}, sum, count); std::cout << rates.size() << "\n"; std::string str; - async_simple::coro::syncAwait(summary.serialize_async(str)); + summary.serialize(str); std::cout << str; #ifdef CINATRA_ENABLE_METRIC_JSON std::string json_str; - async_simple::coro::syncAwait(summary.serialize_to_json_async(json_str)); + summary.serialize_to_json(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}}, + "test_summary", "summary help", std::vector{0.5, 0.9, 0.95, 0.99}, std::map{{"method", "GET"}, {"url", "/"}}}; std::random_device rd; std::mt19937 gen(rd()); @@ -1266,19 +1251,19 @@ TEST_CASE("test summary with static labels") { double sum; uint64_t count; - auto rates = async_simple::coro::syncAwait(summary.get_rates(sum, count)); + auto rates = summary.get_rates(sum, count); std::cout << rates.size() << "\n"; - auto rates1 = async_simple::coro::syncAwait(summary.get_rates(sum, count)); + auto rates1 = summary.get_rates(sum, count); CHECK(rates == rates1); std::string str; - async_simple::coro::syncAwait(summary.serialize_async(str)); + summary.serialize(str); std::cout << str; #ifdef CINATRA_ENABLE_METRIC_JSON std::string json_str; - async_simple::coro::syncAwait(summary.serialize_to_json_async(json_str)); + summary.serialize_to_json(json_str); std::cout << json_str << "\n"; #endif } @@ -1488,9 +1473,9 @@ TEST_CASE("test system metric") { } TEST_CASE("test metric capacity") { - std::cout << g_user_metric_count << "\n"; + std::cout << ylt::metric::metric_t::g_user_metric_count << "\n"; using test_metric_manager = dynamic_metric_manager>; - set_metric_capacity(g_user_metric_count + 2); + set_metric_capacity(ylt::metric::metric_t::g_user_metric_count + 2); auto c = test_metric_manager::instance().create_metric_dynamic( std::string("counter"), "", std::array{});