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 \