The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#) https://grpc.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

529 lines
23 KiB

// Copyright 2024 The gRPC Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// 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.
#include "src/core/lib/channel/metrics.h"
#include <memory>
#include "absl/container/flat_hash_map.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "test/core/util/test_config.h"
namespace grpc_core {
namespace {
void AddKeyValuePairs(absl::Span<const absl::string_view> keys,
absl::Span<const absl::string_view> values,
std::vector<std::string>* key_value_pairs) {
GPR_ASSERT(keys.size() == values.size());
for (size_t i = 0; i < keys.size(); ++i) {
key_value_pairs->push_back(absl::StrCat(keys[i], "=", values[i]));
}
}
std::string MakeLabelString(
absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_label_keys,
absl::Span<const absl::string_view> optional_values) {
std::vector<std::string> key_value_pairs;
AddKeyValuePairs(label_keys, label_values, &key_value_pairs);
AddKeyValuePairs(optional_label_keys, optional_values, &key_value_pairs);
return absl::StrJoin(key_value_pairs, ",");
}
// TODO(yijiem): Move this to test/core/util/fake_stats_plugin.h
class FakeStatsPlugin : public StatsPlugin {
public:
bool IsEnabledForChannel(
const StatsPlugin::ChannelScope& scope) const override {
return channel_filter_(scope);
}
bool IsEnabledForServer(const ChannelArgs& /*args*/) const override {
return false;
}
void AddCounter(
GlobalInstrumentsRegistry::GlobalUInt64CounterHandle handle,
uint64_t value, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) override {
// The problem with this approach is that we initialize uint64_counters_ in
// BuildAndRegister by querying the GlobalInstrumentsRegistry at the time.
// If the GlobalInstrumentsRegistry has changed since then (which we
// currently don't allow), we might not have seen that descriptor nor have
// we created an instrument for it. We probably could copy the existing
// instruments at build time and for the handle that we haven't seen we will
// just ignore it here. This would also prevent us from having to lock the
// GlobalInstrumentsRegistry everytime a metric is recorded. But this is not
// a concern for now.
auto iter = uint64_counters_.find(handle.index);
if (iter == uint64_counters_.end()) {
return;
}
iter->second.Add(value, label_values, optional_values);
}
void AddCounter(
GlobalInstrumentsRegistry::GlobalDoubleCounterHandle handle, double value,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) override {
auto iter = double_counters_.find(handle.index);
if (iter == double_counters_.end()) {
return;
}
iter->second.Add(value, label_values, optional_values);
}
void RecordHistogram(
GlobalInstrumentsRegistry::GlobalUInt64HistogramHandle handle,
uint64_t value, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) override {
auto iter = uint64_histograms_.find(handle.index);
if (iter == uint64_histograms_.end()) {
return;
}
iter->second.Record(value, label_values, optional_values);
}
void RecordHistogram(
GlobalInstrumentsRegistry::GlobalDoubleHistogramHandle handle,
double value, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) override {
auto iter = double_histograms_.find(handle.index);
if (iter == double_histograms_.end()) {
return;
}
iter->second.Record(value, label_values, optional_values);
}
absl::optional<uint64_t> GetCounterValue(
GlobalInstrumentsRegistry::GlobalUInt64CounterHandle handle,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = uint64_counters_.find(handle.index);
if (iter == uint64_counters_.end()) {
return absl::nullopt;
}
return iter->second.GetValue(label_values, optional_values);
}
absl::optional<double> GetCounterValue(
GlobalInstrumentsRegistry::GlobalDoubleCounterHandle handle,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = double_counters_.find(handle.index);
if (iter == double_counters_.end()) {
return absl::nullopt;
}
return iter->second.GetValue(label_values, optional_values);
}
absl::optional<std::vector<uint64_t>> GetHistogramValue(
GlobalInstrumentsRegistry::GlobalUInt64HistogramHandle handle,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = uint64_histograms_.find(handle.index);
if (iter == uint64_histograms_.end()) {
return absl::nullopt;
}
return iter->second.GetValues(label_values, optional_values);
}
absl::optional<std::vector<double>> GetHistogramValue(
GlobalInstrumentsRegistry::GlobalDoubleHistogramHandle handle,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = double_histograms_.find(handle.index);
if (iter == double_histograms_.end()) {
return absl::nullopt;
}
return iter->second.GetValues(label_values, optional_values);
}
private:
friend class FakeStatsPluginBuilder;
explicit FakeStatsPlugin(
absl::AnyInvocable<bool(const StatsPlugin::ChannelScope& /*scope*/) const>
channel_filter)
: channel_filter_(std::move(channel_filter)) {
GlobalInstrumentsRegistry::ForEach(
[this](const GlobalInstrumentsRegistry::GlobalInstrumentDescriptor&
descriptor) {
if (!descriptor.enable_by_default) {
return;
}
if (descriptor.instrument_type ==
GlobalInstrumentsRegistry::InstrumentType::kCounter) {
if (descriptor.value_type ==
GlobalInstrumentsRegistry::ValueType::kUInt64) {
uint64_counters_.emplace(descriptor.index, descriptor);
} else {
double_counters_.emplace(descriptor.index, descriptor);
}
} else {
EXPECT_EQ(descriptor.instrument_type,
GlobalInstrumentsRegistry::InstrumentType::kHistogram);
if (descriptor.value_type ==
GlobalInstrumentsRegistry::ValueType::kUInt64) {
uint64_histograms_.emplace(descriptor.index, descriptor);
} else {
double_histograms_.emplace(descriptor.index, descriptor);
}
}
});
}
template <class T>
class Counter {
public:
explicit Counter(GlobalInstrumentsRegistry::GlobalInstrumentDescriptor u)
: name_(u.name),
description_(u.description),
unit_(u.unit),
label_keys_(std::move(u.label_keys)),
optional_label_keys_(std::move(u.optional_label_keys)) {}
void Add(T t, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = storage_.find(MakeLabelString(
label_keys_, label_values, optional_label_keys_, optional_values));
if (iter != storage_.end()) {
iter->second += t;
} else {
storage_[MakeLabelString(label_keys_, label_values,
optional_label_keys_, optional_values)] = t;
}
}
absl::optional<T> GetValue(
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = storage_.find(MakeLabelString(
label_keys_, label_values, optional_label_keys_, optional_values));
if (iter == storage_.end()) {
return absl::nullopt;
}
return iter->second;
}
private:
absl::string_view name_;
absl::string_view description_;
absl::string_view unit_;
std::vector<absl::string_view> label_keys_;
std::vector<absl::string_view> optional_label_keys_;
// Aggregation of the same key attributes.
absl::flat_hash_map<std::string, T> storage_;
};
template <class T>
class Histogram {
public:
explicit Histogram(GlobalInstrumentsRegistry::GlobalInstrumentDescriptor u)
: name_(u.name),
description_(u.description),
unit_(u.unit),
label_keys_(std::move(u.label_keys)),
optional_label_keys_(std::move(u.optional_label_keys)) {}
void Record(T t, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
std::string key = MakeLabelString(label_keys_, label_values,
optional_label_keys_, optional_values);
auto iter = storage_.find(key);
if (iter == storage_.end()) {
storage_.emplace(key, std::initializer_list<T>{t});
} else {
iter->second.push_back(t);
}
}
absl::optional<std::vector<T>> GetValues(
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = storage_.find(MakeLabelString(
label_keys_, label_values, optional_label_keys_, optional_values));
if (iter == storage_.end()) {
return absl::nullopt;
}
return iter->second;
}
private:
absl::string_view name_;
absl::string_view description_;
absl::string_view unit_;
std::vector<absl::string_view> label_keys_;
std::vector<absl::string_view> optional_label_keys_;
absl::flat_hash_map<std::string, std::vector<T>> storage_;
};
absl::AnyInvocable<bool(const StatsPlugin::ChannelScope& /*scope*/) const>
channel_filter_;
// Instruments.
absl::flat_hash_map<uint32_t, Counter<uint64_t>> uint64_counters_;
absl::flat_hash_map<uint32_t, Counter<double>> double_counters_;
absl::flat_hash_map<uint32_t, Histogram<uint64_t>> uint64_histograms_;
absl::flat_hash_map<uint32_t, Histogram<double>> double_histograms_;
};
// TODO(yijiem): Move this to test/core/util/fake_stats_plugin.h
class FakeStatsPluginBuilder {
public:
FakeStatsPluginBuilder& SetChannelFilter(
absl::AnyInvocable<bool(const StatsPlugin::ChannelScope& /*scope*/) const>
channel_filter) {
channel_filter_ = std::move(channel_filter);
return *this;
}
std::shared_ptr<FakeStatsPlugin> BuildAndRegister() {
auto f = std::shared_ptr<FakeStatsPlugin>(
new FakeStatsPlugin(std::move(channel_filter_)));
GlobalStatsPluginRegistry::RegisterStatsPlugin(f);
return f;
}
private:
absl::AnyInvocable<bool(const StatsPlugin::ChannelScope& /*scope*/) const>
channel_filter_;
};
std::shared_ptr<FakeStatsPlugin> MakeStatsPluginForTarget(
absl::string_view target_suffix) {
return FakeStatsPluginBuilder()
.SetChannelFilter(
[target_suffix](const StatsPlugin::ChannelScope& scope) {
return absl::EndsWith(scope.target(), target_suffix);
})
.BuildAndRegister();
}
class MetricsTest : public testing::Test {
public:
void TearDown() override {
GlobalInstrumentsRegistry::TestOnlyResetGlobalInstrumentsRegistry();
GlobalStatsPluginRegistry::TestOnlyResetGlobalStatsPluginRegistry();
}
};
TEST_F(MetricsTest, UInt64Counter) {
const absl::string_view kLabelKeys[] = {"label_key_1", "label_key_2"};
const absl::string_view kOptionalLabelKeys[] = {"optional_label_key_1",
"optional_label_key_2"};
auto uint64_counter_handle = GlobalInstrumentsRegistry::RegisterUInt64Counter(
"uint64_counter", "A simple uint64 counter.", "unit", kLabelKeys,
kOptionalLabelKeys, true);
constexpr absl::string_view kLabelValues[] = {"label_value_1",
"label_value_2"};
constexpr absl::string_view kOptionalLabelValues[] = {
"optional_label_value_1", "optional_label_value_2"};
constexpr absl::string_view kDomain1To4 = "domain1.domain2.domain3.domain4";
constexpr absl::string_view kDomain2To4 = "domain2.domain3.domain4";
constexpr absl::string_view kDomain3To4 = "domain3.domain4";
auto plugin1 = MakeStatsPluginForTarget(kDomain1To4);
auto plugin2 = MakeStatsPluginForTarget(kDomain2To4);
auto plugin3 = MakeStatsPluginForTarget(kDomain3To4);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain1To4, ""))
.AddCounter(uint64_counter_handle, 1, kLabelValues, kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain2To4, ""))
.AddCounter(uint64_counter_handle, 2, kLabelValues, kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain3To4, ""))
.AddCounter(uint64_counter_handle, 3, kLabelValues, kOptionalLabelValues);
EXPECT_THAT(plugin1->GetCounterValue(uint64_counter_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(1));
EXPECT_THAT(plugin2->GetCounterValue(uint64_counter_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(3));
EXPECT_THAT(plugin3->GetCounterValue(uint64_counter_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(6));
}
TEST_F(MetricsTest, DoubleCounter) {
const absl::string_view kLabelKeys[] = {"label_key_1", "label_key_2"};
const absl::string_view kOptionalLabelKeys[] = {"optional_label_key_1",
"optional_label_key_2"};
auto double_counter_handle = GlobalInstrumentsRegistry::RegisterDoubleCounter(
"double_counter", "A simple double counter.", "unit", kLabelKeys,
kOptionalLabelKeys, true);
constexpr absl::string_view kLabelValues[] = {"label_value_1",
"label_value_2"};
constexpr absl::string_view kOptionalLabelValues[] = {
"optional_label_value_1", "optional_label_value_2"};
constexpr absl::string_view kDomain1To4 = "domain1.domain2.domain3.domain4";
constexpr absl::string_view kDomain2To4 = "domain2.domain3.domain4";
constexpr absl::string_view kDomain3To4 = "domain3.domain4";
auto plugin1 = MakeStatsPluginForTarget(kDomain1To4);
auto plugin2 = MakeStatsPluginForTarget(kDomain2To4);
auto plugin3 = MakeStatsPluginForTarget(kDomain3To4);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain1To4, ""))
.AddCounter(double_counter_handle, 1.23, kLabelValues,
kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain2To4, ""))
.AddCounter(double_counter_handle, 2.34, kLabelValues,
kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain3To4, ""))
.AddCounter(double_counter_handle, 3.45, kLabelValues,
kOptionalLabelValues);
EXPECT_THAT(plugin1->GetCounterValue(double_counter_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(1.23));
EXPECT_THAT(plugin2->GetCounterValue(double_counter_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(3.57));
EXPECT_THAT(plugin3->GetCounterValue(double_counter_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(7.02));
}
TEST_F(MetricsTest, UInt64Histogram) {
const absl::string_view kLabelKeys[] = {"label_key_1", "label_key_2"};
const absl::string_view kOptionalLabelKeys[] = {"optional_label_key_1",
"optional_label_key_2"};
auto uint64_histogram_handle =
GlobalInstrumentsRegistry::RegisterUInt64Histogram(
"uint64_histogram", "A simple uint64 histogram.", "unit", kLabelKeys,
kOptionalLabelKeys, true);
constexpr absl::string_view kLabelValues[] = {"label_value_1",
"label_value_2"};
constexpr absl::string_view kOptionalLabelValues[] = {
"optional_label_value_1", "optional_label_value_2"};
constexpr absl::string_view kDomain1To4 = "domain1.domain2.domain3.domain4";
constexpr absl::string_view kDomain2To4 = "domain2.domain3.domain4";
constexpr absl::string_view kDomain3To4 = "domain3.domain4";
auto plugin1 = MakeStatsPluginForTarget(kDomain1To4);
auto plugin2 = MakeStatsPluginForTarget(kDomain2To4);
auto plugin3 = MakeStatsPluginForTarget(kDomain3To4);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain1To4, ""))
.RecordHistogram(uint64_histogram_handle, 1, kLabelValues,
kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain2To4, ""))
.RecordHistogram(uint64_histogram_handle, 2, kLabelValues,
kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain3To4, ""))
.RecordHistogram(uint64_histogram_handle, 3, kLabelValues,
kOptionalLabelValues);
EXPECT_THAT(plugin1->GetHistogramValue(uint64_histogram_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(::testing::UnorderedElementsAre(1)));
EXPECT_THAT(plugin2->GetHistogramValue(uint64_histogram_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(::testing::UnorderedElementsAre(1, 2)));
EXPECT_THAT(plugin3->GetHistogramValue(uint64_histogram_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(::testing::UnorderedElementsAre(1, 2, 3)));
}
TEST_F(MetricsTest, DoubleHistogram) {
const absl::string_view kLabelKeys[] = {"label_key_1", "label_key_2"};
const absl::string_view kOptionalLabelKeys[] = {"optional_label_key_1",
"optional_label_key_2"};
auto double_histogram_handle =
GlobalInstrumentsRegistry::RegisterDoubleHistogram(
"double_histogram", "A simple double histogram.", "unit", kLabelKeys,
kOptionalLabelKeys, true);
constexpr absl::string_view kLabelValues[] = {"label_value_1",
"label_value_2"};
constexpr absl::string_view kOptionalLabelValues[] = {
"optional_label_value_1", "optional_label_value_2"};
constexpr absl::string_view kDomain1To4 = "domain1.domain2.domain3.domain4";
constexpr absl::string_view kDomain2To4 = "domain2.domain3.domain4";
constexpr absl::string_view kDomain3To4 = "domain3.domain4";
auto plugin1 = MakeStatsPluginForTarget(kDomain1To4);
auto plugin2 = MakeStatsPluginForTarget(kDomain2To4);
auto plugin3 = MakeStatsPluginForTarget(kDomain3To4);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain1To4, ""))
.RecordHistogram(double_histogram_handle, 1.23, kLabelValues,
kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain2To4, ""))
.RecordHistogram(double_histogram_handle, 2.34, kLabelValues,
kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain3To4, ""))
.RecordHistogram(double_histogram_handle, 3.45, kLabelValues,
kOptionalLabelValues);
EXPECT_THAT(plugin1->GetHistogramValue(double_histogram_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(::testing::UnorderedElementsAre(1.23)));
EXPECT_THAT(plugin2->GetHistogramValue(double_histogram_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(::testing::UnorderedElementsAre(1.23, 2.34)));
EXPECT_THAT(
plugin3->GetHistogramValue(double_histogram_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(::testing::UnorderedElementsAre(1.23, 2.34, 3.45)));
}
TEST_F(MetricsTest, DisableByDefaultMetricIsNotRecordedByFakeStatsPlugin) {
const absl::string_view kLabelKeys[] = {"label_key_1", "label_key_2"};
const absl::string_view kOptionalLabelKeys[] = {"optional_label_key_1",
"optional_label_key_2"};
auto double_histogram_handle =
GlobalInstrumentsRegistry::RegisterDoubleHistogram(
"double_histogram", "A simple double histogram.", "unit", kLabelKeys,
kOptionalLabelKeys, /*enable_by_default=*/false);
constexpr absl::string_view kLabelValues[] = {"label_value_1",
"label_value_2"};
constexpr absl::string_view kOptionalLabelValues[] = {
"optional_label_value_1", "optional_label_value_2"};
constexpr absl::string_view kDomain1To4 = "domain1.domain2.domain3.domain4";
auto plugin = MakeStatsPluginForTarget(kDomain1To4);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain1To4, ""))
.RecordHistogram(double_histogram_handle, 1.23, kLabelValues,
kOptionalLabelValues);
EXPECT_EQ(plugin->GetHistogramValue(double_histogram_handle, kLabelValues,
kOptionalLabelValues),
absl::nullopt);
}
using MetricsDeathTest = MetricsTest;
TEST_F(MetricsDeathTest, RegisterTheSameMetricNameWouldCrash) {
const absl::string_view kLabelKeys[] = {"label_key_1", "label_key_2"};
const absl::string_view kOptionalLabelKeys[] = {"optional_label_key_1",
"optional_label_key_2"};
(void)GlobalInstrumentsRegistry::RegisterDoubleHistogram(
"double_histogram", "A simple double histogram.", "unit", kLabelKeys,
kOptionalLabelKeys, true);
EXPECT_DEATH(GlobalInstrumentsRegistry::RegisterDoubleHistogram(
"double_histogram", "A simple double histogram.", "unit",
kLabelKeys, kOptionalLabelKeys, true),
"Metric name double_histogram has already been registered.");
}
} // namespace
} // namespace grpc_core
int main(int argc, char** argv) {
grpc::testing::TestEnvironment env(&argc, argv);
::testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
return ret;
}