From 8e0623c78e4ccd745a806dd59a77fb60873217aa Mon Sep 17 00:00:00 2001 From: "Zezheng.Li" Date: Wed, 25 Sep 2024 17:37:29 +0800 Subject: [PATCH] [metric] refactor metric --- include/ylt/metric/counter.hpp | 353 ++++++---------------- include/ylt/metric/dynamic_metric.hpp | 146 +++++++++ include/ylt/metric/gauge.hpp | 35 +-- include/ylt/metric/histogram.hpp | 33 +- include/ylt/metric/metric.hpp | 50 ++- 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 | 54 +++- 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, 714 insertions(+), 674 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..484f6600e 100644 --- a/include/ylt/metric/counter.hpp +++ b/include/ylt/metric/counter.hpp @@ -1,11 +1,15 @@ #pragma once +#include + #include #include #include #include +#include +#include #include -#include "metric.hpp" +#include "dynamic_metric.hpp" #include "thread_local_value.hpp" namespace ylt::metric { @@ -26,84 +30,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) + basic_static_counter( + std::string name, std::string help, + uint32_t dupli_count = std::thread::hardware_concurrency()) : static_metric(MetricType::Counter, std::move(name), std::move(help)) { init_thread_local(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) + basic_static_counter( + std::string name, std::string help, + std::map labels, + uint32_t dupli_count = 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--; } - 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 +75,7 @@ class basic_static_counter : public static_metric { return; } - serialize_head(str); + metric_t::serialize_head(str); serialize_default_label(str, value); } @@ -139,6 +91,13 @@ class basic_static_counter : public static_metric { iguana::to_json(counter, str); } #endif + private: + void init_thread_local(uint32_t dupli_count) { + if (dupli_count > 0) { + dupli_count_ = dupli_count; + default_label_value_ = {dupli_count}; + } + } protected: void serialize_default_label(std::string &str, value_type value) { @@ -166,158 +125,48 @@ class basic_static_counter : public static_metric { } thread_local_value default_label_value_; - uint32_t dupli_count_ = 2; + uint32_t dupli_count_ = std::min(32u, std::thread::hardware_concurrency()); 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); - } -}; - 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++; + 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) { + Base::try_emplace(labels_value) + ->value.fetch_add(value, std::memory_order::relaxed); } - 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(label_key_type labels_value, value_type value) { + return Base::try_emplace(labels_value) + ->value.exchange(value, std::memory_order::relaxed); } - 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{}; + 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); } - 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; - } - - 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_; + else { + return value_type{}; } - - 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; - } - - 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 +174,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 +219,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 +240,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 +260,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 +287,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 +322,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..6b26c3233 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 { @@ -50,44 +52,19 @@ 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_; public: basic_dynamic_gauge(std::string name, std::string help, - std::array labels_name, - size_t dupli_count = 2) + std::array labels_name) : basic_dynamic_counter(std::move(name), std::move(help), - std::move(labels_name), - dupli_count) { + 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); + basic_dynamic_counter::try_emplace(labels_value) + ->value.fetch_sub(value, std::memory_order::relaxed); } }; diff --git a/include/ylt/metric/histogram.hpp b/include/ylt/metric/histogram.hpp index 66c13d018..983241191 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,7 +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( @@ -167,18 +167,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 +204,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 +213,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 +254,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 +267,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 +275,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..bd814508b 100644 --- a/include/ylt/metric/metric.hpp +++ b/include/ylt/metric/metric.hpp @@ -2,14 +2,21 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include +#include +#include #include +#include +#include #include #include "async_simple/coro/Lazy.h" @@ -34,6 +41,7 @@ inline char* to_chars_float(T value, char* buffer) { #include #endif + namespace ylt::metric { enum class MetricType { Counter, @@ -50,32 +58,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 +90,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 +126,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 +203,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..78ab7b145 100644 --- a/include/ylt/metric/thread_local_value.hpp +++ b/include/ylt/metric/thread_local_value.hpp @@ -11,8 +11,28 @@ inline uint32_t get_round_index(uint32_t size) { static thread_local uint32_t index = round++; return index % size; } +namespace detail {} + template class thread_local_value { + static value_type manually_atomic_fetch_add(std::atomic &obj, + value_type arg) { + value_type v; + do { + v = obj.load(); + } while (!std::atomic_compare_exchange_weak(&obj, &v, v + arg)); + return v; + } + + static value_type manually_atomic_fetch_sub(std::atomic &obj, + value_type arg) { + value_type v; + do { + v = obj.load(); + } while (!std::atomic_compare_exchange_weak(&obj, &v, v - arg)); + return v; + } + public: thread_local_value(uint32_t dupli_count = std::thread::hardware_concurrency()) : duplicates_(dupli_count) {} @@ -56,15 +76,33 @@ class thread_local_value { return *this; } - void inc(value_type value = 1) { local_value() += value; } + void inc(value_type value = 1) { + if constexpr (!requires { + std::atomic{}.fetch_add(value_type{}); + }) { + manually_atomic_fetch_add(&local_value(), value); + } + else { + local_value().fetch_add(value, std::memory_order::relaxed); + } + } - void dec(value_type value = 1) { local_value() -= value; } + void dec(value_type value = 1) { + if constexpr (!requires { + std::atomic{}.fetch_add(value_type{}); + }) { + manually_atomic_fetch_sub(&local_value(), value); + } + else { + local_value().fetch_sub(value, std::memory_order::relaxed); + } + } 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 +137,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{});