diff --git a/CMakeLists.txt b/CMakeLists.txt
index d37c313418e..cf8697a597f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2278,6 +2278,7 @@ add_library(grpc
src/core/lib/gprpp/status_helper.cc
src/core/lib/gprpp/time.cc
src/core/lib/gprpp/time_averaged_stats.cc
+ src/core/lib/gprpp/uuid_v4.cc
src/core/lib/gprpp/validation_errors.cc
src/core/lib/gprpp/windows/directory_reader.cc
src/core/lib/gprpp/work_serializer.cc
@@ -3046,6 +3047,7 @@ add_library(grpc_unsecure
src/core/lib/gprpp/status_helper.cc
src/core/lib/gprpp/time.cc
src/core/lib/gprpp/time_averaged_stats.cc
+ src/core/lib/gprpp/uuid_v4.cc
src/core/lib/gprpp/validation_errors.cc
src/core/lib/gprpp/work_serializer.cc
src/core/lib/handshaker/proxy_mapper_registry.cc
@@ -25839,6 +25841,7 @@ add_executable(rls_end2end_test
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/orca_load_report.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/orca_load_report.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/orca_load_report.grpc.pb.h
+ test/core/util/fake_stats_plugin.cc
test/core/util/test_lb_policies.cc
test/cpp/end2end/rls_end2end_test.cc
test/cpp/end2end/rls_server.cc
@@ -31537,7 +31540,6 @@ endif()
if(gRPC_BUILD_TESTS)
add_executable(uuid_v4_test
- src/core/lib/gprpp/uuid_v4.cc
test/core/gprpp/uuid_v4_test.cc
)
if(WIN32 AND MSVC)
diff --git a/Makefile b/Makefile
index 77c838096dd..2fd360f7202 100644
--- a/Makefile
+++ b/Makefile
@@ -1459,6 +1459,7 @@ LIBGRPC_SRC = \
src/core/lib/gprpp/status_helper.cc \
src/core/lib/gprpp/time.cc \
src/core/lib/gprpp/time_averaged_stats.cc \
+ src/core/lib/gprpp/uuid_v4.cc \
src/core/lib/gprpp/validation_errors.cc \
src/core/lib/gprpp/windows/directory_reader.cc \
src/core/lib/gprpp/work_serializer.cc \
@@ -2060,6 +2061,7 @@ LIBGRPC_UNSECURE_SRC = \
src/core/lib/gprpp/status_helper.cc \
src/core/lib/gprpp/time.cc \
src/core/lib/gprpp/time_averaged_stats.cc \
+ src/core/lib/gprpp/uuid_v4.cc \
src/core/lib/gprpp/validation_errors.cc \
src/core/lib/gprpp/work_serializer.cc \
src/core/lib/handshaker/proxy_mapper_registry.cc \
diff --git a/Package.swift b/Package.swift
index 6f480c481de..64d6d866bf7 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1393,6 +1393,8 @@ let package = Package(
"src/core/lib/gprpp/time_util.h",
"src/core/lib/gprpp/type_list.h",
"src/core/lib/gprpp/unique_type_name.h",
+ "src/core/lib/gprpp/uuid_v4.cc",
+ "src/core/lib/gprpp/uuid_v4.h",
"src/core/lib/gprpp/validation_errors.cc",
"src/core/lib/gprpp/validation_errors.h",
"src/core/lib/gprpp/windows/directory_reader.cc",
@@ -1888,6 +1890,7 @@ let package = Package(
"src/core/load_balancing/ring_hash/ring_hash.cc",
"src/core/load_balancing/ring_hash/ring_hash.h",
"src/core/load_balancing/rls/rls.cc",
+ "src/core/load_balancing/rls/rls.h",
"src/core/load_balancing/round_robin/round_robin.cc",
"src/core/load_balancing/subchannel_interface.h",
"src/core/load_balancing/subchannel_list.h",
diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml
index 073ea701ec2..daafa1d41bd 100644
--- a/build_autogenerated.yaml
+++ b/build_autogenerated.yaml
@@ -926,6 +926,7 @@ libs:
- src/core/lib/gprpp/time_averaged_stats.h
- src/core/lib/gprpp/type_list.h
- src/core/lib/gprpp/unique_type_name.h
+ - src/core/lib/gprpp/uuid_v4.h
- src/core/lib/gprpp/validation_errors.h
- src/core/lib/gprpp/work_serializer.h
- src/core/lib/gprpp/xxhash_inline.h
@@ -1178,6 +1179,7 @@ libs:
- src/core/load_balancing/outlier_detection/outlier_detection.h
- src/core/load_balancing/pick_first/pick_first.h
- src/core/load_balancing/ring_hash/ring_hash.h
+ - src/core/load_balancing/rls/rls.h
- src/core/load_balancing/subchannel_interface.h
- src/core/load_balancing/subchannel_list.h
- src/core/load_balancing/weighted_round_robin/static_stride_scheduler.h
@@ -1738,6 +1740,7 @@ libs:
- src/core/lib/gprpp/status_helper.cc
- src/core/lib/gprpp/time.cc
- src/core/lib/gprpp/time_averaged_stats.cc
+ - src/core/lib/gprpp/uuid_v4.cc
- src/core/lib/gprpp/validation_errors.cc
- src/core/lib/gprpp/windows/directory_reader.cc
- src/core/lib/gprpp/work_serializer.cc
@@ -2428,6 +2431,7 @@ libs:
- src/core/lib/gprpp/time_averaged_stats.h
- src/core/lib/gprpp/type_list.h
- src/core/lib/gprpp/unique_type_name.h
+ - src/core/lib/gprpp/uuid_v4.h
- src/core/lib/gprpp/validation_errors.h
- src/core/lib/gprpp/work_serializer.h
- src/core/lib/handshaker/proxy_mapper.h
@@ -2644,6 +2648,7 @@ libs:
- src/core/load_balancing/oob_backend_metric_internal.h
- src/core/load_balancing/outlier_detection/outlier_detection.h
- src/core/load_balancing/pick_first/pick_first.h
+ - src/core/load_balancing/rls/rls.h
- src/core/load_balancing/subchannel_interface.h
- src/core/load_balancing/subchannel_list.h
- src/core/load_balancing/weighted_round_robin/static_stride_scheduler.h
@@ -2863,6 +2868,7 @@ libs:
- src/core/lib/gprpp/status_helper.cc
- src/core/lib/gprpp/time.cc
- src/core/lib/gprpp/time_averaged_stats.cc
+ - src/core/lib/gprpp/uuid_v4.cc
- src/core/lib/gprpp/validation_errors.cc
- src/core/lib/gprpp/work_serializer.cc
- src/core/lib/handshaker/proxy_mapper_registry.cc
@@ -16869,6 +16875,7 @@ targets:
run: false
language: c++
headers:
+ - test/core/util/fake_stats_plugin.h
- test/core/util/test_lb_policies.h
- test/cpp/end2end/counted_service.h
- test/cpp/end2end/rls_server.h
@@ -16880,6 +16887,7 @@ targets:
- src/proto/grpc/testing/echo_messages.proto
- src/proto/grpc/testing/simple_messages.proto
- src/proto/grpc/testing/xds/v3/orca_load_report.proto
+ - test/core/util/fake_stats_plugin.cc
- test/core/util/test_lb_policies.cc
- test/cpp/end2end/rls_end2end_test.cc
- test/cpp/end2end/rls_server.cc
@@ -19503,10 +19511,8 @@ targets:
gtest: true
build: test
language: c++
- headers:
- - src/core/lib/gprpp/uuid_v4.h
+ headers: []
src:
- - src/core/lib/gprpp/uuid_v4.cc
- test/core/gprpp/uuid_v4_test.cc
deps:
- gtest
diff --git a/config.m4 b/config.m4
index 950fdbb4632..4600f743302 100644
--- a/config.m4
+++ b/config.m4
@@ -584,6 +584,7 @@ if test "$PHP_GRPC" != "no"; then
src/core/lib/gprpp/time.cc \
src/core/lib/gprpp/time_averaged_stats.cc \
src/core/lib/gprpp/time_util.cc \
+ src/core/lib/gprpp/uuid_v4.cc \
src/core/lib/gprpp/validation_errors.cc \
src/core/lib/gprpp/windows/directory_reader.cc \
src/core/lib/gprpp/windows/env.cc \
diff --git a/config.w32 b/config.w32
index 59efe403184..dcf40dbb7d3 100644
--- a/config.w32
+++ b/config.w32
@@ -549,6 +549,7 @@ if (PHP_GRPC != "no") {
"src\\core\\lib\\gprpp\\time.cc " +
"src\\core\\lib\\gprpp\\time_averaged_stats.cc " +
"src\\core\\lib\\gprpp\\time_util.cc " +
+ "src\\core\\lib\\gprpp\\uuid_v4.cc " +
"src\\core\\lib\\gprpp\\validation_errors.cc " +
"src\\core\\lib\\gprpp\\windows\\directory_reader.cc " +
"src\\core\\lib\\gprpp\\windows\\env.cc " +
diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec
index a03916faa25..84a7bfef691 100644
--- a/gRPC-C++.podspec
+++ b/gRPC-C++.podspec
@@ -1032,6 +1032,7 @@ Pod::Spec.new do |s|
'src/core/lib/gprpp/time_util.h',
'src/core/lib/gprpp/type_list.h',
'src/core/lib/gprpp/unique_type_name.h',
+ 'src/core/lib/gprpp/uuid_v4.h',
'src/core/lib/gprpp/validation_errors.h',
'src/core/lib/gprpp/work_serializer.h',
'src/core/lib/gprpp/xxhash_inline.h',
@@ -1284,6 +1285,7 @@ Pod::Spec.new do |s|
'src/core/load_balancing/outlier_detection/outlier_detection.h',
'src/core/load_balancing/pick_first/pick_first.h',
'src/core/load_balancing/ring_hash/ring_hash.h',
+ 'src/core/load_balancing/rls/rls.h',
'src/core/load_balancing/subchannel_interface.h',
'src/core/load_balancing/subchannel_list.h',
'src/core/load_balancing/weighted_round_robin/static_stride_scheduler.h',
@@ -2293,6 +2295,7 @@ Pod::Spec.new do |s|
'src/core/lib/gprpp/time_util.h',
'src/core/lib/gprpp/type_list.h',
'src/core/lib/gprpp/unique_type_name.h',
+ 'src/core/lib/gprpp/uuid_v4.h',
'src/core/lib/gprpp/validation_errors.h',
'src/core/lib/gprpp/work_serializer.h',
'src/core/lib/gprpp/xxhash_inline.h',
@@ -2545,6 +2548,7 @@ Pod::Spec.new do |s|
'src/core/load_balancing/outlier_detection/outlier_detection.h',
'src/core/load_balancing/pick_first/pick_first.h',
'src/core/load_balancing/ring_hash/ring_hash.h',
+ 'src/core/load_balancing/rls/rls.h',
'src/core/load_balancing/subchannel_interface.h',
'src/core/load_balancing/subchannel_list.h',
'src/core/load_balancing/weighted_round_robin/static_stride_scheduler.h',
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index d301e36c83b..35adc20a350 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -1507,6 +1507,8 @@ Pod::Spec.new do |s|
'src/core/lib/gprpp/time_util.h',
'src/core/lib/gprpp/type_list.h',
'src/core/lib/gprpp/unique_type_name.h',
+ 'src/core/lib/gprpp/uuid_v4.cc',
+ 'src/core/lib/gprpp/uuid_v4.h',
'src/core/lib/gprpp/validation_errors.cc',
'src/core/lib/gprpp/validation_errors.h',
'src/core/lib/gprpp/windows/directory_reader.cc',
@@ -1998,6 +2000,7 @@ Pod::Spec.new do |s|
'src/core/load_balancing/ring_hash/ring_hash.cc',
'src/core/load_balancing/ring_hash/ring_hash.h',
'src/core/load_balancing/rls/rls.cc',
+ 'src/core/load_balancing/rls/rls.h',
'src/core/load_balancing/round_robin/round_robin.cc',
'src/core/load_balancing/subchannel_interface.h',
'src/core/load_balancing/subchannel_list.h',
@@ -3073,6 +3076,7 @@ Pod::Spec.new do |s|
'src/core/lib/gprpp/time_util.h',
'src/core/lib/gprpp/type_list.h',
'src/core/lib/gprpp/unique_type_name.h',
+ 'src/core/lib/gprpp/uuid_v4.h',
'src/core/lib/gprpp/validation_errors.h',
'src/core/lib/gprpp/work_serializer.h',
'src/core/lib/gprpp/xxhash_inline.h',
@@ -3325,6 +3329,7 @@ Pod::Spec.new do |s|
'src/core/load_balancing/outlier_detection/outlier_detection.h',
'src/core/load_balancing/pick_first/pick_first.h',
'src/core/load_balancing/ring_hash/ring_hash.h',
+ 'src/core/load_balancing/rls/rls.h',
'src/core/load_balancing/subchannel_interface.h',
'src/core/load_balancing/subchannel_list.h',
'src/core/load_balancing/weighted_round_robin/static_stride_scheduler.h',
diff --git a/grpc.gemspec b/grpc.gemspec
index f31e053b11d..eaed281aa80 100644
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -1399,6 +1399,8 @@ Gem::Specification.new do |s|
s.files += %w( src/core/lib/gprpp/time_util.h )
s.files += %w( src/core/lib/gprpp/type_list.h )
s.files += %w( src/core/lib/gprpp/unique_type_name.h )
+ s.files += %w( src/core/lib/gprpp/uuid_v4.cc )
+ s.files += %w( src/core/lib/gprpp/uuid_v4.h )
s.files += %w( src/core/lib/gprpp/validation_errors.cc )
s.files += %w( src/core/lib/gprpp/validation_errors.h )
s.files += %w( src/core/lib/gprpp/windows/directory_reader.cc )
@@ -1890,6 +1892,7 @@ Gem::Specification.new do |s|
s.files += %w( src/core/load_balancing/ring_hash/ring_hash.cc )
s.files += %w( src/core/load_balancing/ring_hash/ring_hash.h )
s.files += %w( src/core/load_balancing/rls/rls.cc )
+ s.files += %w( src/core/load_balancing/rls/rls.h )
s.files += %w( src/core/load_balancing/round_robin/round_robin.cc )
s.files += %w( src/core/load_balancing/subchannel_interface.h )
s.files += %w( src/core/load_balancing/subchannel_list.h )
diff --git a/grpc.gyp b/grpc.gyp
index fcffddc3163..bb844663ee0 100644
--- a/grpc.gyp
+++ b/grpc.gyp
@@ -774,6 +774,7 @@
'src/core/lib/gprpp/status_helper.cc',
'src/core/lib/gprpp/time.cc',
'src/core/lib/gprpp/time_averaged_stats.cc',
+ 'src/core/lib/gprpp/uuid_v4.cc',
'src/core/lib/gprpp/validation_errors.cc',
'src/core/lib/gprpp/windows/directory_reader.cc',
'src/core/lib/gprpp/work_serializer.cc',
@@ -1316,6 +1317,7 @@
'src/core/lib/gprpp/status_helper.cc',
'src/core/lib/gprpp/time.cc',
'src/core/lib/gprpp/time_averaged_stats.cc',
+ 'src/core/lib/gprpp/uuid_v4.cc',
'src/core/lib/gprpp/validation_errors.cc',
'src/core/lib/gprpp/work_serializer.cc',
'src/core/lib/handshaker/proxy_mapper_registry.cc',
diff --git a/package.xml b/package.xml
index 42ca1197a08..fa35a2c4033 100644
--- a/package.xml
+++ b/package.xml
@@ -1381,6 +1381,8 @@
+
+
@@ -1872,6 +1874,7 @@
+
diff --git a/src/core/BUILD b/src/core/BUILD
index 185aa98a554..3f80297081c 100644
--- a/src/core/BUILD
+++ b/src/core/BUILD
@@ -4802,9 +4802,13 @@ grpc_cc_library(
srcs = [
"load_balancing/rls/rls.cc",
],
+ hdrs = [
+ "load_balancing/rls/rls.h",
+ ],
external_deps = [
"absl/base:core_headers",
"absl/hash",
+ "absl/random",
"absl/status",
"absl/status:statusor",
"absl/strings",
@@ -4830,11 +4834,14 @@ grpc_cc_library(
"lb_policy",
"lb_policy_factory",
"lb_policy_registry",
+ "match",
+ "metrics",
"pollset_set",
"slice",
"slice_refcount",
"status_helper",
"time",
+ "uuid_v4",
"validation_errors",
"//:backoff",
"//:channel",
@@ -7560,6 +7567,7 @@ grpc_cc_library(
],
external_deps = [
"absl/container:flat_hash_map",
+ "absl/functional:any_invocable",
"absl/functional:function_ref",
"absl/strings",
"absl/types:span",
@@ -7568,6 +7576,7 @@ grpc_cc_library(
deps = [
"channel_args",
"no_destruct",
+ "time",
"//:gpr",
],
)
diff --git a/src/core/lib/channel/metrics.cc b/src/core/lib/channel/metrics.cc
index 36e17d2afc9..5a038db187b 100644
--- a/src/core/lib/channel/metrics.cc
+++ b/src/core/lib/channel/metrics.cc
@@ -149,6 +149,122 @@ GlobalInstrumentsRegistry::RegisterDoubleHistogram(
return handle;
}
+GlobalInstrumentsRegistry::GlobalInt64GaugeHandle
+GlobalInstrumentsRegistry::RegisterInt64Gauge(
+ absl::string_view name, absl::string_view description,
+ absl::string_view unit, absl::Span label_keys,
+ absl::Span optional_label_keys,
+ bool enable_by_default) {
+ auto& instruments = GetInstrumentList();
+ if (instruments.find(name) != instruments.end()) {
+ Crash(absl::StrFormat("Metric name %s has already been registered.", name));
+ }
+ uint32_t index = instruments.size();
+ GPR_ASSERT(index < std::numeric_limits::max());
+ GlobalInstrumentDescriptor descriptor;
+ descriptor.value_type = ValueType::kInt64;
+ descriptor.instrument_type = InstrumentType::kGauge;
+ descriptor.index = index;
+ descriptor.enable_by_default = enable_by_default;
+ descriptor.name = name;
+ descriptor.description = description;
+ descriptor.unit = unit;
+ descriptor.label_keys = {label_keys.begin(), label_keys.end()};
+ descriptor.optional_label_keys = {optional_label_keys.begin(),
+ optional_label_keys.end()};
+ instruments.emplace(name, std::move(descriptor));
+ GlobalInt64GaugeHandle handle;
+ handle.index = index;
+ return handle;
+}
+
+GlobalInstrumentsRegistry::GlobalDoubleGaugeHandle
+GlobalInstrumentsRegistry::RegisterDoubleGauge(
+ absl::string_view name, absl::string_view description,
+ absl::string_view unit, absl::Span label_keys,
+ absl::Span optional_label_keys,
+ bool enable_by_default) {
+ auto& instruments = GetInstrumentList();
+ if (instruments.find(name) != instruments.end()) {
+ Crash(absl::StrFormat("Metric name %s has already been registered.", name));
+ }
+ uint32_t index = instruments.size();
+ GPR_ASSERT(index < std::numeric_limits::max());
+ GlobalInstrumentDescriptor descriptor;
+ descriptor.value_type = ValueType::kDouble;
+ descriptor.instrument_type = InstrumentType::kGauge;
+ descriptor.index = index;
+ descriptor.enable_by_default = enable_by_default;
+ descriptor.name = name;
+ descriptor.description = description;
+ descriptor.unit = unit;
+ descriptor.label_keys = {label_keys.begin(), label_keys.end()};
+ descriptor.optional_label_keys = {optional_label_keys.begin(),
+ optional_label_keys.end()};
+ instruments.emplace(name, std::move(descriptor));
+ GlobalDoubleGaugeHandle handle;
+ handle.index = index;
+ return handle;
+}
+
+GlobalInstrumentsRegistry::GlobalCallbackInt64GaugeHandle
+GlobalInstrumentsRegistry::RegisterCallbackInt64Gauge(
+ absl::string_view name, absl::string_view description,
+ absl::string_view unit, absl::Span label_keys,
+ absl::Span optional_label_keys,
+ bool enable_by_default) {
+ auto& instruments = GetInstrumentList();
+ if (instruments.find(name) != instruments.end()) {
+ Crash(absl::StrFormat("Metric name %s has already been registered.", name));
+ }
+ uint32_t index = instruments.size();
+ GPR_ASSERT(index < std::numeric_limits::max());
+ GlobalInstrumentDescriptor descriptor;
+ descriptor.value_type = ValueType::kInt64;
+ descriptor.instrument_type = InstrumentType::kCallbackGauge;
+ descriptor.index = index;
+ descriptor.enable_by_default = enable_by_default;
+ descriptor.name = name;
+ descriptor.description = description;
+ descriptor.unit = unit;
+ descriptor.label_keys = {label_keys.begin(), label_keys.end()};
+ descriptor.optional_label_keys = {optional_label_keys.begin(),
+ optional_label_keys.end()};
+ instruments.emplace(name, std::move(descriptor));
+ GlobalCallbackInt64GaugeHandle handle;
+ handle.index = index;
+ return handle;
+}
+
+GlobalInstrumentsRegistry::GlobalCallbackDoubleGaugeHandle
+GlobalInstrumentsRegistry::RegisterCallbackDoubleGauge(
+ absl::string_view name, absl::string_view description,
+ absl::string_view unit, absl::Span label_keys,
+ absl::Span optional_label_keys,
+ bool enable_by_default) {
+ auto& instruments = GetInstrumentList();
+ if (instruments.find(name) != instruments.end()) {
+ Crash(absl::StrFormat("Metric name %s has already been registered.", name));
+ }
+ uint32_t index = instruments.size();
+ GPR_ASSERT(index < std::numeric_limits::max());
+ GlobalInstrumentDescriptor descriptor;
+ descriptor.value_type = ValueType::kDouble;
+ descriptor.instrument_type = InstrumentType::kCallbackGauge;
+ descriptor.index = index;
+ descriptor.enable_by_default = enable_by_default;
+ descriptor.name = name;
+ descriptor.description = description;
+ descriptor.unit = unit;
+ descriptor.label_keys = {label_keys.begin(), label_keys.end()};
+ descriptor.optional_label_keys = {optional_label_keys.begin(),
+ optional_label_keys.end()};
+ instruments.emplace(name, std::move(descriptor));
+ GlobalCallbackDoubleGaugeHandle handle;
+ handle.index = index;
+ return handle;
+}
+
void GlobalInstrumentsRegistry::ForEach(
absl::FunctionRef f) {
for (const auto& instrument : GetInstrumentList()) {
@@ -156,6 +272,35 @@ void GlobalInstrumentsRegistry::ForEach(
}
}
+RegisteredMetricCallback::RegisteredMetricCallback(
+ GlobalStatsPluginRegistry::StatsPluginGroup& stats_plugin_group,
+ absl::AnyInvocable callback,
+ std::vector metrics,
+ Duration min_interval)
+ : stats_plugin_group_(stats_plugin_group),
+ callback_(std::move(callback)),
+ metrics_(std::move(metrics)),
+ min_interval_(min_interval) {
+ for (auto& plugin : stats_plugin_group_.plugins_) {
+ plugin->AddCallback(this);
+ }
+}
+
+RegisteredMetricCallback::~RegisteredMetricCallback() {
+ for (auto& plugin : stats_plugin_group_.plugins_) {
+ plugin->RemoveCallback(this);
+ }
+}
+
+std::unique_ptr
+GlobalStatsPluginRegistry::StatsPluginGroup::RegisterCallback(
+ absl::AnyInvocable callback,
+ std::vector metrics,
+ Duration min_interval) {
+ return std::make_unique(
+ *this, std::move(callback), std::move(metrics), min_interval);
+}
+
NoDestruct GlobalStatsPluginRegistry::mutex_;
NoDestruct>>
GlobalStatsPluginRegistry::plugins_;
diff --git a/src/core/lib/channel/metrics.h b/src/core/lib/channel/metrics.h
index 51803e7c4fa..a93f0037cb3 100644
--- a/src/core/lib/channel/metrics.h
+++ b/src/core/lib/channel/metrics.h
@@ -22,6 +22,7 @@
#include
#include "absl/container/flat_hash_map.h"
+#include "absl/functional/any_invocable.h"
#include "absl/functional/function_ref.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
@@ -31,20 +32,22 @@
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/gprpp/no_destruct.h"
#include "src/core/lib/gprpp/sync.h"
+#include "src/core/lib/gprpp/time.h"
namespace grpc_core {
constexpr absl::string_view kMetricLabelTarget = "grpc.target";
-// A global registry of instruments(metrics). This API is designed to be used to
-// register instruments (Counter and Histogram) as part of program startup,
-// before the execution of the main function (during dynamic initialization
-// time). Using this API after the main function begins may result into missing
-// instruments. This API is thread-unsafe.
+// A global registry of instruments(metrics). This API is designed to be used
+// to register instruments (Counter, Histogram, and Gauge) as part of program
+// startup, before the execution of the main function (during dynamic
+// initialization time). Using this API after the main function begins may
+// result into missing instruments. This API is thread-unsafe.
class GlobalInstrumentsRegistry {
public:
enum class ValueType {
kUndefined,
+ kInt64,
kUInt64,
kDouble,
};
@@ -52,6 +55,8 @@ class GlobalInstrumentsRegistry {
kUndefined,
kCounter,
kHistogram,
+ kGauge,
+ kCallbackGauge,
};
struct GlobalInstrumentDescriptor {
ValueType value_type;
@@ -75,6 +80,11 @@ class GlobalInstrumentsRegistry {
struct GlobalDoubleCounterHandle : public GlobalHandle {};
struct GlobalUInt64HistogramHandle : public GlobalHandle {};
struct GlobalDoubleHistogramHandle : public GlobalHandle {};
+ struct GlobalInt64GaugeHandle : public GlobalHandle {};
+ struct GlobalDoubleGaugeHandle : public GlobalHandle {};
+ struct GlobalCallbackHandle : public GlobalHandle {};
+ struct GlobalCallbackInt64GaugeHandle : public GlobalCallbackHandle {};
+ struct GlobalCallbackDoubleGaugeHandle : public GlobalCallbackHandle {};
// Creates instrument in the GlobalInstrumentsRegistry.
static GlobalUInt64CounterHandle RegisterUInt64Counter(
@@ -97,6 +107,27 @@ class GlobalInstrumentsRegistry {
absl::string_view unit, absl::Span label_keys,
absl::Span optional_label_keys,
bool enable_by_default);
+ static GlobalInt64GaugeHandle RegisterInt64Gauge(
+ absl::string_view name, absl::string_view description,
+ absl::string_view unit, absl::Span label_keys,
+ absl::Span optional_label_keys,
+ bool enable_by_default);
+ static GlobalDoubleGaugeHandle RegisterDoubleGauge(
+ absl::string_view name, absl::string_view description,
+ absl::string_view unit, absl::Span label_keys,
+ absl::Span optional_label_keys,
+ bool enable_by_default);
+ static GlobalCallbackInt64GaugeHandle RegisterCallbackInt64Gauge(
+ absl::string_view name, absl::string_view description,
+ absl::string_view unit, absl::Span label_keys,
+ absl::Span optional_label_keys,
+ bool enable_by_default);
+ static GlobalCallbackDoubleGaugeHandle RegisterCallbackDoubleGauge(
+ absl::string_view name, absl::string_view description,
+ absl::string_view unit, absl::Span label_keys,
+ absl::Span optional_label_keys,
+ bool enable_by_default);
+
static void ForEach(
absl::FunctionRef f);
@@ -110,6 +141,24 @@ class GlobalInstrumentsRegistry {
GetInstrumentList();
};
+// An interface for implementing callback-style metrics.
+// To be implemented by stats plugins.
+class CallbackMetricReporter {
+ public:
+ virtual ~CallbackMetricReporter() = default;
+
+ virtual void Report(
+ GlobalInstrumentsRegistry::GlobalCallbackInt64GaugeHandle handle,
+ int64_t value, absl::Span label_values,
+ absl::Span optional_values) = 0;
+ virtual void Report(
+ GlobalInstrumentsRegistry::GlobalCallbackDoubleGaugeHandle handle,
+ double value, absl::Span label_values,
+ absl::Span optional_values) = 0;
+};
+
+class RegisteredMetricCallback;
+
// The StatsPlugin interface.
class StatsPlugin {
public:
@@ -147,6 +196,22 @@ class StatsPlugin {
GlobalInstrumentsRegistry::GlobalDoubleHistogramHandle handle,
double value, absl::Span label_values,
absl::Span optional_values) = 0;
+ virtual void SetGauge(
+ GlobalInstrumentsRegistry::GlobalInt64GaugeHandle handle, int64_t value,
+ absl::Span label_values,
+ absl::Span optional_values) = 0;
+ virtual void SetGauge(
+ GlobalInstrumentsRegistry::GlobalDoubleGaugeHandle handle, double value,
+ absl::Span label_values,
+ absl::Span optional_values) = 0;
+
+ // Adds a callback to be invoked when the stats plugin wants to
+ // populate the corresponding metrics (see callback->metrics() for list).
+ virtual void AddCallback(RegisteredMetricCallback* callback) = 0;
+ // Removes a callback previously added via AddCallback(). The stats
+ // plugin may not use the callback after this method returns.
+ virtual void RemoveCallback(RegisteredMetricCallback* callback) = 0;
+
// TODO(yijiem): Details pending.
// std::unique_ptr GetObservableGauge(
// absl::string_view name, absl::string_view description,
@@ -208,8 +273,39 @@ class GlobalStatsPluginRegistry {
plugin->RecordHistogram(handle, value, label_values, optional_values);
}
}
+ void SetGauge(GlobalInstrumentsRegistry::GlobalInt64GaugeHandle handle,
+ int64_t value,
+ absl::Span label_values,
+ absl::Span optional_values) {
+ for (auto& plugin : plugins_) {
+ plugin->SetGauge(handle, value, label_values, optional_values);
+ }
+ }
+ void SetGauge(GlobalInstrumentsRegistry::GlobalDoubleGaugeHandle handle,
+ double value,
+ absl::Span label_values,
+ absl::Span optional_values) {
+ for (auto& plugin : plugins_) {
+ plugin->SetGauge(handle, value, label_values, optional_values);
+ }
+ }
+
+ // Registers a callback to be used to populate callback metrics.
+ // The callback will update the specified metrics. The callback
+ // will be invoked no more often than min_interval.
+ //
+ // The returned object is a handle that allows the caller to control
+ // the lifetime of the callback; when the returned object is
+ // destroyed, the callback is de-registered. The returned object
+ // must not outlive the StatsPluginGroup object that created it.
+ std::unique_ptr RegisterCallback(
+ absl::AnyInvocable callback,
+ std::vector metrics,
+ Duration min_interval = Duration::Seconds(5));
private:
+ friend class RegisteredMetricCallback;
+
std::vector> plugins_;
};
@@ -231,6 +327,37 @@ class GlobalStatsPluginRegistry {
ABSL_GUARDED_BY(mutex_);
};
+// A metric callback that is registered with a stats plugin group.
+class RegisteredMetricCallback {
+ public:
+ RegisteredMetricCallback(
+ GlobalStatsPluginRegistry::StatsPluginGroup& stats_plugin_group,
+ absl::AnyInvocable callback,
+ std::vector metrics,
+ Duration min_interval);
+
+ ~RegisteredMetricCallback();
+
+ // Invokes the callback. The callback will report metric data via reporter.
+ void Run(CallbackMetricReporter& reporter) { callback_(reporter); }
+
+ // Returns the set of metrics that this callback will modify.
+ const std::vector& metrics()
+ const {
+ return metrics_;
+ }
+
+ // Returns the minimum interval at which a stats plugin may invoke the
+ // callback.
+ Duration min_interval() const { return min_interval_; }
+
+ private:
+ GlobalStatsPluginRegistry::StatsPluginGroup& stats_plugin_group_;
+ absl::AnyInvocable callback_;
+ std::vector metrics_;
+ Duration min_interval_;
+};
+
} // namespace grpc_core
#endif // GRPC_SRC_CORE_LIB_CHANNEL_METRICS_H
diff --git a/src/core/load_balancing/rls/rls.cc b/src/core/load_balancing/rls/rls.cc
index 4a90a17cfe7..70a9a5b59dd 100644
--- a/src/core/load_balancing/rls/rls.cc
+++ b/src/core/load_balancing/rls/rls.cc
@@ -22,6 +22,8 @@
#include
+#include "src/core/load_balancing/rls/rls.h"
+
#include
#include
#include
@@ -41,6 +43,7 @@
#include "absl/base/thread_annotations.h"
#include "absl/hash/hash.h"
+#include "absl/random/random.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
@@ -68,15 +71,18 @@
#include "src/core/lib/backoff/backoff.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/channel/channelz.h"
+#include "src/core/lib/channel/metrics.h"
#include "src/core/lib/config/core_configuration.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/gprpp/debug_location.h"
#include "src/core/lib/gprpp/dual_ref_counted.h"
+#include "src/core/lib/gprpp/match.h"
#include "src/core/lib/gprpp/orphanable.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/gprpp/status_helper.h"
#include "src/core/lib/gprpp/sync.h"
#include "src/core/lib/gprpp/time.h"
+#include "src/core/lib/gprpp/uuid_v4.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/lib/gprpp/work_serializer.h"
#include "src/core/lib/iomgr/closure.h"
@@ -103,13 +109,66 @@
#include "src/core/resolver/resolver_registry.h"
#include "src/proto/grpc/lookup/v1/rls.upb.h"
+using ::grpc_event_engine::experimental::EventEngine;
+
namespace grpc_core {
TraceFlag grpc_lb_rls_trace(false, "rls_lb");
namespace {
-using ::grpc_event_engine::experimental::EventEngine;
+constexpr absl::string_view kMetricLabelRlsServerTarget =
+ "grpc.lb.rls.server_target";
+constexpr absl::string_view kMetricLabelRlsInstanceUuid =
+ "grpc.lb.rls.instance_uuid";
+constexpr absl::string_view kMetricRlsDataPlaneTarget =
+ "grpc.lb.rls.data_plane_target";
+constexpr absl::string_view kMetricLabelPickResult = "grpc.lb.pick_result";
+
+const auto kMetricCacheSize =
+ GlobalInstrumentsRegistry::RegisterCallbackInt64Gauge(
+ "grpc.lb.rls.cache_size", "EXPERIMENTAL. Size of the RLS cache.",
+ "By",
+ {kMetricLabelTarget, kMetricLabelRlsServerTarget,
+ kMetricLabelRlsInstanceUuid},
+ {}, false);
+
+const auto kMetricCacheEntries =
+ GlobalInstrumentsRegistry::RegisterCallbackInt64Gauge(
+ "grpc.lb.rls.cache_entries",
+ "EXPERIMENTAL. Number of entries in the RLS cache.", "{entry}",
+ {kMetricLabelTarget, kMetricLabelRlsServerTarget,
+ kMetricLabelRlsInstanceUuid},
+ {}, false);
+
+const auto kMetricDefaultTargetPicks =
+ GlobalInstrumentsRegistry::RegisterUInt64Counter(
+ "grpc.lb.rls.default_target_picks",
+ "EXPERIMENTAL. Number of LB picks sent to the default target.",
+ "{pick}",
+ {kMetricLabelTarget, kMetricLabelRlsServerTarget,
+ kMetricRlsDataPlaneTarget, kMetricLabelPickResult},
+ {}, false);
+
+const auto kMetricTargetPicks =
+ GlobalInstrumentsRegistry::RegisterUInt64Counter(
+ "grpc.lb.rls.target_picks",
+ "EXPERIMENTAL. Number of LB picks sent to each RLS target. Note that "
+ "if the default target is also returned by the RLS server, RPCs sent "
+ "to that target from the cache will be counted in this metric, not "
+ "in grpc.rls.default_target_picks.",
+ "{pick}",
+ {kMetricLabelTarget, kMetricLabelRlsServerTarget,
+ kMetricRlsDataPlaneTarget, kMetricLabelPickResult},
+ {}, false);
+
+const auto kMetricFailedPicks =
+ GlobalInstrumentsRegistry::RegisterUInt64Counter(
+ "grpc.lb.rls.failed_picks",
+ "EXPERIMENTAL. Number of LB picks failed due to either a failed RLS "
+ "request or the RLS channel being throttled.",
+ "{pick}", {kMetricLabelTarget, kMetricLabelRlsServerTarget}, {},
+ false);
constexpr absl::string_view kRls = "rls_experimental";
const char kGrpc[] = "grpc";
@@ -365,6 +424,10 @@ class RlsLb : public LoadBalancingPolicy {
PickResult Pick(PickArgs args) override;
private:
+ PickResult PickFromDefaultTargetOrFail(const char* reason, PickArgs args,
+ absl::Status status)
+ ABSL_EXCLUSIVE_LOCKS_REQUIRED(&RlsLb::mu_);
+
RefCountedPtr lb_policy_;
RefCountedPtr config_;
RefCountedPtr default_child_policy_;
@@ -516,6 +579,9 @@ class RlsLb : public LoadBalancingPolicy {
// Shutdown the cache; clean-up and orphan all the stored cache entries.
void Shutdown() ABSL_EXCLUSIVE_LOCKS_REQUIRED(&RlsLb::mu_);
+ void ReportMetricsLocked(CallbackMetricReporter& reporter)
+ ABSL_EXCLUSIVE_LOCKS_REQUIRED(&RlsLb::mu_);
+
private:
// Shared logic for starting the cleanup timer
void StartCleanupTimer() ABSL_EXCLUSIVE_LOCKS_REQUIRED(&RlsLb::mu_);
@@ -690,6 +756,12 @@ class RlsLb : public LoadBalancingPolicy {
// Updates the picker in the work serializer.
void UpdatePickerLocked() ABSL_LOCKS_EXCLUDED(&mu_);
+ void MaybeExportPickCount(
+ GlobalInstrumentsRegistry::GlobalUInt64CounterHandle handle,
+ absl::string_view target, const PickResult& pick_result);
+
+ const std::string instance_uuid_;
+
// Mutex to guard LB policy state that is accessed by the picker.
Mutex mu_;
bool is_shutdown_ ABSL_GUARDED_BY(mu_) = false;
@@ -712,6 +784,9 @@ class RlsLb : public LoadBalancingPolicy {
RefCountedPtr config_;
RefCountedPtr default_child_policy_;
std::map child_policy_map_;
+
+ // Must be after mu_, so that it is destroyed before mu_.
+ std::unique_ptr registered_metric_callback_;
};
//
@@ -992,21 +1067,8 @@ LoadBalancingPolicy::PickResult RlsLb::Picker::Pick(PickArgs args) {
// If there is no non-expired data in the cache, then we use the
// default target if set, or else we fail the pick.
if (entry == nullptr || entry->data_expiration_time() < now) {
- if (default_child_policy_ != nullptr) {
- if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_rls_trace)) {
- gpr_log(GPR_INFO,
- "[rlslb %p] picker=%p: RLS call throttled; "
- "using default target",
- lb_policy_.get(), this);
- }
- return default_child_policy_->Pick(args);
- }
- if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_rls_trace)) {
- gpr_log(GPR_INFO,
- "[rlslb %p] picker=%p: RLS call throttled; failing pick",
- lb_policy_.get(), this);
- }
- return PickResult::Fail(
+ return PickFromDefaultTargetOrFail(
+ "RLS call throttled", args,
absl::UnavailableError("RLS request throttled"));
}
}
@@ -1028,22 +1090,10 @@ LoadBalancingPolicy::PickResult RlsLb::Picker::Pick(PickArgs args) {
// If the entry is in backoff, then use the default target if set,
// or else fail the pick.
if (entry->backoff_time() >= now) {
- if (default_child_policy_ != nullptr) {
- if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_rls_trace)) {
- gpr_log(
- GPR_INFO,
- "[rlslb %p] picker=%p: RLS call in backoff; using default target",
- lb_policy_.get(), this);
- }
- return default_child_policy_->Pick(args);
- }
- if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_rls_trace)) {
- gpr_log(GPR_INFO,
- "[rlslb %p] picker=%p: RLS call in backoff; failing pick",
- lb_policy_.get(), this);
- }
- return PickResult::Fail(absl::UnavailableError(
- absl::StrCat("RLS request failed: ", entry->status().ToString())));
+ return PickFromDefaultTargetOrFail(
+ "RLS call in backoff", args,
+ absl::UnavailableError(absl::StrCat(
+ "RLS request failed: ", entry->status().ToString())));
}
}
// RLS call pending. Queue the pick.
@@ -1054,6 +1104,31 @@ LoadBalancingPolicy::PickResult RlsLb::Picker::Pick(PickArgs args) {
return PickResult::Queue();
}
+LoadBalancingPolicy::PickResult RlsLb::Picker::PickFromDefaultTargetOrFail(
+ const char* reason, PickArgs args, absl::Status status) {
+ if (default_child_policy_ != nullptr) {
+ if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_rls_trace)) {
+ gpr_log(GPR_INFO, "[rlslb %p] picker=%p: %s; using default target",
+ lb_policy_.get(), this, reason);
+ }
+ auto pick_result = default_child_policy_->Pick(args);
+ lb_policy_->MaybeExportPickCount(
+ kMetricDefaultTargetPicks, config_->default_target(), pick_result);
+ return pick_result;
+ }
+ if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_rls_trace)) {
+ gpr_log(GPR_INFO, "[rlslb %p] picker=%p: %s; failing pick",
+ lb_policy_.get(), this, reason);
+ }
+ auto& stats_plugins =
+ lb_policy_->channel_control_helper()->GetStatsPluginGroup();
+ stats_plugins.AddCounter(
+ kMetricFailedPicks, 1,
+ {lb_policy_->channel_control_helper()->GetTarget(),
+ config_->lookup_service()}, {});
+ return PickResult::Fail(std::move(status));
+}
+
//
// RlsLb::Cache::Entry::BackoffTimer
//
@@ -1195,7 +1270,10 @@ LoadBalancingPolicy::PickResult RlsLb::Cache::Entry::Pick(PickArgs args) {
strcpy(copied_header_data, header_data_.c_str());
args.initial_metadata->Add(kRlsHeaderKey, copied_header_data);
}
- return child_policy_wrapper->Pick(args);
+ auto pick_result = child_policy_wrapper->Pick(args);
+ lb_policy_->MaybeExportPickCount(
+ kMetricTargetPicks, child_policy_wrapper->target(), pick_result);
+ return pick_result;
}
void RlsLb::Cache::Entry::ResetBackoff() {
@@ -1374,6 +1452,19 @@ void RlsLb::Cache::Shutdown() {
cleanup_timer_handle_.reset();
}
+void RlsLb::Cache::ReportMetricsLocked(CallbackMetricReporter& reporter) {
+ reporter.Report(kMetricCacheSize, size_,
+ {lb_policy_->channel_control_helper()->GetTarget(),
+ lb_policy_->config_->lookup_service(),
+ lb_policy_->instance_uuid_},
+ {});
+ reporter.Report(kMetricCacheEntries, map_.size(),
+ {lb_policy_->channel_control_helper()->GetTarget(),
+ lb_policy_->config_->lookup_service(),
+ lb_policy_->instance_uuid_},
+ {});
+}
+
void RlsLb::Cache::StartCleanupTimer() {
cleanup_timer_handle_ =
lb_policy_->channel_control_helper()->GetEventEngine()->RunAfter(
@@ -1846,7 +1937,27 @@ RlsLb::ResponseInfo RlsLb::RlsRequest::ParseResponseProto() {
// RlsLb
//
-RlsLb::RlsLb(Args args) : LoadBalancingPolicy(std::move(args)), cache_(this) {
+std::string GenerateUUID() {
+ absl::uniform_int_distribution distribution;
+ absl::BitGen bitgen;
+ uint64_t hi = distribution(bitgen);
+ uint64_t lo = distribution(bitgen);
+ return GenerateUUIDv4(hi, lo);
+}
+
+RlsLb::RlsLb(Args args)
+ : LoadBalancingPolicy(std::move(args)),
+ instance_uuid_(
+ channel_args().GetOwnedString(GRPC_ARG_TEST_ONLY_RLS_INSTANCE_ID)
+ .value_or(GenerateUUID())),
+ cache_(this),
+ registered_metric_callback_(
+ channel_control_helper()->GetStatsPluginGroup().RegisterCallback(
+ [this](CallbackMetricReporter& reporter) {
+ MutexLock lock(&mu_);
+ cache_.ReportMetricsLocked(reporter);
+ },
+ {kMetricCacheSize, kMetricCacheEntries})) {
if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_rls_trace)) {
gpr_log(GPR_INFO, "[rlslb %p] policy created", this);
}
@@ -2025,6 +2136,7 @@ void RlsLb::ShutdownLocked() {
if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_rls_trace)) {
gpr_log(GPR_INFO, "[rlslb %p] policy shutdown", this);
}
+ registered_metric_callback_.reset();
MutexLock lock(&mu_);
is_shutdown_ = true;
config_.reset(DEBUG_LOCATION, "ShutdownLocked");
@@ -2114,6 +2226,32 @@ void RlsLb::UpdatePickerLocked() {
MakeRefCounted(RefAsSubclass(DEBUG_LOCATION, "Picker")));
}
+void RlsLb::MaybeExportPickCount(
+ GlobalInstrumentsRegistry::GlobalUInt64CounterHandle handle,
+ absl::string_view target, const PickResult& pick_result) {
+ absl::string_view pick_result_string =
+ Match(pick_result.result,
+ [](const LoadBalancingPolicy::PickResult::Complete&) {
+ return "complete";
+ },
+ [](const LoadBalancingPolicy::PickResult::Queue&) {
+ return "";
+ },
+ [](const LoadBalancingPolicy::PickResult::Fail&) {
+ return "fail";
+ },
+ [](const LoadBalancingPolicy::PickResult::Drop&) {
+ return "drop";
+ });
+ if (pick_result_string.empty()) return; // Don't report queued picks.
+ auto& stats_plugins = channel_control_helper()->GetStatsPluginGroup();
+ stats_plugins.AddCounter(
+ handle, 1,
+ {channel_control_helper()->GetTarget(), config_->lookup_service(),
+ target, pick_result_string},
+ {});
+}
+
//
// RlsLbFactory
//
diff --git a/src/core/load_balancing/rls/rls.h b/src/core/load_balancing/rls/rls.h
new file mode 100644
index 00000000000..2be787b62d7
--- /dev/null
+++ b/src/core/load_balancing/rls/rls.h
@@ -0,0 +1,26 @@
+//
+// Copyright 2024 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.
+//
+
+#ifndef GRPC_SRC_CORE_LOAD_BALANCING_RLS_RLS_H
+#define GRPC_SRC_CORE_LOAD_BALANCING_RLS_RLS_H
+
+#include
+
+// A test-only channel arg to set the instance ID of the RLS LB
+// policy for use in metric labels.
+#define GRPC_ARG_TEST_ONLY_RLS_INSTANCE_ID "grpc.test-only.rls.instance_id"
+
+#endif // GRPC_SRC_CORE_LOAD_BALANCING_RLS_RLS_H
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index 57a24b8d1fb..857b62a8259 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -558,6 +558,7 @@ CORE_SOURCE_FILES = [
'src/core/lib/gprpp/time.cc',
'src/core/lib/gprpp/time_averaged_stats.cc',
'src/core/lib/gprpp/time_util.cc',
+ 'src/core/lib/gprpp/uuid_v4.cc',
'src/core/lib/gprpp/validation_errors.cc',
'src/core/lib/gprpp/windows/directory_reader.cc',
'src/core/lib/gprpp/windows/env.cc',
diff --git a/test/core/channel/metrics_test.cc b/test/core/channel/metrics_test.cc
index e27a8d09350..6cf9d47312c 100644
--- a/test/core/channel/metrics_test.cc
+++ b/test/core/channel/metrics_test.cc
@@ -197,6 +197,492 @@ TEST_F(MetricsTest, DoubleHistogram) {
::testing::Optional(::testing::UnorderedElementsAre(1.23, 2.34, 3.45)));
}
+TEST_F(MetricsTest, Int64Gauge) {
+ 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 int64_gauge_handle = GlobalInstrumentsRegistry::RegisterInt64Gauge(
+ "int64_gauge", "A simple int64 gauge.", "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, ""))
+ .SetGauge(int64_gauge_handle, 1, kLabelValues, kOptionalLabelValues);
+ GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
+ StatsPlugin::ChannelScope(kDomain2To4, ""))
+ .SetGauge(int64_gauge_handle, 2, kLabelValues, kOptionalLabelValues);
+ GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
+ StatsPlugin::ChannelScope(kDomain3To4, ""))
+ .SetGauge(int64_gauge_handle, 3, kLabelValues, kOptionalLabelValues);
+ EXPECT_THAT(plugin1->GetGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(1));
+ EXPECT_THAT(plugin2->GetGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(2));
+ EXPECT_THAT(plugin3->GetGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(3));
+}
+
+TEST_F(MetricsTest, DoubleGauge) {
+ 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_gauge_handle = GlobalInstrumentsRegistry::RegisterDoubleGauge(
+ "double_gauge", "A simple double gauge.", "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, ""))
+ .SetGauge(double_gauge_handle, 1.23, kLabelValues, kOptionalLabelValues);
+ GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
+ StatsPlugin::ChannelScope(kDomain2To4, ""))
+ .SetGauge(double_gauge_handle, 2.34, kLabelValues, kOptionalLabelValues);
+ GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
+ StatsPlugin::ChannelScope(kDomain3To4, ""))
+ .SetGauge(double_gauge_handle, 3.45, kLabelValues, kOptionalLabelValues);
+ EXPECT_THAT(plugin1->GetGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(1.23));
+ EXPECT_THAT(plugin2->GetGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(2.34));
+ EXPECT_THAT(plugin3->GetGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(3.45));
+}
+
+TEST_F(MetricsTest, Int64CallbackGauge) {
+ 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 int64_gauge_handle =
+ GlobalInstrumentsRegistry::RegisterCallbackInt64Gauge(
+ "int64_gauge", "A simple int64 gauge.", "unit", kLabelKeys,
+ kOptionalLabelKeys, true);
+ constexpr absl::string_view kLabelValues[] = {"label_value_1",
+ "label_value_2"};
+ constexpr absl::string_view kLabelValues2[] = {"label_value_3",
+ "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(kDomain3To4);
+ auto plugin2 = MakeStatsPluginForTarget(kDomain2To4);
+ auto plugin3 = MakeStatsPluginForTarget(kDomain1To4);
+ // Register two callbacks that set the same metric but with different
+ // label values. The callbacks get used only by plugin1.
+ gpr_log(GPR_INFO, "testing callbacks for: plugin1");
+ auto group1 = GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
+ StatsPlugin::ChannelScope(kDomain3To4, ""));
+ auto callback1 = group1.RegisterCallback(
+ [&](CallbackMetricReporter& reporter) {
+ reporter.Report(int64_gauge_handle, 1, kLabelValues,
+ kOptionalLabelValues);
+ },
+ {int64_gauge_handle});
+ auto callback2 = group1.RegisterCallback(
+ [&](CallbackMetricReporter& reporter) {
+ reporter.Report(int64_gauge_handle, 2, kLabelValues2,
+ kOptionalLabelValues);
+ },
+ {int64_gauge_handle});
+ // No plugins have data yet.
+ EXPECT_EQ(plugin1->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin1->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin2->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin2->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ // Now invoke the callbacks.
+ plugin1->TriggerCallbacks();
+ plugin2->TriggerCallbacks();
+ plugin3->TriggerCallbacks();
+ // Now plugin1 should have data, but the others should not.
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(1));
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(2));
+ EXPECT_EQ(plugin2->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin2->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ // De-register the callbacks.
+ callback1.reset();
+ callback2.reset();
+ // Now register callbacks that hit both plugin1 and plugin2.
+ gpr_log(GPR_INFO, "testing callbacks for: plugin1, plugin2");
+ auto group2 = GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
+ StatsPlugin::ChannelScope(kDomain2To4, ""));
+ callback1 = group2.RegisterCallback(
+ [&](CallbackMetricReporter& reporter) {
+ reporter.Report(int64_gauge_handle, 3, kLabelValues,
+ kOptionalLabelValues);
+ },
+ {int64_gauge_handle});
+ callback2 = group2.RegisterCallback(
+ [&](CallbackMetricReporter& reporter) {
+ reporter.Report(int64_gauge_handle, 4, kLabelValues2,
+ kOptionalLabelValues);
+ },
+ {int64_gauge_handle});
+ // Plugin1 still has data from before, but the others have none.
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(1));
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(2));
+ EXPECT_EQ(plugin2->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin2->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ // Now invoke the callbacks.
+ plugin1->TriggerCallbacks();
+ plugin2->TriggerCallbacks();
+ plugin3->TriggerCallbacks();
+ // Now plugin1 and plugin2 should have data, but plugin3 should not.
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(3));
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(4));
+ EXPECT_THAT(plugin2->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(3));
+ EXPECT_THAT(plugin2->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(4));
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ // De-register the callbacks.
+ callback1.reset();
+ callback2.reset();
+ // Now register callbacks that hit all three plugins.
+ gpr_log(GPR_INFO, "testing callbacks for: plugin1, plugin2, plugin3");
+ auto group3 = GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
+ StatsPlugin::ChannelScope(kDomain1To4, ""));
+ callback1 = group3.RegisterCallback(
+ [&](CallbackMetricReporter& reporter) {
+ reporter.Report(int64_gauge_handle, 5, kLabelValues,
+ kOptionalLabelValues);
+ },
+ {int64_gauge_handle});
+ callback2 = group3.RegisterCallback(
+ [&](CallbackMetricReporter& reporter) {
+ reporter.Report(int64_gauge_handle, 6, kLabelValues2,
+ kOptionalLabelValues);
+ },
+ {int64_gauge_handle});
+ // Plugin1 and plugin2 still has data from before, but plugin3 has none.
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(3));
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(4));
+ EXPECT_THAT(plugin2->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(3));
+ EXPECT_THAT(plugin2->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(4));
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ // Now invoke the callbacks.
+ plugin1->TriggerCallbacks();
+ plugin2->TriggerCallbacks();
+ plugin3->TriggerCallbacks();
+ // Now plugin1 and plugin2 should have data, but plugin3 should not.
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(5));
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(6));
+ EXPECT_THAT(plugin2->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(5));
+ EXPECT_THAT(plugin2->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(6));
+ EXPECT_THAT(plugin3->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(5));
+ EXPECT_THAT(plugin3->GetCallbackGaugeValue(int64_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(6));
+ // Need to destroy callbacks before the plugin group that created them.
+ callback1.reset();
+ callback2.reset();
+}
+
+TEST_F(MetricsTest, DoubleCallbackGauge) {
+ 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_gauge_handle =
+ GlobalInstrumentsRegistry::RegisterCallbackDoubleGauge(
+ "double_gauge", "A simple double gauge.", "unit", kLabelKeys,
+ kOptionalLabelKeys, true);
+ constexpr absl::string_view kLabelValues[] = {"label_value_1",
+ "label_value_2"};
+ constexpr absl::string_view kLabelValues2[] = {"label_value_3",
+ "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(kDomain3To4);
+ auto plugin2 = MakeStatsPluginForTarget(kDomain2To4);
+ auto plugin3 = MakeStatsPluginForTarget(kDomain1To4);
+ // Register two callbacks that set the same metric but with different
+ // label values. The callbacks get used only by plugin1.
+ gpr_log(GPR_INFO, "testing callbacks for: plugin1");
+ auto group1 = GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
+ StatsPlugin::ChannelScope(kDomain3To4, ""));
+ auto callback1 = group1.RegisterCallback(
+ [&](CallbackMetricReporter& reporter) {
+ reporter.Report(double_gauge_handle, 1.23, kLabelValues,
+ kOptionalLabelValues);
+ },
+ {double_gauge_handle});
+ auto callback2 = group1.RegisterCallback(
+ [&](CallbackMetricReporter& reporter) {
+ reporter.Report(double_gauge_handle, 2.34, kLabelValues2,
+ kOptionalLabelValues);
+ },
+ {double_gauge_handle});
+ // No plugins have data yet.
+ EXPECT_EQ(plugin1->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin1->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin2->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin2->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ // Now invoke the callbacks.
+ plugin1->TriggerCallbacks();
+ plugin2->TriggerCallbacks();
+ plugin3->TriggerCallbacks();
+ // Now plugin1 should have data, but the others should not.
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(1.23));
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(2.34));
+ EXPECT_EQ(plugin2->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin2->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ // De-register the callbacks.
+ callback1.reset();
+ callback2.reset();
+ // Now register callbacks that hit both plugin1 and plugin2.
+ gpr_log(GPR_INFO, "testing callbacks for: plugin1, plugin2");
+ auto group2 = GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
+ StatsPlugin::ChannelScope(kDomain2To4, ""));
+ callback1 = group2.RegisterCallback(
+ [&](CallbackMetricReporter& reporter) {
+ reporter.Report(double_gauge_handle, 3.45, kLabelValues,
+ kOptionalLabelValues);
+ },
+ {double_gauge_handle});
+ callback2 = group2.RegisterCallback(
+ [&](CallbackMetricReporter& reporter) {
+ reporter.Report(double_gauge_handle, 4.56, kLabelValues2,
+ kOptionalLabelValues);
+ },
+ {double_gauge_handle});
+ // Plugin1 still has data from before, but the others have none.
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(1.23));
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(2.34));
+ EXPECT_EQ(plugin2->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin2->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ // Now invoke the callbacks.
+ plugin1->TriggerCallbacks();
+ plugin2->TriggerCallbacks();
+ plugin3->TriggerCallbacks();
+ // Now plugin1 and plugin2 should have data, but plugin3 should not.
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(3.45));
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(4.56));
+ EXPECT_THAT(plugin2->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(3.45));
+ EXPECT_THAT(plugin2->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(4.56));
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ // De-register the callbacks.
+ callback1.reset();
+ callback2.reset();
+ // Now register callbacks that hit all three plugins.
+ gpr_log(GPR_INFO, "testing callbacks for: plugin1, plugin2, plugin3");
+ auto group3 = GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
+ StatsPlugin::ChannelScope(kDomain1To4, ""));
+ callback1 = group3.RegisterCallback(
+ [&](CallbackMetricReporter& reporter) {
+ reporter.Report(double_gauge_handle, 5.67, kLabelValues,
+ kOptionalLabelValues);
+ },
+ {double_gauge_handle});
+ callback2 = group3.RegisterCallback(
+ [&](CallbackMetricReporter& reporter) {
+ reporter.Report(double_gauge_handle, 6.78, kLabelValues2,
+ kOptionalLabelValues);
+ },
+ {double_gauge_handle});
+ // Plugin1 and plugin2 still has data from before, but plugin3 has none.
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(3.45));
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(4.56));
+ EXPECT_THAT(plugin2->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(3.45));
+ EXPECT_THAT(plugin2->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(4.56));
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ absl::nullopt);
+ EXPECT_EQ(plugin3->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ absl::nullopt);
+ // Now invoke the callbacks.
+ plugin1->TriggerCallbacks();
+ plugin2->TriggerCallbacks();
+ plugin3->TriggerCallbacks();
+ // Now plugin1 and plugin2 should have data, but plugin3 should not.
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(5.67));
+ EXPECT_THAT(plugin1->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(6.78));
+ EXPECT_THAT(plugin2->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(5.67));
+ EXPECT_THAT(plugin2->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(6.78));
+ EXPECT_THAT(plugin3->GetCallbackGaugeValue(double_gauge_handle, kLabelValues,
+ kOptionalLabelValues),
+ ::testing::Optional(5.67));
+ EXPECT_THAT(plugin3->GetCallbackGaugeValue(double_gauge_handle, kLabelValues2,
+ kOptionalLabelValues),
+ ::testing::Optional(6.78));
+ // Need to destroy callbacks before the plugin group that created them.
+ callback1.reset();
+ callback2.reset();
+}
+
TEST_F(MetricsTest, DisableByDefaultMetricIsNotRecordedByFakeStatsPlugin) {
const absl::string_view kLabelKeys[] = {"label_key_1", "label_key_2"};
const absl::string_view kOptionalLabelKeys[] = {"optional_label_key_1",
diff --git a/test/core/util/fake_stats_plugin.cc b/test/core/util/fake_stats_plugin.cc
index 9aa31783e0e..d00df284599 100644
--- a/test/core/util/fake_stats_plugin.cc
+++ b/test/core/util/fake_stats_plugin.cc
@@ -175,6 +175,44 @@ GlobalInstrumentsRegistryTestPeer::FindDoubleHistogramHandleByName(
GlobalInstrumentsRegistry::InstrumentType::kHistogram);
}
+absl::optional
+GlobalInstrumentsRegistryTestPeer::FindInt64GaugeHandleByName(
+ absl::string_view name) {
+ return FindInstrument(
+ GlobalInstrumentsRegistry::GetInstrumentList(), name,
+ GlobalInstrumentsRegistry::ValueType::kInt64,
+ GlobalInstrumentsRegistry::InstrumentType::kGauge);
+}
+
+absl::optional
+GlobalInstrumentsRegistryTestPeer::FindDoubleGaugeHandleByName(
+ absl::string_view name) {
+ return FindInstrument(
+ GlobalInstrumentsRegistry::GetInstrumentList(), name,
+ GlobalInstrumentsRegistry::ValueType::kDouble,
+ GlobalInstrumentsRegistry::InstrumentType::kGauge);
+}
+
+absl::optional
+GlobalInstrumentsRegistryTestPeer::FindCallbackInt64GaugeHandleByName(
+ absl::string_view name) {
+ return FindInstrument<
+ GlobalInstrumentsRegistry::GlobalCallbackInt64GaugeHandle>(
+ GlobalInstrumentsRegistry::GetInstrumentList(), name,
+ GlobalInstrumentsRegistry::ValueType::kInt64,
+ GlobalInstrumentsRegistry::InstrumentType::kCallbackGauge);
+}
+
+absl::optional
+GlobalInstrumentsRegistryTestPeer::FindCallbackDoubleGaugeHandleByName(
+ absl::string_view name) {
+ return FindInstrument<
+ GlobalInstrumentsRegistry::GlobalCallbackDoubleGaugeHandle>(
+ GlobalInstrumentsRegistry::GetInstrumentList(), name,
+ GlobalInstrumentsRegistry::ValueType::kDouble,
+ GlobalInstrumentsRegistry::InstrumentType::kCallbackGauge);
+}
+
GlobalInstrumentsRegistry::GlobalInstrumentDescriptor*
GlobalInstrumentsRegistryTestPeer::FindMetricDescriptorByName(
absl::string_view name) {
diff --git a/test/core/util/fake_stats_plugin.h b/test/core/util/fake_stats_plugin.h
index a7a4751ebbd..04268ca694e 100644
--- a/test/core/util/fake_stats_plugin.h
+++ b/test/core/util/fake_stats_plugin.h
@@ -219,23 +219,41 @@ class FakeStatsPlugin : public StatsPlugin {
std::string(descriptor.name).c_str());
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);
- }
+ switch (descriptor.instrument_type) {
+ case GlobalInstrumentsRegistry::InstrumentType::kCounter:
+ if (descriptor.value_type ==
+ GlobalInstrumentsRegistry::ValueType::kUInt64) {
+ uint64_counters_.emplace(descriptor.index, descriptor);
+ } else {
+ double_counters_.emplace(descriptor.index, descriptor);
+ }
+ break;
+ case GlobalInstrumentsRegistry::InstrumentType::kHistogram:
+ if (descriptor.value_type ==
+ GlobalInstrumentsRegistry::ValueType::kUInt64) {
+ uint64_histograms_.emplace(descriptor.index, descriptor);
+ } else {
+ double_histograms_.emplace(descriptor.index, descriptor);
+ }
+ break;
+ case GlobalInstrumentsRegistry::InstrumentType::kGauge:
+ if (descriptor.value_type ==
+ GlobalInstrumentsRegistry::ValueType::kInt64) {
+ int64_gauges_.emplace(descriptor.index, descriptor);
+ } else {
+ double_gauges_.emplace(descriptor.index, descriptor);
+ }
+ break;
+ case GlobalInstrumentsRegistry::InstrumentType::kCallbackGauge:
+ if (descriptor.value_type ==
+ GlobalInstrumentsRegistry::ValueType::kInt64) {
+ int64_callback_gauges_.emplace(descriptor.index, descriptor);
+ } else {
+ double_callback_gauges_.emplace(descriptor.index, descriptor);
+ }
+ break;
+ default:
+ Crash("unknown instrument type");
}
});
}
@@ -314,6 +332,41 @@ class FakeStatsPlugin : public StatsPlugin {
if (iter == double_histograms_.end()) return;
iter->second.Record(value, label_values, optional_values);
}
+ void SetGauge(GlobalInstrumentsRegistry::GlobalInt64GaugeHandle handle,
+ int64_t value, absl::Span label_values,
+ absl::Span optional_values) override {
+ gpr_log(GPR_INFO,
+ "FakeStatsPlugin[%p]::RecordGauge(index=%u, value=(uint64)%lu, "
+ "label_values={%s}, optional_label_values={%s}",
+ this, handle.index, value,
+ absl::StrJoin(label_values, ", ").c_str(),
+ absl::StrJoin(optional_values, ", ").c_str());
+ auto iter = int64_gauges_.find(handle.index);
+ if (iter == int64_gauges_.end()) return;
+ iter->second.Set(value, label_values, optional_values);
+ }
+ void SetGauge(GlobalInstrumentsRegistry::GlobalDoubleGaugeHandle handle,
+ double value, absl::Span label_values,
+ absl::Span optional_values) override {
+ gpr_log(GPR_INFO,
+ "FakeStatsPlugin[%p]::RecordGauge(index=%u, value=(double)%f, "
+ "label_values={%s}, optional_label_values={%s}",
+ this, handle.index, value,
+ absl::StrJoin(label_values, ", ").c_str(),
+ absl::StrJoin(optional_values, ", ").c_str());
+ auto iter = double_gauges_.find(handle.index);
+ if (iter == double_gauges_.end()) return;
+ iter->second.Set(value, label_values, optional_values);
+ }
+ void AddCallback(RegisteredMetricCallback* callback) override {
+ gpr_log(GPR_INFO, "FakeStatsPlugin[%p]::AddCallback(%p)", this, callback);
+ callbacks_.insert(callback);
+ }
+ void RemoveCallback(RegisteredMetricCallback* callback) override {
+ gpr_log(GPR_INFO, "FakeStatsPlugin[%p]::RemoveCallback(%p)", this,
+ callback);
+ callbacks_.erase(callback);
+ }
absl::optional GetCounterValue(
GlobalInstrumentsRegistry::GlobalUInt64CounterHandle handle,
@@ -355,8 +408,96 @@ class FakeStatsPlugin : public StatsPlugin {
}
return iter->second.GetValues(label_values, optional_values);
}
+ absl::optional GetGaugeValue(
+ GlobalInstrumentsRegistry::GlobalInt64GaugeHandle handle,
+ absl::Span label_values,
+ absl::Span optional_values) {
+ auto iter = int64_gauges_.find(handle.index);
+ if (iter == int64_gauges_.end()) {
+ return absl::nullopt;
+ }
+ return iter->second.GetValue(label_values, optional_values);
+ }
+ absl::optional GetGaugeValue(
+ GlobalInstrumentsRegistry::GlobalDoubleGaugeHandle handle,
+ absl::Span label_values,
+ absl::Span optional_values) {
+ auto iter = double_gauges_.find(handle.index);
+ if (iter == double_gauges_.end()) {
+ return absl::nullopt;
+ }
+ return iter->second.GetValue(label_values, optional_values);
+ }
+ void TriggerCallbacks() {
+ gpr_log(GPR_INFO, "FakeStatsPlugin[%p]::TriggerCallbacks(): START", this);
+ Reporter reporter(*this);
+ for (auto* callback : callbacks_) {
+ callback->Run(reporter);
+ }
+ gpr_log(GPR_INFO, "FakeStatsPlugin[%p]::TriggerCallbacks(): END", this);
+ }
+ absl::optional GetCallbackGaugeValue(
+ GlobalInstrumentsRegistry::GlobalCallbackInt64GaugeHandle handle,
+ absl::Span label_values,
+ absl::Span optional_values) {
+ auto iter = int64_callback_gauges_.find(handle.index);
+ if (iter == int64_callback_gauges_.end()) {
+ return absl::nullopt;
+ }
+ return iter->second.GetValue(label_values, optional_values);
+ }
+ absl::optional GetCallbackGaugeValue(
+ GlobalInstrumentsRegistry::GlobalCallbackDoubleGaugeHandle handle,
+ absl::Span label_values,
+ absl::Span optional_values) {
+ auto iter = double_callback_gauges_.find(handle.index);
+ if (iter == double_callback_gauges_.end()) {
+ return absl::nullopt;
+ }
+ return iter->second.GetValue(label_values, optional_values);
+ }
private:
+ class Reporter : public CallbackMetricReporter {
+ public:
+ explicit Reporter(FakeStatsPlugin& plugin) : plugin_(plugin) {}
+
+ void Report(
+ GlobalInstrumentsRegistry::GlobalCallbackInt64GaugeHandle handle,
+ int64_t value, absl::Span label_values,
+ absl::Span optional_values) override {
+ gpr_log(GPR_INFO,
+ "FakeStatsPlugin[%p]::Reporter::Report(index=%u, "
+ "value=(uint64)%ld, label_values={%s}, "
+ "optional_label_values={%s}",
+ this, handle.index, value,
+ absl::StrJoin(label_values, ", ").c_str(),
+ absl::StrJoin(optional_values, ", ").c_str());
+ auto iter = plugin_.int64_callback_gauges_.find(handle.index);
+ if (iter == plugin_.int64_callback_gauges_.end()) return;
+ iter->second.Set(value, label_values, optional_values);
+ }
+
+ void Report(
+ GlobalInstrumentsRegistry::GlobalCallbackDoubleGaugeHandle handle,
+ double value, absl::Span label_values,
+ absl::Span optional_values) override {
+ gpr_log(GPR_INFO,
+ "FakeStatsPlugin[%p]::Reporter::Report(index=%u, "
+ "value=(double)%f, label_values={%s}, "
+ "optional_label_values={%s}",
+ this, handle.index, value,
+ absl::StrJoin(label_values, ", ").c_str(),
+ absl::StrJoin(optional_values, ", ").c_str());
+ auto iter = plugin_.double_callback_gauges_.find(handle.index);
+ if (iter == plugin_.double_callback_gauges_.end()) return;
+ iter->second.Set(value, label_values, optional_values);
+ }
+
+ private:
+ FakeStatsPlugin& plugin_;
+ };
+
template
class Counter {
public:
@@ -442,12 +583,53 @@ class FakeStatsPlugin : public StatsPlugin {
absl::flat_hash_map> storage_;
};
+ template
+ class Gauge {
+ public:
+ explicit Gauge(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 Set(T t, absl::Span label_values,
+ absl::Span optional_values) {
+ storage_[MakeLabelString(label_keys_, label_values, optional_label_keys_,
+ optional_values)] = t;
+ }
+
+ absl::optional GetValue(
+ absl::Span label_values,
+ absl::Span 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 label_keys_;
+ std::vector optional_label_keys_;
+ absl::flat_hash_map storage_;
+ };
+
absl::AnyInvocable channel_filter_;
// Instruments.
absl::flat_hash_map> uint64_counters_;
absl::flat_hash_map> double_counters_;
absl::flat_hash_map> uint64_histograms_;
absl::flat_hash_map> double_histograms_;
+ absl::flat_hash_map> int64_gauges_;
+ absl::flat_hash_map> double_gauges_;
+ absl::flat_hash_map> int64_callback_gauges_;
+ absl::flat_hash_map> double_callback_gauges_;
+ std::set callbacks_;
};
class FakeStatsPluginBuilder {
@@ -492,6 +674,16 @@ class GlobalInstrumentsRegistryTestPeer {
FindUInt64HistogramHandleByName(absl::string_view name);
static absl::optional
FindDoubleHistogramHandleByName(absl::string_view name);
+ static absl::optional
+ FindInt64GaugeHandleByName(absl::string_view name);
+ static absl::optional
+ FindDoubleGaugeHandleByName(absl::string_view name);
+ static absl::optional<
+ GlobalInstrumentsRegistry::GlobalCallbackInt64GaugeHandle>
+ FindCallbackInt64GaugeHandleByName(absl::string_view name);
+ static absl::optional<
+ GlobalInstrumentsRegistry::GlobalCallbackDoubleGaugeHandle>
+ FindCallbackDoubleGaugeHandleByName(absl::string_view name);
static GlobalInstrumentsRegistry::GlobalInstrumentDescriptor*
FindMetricDescriptorByName(absl::string_view name);
diff --git a/test/cpp/end2end/BUILD b/test/cpp/end2end/BUILD
index c06aafc882e..f0f5305a67d 100644
--- a/test/cpp/end2end/BUILD
+++ b/test/cpp/end2end/BUILD
@@ -568,6 +568,7 @@ grpc_cc_test(
"//src/proto/grpc/testing:echo_messages_proto",
"//src/proto/grpc/testing:echo_proto",
"//src/proto/grpc/testing/duplicate:echo_duplicate_proto",
+ "//test/core/util:fake_stats_plugin",
"//test/core/util:grpc_test_util",
"//test/core/util:test_lb_policies",
"//test/cpp/util:test_config",
diff --git a/test/cpp/end2end/rls_end2end_test.cc b/test/cpp/end2end/rls_end2end_test.cc
index faa798acfbc..ec48de736da 100644
--- a/test/cpp/end2end/rls_end2end_test.cc
+++ b/test/cpp/end2end/rls_end2end_test.cc
@@ -49,6 +49,7 @@
#include "src/core/lib/iomgr/sockaddr.h"
#include "src/core/lib/security/credentials/fake/fake_credentials.h"
#include "src/core/lib/uri/uri_parser.h"
+#include "src/core/load_balancing/rls/rls.h"
#include "src/core/resolver/fake/fake_resolver.h"
#include "src/core/service_config/service_config_impl.h"
#include "src/cpp/client/secure_credentials.h"
@@ -56,6 +57,7 @@
#include "src/proto/grpc/lookup/v1/rls.grpc.pb.h"
#include "src/proto/grpc/lookup/v1/rls.pb.h"
#include "src/proto/grpc/testing/echo.grpc.pb.h"
+#include "test/core/util/fake_stats_plugin.h"
#include "test/core/util/port.h"
#include "test/core/util/resolve_localhost_ip46.h"
#include "test/core/util/test_config.h"
@@ -73,6 +75,7 @@ namespace {
const char* kServerName = "test.google.fr";
const char* kRequestMessage = "Live long and prosper.";
+const char* kRlsInstanceUuid = "rls_instance_uuid";
const char* kCallCredsMdKey = "call_cred_name";
const char* kCallCredsMdValue = "call_cred_value";
@@ -182,6 +185,7 @@ class RlsEnd2endTest : public ::testing::Test {
EXPECT_EQ(ctx->ExperimentalGetAuthority(), kServerName);
});
rls_server_->Start();
+ rls_server_target_ = absl::StrFormat("localhost:%d", rls_server_->port_);
// Set up client.
resolver_response_generator_ =
std::make_unique();
@@ -189,6 +193,7 @@ class RlsEnd2endTest : public ::testing::Test {
args.SetPointer(GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR,
resolver_response_generator_->Get());
args.SetString(GRPC_ARG_FAKE_SECURITY_EXPECTED_TARGETS, kServerName);
+ args.SetString(GRPC_ARG_TEST_ONLY_RLS_INSTANCE_ID, kRlsInstanceUuid);
grpc_channel_credentials* channel_creds =
grpc_fake_transport_security_credentials_create();
grpc_call_credentials* call_creds = grpc_md_only_test_credentials_create(
@@ -198,8 +203,8 @@ class RlsEnd2endTest : public ::testing::Test {
nullptr));
call_creds->Unref();
channel_creds->Unref();
- channel_ = grpc::CreateCustomChannel(absl::StrCat("fake:///", kServerName),
- std::move(creds), args);
+ target_uri_ = absl::StrCat("fake:///", kServerName);
+ channel_ = grpc::CreateCustomChannel(target_uri_, std::move(creds), args);
stub_ = grpc::testing::EchoTestService::NewStub(channel_);
}
@@ -295,8 +300,8 @@ class RlsEnd2endTest : public ::testing::Test {
class ServiceConfigBuilder {
public:
- explicit ServiceConfigBuilder(int rls_server_port)
- : rls_server_port_(rls_server_port) {}
+ explicit ServiceConfigBuilder(absl::string_view rls_server_target)
+ : rls_server_target_(rls_server_target) {}
ServiceConfigBuilder& set_lookup_service_timeout(
grpc_core::Duration timeout) {
@@ -333,7 +338,7 @@ class RlsEnd2endTest : public ::testing::Test {
// First build parts of routeLookupConfig.
std::vector route_lookup_config_parts;
route_lookup_config_parts.push_back(absl::StrFormat(
- " \"lookupService\":\"localhost:%d\"", rls_server_port_));
+ " \"lookupService\":\"%s\"", rls_server_target_));
if (lookup_service_timeout_ > grpc_core::Duration::Zero()) {
route_lookup_config_parts.push_back(
absl::StrFormat(" \"lookupServiceTimeout\":\"%fs\"",
@@ -382,7 +387,7 @@ class RlsEnd2endTest : public ::testing::Test {
}
private:
- int rls_server_port_;
+ absl::string_view rls_server_target_;
grpc_core::Duration lookup_service_timeout_;
std::string default_target_;
grpc_core::Duration max_age_;
@@ -392,7 +397,7 @@ class RlsEnd2endTest : public ::testing::Test {
};
ServiceConfigBuilder MakeServiceConfigBuilder() {
- return ServiceConfigBuilder(rls_server_->port_);
+ return ServiceConfigBuilder(rls_server_target_);
}
void SetNextResolution(absl::string_view service_config_json) {
@@ -456,9 +461,11 @@ class RlsEnd2endTest : public ::testing::Test {
};
std::vector>> backends_;
+ std::string rls_server_target_;
std::unique_ptr> rls_server_;
std::unique_ptr
resolver_response_generator_;
+ std::string target_uri_;
std::shared_ptr channel_;
std::unique_ptr stub_;
};
@@ -1370,6 +1377,309 @@ TEST_F(RlsEnd2endTest, ConnectivityStateTransientFailure) {
channel_->GetState(/*try_to_connect=*/false));
}
+class RlsMetricsEnd2endTest : public RlsEnd2endTest {
+ protected:
+ void SetUp() override {
+ // Register stats plugin before initializing client.
+ stats_plugin_ = grpc_core::FakeStatsPluginBuilder()
+ .UseDisabledByDefaultMetrics(true)
+ .BuildAndRegister();
+ RlsEnd2endTest::SetUp();
+ }
+
+ std::shared_ptr stats_plugin_;
+};
+
+TEST_F(RlsMetricsEnd2endTest, MetricDefinitionDefaultTargetPicks) {
+ const auto* descriptor =
+ grpc_core::GlobalInstrumentsRegistryTestPeer::FindMetricDescriptorByName(
+ "grpc.lb.rls.default_target_picks");
+ ASSERT_NE(descriptor, nullptr);
+ EXPECT_EQ(descriptor->value_type,
+ grpc_core::GlobalInstrumentsRegistry::ValueType::kUInt64);
+ EXPECT_EQ(descriptor->instrument_type,
+ grpc_core::GlobalInstrumentsRegistry::InstrumentType::kCounter);
+ EXPECT_EQ(descriptor->enable_by_default, false);
+ EXPECT_EQ(descriptor->name, "grpc.lb.rls.default_target_picks");
+ EXPECT_EQ(descriptor->unit, "{pick}");
+ EXPECT_THAT(descriptor->label_keys,
+ ::testing::ElementsAre("grpc.target", "grpc.lb.rls.server_target",
+ "grpc.lb.rls.data_plane_target",
+ "grpc.lb.pick_result"));
+ EXPECT_THAT(descriptor->optional_label_keys, ::testing::ElementsAre());
+}
+
+TEST_F(RlsMetricsEnd2endTest, MetricDefinitionTargetPicks) {
+ const auto* descriptor =
+ grpc_core::GlobalInstrumentsRegistryTestPeer::FindMetricDescriptorByName(
+ "grpc.lb.rls.target_picks");
+ ASSERT_NE(descriptor, nullptr);
+ EXPECT_EQ(descriptor->value_type,
+ grpc_core::GlobalInstrumentsRegistry::ValueType::kUInt64);
+ EXPECT_EQ(descriptor->instrument_type,
+ grpc_core::GlobalInstrumentsRegistry::InstrumentType::kCounter);
+ EXPECT_EQ(descriptor->enable_by_default, false);
+ EXPECT_EQ(descriptor->name, "grpc.lb.rls.target_picks");
+ EXPECT_EQ(descriptor->unit, "{pick}");
+ EXPECT_THAT(descriptor->label_keys,
+ ::testing::ElementsAre("grpc.target", "grpc.lb.rls.server_target",
+ "grpc.lb.rls.data_plane_target",
+ "grpc.lb.pick_result"));
+ EXPECT_THAT(descriptor->optional_label_keys, ::testing::ElementsAre());
+}
+
+TEST_F(RlsMetricsEnd2endTest, MetricDefinitionFailedPicks) {
+ const auto* descriptor =
+ grpc_core::GlobalInstrumentsRegistryTestPeer::FindMetricDescriptorByName(
+ "grpc.lb.rls.failed_picks");
+ ASSERT_NE(descriptor, nullptr);
+ EXPECT_EQ(descriptor->value_type,
+ grpc_core::GlobalInstrumentsRegistry::ValueType::kUInt64);
+ EXPECT_EQ(descriptor->instrument_type,
+ grpc_core::GlobalInstrumentsRegistry::InstrumentType::kCounter);
+ EXPECT_EQ(descriptor->enable_by_default, false);
+ EXPECT_EQ(descriptor->name, "grpc.lb.rls.failed_picks");
+ EXPECT_EQ(descriptor->unit, "{pick}");
+ EXPECT_THAT(
+ descriptor->label_keys,
+ ::testing::ElementsAre("grpc.target", "grpc.lb.rls.server_target"));
+ EXPECT_THAT(descriptor->optional_label_keys, ::testing::ElementsAre());
+}
+
+TEST_F(RlsMetricsEnd2endTest, MetricDefinitionCacheEntries) {
+ const auto* descriptor =
+ grpc_core::GlobalInstrumentsRegistryTestPeer::FindMetricDescriptorByName(
+ "grpc.lb.rls.cache_entries");
+ ASSERT_NE(descriptor, nullptr);
+ EXPECT_EQ(descriptor->value_type,
+ grpc_core::GlobalInstrumentsRegistry::ValueType::kInt64);
+ EXPECT_EQ(
+ descriptor->instrument_type,
+ grpc_core::GlobalInstrumentsRegistry::InstrumentType::kCallbackGauge);
+ EXPECT_EQ(descriptor->enable_by_default, false);
+ EXPECT_EQ(descriptor->name, "grpc.lb.rls.cache_entries");
+ EXPECT_EQ(descriptor->unit, "{entry}");
+ EXPECT_THAT(descriptor->label_keys,
+ ::testing::ElementsAre("grpc.target", "grpc.lb.rls.server_target",
+ "grpc.lb.rls.instance_uuid"));
+ EXPECT_THAT(descriptor->optional_label_keys, ::testing::ElementsAre());
+}
+
+TEST_F(RlsMetricsEnd2endTest, MetricDefinitionCacheSize) {
+ const auto* descriptor =
+ grpc_core::GlobalInstrumentsRegistryTestPeer::FindMetricDescriptorByName(
+ "grpc.lb.rls.cache_size");
+ ASSERT_NE(descriptor, nullptr);
+ EXPECT_EQ(descriptor->value_type,
+ grpc_core::GlobalInstrumentsRegistry::ValueType::kInt64);
+ EXPECT_EQ(
+ descriptor->instrument_type,
+ grpc_core::GlobalInstrumentsRegistry::InstrumentType::kCallbackGauge);
+ EXPECT_EQ(descriptor->enable_by_default, false);
+ EXPECT_EQ(descriptor->name, "grpc.lb.rls.cache_size");
+ EXPECT_EQ(descriptor->unit, "By");
+ EXPECT_THAT(descriptor->label_keys,
+ ::testing::ElementsAre("grpc.target", "grpc.lb.rls.server_target",
+ "grpc.lb.rls.instance_uuid"));
+ EXPECT_THAT(descriptor->optional_label_keys, ::testing::ElementsAre());
+}
+
+TEST_F(RlsMetricsEnd2endTest, MetricValues) {
+ auto kMetricTargetPicks =
+ grpc_core::GlobalInstrumentsRegistryTestPeer::
+ FindUInt64CounterHandleByName("grpc.lb.rls.target_picks")
+ .value();
+ auto kMetricFailedPicks =
+ grpc_core::GlobalInstrumentsRegistryTestPeer::
+ FindUInt64CounterHandleByName("grpc.lb.rls.failed_picks")
+ .value();
+ auto kMetricCacheEntries =
+ grpc_core::GlobalInstrumentsRegistryTestPeer::
+ FindCallbackInt64GaugeHandleByName("grpc.lb.rls.cache_entries")
+ .value();
+ auto kMetricCacheSize =
+ grpc_core::GlobalInstrumentsRegistryTestPeer::
+ FindCallbackInt64GaugeHandleByName("grpc.lb.rls.cache_size")
+ .value();
+ StartBackends(2);
+ SetNextResolution(
+ MakeServiceConfigBuilder()
+ .AddKeyBuilder(absl::StrFormat("\"names\":[{"
+ " \"service\":\"%s\","
+ " \"method\":\"%s\""
+ "}],"
+ "\"headers\":["
+ " {"
+ " \"key\":\"%s\","
+ " \"names\":["
+ " \"key1\""
+ " ]"
+ " }"
+ "]",
+ kServiceValue, kMethodValue, kTestKey))
+ .Build());
+ const std::string rls_target0 = grpc_core::LocalIpUri(backends_[0]->port_);
+ const std::string rls_target1 = grpc_core::LocalIpUri(backends_[1]->port_);
+ // Send an RPC to the target for backend 0.
+ rls_server_->service_.SetResponse(BuildRlsRequest({{kTestKey, rls_target0}}),
+ BuildRlsResponse({rls_target0}));
+ CheckRpcSendOk(DEBUG_LOCATION,
+ RpcOptions().set_metadata({{"key1", rls_target0}}));
+ EXPECT_EQ(rls_server_->service_.request_count(), 1);
+ EXPECT_EQ(rls_server_->service_.response_count(), 1);
+ EXPECT_EQ(backends_[0]->service_.request_count(), 1);
+ EXPECT_EQ(backends_[1]->service_.request_count(), 0);
+ // Check exported metrics.
+ EXPECT_THAT(
+ stats_plugin_->GetCounterValue(
+ kMetricTargetPicks,
+ {target_uri_, rls_server_target_, rls_target0, "complete"}, {}),
+ ::testing::Optional(1));
+ EXPECT_THAT(
+ stats_plugin_->GetCounterValue(
+ kMetricTargetPicks,
+ {target_uri_, rls_server_target_, rls_target1, "complete"}, {}),
+ absl::nullopt);
+ EXPECT_EQ(stats_plugin_->GetCounterValue(
+ kMetricFailedPicks, {target_uri_, rls_server_target_}, {}),
+ absl::nullopt);
+ stats_plugin_->TriggerCallbacks();
+ EXPECT_THAT(stats_plugin_->GetCallbackGaugeValue(
+ kMetricCacheEntries,
+ {target_uri_, rls_server_target_, kRlsInstanceUuid}, {}),
+ ::testing::Optional(1));
+ auto cache_size = stats_plugin_->GetCallbackGaugeValue(
+ kMetricCacheSize, {target_uri_, rls_server_target_, kRlsInstanceUuid},
+ {});
+ EXPECT_THAT(cache_size, ::testing::Optional(::testing::Ge(1)));
+ // Send an RPC to the target for backend 1.
+ rls_server_->service_.SetResponse(BuildRlsRequest({{kTestKey, rls_target1}}),
+ BuildRlsResponse({rls_target1}));
+ CheckRpcSendOk(DEBUG_LOCATION,
+ RpcOptions().set_metadata({{"key1", rls_target1}}));
+ EXPECT_EQ(rls_server_->service_.request_count(), 2);
+ EXPECT_EQ(rls_server_->service_.response_count(), 2);
+ EXPECT_EQ(backends_[0]->service_.request_count(), 1);
+ EXPECT_EQ(backends_[1]->service_.request_count(), 1);
+ // Check exported metrics.
+ EXPECT_THAT(
+ stats_plugin_->GetCounterValue(
+ kMetricTargetPicks,
+ {target_uri_, rls_server_target_, rls_target0, "complete"}, {}),
+ ::testing::Optional(1));
+ EXPECT_THAT(
+ stats_plugin_->GetCounterValue(
+ kMetricTargetPicks,
+ {target_uri_, rls_server_target_, rls_target1, "complete"}, {}),
+ ::testing::Optional(1));
+ EXPECT_EQ(stats_plugin_->GetCounterValue(
+ kMetricFailedPicks, {target_uri_, rls_server_target_}, {}),
+ absl::nullopt);
+ stats_plugin_->TriggerCallbacks();
+ EXPECT_THAT(stats_plugin_->GetCallbackGaugeValue(
+ kMetricCacheEntries,
+ {target_uri_, rls_server_target_, kRlsInstanceUuid}, {}),
+ ::testing::Optional(2));
+ auto cache_size2 = stats_plugin_->GetCallbackGaugeValue(
+ kMetricCacheSize, {target_uri_, rls_server_target_, kRlsInstanceUuid},
+ {});
+ EXPECT_THAT(cache_size2, ::testing::Optional(::testing::Ge(2)));
+ if (cache_size.has_value() && cache_size2.has_value()) {
+ EXPECT_GT(*cache_size2, *cache_size);
+ }
+ // Send an RPC for which the RLS server has no response, which means
+ // that the RLS request will fail. There is no default target, so the
+ // data plane RPC will fail.
+ CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::UNAVAILABLE,
+ "RLS request failed: INTERNAL: no response entry",
+ RpcOptions().set_metadata({{"key1", kTestValue}}));
+ EXPECT_THAT(
+ rls_server_->service_.GetUnmatchedRequests(),
+ ::testing::ElementsAre(
+ // TODO(roth): Change this to use ::testing::ProtoEquals()
+ // once that becomes available in OSS.
+ ::testing::Property(
+ &RouteLookupRequest::DebugString,
+ BuildRlsRequest({{kTestKey, kTestValue}}).DebugString())));
+ EXPECT_EQ(rls_server_->service_.request_count(), 3);
+ EXPECT_EQ(rls_server_->service_.response_count(), 2);
+ EXPECT_EQ(backends_[0]->service_.request_count(), 1);
+ EXPECT_EQ(backends_[1]->service_.request_count(), 1);
+ // Check exported metrics.
+ EXPECT_THAT(
+ stats_plugin_->GetCounterValue(
+ kMetricTargetPicks,
+ {target_uri_, rls_server_target_, rls_target0, "complete"}, {}),
+ ::testing::Optional(1));
+ EXPECT_THAT(
+ stats_plugin_->GetCounterValue(
+ kMetricTargetPicks,
+ {target_uri_, rls_server_target_, rls_target1, "complete"}, {}),
+ ::testing::Optional(1));
+ EXPECT_THAT(stats_plugin_->GetCounterValue(
+ kMetricFailedPicks, {target_uri_, rls_server_target_}, {}),
+ ::testing::Optional(1));
+ stats_plugin_->TriggerCallbacks();
+ EXPECT_THAT(stats_plugin_->GetCallbackGaugeValue(
+ kMetricCacheEntries,
+ {target_uri_, rls_server_target_, kRlsInstanceUuid}, {}),
+ ::testing::Optional(3));
+ auto cache_size3 = stats_plugin_->GetCallbackGaugeValue(
+ kMetricCacheSize, {target_uri_, rls_server_target_, kRlsInstanceUuid},
+ {});
+ EXPECT_THAT(cache_size3, ::testing::Optional(::testing::Ge(3)));
+ if (cache_size.has_value() && cache_size3.has_value()) {
+ EXPECT_GT(*cache_size3, *cache_size);
+ }
+}
+
+TEST_F(RlsMetricsEnd2endTest, MetricValuesDefaultTargetRpcs) {
+ auto kMetricDefaultTargetPicks =
+ grpc_core::GlobalInstrumentsRegistryTestPeer::
+ FindUInt64CounterHandleByName("grpc.lb.rls.default_target_picks")
+ .value();
+ StartBackends(1);
+ const std::string default_target = grpc_core::LocalIpUri(backends_[0]->port_);
+ SetNextResolution(
+ MakeServiceConfigBuilder()
+ .AddKeyBuilder(absl::StrFormat("\"names\":[{"
+ " \"service\":\"%s\","
+ " \"method\":\"%s\""
+ "}],"
+ "\"headers\":["
+ " {"
+ " \"key\":\"%s\","
+ " \"names\":["
+ " \"key1\""
+ " ]"
+ " }"
+ "]",
+ kServiceValue, kMethodValue, kTestKey))
+ .set_default_target(default_target)
+ .Build());
+ // Don't give the RLS server a response, so the RLS request will fail.
+ // The data plane RPC should be sent to the default target.
+ CheckRpcSendOk(DEBUG_LOCATION,
+ RpcOptions().set_metadata({{"key1", kTestValue}}));
+ EXPECT_THAT(
+ rls_server_->service_.GetUnmatchedRequests(),
+ ::testing::ElementsAre(
+ // TODO(roth): Change this to use ::testing::ProtoEquals()
+ // once that becomes available in OSS.
+ ::testing::Property(
+ &RouteLookupRequest::DebugString,
+ BuildRlsRequest({{kTestKey, kTestValue}}).DebugString())));
+ EXPECT_EQ(rls_server_->service_.request_count(), 1);
+ EXPECT_EQ(rls_server_->service_.response_count(), 0);
+ EXPECT_EQ(backends_[0]->service_.request_count(), 1);
+ // Check expected metrics.
+ EXPECT_THAT(
+ stats_plugin_->GetCounterValue(
+ kMetricDefaultTargetPicks,
+ {target_uri_, rls_server_target_, default_target, "complete"}, {}),
+ ::testing::Optional(1));
+}
+
} // namespace
} // namespace testing
} // namespace grpc
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index ae04b133f2f..1fbf624c5d3 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -2398,6 +2398,8 @@ src/core/lib/gprpp/time_util.cc \
src/core/lib/gprpp/time_util.h \
src/core/lib/gprpp/type_list.h \
src/core/lib/gprpp/unique_type_name.h \
+src/core/lib/gprpp/uuid_v4.cc \
+src/core/lib/gprpp/uuid_v4.h \
src/core/lib/gprpp/validation_errors.cc \
src/core/lib/gprpp/validation_errors.h \
src/core/lib/gprpp/windows/directory_reader.cc \
@@ -2889,6 +2891,7 @@ src/core/load_balancing/priority/priority.cc \
src/core/load_balancing/ring_hash/ring_hash.cc \
src/core/load_balancing/ring_hash/ring_hash.h \
src/core/load_balancing/rls/rls.cc \
+src/core/load_balancing/rls/rls.h \
src/core/load_balancing/round_robin/round_robin.cc \
src/core/load_balancing/subchannel_interface.h \
src/core/load_balancing/subchannel_list.h \
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index 5990bd20e58..9f4eb9b2777 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -2172,6 +2172,8 @@ src/core/lib/gprpp/time_util.cc \
src/core/lib/gprpp/time_util.h \
src/core/lib/gprpp/type_list.h \
src/core/lib/gprpp/unique_type_name.h \
+src/core/lib/gprpp/uuid_v4.cc \
+src/core/lib/gprpp/uuid_v4.h \
src/core/lib/gprpp/validation_errors.cc \
src/core/lib/gprpp/validation_errors.h \
src/core/lib/gprpp/windows/directory_reader.cc \
@@ -2666,6 +2668,7 @@ src/core/load_balancing/priority/priority.cc \
src/core/load_balancing/ring_hash/ring_hash.cc \
src/core/load_balancing/ring_hash/ring_hash.h \
src/core/load_balancing/rls/rls.cc \
+src/core/load_balancing/rls/rls.h \
src/core/load_balancing/round_robin/round_robin.cc \
src/core/load_balancing/subchannel_interface.h \
src/core/load_balancing/subchannel_list.h \