This is an automated email from the ASF dual-hosted git repository.

guangmingchen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git


The following commit(s) were added to refs/heads/master by this push:
     new ef82950d Support shared mbvar (#3129)
ef82950d is described below

commit ef82950d172346209db728d8e68522bb5fa489ed
Author: Bright Chen <[email protected]>
AuthorDate: Sat Nov 1 13:07:21 2025 +0800

    Support shared mbvar (#3129)
    
    * Support shared mbvar
    
    * Update document
---
 docs/cn/mbvar_c++.md                   |  21 +++++
 src/bvar/detail/combiner.h             |  10 +-
 src/bvar/multi_dimension.h             |  57 ++++++++---
 src/bvar/multi_dimension_inl.h         | 131 ++++++++++++-------------
 test/bvar_multi_dimension_unittest.cpp | 168 ++++++++++++++++++++++++++++++++-
 test/bvar_mvariable_unittest.cpp       | 113 ----------------------
 6 files changed, 303 insertions(+), 197 deletions(-)

diff --git a/docs/cn/mbvar_c++.md b/docs/cn/mbvar_c++.md
index 8a81e714..1ce1364f 100644
--- a/docs/cn/mbvar_c++.md
+++ b/docs/cn/mbvar_c++.md
@@ -412,6 +412,7 @@ public:
 ```
 
 ### get_stats
+
 根据指定label获取对应的单维度统计项bvar。
 
 get_stats除了支持(默认)std::list<std::string>参数类型,也支持自定义参数类型,满足以下条件:
@@ -421,6 +422,12 @@ get_stats除了支持(默认)std::list<std::string>参数类型,也支持
 
 推荐使用不需要分配内存的容器(例如,std::array、absl::InlinedVector)和不需要拷贝字符串的数据结构(例如,const 
char*、std::string_view、butil::StringPieces),可以提高性能。
 
+bvar::MultiDimension的模板参数Shared,默认为false:
+1. 如果Shared等于false,get_stats返回模板参数T的指针,例如bvar::Adder<int>*。
+2. 
如果Shared等于true,get_stats返回模板参数T的shared_ptr,例如std::shared_ptr\<bvar::Adder<int>\>。
+
+**注意**:因为shared_ptr的开销,Shared等于true的性能会比Shared等于false的性能差一些。
+
 ```c++
 #include <bvar/bvar.h>
 #include <bvar/multi_dimension.h>
@@ -523,6 +530,20 @@ int request_count = get_request_count(request_label_list);
     * store(bvar)
     * return bvar
 
+### delete_stats
+
+根据指定label删除对应的单维度统计项bvar。
+
+bvar::MultiDimension的模板参数Shared,默认为false:
+1. 如果Shared等于false,get_stats返回的是模板参数T的指针,delete_stats无法保证没有使用者,所以无法安全删除bvar。
+2. 如果Shared等于true,get_stats返回的是模板参数T的shared_ptr,delete_stats可以安全删除bvar。
+
+### clear_stats
+
+清理所有单维度统计项bvar。
+
+安全性同样受bvar::MultiDimension的模板参数Shared的影响,见delete_stats一节说明。
+
 **bvar的生命周期**
 
 
label对应的单维度统计项bvar存储在多维度统计项(mbvar)中,当mbvar析构的时候会释放自身所有bvar,所以用户必须保证在mbvar的生命周期之内操作bvar,在mbvar生命周期外访问bvar的行为未定义,极有可能出core。
diff --git a/src/bvar/detail/combiner.h b/src/bvar/detail/combiner.h
index 3b442180..cae1b8ea 100644
--- a/src/bvar/detail/combiner.h
+++ b/src/bvar/detail/combiner.h
@@ -205,12 +205,10 @@ friend class GlobalValue<self_type>;
         // 
         // NOTE: Only available to non-atomic types.
         template <typename Op>
-        void merge_global(const Op &op, self_shared_type c = NULL) {
-            if (NULL == c) {
-                c = combiner.lock();
-            }
-            if (NULL != c) {
-                GlobalValue<self_type> g(this, c.get());
+        void merge_global(const Op &op, self_shared_type& c) {
+            const self_shared_type& c_ref = NULL != c ? c : combiner.lock();
+            if (NULL != c_ref) {
+                GlobalValue<self_type> g(this, c_ref.get());
                 element.merge_global(op, g);
             }
         }
diff --git a/src/bvar/multi_dimension.h b/src/bvar/multi_dimension.h
index 9163e346..ad352a02 100644
--- a/src/bvar/multi_dimension.h
+++ b/src/bvar/multi_dimension.h
@@ -20,6 +20,8 @@
 #ifndef BVAR_MULTI_DIMENSION_H
 #define BVAR_MULTI_DIMENSION_H
 
+#include <memory>
+#include <type_traits>
 #include "butil/logging.h"                           // LOG
 #include "butil/macros.h"                            // BAIDU_CASSERT
 #include "butil/scoped_lock.h"                       // BAIDU_SCOPE_LOCK
@@ -35,8 +37,15 @@ namespace bvar {
 // 2. KeyType::value_type must be std::string.
 // 3. KeyType::size() returns the number of labels.
 // 4. KeyType::push_back() adds a label to the end of the container.
-template <typename T, typename KeyType = std::list<std::string>>
+//
+// If `Shared' is false, `get_stats' returns a raw pointer,
+// `delete_stats' and `clear_stats' are not thread safe.
+// If `Shared' is true, `get_stats` returns a shared_ptr,
+// `delete_stats' and `clear_stats' are thread safe.
+// Note: The shared mode may be less performant than the non-shared mode.
+template <typename T, typename KeyType = std::list<std::string>, bool Shared = 
false>
 class MultiDimension : public MVariable<KeyType> {
+    typedef std::shared_ptr<T> shared_value_type;
 public:
     enum STATS_OP {
         READ_ONLY,
@@ -45,7 +54,7 @@ public:
 
     typedef KeyType key_type;
     typedef T value_type;
-    typedef T* value_ptr_type;
+    typedef typename std::conditional<Shared, shared_value_type, T*>::type 
value_ptr_type;
     typedef MVariable<key_type> Base;
 
     struct KeyHash {
@@ -102,11 +111,15 @@ public:
     // 2. K::value_type must be able to convert to std::string and 
butil::StringPiece
     //    through operator std::string() function and operator 
butil::StringPiece() function.
     // 3. K::value_type must be able to compare with std::string.
+    //
+    // Returns a shared_ptr if `Shared' is true, otherwise returns a raw 
pointer.
     template <typename K = key_type>
-    T* get_stats(const K& labels_value) {
+    value_ptr_type get_stats(const K& labels_value) {
         return get_stats_impl(labels_value, READ_OR_INSERT);
     }
 
+    // `delete_stats' and `clear_stats' are thread safe
+    // if `Shared' is true, otherwise not.
     // Remove stat so those not count and dump
     template <typename K = key_type>
     void delete_stats(const K& labels_value);
@@ -133,7 +146,7 @@ public:
     // Return real bvar pointer if labels_name exist, NULL otherwise.
     // CAUTION!!! Just For Debug!!!
     template <typename K = key_type>
-    T* get_stats_read_only(const K& labels_value) {
+    value_ptr_type get_stats_read_only(const K& labels_value) {
         return get_stats_impl(labels_value);
     }
 
@@ -141,17 +154,18 @@ public:
     // Return real bvar pointer if labels_name exist, otherwise(not exist) 
create bvar pointer.
     // CAUTION!!! Just For Debug!!!
     template <typename K = key_type>
-    T* get_stats_read_or_insert(const K& labels_value, bool* do_write = NULL) {
+    value_ptr_type get_stats_read_or_insert(const K& labels_value, bool* 
do_write = NULL) {
         return get_stats_impl(labels_value, READ_OR_INSERT, do_write);
     }
 #endif
 
 private:
     template <typename K>
-    T* get_stats_impl(const K& labels_value);
+    value_ptr_type get_stats_impl(const K& labels_value);
 
     template <typename K>
-    T* get_stats_impl(const K& labels_value, STATS_OP stats_op, bool* do_write 
= NULL);
+    value_ptr_type get_stats_impl(
+        const K& labels_value, STATS_OP stats_op, bool* do_write = NULL);
 
     template <typename K>
     static typename std::enable_if<butil::is_same<K, key_type>::value>::type
@@ -162,11 +176,8 @@ private:
     template <typename K>
     static typename std::enable_if<!butil::is_same<K, key_type>::value>::type
     insert_metrics_map(MetricMap& bg, const K& labels_value, op_value_type 
metric) {
-        key_type labels_value_str;
-        for (auto& label : labels_value) {
-            // key_type::value_type must be able to convert to std::string.
-            labels_value_str.push_back(std::string(label));
-        }
+        // key_type::value_type must be able to convert to std::string.
+        key_type labels_value_str(labels_value.cbegin(), labels_value.cend());
         bg.insert(labels_value_str, metric);
     }
 
@@ -192,7 +203,27 @@ private:
     void delete_stats();
     
     static size_t init_flatmap(MetricMap& bg);
-    
+
+    // If Shared is true, return std::shared_ptr, otherwise return raw pointer.
+    template <bool S = Shared>
+    typename std::enable_if<S, value_ptr_type>::type  new_value() {
+        return std::make_shared<value_type>();
+    }
+    template <bool S = Shared>
+    typename std::enable_if<!S, value_ptr_type>::type  new_value() {
+        return new value_type();
+    }
+
+    // If Shared is true, reset std::shared_ptr, otherwise delete raw pointer.
+    template <bool S = Shared>
+    typename std::enable_if<S>::type delete_value(value_ptr_type& v) {
+        v.reset();
+    }
+    template <bool S = Shared>
+    typename std::enable_if<!S>::type delete_value(value_ptr_type& v) {
+        delete v;
+    }
+
     size_t _max_stats_count;
     MetricMapDBD _metric_map;
 };
diff --git a/src/bvar/multi_dimension_inl.h b/src/bvar/multi_dimension_inl.h
index 15378248..c407567c 100644
--- a/src/bvar/multi_dimension_inl.h
+++ b/src/bvar/multi_dimension_inl.h
@@ -21,6 +21,7 @@
 #define BVAR_MULTI_DIMENSION_INL_H
 
 #include <gflags/gflags_declare.h>
+#include "butil/compiler_specific.h"
 
 namespace bvar {
 
@@ -34,43 +35,43 @@ static const std::string ALLOW_UNUSED METRIC_TYPE_SUMMARY = 
"summary";
 static const std::string ALLOW_UNUSED METRIC_TYPE_HISTOGRAM = "histogram";
 static const std::string ALLOW_UNUSED METRIC_TYPE_GAUGE = "gauge";
 
-template <typename T, typename KeyType>
-MultiDimension<T, KeyType>::MultiDimension(const key_type& labels)
+template <typename T, typename KeyType, bool Shared>
+MultiDimension<T, KeyType, Shared>::MultiDimension(const key_type& labels)
     : Base(labels)
     , _max_stats_count(FLAGS_max_multi_dimension_stats_count) {
     _metric_map.Modify(init_flatmap);
 }
 
-template <typename T, typename KeyType>
-MultiDimension<T, KeyType>::MultiDimension(const butil::StringPiece& name,
+template <typename T, typename KeyType, bool Shared>
+MultiDimension<T, KeyType, Shared>::MultiDimension(const butil::StringPiece& 
name,
                                            const key_type& labels)
     : MultiDimension(labels) {
     this->expose(name);
 }
 
-template <typename T, typename KeyType>
-MultiDimension<T, KeyType>::MultiDimension(const butil::StringPiece& prefix,
+template <typename T, typename KeyType, bool Shared>
+MultiDimension<T, KeyType, Shared>::MultiDimension(const butil::StringPiece& 
prefix,
                                            const butil::StringPiece& name,
                                            const key_type& labels)
     : MultiDimension(labels) {
     this->expose_as(prefix, name);
 }
 
-template <typename T, typename KeyType>
-MultiDimension<T, KeyType>::~MultiDimension() {
+template <typename T, typename KeyType, bool Shared>
+MultiDimension<T, KeyType, Shared>::~MultiDimension() {
     this->hide();
     delete_stats();
 }
 
-template <typename T, typename KeyType>
-size_t MultiDimension<T, KeyType>::init_flatmap(MetricMap& bg) {
+template <typename T, typename KeyType, bool Shared>
+size_t MultiDimension<T, KeyType, Shared>::init_flatmap(MetricMap& bg) {
     // size = 1 << 13
     CHECK_EQ(0, bg.init(8192, 80));
-    return (size_t)1;
+    return 1;
 }
 
-template <typename T, typename KeyType>
-size_t MultiDimension<T, KeyType>::count_stats() {
+template <typename T, typename KeyType, bool Shared>
+size_t MultiDimension<T, KeyType, Shared>::count_stats() {
     MetricMapScopedPtr metric_map_ptr;
     if (_metric_map.Read(&metric_map_ptr) != 0) {
         LOG(ERROR) << "Fail to read dbd";
@@ -79,9 +80,9 @@ size_t MultiDimension<T, KeyType>::count_stats() {
     return metric_map_ptr->size();
 }
 
-template <typename T, typename KeyType>
+template <typename T, typename KeyType, bool Shared>
 template <typename K>
-void MultiDimension<T, KeyType>::delete_stats(const K& labels_value) {
+void MultiDimension<T, KeyType, Shared>::delete_stats(const K& labels_value) {
     if (is_valid_lables_value(labels_value)) {
         // Because there are two copies(foreground and background) in DBD,
         // we need to use an empty tmp_metric, get the deleted value of
@@ -93,35 +94,35 @@ void MultiDimension<T, KeyType>::delete_stats(const K& 
labels_value) {
         };
         _metric_map.Modify(erase_fn);
         if (tmp_metric) {
-            delete tmp_metric;
+            delete_value(tmp_metric);
         }
     }
 }
 
-template <typename T, typename KeyType>
-void MultiDimension<T, KeyType>::delete_stats() {
+template <typename T, typename KeyType, bool Shared>
+void MultiDimension<T, KeyType, Shared>::delete_stats() {
     // Because there are two copies(foreground and background) in DBD, we need 
to use an empty tmp_map,
     // swap two copies with empty, and get the value of second copy into 
tmp_map,
     // then traversal tmp_map and delete bvar object,
     // which can prevent the bvar object from being deleted twice.
     MetricMap tmp_map;
     CHECK_EQ(0, tmp_map.init(8192, 80));
-    auto clear_fn = [&tmp_map](MetricMap& map) {
+    auto clear_fn = [&tmp_map](MetricMap& map) -> size_t {
         if (!tmp_map.empty()) {
             tmp_map.clear();
         }
         tmp_map.swap(map);
-        return (size_t)1;
+        return 1;
     };
     int ret = _metric_map.Modify(clear_fn);
     CHECK_EQ(1, ret);
-    for (auto &kv : tmp_map) {
-        delete kv.second;
+    for (auto& kv : tmp_map) {
+        delete_value(kv.second);
     }
 }
 
-template <typename T, typename KeyType>
-void MultiDimension<T, KeyType>::list_stats(std::vector<key_type>* names) {
+template <typename T, typename KeyType, bool Shared>
+void MultiDimension<T, KeyType, Shared>::list_stats(std::vector<key_type>* 
names) {
     if (names == NULL) {
         return;
     }
@@ -137,58 +138,60 @@ void MultiDimension<T, 
KeyType>::list_stats(std::vector<key_type>* names) {
     }
 }
 
-template <typename T, typename KeyType>
+template <typename T, typename KeyType, bool Shared>
 template <typename K>
-T* MultiDimension<T, KeyType>::get_stats_impl(const K& labels_value) {
+typename MultiDimension<T, KeyType, Shared>::value_ptr_type
+MultiDimension<T, KeyType, Shared>::get_stats_impl(const K& labels_value) {
     if (!is_valid_lables_value(labels_value)) {
-        return nullptr;
+        return NULL;
     }
     MetricMapScopedPtr metric_map_ptr;
     if (_metric_map.Read(&metric_map_ptr) != 0) {
         LOG(ERROR) << "Fail to read dbd";
-        return nullptr;
+        return NULL;
     }
 
     auto it = metric_map_ptr->seek(labels_value);
-    if (it == nullptr) {
-        return nullptr;
+    if (NULL == it) {
+        return NULL;
     }
     return (*it);
 }
 
-template <typename T, typename KeyType>
+template <typename T, typename KeyType, bool Shared>
 template <typename K>
-T* MultiDimension<T, KeyType>::get_stats_impl(
+typename MultiDimension<T, KeyType, Shared>::value_ptr_type
+MultiDimension<T, KeyType, Shared>::get_stats_impl(
     const K& labels_value, STATS_OP stats_op, bool* do_write) {
     if (!is_valid_lables_value(labels_value)) {
-        return nullptr;
+        return NULL;
     }
     {
         MetricMapScopedPtr metric_map_ptr;
         if (0 != _metric_map.Read(&metric_map_ptr)) {
             LOG(ERROR) << "Fail to read dbd";
-            return nullptr;
+            return NULL;
         }
 
         auto it = metric_map_ptr->seek(labels_value);
         if (NULL != it) {
             return (*it);
         } else if (READ_ONLY == stats_op) {
-            return nullptr;
+            return NULL;
         }
 
         if (metric_map_ptr->size() > _max_stats_count) {
             LOG(ERROR) << "Too many stats seen, overflow detected, max stats 
count="
                        << _max_stats_count;
-            return nullptr;
+            return NULL;
         }
     }
 
-    // Because DBD has two copies(foreground and background) MetricMap, both 
copies need to be modify,
+    // Because DBD has two copies(foreground and background) MetricMap, both 
copies need to be modified,
     // In order to avoid new duplicate bvar object, need use cache_metric to 
cache the new bvar object,
     // In this way, when modifying the second copy, can directly use the 
cache_metric bvar object.
     op_value_type cache_metric = NULL;
-    auto insert_fn = [&labels_value, &cache_metric, &do_write](MetricMap& bg) {
+    auto insert_fn = [this, &labels_value, &cache_metric, 
&do_write](MetricMap& bg) {
         auto bg_metric = bg.seek(labels_value);
         if (NULL != bg_metric) {
             cache_metric = *bg_metric;
@@ -199,7 +202,7 @@ T* MultiDimension<T, KeyType>::get_stats_impl(
         }
 
         if (NULL == cache_metric) {
-            cache_metric = new T();
+            cache_metric = new_value();
         }
         insert_metrics_map(bg, labels_value, cache_metric);
         return 1;
@@ -208,21 +211,21 @@ T* MultiDimension<T, KeyType>::get_stats_impl(
     return cache_metric;
 }
 
-template <typename T, typename KeyType>
-void MultiDimension<T, KeyType>::clear_stats() {
+template <typename T, typename KeyType, bool Shared>
+void MultiDimension<T, KeyType, Shared>::clear_stats() {
     delete_stats();
 }
 
-template <typename T, typename KeyType>
+template <typename T, typename KeyType, bool Shared>
 template <typename K>
-bool MultiDimension<T, KeyType>::has_stats(const K& labels_value) {
-    return get_stats_impl(labels_value) != nullptr;
+bool MultiDimension<T, KeyType, Shared>::has_stats(const K& labels_value) {
+    return get_stats_impl(labels_value) != NULL;
 }
 
-template <typename T, typename KeyType>
+template <typename T, typename KeyType, bool Shared>
 template <typename U>
 typename std::enable_if<!butil::is_same<LatencyRecorder, U>::value, 
size_t>::type
-MultiDimension<T, KeyType>::dump_impl(Dumper* dumper, const DumpOptions* 
options) {
+MultiDimension<T, KeyType, Shared>::dump_impl(Dumper* dumper, const 
DumpOptions* options) {
     std::vector<key_type> label_names;
     list_stats(&label_names);
     if (label_names.empty() || !dumper->dump_comment(this->name(), 
METRIC_TYPE_GAUGE)) {
@@ -230,8 +233,8 @@ MultiDimension<T, KeyType>::dump_impl(Dumper* dumper, const 
DumpOptions* options
     }
     size_t n = 0;
     for (auto &label_name : label_names) {
-        T* bvar = get_stats_impl(label_name);
-        if (!bvar) {
+        value_ptr_type bvar = get_stats_impl(label_name);
+        if (NULL == bvar) {
             continue;
         }
         std::ostringstream oss;
@@ -246,10 +249,10 @@ MultiDimension<T, KeyType>::dump_impl(Dumper* dumper, 
const DumpOptions* options
     return n;
 }
 
-template <typename T, typename KeyType>
+template <typename T, typename KeyType, bool Shared>
 template <typename U>
 typename std::enable_if<butil::is_same<LatencyRecorder, U>::value, 
size_t>::type
-MultiDimension<T, KeyType>::dump_impl(Dumper* dumper, const DumpOptions*) {
+MultiDimension<T, KeyType, Shared>::dump_impl(Dumper* dumper, const 
DumpOptions*) {
     std::vector<key_type> label_names;
     list_stats(&label_names);
     if (label_names.empty()) {
@@ -299,8 +302,8 @@ MultiDimension<T, KeyType>::dump_impl(Dumper* dumper, const 
DumpOptions*) {
     // max_latency comment
     dumper->dump_comment(this->name() + "_max_latency", METRIC_TYPE_GAUGE);
     for (auto &label_name : label_names) {
-        bvar::LatencyRecorder* bvar = get_stats_impl(label_name);
-        if (!bvar) {
+        LatencyRecorder* bvar = get_stats_impl(label_name);
+        if (NULL == bvar) {
             continue;
         }
         std::ostringstream oss_max_latency_key;
@@ -313,8 +316,8 @@ MultiDimension<T, KeyType>::dump_impl(Dumper* dumper, const 
DumpOptions*) {
     // qps comment
     dumper->dump_comment(this->name() + "_qps", METRIC_TYPE_GAUGE);
     for (auto &label_name : label_names) {
-        bvar::LatencyRecorder* bvar = get_stats_impl(label_name);
-        if (!bvar) {
+        LatencyRecorder* bvar = get_stats_impl(label_name);
+        if (NULL == bvar) {
             continue;
         }
         std::ostringstream oss_qps_key;
@@ -327,8 +330,8 @@ MultiDimension<T, KeyType>::dump_impl(Dumper* dumper, const 
DumpOptions*) {
     // count comment
     dumper->dump_comment(this->name() + "_count", METRIC_TYPE_COUNTER);
     for (auto &label_name : label_names) {
-        bvar::LatencyRecorder* bvar = get_stats_impl(label_name);
-        if (!bvar) {
+        LatencyRecorder* bvar = get_stats_impl(label_name);
+        if (NULL == bvar) {
             continue;
         }
         std::ostringstream oss_count_key;
@@ -340,8 +343,8 @@ MultiDimension<T, KeyType>::dump_impl(Dumper* dumper, const 
DumpOptions*) {
     return n;
 }
 
-template <typename T, typename KeyType>
-void MultiDimension<T, KeyType>::make_dump_key(std::ostream& os, const 
key_type& labels_value,
+template <typename T, typename KeyType, bool Shared>
+void MultiDimension<T, KeyType, Shared>::make_dump_key(std::ostream& os, const 
key_type& labels_value,
                                                const std::string& suffix, int 
quantile) {
     os << this->name();
     if (!suffix.empty()) {
@@ -350,8 +353,8 @@ void MultiDimension<T, 
KeyType>::make_dump_key(std::ostream& os, const key_type&
     make_labels_kvpair_string(os, labels_value, quantile);
 }
 
-template <typename T, typename KeyType>
-void MultiDimension<T, KeyType>::make_labels_kvpair_string(
+template <typename T, typename KeyType, bool Shared>
+void MultiDimension<T, KeyType, Shared>::make_labels_kvpair_string(
     std::ostream& os, const key_type& labels_value, int quantile) {
     os << "{";
     auto label_key = this->_labels.cbegin();
@@ -368,9 +371,9 @@ void MultiDimension<T, KeyType>::make_labels_kvpair_string(
     os << "}";
 }
 
-template <typename T, typename KeyType>
+template <typename T, typename KeyType, bool Shared>
 template <typename K>
-bool MultiDimension<T, KeyType>::is_valid_lables_value(const K& labels_value) 
const {
+bool MultiDimension<T, KeyType, Shared>::is_valid_lables_value(const K& 
labels_value) const {
     if (this->count_labels() != labels_value.size()) {
         LOG(ERROR) << "Invalid labels count" << this->count_labels()
                    << " != " << labels_value.size();
@@ -379,8 +382,8 @@ bool MultiDimension<T, 
KeyType>::is_valid_lables_value(const K& labels_value) co
     return true;
 }
 
-template <typename T, typename KeyType>
-void MultiDimension<T, KeyType>::describe(std::ostream& os) {
+template <typename T, typename KeyType, bool Shared>
+void MultiDimension<T, KeyType, Shared>::describe(std::ostream& os) {
     os << "{\"name\" : \"" << this->name() << "\", \"labels\" : [";
     char comma[3] = {'\0', ' ', '\0'};
     for (auto& label : this->_labels) {
diff --git a/test/bvar_multi_dimension_unittest.cpp 
b/test/bvar_multi_dimension_unittest.cpp
index 8d0e479d..a04ab4b7 100644
--- a/test/bvar_multi_dimension_unittest.cpp
+++ b/test/bvar_multi_dimension_unittest.cpp
@@ -21,7 +21,7 @@
 #include <cstddef>
 #include <memory>
 #include <iostream>
-#include <set>
+#include <array>
 #include <string>
 #include <gflags/gflags.h>
 #include <gtest/gtest.h>
@@ -497,3 +497,169 @@ TEST_F(MultiDimensionTest, test_hash) {
     LOG(INFO) << "Hash fun performance:\n" << oss.str();
 }
 
+
+class MyStringView {
+public:
+    MyStringView() : _ptr(NULL), _len(0) {}
+    MyStringView(const char* str)
+        : _ptr(str),
+          _len(str == NULL ? 0 : strlen(str)) {}
+#if __cplusplus >= 201703L
+    MyStringView(const std::string_view& str)
+        : _ptr(str.data()), _len(str.size()) {}
+#endif // __cplusplus >= 201703L
+    MyStringView(const std::string& str)
+        : _ptr(str.data()), _len(str.size()) {}
+    MyStringView(const char* offset, size_t len)
+        : _ptr(offset), _len(len) {}
+
+    const char* data() const { return _ptr; }
+    size_t size() const { return _len; }
+
+    // Converts to `std::basic_string`.
+    explicit operator std::string() const {
+        if (NULL == _ptr) {
+            return {};
+        }
+        return {_ptr, size()};
+    }
+
+    // Converts to butil::StringPiece.
+    explicit operator butil::StringPiece() const {
+        if (NULL == _ptr) {
+            return {};
+        }
+        return {_ptr, size()};
+    }
+
+private:
+    const char* _ptr;
+    size_t _len;
+};
+
+bool operator==(const MyStringView& x, const std::string& y) {
+    if (x.size() != y.size()) {
+        return false;
+    }
+
+    return butil::StringPiece::wordmemcmp(x.data(), y.data(), x.size()) == 0;
+}
+
+bool operator==(const std::string& x, const MyStringView& y) {
+    if (x.size() != y.size()) {
+        return false;
+    }
+
+    return butil::StringPiece::wordmemcmp(x.data(), y.data(), x.size()) == 0;
+}
+
+static int g_exposed_count = 0;
+
+template <typename KeyType, typename ValueType>
+static void TestLabels() {
+    std::string mbvar_name = butil::string_printf("my_madder_%d", 
g_exposed_count);
+    KeyType labels{"idc", "method", "status"};
+    bvar::MultiDimension<bvar::Adder<int>, KeyType> my_madder(mbvar_name, 
labels);
+    ASSERT_EQ(labels.size(), my_madder.count_labels());
+    ASSERT_STREQ(mbvar_name.c_str(), my_madder.name().c_str());
+    ASSERT_EQ(labels, my_madder.labels());
+
+    using ItemType = typename ValueType::value_type;
+    ValueType labels_value{ItemType("cv"), ItemType("post"), ItemType("200")};
+    bvar::Adder<int>* adder = my_madder.get_stats(labels_value);
+    ASSERT_NE(nullptr, adder);
+    ASSERT_TRUE(my_madder.has_stats(labels_value));
+    ASSERT_EQ((size_t)1, my_madder.count_stats());
+    {
+        // Compatible with old API.
+        bvar::Adder<int>* temp = my_madder.get_stats({"cv", "post", "200"});
+        ASSERT_EQ(adder, temp);
+    }
+    *adder << g_exposed_count;
+    ASSERT_EQ(g_exposed_count, adder->get_value());
+    my_madder.delete_stats(labels_value);
+    ASSERT_FALSE(my_madder.has_stats(labels_value));
+    ASSERT_EQ((size_t)0, my_madder.count_stats());
+}
+
+TEST_F(MultiDimensionTest, labels) {
+    TestLabels<std::list<std::string>, std::list<std::string>>();
+    TestLabels<std::list<std::string>, std::vector<std::string>>();
+    TestLabels<std::list<std::string>, std::array<std::string, 3>>();
+
+    TestLabels<std::vector<std::string>, std::list<std::string>>();
+    TestLabels<std::vector<std::string>, std::vector<std::string>>();
+    TestLabels<std::vector<std::string>, std::array<std::string, 3>>();
+
+#if __cplusplus >= 201703L
+    TestLabels<std::list<std::string>, std::list<std::string_view>>();
+    TestLabels<std::list<std::string>, std::vector<std::string_view>>();
+    TestLabels<std::list<std::string>, std::array<std::string_view, 3>>();
+#endif // __cplusplus >= 201703L
+
+    TestLabels<std::vector<std::string>, std::list<butil::StringPiece>>();
+    TestLabels<std::vector<std::string>, std::vector<butil::StringPiece>>();
+    TestLabels<std::vector<std::string>, std::array<butil::StringPiece, 3>>();
+
+    TestLabels<std::list<std::string>, std::list<MyStringView>>();
+    TestLabels<std::list<std::string>, std::vector<MyStringView>>();
+    TestLabels<std::list<std::string>, std::array<MyStringView, 3>>();
+
+    TestLabels<std::vector<std::string>, std::list<MyStringView>>();
+    TestLabels<std::vector<std::string>, std::vector<MyStringView>>();
+    TestLabels<std::vector<std::string>, std::array<MyStringView, 3>>();
+}
+
+std::array<const char*, 3> g_labels_value{"idc", "post", "200"};
+bool g_shared_stop = false;
+
+void* get_shared_adder_thread(void* arg) {
+    auto my_madder =
+        (bvar::MultiDimension<bvar::Adder<int>, std::vector<std::string>, 
true>*)arg;
+    while (!g_shared_stop) {
+        auto adder = my_madder->get_stats(g_labels_value);
+        EXPECT_NE(nullptr, adder);
+        *adder << 1;
+    }
+    return NULL;
+}
+
+void* delete_shared_adder_thread(void* arg) {
+    auto my_madder =
+        (bvar::MultiDimension<bvar::Adder<int>, std::vector<std::string>, 
true>*)arg;
+    while (!g_shared_stop) {
+        my_madder->delete_stats(g_labels_value);
+    }
+    return NULL;
+}
+
+TEST_F(MultiDimensionTest, shared) {
+    bvar::MultiDimension<bvar::Adder<int>, std::vector<std::string>, true> 
my_madder(
+        "my_adder", {"idc", "method", "status"});
+    std::shared_ptr<bvar::Adder<int>> adder = 
my_madder.get_stats(g_labels_value);
+    ASSERT_NE(nullptr, adder);
+    ASSERT_TRUE(my_madder.has_stats(g_labels_value));
+    ASSERT_EQ((size_t)1, my_madder.count_stats());
+
+    *adder << 1;
+    ASSERT_EQ(1, adder->get_value());
+    my_madder.delete_stats(g_labels_value);
+    *adder << 1;
+    ASSERT_EQ(2, adder->get_value());
+    ASSERT_FALSE(my_madder.has_stats(g_labels_value));
+
+    const int get_num = 8;
+    std::vector<pthread_t> get_threads(get_num);
+    for (int i = 0; i < get_num; ++i) {
+        ASSERT_EQ(0, pthread_create(&get_threads[i], NULL, 
get_shared_adder_thread, &my_madder));
+    }
+    pthread_t delete_thread;
+    ASSERT_EQ(0, pthread_create(&delete_thread, NULL, 
delete_shared_adder_thread, &my_madder));
+    usleep(100 * 1000); // 100ms
+    g_shared_stop = true;
+    for (int i = 0; i < get_num; ++i) {
+        ASSERT_EQ(0, pthread_join(get_threads[i], NULL));
+    }
+    ASSERT_EQ(0, pthread_join(delete_thread, NULL));
+}
+
diff --git a/test/bvar_mvariable_unittest.cpp b/test/bvar_mvariable_unittest.cpp
index d72a2e5d..aca50d5f 100644
--- a/test/bvar_mvariable_unittest.cpp
+++ b/test/bvar_mvariable_unittest.cpp
@@ -117,118 +117,6 @@ TEST_F(MVariableTest, expose) {
     ASSERT_EQ(2, exposed_vars.size());
 }
 
-class MyStringView {
-public:
-    MyStringView() : _ptr(NULL), _len(0) {}
-    MyStringView(const char* str)
-        : _ptr(str),
-          _len(str == NULL ? 0 : strlen(str)) {}
-#if __cplusplus >= 201703L
-    MyStringView(const std::string_view& str)
-        : _ptr(str.data()), _len(str.size()) {}
-#endif // __cplusplus >= 201703L
-    MyStringView(const std::string& str)
-        : _ptr(str.data()), _len(str.size()) {}
-    MyStringView(const char* offset, size_t len)
-        : _ptr(offset), _len(len) {}
-
-    const char* data() const { return _ptr; }
-    size_t size() const { return _len; }
-
-    // Converts to `std::basic_string`.
-    explicit operator std::string() const {
-        if (NULL == _ptr) {
-            return {};
-        }
-        return {_ptr, size()};
-    }
-
-    // Converts to butil::StringPiece.
-    explicit operator butil::StringPiece() const {
-        if (NULL == _ptr) {
-            return {};
-        }
-        return {_ptr, size()};
-    }
-
-private:
-    const char* _ptr;
-    size_t _len;
-};
-
-bool operator==(const MyStringView& x, const std::string& y) {
-    if (x.size() != y.size()) {
-        return false;
-    }
-
-    return butil::StringPiece::wordmemcmp(x.data(), y.data(), x.size()) == 0;
-}
-
-bool operator==(const std::string& x, const MyStringView& y) {
-    if (x.size() != y.size()) {
-        return false;
-    }
-
-    return butil::StringPiece::wordmemcmp(x.data(), y.data(), x.size()) == 0;
-}
-
-static int g_exposed_count = 0;
-
-template <typename KeyType, typename ValueType>
-static void TestLabels() {
-    std::string mbvar_name = butil::string_printf("my_madder_%d", 
g_exposed_count);
-    KeyType labels{"idc", "method", "status"};
-    bvar::MultiDimension<bvar::Adder<int>, KeyType> my_madder(mbvar_name, 
labels);
-    ASSERT_EQ(labels.size(), my_madder.count_labels());
-    ASSERT_STREQ(mbvar_name.c_str(), my_madder.name().c_str());
-    ASSERT_EQ(labels, my_madder.labels());
-
-    using ItemType = typename ValueType::value_type;
-    ValueType labels_value{ItemType("cv"), ItemType("post"), ItemType("200")};
-    bvar::Adder<int>* adder = my_madder.get_stats(labels_value);
-    ASSERT_NE(nullptr, adder);
-    ASSERT_TRUE(my_madder.has_stats(labels_value));
-    ASSERT_EQ((size_t)1, my_madder.count_stats());
-    {
-        // Compatible with old API.
-        bvar::Adder<int>* temp = my_madder.get_stats({"cv", "post", "200"});
-        ASSERT_EQ(adder, temp);
-    }
-    *adder << g_exposed_count;
-    ASSERT_EQ(g_exposed_count, adder->get_value());
-    my_madder.delete_stats(labels_value);
-    ASSERT_FALSE(my_madder.has_stats(labels_value));
-    ASSERT_EQ((size_t)0, my_madder.count_stats());
-}
-
-TEST_F(MVariableTest, labels) {
-    TestLabels<std::list<std::string>, std::list<std::string>>();
-    TestLabels<std::list<std::string>, std::vector<std::string>>();
-    TestLabels<std::list<std::string>, std::array<std::string, 3>>();
-
-    TestLabels<std::vector<std::string>, std::list<std::string>>();
-    TestLabels<std::vector<std::string>, std::vector<std::string>>();
-    TestLabels<std::vector<std::string>, std::array<std::string, 3>>();
-
-#if __cplusplus >= 201703L
-    TestLabels<std::list<std::string>, std::list<std::string_view>>();
-    TestLabels<std::list<std::string>, std::vector<std::string_view>>();
-    TestLabels<std::list<std::string>, std::array<std::string_view, 3>>();
-#endif // __cplusplus >= 201703L
-
-    TestLabels<std::vector<std::string>, std::list<butil::StringPiece>>();
-    TestLabels<std::vector<std::string>, std::vector<butil::StringPiece>>();
-    TestLabels<std::vector<std::string>, std::array<butil::StringPiece, 3>>();
-
-    TestLabels<std::list<std::string>, std::list<MyStringView>>();
-    TestLabels<std::list<std::string>, std::vector<MyStringView>>();
-    TestLabels<std::list<std::string>, std::array<MyStringView, 3>>();
-
-    TestLabels<std::vector<std::string>, std::list<MyStringView>>();
-    TestLabels<std::vector<std::string>, std::vector<MyStringView>>();
-    TestLabels<std::vector<std::string>, std::array<MyStringView, 3>>();
-}
-
 TEST_F(MVariableTest, dump) {
     std::string old_bvar_dump_interval;
     std::string old_mbvar_dump;
@@ -302,7 +190,6 @@ TEST_F(MVariableTest, dump) {
 }
 
 TEST_F(MVariableTest, test_describe_exposed) {
-    std::list<std::string> labels_value1 {"bj", "get", "200"};
     std::string bvar_name("request_count_describe");
     bvar::MultiDimension<bvar::Adder<int> > my_madder1(bvar_name, labels);
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to