This is an automated email from the ASF dual-hosted git repository.
zhaoliwei 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 b26a1130c feat(new_metrics): support filters for selected metric
fields in response (#1237)
b26a1130c is described below
commit b26a1130cd2d79f9c4fa3d203182115cbe032ad8
Author: Dan Wang <[email protected]>
AuthorDate: Fri Nov 18 10:26:40 2022 +0800
feat(new_metrics): support filters for selected metric fields in response
(#1237)
---
src/utils/enum_helper.h | 1 +
src/utils/metrics.cpp | 9 -
src/utils/metrics.h | 217 +++++++++++++++++------
src/utils/test/metrics_test.cpp | 368 ++++++++++++++++++++++++++++++++--------
4 files changed, 459 insertions(+), 136 deletions(-)
diff --git a/src/utils/enum_helper.h b/src/utils/enum_helper.h
index e5d83e0d0..25d78b909 100644
--- a/src/utils/enum_helper.h
+++ b/src/utils/enum_helper.h
@@ -49,6 +49,7 @@
#define ENUM_BEGIN(type, invalid_value) ENUM_BEGIN2(type, type, invalid_value)
#define ENUM_REG2(type, name) helper->register_enum(#name, type::name);
+#define ENUM_REG_WITH_CUSTOM_NAME(type, name) helper->register_enum(#name,
type);
#define ENUM_REG(e) helper->register_enum(#e, e);
#define ENUM_END2(type, name)
\
diff --git a/src/utils/metrics.cpp b/src/utils/metrics.cpp
index 07a1210f2..2036a135c 100644
--- a/src/utils/metrics.cpp
+++ b/src/utils/metrics.cpp
@@ -24,15 +24,6 @@
namespace dsn {
-std::set<kth_percentile_type> get_all_kth_percentile_types()
-{
- std::set<kth_percentile_type> all_types;
- for (size_t i = 0; i < static_cast<size_t>(kth_percentile_type::COUNT);
++i) {
- all_types.insert(static_cast<kth_percentile_type>(i));
- }
- return all_types;
-}
-
metric_entity::metric_entity(const std::string &id, attr_map &&attrs)
: _id(id), _lock(), _attrs(std::move(attrs)), _metrics()
{
diff --git a/src/utils/metrics.h b/src/utils/metrics.h
index 87cbb3dc2..e9490ead2 100644
--- a/src/utils/metrics.h
+++ b/src/utils/metrics.h
@@ -27,6 +27,7 @@
#include <string>
#include <type_traits>
#include <unordered_map>
+#include <unordered_set>
#include <utility>
#include <vector>
@@ -131,6 +132,29 @@
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>;
@@ -256,10 +280,10 @@ enum class metric_type
};
ENUM_BEGIN(metric_type, metric_type::kInvalidUnit)
-ENUM_REG(metric_type::kGauge)
-ENUM_REG(metric_type::kCounter)
-ENUM_REG(metric_type::kVolatileCounter)
-ENUM_REG(metric_type::kPercentile)
+ENUM_REG_WITH_CUSTOM_NAME(metric_type::kGauge, gauge)
+ENUM_REG_WITH_CUSTOM_NAME(metric_type::kCounter, counter)
+ENUM_REG_WITH_CUSTOM_NAME(metric_type::kVolatileCounter, volatile_counter)
+ENUM_REG_WITH_CUSTOM_NAME(metric_type::kPercentile, percentile)
ENUM_END(metric_type)
enum class metric_unit
@@ -273,10 +297,11 @@ enum class metric_unit
};
ENUM_BEGIN(metric_unit, metric_unit::kInvalidUnit)
-ENUM_REG(metric_unit::kNanoSeconds)
-ENUM_REG(metric_unit::kMicroSeconds)
-ENUM_REG(metric_unit::kMilliSeconds)
-ENUM_REG(metric_unit::kSeconds)
+ENUM_REG_WITH_CUSTOM_NAME(metric_unit::kNanoSeconds, nanoseconds)
+ENUM_REG_WITH_CUSTOM_NAME(metric_unit::kMicroSeconds, microseconds)
+ENUM_REG_WITH_CUSTOM_NAME(metric_unit::kMilliSeconds, milliseconds)
+ENUM_REG_WITH_CUSTOM_NAME(metric_unit::kSeconds, seconds)
+ENUM_REG_WITH_CUSTOM_NAME(metric_unit::kRequests, requests)
ENUM_END(metric_unit)
class metric_prototype
@@ -331,6 +356,12 @@ private:
DISALLOW_COPY_AND_ASSIGN(metric_prototype_with);
};
+const std::string kMetricTypeField = "type";
+const std::string kMetricNameField = "name";
+const std::string kMetricUnitField = "unit";
+const std::string kMetricDescField = "desc";
+const std::string kMetricSingleValueField = "value";
+
// Base class for each type of metric.
// Every metric class should inherit from this class.
//
@@ -345,13 +376,73 @@ class metric : public ref_counter
public:
const metric_prototype *prototype() const { return _prototype; }
- // Take snapshot of each metric to collect current values as json format.
- virtual void take_snapshot(dsn::json::JsonWriter &writer) = 0;
+ // 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;
protected:
explicit metric(const metric_prototype *prototype);
virtual ~metric() = default;
+ // 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
+ {
+ if (!filters.include_metric_field(field_name)) {
+ return;
+ }
+
+ writer.Key(field_name.c_str());
+ json::json_encode(writer, value);
+ }
+
+ // 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
+ {
+ 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
+ {
+ 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
+ {
+ 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
+ {
+ 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
+ {
+ encode_type(writer, filters);
+ encode_name(writer, filters);
+ encode_unit(writer, filters);
+ encode_desc(writer, filters);
+ }
+
+ // 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
+ {
+ encode(writer, kMetricSingleValueField, value, filters);
+ }
+
const metric_prototype *const _prototype;
private:
@@ -405,15 +496,12 @@ 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) override
+ void take_snapshot(json::JsonWriter &writer, const metric_filters
&filters) override
{
writer.StartObject();
- writer.Key("name");
- json::json_encode(writer, prototype()->name().data());
-
- writer.Key("value");
- json::json_encode(writer, value());
+ encode_prototype(writer, filters);
+ encode_single_value(writer, value(), filters);
writer.EndObject();
}
@@ -525,15 +613,12 @@ 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) override
+ void take_snapshot(json::JsonWriter &writer, const metric_filters
&filters) override
{
writer.StartObject();
- writer.Key("name");
- json::json_encode(writer, prototype()->name().data());
-
- writer.Key("value");
- json::json_encode(writer, value());
+ encode_prototype(writer, filters);
+ encode_single_value(writer, value(), filters);
writer.EndObject();
}
@@ -580,40 +665,74 @@ using concurrent_volatile_counter_ptr =
counter_ptr<concurrent_long_adder, true>
template <typename Adder = striped_long_adder>
using volatile_counter_prototype = metric_prototype_with<counter<Adder, true>>;
+#define KTH_PERCENTILE(prefix, kth) prefix##kth
+#define KTH_PERCENTILE_TYPE(kth) KTH_PERCENTILE(P, kth)
+#define ENUM_KTH_PERCENTILE_TYPE(qualifier, kth) qualifier
KTH_PERCENTILE_TYPE(kth)
+#define KTH_PERCENTILE_NAME(kth) KTH_PERCENTILE(p, kth)
+
+#define ENUM_REG_WITH_KTH_PERCENTILE_TYPE(kth)
\
+ ENUM_REG_WITH_CUSTOM_NAME(ENUM_KTH_PERCENTILE_TYPE(kth_percentile_type::,
kth), \
+ KTH_PERCENTILE_NAME(kth))
+
+struct kth_percentile_property
+{
+ std::string name;
+ double decimal;
+};
+
+#define STRINGIFY_HELPER(x) #x
+#define STRINGIFY(x) STRINGIFY_HELPER(x)
+#define STRINGIFY_KTH_PERCENTILE_NAME(kth) STRINGIFY(KTH_PERCENTILE_NAME(kth))
+#define KTH_TO_DECIMAL(kth) 0.##kth
+#define KTH_PERCENTILE_PROPERTY_LIST(kth)
\
+ {
\
+ STRINGIFY_KTH_PERCENTILE_NAME(kth), KTH_TO_DECIMAL(kth)
\
+ }
+
// All supported kinds of kth percentiles. User can configure required kth
percentiles for
// each percentile. Only configured kth percentiles will be computed. This can
reduce CPU
// consumption.
+#define ALL_KTH_PERCENTILE_TYPES(qualifier)
\
+ ENUM_KTH_PERCENTILE_TYPE(qualifier, 50)
\
+ , ENUM_KTH_PERCENTILE_TYPE(qualifier, 90),
ENUM_KTH_PERCENTILE_TYPE(qualifier, 95), \
+ ENUM_KTH_PERCENTILE_TYPE(qualifier, 99),
ENUM_KTH_PERCENTILE_TYPE(qualifier, 999)
+
enum class kth_percentile_type : size_t
{
- P50,
- P90,
- P95,
- P99,
- P999,
+ ALL_KTH_PERCENTILE_TYPES(),
COUNT,
- INVALID
+ INVALID,
};
// Support to load from configuration files for percentiles.
ENUM_BEGIN(kth_percentile_type, kth_percentile_type::INVALID)
-ENUM_REG(kth_percentile_type::P50)
-ENUM_REG(kth_percentile_type::P90)
-ENUM_REG(kth_percentile_type::P95)
-ENUM_REG(kth_percentile_type::P99)
-ENUM_REG(kth_percentile_type::P999)
+ENUM_REG_WITH_KTH_PERCENTILE_TYPE(50)
+ENUM_REG_WITH_KTH_PERCENTILE_TYPE(90)
+ENUM_REG_WITH_KTH_PERCENTILE_TYPE(95)
+ENUM_REG_WITH_KTH_PERCENTILE_TYPE(99)
+ENUM_REG_WITH_KTH_PERCENTILE_TYPE(999)
ENUM_END(kth_percentile_type)
-struct kth_percentile
-{
- std::string name;
- double decimal;
-};
+// Generate decimals from kth percentiles.
+const std::vector<kth_percentile_property> kAllKthPercentiles =
{KTH_PERCENTILE_PROPERTY_LIST(50),
+
KTH_PERCENTILE_PROPERTY_LIST(90),
+
KTH_PERCENTILE_PROPERTY_LIST(95),
+
KTH_PERCENTILE_PROPERTY_LIST(99),
+
KTH_PERCENTILE_PROPERTY_LIST(999)};
+
+const std::set<kth_percentile_type> kAllKthPercentileTypes = {
+ ALL_KTH_PERCENTILE_TYPES(kth_percentile_type::)};
-const std::vector<kth_percentile> kAllKthPercentiles = {
- {"p50", 0.5}, {"p90", 0.9}, {"p95", 0.95}, {"p99", 0.99}, {"p999", 0.999}};
+inline std::string kth_percentile_to_name(const kth_percentile_type &type)
+{
+ auto index = static_cast<size_t>(type);
+ CHECK_LT(index, kAllKthPercentiles.size());
+ return kAllKthPercentiles[index].name;
+}
inline size_t kth_percentile_to_nth_index(size_t size, size_t kth_index)
{
+ CHECK_LT(kth_index, kAllKthPercentiles.size());
auto decimal = kAllKthPercentiles[kth_index].decimal;
// Since the kth percentile is the value that is greater than k percent of
the data values after
// ranking them
(https://people.richland.edu/james/ictcm/2001/descriptive/helpposition.html),
@@ -626,16 +745,6 @@ inline size_t kth_percentile_to_nth_index(size_t size,
kth_percentile_type type)
return kth_percentile_to_nth_index(size, static_cast<size_t>(type));
}
-std::set<kth_percentile_type> get_all_kth_percentile_types();
-const std::set<kth_percentile_type> kAllKthPercentileTypes =
get_all_kth_percentile_types();
-
-inline std::string kth_percentile_to_name(const kth_percentile_type &type)
-{
- auto index = static_cast<size_t>(type);
- CHECK_LT(index, kAllKthPercentiles.size());
- return kAllKthPercentiles[index].name;
-}
-
// `percentile_timer` is a timer class that encapsulates the details how each
percentile is
// computed periodically.
//
@@ -736,20 +845,18 @@ 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) override
+ void take_snapshot(json::JsonWriter &writer, const metric_filters
&filters) override
{
writer.StartObject();
- writer.Key("name");
- json::json_encode(writer, prototype()->name().data());
+ encode_prototype(writer, filters);
for (size_t i = 0; i <
static_cast<size_t>(kth_percentile_type::COUNT); ++i) {
if (!_kth_percentile_bitset.test(i)) {
continue;
}
- writer.Key(kAllKthPercentiles[i].name.c_str());
- json::json_encode(writer, value(i));
+ encode(writer, kAllKthPercentiles[i].name, value(i), filters);
}
writer.EndObject();
diff --git a/src/utils/test/metrics_test.cpp b/src/utils/test/metrics_test.cpp
index 511ecbfc0..36bf79f78 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; }
- virtual void take_snapshot(json::JsonWriter &) override {}
+ void take_snapshot(json::JsonWriter &, const metric_filters &) override {}
protected:
explicit my_gauge(const metric_prototype *prototype) : metric(prototype),
_value(0) {}
@@ -906,13 +906,13 @@ TEST(metrics_test, percentile_double)
floating_checker<value_type>>(METRIC_test_percentile_double);
}
-std::string take_snapshot_and_get_json_string(metric *m)
+std::string take_snapshot_and_get_json_string(metric *m, const metric_filters
&filters)
{
std::stringstream out;
rapidjson::OStreamWrapper wrapper(out);
json::JsonWriter writer(wrapper);
- m->take_snapshot(writer);
+ m->take_snapshot(writer, filters);
return out.str();
}
@@ -921,24 +921,42 @@ template <typename T, typename = typename
std::enable_if<std::is_arithmetic<T>::
using metric_value_map = std::map<std::string, T>;
template <typename T, typename = typename
std::enable_if<std::is_arithmetic<T>::value>::type>
-void check_and_extract_metric_value_map_from_json_string(const std::string
&json_string,
- const std::string
&metric_name,
- const bool
is_integral,
- metric_value_map<T>
&value_map)
+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,
+ 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;
+
+ // 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.
ASSERT_TRUE(elem.name.IsString());
if (elem.value.IsString()) {
- ASSERT_STREQ(elem.name.GetString(), "name");
-
- ASSERT_STREQ(elem.value.GetString(), metric_name.c_str());
+ // Must be a field of metric prototype.
+ if (std::strcmp(elem.name.GetString(), kMetricTypeField.c_str())
== 0) {
+ ASSERT_STREQ(elem.value.GetString(),
+ enum_to_string(my_metric->prototype()->type()));
+ } else if (std::strcmp(elem.name.GetString(),
kMetricNameField.c_str()) == 0) {
+ ASSERT_STREQ(elem.value.GetString(),
my_metric->prototype()->name().data());
+ } else if (std::strcmp(elem.name.GetString(),
kMetricUnitField.c_str()) == 0) {
+ ASSERT_STREQ(elem.value.GetString(),
+ enum_to_string(my_metric->prototype()->unit()));
+ } else if (std::strcmp(elem.name.GetString(),
kMetricDescField.c_str()) == 0) {
+ ASSERT_STREQ(elem.value.GetString(),
my_metric->prototype()->description().data());
+ } else {
+ ASSERT_TRUE(false);
+ }
} else {
+ // Must be a field of metric value.
T value;
if (is_integral) {
ASSERT_TRUE(elem.value.IsInt64());
@@ -949,17 +967,25 @@ void
check_and_extract_metric_value_map_from_json_string(const std::string &json
}
value_map[elem.name.GetString()] = value;
}
+
+ actual_metric_fields.emplace(elem.name.GetString());
}
+
+ // Check if the fields of metric prototype have been provided to the
client as expected.
+ ASSERT_EQ(actual_metric_fields, expected_metric_fields);
}
+// Take snapshot as json format, then decode to a value map.
template <typename T, typename = typename
std::enable_if<std::is_arithmetic<T>::value>::type>
void generate_metric_value_map(metric *my_metric,
const bool is_integral,
+ const metric_filters &filters,
+ const metric_fields_type
&expected_metric_fields,
metric_value_map<T> &value_map)
{
- auto json_string = take_snapshot_and_get_json_string(my_metric);
- check_and_extract_metric_value_map_from_json_string(
- json_string, my_metric->prototype()->name().data(), is_integral,
value_map);
+ auto json_string = take_snapshot_and_get_json_string(my_metric, filters);
+ check_prototype_and_extract_value_map_from_json_string(
+ my_metric, json_string, is_integral, expected_metric_fields,
value_map);
}
template <typename T, typename = typename
std::enable_if<std::is_integral<T>::value>::type>
@@ -984,67 +1010,154 @@ void compare_floating_metric_value_map(const
metric_value_map<T> &actual_value_m
}
}
-#define TEST_METRIC_SNAPSHOT_WITH_SINGLE_VALUE(
\
- metric_prototype, updater, value_type, is_integral, value_map_comparator)
\
+#define TEST_METRIC_SNAPSHOT_WITH_SINGLE_VALUE(metric_prototype,
\
+ updater,
\
+ value_type,
\
+ is_integral,
\
+ metric_fields,
\
+ expected_metric_fields,
\
+ value_map_comparator)
\
do {
\
auto my_server_entity =
METRIC_ENTITY_my_server.instantiate(test.entity_id); \
auto my_metric = metric_prototype.instantiate(my_server_entity);
\
my_metric->updater(test.expected_value);
\
\
- const metric_value_map<value_type> expected_value_map = {{"value",
test.expected_value}}; \
+ metric_filters filters;
\
+ filters.with_metric_fields = metric_fields;
\
+
\
+ metric_value_map<value_type> expected_value_map;
\
+ if (expected_metric_fields.find(kMetricSingleValueField) !=
\
+ expected_metric_fields.end()) {
\
+ expected_value_map[kMetricSingleValueField] = test.expected_value;
\
+ }
\
\
metric_value_map<value_type> actual_value_map;
\
- generate_metric_value_map(my_metric.get(), is_integral,
actual_value_map); \
+ generate_metric_value_map(
\
+ my_metric.get(), is_integral, filters, expected_metric_fields,
actual_value_map); \
\
value_map_comparator(actual_value_map, expected_value_map);
\
} while (0)
-TEST(metrics_test, take_snapshot_gauge_int64)
-{
- struct test_case
- {
- std::string entity_id;
- int64_t expected_value;
- } tests[]{{"server_60", 5}};
-
- for (const auto &test : tests) {
- TEST_METRIC_SNAPSHOT_WITH_SINGLE_VALUE(
- METRIC_test_gauge_int64, set, int64_t, true,
compare_integral_metric_value_map);
- }
-}
+const metric_fields_type kAllPrototypeMetricFields = {
+ kMetricTypeField, kMetricNameField, kMetricUnitField, kMetricDescField};
-TEST(metrics_test, take_snapshot_gauge_double)
+metric_fields_type get_all_single_value_metric_fields()
{
- struct test_case
- {
- std::string entity_id;
- double expected_value;
- } tests[]{{"server_60", 6.789}};
-
- for (const auto &test : tests) {
- TEST_METRIC_SNAPSHOT_WITH_SINGLE_VALUE(
- METRIC_test_gauge_double, set, double, false,
compare_floating_metric_value_map);
- }
+ auto fields = kAllPrototypeMetricFields;
+ fields.insert(kMetricSingleValueField);
+ return fields;
}
-#define TEST_METRIC_SNAPSHOT_WITH_COUNTER(metric_prototype)
\
- do {
\
- TEST_METRIC_SNAPSHOT_WITH_SINGLE_VALUE(
\
- metric_prototype, increment_by, int64_t, true,
compare_integral_metric_value_map); \
- } while (0)
-
-#define RUN_CASES_WITH_COUNTER_SNAPSHOT(metric_prototype)
\
+// Test cases:
+// - with_metric_fields is empty
+// - with_metric_fields has a field of prototype that exists
+// - with_metric_fields has a field of value that exists
+// - with_metric_fields has 2 fields of prototype that exist
+// - with_metric_fields has 2 fields that exist where there is a field of
prototype and a field
+// of value
+// - with_metric_fields has 3 fields that exist where there is 2 fields of
prototype and a field
+// of value
+// - with_metric_fields has all fields which exist
+// - with_metric_fields has a field that does not exist
+// - with_metric_fields has 2 fields both of which does not exist
+// - with_metric_fields has a field that does not exist and another field of
prototype that
+// exists
+// - with_metric_fields has a field that does not exist and another field of
value that exists
+// - with_metric_fields has a field that does not exist and another 2 fields
of prototype that
+// exists
+// - with_metric_fields has a field that does not exist and another 2 fields
that exist where
+// there is a field of prototype and a field of value
+// - with_metric_fields has a field that does not exist and another 3 fields
that exist where
+// there is 2 fields of prototype and a field of value
+// - with_metric_fields has 2 fields that does not exist and another 3 fields
that exist where
+// there is 2 fields of prototype and a field of value
+#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 =
\
+ get_all_single_value_metric_fields();
\
struct test_case
\
{
\
std::string entity_id;
\
- int64_t expected_value;
\
- } tests[]{{"server_60", 10}};
\
+ value_type expected_value;
\
+ metric_fields_type with_metric_fields;
\
+ metric_fields_type expected_metric_fields;
\
+ } tests[] = {
\
+ {"server_60", value, {}, kAllSingleValueMetricFields},
\
+ {"server_61", value, {kMetricNameField}, {kMetricNameField}},
\
+ {"server_62", value, {kMetricSingleValueField},
{kMetricSingleValueField}}, \
+ {"server_63",
\
+ value,
\
+ {kMetricNameField, kMetricDescField},
\
+ {kMetricNameField, kMetricDescField}},
\
+ {"server_64",
\
+ value,
\
+ {kMetricNameField, kMetricSingleValueField},
\
+ {kMetricNameField, kMetricSingleValueField}},
\
+ {"server_65",
\
+ value,
\
+ {kMetricTypeField, kMetricNameField, kMetricSingleValueField},
\
+ {kMetricTypeField, kMetricNameField, kMetricSingleValueField}},
\
+ {"server_66", value, kAllSingleValueMetricFields,
kAllSingleValueMetricFields}, \
+ {"server_67", value, {"field_not_exist"}, {}},
\
+ {"server_68", value, {"field_not_exist",
"another_field_not_exist"}, {}}, \
+ {"server_69", value, {"field_not_exist", kMetricNameField},
{kMetricNameField}}, \
+ {"server_70",
\
+ value,
\
+ {"field_not_exist", kMetricSingleValueField},
\
+ {kMetricSingleValueField}},
\
+ {"server_71",
\
+ value,
\
+ {"field_not_exist", kMetricTypeField, kMetricUnitField},
\
+ {kMetricTypeField, kMetricUnitField}},
\
+ {"server_72",
\
+ value,
\
+ {"field_not_exist", kMetricDescField, kMetricSingleValueField},
\
+ {kMetricDescField, kMetricSingleValueField}},
\
+ {"server_73",
\
+ value,
\
+ {"field_not_exist", kMetricTypeField, kMetricNameField,
kMetricSingleValueField}, \
+ {kMetricTypeField, kMetricNameField, kMetricSingleValueField}},
\
+ {"server_74",
\
+ value,
\
+ {"field_not_exist",
\
+ "another_field_not_exist",
\
+ kMetricUnitField,
\
+ kMetricDescField,
\
+ kMetricSingleValueField},
\
+ {kMetricUnitField, kMetricDescField, kMetricSingleValueField}}};
\
\
for (const auto &test : tests) {
\
- TEST_METRIC_SNAPSHOT_WITH_COUNTER(metric_prototype);
\
+ TEST_METRIC_SNAPSHOT_WITH_SINGLE_VALUE(metric_prototype,
\
+ updater,
\
+ value_type,
\
+ is_integral,
\
+ test.with_metric_fields,
\
+
test.expected_metric_fields, \
+ value_map_comparator);
\
}
\
- } while (0)
+ } while (0);
+
+#define RUN_CASES_WITH_GAUGE_SNAPSHOT(
\
+ metric_prototype, value_type, is_integral, value, value_map_comparator)
\
+ RUN_CASES_WITH_SINGLE_VALUE_SNAPSHOT(
\
+ metric_prototype, set, value_type, is_integral, value,
value_map_comparator)
+
+TEST(metrics_test, take_snapshot_gauge_int64)
+{
+ RUN_CASES_WITH_GAUGE_SNAPSHOT(
+ METRIC_test_gauge_int64, int64_t, true, 5,
compare_integral_metric_value_map);
+}
+
+TEST(metrics_test, take_snapshot_gauge_double)
+{
+ RUN_CASES_WITH_GAUGE_SNAPSHOT(
+ METRIC_test_gauge_double, double, false, 6.789,
compare_floating_metric_value_map);
+}
+
+#define RUN_CASES_WITH_COUNTER_SNAPSHOT(metric_prototype)
\
+ RUN_CASES_WITH_SINGLE_VALUE_SNAPSHOT(
\
+ metric_prototype, increment_by, int64_t, true, 10,
compare_integral_metric_value_map)
TEST(metrics_test, take_snapshot_counter) {
RUN_CASES_WITH_COUNTER_SNAPSHOT(METRIC_test_counter); }
@@ -1063,12 +1176,15 @@ TEST(metrics_test,
take_snapshot_concurrent_volatile_counter)
RUN_CASES_WITH_COUNTER_SNAPSHOT(METRIC_test_concurrent_volatile_counter);
}
+// Set to percentile metric with values output by case generator, and generate
the expected
+// value map.
template <typename MetricType, typename CaseGenerator>
void generate_metric_value_map(MetricType *my_metric,
CaseGenerator &generator,
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,
metric_value_map<typename
MetricType::value_type> &value_map)
{
using value_type = typename MetricType::value_type;
@@ -1089,54 +1205,162 @@ void generate_metric_value_map(MetricType *my_metric,
auto value = values.begin();
for (const auto &type : kth_percentiles) {
auto name = kth_percentile_to_name(type);
- value_map[name] = *value++;
+ // Only add the chosen fields to the expected value map.
+ if (expected_metric_fields.find(name) != expected_metric_fields.end())
{
+ value_map[name] = *value;
+ }
+ ++value;
}
}
-#define TEST_METRIC_SNAPSHOT_WITH_PERCENTILE(
\
- metric_prototype, case_generator, is_integral, value_map_comparator)
\
+#define TEST_METRIC_SNAPSHOT_WITH_PERCENTILE(metric_prototype,
\
+ case_generator,
\
+ is_integral,
\
+ metric_fields,
\
+ expected_metric_fields,
\
+ value_map_comparator)
\
do {
\
using value_type = typename case_generator::value_type;
\
\
+ const uint64_t interval_ms = 50;
\
+ const auto kth_percentiles = kAllKthPercentileTypes;
\
+ const size_t sample_size = 4096;
\
+ const size_t data_size = 4096;
\
+ const uint64_t exec_ms = 10;
\
+
\
auto my_server_entity =
METRIC_ENTITY_my_server.instantiate(test.entity_id); \
auto my_metric = metric_prototype.instantiate(
\
- my_server_entity, test.interval_ms, test.kth_percentiles,
test.sample_size); \
+ my_server_entity, interval_ms, kth_percentiles, sample_size);
\
\
- case_generator generator(test.data_size,
\
- value_type() /* initial_value */,
\
- 5 /* range_size */,
\
- test.kth_percentiles);
\
+ metric_filters filters;
\
+ filters.with_metric_fields = metric_fields;
\
+
\
+ case_generator generator(
\
+ data_size, value_type() /* initial_value */, 5 /* range_size */,
kth_percentiles); \
\
metric_value_map<value_type> expected_value_map;
\
generate_metric_value_map(my_metric.get(),
\
generator,
\
- test.interval_ms,
\
- test.exec_ms,
\
- test.kth_percentiles,
\
+ interval_ms,
\
+ exec_ms,
\
+ kth_percentiles,
\
+ expected_metric_fields,
\
expected_value_map);
\
\
metric_value_map<value_type> actual_value_map;
\
- generate_metric_value_map(my_metric.get(), is_integral,
actual_value_map); \
+ generate_metric_value_map(
\
+ my_metric.get(), is_integral, filters, expected_metric_fields,
actual_value_map); \
\
value_map_comparator(actual_value_map, expected_value_map);
\
} while (0)
+metric_fields_type get_all_kth_percentile_fields()
+{
+ auto fields = kAllPrototypeMetricFields;
+ for (const auto &kth : kAllKthPercentiles) {
+ fields.insert(kth.name);
+ }
+ return fields;
+}
+
+// Test cases:
+// - with_metric_fields is empty
+// - with_metric_fields has a field of prototype that exists
+// - with_metric_fields has a field of value that exists
+// - with_metric_fields has 2 fields of prototype that exist
+// - with_metric_fields has 2 fields of value that exist
+// - with_metric_fields has 2 fields that exist where there is a field of
prototype and a field
+// of value
+// - with_metric_fields has 3 fields that exist where there is 2 fields of
prototype and a field
+// of value
+// - with_metric_fields has 3 fields that exist where there is a field of
prototype and 2 fields
+// of value
+// - with_metric_fields has 4 fields that exist where there is 2 fields of
prototype and 2 fields
+// of value
+// - with_metric_fields has all fields that exist
+// - with_metric_fields has a field that does not exist
+// - with_metric_fields has 2 fields both of which does not exist
+// - with_metric_fields has a field that does not exist and another field of
prototype that
+// exists
+// - with_metric_fields has a field that does not exist and another field of
value that
+// exists
+// - with_metric_fields has a field that does not exist and another 2 fields
of prototype that
+// exist
+// - with_metric_fields has a field that does not exist and another 2 fields
of value that
+// exist
+// - with_metric_fields has a field that does not exist and another 2 fields
that exist where
+// there is a field of prototype and a field of value
+// - with_metric_fields has a field that does not exist and another 3 fields
that exist where
+// there is 2 fields of prototype and a field of value
+// - with_metric_fields has a field that does not exist and another 3 fields
that exist where
+// there is a field of prototype and 2 fields of value
+// - with_metric_fields has a field that does not exist and another 4 fields
that exist where
+// there is 2 fields of prototype and 2 fields of value
+// - with_metric_fields has 2 fields that does not exist and another 4 fields
that exist where
+// there is 2 fields of prototype and 2 fields of value
#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(); \
+
\
struct test_case
\
{
\
std::string entity_id;
\
- uint64_t interval_ms;
\
- std::set<kth_percentile_type> kth_percentiles;
\
- size_t sample_size;
\
- size_t data_size;
\
- uint64_t exec_ms;
\
- } tests[]{{"server_60", 50, kAllKthPercentileTypes, 4096, 4096, 10}};
\
+ metric_fields_type with_metric_fields;
\
+ metric_fields_type expected_metric_fields;
\
+ } tests[] = {
\
+ {"server_60", {}, kAllKthPercentileFields},
\
+ {"server_61", {kMetricNameField}, {kMetricNameField}},
\
+ {"server_62", {"p999"}, {"p999"}},
\
+ {"server_63",
\
+ {kMetricTypeField, kMetricDescField},
\
+ {kMetricTypeField, kMetricDescField}},
\
+ {"server_64", {"p50", "p999"}, {"p50", "p999"}},
\
+ {"server_65", {kMetricUnitField, "p99"}, {kMetricUnitField,
"p99"}}, \
+ {"server_66",
\
+ {kMetricNameField, kMetricUnitField, "p99"},
\
+ {kMetricNameField, kMetricUnitField, "p99"}},
\
+ {"server_67", {kMetricDescField, "p95", "p999"},
{kMetricDescField, "p95", "p999"}}, \
+ {"server_68",
\
+ {kMetricTypeField, kMetricNameField, "p90", "p99"},
\
+ {kMetricTypeField, kMetricNameField, "p90", "p99"}},
\
+ {"server_69", kAllKthPercentileFields, kAllKthPercentileFields},
\
+ {"server_70", {"field_not_exist"}, {}},
\
+ {"server_71", {"field_not_exist", "another_field_not_exist"}, {}},
\
+ {"server_72", {"file_not_exist", kMetricTypeField},
{kMetricTypeField}}, \
+ {"server_73", {"file_not_exist", "p99"}, {"p99"}},
\
+ {"server_74",
\
+ {"field_not_exist", kMetricUnitField, kMetricDescField},
\
+ {kMetricUnitField, kMetricDescField}},
\
+ {"server_75", {"file_not_exist", "p50", "p99"}, {"p50", "p99"}},
\
+ {"server_76",
\
+ {"field_not_exist", kMetricNameField, "p999"},
\
+ {kMetricNameField, "p999"}},
\
+ {"server_77",
\
+ {"field_not_exist", kMetricTypeField, kMetricUnitField, "p99"},
\
+ {kMetricTypeField, kMetricUnitField, "p99"}},
\
+ {"server_78",
\
+ {"field_not_exist", kMetricDescField, "p90", "p999"},
\
+ {kMetricDescField, "p90", "p999"}},
\
+ {"server_79",
\
+ {"field_not_exist", kMetricNameField, kMetricUnitField, "p95",
"p99"}, \
+ {kMetricNameField, kMetricUnitField, "p95", "p99"}},
\
+ {"server_80",
\
+ {"field_not_exist",
\
+ "another_field_not_exist",
\
+ kMetricTypeField,
\
+ kMetricDescField,
\
+ "p50",
\
+ "p999"},
\
+ {kMetricTypeField, kMetricDescField, "p50", "p999"}}};
\
\
for (const auto &test : tests) {
\
- TEST_METRIC_SNAPSHOT_WITH_PERCENTILE(
\
- metric_prototype, case_generator, is_integral,
value_map_comparator); \
+ TEST_METRIC_SNAPSHOT_WITH_PERCENTILE(metric_prototype,
\
+ case_generator,
\
+ is_integral,
\
+ test.with_metric_fields,
\
+ test.expected_metric_fields,
\
+ value_map_comparator);
\
}
\
} while (0)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]