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

wwbmmm 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 1ff5f3fc Support generics for MultiDimension APIs (#3026)
1ff5f3fc is described below

commit 1ff5f3fc5a210da66a8855aa6a355e598005cbd9
Author: Bright Chen <chenguangmin...@foxmail.com>
AuthorDate: Wed Jul 23 10:14:05 2025 +0800

    Support generics for MultiDimension APIs (#3026)
    
    * Support generics for MVariable api
    
    * Update document
---
 docs/cn/mbvar_c++.md                            |  87 ++++++++++-
 src/brpc/builtin/prometheus_metrics_service.cpp |   2 +-
 src/butil/containers/flat_map.h                 |   4 +-
 src/butil/containers/flat_map_inl.h             |   1 -
 src/butil/strings/string_piece.h                |  16 +++
 src/bvar/multi_dimension.h                      | 113 +++++++++++----
 src/bvar/multi_dimension_inl.h                  | 184 +++++++++++-------------
 src/bvar/mvariable.cpp                          |  45 +++---
 src/bvar/mvariable.h                            |  33 +++--
 src/bvar/variable.cpp                           |   2 +-
 test/CMakeLists.txt                             |  10 +-
 test/bvar_mvariable_unittest.cpp                | 165 +++++++++++++++------
 12 files changed, 432 insertions(+), 230 deletions(-)

diff --git a/docs/cn/mbvar_c++.md b/docs/cn/mbvar_c++.md
index b7a0d417..8a81e714 100644
--- a/docs/cn/mbvar_c++.md
+++ b/docs/cn/mbvar_c++.md
@@ -331,11 +331,13 @@ size_t mbvar_list_exposed(std::vector<std::string>* 
names) {
 
 多维度统计的实现,主要提供bvar的获取、列举等功能。
 
+为了兼容旧的用法,KeyType默认类型是std::list<std::string>。KeyType必须是(STL或者自定义)容器,value_type必须是std::string。
+
 ## constructor
 
 有三个构造函数:
 ```c++
-template <typename T>
+template <typename T, typename KeyType = std::list<std::string>>
 class MultiDimension : public MVariable {
 public:
     // 不建议使用
@@ -398,7 +400,7 @@ bvar::MultiDimension<bvar::Adder<int> > 
g_request_count("foo_bar", "request_coun
 
 ## stats
 ```c++
-template <typename T>
+template <typename T, typename KeyType = std::list<std::string>>
 class MultiDimension : public MVariable {
 public:
     ...
@@ -410,7 +412,15 @@ public:
 ```
 
 ### get_stats
-根据指定label获取对应的单维度统计项bvar
+根据指定label获取对应的单维度统计项bvar。
+
+get_stats除了支持(默认)std::list<std::string>参数类型,也支持自定义参数类型,满足以下条件:
+1. (STL或者自定义)容器。
+2. K::value_type支持通过operator std::string()转换为std::string和通过operator 
butil::StringPiece()转换为butil::StringPiece。
+3. K::value_type支持和std::string进行比较。
+
+推荐使用不需要分配内存的容器(例如,std::array、absl::InlinedVector)和不需要拷贝字符串的数据结构(例如,const 
char*、std::string_view、butil::StringPieces),可以提高性能。
+
 ```c++
 #include <bvar/bvar.h>
 #include <bvar/multi_dimension.h>
@@ -420,7 +430,8 @@ namespace bar {
 // 定义一个全局的多维度mbvar变量
 bvar::MultiDimension<bvar::Adder<int> > g_request_count("request_count", 
{"idc", "method", "status"});
 
-int get_request_count(const std::list<std::string>& request_label) {
+template <typename K>
+int get_request_count(const K& request_label) {
     // 获取request_label对应的单维度bvar指针,比如:request_label = {"tc", "get", "200"}
     bvar::Adder<int> *request_adder = g_request_count.get_stats(request_label);
     // 判断指针非空
@@ -433,6 +444,70 @@ int get_request_count(const std::list<std::string>& 
request_label) {
     return request_adder->get_value();
 }
 
+std::list<std::string> request_label_list = {"tc", "get", "200"};
+int request_count = get_request_count(request_label_list);
+
+std::vector<std::string_view> request_label_list = {"tc", "get", "200"};
+int request_count = get_request_count(request_label_list);
+
+std::vector<butil::StringPiece> request_label_list = {"tc", "get", "200"};
+int request_count = get_request_count(request_label_list);
+
+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;
+}
+
+std::vector<MyStringView> request_label_list = {"tc", "get", "200"};
+int request_count = get_request_count(request_label_list);
+
 } // namespace bar
 } // namespace foo
 ```
@@ -462,7 +537,7 @@ public:
     size_t count_labels() const;
 };
 
-template <typename T>
+template <typename T, typename KeyType = std::list<std::string>>
 class MultiDimension : public MVariable {
 public:
     ...
@@ -536,7 +611,7 @@ size_t count_stats() {
 
 ## list
 ```c++
-template <typename T>
+template <typename T, typename KeyType = std::list<std::string>>
 class MultiDimension : public MVariable {
 public:
     ...
diff --git a/src/brpc/builtin/prometheus_metrics_service.cpp 
b/src/brpc/builtin/prometheus_metrics_service.cpp
index 4c5dd590..4efc24b9 100644
--- a/src/brpc/builtin/prometheus_metrics_service.cpp
+++ b/src/brpc/builtin/prometheus_metrics_service.cpp
@@ -232,7 +232,7 @@ int DumpPrometheusMetricsToIOBuf(butil::IOBuf* output) {
 
     if (bvar::FLAGS_bvar_max_dump_multi_dimension_metric_number > 0) {
         PrometheusMetricsDumper dumper_md(&os, g_server_info_prefix);
-        const int ndump_md = bvar::MVariable::dump_exposed(&dumper_md, NULL);
+        const int ndump_md = bvar::MVariableBase::dump_exposed(&dumper_md, 
NULL);
         if (ndump_md < 0) {
             return -1;
         }
diff --git a/src/butil/containers/flat_map.h b/src/butil/containers/flat_map.h
index 9ae72db9..54981b81 100644
--- a/src/butil/containers/flat_map.h
+++ b/src/butil/containers/flat_map.h
@@ -199,12 +199,12 @@ public:
     // Remove |key| and all associated value
     // Returns: 1 on erased, 0 otherwise.
     template <typename K2, bool Multi = _Multi>
-    typename std::enable_if<!Multi, size_t >::type
+    typename std::enable_if<!Multi, size_t>::type
     erase(const K2& key, mapped_type* old_value = NULL);
     // For `_Multi=true'.
     // Returns: num of value on erased, 0 otherwise.
     template <typename K2, bool Multi = _Multi>
-    typename std::enable_if<Multi, size_t >::type
+    typename std::enable_if<Multi, size_t>::type
     erase(const K2& key, std::vector<mapped_type>* old_values = NULL);
 
     // Remove all items. Allocated spaces are NOT returned by system.
diff --git a/src/butil/containers/flat_map_inl.h 
b/src/butil/containers/flat_map_inl.h
index 3b82b9ec..13444b2e 100644
--- a/src/butil/containers/flat_map_inl.h
+++ b/src/butil/containers/flat_map_inl.h
@@ -592,7 +592,6 @@ typename std::enable_if<!Multi, _T&>::type
 FlatMap<_K, _T, _H, _E, _S, _A, _M>::operator[](const key_type& key) {
     const size_t index = flatmap_mod(_hashfn(key), _nbucket);
     Bucket& first_node = _buckets[index];
-    // LOG(INFO) << "index=" << index;
     if (!first_node.is_valid()) {
         ++_size;
         if (_S) {
diff --git a/src/butil/strings/string_piece.h b/src/butil/strings/string_piece.h
index 0489b3cd..dbfd69e6 100644
--- a/src/butil/strings/string_piece.h
+++ b/src/butil/strings/string_piece.h
@@ -32,6 +32,9 @@
 
 #include <iosfwd>
 #include <string>
+#if __cplusplus >= 201703L
+#include <string_view>
+#endif // __cplusplus >= 201703L
 
 #include "butil/base_export.h"
 #include "butil/basictypes.h"
@@ -182,6 +185,11 @@ template <typename STRING_TYPE> class BasicStringPiece {
   BasicStringPiece(const value_type* str)
       : ptr_(str),
         length_((str == NULL) ? 0 : STRING_TYPE::traits_type::length(str)) {}
+#if __cplusplus >= 201703L
+  BasicStringPiece(
+    const std::basic_string_view<value_type, typename 
STRING_TYPE::traits_type>& str)
+      : ptr_(str.data()), length_(str.size()) {}
+#endif // __cplusplus >= 201703L
   BasicStringPiece(const STRING_TYPE& str)
       : ptr_(str.data()), length_(str.size()) {}
   BasicStringPiece(const value_type* offset, size_type len)
@@ -370,6 +378,14 @@ template <typename STRING_TYPE> class BasicStringPiece {
     return internal::substr(*this, pos, n);
   }
 
+  // Converts to `std::basic_string`.
+  explicit operator STRING_TYPE() const {
+    if (NULL == data()) {
+      return {};
+    }
+    return STRING_TYPE(data(), size());
+  }
+
  protected:
   const value_type* ptr_;
   size_type     length_;
diff --git a/src/bvar/multi_dimension.h b/src/bvar/multi_dimension.h
index cc249c5f..8eb55dc7 100644
--- a/src/bvar/multi_dimension.h
+++ b/src/bvar/multi_dimension.h
@@ -25,39 +25,54 @@
 #include "butil/scoped_lock.h"                       // BAIDU_SCOPE_LOCK
 #include "butil/containers/doubly_buffered_data.h"   // DBD
 #include "butil/containers/flat_map.h"               // butil::FlatMap
+#include "butil/strings/string_piece.h"
 #include "bvar/mvariable.h"
 
 namespace bvar {
 
-template <typename  T>
-class MultiDimension : public MVariable {
+// KeyType requirements:
+// 1. KeyType must be a container type with iterator, e.g. std::vector, 
std::list, std::set.
+// 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>>
+class MultiDimension : public MVariable<KeyType> {
 public:
-
     enum STATS_OP {
         READ_ONLY,
         READ_OR_INSERT,
     };
 
-    typedef MVariable Base;
-    typedef std::list<std::string> key_type;
+    typedef KeyType key_type;
     typedef T value_type;
     typedef T* value_ptr_type;
+    typedef MVariable<key_type> Base;
 
     struct KeyHash {
-        size_t operator() (const key_type& key) const {
+        template <typename K>
+        size_t operator() (const K& key) const {
             size_t hash_value = 0;
-            for (auto &k : key) {
-                hash_value += std::hash<std::string>()(k);
+            for (auto& k : key) {
+                hash_value += BUTIL_HASH_NAMESPACE::hash<butil::StringPiece>()(
+                    butil::StringPiece(k));
             }
             return hash_value;
         }
     };
+
+    struct KeyEqualTo {
+        template <typename K>
+        bool operator()(const key_type& k1, const K& k2) const {
+            return k1.size() == k2.size() &&
+                   std::equal(k1.cbegin(), k1.cend(), k2.cbegin());
+        }
+    };
     
     typedef value_ptr_type op_value_type;
-    typedef typename butil::FlatMap<key_type, op_value_type, KeyHash> 
MetricMap;
+    typedef butil::FlatMap<key_type, op_value_type, KeyHash, KeyEqualTo> 
MetricMap;
 
     typedef typename MetricMap::const_iterator MetricMapConstIterator;
-    typedef typename butil::DoublyBufferedData<MetricMap> MetricMapDBD;
+    typedef butil::DoublyBufferedData<MetricMap> MetricMapDBD;
     typedef typename MetricMapDBD::ScopedPtr MetricMapScopedPtr;
     
     explicit MultiDimension(const key_type& labels);
@@ -69,28 +84,39 @@ public:
                    const butil::StringPiece& name,
                    const key_type& labels);
 
-    ~MultiDimension();
+    ~MultiDimension() override;
 
     // Implement this method to print the variable into ostream.
-    void describe(std::ostream& os);
+    void describe(std::ostream& os) override;
 
-    // Dump real bvar pointer 
-    size_t dump(Dumper* dumper, const DumpOptions* options);
+    // Dump real bvar pointer
+    size_t dump(Dumper* dumper, const DumpOptions* options) override {
+        return dump_impl(dumper, options);
+    }
 
     // Get real bvar pointer object
     // Return real bvar pointer on success, NULL otherwise.
-    T* get_stats(const key_type& labels_value) {
+    // K requirements:
+    // 1. K must be a container type with iterator,
+    //    e.g. std::vector, std::list, std::set, std::array.
+    // 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.
+    template <typename K = key_type>
+    T* get_stats(const K& labels_value) {
         return get_stats_impl(labels_value, READ_OR_INSERT);
     }
 
     // Remove stat so those not count and dump
-    void delete_stats(const key_type& labels_value);
+    template <typename K = key_type>
+    void delete_stats(const K& labels_value);
 
     // Remove all stat
     void clear_stats();
 
     // True if bvar pointer exists
-    bool has_stats(const key_type& labels_value);
+    template <typename K = key_type>
+    bool has_stats(const K& labels_value);
 
     // Get number of stats
     size_t count_stats();
@@ -102,33 +128,61 @@ public:
     // Get real bvar pointer object 
     // Return real bvar pointer if labels_name exist, NULL otherwise.
     // CAUTION!!! Just For Debug!!!
-    T* get_stats_read_only(const key_type& labels_value) {
+    template <typename K = key_type>
+    T* get_stats_read_only(const K& labels_value) {
         return get_stats_impl(labels_value);
     }
 
     // Get real bvar pointer object 
     // Return real bvar pointer if labels_name exist, otherwise(not exist) 
create bvar pointer.
     // CAUTION!!! Just For Debug!!!
-    T* get_stats_read_or_insert(const key_type& labels_value, bool* do_write = 
NULL) {
+    template <typename K = key_type>
+    T* 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:
-    T* get_stats_impl(const key_type& labels_value);
+    template <typename K>
+    T* 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);
+
+    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) {
+        bg.insert(labels_value, metric);
+    }
+
+    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));
+        }
+        bg.insert(labels_value_str, metric);
+    }
 
-    T* get_stats_impl(const key_type& labels_value, STATS_OP stats_op, bool* 
do_write = NULL);
+    template <typename U = T>
+    typename std::enable_if<!butil::is_same<LatencyRecorder, U>::value, 
size_t>::type
+    dump_impl(Dumper* dumper, const DumpOptions* options);
 
-    void make_dump_key(std::ostream& os, 
-                       const key_type& labels_value, 
-                       const std::string& suffix = "", 
-                       const int quantile = 0);
+    template <typename U = T>
+    typename std::enable_if<butil::is_same<LatencyRecorder, U>::value, 
size_t>::type
+    dump_impl(Dumper* dumper, const DumpOptions* options);
 
-    void make_labels_kvpair_string(std::ostream& os, 
-                       const key_type& labels_value, 
-                       const int quantile);
+    void make_dump_key(std::ostream& os, const key_type& labels_value,
+                       const std::string& suffix = "",  int quantile = 0);
 
-    bool is_valid_lables_value(const key_type& labels_value) const;
+    void make_labels_kvpair_string(
+        std::ostream& os, const key_type& labels_value, int quantile);
+
+
+    template <typename K>
+    bool is_valid_lables_value(const K& labels_value) const;
     
     // Remove all stats so those not count and dump
     void delete_stats();
@@ -139,7 +193,6 @@ private:
     
     static size_t init_flatmap(MetricMap& bg);
     
-private:
     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 b7ef2b2d..15378248 100644
--- a/src/bvar/multi_dimension_inl.h
+++ b/src/bvar/multi_dimension_inl.h
@@ -34,51 +34,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>
-inline
-MultiDimension<T>::MultiDimension(const key_type& labels)
+template <typename T, typename KeyType>
+MultiDimension<T, KeyType>::MultiDimension(const key_type& labels)
     : Base(labels)
-    , _max_stats_count(FLAGS_max_multi_dimension_stats_count)
-{
+    , _max_stats_count(FLAGS_max_multi_dimension_stats_count) {
     _metric_map.Modify(init_flatmap);
 }
 
-template <typename T>
-inline
-MultiDimension<T>::MultiDimension(const butil::StringPiece& name,
-                                  const key_type& labels)
-    : MultiDimension(labels)
-{
+template <typename T, typename KeyType>
+MultiDimension<T, KeyType>::MultiDimension(const butil::StringPiece& name,
+                                           const key_type& labels)
+    : MultiDimension(labels) {
     this->expose(name);
 }
 
-template <typename T>
-inline
-MultiDimension<T>::MultiDimension(const butil::StringPiece& prefix,
-                                  const butil::StringPiece& name,
-                                  const key_type& labels)
-    : MultiDimension(labels)
-{
+template <typename T, typename KeyType>
+MultiDimension<T, KeyType>::MultiDimension(const butil::StringPiece& prefix,
+                                           const butil::StringPiece& name,
+                                           const key_type& labels)
+    : MultiDimension(labels) {
     this->expose_as(prefix, name);
 }
 
-template <typename T>
-MultiDimension<T>::~MultiDimension() {
-    hide();
+template <typename T, typename KeyType>
+MultiDimension<T, KeyType>::~MultiDimension() {
+    this->hide();
     delete_stats();
 }
 
-template <typename T>
-inline
-size_t MultiDimension<T>::init_flatmap(MetricMap& bg) {
+template <typename T, typename KeyType>
+size_t MultiDimension<T, KeyType>::init_flatmap(MetricMap& bg) {
     // size = 1 << 13
     CHECK_EQ(0, bg.init(8192, 80));
     return (size_t)1;
 }
 
-template <typename T>
-inline
-size_t MultiDimension<T>::count_stats() {
+template <typename T, typename KeyType>
+size_t MultiDimension<T, KeyType>::count_stats() {
     MetricMapScopedPtr metric_map_ptr;
     if (_metric_map.Read(&metric_map_ptr) != 0) {
         LOG(ERROR) << "Fail to read dbd";
@@ -87,21 +79,17 @@ size_t MultiDimension<T>::count_stats() {
     return metric_map_ptr->size();
 }
 
-template <typename T>
-inline
-void MultiDimension<T>::delete_stats(const key_type& labels_value) {
+template <typename T, typename KeyType>
+template <typename K>
+void MultiDimension<T, KeyType>::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 second copy into tmp_metric, which can 
prevent the bvar object from being deleted twice.
+        // Because there are two copies(foreground and background) in DBD,
+        // we need to use an empty tmp_metric, get the deleted value of
+        // second copy into tmp_metric, which can prevent the bvar object
+        // from being deleted twice.
         op_value_type tmp_metric = NULL;
         auto erase_fn = [&labels_value, &tmp_metric](MetricMap& bg) {
-            auto it = bg.seek(labels_value);
-            if (it != NULL) {
-                tmp_metric = *it;
-                bg.erase(labels_value);
-                return 1;
-            }
-            return 0;
+            return bg.erase(labels_value, &tmp_metric);
         };
         _metric_map.Modify(erase_fn);
         if (tmp_metric) {
@@ -110,9 +98,8 @@ void MultiDimension<T>::delete_stats(const key_type& 
labels_value) {
     }
 }
 
-template <typename T>
-inline
-void MultiDimension<T>::delete_stats() {
+template <typename T, typename KeyType>
+void MultiDimension<T, KeyType>::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,
@@ -133,9 +120,8 @@ void MultiDimension<T>::delete_stats() {
     }
 }
 
-template <typename T>
-inline
-void MultiDimension<T>::list_stats(std::vector<key_type>* names) {
+template <typename T, typename KeyType>
+void MultiDimension<T, KeyType>::list_stats(std::vector<key_type>* names) {
     if (names == NULL) {
         return;
     }
@@ -151,9 +137,9 @@ void MultiDimension<T>::list_stats(std::vector<key_type>* 
names) {
     }
 }
 
-template <typename T>
-inline
-T* MultiDimension<T>::get_stats_impl(const key_type& labels_value) {
+template <typename T, typename KeyType>
+template <typename K>
+T* MultiDimension<T, KeyType>::get_stats_impl(const K& labels_value) {
     if (!is_valid_lables_value(labels_value)) {
         return nullptr;
     }
@@ -170,28 +156,30 @@ T* MultiDimension<T>::get_stats_impl(const key_type& 
labels_value) {
     return (*it);
 }
 
-template <typename T>
-inline
-T* MultiDimension<T>::get_stats_impl(const key_type& labels_value, STATS_OP 
stats_op, bool* do_write) {
+template <typename T, typename KeyType>
+template <typename K>
+T* MultiDimension<T, KeyType>::get_stats_impl(
+    const K& labels_value, STATS_OP stats_op, bool* do_write) {
     if (!is_valid_lables_value(labels_value)) {
         return nullptr;
     }
     {
         MetricMapScopedPtr metric_map_ptr;
-        if (_metric_map.Read(&metric_map_ptr) != 0) {
+        if (0 != _metric_map.Read(&metric_map_ptr)) {
             LOG(ERROR) << "Fail to read dbd";
             return nullptr;
         }
 
         auto it = metric_map_ptr->seek(labels_value);
-        if (it != NULL) {
+        if (NULL != it) {
             return (*it);
         } else if (READ_ONLY == stats_op) {
             return nullptr;
         }
 
         if (metric_map_ptr->size() > _max_stats_count) {
-            LOG(ERROR) << "Too many stats seen, overflow detected, max stats 
count=" << _max_stats_count;
+            LOG(ERROR) << "Too many stats seen, overflow detected, max stats 
count="
+                       << _max_stats_count;
             return nullptr;
         }
     }
@@ -209,37 +197,35 @@ T* MultiDimension<T>::get_stats_impl(const key_type& 
labels_value, STATS_OP stat
         if (do_write) {
             *do_write = true;
         }
-        if (NULL != cache_metric) {
-            bg.insert(labels_value, cache_metric);
-        } else {
-            T* add_metric = new T();
-            bg.insert(labels_value, add_metric);
-            cache_metric = add_metric;
+
+        if (NULL == cache_metric) {
+            cache_metric = new T();
         }
+        insert_metrics_map(bg, labels_value, cache_metric);
         return 1;
     };
     _metric_map.Modify(insert_fn);
     return cache_metric;
 }
 
-template <typename T>
-inline
-void MultiDimension<T>::clear_stats() {
+template <typename T, typename KeyType>
+void MultiDimension<T, KeyType>::clear_stats() {
     delete_stats();
 }
 
-template <typename T>
-inline
-bool MultiDimension<T>::has_stats(const key_type& labels_value) {
+template <typename T, typename KeyType>
+template <typename K>
+bool MultiDimension<T, KeyType>::has_stats(const K& labels_value) {
     return get_stats_impl(labels_value) != nullptr;
 }
 
-template <typename T>
-inline
-size_t MultiDimension<T>::dump(Dumper* dumper, const DumpOptions* options) {
+template <typename T, typename KeyType>
+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) {
     std::vector<key_type> label_names;
     list_stats(&label_names);
-    if (label_names.empty() || !dumper->dump_comment(name(), 
METRIC_TYPE_GAUGE)) {
+    if (label_names.empty() || !dumper->dump_comment(this->name(), 
METRIC_TYPE_GAUGE)) {
         return 0;
     }
     size_t n = 0;
@@ -260,9 +246,10 @@ size_t MultiDimension<T>::dump(Dumper* dumper, const 
DumpOptions* options) {
     return n;
 }
 
-template <>
-inline
-size_t MultiDimension<bvar::LatencyRecorder>::dump(Dumper* dumper, const 
DumpOptions*) {
+template <typename T, typename KeyType>
+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*) {
     std::vector<key_type> label_names;
     list_stats(&label_names);
     if (label_names.empty()) {
@@ -272,7 +259,7 @@ size_t MultiDimension<bvar::LatencyRecorder>::dump(Dumper* 
dumper, const DumpOpt
     // To meet prometheus specification, we must guarantee no second TYPE line 
for one metric name
 
     // latency comment
-    dumper->dump_comment(name() + "_latency", METRIC_TYPE_GAUGE);
+    dumper->dump_comment(this->name() + "_latency", METRIC_TYPE_GAUGE);
     for (auto &label_name : label_names) {
         bvar::LatencyRecorder* bvar = get_stats_impl(label_name);
         if (!bvar) {
@@ -310,7 +297,7 @@ size_t MultiDimension<bvar::LatencyRecorder>::dump(Dumper* 
dumper, const DumpOpt
     }
 
     // max_latency comment
-    dumper->dump_comment(name() + "_max_latency", METRIC_TYPE_GAUGE);
+    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) {
@@ -324,7 +311,7 @@ size_t MultiDimension<bvar::LatencyRecorder>::dump(Dumper* 
dumper, const DumpOpt
     }
 
     // qps comment
-    dumper->dump_comment(name() + "_qps", METRIC_TYPE_GAUGE);
+    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) {
@@ -338,7 +325,7 @@ size_t MultiDimension<bvar::LatencyRecorder>::dump(Dumper* 
dumper, const DumpOpt
     }
 
     // count comment
-    dumper->dump_comment(name() + "_count", METRIC_TYPE_COUNTER);
+    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) {
@@ -353,29 +340,24 @@ size_t 
MultiDimension<bvar::LatencyRecorder>::dump(Dumper* dumper, const DumpOpt
     return n;
 }
 
-template <typename T>
-inline
-void MultiDimension<T>::make_dump_key(std::ostream& os, 
-                                      const key_type& labels_value,
-                                      const std::string& suffix,
-                                      const int quantile) {
-    os << name();
+template <typename T, typename KeyType>
+void MultiDimension<T, KeyType>::make_dump_key(std::ostream& os, const 
key_type& labels_value,
+                                               const std::string& suffix, int 
quantile) {
+    os << this->name();
     if (!suffix.empty()) {
         os << suffix;
     }
     make_labels_kvpair_string(os, labels_value, quantile);
 }
 
-template <typename T>
-inline
-void MultiDimension<T>::make_labels_kvpair_string(std::ostream& os, 
-                                                  const key_type& 
labels_value, 
-                                                  const int quantile) {
+template <typename T, typename KeyType>
+void MultiDimension<T, KeyType>::make_labels_kvpair_string(
+    std::ostream& os, const key_type& labels_value, int quantile) {
     os << "{";
-    auto label_key = _labels.cbegin();
+    auto label_key = this->_labels.cbegin();
     auto label_value = labels_value.cbegin();
     char comma[2] = {'\0', '\0'};
-    for (; label_key != _labels.cend() && label_value != labels_value.cend();
+    for (; label_key != this->_labels.cend() && label_value != 
labels_value.cend();
         label_key++, label_value++) {
         os << comma << label_key->c_str() << "=\"" << label_value->c_str() << 
"\"";
         comma[0] = ',';
@@ -386,22 +368,22 @@ void 
MultiDimension<T>::make_labels_kvpair_string(std::ostream& os,
     os << "}";
 }
 
-template <typename T>
-inline
-bool MultiDimension<T>::is_valid_lables_value(const key_type& labels_value) 
const {
-    if (count_labels() != labels_value.size()) {
-        LOG(ERROR) << "Invalid labels count";
+template <typename T, typename KeyType>
+template <typename K>
+bool MultiDimension<T, KeyType>::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();
         return false;
     }
     return true;
 }
 
-template <typename T>
-inline
-void MultiDimension<T>::describe(std::ostream& os) {
-    os << "{\"name\" : \"" << _name << "\", \"labels\" : [";
+template <typename T, typename KeyType>
+void MultiDimension<T, KeyType>::describe(std::ostream& os) {
+    os << "{\"name\" : \"" << this->name() << "\", \"labels\" : [";
     char comma[3] = {'\0', ' ', '\0'};
-    for (auto &label : _labels) {
+    for (auto& label : this->_labels) {
         os << comma << "\"" << label << "\"";
         comma[0] = ',';
     }
diff --git a/src/bvar/mvariable.cpp b/src/bvar/mvariable.cpp
index 26bb97c4..5503748b 100644
--- a/src/bvar/mvariable.cpp
+++ b/src/bvar/mvariable.cpp
@@ -30,8 +30,6 @@
 
 namespace bvar {
 
-constexpr uint64_t MAX_LABELS_COUNT = 10;
-
 DECLARE_bool(bvar_abort_on_same_name);
 
 extern bool s_bvar_may_abort;
@@ -75,7 +73,7 @@ class MVarEntry {
 public:
     MVarEntry() : var(NULL) {}
 
-    MVariable* var;
+    MVariableBase* var;
 };
 
 typedef butil::FlatMap<std::string, MVarEntry> MVarMap;
@@ -107,27 +105,18 @@ inline MVarMapWithLock& get_mvar_map() {
     return *s_mvar_map;
 }
 
-MVariable::MVariable(const std::list<std::string>& labels) {
-    _labels.assign(labels.begin(), labels.end());
-    size_t n = labels.size();
-    if (n > MAX_LABELS_COUNT) {
-        LOG(ERROR) << "Too many labels: " << n << " seen, overflow detected, 
max labels count: " << MAX_LABELS_COUNT;
-        _labels.resize(MAX_LABELS_COUNT);
-    }
-}
-
-MVariable::~MVariable() {
-    CHECK(!hide()) << "Subclass of MVariable MUST call hide() manually in 
their"
-    " dtors to avoid displaying a variable that is just destructing";
+MVariableBase::~MVariableBase() {
+    CHECK(!hide()) << "Subclass of MVariableBase MUST call hide() manually in 
their "
+                      "dtors to avoid displaying a variable that is just 
destructing";
 }
 
-std::string MVariable::get_description() {
+std::string MVariableBase::get_description() {
     std::ostringstream os;
     describe(os);
     return os.str();
 }
 
-int MVariable::describe_exposed(const std::string& name,
+int MVariableBase::describe_exposed(const std::string& name,
                                 std::ostream& os) {
     MVarMapWithLock& m = get_mvar_map();
     BAIDU_SCOPED_LOCK(m.mutex);
@@ -139,7 +128,7 @@ int MVariable::describe_exposed(const std::string& name,
     return 0;
 }
 
-std::string MVariable::describe_exposed(const std::string& name) {
+std::string MVariableBase::describe_exposed(const std::string& name) {
     std::ostringstream oss;
     if (describe_exposed(name, oss) == 0) {
         return oss.str();
@@ -147,8 +136,8 @@ std::string MVariable::describe_exposed(const std::string& 
name) {
     return std::string();
 }
 
-int MVariable::expose_impl(const butil::StringPiece& prefix,
-                           const butil::StringPiece& name) {
+int MVariableBase::expose_impl(const butil::StringPiece& prefix,
+                               const butil::StringPiece& name) {
     if (name.empty()) {
         LOG(ERROR) << "Parameter[name] is empty";
         return -1;
@@ -205,7 +194,7 @@ int MVariable::expose_impl(const butil::StringPiece& prefix,
     return 0;
 }
 
-bool MVariable::hide() {
+bool MVariableBase::hide() {
     if (_name.empty()) {
         return false;
     }
@@ -223,20 +212,20 @@ bool MVariable::hide() {
 }
 
 #ifdef UNIT_TEST
-void MVariable::hide_all() {
+void MVariableBase::hide_all() {
     MVarMapWithLock& m = get_mvar_map();
     BAIDU_SCOPED_LOCK(m.mutex);
     m.clear();
 }
 #endif // end UNIT_TEST
 
-size_t MVariable::count_exposed() {
+size_t MVariableBase::count_exposed() {
     MVarMapWithLock& m = get_mvar_map();
     BAIDU_SCOPED_LOCK(m.mutex);
     return m.size();
 }
 
-void MVariable::list_exposed(std::vector<std::string>* names) {
+void MVariableBase::list_exposed(std::vector<std::string>* names) {
     if (names == NULL) {
         return;
     }
@@ -251,7 +240,7 @@ void MVariable::list_exposed(std::vector<std::string>* 
names) {
     }
 }
 
-size_t MVariable::dump_exposed(Dumper* dumper, const DumpOptions* options) {
+size_t MVariableBase::dump_exposed(Dumper* dumper, const DumpOptions* options) 
{
     if (NULL == dumper) {
         LOG(ERROR) << "Parameter[dumper] is NULL";
         return -1;
@@ -271,10 +260,8 @@ size_t MVariable::dump_exposed(Dumper* dumper, const 
DumpOptions* options) {
             n += entry->var->dump(dumper, &opt);
         }
         if (n > 
static_cast<size_t>(FLAGS_bvar_max_dump_multi_dimension_metric_number)) {
-            LOG(WARNING) << "truncated because of \
-                            exceed max dump multi dimension label number["
-                         << FLAGS_bvar_max_dump_multi_dimension_metric_number
-                         << "]";
+            LOG(WARNING) << "truncated because of exceed max dump multi 
dimension label number["
+                         << FLAGS_bvar_max_dump_multi_dimension_metric_number 
<< "]";
             break;
         }
     }
diff --git a/src/bvar/mvariable.h b/src/bvar/mvariable.h
index 06f8a5d1..22719554 100644
--- a/src/bvar/mvariable.h
+++ b/src/bvar/mvariable.h
@@ -32,11 +32,11 @@ namespace bvar {
 class Dumper;
 struct DumpOptions;
 
-class MVariable {
+class MVariableBase {
 public:
-    explicit MVariable(const std::list<std::string>& labels);
+    MVariableBase() = default;
 
-    virtual ~MVariable();
+    virtual ~MVariableBase();
 
     // Implement this method to print the mvariable info into ostream.
     virtual void describe(std::ostream&) = 0;
@@ -46,12 +46,6 @@ public:
    
     // Get mvariable name
     const std::string& name() const { return _name; }
-    
-    // Get mvariable labels
-    const std::list<std::string>& labels() const { return _labels; }
-
-    // Get number of mvariable labels
-    size_t count_labels() const { return _labels.size(); }
 
     // Expose this mvariable globally so that it's counted in following
     // functions:
@@ -113,11 +107,28 @@ protected:
 
 protected:
     std::string _name;
-    std::list<std::string>  _labels;
 
     // mbvar uses bvar, bvar uses TLS, thus copying/assignment need to copy 
TLS stuff as well,
     // which is heavy. We disable copying/assignment now. 
-    DISALLOW_COPY_AND_ASSIGN(MVariable);
+    DISALLOW_COPY_AND_ASSIGN(MVariableBase);
+};
+
+template <typename KeyType>
+class MVariable : public MVariableBase {
+public:
+    explicit MVariable(const KeyType& labels) : _labels(labels.cbegin(), 
labels.cend()) {
+        static_assert(std::is_same<typename KeyType::value_type, 
std::string>::value,
+                      "value_type of KeyType must be std::string");
+    }
+
+    // Get mvariable labels
+    const KeyType& labels() const { return _labels; }
+
+    // Get number of mvariable labels
+    size_t count_labels() const { return _labels.size(); }
+
+protected:
+    KeyType  _labels;
 };
 
 } // namespace bvar
diff --git a/src/bvar/variable.cpp b/src/bvar/variable.cpp
index 0b1e20d9..fe76b347 100644
--- a/src/bvar/variable.cpp
+++ b/src/bvar/variable.cpp
@@ -833,7 +833,7 @@ static void* dumping_thread(void*) {
             } else if ("prometheus" == mbvar_format) {
                 dumper = new PrometheusFileDumper(mbvar_filename, 
mbvar_prefix);
             }
-            int nline = MVariable::dump_exposed(dumper, &options);
+            int nline = MVariableBase::dump_exposed(dumper, &options);
             if (nline < 0) {
                 LOG(ERROR) << "Fail to dump mvars into " << filename;
             }
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 9f635e67..a478e8cc 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -188,10 +188,12 @@ endif()
 # bthread_* functions are used in logging.cc, and they need to be marked as
 # weak symbols explicitly in Darwin system.
 if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
-    set(DYNAMIC_LIB ${DYNAMIC_LIB}
-                    "-Wl,-U,_bthread_getspecific"
-                    "-Wl,-U,_bthread_setspecific"
-                    "-Wl,-U,_bthread_key_create")
+    set(DYNAMIC_LIB ${DYNAMIC_LIB} "-Wl,-U,_bthread_getspecific"
+                                   "-Wl,-U,_bthread_setspecific"
+                                   "-Wl,-U,_bthread_key_create"
+                                   "-Wl,-U,_bthread_self"
+                                   "-Wl,-U,_bthread_timed_connect"
+    )
 endif()
 
 add_library(BUTIL_DEBUG_LIB OBJECT ${BUTIL_SOURCES})
diff --git a/test/bvar_mvariable_unittest.cpp b/test/bvar_mvariable_unittest.cpp
index 5ab171c4..d72a2e5d 100644
--- a/test/bvar_mvariable_unittest.cpp
+++ b/test/bvar_mvariable_unittest.cpp
@@ -17,12 +17,16 @@
 
 // Date 2021/11/17 14:57:49
 
+// #if __cplusplus >= 201703L
+#include <string_view>
+// #endif // __cplusplus >= 201703L
 #include <pthread.h>                                // pthread_*
 #include <cstddef>
 #include <memory>
 #include <iostream>
 #include <set>
 #include <string>
+#include <array>
 #include <gflags/gflags.h>
 #include <gtest/gtest.h>
 #include "butil/time.h"
@@ -30,21 +34,8 @@
 #include "bvar/bvar.h"
 #include "bvar/multi_dimension.h"
 
-static const int num_thread = 24;
-
-static const int idc_count = 20;
-static const int method_count = 20;
-static const int status_count = 50;
-static const int labels_count = idc_count * method_count * status_count;
-
 static const std::list<std::string> labels = {"idc", "method", "status"};
 
-struct thread_perf_data {
-    bvar::MVariable* mbvar;
-    bvar::Variable*  rbvar;
-    bvar::Variable*  wbvar;
-};
-
 class MVariableTest : public testing::Test {
 protected:
     void SetUp() {}
@@ -66,9 +57,9 @@ TEST_F(MVariableTest, expose) {
     std::vector<std::string> list_exposed_vars;
     std::list<std::string> labels_value1 {"bj", "get", "200"};
     bvar::MultiDimension<bvar::Adder<int> > my_madder1(labels);
-    ASSERT_EQ(0UL, bvar::MVariable::count_exposed());
+    ASSERT_EQ(0UL, bvar::MVariableBase::count_exposed());
     my_madder1.expose("request_count_madder");
-    ASSERT_EQ(1UL, bvar::MVariable::count_exposed());
+    ASSERT_EQ(1UL, bvar::MVariableBase::count_exposed());
     bvar::Adder<int>* my_adder1 = my_madder1.get_stats(labels_value1);
     ASSERT_TRUE(my_adder1);
     ASSERT_STREQ("request_count_madder", my_madder1.name().c_str());
@@ -102,54 +93,140 @@ TEST_F(MVariableTest, expose) {
     list_exposed_vars.push_back("request_count_madder");
 
     ASSERT_EQ(1UL, my_madder1.count_stats());
-    ASSERT_EQ(1UL, bvar::MVariable::count_exposed());
+    ASSERT_EQ(1UL, bvar::MVariableBase::count_exposed());
 
     std::list<std::string> labels2 {"user", "url", "cost"};
     bvar::MultiDimension<bvar::Adder<int> > my_madder2("client_url", labels2);
-    ASSERT_EQ(2UL, bvar::MVariable::count_exposed());
+    ASSERT_EQ(2UL, bvar::MVariableBase::count_exposed());
     list_exposed_vars.push_back("client_url");
 
     std::list<std::string> labels3 {"product", "system", "module"};
     bvar::MultiDimension<bvar::Adder<int> > my_madder3("request_from", 
labels3);
     list_exposed_vars.push_back("request_from");
-    ASSERT_EQ(3UL, bvar::MVariable::count_exposed());
+    ASSERT_EQ(3UL, bvar::MVariableBase::count_exposed());
 
     std::vector<std::string> exposed_vars;
-    bvar::MVariable::list_exposed(&exposed_vars);
+    bvar::MVariableBase::list_exposed(&exposed_vars);
     ASSERT_EQ(3, exposed_vars.size());
 
     my_madder3.hide();
-    ASSERT_EQ(2UL, bvar::MVariable::count_exposed());
+    ASSERT_EQ(2UL, bvar::MVariableBase::count_exposed());
     list_exposed_vars.pop_back();
     exposed_vars.clear();
-    bvar::MVariable::list_exposed(&exposed_vars);
+    bvar::MVariableBase::list_exposed(&exposed_vars);
     ASSERT_EQ(2, exposed_vars.size());
 }
 
-TEST_F(MVariableTest, labels) {
-    std::list<std::string> labels_value1 {"bj", "get", "200"};
-    bvar::MultiDimension<bvar::Adder<int> > my_madder1("request_count_madder", 
labels);
-
-    ASSERT_EQ(labels.size(), my_madder1.count_labels());
-    ASSERT_STREQ("request_count_madder", my_madder1.name().c_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()};
+    }
 
-    ASSERT_EQ(labels, my_madder1.labels());
-
-    std::list<std::string> labels_too_long;
-    std::list<std::string> labels_max;
-    int labels_too_long_count = 15;
-    for (int i = 0; i < labels_too_long_count; ++i) {
-        std::ostringstream os;
-        os << "label" << i;
-        labels_too_long.push_back(os.str());
-        if (i < 10) {
-            labels_max.push_back(os.str());
+    // Converts to butil::StringPiece.
+    explicit operator butil::StringPiece() const {
+        if (NULL == _ptr) {
+            return {};
         }
+        return {_ptr, size()};
     }
-    ASSERT_EQ(labels_too_long_count, labels_too_long.size());
-    bvar::MultiDimension<bvar::Adder<int> > 
my_madder2("request_labels_too_long", labels_too_long);
-    ASSERT_EQ(10, my_madder2.count_labels());
-    ASSERT_EQ(labels_max, my_madder2.labels());
+
+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) {
@@ -229,9 +306,9 @@ TEST_F(MVariableTest, test_describe_exposed) {
     std::string bvar_name("request_count_describe");
     bvar::MultiDimension<bvar::Adder<int> > my_madder1(bvar_name, labels);
 
-    std::string describe_str = bvar::MVariable::describe_exposed(bvar_name);
+    std::string describe_str = 
bvar::MVariableBase::describe_exposed(bvar_name);
 
     std::ostringstream describe_oss;
-    ASSERT_EQ(0, bvar::MVariable::describe_exposed(bvar_name, describe_oss));
+    ASSERT_EQ(0, bvar::MVariableBase::describe_exposed(bvar_name, 
describe_oss));
     ASSERT_STREQ(describe_str.c_str(), describe_oss.str().c_str());
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@brpc.apache.org
For additional commands, e-mail: dev-h...@brpc.apache.org


Reply via email to