You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pegasus.apache.org by wa...@apache.org on 2022/12/02 06:11:26 UTC

[incubator-pegasus] branch master updated: feat(new_metrics): take snapshot of each entity as json format (#1258)

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

wangdan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pegasus.git


The following commit(s) were added to refs/heads/master by this push:
     new 8205f245a feat(new_metrics): take snapshot of each entity as json format (#1258)
8205f245a is described below

commit 8205f245a11edc0f7298df632cdfad713be88a37
Author: Dan Wang <wa...@apache.org>
AuthorDate: Fri Dec 2 14:11:20 2022 +0800

    feat(new_metrics): take snapshot of each entity as json format (#1258)
---
 src/common/json_helper.h        |  24 +-
 src/utils/metrics.cpp           | 136 ++++++-
 src/utils/metrics.h             | 176 ++++++---
 src/utils/test/metrics_test.cpp | 856 ++++++++++++++++++++++++++++++++++++++--
 4 files changed, 1102 insertions(+), 90 deletions(-)

diff --git a/src/common/json_helper.h b/src/common/json_helper.h
index 001ffb6bd..399052f89 100644
--- a/src/common/json_helper.h
+++ b/src/common/json_helper.h
@@ -252,7 +252,11 @@ void json_encode(Writer &out, const std::string &str)
 {
     out.String(str.c_str(), str.length(), true);
 }
-inline void json_encode(JsonWriter &out, const char *str) { out.String(str, strlen(str), true); }
+template <typename Writer>
+inline void json_encode(Writer &out, const char *str)
+{
+    out.String(str, std::strlen(str), true);
+}
 inline bool json_decode(const JsonObject &in, std::string &str)
 {
     dverify(in.IsString());
@@ -289,7 +293,11 @@ inline bool json_decode(const JsonObject &in, bool &t)
 }
 
 // json serialization for double types
-inline void json_encode(JsonWriter &out, double d) { out.Double(d); }
+template <typename Writer>
+inline void json_encode(Writer &out, double d)
+{
+    out.Double(d);
+}
 inline bool json_decode(const JsonObject &in, double &t)
 {
     if (in.IsDouble()) {
@@ -307,7 +315,11 @@ inline bool json_decode(const JsonObject &in, double &t)
 
 // json serialization for int types
 #define INT_TYPE_SERIALIZATION(TName)                                                              \
-    inline void json_encode(JsonWriter &out, TName t) { out.Int64((int64_t)t); }                   \
+    template <typename Writer>                                                                     \
+    inline void json_encode(Writer &out, TName t)                                                  \
+    {                                                                                              \
+        out.Int64(static_cast<int64_t>(t));                                                        \
+    }                                                                                              \
     inline bool json_decode(const JsonObject &in, TName &t)                                        \
     {                                                                                              \
         dverify(in.IsInt64());                                                                     \
@@ -325,7 +337,11 @@ INT_TYPE_SERIALIZATION(int64_t)
 
 // json serialization for uint types
 #define UINT_TYPE_SERIALIZATION(TName)                                                             \
-    inline void json_encode(JsonWriter &out, TName t) { out.Uint64((uint64_t)t); }                 \
+    template <typename Writer>                                                                     \
+    inline void json_encode(Writer &out, TName t)                                                  \
+    {                                                                                              \
+        out.Uint64(static_cast<uint64_t>(t));                                                      \
+    }                                                                                              \
     inline bool json_decode(const JsonObject &in, TName &t)                                        \
     {                                                                                              \
         dverify(in.IsUint64());                                                                    \
diff --git a/src/utils/metrics.cpp b/src/utils/metrics.cpp
index 2036a135c..e5522d9b6 100644
--- a/src/utils/metrics.cpp
+++ b/src/utils/metrics.cpp
@@ -24,8 +24,10 @@
 
 namespace dsn {
 
-metric_entity::metric_entity(const std::string &id, attr_map &&attrs)
-    : _id(id), _lock(), _attrs(std::move(attrs)), _metrics()
+metric_entity::metric_entity(const metric_entity_prototype *prototype,
+                             const std::string &id,
+                             const attr_map &attrs)
+    : _prototype(prototype), _id(id), _attrs(attrs)
 {
 }
 
@@ -81,19 +83,119 @@ metric_entity::metric_map metric_entity::metrics() const
     return _metrics;
 }
 
-void metric_entity::set_attributes(attr_map &&attrs)
+void metric_entity::set_attributes(const attr_map &attrs)
 {
     utils::auto_write_lock l(_lock);
-    _attrs = std::move(attrs);
+    _attrs = attrs;
 }
 
-metric_entity_ptr metric_entity_prototype::instantiate(const std::string &id,
-                                                       metric_entity::attr_map attrs) const
+void metric_entity::encode_type(metric_json_writer &writer) const
+{
+    writer.Key(kMetricEntityTypeField.c_str());
+    json::json_encode(writer, _prototype->name());
+}
+
+void metric_entity::encode_id(metric_json_writer &writer) const
+{
+    writer.Key(kMetricEntityIdField.c_str());
+    json::json_encode(writer, _id);
+}
+
+namespace {
+
+void encode_attrs(dsn::metric_json_writer &writer, const dsn::metric_entity::attr_map &attrs)
+{
+    // Empty attributes are allowed and will just be encoded as {}.
+
+    writer.Key(dsn::kMetricEntityAttrsField.c_str());
+
+    writer.StartObject();
+    for (const auto &attr : attrs) {
+        writer.Key(attr.first.c_str());
+        dsn::json::json_encode(writer, attr.second);
+    }
+    writer.EndObject();
+}
+
+void encode_metrics(dsn::metric_json_writer &writer,
+                    const dsn::metric_entity::metric_map &metrics,
+                    const dsn::metric_filters &filters)
+{
+    // We shouldn't reach here if no metric is chosen, thus just mark an assertion.
+    CHECK(!metrics.empty(),
+          "this entity should not be encoded into the response since no metric is chosen");
+
+    writer.Key(dsn::kMetricEntityMetricsField.c_str());
+
+    writer.StartArray();
+    for (const auto &m : metrics) {
+        m.second->take_snapshot(writer, filters);
+    }
+    writer.EndArray();
+}
+
+} // anonymous namespace
+
+void metric_entity::take_snapshot(metric_json_writer &writer, const metric_filters &filters) const
+{
+    if (!filters.match_entity_type(_prototype->name())) {
+        return;
+    }
+
+    if (!filters.match_entity_id(_id)) {
+        return;
+    }
+
+    attr_map my_attrs;
+    metric_map target_metrics;
+
+    {
+        utils::auto_read_lock l(_lock);
+
+        if (!filters.match_entity_attrs(_attrs)) {
+            return;
+        }
+
+        filters.extract_entity_metrics(_metrics, target_metrics);
+        if (target_metrics.empty()) {
+            // None of metrics is chosen, there is no need to take snapshot for
+            // this entity.
+            return;
+        }
+
+        my_attrs = _attrs;
+    }
+
+    // At least one metric of this entity has been chosen, thus take snapshot and encode
+    // this entity as json format.
+    writer.StartObject();
+    encode_type(writer);
+    encode_id(writer);
+    encode_attrs(writer, my_attrs);
+    encode_metrics(writer, target_metrics, filters);
+    writer.EndObject();
+}
+
+void metric_filters::extract_entity_metrics(const metric_entity::metric_map &candidates,
+                                            metric_entity::metric_map &target_metrics) const
 {
-    CHECK(attrs.find("entity") == attrs.end(), "{}'s attribute \"entity\" is reserved", id);
+    if (entity_metrics.empty()) {
+        target_metrics = candidates;
+        return;
+    }
 
-    attrs["entity"] = _name;
-    return metric_registry::instance().find_or_create_entity(id, std::move(attrs));
+    target_metrics.clear();
+    for (const auto &candidate : candidates) {
+        if (match(candidate.first->name().data(), entity_metrics)) {
+            target_metrics.emplace(candidate.first, candidate.second);
+        }
+    }
+}
+
+metric_entity_ptr metric_entity_prototype::instantiate(const std::string &id,
+                                                       const metric_entity::attr_map &attrs) const
+{
+    return metric_registry::instance().find_or_create_entity(this, id, attrs);
 }
 
 metric_entity_ptr metric_entity_prototype::instantiate(const std::string &id) const
@@ -141,8 +243,9 @@ metric_registry::entity_map metric_registry::entities() const
     return _entities;
 }
 
-metric_entity_ptr metric_registry::find_or_create_entity(const std::string &id,
-                                                         metric_entity::attr_map &&attrs)
+metric_entity_ptr metric_registry::find_or_create_entity(const metric_entity_prototype *prototype,
+                                                         const std::string &id,
+                                                         const metric_entity::attr_map &attrs)
 {
     utils::auto_write_lock l(_lock);
 
@@ -150,10 +253,17 @@ metric_entity_ptr metric_registry::find_or_create_entity(const std::string &id,
 
     metric_entity_ptr entity;
     if (iter == _entities.end()) {
-        entity = new metric_entity(id, std::move(attrs));
+        entity = new metric_entity(prototype, id, attrs);
         _entities[id] = entity;
     } else {
-        iter->second->set_attributes(std::move(attrs));
+        CHECK_EQ_MSG(std::strcmp(prototype->name(), iter->second->prototype()->name()),
+                     0,
+                     "new prototype '{}' is inconsistent with old prototype '{}' for entity '{}'",
+                     prototype->name(),
+                     iter->second->prototype()->name(),
+                     id);
+
+        iter->second->set_attributes(attrs);
         entity = iter->second;
     }
 
diff --git a/src/utils/metrics.h b/src/utils/metrics.h
index e9490ead2..d130da5bf 100644
--- a/src/utils/metrics.h
+++ b/src/utils/metrics.h
@@ -132,32 +132,18 @@
 
 namespace dsn {
 
-using metric_fields_type = std::unordered_set<std::string>;
-
-// This struct includes a set of filters for both entities and metrics requested by client.
-struct metric_filters
-{
-    // According to the parameters requested by client, this function will filter metric
-    // fields that will be put in the response.
-    bool include_metric_field(const std::string &field_name) const
-    {
-        // NOTICE: empty `with_metric_fields` means every field is required by client.
-        if (with_metric_fields.empty()) {
-            return true;
-        }
-
-        return with_metric_fields.find(field_name) != with_metric_fields.end();
-    }
-
-    // `with_metric_fields` includes all the metric fields that are wanted by client. If it
-    // is empty, there will be no restriction: in other words, all fields owned by the metric
-    // will be put in the response.
-    metric_fields_type with_metric_fields;
-};
-
 class metric_prototype;
 class metric;
 using metric_ptr = ref_ptr<metric>;
+struct metric_filters;
+class metric_entity_prototype;
+
+using metric_json_writer = dsn::json::PrettyJsonWriter;
+
+const std::string kMetricEntityTypeField = "type";
+const std::string kMetricEntityIdField = "id";
+const std::string kMetricEntityAttrsField = "attributes";
+const std::string kMetricEntityMetricsField = "metrics";
 
 class metric_entity : public ref_counter
 {
@@ -165,6 +151,8 @@ public:
     using attr_map = std::unordered_map<std::string, std::string>;
     using metric_map = std::unordered_map<const metric_prototype *, metric_ptr>;
 
+    const metric_entity_prototype *prototype() const { return _prototype; }
+
     const std::string &id() const { return _id; }
 
     attr_map attributes() const;
@@ -188,11 +176,15 @@ public:
         return ptr;
     }
 
+    void take_snapshot(metric_json_writer &writer, const metric_filters &filters) const;
+
 private:
     friend class metric_registry;
     friend class ref_ptr<metric_entity>;
 
-    metric_entity(const std::string &id, attr_map &&attrs);
+    metric_entity(const metric_entity_prototype *prototype,
+                  const std::string &id,
+                  const attr_map &attrs);
 
     ~metric_entity();
 
@@ -209,8 +201,13 @@ private:
     };
     void close(close_option option);
 
-    void set_attributes(attr_map &&attrs);
+    void set_attributes(const attr_map &attrs);
+
+    void encode_type(metric_json_writer &writer) const;
 
+    void encode_id(metric_json_writer &writer) const;
+
+    const metric_entity_prototype *const _prototype;
     const std::string _id;
 
     mutable utils::rw_lock_nr _lock;
@@ -222,6 +219,95 @@ private:
 
 using metric_entity_ptr = ref_ptr<metric_entity>;
 
+// This struct includes a set of filters for both entities and metrics requested by client.
+struct metric_filters
+{
+    using metric_fields_type = std::unordered_set<std::string>;
+    using entity_types_type = std::vector<std::string>;
+    using entity_ids_type = std::unordered_set<std::string>;
+    using entity_attrs_type = std::vector<std::string>;
+    using entity_metrics_type = std::vector<std::string>;
+
+// NOTICE: empty `white_list` means every field is required by client.
+#define RETURN_MATCHED_WITH_EMPTY_WHITE_LIST(white_list)                                           \
+    do {                                                                                           \
+        if (white_list.empty()) {                                                                  \
+            return true;                                                                           \
+        }                                                                                          \
+    } while (0)
+
+#define DEFINE_SIMPLE_MATCHER(name)                                                                \
+    template <typename T>                                                                          \
+    inline bool match_##name(const T &candidate) const                                             \
+    {                                                                                              \
+        return match(candidate, name##s);                                                          \
+    }
+
+    static inline bool match(const char *candidate, const std::vector<std::string> &white_list)
+    {
+        RETURN_MATCHED_WITH_EMPTY_WHITE_LIST(white_list);
+        // Will use `bool operator==(const string &lhs, const char *rhs);` to compare each element
+        // in `white_list` with `candidate`.
+        return std::find(white_list.begin(), white_list.end(), candidate) != white_list.end();
+    }
+
+    static inline bool match(const std::string &candidate,
+                             const std::unordered_set<std::string> &white_list)
+    {
+        RETURN_MATCHED_WITH_EMPTY_WHITE_LIST(white_list);
+        return white_list.find(candidate) != white_list.end();
+    }
+
+    // According to the parameters requested by client, this function will filter metric
+    // fields that will be put in the response.
+    DEFINE_SIMPLE_MATCHER(with_metric_field)
+
+    DEFINE_SIMPLE_MATCHER(entity_type)
+
+    DEFINE_SIMPLE_MATCHER(entity_id)
+
+    bool match_entity_attrs(const metric_entity::attr_map &candidates) const
+    {
+        RETURN_MATCHED_WITH_EMPTY_WHITE_LIST(entity_attrs);
+
+        // The size of container must be divisible by 2, since attribute name always pairs
+        // with value in it.
+        CHECK_EQ(entity_attrs.size() & 1, 0);
+
+        for (entity_attrs_type::size_type i = 0; i < entity_attrs.size(); i += 2) {
+            const auto &iter = candidates.find(entity_attrs[i]);
+            if (iter == candidates.end()) {
+                continue;
+            }
+            if (iter->second == entity_attrs[i + 1]) {
+                // It will be considered as matched once any attribute is matched
+                // for both name and value.
+                return true;
+            }
+        }
+        return false;
+    }
+
+#undef DEFINE_SIMPLE_MATCHER
+#undef RETURN_MATCHED_WITH_EMPTY_WHITE_LIST
+
+    void extract_entity_metrics(const metric_entity::metric_map &candidates,
+                                metric_entity::metric_map &target_metrics) const;
+
+    // `with_metric_fields` includes all the metric fields that are wanted by client. If it
+    // is empty, there will be no restriction: in other words, all fields owned by the metric
+    // will be put in the response.
+    metric_fields_type with_metric_fields;
+
+    entity_types_type entity_types;
+
+    entity_ids_type entity_ids;
+
+    entity_attrs_type entity_attrs;
+
+    entity_metrics_type entity_metrics;
+};
+
 class metric_entity_prototype
 {
 public:
@@ -231,7 +317,8 @@ public:
     const char *name() const { return _name; }
 
     // Create an entity with the given ID and attributes, if any.
-    metric_entity_ptr instantiate(const std::string &id, metric_entity::attr_map attrs) const;
+    metric_entity_ptr instantiate(const std::string &id,
+                                  const metric_entity::attr_map &attrs) const;
     metric_entity_ptr instantiate(const std::string &id) const;
 
 private:
@@ -254,7 +341,9 @@ private:
     metric_registry();
     ~metric_registry();
 
-    metric_entity_ptr find_or_create_entity(const std::string &id, metric_entity::attr_map &&attrs);
+    metric_entity_ptr find_or_create_entity(const metric_entity_prototype *prototype,
+                                            const std::string &id,
+                                            const metric_entity::attr_map &attrs);
 
     mutable utils::rw_lock_nr _lock;
     entity_map _entities;
@@ -378,7 +467,7 @@ public:
 
     // Take snapshot of each metric to collect current values as json format with fields chosen
     // by `filters`.
-    virtual void take_snapshot(dsn::json::JsonWriter &writer, const metric_filters &filters) = 0;
+    virtual void take_snapshot(metric_json_writer &writer, const metric_filters &filters) = 0;
 
 protected:
     explicit metric(const metric_prototype *prototype);
@@ -387,12 +476,12 @@ protected:
     // Encode a metric field specified by `field_name` as json format. However, once the field
     // are not chosen by `filters`, this function will do nothing.
     template <typename T>
-    inline void encode(dsn::json::JsonWriter &writer,
-                       const std::string &field_name,
-                       const T &value,
-                       const metric_filters &filters) const
+    static inline void encode(metric_json_writer &writer,
+                              const std::string &field_name,
+                              const T &value,
+                              const metric_filters &filters)
     {
-        if (!filters.include_metric_field(field_name)) {
+        if (!filters.match_with_metric_field(field_name)) {
             return;
         }
 
@@ -401,31 +490,31 @@ protected:
     }
 
     // Encode the metric type as json format, if it is chosen by `filters`.
-    inline void encode_type(dsn::json::JsonWriter &writer, const metric_filters &filters) const
+    inline void encode_type(metric_json_writer &writer, const metric_filters &filters) const
     {
         encode(writer, kMetricTypeField, enum_to_string(prototype()->type()), filters);
     }
 
     // Encode the metric name as json format, if it is chosen by `filters`.
-    inline void encode_name(dsn::json::JsonWriter &writer, const metric_filters &filters) const
+    inline void encode_name(metric_json_writer &writer, const metric_filters &filters) const
     {
         encode(writer, kMetricNameField, prototype()->name().data(), filters);
     }
 
     // Encode the metric unit as json format, if it is chosen by `filters`.
-    inline void encode_unit(dsn::json::JsonWriter &writer, const metric_filters &filters) const
+    inline void encode_unit(metric_json_writer &writer, const metric_filters &filters) const
     {
         encode(writer, kMetricUnitField, enum_to_string(prototype()->unit()), filters);
     }
 
     // Encode the metric description as json format, if it is chosen by `filters`.
-    inline void encode_desc(dsn::json::JsonWriter &writer, const metric_filters &filters) const
+    inline void encode_desc(metric_json_writer &writer, const metric_filters &filters) const
     {
         encode(writer, kMetricDescField, prototype()->description().data(), filters);
     }
 
     // Encode the metric prototype as json format, if some attributes in it are chosen by `filters`.
-    inline void encode_prototype(dsn::json::JsonWriter &writer, const metric_filters &filters) const
+    inline void encode_prototype(metric_json_writer &writer, const metric_filters &filters) const
     {
         encode_type(writer, filters);
         encode_name(writer, filters);
@@ -436,9 +525,8 @@ protected:
     // Encode the unique value of a metric as json format, if it is chosen by `filters`. Notice
     // that the metric should have only one value. like gauge and counter.
     template <typename T>
-    inline void encode_single_value(dsn::json::JsonWriter &writer,
-                                    const T &value,
-                                    const metric_filters &filters) const
+    static inline void
+    encode_single_value(metric_json_writer &writer, const T &value, const metric_filters &filters)
     {
         encode(writer, kMetricSingleValueField, value, filters);
     }
@@ -496,7 +584,7 @@ public:
     // where "name" is the name of the gauge in string type, and "value" is just current value
     // of the gauge fetched by `value()`, in numeric types (i.e. integral or floating-point type,
     // determined by `value_type`).
-    void take_snapshot(json::JsonWriter &writer, const metric_filters &filters) override
+    void take_snapshot(metric_json_writer &writer, const metric_filters &filters) override
     {
         writer.StartObject();
 
@@ -613,7 +701,7 @@ public:
     // }
     // where "name" is the name of the counter in string type, and "value" is just current value
     // of the counter fetched by `value()`, in integral type (namely int64_t).
-    void take_snapshot(json::JsonWriter &writer, const metric_filters &filters) override
+    void take_snapshot(metric_json_writer &writer, const metric_filters &filters) override
     {
         writer.StartObject();
 
@@ -845,7 +933,7 @@ public:
     // where "name" is the name of the percentile in string type, with each configured kth
     // percentile followed, such as "p50", "p90", "p95", etc. All of them are in numeric types
     // (i.e. integral or floating-point type, determined by `value_type`).
-    void take_snapshot(json::JsonWriter &writer, const metric_filters &filters) override
+    void take_snapshot(metric_json_writer &writer, const metric_filters &filters) override
     {
         writer.StartObject();
 
diff --git a/src/utils/test/metrics_test.cpp b/src/utils/test/metrics_test.cpp
index 36bf79f78..acfb7cda0 100644
--- a/src/utils/test/metrics_test.cpp
+++ b/src/utils/test/metrics_test.cpp
@@ -34,7 +34,7 @@ class my_gauge : public metric
 public:
     int64_t value() { return _value; }
 
-    void take_snapshot(json::JsonWriter &, const metric_filters &) override {}
+    void take_snapshot(metric_json_writer &, const metric_filters &) override {}
 
 protected:
     explicit my_gauge(const metric_prototype *prototype) : metric(prototype), _value(0) {}
@@ -158,7 +158,7 @@ TEST(metrics_test, create_entity)
 
     metric_registry::entity_map entities;
     for (const auto &test : tests) {
-        ASSERT_EQ(test.prototype->name(), test.type_name);
+        ASSERT_STREQ(test.prototype->name(), test.type_name.c_str());
 
         metric_entity_ptr entity;
         if (test.entity_attrs.empty() && !test.use_attrs_arg_if_empty) {
@@ -171,10 +171,6 @@ TEST(metrics_test, create_entity)
         ASSERT_EQ(id, test.entity_id);
 
         auto attrs = entity->attributes();
-        ASSERT_NE(attrs.find("entity"), attrs.end());
-        ASSERT_EQ(attrs["entity"], test.type_name);
-        ASSERT_EQ(attrs.size(), test.entity_attrs.size() + 1);
-        ASSERT_EQ(attrs.erase("entity"), 1);
         ASSERT_EQ(attrs, test.entity_attrs);
 
         ASSERT_EQ(entities.find(test.entity_id), entities.end());
@@ -207,7 +203,6 @@ TEST(metrics_test, recreate_entity)
 
         // the attributes will be updated
         auto attrs = entity->attributes();
-        ASSERT_EQ(attrs.erase("entity"), 1);
         ASSERT_EQ(attrs, test.entity_attrs);
     }
 }
@@ -906,15 +901,24 @@ TEST(metrics_test, percentile_double)
                          floating_checker<value_type>>(METRIC_test_percentile_double);
 }
 
-std::string take_snapshot_and_get_json_string(metric *m, const metric_filters &filters)
+template <typename T>
+std::string take_snapshot_and_get_json_string(T *m, const metric_filters &filters)
 {
     std::stringstream out;
     rapidjson::OStreamWrapper wrapper(out);
-    json::JsonWriter writer(wrapper);
+    metric_json_writer writer(wrapper);
 
     m->take_snapshot(writer, filters);
 
-    return out.str();
+    auto out_str = out.str();
+    if (out_str.empty()) {
+        std::cout << "The json string is empty." << std::endl;
+    } else {
+        std::cout << "The json string is: " << std::endl;
+        std::cout << out_str << std::endl;
+    }
+
+    return out_str;
 }
 
 template <typename T, typename = typename std::enable_if<std::is_arithmetic<T>::value>::type>
@@ -925,35 +929,35 @@ void check_prototype_and_extract_value_map_from_json_string(
     metric *my_metric,
     const std::string &json_string,
     const bool is_integral,
-    const metric_fields_type &expected_metric_fields,
+    const metric_filters::metric_fields_type &expected_metric_fields,
     metric_value_map<T> &value_map)
 {
     rapidjson::Document doc;
     rapidjson::ParseResult result = doc.Parse(json_string.c_str());
     ASSERT_FALSE(result.IsError());
 
-    metric_fields_type actual_metric_fields;
+    metric_filters::metric_fields_type actual_metric_fields;
 
     // The json format for each metric should be an object.
     ASSERT_TRUE(doc.IsObject());
     for (const auto &elem : doc.GetObject()) {
-        // Each metric name must be a string.
+        // Each name must be a string.
         ASSERT_TRUE(elem.name.IsString());
 
         if (elem.value.IsString()) {
             // Must be a field of metric prototype.
-            if (std::strcmp(elem.name.GetString(), kMetricTypeField.c_str()) == 0) {
+            if (kMetricTypeField == elem.name.GetString()) {
                 ASSERT_STREQ(elem.value.GetString(),
                              enum_to_string(my_metric->prototype()->type()));
-            } else if (std::strcmp(elem.name.GetString(), kMetricNameField.c_str()) == 0) {
+            } else if (kMetricNameField == elem.name.GetString()) {
                 ASSERT_STREQ(elem.value.GetString(), my_metric->prototype()->name().data());
-            } else if (std::strcmp(elem.name.GetString(), kMetricUnitField.c_str()) == 0) {
+            } else if (kMetricUnitField == elem.name.GetString()) {
                 ASSERT_STREQ(elem.value.GetString(),
                              enum_to_string(my_metric->prototype()->unit()));
-            } else if (std::strcmp(elem.name.GetString(), kMetricDescField.c_str()) == 0) {
+            } else if (kMetricDescField == elem.name.GetString()) {
                 ASSERT_STREQ(elem.value.GetString(), my_metric->prototype()->description().data());
             } else {
-                ASSERT_TRUE(false);
+                ASSERT_TRUE(false) << "invalid field name: " << elem.name.GetString();
             }
         } else {
             // Must be a field of metric value.
@@ -980,7 +984,7 @@ template <typename T, typename = typename std::enable_if<std::is_arithmetic<T>::
 void generate_metric_value_map(metric *my_metric,
                                const bool is_integral,
                                const metric_filters &filters,
-                               const metric_fields_type &expected_metric_fields,
+                               const metric_filters::metric_fields_type &expected_metric_fields,
                                metric_value_map<T> &value_map)
 {
     auto json_string = take_snapshot_and_get_json_string(my_metric, filters);
@@ -1038,10 +1042,10 @@ void compare_floating_metric_value_map(const metric_value_map<T> &actual_value_m
         value_map_comparator(actual_value_map, expected_value_map);                                \
     } while (0)
 
-const metric_fields_type kAllPrototypeMetricFields = {
+const metric_filters::metric_fields_type kAllPrototypeMetricFields = {
     kMetricTypeField, kMetricNameField, kMetricUnitField, kMetricDescField};
 
-metric_fields_type get_all_single_value_metric_fields()
+metric_filters::metric_fields_type get_all_single_value_metric_fields()
 {
     auto fields = kAllPrototypeMetricFields;
     fields.insert(kMetricSingleValueField);
@@ -1074,14 +1078,14 @@ metric_fields_type get_all_single_value_metric_fields()
 #define RUN_CASES_WITH_SINGLE_VALUE_SNAPSHOT(                                                      \
     metric_prototype, updater, value_type, is_integral, value, value_map_comparator)               \
     do {                                                                                           \
-        static const metric_fields_type kAllSingleValueMetricFields =                              \
+        static const metric_filters::metric_fields_type kAllSingleValueMetricFields =              \
             get_all_single_value_metric_fields();                                                  \
         struct test_case                                                                           \
         {                                                                                          \
             std::string entity_id;                                                                 \
             value_type expected_value;                                                             \
-            metric_fields_type with_metric_fields;                                                 \
-            metric_fields_type expected_metric_fields;                                             \
+            metric_filters::metric_fields_type with_metric_fields;                                 \
+            metric_filters::metric_fields_type expected_metric_fields;                             \
         } tests[] = {                                                                              \
             {"server_60", value, {}, kAllSingleValueMetricFields},                                 \
             {"server_61", value, {kMetricNameField}, {kMetricNameField}},                          \
@@ -1184,7 +1188,7 @@ void generate_metric_value_map(MetricType *my_metric,
                                const uint64_t interval_ms,
                                const uint64_t exec_ms,
                                const std::set<kth_percentile_type> &kth_percentiles,
-                               const metric_fields_type &expected_metric_fields,
+                               const metric_filters::metric_fields_type &expected_metric_fields,
                                metric_value_map<typename MetricType::value_type> &value_map)
 {
     using value_type = typename MetricType::value_type;
@@ -1254,7 +1258,7 @@ void generate_metric_value_map(MetricType *my_metric,
         value_map_comparator(actual_value_map, expected_value_map);                                \
     } while (0)
 
-metric_fields_type get_all_kth_percentile_fields()
+metric_filters::metric_fields_type get_all_kth_percentile_fields()
 {
     auto fields = kAllPrototypeMetricFields;
     for (const auto &kth : kAllKthPercentiles) {
@@ -1301,13 +1305,14 @@ metric_fields_type get_all_kth_percentile_fields()
 #define RUN_CASES_WITH_PERCENTILE_SNAPSHOT(                                                        \
     metric_prototype, case_generator, is_integral, value_map_comparator)                           \
     do {                                                                                           \
-        static const metric_fields_type kAllKthPercentileFields = get_all_kth_percentile_fields(); \
+        static const metric_filters::metric_fields_type kAllKthPercentileFields =                  \
+            get_all_kth_percentile_fields();                                                       \
                                                                                                    \
         struct test_case                                                                           \
         {                                                                                          \
             std::string entity_id;                                                                 \
-            metric_fields_type with_metric_fields;                                                 \
-            metric_fields_type expected_metric_fields;                                             \
+            metric_filters::metric_fields_type with_metric_fields;                                 \
+            metric_filters::metric_fields_type expected_metric_fields;                             \
         } tests[] = {                                                                              \
             {"server_60", {}, kAllKthPercentileFields},                                            \
             {"server_61", {kMetricNameField}, {kMetricNameField}},                                 \
@@ -1380,4 +1385,797 @@ TEST(metrics_test, take_snapshot_percentile_double)
                                        compare_floating_metric_value_map);
 }
 
+void check_entity_from_json_string(metric_entity *my_entity,
+                                   const std::string &json_string,
+                                   const std::string &expected_entity_type,
+                                   const std::string &expected_entity_id,
+                                   const metric_entity::attr_map &expected_entity_attrs,
+                                   const std::unordered_set<std::string> &expected_entity_metrics)
+{
+    // `json_string` and `expected_entity_metrics` should be empty or non-empty simultaneously;
+    // empty `json_string` means this entity is not selected by the filters.
+    ASSERT_EQ(json_string.empty(), expected_entity_metrics.empty());
+    if (json_string.empty()) {
+        std::cout << "Empty json string means this entity is not selected by the filters."
+                  << std::endl;
+        return;
+    }
+
+    rapidjson::Document doc;
+    rapidjson::ParseResult result = doc.Parse(json_string.c_str());
+    ASSERT_FALSE(result.IsError());
+
+    // Actual fields parsed from json string for each entity.
+    std::unordered_set<std::string> actual_fields;
+
+    // The json format for each entity should be an object.
+    ASSERT_TRUE(doc.IsObject());
+    for (const auto &elem : doc.GetObject()) {
+        // Each name must be a string.
+        ASSERT_TRUE(elem.name.IsString());
+
+        if (kMetricEntityTypeField == elem.name.GetString()) {
+            ASSERT_STREQ(elem.value.GetString(), expected_entity_type.c_str());
+            ASSERT_STREQ(elem.value.GetString(), my_entity->prototype()->name());
+        } else if (kMetricEntityIdField == elem.name.GetString()) {
+            ASSERT_STREQ(elem.value.GetString(), expected_entity_id.c_str());
+            ASSERT_STREQ(elem.value.GetString(), my_entity->id().c_str());
+        } else if (kMetricEntityAttrsField == elem.name.GetString()) {
+            ASSERT_TRUE(elem.value.IsObject());
+
+            metric_entity::attr_map actual_entity_attrs;
+            for (const auto &attr : elem.value.GetObject()) {
+                // Each name must be a string.
+                ASSERT_TRUE(attr.name.IsString());
+                ASSERT_TRUE(attr.value.IsString());
+                actual_entity_attrs.emplace(attr.name.GetString(), attr.value.GetString());
+            }
+            ASSERT_EQ(actual_entity_attrs, expected_entity_attrs);
+            ASSERT_EQ(actual_entity_attrs, my_entity->attributes());
+        } else if (kMetricEntityMetricsField == elem.name.GetString()) {
+            ASSERT_TRUE(elem.value.IsArray());
+
+            std::unordered_set<std::string> actual_entity_metrics;
+            for (const auto &m : elem.value.GetArray()) {
+                ASSERT_TRUE(m.IsObject());
+
+                for (const auto &field : m.GetObject()) {
+                    // Each name must be a string.
+                    ASSERT_TRUE(field.name.IsString());
+                    if (kMetricNameField == field.name.GetString()) {
+                        ASSERT_TRUE(field.value.IsString());
+                        actual_entity_metrics.emplace(field.value.GetString());
+                    }
+                }
+            }
+
+            ASSERT_EQ(actual_entity_metrics, expected_entity_metrics);
+        } else {
+            ASSERT_TRUE(false) << "invalid field name: " << elem.name.GetString();
+        }
+
+        actual_fields.emplace(elem.name.GetString());
+    }
+
+    static const std::unordered_set<std::string> kAllMetricEntityFields = {
+        kMetricEntityTypeField,
+        kMetricEntityIdField,
+        kMetricEntityAttrsField,
+        kMetricEntityMetricsField};
+    ASSERT_EQ(actual_fields, kAllMetricEntityFields);
+}
+
+TEST(metrics_test, take_snapshot_entity)
+{
+    static const std::unordered_set<std::string> kAllEntityMetrics = {"test_gauge_int64",
+                                                                      "test_counter"};
+
+    // Test cases:
+    // - both attributes and filters are empty
+    // - entity has an attribute while filters are empty
+    // - entity has 2 attributes while filters are empty
+    // - entity has 2 attributes while filters are empty and metrics are empty
+    // - filter has one matched entity type
+    // - filter has one mismatched entity type
+    // - filter has 2 entity types one of which is matched
+    // - filter has 2 mismatched entity types
+    // - filter has one matched entity id
+    // - filter has one mismatched entity id
+    // - filter has 2 entity ids one of which is matched
+    // - filter has 2 mismatched entity ids
+    // - entity has no attribute while filter has one mismatched entity attribute
+    // - entity has no attribute while filter has 2 mismatched entity attributes
+    // - entity has an attribute while filter has one matched entity attribute
+    // - entity has an attribute while filter has one entity attribute whose key is mismatched
+    // - entity has an attribute while filter has one entity attribute whose value is mismatched
+    // - entity has an attribute while filter has one entity attribute whose key and value are
+    // both mismatched
+    // - entity has an attribute while filter has 2 entity attributes one of which is matched
+    // - entity has an attribute while filter has 2 mismatched entity attributes
+    // - entity has 2 attributes while filter has one matched entity attribute
+    // - entity has 2 attributes while filter has one entity attribute whose key is mismatched
+    // - entity has 2 attributes while filter has one entity attribute whose value is mismatched
+    // - entity has 2 attributes while filter has one entity attribute whose key and value are
+    // both mismatched
+    // - entity has 2 attributes while filter has 2 matched entity attributes
+    // - entity has 2 attributes while filter has 2 entity attributes one of which is matched
+    // - entity has 2 attributes while filter has 2 mismatched entity attributes
+    // - entity has no metrics while filter has one mismatched entity metrics
+    // - entity has no metrics while filter has 2 mismatched entity metrics
+    // - entity has an metric while filter has one matched entity metric
+    // - entity has an metric while filter has one mismatched entity metric
+    // - entity has an metric while filter has 2 entity metrics one of which is matched
+    // - entity has an metric while filter has 2 mismatched entity metrics
+    // - entity has 2 metrics while filter has one matched entity metric
+    // - entity has 2 metrics while filter has one mismatched entity metric
+    // - entity has 2 metrics while filter has 2 matched entity metrics
+    // - entity has 2 metrics while filter has 2 entity metrics one of which is matched
+    // - entity has 2 metrics while filter has 2 mismatched entity metrics
+    // - matched for entity types and ids
+    // - mismatched for entity types and ids
+    // - matched for entity types and attributes
+    // - mismatched for entity types and attributes
+    // - matched for entity types and metrics
+    // - mismatched for entity types and metrics
+    // - matched for entity ids and attributes
+    // - mismatched for entity ids and attributes
+    // - matched for entity ids and metrics
+    // - mismatched for entity ids and metrics
+    // - matched for entity attributes and metrics
+    // - mismatched for entity attributes and metrics
+    // - matched for entity types, ids and attributes
+    // - mismatched for entity types, ids and attributes
+    // - matched for entity types, ids and metrics
+    // - mismatched for entity types, ids and metrics
+    // - matched for entity types, attributes and metrics
+    // - mismatched for entity types, attributes and metrics
+    // - matched for entity ids, attributes and metrics
+    // - mismatched for entity ids, attributes and metrics
+    // - matched for entity types, ids, attributes and metrics
+    // - mismatched for entity types, ids, attributes and metrics
+    struct test_case
+    {
+        metric_entity_prototype *entity_prototype;
+        std::unordered_set<std::string> entity_metrics;
+        std::string expected_entity_type;
+        std::string expected_entity_id;
+        metric_entity::attr_map expected_entity_attrs;
+        std::unordered_set<std::string> expected_entity_metrics;
+        metric_filters::entity_types_type filter_entity_types;
+        metric_filters::entity_ids_type filter_entity_ids;
+        metric_filters::entity_attrs_type filter_entity_attrs;
+        metric_filters::entity_metrics_type filter_entity_metrics;
+    } tests[] = {
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_81",
+         {},
+         kAllEntityMetrics,
+         {},
+         {},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_table,
+         kAllEntityMetrics,
+         "my_table",
+         "table_1",
+         {{"table", "test_table_1"}},
+         kAllEntityMetrics,
+         {},
+         {},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_1.0",
+         {{"table", "test_table_1"}, {"partition", "0"}},
+         kAllEntityMetrics,
+         {},
+         {},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         {},
+         "my_replica",
+         "replica_1.1",
+         {{"table", "test_table_1"}, {"partition", "1"}},
+         {},
+         {},
+         {},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_82",
+         {},
+         kAllEntityMetrics,
+         {"my_server"},
+         {},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_83",
+         {},
+         {},
+         {"another_server"},
+         {},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_84",
+         {},
+         kAllEntityMetrics,
+         {"another_server", "my_server"},
+         {},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_85",
+         {},
+         {},
+         {"another_server", "another_another_server"},
+         {},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_86",
+         {},
+         kAllEntityMetrics,
+         {},
+         {"server_86"},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_87",
+         {},
+         {},
+         {},
+         {"another_server_87"},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_88",
+         {},
+         kAllEntityMetrics,
+         {},
+         {"another_server_88", "server_88"},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_89",
+         {},
+         {},
+         {},
+         {"another_server_89", "another_another_server_89"},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_90",
+         {},
+         {},
+         {},
+         {},
+         {"attr_name_1", "attr_value_1"},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_91",
+         {},
+         {},
+         {},
+         {},
+         {"attr_name_1", "attr_value_1", "attr_name_2", "attr_value_2"},
+         {}},
+        {&METRIC_ENTITY_my_table,
+         kAllEntityMetrics,
+         "my_table",
+         "table_2",
+         {{"table", "test_table_2"}},
+         kAllEntityMetrics,
+         {},
+         {},
+         {"table", "test_table_2"},
+         {}},
+        {&METRIC_ENTITY_my_table,
+         kAllEntityMetrics,
+         "my_table",
+         "table_3",
+         {{"table", "test_table_3"}},
+         {},
+         {},
+         {},
+         {"another_table", "test_table_3"},
+         {}},
+        {&METRIC_ENTITY_my_table,
+         kAllEntityMetrics,
+         "my_table",
+         "table_4",
+         {{"table", "test_table_4"}},
+         {},
+         {},
+         {},
+         {"table", "another_test_table_4"},
+         {}},
+        {&METRIC_ENTITY_my_table,
+         kAllEntityMetrics,
+         "my_table",
+         "table_5",
+         {{"table", "test_table_5"}},
+         {},
+         {},
+         {},
+         {"another_table", "another_test_table_5"},
+         {}},
+        {&METRIC_ENTITY_my_table,
+         kAllEntityMetrics,
+         "my_table",
+         "table_6",
+         {{"table", "test_table_6"}},
+         kAllEntityMetrics,
+         {},
+         {},
+         {"another_table", "another_test_table_6", "table", "test_table_6"},
+         {}},
+        {&METRIC_ENTITY_my_table,
+         kAllEntityMetrics,
+         "my_table",
+         "table_7",
+         {{"table", "test_table_7"}},
+         {},
+         {},
+         {},
+         {"another_table", "test_table_7", "table", "another_test_table_7"},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_1.2",
+         {{"table", "test_table_1"}, {"partition", "2"}},
+         kAllEntityMetrics,
+         {},
+         {},
+         {"table", "test_table_1"},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_1.3",
+         {{"table", "test_table_1"}, {"partition", "3"}},
+         {},
+         {},
+         {},
+         {"another_partition", "3"},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_1.4",
+         {{"table", "test_table_1"}, {"partition", "4"}},
+         {},
+         {},
+         {},
+         {"table", "another_test_table_1"},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_1.5",
+         {{"table", "test_table_1"}, {"partition", "5"}},
+         {},
+         {},
+         {},
+         {"another_table", "another_test_table_1"},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_1.6",
+         {{"table", "test_table_1"}, {"partition", "6"}},
+         kAllEntityMetrics,
+         {},
+         {},
+         {"table", "test_table_1", "partition", "6"},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_1.7",
+         {{"table", "test_table_1"}, {"partition", "7"}},
+         kAllEntityMetrics,
+         {},
+         {},
+         {"another_table", "another_test_table_1", "partition", "7"},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_2.0",
+         {{"table", "test_table_2"}, {"partition", "0"}},
+         {},
+         {},
+         {},
+         {"table", "another_test_table_2", "partition", "1"},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         {},
+         "my_server",
+         "server_92",
+         {},
+         {},
+         {},
+         {},
+         {},
+         {"test_gauge_int64"}},
+        {&METRIC_ENTITY_my_server,
+         {},
+         "my_server",
+         "server_93",
+         {},
+         {},
+         {},
+         {},
+         {},
+         {"test_gauge_int64", "test_counter"}},
+        {&METRIC_ENTITY_my_server,
+         {"test_gauge_int64"},
+         "my_server",
+         "server_94",
+         {},
+         {"test_gauge_int64"},
+         {},
+         {},
+         {},
+         {"test_gauge_int64"}},
+        {&METRIC_ENTITY_my_server,
+         {"test_gauge_int64"},
+         "my_server",
+         "server_95",
+         {},
+         {},
+         {},
+         {},
+         {},
+         {"test_counter"}},
+        {&METRIC_ENTITY_my_server,
+         {"test_gauge_int64"},
+         "my_server",
+         "server_96",
+         {},
+         {"test_gauge_int64"},
+         {},
+         {},
+         {},
+         {"test_gauge_int64", "test_counter"}},
+        {&METRIC_ENTITY_my_server,
+         {"test_gauge_int64"},
+         "my_server",
+         "server_97",
+         {},
+         {},
+         {},
+         {},
+         {},
+         {"test_gauge_double", "test_counter"}},
+        {&METRIC_ENTITY_my_server,
+         {"test_gauge_int64", "test_counter"},
+         "my_server",
+         "server_98",
+         {},
+         {"test_counter"},
+         {},
+         {},
+         {},
+         {"test_counter"}},
+        {&METRIC_ENTITY_my_server,
+         {"test_gauge_int64", "test_counter"},
+         "my_server",
+         "server_99",
+         {},
+         {},
+         {},
+         {},
+         {},
+         {"test_gauge_double"}},
+        {&METRIC_ENTITY_my_server,
+         {"test_gauge_int64", "test_counter"},
+         "my_server",
+         "server_100",
+         {},
+         {"test_gauge_int64", "test_counter"},
+         {},
+         {},
+         {},
+         {"test_gauge_int64", "test_counter"}},
+        {&METRIC_ENTITY_my_server,
+         {"test_gauge_int64", "test_counter"},
+         "my_server",
+         "server_101",
+         {},
+         {"test_gauge_int64"},
+         {},
+         {},
+         {},
+         {"test_gauge_int64", "test_gauge_double"}},
+        {&METRIC_ENTITY_my_server,
+         {"test_gauge_int64", "test_counter"},
+         "my_server",
+         "server_102",
+         {},
+         {},
+         {},
+         {},
+         {},
+         {"test_gauge_double", "test_concurrent_counter"}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_103",
+         {},
+         kAllEntityMetrics,
+         {"another_server", "my_server"},
+         {"another_server_103", "server_103"},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_104",
+         {},
+         {},
+         {"another_server", "my_server"},
+         {"another_server_104"},
+         {},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_2.1",
+         {{"table", "test_table_2"}, {"partition", "1"}},
+         kAllEntityMetrics,
+         {"another_replica", "my_replica"},
+         {},
+         {"table", "test_table_2", "partition", "1"},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_2.2",
+         {{"table", "test_table_2"}, {"partition", "2"}},
+         {},
+         {"another_replica", "my_replica"},
+         {},
+         {"table", "another_test_table_2", "another_partition", "2"},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         {"test_gauge_int64", "test_counter"},
+         "my_server",
+         "server_105",
+         {},
+         {"test_gauge_int64", "test_counter"},
+         {"another_server", "my_server"},
+         {},
+         {},
+         {"test_gauge_int64", "test_counter"}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_106",
+         {},
+         {"test_counter"},
+         {"another_server", "my_server"},
+         {},
+         {},
+         {"test_gauge_double", "test_counter"}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_2.3",
+         {{"table", "test_table_2"}, {"partition", "3"}},
+         kAllEntityMetrics,
+         {},
+         {"another_replica_2.3", "replica_2.3"},
+         {"table", "another_test_table_2", "partition", "3"},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_2.4",
+         {{"table", "test_table_2"}, {"partition", "4"}},
+         {},
+         {},
+         {"another_replica_2.4", "replica_2.4"},
+         {"table", "another_test_table_2", "another_partition", "4"},
+         {}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_107",
+         {},
+         {"test_gauge_int64"},
+         {},
+         {"another_server_107", "server_107"},
+         {},
+         {"test_gauge_int64", "test_gauge_double"}},
+        {&METRIC_ENTITY_my_server,
+         kAllEntityMetrics,
+         "my_server",
+         "server_108",
+         {},
+         {},
+         {},
+         {"another_server_108", "server_108"},
+         {},
+         {"test_gauge_double", "test_concurrent_counter"}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_2.5",
+         {{"table", "test_table_2"}, {"partition", "5"}},
+         {"test_gauge_int64"},
+         {},
+         {},
+         {"table", "another_test_table_2", "partition", "5"},
+         {"test_gauge_int64", "test_gauge_double"}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_2.6",
+         {{"table", "test_table_2"}, {"partition", "6"}},
+         {},
+         {},
+         {},
+         {"table", "test_table_2", "partition", "6"},
+         {"test_gauge_double", "test_concurrent_counter"}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_2.7",
+         {{"table", "test_table_2"}, {"partition", "7"}},
+         kAllEntityMetrics,
+         {"another_replica", "my_replica"},
+         {"another_replica_2.7", "replica_2.7"},
+         {"table", "another_test_table_2", "partition", "7"},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_3.0",
+         {{"table", "test_table_3"}, {"partition", "0"}},
+         {},
+         {"another_replica", "my_replica"},
+         {"another_replica_3.0", "replica_3.0"},
+         {"table", "another_test_table_3", "another_partition", "0"},
+         {}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_3.1",
+         {},
+         {"test_counter"},
+         {"another_replica", "my_replica"},
+         {"another_replica_3.1", "replica_3.1"},
+         {},
+         {"test_gauge_double", "test_counter"}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_3.2",
+         {},
+         {},
+         {"another_replica", "my_replica"},
+         {"another_replica_3.2", "replica_3.2"},
+         {},
+         {"test_gauge_double", "test_concurrent_counter"}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_3.3",
+         {{"table", "test_table_3"}, {"partition", "3"}},
+         {"test_gauge_int64"},
+         {"another_replica", "my_replica"},
+         {},
+         {"table", "test_table_3", "another_partition", "3"},
+         {"test_gauge_int64", "test_gauge_double"}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_3.4",
+         {{"table", "test_table_3"}, {"partition", "4"}},
+         {},
+         {"another_replica", "my_replica"},
+         {},
+         {"table", "another_test_table_3", "partition", "4"},
+         {"test_gauge_double", "test_concurrent_counter"}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_3.5",
+         {{"table", "test_table_3"}, {"partition", "5"}},
+         {"test_counter"},
+         {},
+         {"another_replica_3.5", "replica_3.5"},
+         {"table", "test_table_3", "another_partition", "5"},
+         {"test_gauge_double", "test_counter"}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_3.6",
+         {{"table", "test_table_3"}, {"partition", "6"}},
+         {},
+         {},
+         {"another_replica_3.6", "replica_3.6"},
+         {"table", "another_test_table_3", "partition", "6"},
+         {"test_gauge_double", "test_concurrent_counter"}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_3.7",
+         {{"table", "test_table_3"}, {"partition", "7"}},
+         {"test_gauge_int64"},
+         {"another_replica", "my_replica"},
+         {"another_replica_3.7", "replica_3.7"},
+         {"table", "test_table_3", "another_partition", "7"},
+         {"test_gauge_int64", "test_gauge_double"}},
+        {&METRIC_ENTITY_my_replica,
+         kAllEntityMetrics,
+         "my_replica",
+         "replica_4.0",
+         {{"table", "test_table_4"}, {"partition", "0"}},
+         {},
+         {"another_replica", "my_replica"},
+         {"another_replica_4.0", "replica_4.0"},
+         {"table", "another_test_table_4", "partition", "0"},
+         {"test_gauge_double", "test_concurrent_counter"}},
+    };
+
+    for (const auto &test : tests) {
+        auto my_entity =
+            test.entity_prototype->instantiate(test.expected_entity_id, test.expected_entity_attrs);
+
+        if (test.entity_metrics.find("test_gauge_int64") != test.entity_metrics.end()) {
+            auto my_gauge_int64 = METRIC_test_gauge_int64.instantiate(my_entity);
+            my_gauge_int64->set(5);
+        }
+
+        if (test.entity_metrics.find("test_counter") != test.entity_metrics.end()) {
+            auto my_counter = METRIC_test_counter.instantiate(my_entity);
+            my_counter->increment();
+        }
+
+        metric_filters filters;
+        filters.entity_types = test.filter_entity_types;
+        filters.entity_ids = test.filter_entity_ids;
+        filters.entity_attrs = test.filter_entity_attrs;
+        filters.entity_metrics = test.filter_entity_metrics;
+
+        auto json_string = take_snapshot_and_get_json_string(my_entity.get(), filters);
+        check_entity_from_json_string(my_entity,
+                                      json_string,
+                                      test.expected_entity_type,
+                                      test.expected_entity_id,
+                                      test.expected_entity_attrs,
+                                      test.expected_entity_metrics);
+    }
+}
+
 } // namespace dsn


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pegasus.apache.org
For additional commands, e-mail: commits-help@pegasus.apache.org