agent-enviroments/builder/libs/seastar/tests/unit/metrics_test.cc
2024-09-10 17:06:08 +03:00

444 lines
18 KiB
C++

/*
* This file is open source software, licensed to you under the terms
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. You may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Copyright (C) 2019 ScyllaDB.
*/
#include <seastar/core/metrics_registration.hh>
#include <seastar/core/metrics.hh>
#include <seastar/core/metrics_api.hh>
#include <seastar/core/relabel_config.hh>
#include <seastar/core/scheduling.hh>
#include <seastar/core/sleep.hh>
#include <seastar/core/sharded.hh>
#include <seastar/core/do_with.hh>
#include <seastar/core/io_queue.hh>
#include <seastar/core/loop.hh>
#include <seastar/core/internal/estimated_histogram.hh>
#include <seastar/testing/random.hh>
#include <seastar/testing/test_case.hh>
#include <seastar/testing/thread_test_case.hh>
#include <seastar/testing/test_runner.hh>
#include <boost/range/irange.hpp>
SEASTAR_TEST_CASE(test_add_group) {
using namespace seastar::metrics;
// Just has to compile:
metric_groups()
.add_group("g1", {})
.add_group("g2", std::vector<metric_definition>());
return seastar::make_ready_future();
}
/**
* This function return the different name label values
* for the named metric.
*
* @note: If the statistic or label doesn't exist, the test
* that calls this function will fail.
*
* @param metric_name - the metric name
* @param label_name - the label name
* @return a set containing all the different values
* of the label.
*/
static std::set<seastar::sstring> get_label_values(seastar::sstring metric_name, seastar::sstring label_name) {
namespace smi = seastar::metrics::impl;
auto all_metrics = smi::get_values();
const auto& all_metadata = *all_metrics->metadata;
const auto qp_group = find_if(cbegin(all_metadata), cend(all_metadata),
[&metric_name] (const auto& x) { return x.mf.name == metric_name; });
BOOST_REQUIRE(qp_group != cend(all_metadata));
std::set<seastar::sstring> labels;
for (const auto& metric : qp_group->metrics) {
const auto found = metric.id.labels().find(label_name);
BOOST_REQUIRE(found != metric.id.labels().cend());
labels.insert(found->second);
}
return labels;
}
SEASTAR_THREAD_TEST_CASE(test_renaming_scheuling_groups) {
// this seams a little bit out of place but the
// renaming functionality is primarily for statistics
// otherwise those classes could have just been reused
// without renaming them.
using namespace seastar;
static const char* name1 = "A";
static const char* name2 = "B";
scheduling_group sg = create_scheduling_group("hello", 111).get();
boost::integer_range<int> rng(0, 1000);
// repeatedly change the group name back and forth in
// decresing time intervals to see if it generate double
//registration statistics errors.
for (auto&& i : rng) {
const char* name = i%2 ? name1 : name2;
const char* prev_name = i%2 ? name2 : name1;
sleep(std::chrono::microseconds(100000/(i+1))).get();
rename_scheduling_group(sg, name).get();
std::set<sstring> label_vals = get_label_values(sstring("scheduler_shares"), sstring("group"));
// validate that the name that we *renamed to* is in the stats
BOOST_REQUIRE(label_vals.find(sstring(name)) != label_vals.end());
// validate that the name that we *renamed from* is *not* in the stats
BOOST_REQUIRE(label_vals.find(sstring(prev_name)) == label_vals.end());
}
smp::invoke_on_all([sg] () {
return do_with(std::uniform_int_distribution<int>(), boost::irange<int>(0, 1000),
[sg] (std::uniform_int_distribution<int>& dist, boost::integer_range<int>& rng) {
// flip a fair coin and rename to one of two options and rename to that
// scheduling group name, do it 1000 in parallel on all shards so there
// is a chance of collision.
return do_for_each(rng, [sg, &dist] (auto i) {
bool odd = dist(seastar::testing::local_random_engine)%2;
return rename_scheduling_group(sg, odd ? name1 : name2);
});
});
}).get();
std::set<sstring> label_vals = get_label_values(sstring("scheduler_shares"), sstring("group"));
// validate that only one of the names is eventually in the metrics
bool name1_found = label_vals.find(sstring(name1)) != label_vals.end();
bool name2_found = label_vals.find(sstring(name2)) != label_vals.end();
BOOST_REQUIRE((name1_found && !name2_found) || (name2_found && !name1_found));
}
#if SEASTAR_API_LEVEL < 7
SEASTAR_THREAD_TEST_CASE(test_renaming_io_priority_classes) {
// this seams a little bit out of place but the
// renaming functionality is primarily for statistics
// otherwise those classes could have just been reused
// without renaming them.
using namespace seastar;
static const char* name1 = "A";
static const char* name2 = "B";
seastar::io_priority_class pc = io_priority_class::register_one("hello",100);
smp::invoke_on_all([&pc] () {
// this is a trick to get all of the queues actually register their
// stats.
return pc.update_shares(101);
}).get();
boost::integer_range<int> rng(0, 1000);
// repeatedly change the group name back and forth in
// decresing time intervals to see if it generate double
//registration statistics errors.
for (auto&& i : rng) {
const char* name = i%2 ? name1 : name2;
const char* prev_name = i%2 ? name2 : name1;
sleep(std::chrono::microseconds(100000/(i+1))).get();
pc.rename(name).get();
std::set<sstring> label_vals = get_label_values(sstring("io_queue_shares"), sstring("class"));
// validate that the name that we *renamed to* is in the stats
BOOST_REQUIRE(label_vals.find(sstring(name)) != label_vals.end());
// validate that the name that we *renamed from* is *not* in the stats
BOOST_REQUIRE(label_vals.find(sstring(prev_name)) == label_vals.end());
}
smp::invoke_on_all([&pc] () {
return do_with(std::uniform_int_distribution<int>(), boost::irange<int>(0, 1000),
[&pc] (std::uniform_int_distribution<int>& dist, boost::integer_range<int>& rng) {
// flip a fair coin and rename to one of two options and rename to that
// scheduling group name, do it 1000 in parallel on all shards so there
// is a chance of collision.
return do_for_each(rng, [&pc, &dist] (auto i) {
bool odd = dist(seastar::testing::local_random_engine)%2;
return pc.rename(odd ? name1 : name2);
});
});
}).get();
std::set<sstring> label_vals = get_label_values(sstring("io_queue_shares"), sstring("class"));
// validate that only one of the names is eventually in the metrics
bool name1_found = label_vals.find(sstring(name1)) != label_vals.end();
bool name2_found = label_vals.find(sstring(name2)) != label_vals.end();
BOOST_REQUIRE((name1_found && !name2_found) || (name2_found && !name1_found));
}
#endif
int count_by_label(const std::string& label) {
seastar::foreign_ptr<seastar::metrics::impl::values_reference> values = seastar::metrics::impl::get_values();
int count = 0;
for (auto&& md : (*values->metadata)) {
for (auto&& mi : md.metrics) {
if (label == "" || mi.id.labels().find(label) != mi.id.labels().end()) {
count++;
}
}
}
return count;
}
int count_by_fun(std::function<bool(const seastar::metrics::impl::metric_info&)> f) {
seastar::foreign_ptr<seastar::metrics::impl::values_reference> values = seastar::metrics::impl::get_values();
int count = 0;
for (auto&& md : (*values->metadata)) {
for (auto&& mi : md.metrics) {
if (f(mi)) {
count++;
}
}
}
return count;
}
SEASTAR_THREAD_TEST_CASE(test_relabel_add_labels) {
using namespace seastar::metrics;
namespace sm = seastar::metrics;
sm::metric_groups app_metrics;
app_metrics.add_group("test", {
sm::make_gauge("gauge_1", sm::description("gague 1"), [] { return 0; }),
sm::make_counter("counter_1", sm::description("counter 1"), [] { return 1; })
});
std::vector<sm::relabel_config> rl(1);
rl[0].source_labels = {"__name__"};
rl[0].target_label = "level";
rl[0].replacement = "1";
rl[0].expr = "test_counter_.*";
sm::metric_relabeling_result success = sm::set_relabel_configs(rl).get();
BOOST_CHECK_EQUAL(success.metrics_relabeled_due_to_collision, 0);
BOOST_CHECK_EQUAL(count_by_label("level"), 1);
app_metrics.add_group("test", {
sm::make_counter("counter_2", sm::description("counter 2"), [] { return 2; })
});
BOOST_CHECK_EQUAL(count_by_label("level"), 2);
sm::set_relabel_configs({}).get();
}
SEASTAR_THREAD_TEST_CASE(test_metrics_family_aggregate) {
using namespace seastar::metrics;
namespace sm = seastar::metrics;
sm::metric_groups app_metrics;
sm::label lb("lb");
app_metrics.add_group("test", {
sm::make_gauge("gauge_1", sm::description("gague 1"), [] { return 1; })(lb("1")),
sm::make_gauge("gauge_1", sm::description("gague 1"), [] { return 2; })(lb("2")),
sm::make_counter("counter_1", sm::description("counter 1"), [] { return 3; })(lb("1")),
sm::make_counter("counter_1", sm::description("counter 1"), [] { return 4; })(lb("2"))
});
std::vector<sm::relabel_config> rl(2);
rl[0].source_labels = {"__name__"};
rl[0].action = sm::relabel_config::relabel_action::drop;
rl[1].source_labels = {"lb"};
rl[1].action = sm::relabel_config::relabel_action::keep;
// Dropping the lev label would cause a conflict, but not crash the system
sm::set_relabel_configs(rl).get();
std::vector<sm::metric_family_config> fc(2);
fc[0].name = "test_gauge_1";
fc[0].aggregate_labels = { "lb" };
fc[1].regex_name = "test_gauge1.*";
fc[1].aggregate_labels = { "ll", "aa" };
sm::set_metric_family_configs(fc);
seastar::foreign_ptr<seastar::metrics::impl::values_reference> values = seastar::metrics::impl::get_values();
int count = 0;
for (auto&& md : (*values->metadata)) {
if (md.mf.name == "test_gauge_1") {
BOOST_CHECK_EQUAL(md.mf.aggregate_labels.size(), 1);
BOOST_CHECK_EQUAL(md.mf.aggregate_labels[0], "lb");
} else {
BOOST_CHECK_EQUAL(md.mf.aggregate_labels.size(), 0);
}
count++;
}
BOOST_CHECK_EQUAL(count, 2);
app_metrics.add_group("test", {
sm::make_gauge("gauge1_1", sm::description("gague 1"), [] { return 1; })(lb("1")),
sm::make_gauge("gauge1_1", sm::description("gague 1"), [] { return 2; })(lb("2")),
sm::make_counter("counter1_1", sm::description("counter 1"), [] { return 3; })(lb("1")),
sm::make_counter("counter1_1", sm::description("counter 1"), [] { return 4; })(lb("2"))
});
values = seastar::metrics::impl::get_values();
count = 0;
for (auto&& md : (*values->metadata)) {
if (md.mf.name == "test_gauge_1") {
BOOST_CHECK_EQUAL(md.mf.aggregate_labels.size(), 1);
BOOST_CHECK_EQUAL(md.mf.aggregate_labels[0], "lb");
} else if (md.mf.name == "test_gauge1_1") {
BOOST_CHECK_EQUAL(md.mf.aggregate_labels.size(), 2);
BOOST_CHECK_EQUAL(md.mf.aggregate_labels[0], "ll");
} else {
BOOST_CHECK_EQUAL(md.mf.aggregate_labels.size(), 0);
}
count++;
}
BOOST_CHECK_EQUAL(count, 4);
std::vector<sm::relabel_config> rl1;
sm::set_relabel_configs(rl1).get();
}
SEASTAR_THREAD_TEST_CASE(test_relabel_drop_label_prevent_runtime_conflicts) {
using namespace seastar::metrics;
namespace sm = seastar::metrics;
sm::metric_groups app_metrics;
app_metrics.add_group("test2", {
sm::make_gauge("gauge_1", sm::description("gague 1"), { sm::label_instance("g", "1")}, [] { return 0; }),
sm::make_counter("counter_1", sm::description("counter 1"), [] { return 0; }),
sm::make_counter("counter_1", sm::description("counter 1"), { sm::label_instance("lev", "2")}, [] { return 0; })
});
BOOST_CHECK_EQUAL(count_by_label("lev"), 1);
std::vector<sm::relabel_config> rl(1);
rl[0].source_labels = {"lev"};
rl[0].expr = "2";
rl[0].target_label = "lev";
rl[0].action = sm::relabel_config::relabel_action::drop_label;
// Dropping the lev label would cause a conflict, but not crash the system
sm::metric_relabeling_result success = sm::set_relabel_configs(rl).get();
BOOST_CHECK_EQUAL(success.metrics_relabeled_due_to_collision, 1);
BOOST_CHECK_EQUAL(count_by_label("lev"), 0);
BOOST_CHECK_EQUAL(count_by_label("err"), 1);
//reseting all the labels to their original state
success = sm::set_relabel_configs({}).get();
BOOST_CHECK_EQUAL(success.metrics_relabeled_due_to_collision, 0);
BOOST_CHECK_EQUAL(count_by_label("lev"), 1);
BOOST_CHECK_EQUAL(count_by_label("err"), 0);
sm::set_relabel_configs({}).get();
}
SEASTAR_THREAD_TEST_CASE(test_relabel_enable_disable_skip_when_empty) {
using namespace seastar::metrics;
namespace sm = seastar::metrics;
sm::metric_groups app_metrics;
app_metrics.add_group("test3", {
sm::make_gauge("gauge_1", sm::description("gague 1"), { sm::label_instance("lev3", "3")}, [] { return 0; }),
sm::make_counter("counter_1", sm::description("counter 1"), { sm::label_instance("lev3", "3")}, [] { return 0; }),
sm::make_counter("counter_2", sm::description("counter 2"), { sm::label_instance("lev3", "3")}, [] { return 0; })
});
std::vector<sm::relabel_config> rl(2);
rl[0].source_labels = {"__name__"};
rl[0].action = sm::relabel_config::relabel_action::drop;
rl[1].source_labels = {"lev3"};
rl[1].expr = "3";
rl[1].action = sm::relabel_config::relabel_action::keep;
// We just disable all metrics besides those mark as lev3
sm::metric_relabeling_result success = sm::set_relabel_configs(rl).get();
BOOST_CHECK_EQUAL(success.metrics_relabeled_due_to_collision, 0);
BOOST_CHECK_EQUAL(count_by_label(""), 3);
BOOST_CHECK_EQUAL(count_by_fun([](const seastar::metrics::impl::metric_info& mi) {
return mi.should_skip_when_empty == sm::skip_when_empty::yes;
}), 0);
std::vector<sm::relabel_config> rl2(3);
rl2[0].source_labels = {"__name__"};
rl2[0].action = sm::relabel_config::relabel_action::drop;
rl2[1].source_labels = {"lev3"};
rl2[1].expr = "3";
rl2[1].action = sm::relabel_config::relabel_action::keep;
rl2[2].source_labels = {"__name__"};
rl2[2].expr = "test3.*";
rl2[2].action = sm::relabel_config::relabel_action::skip_when_empty;
success = sm::set_relabel_configs(rl2).get();
BOOST_CHECK_EQUAL(success.metrics_relabeled_due_to_collision, 0);
BOOST_CHECK_EQUAL(count_by_label(""), 3);
BOOST_CHECK_EQUAL(count_by_fun([](const seastar::metrics::impl::metric_info& mi) {
return mi.should_skip_when_empty == sm::skip_when_empty::yes;
}), 3);
// clear the configuration
success = sm::set_relabel_configs({}).get();
app_metrics.add_group("test3", {
sm::make_counter("counter_3", sm::description("counter 2"), { sm::label_instance("lev3", "3")}, [] { return 0; })(sm::skip_when_empty::yes)
});
std::vector<sm::relabel_config> rl3(3);
rl3[0].source_labels = {"__name__"};
rl3[0].action = sm::relabel_config::relabel_action::drop;
rl3[1].source_labels = {"lev3"};
rl3[1].expr = "3";
rl3[1].action = sm::relabel_config::relabel_action::keep;
rl3[2].source_labels = {"__name__"};
rl3[2].expr = "test3.*";
rl3[2].action = sm::relabel_config::relabel_action::report_when_empty;
success = sm::set_relabel_configs(rl3).get();
BOOST_CHECK_EQUAL(success.metrics_relabeled_due_to_collision, 0);
BOOST_CHECK_EQUAL(count_by_fun([](const seastar::metrics::impl::metric_info& mi) {
return mi.should_skip_when_empty == sm::skip_when_empty::yes;
}), 0);
sm::set_relabel_configs({}).get();
}
SEASTAR_THREAD_TEST_CASE(test_estimated_histogram) {
using namespace seastar::metrics;
using namespace std::chrono_literals;
internal::time_estimated_histogram histogram1;
internal::time_estimated_histogram histogram2;
// The number of linearly-spaced buckets between consecutive powers of 2 in time_estimated_histogram.
constexpr int PRECISION = 4;
// The lower bound of time_estimated_histogram is 512 us.
std::chrono::steady_clock::duration min = std::chrono::microseconds(512);
std::chrono::steady_clock::duration next = min*2;
for (size_t i = 0; i < 16; i++) {
auto delta = (next - min)/PRECISION;
for (size_t j = 0; j< PRECISION; j++) {
histogram1.add(min + delta*j);
}
min = next;
next *= 2;
}
BOOST_CHECK_EQUAL(histogram1.count(), 64);
for (size_t i = 0; i < 64; i++) {
BOOST_CHECK_EQUAL(histogram1.get(i), 1);
}
min = std::chrono::microseconds(512);
next = min*2;
for (size_t i = 0; i < 8; i++) {
auto delta = (next - min)/PRECISION;
for (size_t j = 0; j< PRECISION; j++) {
histogram2.add(min + delta*j);
}
min = next;
next *= 2;
}
BOOST_CHECK_EQUAL(histogram2.count(), 32);
for (size_t i = 0; i < 32; i++) {
BOOST_CHECK_EQUAL(histogram2.get(i), 1);
}
for (size_t i = 33; i < 64; i++) {
BOOST_CHECK_EQUAL(histogram2.get(i), 0);
}
histogram1.merge(histogram2);
BOOST_CHECK_EQUAL(histogram1.count(), 96);
for (size_t i = 0; i < 32; i++) {
BOOST_CHECK_EQUAL(histogram1.get(i), 2);
}
for (size_t i = 33; i < 64; i++) {
BOOST_CHECK_EQUAL(histogram1.get(i), 1);
}
auto mh = histogram1.to_metrics_histogram();
for (size_t i = 0; i < 32; i++) {
BOOST_CHECK_EQUAL(mh.buckets[i].count, 2 + i*2);
}
for (size_t i = 33; i < 64; i++) {
BOOST_CHECK_EQUAL(mh.buckets[i].count, 33 + i);
}
}