From 3a4c07020d3ddb5fc6be4239fa14cdf0655a45bb Mon Sep 17 00:00:00 2001 From: "Zezheng.Li" Date: Thu, 26 Sep 2024 12:14:15 +0800 Subject: [PATCH 1/4] [metric] refactor metric --- include/ylt/metric/counter.hpp | 356 ++++++---------------- include/ylt/metric/dynamic_metric.hpp | 146 +++++++++ include/ylt/metric/gauge.hpp | 45 +-- include/ylt/metric/histogram.hpp | 34 +-- include/ylt/metric/metric.hpp | 53 +--- include/ylt/metric/metric_manager.hpp | 4 +- include/ylt/metric/summary.hpp | 99 +++--- include/ylt/metric/summary_impl.hpp | 4 +- include/ylt/metric/system_metric.hpp | 4 +- include/ylt/metric/thread_local_value.hpp | 52 +++- src/metric/benchmark/bench.hpp | 275 +++++++++++++++++ src/metric/benchmark/main.cpp | 290 +++--------------- src/metric/tests/CMakeLists.txt | 3 + src/metric/tests/parallel_test.cpp | 13 + src/metric/tests/test_metric.cpp | 25 +- 15 files changed, 705 insertions(+), 698 deletions(-) create mode 100644 include/ylt/metric/dynamic_metric.hpp create mode 100644 src/metric/benchmark/bench.hpp create mode 100644 src/metric/tests/parallel_test.cpp diff --git a/include/ylt/metric/counter.hpp b/include/ylt/metric/counter.hpp index d7f29e9ec..d1d801ba1 100644 --- a/include/ylt/metric/counter.hpp +++ b/include/ylt/metric/counter.hpp @@ -1,11 +1,12 @@ #pragma once + #include #include -#include -#include +#include +#include #include -#include "metric.hpp" +#include "dynamic_metric.hpp" #include "thread_local_value.hpp" namespace ylt::metric { @@ -26,84 +27,32 @@ struct json_counter_t { YLT_REFL(json_counter_t, name, help, type, 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,7 +72,7 @@ class basic_static_counter : public static_metric { return; } - serialize_head(str); + metric_t::serialize_head(str); serialize_default_label(str, value); } @@ -139,7 +88,7 @@ class basic_static_counter : public static_metric { iguana::to_json(counter, str); } #endif - + private: protected: void serialize_default_label(std::string &str, value_type value) { str.append(name_); @@ -165,159 +114,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); - } - - 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 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(); - } - - 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; + 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)->value, value); } - dynamic_metric_hash_map, - thread_local_value> - value_map() { - [[maybe_unused]] bool has_change = false; - return value_map(has_change); + value_type update(label_key_type labels_value, value_type value) { + return Base::try_emplace(labels_value) + ->value.exchange(value, std::memory_order::relaxed); } - 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_; + 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 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 +163,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 +208,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,21 +229,19 @@ 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); } } @@ -423,24 +249,21 @@ class basic_dynamic_counter : public dynamic_metric { #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_, + std::string(Base::metric_name())}; + 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; for (auto &label_value : k) { - metric.labels.emplace(labels_name_[index++], label_value); + metric.labels.emplace(Base::labels_name_[index++], label_value); } metric.value = (int64_t)val; counter.metrics.push_back(std::move(metric)); @@ -453,19 +276,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; + 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 +311,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/ylt/metric/dynamic_metric.hpp b/include/ylt/metric/dynamic_metric.hpp new file mode 100644 index 000000000..2508ac572 --- /dev/null +++ b/include/ylt/metric/dynamic_metric.hpp @@ -0,0 +1,146 @@ +#pragma once +#include "metric.hpp" +namespace ylt::metric { + +class dynamic_metric : public metric_t { + public: + using metric_t::metric_t; +}; + +template +class dynamic_metric_impl : public dynamic_metric { + struct my_hash { + using is_transparent = void; + std::size_t operator()( + const std::span& s) const noexcept { + unsigned int seed = 131; + 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 seed = 131; + 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 { + std::lock_guard guard(mtx_); + return table.size(); + } + size_t empty() const { return !size(); } + size_t label_value_count() const { return size(); } + + std::vector> copy() const { + std::lock_guard guard(mtx_); + std::vector> ret; + ret.reserve(table.size()); + for (auto& e : table) { + ret.push_back(e.second); + } + return ret; + } + + protected: + template + std::shared_ptr try_emplace(Key&& key, Args&&... args) { + std::lock_guard guard(mtx_); + std::span view = key; + auto iter = table.try_emplace(view, std::forward(key), + std::forward(args)...); + if (iter.second) { + *const_cast*>(&iter.first->first) = + iter.first->second->label; + } + return table + .try_emplace(view, std::forward(key), std::forward(args)...) + .first->second; + } + 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) { + std::lock_guard guard(mtx_); + auto iter = table.find(key); + if (iter != table.end()) { + return iter->second; + } + else { + return nullptr; + } + } + size_t erase(std::span key) { + std::lock_guard guard(mtx_); + return table.erase(key); + } + void erase_if(auto&& op) { + std::lock_guard guard(mtx_); + std::erase_if(table, op); + } + + private: + mutable std::mutex mtx_; + std::unordered_map, value_type, my_hash, + my_equal> + table; +}; +} // namespace ylt::metric \ No newline at end of file diff --git a/include/ylt/metric/gauge.hpp b/include/ylt/metric/gauge.hpp index 50d678c1c..5701dcf0c 100644 --- a/include/ylt/metric/gauge.hpp +++ b/include/ylt/metric/gauge.hpp @@ -1,7 +1,9 @@ #pragma once +#include #include #include "counter.hpp" +#include "ylt/metric/metric.hpp" namespace ylt::metric { @@ -32,16 +34,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 +43,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)->value, value); } }; diff --git a/include/ylt/metric/histogram.hpp b/include/ylt/metric/histogram.hpp index 66c13d018..5e3e58f59 100644 --- a/include/ylt/metric/histogram.hpp +++ b/include/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 @@ -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/ylt/metric/metric.hpp b/include/ylt/metric/metric.hpp index 8c880765e..7ce70203c 100644 --- a/include/ylt/metric/metric.hpp +++ b/include/ylt/metric/metric.hpp @@ -1,15 +1,13 @@ #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 +32,7 @@ inline char* to_chars_float(T value, char* buffer) { #include #endif + namespace ylt::metric { enum class MetricType { Counter, @@ -50,32 +49,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 +81,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 +117,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(); @@ -208,19 +194,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_user_metric_count = 0; +inline std::chrono::seconds ylt_label_max_age{0}; +inline std::chrono::seconds ylt_label_check_expire_duration{0}; 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; } diff --git a/include/ylt/metric/metric_manager.hpp b/include/ylt/metric/metric_manager.hpp index b3b90c0f0..62833614a 100644 --- a/include/ylt/metric/metric_manager.hpp +++ b/include/ylt/metric/metric_manager.hpp @@ -9,9 +9,9 @@ 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); diff --git a/include/ylt/metric/summary.hpp b/include/ylt/metric/summary.hpp index 1d97e968d..6caccb3f0 100644 --- a/include/ylt/metric/summary.hpp +++ b/include/ylt/metric/summary.hpp @@ -6,7 +6,7 @@ #include #include "counter.hpp" -#include "metric.hpp" +#include "dynamic_metric.hpp" #include "summary_impl.hpp" #if __has_include("ylt/util/concurrentqueue.h") #include "ylt/util/concurrentqueue.h" @@ -42,7 +42,6 @@ class summary_t : public static_metric { std::chrono::duration_cast(max_age)) { if (!std::is_sorted(quantiles_.begin(), quantiles_.end())) std::sort(quantiles_.begin(), quantiles_.end()); - g_user_metric_count++; } summary_t(std::string name, std::string help, std::vector quantiles, @@ -55,7 +54,6 @@ class summary_t : public static_metric { std::chrono::duration_cast(max_age)) { if (!std::is_sorted(quantiles_.begin(), quantiles_.end())) std::sort(quantiles_.begin(), quantiles_.end()); - g_user_metric_count++; } void observe(float value) { impl_.insert(value); } @@ -149,89 +147,80 @@ class summary_t : public static_metric { }; template -class basic_dynamic_summary : public dynamic_metric { +class basic_dynamic_summary + : public dynamic_metric_impl, N> { private: - auto visit(const std::array& labels_value) { - decltype(label_quantile_values_.begin()) iter; - bool has_inserted; - { - std::lock_guard guard(mutex_); - std::tie(iter, has_inserted) = - label_quantile_values_.try_emplace(labels_value, nullptr); - if (has_inserted) { - iter->second = std::make_unique>(quantiles_); - } - } - return iter; - } + using Base = dynamic_metric_impl, N>; public: basic_dynamic_summary( std::string name, std::string help, std::vector quantiles, std::array labels_name, std::chrono::milliseconds max_age = std::chrono::seconds{60}) - : dynamic_metric(MetricType::Summary, std::move(name), std::move(help), - std::move(labels_name)), + : 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()); - g_user_metric_count++; } void observe(const std::array& labels_value, float value) { - visit(labels_value)->second->insert(value); + Base::try_emplace(labels_value, quantiles_)->value.insert(value); } std::vector get_rates(const std::array& labels_value) { double sum; uint64_t count; - return visit(labels_value)->second->get_rates(sum, count); + return Base::try_emplace(labels_value, quantiles_) + ->value.get_rates(sum, count); } std::vector get_rates(const std::array& labels_value, uint64_t& count) { double sum; - return visit(labels_value)->second->get_rates(sum, count); + return Base::try_emplace(labels_value, quantiles_) + ->value.get_rates(sum, count); } std::vector get_rates(const std::array& labels_value, double& sum) { uint64_t count; - return visit(labels_value)->second->get_rates(sum, count); + return Base::try_emplace(labels_value, quantiles_) + ->value.get_rates(sum, count); } std::vector get_rates(const std::array& labels_value, double& sum, uint64_t& count) { - return visit(labels_value)->second->stat(sum, count); + return Base::try_emplace(labels_value, quantiles_)->value.stat(sum, count); } virtual void serialize(std::string& str) override { double sum = 0; uint64_t count = 0; - std::lock_guard guard(mutex_); - // TODO: copy pointer to avoid big lock - for (auto& [labels_value, summary_value] : label_quantile_values_) { - auto rates = summary_value->stat(sum, count); + 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(name_); + 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(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"); } @@ -239,37 +228,33 @@ class basic_dynamic_summary : public dynamic_metric { #ifdef CINATRA_ENABLE_METRIC_JSON virtual void serialize_to_json(std::string& str) override { - json_summary_t summary{name_, help_, std::string(metric_name())}; - { - std::lock_guard guard(mutex_); - for (auto& [labels_value, summary_value] : label_quantile_values_) { - json_summary_metric_t metric; - double sum = 0; - uint64_t count = 0; - auto rates = summary_value->stat(sum, count); - metric.count = count; - metric.sum = sum; - for (size_t i = 0; i < quantiles_.size(); i++) { - for (size_t i = 0; i < labels_value.size(); i++) { - metric.labels[labels_name_[i]] = labels_value[i]; - } - metric.quantiles.emplace(quantiles_[i], rates[i]); + json_summary_t summary{Base::name_, Base::help_, + std::string(Base::metric_name())}; + auto map = Base::copy(); + for (auto& e : map) { + auto& labels_value = e->label; + auto& summary_value = e->value; + json_summary_metric_t metric; + double sum = 0; + uint64_t count = 0; + auto rates = summary_value.stat(sum, count); + metric.count = count; + metric.sum = sum; + for (size_t i = 0; i < quantiles_.size(); i++) { + for (size_t i = 0; i < labels_value.size(); i++) { + metric.labels[Base::labels_name_[i]] = labels_value[i]; } - summary.metrics.push_back(std::move(metric)); + metric.quantiles.emplace(quantiles_[i], rates[i]); } + summary.metrics.push_back(std::move(metric)); } iguana::to_json(summary, str); } #endif private: - using hashtable_t = dynamic_metric_hash_map< - std::array, - std::unique_ptr>>; - std::mutex mutex_; std::vector quantiles_; std::chrono::milliseconds max_age_; - hashtable_t label_quantile_values_; }; using dynamic_summary_1 = basic_dynamic_summary<1>; diff --git a/include/ylt/metric/summary_impl.hpp b/include/ylt/metric/summary_impl.hpp index 4747b6910..e32c42eaf 100644 --- a/include/ylt/metric/summary_impl.hpp +++ b/include/ylt/metric/summary_impl.hpp @@ -95,7 +95,9 @@ class summary_impl { piece_t* piece = arr[index / piece_size]; if (piece == nullptr) { auto ptr = new piece_t{}; - arr[index / piece_size].compare_exchange_strong(piece, ptr); + if (!arr[index / piece_size].compare_exchange_strong(piece, ptr)) { + delete ptr; + } return (*arr[index / piece_size].load())[index % piece_size]; } else { diff --git a/include/ylt/metric/system_metric.hpp b/include/ylt/metric/system_metric.hpp index 74156b90c..97e1c26d1 100644 --- a/include/ylt/metric/system_metric.hpp +++ b/include/ylt/metric/system_metric.hpp @@ -357,12 +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()); + user_metric_label_count->update(metric_t::g_user_metric_label_count->value()); } inline void ylt_stat() { diff --git a/include/ylt/metric/thread_local_value.hpp b/include/ylt/metric/thread_local_value.hpp index b73789dd8..e133788db 100644 --- a/include/ylt/metric/thread_local_value.hpp +++ b/include/ylt/metric/thread_local_value.hpp @@ -5,14 +5,50 @@ #include #include +#include "ylt/metric/metric.hpp" + namespace ylt::metric { inline uint32_t get_round_index(uint32_t size) { static std::atomic round = 0; 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 (!obj.compare_exchange_weak(&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 (!obj.compare_exchange_weak(&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 +92,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; @@ -99,14 +135,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/src/metric/benchmark/bench.hpp b/src/metric/benchmark/bench.hpp new file mode 100644 index 000000000..2f1dcb0df --- /dev/null +++ b/src/metric/benchmark/bench.hpp @@ -0,0 +1,275 @@ +#include + +#include "ylt/metric.hpp" +#include "ylt/metric/counter.hpp" +#include "ylt/metric/summary.hpp" + +using namespace std::chrono_literals; +using namespace ylt::metric; + +inline auto get_random(size_t range = 10000) { + thread_local std::default_random_engine gen(std::time(nullptr)); + std::uniform_int_distribution<> distr(1, range); + return distr(gen); +} + +struct bench_clock_t { + bench_clock_t() : start_(std::chrono::steady_clock::now()) {} + template + unit duration() { + auto now = std::chrono::steady_clock::now(); + auto ret = now - start_; + return std::chrono::duration_cast(ret); + } + std::chrono::steady_clock::time_point start_; +}; + +template +void bench_mixed_impl(IMPL& impl, WRITE_OP&& op, size_t thd_num, + std::chrono::seconds duration) { + ylt::metric::summary_t lantency_summary( + "write latency(ms)", "", {0.99, 0.999, 0.9999, 0.99999, 0.999999, 1.0}); + std::atomic stop = false; + std::vector vec; + std::array arr{"/test", "200"}; + bench_clock_t clock; + std::string val(36, ' '); + for (size_t i = 0; i < thd_num; i++) { + vec.push_back(std::thread([&, i] { + while (!stop) { + bench_clock_t clock; + op(); + auto t = clock.duration(); + lantency_summary.observe(t.count() / 1000.0f); + } + })); + } + std::string s; + + bench_clock_t clock2; + int64_t serialze_cnt = 0; + do { + s.clear(); + impl.serialize(s); + ++serialze_cnt; + } while (clock2.duration() < duration); + auto total_ms = clock.duration(); + stop = true; + std::cout << "run " << total_ms.count() << "ms\n"; + size_t cnt; + double sum; + auto result = lantency_summary.get_rates(sum, cnt); + auto seconds = total_ms.count() / 1000.0; + auto qps = 1.0 * cnt / seconds; + std::cout << "write thd num: " << thd_num << ", write qps: " << (int64_t)qps + << "\n"; + std::cout << "serialize qps:" << 1000.0 * serialze_cnt / total_ms.count() + << ", str size=" << s.size() << "\n"; + s = ""; + lantency_summary.serialize(s); + std::cout << s; + for (auto& thd : vec) { + thd.join(); + } +} + +inline void bench_static_summary_mixed(size_t thd_num, + std::chrono::seconds duration, + std::chrono::seconds age = 1s) { + ylt::metric::summary_t summary("summary mixed test", "", + {0.5, 0.9, 0.95, 0.99, 0.995}, 1s); + bench_mixed_impl( + summary, + [&]() { + summary.observe(get_random(100)); + }, + thd_num, duration); +} + +inline void bench_static_counter_mixed(size_t thd_num, + std::chrono::seconds duration) { + ylt::metric::counter_t counter("counter mixed test", ""); + bench_mixed_impl( + counter, + [&]() { + counter.inc(1); + }, + thd_num, duration); +} + +inline void bench_dynamic_summary_mixed(size_t thd_num, + std::chrono::seconds duration, + std::chrono::seconds age = 1s) { + ylt::metric::dynamic_summary summary("dynamic summary mixed test", "", + {0.5, 0.9, 0.95, 0.99, 0.995}, + {"a", "b"}, age); + bench_mixed_impl( + summary, + [&, i = 0]() mutable { + ++i; + summary.observe( + {"123e4567-e89b-12d3-a456-426614174000", std::to_string(i)}, + get_random(100)); + }, + thd_num, duration); +} + +inline void bench_dynamic_counter_mixed(size_t thd_num, + std::chrono::seconds duration) { + ylt::metric::dynamic_counter_2d counter("dynamic summary mixed test", "", + {"a", "b"}); + bench_mixed_impl( + counter, + [&, i = 0]() mutable { + ++i; + counter.inc({"123e4567-e89b-12d3-a456-426614174000", std::to_string(i)}, + 1); + }, + thd_num, duration); +} + +template +void bench_serialize_impl(IMPL& impl, OP&& op, size_t COUNT, bool to_json) { + for (size_t i = 0; i < COUNT; i++) { + op(i); + } + std::string str; + bench_clock_t clock; + if (to_json) { + if constexpr (requires { impl[0]; }) { + str = manager_helper::serialize_to_json(impl); + } + else { + impl.serialize_to_json(str); + } + } + else { + if constexpr (requires { impl[0]; }) { + str = manager_helper::serialize(impl); + } + else { + impl.serialize(str); + } + } + std::cout << "COUNT:" << COUNT << ", string size: " << str.size() << ", " + << clock.duration().count() << "ms\n"; + if (str.size() < 1000) { + std::cout << str << std::endl; + } +} + +inline void bench_many_metric_serialize(size_t COUNT, size_t LABEL_COUNT, + bool to_json = false) { + std::vector> vec; + bench_serialize_impl( + vec, + [LABEL_COUNT, &vec](int) { + auto counter = std::make_shared( + std::string("qps"), "", std::array{"url", "code"}); + for (size_t j = 0; j < LABEL_COUNT; j++) { + counter->inc({"test_label_value", std::to_string(j)}); + } + vec.push_back(counter); + }, + COUNT, to_json); +} + +inline void bench_dynamic_counter_serialize(size_t COUNT, + bool to_json = false) { + dynamic_counter_t counter("qps2", "", {"url", "code"}); + bench_serialize_impl( + counter, + [&](int i) { + counter.inc( + {"123e4567-e89b-12d3-a456-426614174000", std::to_string(i)}); + }, + COUNT, to_json); +} + +inline void bench_dynamic_summary_serialize(size_t COUNT, + bool to_json = false) { + dynamic_summary_2 summary("qps2", "", {0.5, 0.9, 0.95, 0.995}, + std::array{"method", "url"}); + bench_serialize_impl( + summary, + [&](int i) { + summary.observe( + {"123e4567-e89b-12d3-a456-426614174000", std::to_string(i)}, i); + }, + COUNT, to_json); +} + +template +void bench_write_impl(IMPL& impl, OP&& op, size_t thd_num, + std::chrono::seconds duration) { + std::atomic stop = false; + std::vector> vec; + std::array arr{"/test", "200"}; + thread_local_value local_val(thd_num); + bench_clock_t clock; + for (size_t i = 0; i < thd_num; i++) { + vec.push_back(std::async([&] { + int64_t cnt = 0; + while (!stop) { + op(); + ++cnt; + } + return cnt; + })); + } + std::this_thread::sleep_for(duration); + stop = true; + std::cout << "run " << clock.duration().count() << "ms\n"; + double qps = 0; + for (auto& thd : vec) { + qps += thd.get(); + } + qps /= (clock.duration().count() / 1000.0); + std::cout << "thd num: " << thd_num << ", qps: " << (int64_t)qps << "\n"; +} + +inline void bench_static_counter_write(size_t thd_num, + std::chrono::seconds duration) { + counter_t counter("qps", ""); + bench_write_impl( + counter, + [&] { + counter.inc(1); + }, + thd_num, duration); +} + +inline void bench_dynamic_counter_write(size_t thd_num, + std::chrono::seconds duration) { + dynamic_counter_t counter("qps2", "", {"url", "code"}); + bench_write_impl( + counter, + [&] { + counter.inc({"/test", std::to_string(get_random())}, 1); + }, + thd_num, duration); +} + +inline void bench_dynamic_summary_write(size_t thd_num, + std::chrono::seconds duration) { + dynamic_summary_2 summary("qps2", "", {0.5, 0.9, 0.95, 0.99}, + std::array{"method", "url"}); + bench_write_impl( + summary, + [&]() { + summary.observe({"/test", std::to_string(get_random())}, + get_random(100)); + }, + thd_num, duration); +} + +inline void bench_static_summary_write(size_t thd_num, + std::chrono::seconds duration) { + ylt::metric::summary_t summary("qps2", "", {0.5, 0.9, 0.95, 0.99}); + bench_write_impl( + summary, + [&]() { + summary.observe(get_random(100)); + }, + thd_num, duration); +} \ No newline at end of file diff --git a/src/metric/benchmark/main.cpp b/src/metric/benchmark/main.cpp index 287efed0e..1ad87f2a1 100644 --- a/src/metric/benchmark/main.cpp +++ b/src/metric/benchmark/main.cpp @@ -1,255 +1,55 @@ -#include -#include -#include -#include -#include -#include - -#include "ylt/metric.hpp" - -using namespace std::chrono_literals; -using namespace ylt::metric; - -void bench_static_counter_qps(size_t thd_num, std::chrono::seconds duration, - size_t dupli_count = 2) { - counter_t counter("qps", "", dupli_count); - std::vector vec; - std::atomic stop = false; - auto start = std::chrono::system_clock::now(); - for (size_t i = 0; i < thd_num; i++) { - vec.push_back(std::thread([&] { - while (!stop) { - counter.inc(1); - } - })); - } - - std::this_thread::sleep_for(duration); - stop = true; - auto end = std::chrono::system_clock::now(); - - auto elaps = - std::chrono::duration_cast(end - start) - .count(); - - double seconds = double(elaps) / 1000; - - auto qps = counter.value() / seconds; - std::cout << "duplicate count: " << dupli_count << ", thd num: " << thd_num - << ", qps: " << (uint64_t)qps << "\n"; - - for (auto& thd : vec) { - thd.join(); - } -} - -auto get_random(size_t range = 10000) { - thread_local std::default_random_engine gen(std::time(nullptr)); - std::uniform_int_distribution<> distr(1, range); - return distr(gen); -} - -void bench_dynamic_counter_qps(size_t thd_num, std::chrono::seconds duration, - size_t dupli_count = 2) { - dynamic_counter_t counter("qps2", "", {"url", "code"}, dupli_count); - std::atomic stop = false; - std::vector vec; - std::array arr{"/test", "200"}; - auto start = std::chrono::system_clock::now(); - for (size_t i = 0; i < thd_num; i++) { - vec.push_back(std::thread([&, i] { - while (!stop) { - counter.inc({"/test", std::to_string(get_random())}, 1); - } - })); - } - std::this_thread::sleep_for(duration); - stop = true; - auto end = std::chrono::system_clock::now(); - - auto elaps = - std::chrono::duration_cast(end - start) - .count(); - - double seconds = double(elaps) / 1000; - std::cout << "run " << elaps << "ms, " << seconds << " seconds\n"; - - size_t total = 0; - for (size_t i = 0; i < 10000; i++) { - total += counter.value({"/test", std::to_string(i)}); - } - auto qps = total / seconds; - std::cout << "duplicate count: " << dupli_count << ", thd num: " << thd_num - << ", qps: " << (int64_t)qps << "\n"; - for (auto& thd : vec) { - thd.join(); - } -} - -void bench_many_metric_serialize(size_t COUNT, size_t LABEL_COUNT, - bool to_json = false) { - std::vector> vec; - for (size_t i = 0; i < COUNT; i++) { - auto counter = std::make_shared( - std::string("qps"), "", std::array{"url", "code"}); - for (size_t j = 0; j < LABEL_COUNT; j++) { - counter->inc({"test_label_value", std::to_string(j)}); - } - vec.push_back(counter); - } - - std::cout << "begin test\n"; - - std::string str; - - auto start = std::chrono::system_clock::now(); - if (to_json) { - str = manager_helper::serialize_to_json(vec); - } - else { - str = manager_helper::serialize(vec); - } - - auto end = std::chrono::system_clock::now(); - auto elaps = - std::chrono::duration_cast(end - start) - .count(); - std::cout << "string size: " << str.size() << ", " << elaps << "ms\n"; -} - -void bench_many_labels_serialize(size_t COUNT, bool to_json = false) { - dynamic_counter_t counter("qps2", "", {"url", "code"}); - std::string val(36, ' '); - for (size_t i = 0; i < COUNT; i++) { - strcpy(val.data(), std::to_string(i).data()); - counter.inc({"123e4567-e89b-12d3-a456-426614174000", val}); - } - - std::string str; - auto start = std::chrono::system_clock::now(); - if (to_json) { - counter.serialize_to_json(str); - } - else { - counter.serialize(str); - } - - auto end = std::chrono::system_clock::now(); - auto elaps = - std::chrono::duration_cast(end - start) - .count(); - std::cout << elaps << "ms\n"; - std::cout << "label value count: " << counter.label_value_count() - << " string size: " << str.size() << "\n"; -} - -void bench_many_labels_qps_summary(size_t thd_num, - std::chrono::seconds duration) { - dynamic_summary_2 summary("qps2", "", {0.5, 0.9, 0.95, 0.99}, - std::array{"method", "url"}); - std::atomic stop = false; - std::vector vec; - std::array arr{"/test", "200"}; - thread_local_value local_val(thd_num); - auto start = std::chrono::system_clock::now(); - std::string val(36, ' '); - for (size_t i = 0; i < thd_num; i++) { - vec.push_back(std::thread([&, i] { - while (!stop) { - strcpy(val.data(), std::to_string(i).data()); - summary.observe({"/test", std::to_string(get_random())}, - get_random(100)); - local_val.inc(); - } - })); - } - std::this_thread::sleep_for(duration); - stop = true; - auto end = std::chrono::system_clock::now(); - - auto elaps = - std::chrono::duration_cast(end - start) - .count(); - - double seconds = double(elaps) / 1000; - std::cout << "run " << elaps << "ms, " << seconds << " seconds\n"; - - auto qps = local_val.value() / seconds; - std::cout << "thd num: " << thd_num << ", qps: " << (int64_t)qps << "\n"; - for (auto& thd : vec) { - thd.join(); - } -} - -void bench_many_labels_serialize_summary(size_t COUNT, bool to_json = false) { - dynamic_summary_2 summary("qps2", "", {0.5, 0.9, 0.95, 0.005}, - std::array{"method", "url"}); - std::string val(36, ' '); - for (size_t i = 0; i < COUNT; i++) { - strcpy(val.data(), std::to_string(i).data()); - summary.observe({"123e4567-e89b-12d3-a456-426614174000", val}, - get_random(100)); - } - - std::string str; - auto start = std::chrono::system_clock::now(); - if (to_json) { - summary.serialize_to_json(str); - } - else { - summary.serialize(str); - } - - auto end = std::chrono::system_clock::now(); - auto elaps = - std::chrono::duration_cast(end - start) - .count(); - std::cout << elaps << "ms\n"; - std::cout << "label value count: " << summary.label_value_count() - << " string size: " << str.size() << "\n"; -} +#include "bench.hpp" int main() { - bench_many_labels_serialize_summary(100000); - bench_many_labels_serialize_summary(1000000); + std::cout << "start serialize bench" << std::endl; - bench_many_labels_serialize_summary(100000, true); - bench_many_labels_serialize_summary(1000000, true); + std::cout << "\ndynamic summary serialize:" << std::endl; + bench_dynamic_summary_serialize(100000); - bench_many_labels_qps_summary(1, 5s); - bench_many_labels_qps_summary(2, 5s); - bench_many_labels_qps_summary(8, 5s); - bench_many_labels_qps_summary(16, 5s); - bench_many_labels_qps_summary(32, 5s); - bench_many_labels_qps_summary(96, 5s); + std::cout << "\ndynamic summary serialize(json):" << std::endl; + bench_dynamic_summary_serialize(100000, true); - bench_many_labels_serialize(100000); - bench_many_labels_serialize(1000000); - bench_many_labels_serialize(10000000); - bench_many_labels_serialize(100000, true); - bench_many_labels_serialize(1000000, true); - bench_many_labels_serialize(10000000, true); + std::cout << "\ndynamic counter with many labels serialize:" << std::endl; + bench_dynamic_counter_serialize(100000); + std::cout << "\ndynamic counter with many labels serialize(json):" + << std::endl; + bench_dynamic_counter_serialize(100000, true); + + std::cout << "\nmulti dynamic counter serialize:" << std::endl; bench_many_metric_serialize(100000, 10); - bench_many_metric_serialize(1000000, 10); + std::cout << "\nmulti dynamic counter serialize(json):" << std::endl; bench_many_metric_serialize(100000, 10, true); - bench_many_metric_serialize(1000000, 10, true); - - bench_static_counter_qps(1, 5s); - bench_static_counter_qps(2, 5s); - bench_static_counter_qps(8, 5s); - bench_static_counter_qps(16, 5s); - bench_static_counter_qps(32, 5s); - bench_static_counter_qps(96, 5s); - bench_static_counter_qps(32, 5s, 32); - bench_static_counter_qps(96, 5s, 96); - bench_dynamic_counter_qps(1, 5s); - bench_dynamic_counter_qps(2, 5s); - bench_dynamic_counter_qps(8, 5s); - bench_dynamic_counter_qps(16, 5s); - bench_dynamic_counter_qps(32, 5s); - bench_dynamic_counter_qps(96, 10s); - bench_dynamic_counter_qps(32, 5s, 32); - bench_dynamic_counter_qps(96, 5s, 96); + std::cout << "\nstart write bench" << std::endl; + + std::cout << "\nstatic summary performance test:" << std::endl; + bench_static_summary_write(1, 5s); + bench_static_summary_write(std::thread::hardware_concurrency(), 5s); + + std::cout << "\ndynamic summary performance test:" << std::endl; + bench_dynamic_summary_write(1, 5s); + bench_dynamic_summary_write(std::thread::hardware_concurrency(), 5s); + + std::cout << "\nstatic counter performance test:" << std::endl; + bench_static_counter_write(1, 5s); + bench_static_counter_write(std::thread::hardware_concurrency(), 5s); + + std::cout << "\ndynamic counter performance test:" << std::endl; + bench_dynamic_counter_write(1, 5s); + bench_dynamic_counter_write(std::thread::hardware_concurrency(), 5s); + + std::cout << "\nstart write/seriailize mixed bench" << std::endl; + std::cout << "\nstatic summary mixed test:" << std::endl; + bench_static_summary_mixed(1, 5s); + bench_static_summary_mixed(std::thread::hardware_concurrency(), 5s); + std::cout << "\nstatic counter mixed test:" << std::endl; + bench_static_counter_mixed(1, 5s); + bench_static_counter_mixed(std::thread::hardware_concurrency(), 5s); + std::cout << "\ndynamic summary mixed test:" << std::endl; + bench_dynamic_summary_mixed(1, 5s); + bench_dynamic_summary_mixed(std::thread::hardware_concurrency(), 5s); + std::cout << "\ndynamic counter mixed test:" << std::endl; + bench_dynamic_counter_mixed(1, 5s); + bench_dynamic_counter_mixed(std::thread::hardware_concurrency(), 5s); } diff --git a/src/metric/tests/CMakeLists.txt b/src/metric/tests/CMakeLists.txt index ccb2b9b66..100020b5c 100644 --- a/src/metric/tests/CMakeLists.txt +++ b/src/metric/tests/CMakeLists.txt @@ -1,5 +1,8 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/tests) add_executable(metric_test test_metric.cpp + parallel_test.cpp ) +target_compile_options(metric_test PRIVATE -fsanitize=address) +target_link_options(metric_test PRIVATE -fsanitize=address) add_test(NAME metric_test COMMAND metric_test) diff --git a/src/metric/tests/parallel_test.cpp b/src/metric/tests/parallel_test.cpp new file mode 100644 index 000000000..971d4aed9 --- /dev/null +++ b/src/metric/tests/parallel_test.cpp @@ -0,0 +1,13 @@ +#include +#include + +#include "../benchmark/bench.hpp" +#include "doctest.h" +#include "ylt/metric.hpp" + +TEST_CASE("test high parallel perform test") { + bench_static_summary_mixed(std::thread::hardware_concurrency() * 4, 3s); + bench_dynamic_summary_mixed(std::thread::hardware_concurrency() * 4, 2s); + bench_static_counter_mixed(std::thread::hardware_concurrency() * 4, 2s); + bench_static_counter_mixed(std::thread::hardware_concurrency() * 4, 2s); +} \ No newline at end of file diff --git a/src/metric/tests/test_metric.cpp b/src/metric/tests/test_metric.cpp index cfa400c24..b8d853281 100644 --- a/src/metric/tests/test_metric.cpp +++ b/src/metric/tests/test_metric.cpp @@ -575,11 +575,9 @@ 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); @@ -590,8 +588,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); } } @@ -624,11 +621,9 @@ 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); @@ -647,11 +642,9 @@ 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); } } @@ -1572,9 +1565,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{}); From 3a91b3b6c83a392e10e1c850659a5a179a6f2251 Mon Sep 17 00:00:00 2001 From: "Zezheng.Li" Date: Thu, 26 Sep 2024 14:02:43 +0800 Subject: [PATCH 2/4] fix --- include/ylt/metric/thread_local_value.hpp | 4 ++-- src/metric/benchmark/bench.hpp | 2 +- src/metric/tests/CMakeLists.txt | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/include/ylt/metric/thread_local_value.hpp b/include/ylt/metric/thread_local_value.hpp index e133788db..bb441691d 100644 --- a/include/ylt/metric/thread_local_value.hpp +++ b/include/ylt/metric/thread_local_value.hpp @@ -21,7 +21,7 @@ static value_type inc_impl(std::atomic &obj, value_type value) { std::atomic{}.fetch_add(value_type{}); }) { value_type v = obj.load(std::memory_order::relaxed); - while (!obj.compare_exchange_weak(&v, v + value)) + while (!atomic_compare_exchange_weak(v, v + value)) ; return v; } @@ -35,7 +35,7 @@ static value_type dec_impl(std::atomic &obj, value_type value) { std::atomic{}.fetch_add(value_type{}); }) { value_type v = obj.load(std::memory_order::relaxed); - while (!obj.compare_exchange_weak(&v, v - value)) + while (!obj.compare_exchange_weak(v, v - value)) ; return v; } diff --git a/src/metric/benchmark/bench.hpp b/src/metric/benchmark/bench.hpp index 2f1dcb0df..f33e6fb25 100644 --- a/src/metric/benchmark/bench.hpp +++ b/src/metric/benchmark/bench.hpp @@ -56,7 +56,7 @@ void bench_mixed_impl(IMPL& impl, WRITE_OP&& op, size_t thd_num, auto total_ms = clock.duration(); stop = true; std::cout << "run " << total_ms.count() << "ms\n"; - size_t cnt; + uint64_t cnt; double sum; auto result = lantency_summary.get_rates(sum, cnt); auto seconds = total_ms.count() / 1000.0; diff --git a/src/metric/tests/CMakeLists.txt b/src/metric/tests/CMakeLists.txt index 100020b5c..0c37b056f 100644 --- a/src/metric/tests/CMakeLists.txt +++ b/src/metric/tests/CMakeLists.txt @@ -3,6 +3,9 @@ add_executable(metric_test test_metric.cpp parallel_test.cpp ) -target_compile_options(metric_test PRIVATE -fsanitize=address) -target_link_options(metric_test PRIVATE -fsanitize=address) +check_asan(HAS_ASAN) +if (has_asan) + target_compile_options(metric_test PRIVATE -fsanitize=address) + target_link_options(metric_test PRIVATE -fsanitize=address) +endif() add_test(NAME metric_test COMMAND metric_test) From 22fe97930a08bd450085f3f0fa844275a53d5e39 Mon Sep 17 00:00:00 2001 From: "Zezheng.Li" Date: Thu, 26 Sep 2024 17:15:44 +0800 Subject: [PATCH 3/4] fix --- include/ylt/metric/thread_local_value.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/ylt/metric/thread_local_value.hpp b/include/ylt/metric/thread_local_value.hpp index bb441691d..04a9894c2 100644 --- a/include/ylt/metric/thread_local_value.hpp +++ b/include/ylt/metric/thread_local_value.hpp @@ -21,7 +21,7 @@ static value_type inc_impl(std::atomic &obj, value_type value) { std::atomic{}.fetch_add(value_type{}); }) { value_type v = obj.load(std::memory_order::relaxed); - while (!atomic_compare_exchange_weak(v, v + value)) + while (!std::atomic_compare_exchange_weak(&obj, v, v + value)) ; return v; } @@ -35,7 +35,7 @@ static value_type dec_impl(std::atomic &obj, value_type value) { std::atomic{}.fetch_add(value_type{}); }) { value_type v = obj.load(std::memory_order::relaxed); - while (!obj.compare_exchange_weak(v, v - value)) + while (!std::atomic_compare_exchange_weak(&obj, v, v - value)) ; return v; } From b2689cdb549cd7e83ea289bec7b726158e01bf54 Mon Sep 17 00:00:00 2001 From: "Zezheng.Li" Date: Thu, 26 Sep 2024 17:28:09 +0800 Subject: [PATCH 4/4] fix --- include/ylt/metric/thread_local_value.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/ylt/metric/thread_local_value.hpp b/include/ylt/metric/thread_local_value.hpp index 04a9894c2..8d9293823 100644 --- a/include/ylt/metric/thread_local_value.hpp +++ b/include/ylt/metric/thread_local_value.hpp @@ -21,7 +21,7 @@ static value_type inc_impl(std::atomic &obj, value_type value) { 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)) + while (!std::atomic_compare_exchange_weak(&obj, &v, v + value)) ; return v; } @@ -35,7 +35,7 @@ static value_type dec_impl(std::atomic &obj, value_type value) { 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)) + while (!std::atomic_compare_exchange_weak(&obj, &v, v - value)) ; return v; }