[RLS] implement non-per-call metrics (#36001)

Closes #36001

COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/36001 from markdroth:non_per_call_metrics_rls b713980f21
PiperOrigin-RevId: 612532005
pull/36052/head
Mark D. Roth 9 months ago committed by Copybara-Service
parent 0213523907
commit c43f1a63b0
  1. 4
      CMakeLists.txt
  2. 2
      Makefile
  3. 3
      Package.swift
  4. 12
      build_autogenerated.yaml
  5. 1
      config.m4
  6. 1
      config.w32
  7. 4
      gRPC-C++.podspec
  8. 5
      gRPC-Core.podspec
  9. 3
      grpc.gemspec
  10. 2
      grpc.gyp
  11. 3
      package.xml
  12. 9
      src/core/BUILD
  13. 145
      src/core/lib/channel/metrics.cc
  14. 137
      src/core/lib/channel/metrics.h
  15. 206
      src/core/load_balancing/rls/rls.cc
  16. 26
      src/core/load_balancing/rls/rls.h
  17. 1
      src/python/grpcio/grpc_core_dependencies.py
  18. 486
      test/core/channel/metrics_test.cc
  19. 38
      test/core/util/fake_stats_plugin.cc
  20. 226
      test/core/util/fake_stats_plugin.h
  21. 1
      test/cpp/end2end/BUILD
  22. 324
      test/cpp/end2end/rls_end2end_test.cc
  23. 3
      tools/doxygen/Doxyfile.c++.internal
  24. 3
      tools/doxygen/Doxyfile.core.internal

4
CMakeLists.txt generated

@ -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)

2
Makefile generated

@ -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 \

3
Package.swift generated

@ -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",

@ -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

1
config.m4 generated

@ -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 \

1
config.w32 generated

@ -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 " +

4
gRPC-C++.podspec generated

@ -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',

5
gRPC-Core.podspec generated

@ -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',

3
grpc.gemspec generated

@ -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 )

2
grpc.gyp generated

@ -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',

3
package.xml generated

@ -1381,6 +1381,8 @@
<file baseinstalldir="/" name="src/core/lib/gprpp/time_util.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/type_list.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/unique_type_name.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/uuid_v4.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/uuid_v4.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/validation_errors.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/validation_errors.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/windows/directory_reader.cc" role="src" />
@ -1872,6 +1874,7 @@
<file baseinstalldir="/" name="src/core/load_balancing/ring_hash/ring_hash.cc" role="src" />
<file baseinstalldir="/" name="src/core/load_balancing/ring_hash/ring_hash.h" role="src" />
<file baseinstalldir="/" name="src/core/load_balancing/rls/rls.cc" role="src" />
<file baseinstalldir="/" name="src/core/load_balancing/rls/rls.h" role="src" />
<file baseinstalldir="/" name="src/core/load_balancing/round_robin/round_robin.cc" role="src" />
<file baseinstalldir="/" name="src/core/load_balancing/subchannel_interface.h" role="src" />
<file baseinstalldir="/" name="src/core/load_balancing/subchannel_list.h" role="src" />

@ -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",
],
)

@ -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<const absl::string_view> label_keys,
absl::Span<const absl::string_view> 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<uint32_t>::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<const absl::string_view> label_keys,
absl::Span<const absl::string_view> 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<uint32_t>::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<const absl::string_view> label_keys,
absl::Span<const absl::string_view> 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<uint32_t>::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<const absl::string_view> label_keys,
absl::Span<const absl::string_view> 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<uint32_t>::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<void(const GlobalInstrumentDescriptor&)> f) {
for (const auto& instrument : GetInstrumentList()) {
@ -156,6 +272,35 @@ void GlobalInstrumentsRegistry::ForEach(
}
}
RegisteredMetricCallback::RegisteredMetricCallback(
GlobalStatsPluginRegistry::StatsPluginGroup& stats_plugin_group,
absl::AnyInvocable<void(CallbackMetricReporter&)> callback,
std::vector<GlobalInstrumentsRegistry::GlobalCallbackHandle> 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<RegisteredMetricCallback>
GlobalStatsPluginRegistry::StatsPluginGroup::RegisterCallback(
absl::AnyInvocable<void(CallbackMetricReporter&)> callback,
std::vector<GlobalInstrumentsRegistry::GlobalCallbackHandle> metrics,
Duration min_interval) {
return std::make_unique<RegisteredMetricCallback>(
*this, std::move(callback), std::move(metrics), min_interval);
}
NoDestruct<Mutex> GlobalStatsPluginRegistry::mutex_;
NoDestruct<std::vector<std::shared_ptr<StatsPlugin>>>
GlobalStatsPluginRegistry::plugins_;

@ -22,6 +22,7 @@
#include <vector>
#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<const absl::string_view> label_keys,
absl::Span<const absl::string_view> optional_label_keys,
bool enable_by_default);
static GlobalInt64GaugeHandle RegisterInt64Gauge(
absl::string_view name, absl::string_view description,
absl::string_view unit, absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> optional_label_keys,
bool enable_by_default);
static GlobalDoubleGaugeHandle RegisterDoubleGauge(
absl::string_view name, absl::string_view description,
absl::string_view unit, absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> optional_label_keys,
bool enable_by_default);
static GlobalCallbackInt64GaugeHandle RegisterCallbackInt64Gauge(
absl::string_view name, absl::string_view description,
absl::string_view unit, absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> optional_label_keys,
bool enable_by_default);
static GlobalCallbackDoubleGaugeHandle RegisterCallbackDoubleGauge(
absl::string_view name, absl::string_view description,
absl::string_view unit, absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> optional_label_keys,
bool enable_by_default);
static void ForEach(
absl::FunctionRef<void(const GlobalInstrumentDescriptor&)> 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<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) = 0;
virtual void Report(
GlobalInstrumentsRegistry::GlobalCallbackDoubleGaugeHandle handle,
double value, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) = 0;
};
class RegisteredMetricCallback;
// The StatsPlugin interface.
class StatsPlugin {
public:
@ -147,6 +196,22 @@ class StatsPlugin {
GlobalInstrumentsRegistry::GlobalDoubleHistogramHandle handle,
double value, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) = 0;
virtual void SetGauge(
GlobalInstrumentsRegistry::GlobalInt64GaugeHandle handle, int64_t value,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) = 0;
virtual void SetGauge(
GlobalInstrumentsRegistry::GlobalDoubleGaugeHandle handle, double value,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> 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<AsyncInstrument> 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<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
for (auto& plugin : plugins_) {
plugin->SetGauge(handle, value, label_values, optional_values);
}
}
void SetGauge(GlobalInstrumentsRegistry::GlobalDoubleGaugeHandle handle,
double value,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> 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<RegisteredMetricCallback> RegisterCallback(
absl::AnyInvocable<void(CallbackMetricReporter&)> callback,
std::vector<GlobalInstrumentsRegistry::GlobalCallbackHandle> metrics,
Duration min_interval = Duration::Seconds(5));
private:
friend class RegisteredMetricCallback;
std::vector<std::shared_ptr<StatsPlugin>> 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<void(CallbackMetricReporter&)> callback,
std::vector<GlobalInstrumentsRegistry::GlobalCallbackHandle> 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<GlobalInstrumentsRegistry::GlobalCallbackHandle>& 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<void(CallbackMetricReporter&)> callback_;
std::vector<GlobalInstrumentsRegistry::GlobalCallbackHandle> metrics_;
Duration min_interval_;
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_LIB_CHANNEL_METRICS_H

@ -22,6 +22,8 @@
#include <grpc/support/port_platform.h>
#include "src/core/load_balancing/rls/rls.h"
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
@ -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<RlsLb> lb_policy_;
RefCountedPtr<RlsLbConfig> config_;
RefCountedPtr<ChildPolicyWrapper> 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<RlsLbConfig> config_;
RefCountedPtr<ChildPolicyWrapper> default_child_policy_;
std::map<std::string /*target*/, ChildPolicyWrapper*> child_policy_map_;
// Must be after mu_, so that it is destroyed before mu_.
std::unique_ptr<RegisteredMetricCallback> 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<uint64_t> 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<Picker>(RefAsSubclass<RlsLb>(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
//

@ -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 <grpc/support/port_platform.h>
// 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

@ -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',

@ -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",

@ -175,6 +175,44 @@ GlobalInstrumentsRegistryTestPeer::FindDoubleHistogramHandleByName(
GlobalInstrumentsRegistry::InstrumentType::kHistogram);
}
absl::optional<GlobalInstrumentsRegistry::GlobalInt64GaugeHandle>
GlobalInstrumentsRegistryTestPeer::FindInt64GaugeHandleByName(
absl::string_view name) {
return FindInstrument<GlobalInstrumentsRegistry::GlobalInt64GaugeHandle>(
GlobalInstrumentsRegistry::GetInstrumentList(), name,
GlobalInstrumentsRegistry::ValueType::kInt64,
GlobalInstrumentsRegistry::InstrumentType::kGauge);
}
absl::optional<GlobalInstrumentsRegistry::GlobalDoubleGaugeHandle>
GlobalInstrumentsRegistryTestPeer::FindDoubleGaugeHandleByName(
absl::string_view name) {
return FindInstrument<GlobalInstrumentsRegistry::GlobalDoubleGaugeHandle>(
GlobalInstrumentsRegistry::GetInstrumentList(), name,
GlobalInstrumentsRegistry::ValueType::kDouble,
GlobalInstrumentsRegistry::InstrumentType::kGauge);
}
absl::optional<GlobalInstrumentsRegistry::GlobalCallbackInt64GaugeHandle>
GlobalInstrumentsRegistryTestPeer::FindCallbackInt64GaugeHandleByName(
absl::string_view name) {
return FindInstrument<
GlobalInstrumentsRegistry::GlobalCallbackInt64GaugeHandle>(
GlobalInstrumentsRegistry::GetInstrumentList(), name,
GlobalInstrumentsRegistry::ValueType::kInt64,
GlobalInstrumentsRegistry::InstrumentType::kCallbackGauge);
}
absl::optional<GlobalInstrumentsRegistry::GlobalCallbackDoubleGaugeHandle>
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) {

@ -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<const absl::string_view> label_values,
absl::Span<const absl::string_view> 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<const absl::string_view> label_values,
absl::Span<const absl::string_view> 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<uint64_t> GetCounterValue(
GlobalInstrumentsRegistry::GlobalUInt64CounterHandle handle,
@ -355,8 +408,96 @@ class FakeStatsPlugin : public StatsPlugin {
}
return iter->second.GetValues(label_values, optional_values);
}
absl::optional<int64_t> GetGaugeValue(
GlobalInstrumentsRegistry::GlobalInt64GaugeHandle handle,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> 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<double> GetGaugeValue(
GlobalInstrumentsRegistry::GlobalDoubleGaugeHandle handle,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> 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<int64_t> GetCallbackGaugeValue(
GlobalInstrumentsRegistry::GlobalCallbackInt64GaugeHandle handle,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> 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<double> GetCallbackGaugeValue(
GlobalInstrumentsRegistry::GlobalCallbackDoubleGaugeHandle handle,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> 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<const absl::string_view> label_values,
absl::Span<const absl::string_view> 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<const absl::string_view> label_values,
absl::Span<const absl::string_view> 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 T>
class Counter {
public:
@ -442,12 +583,53 @@ class FakeStatsPlugin : public StatsPlugin {
absl::flat_hash_map<std::string, std::vector<T>> storage_;
};
template <class T>
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<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
storage_[MakeLabelString(label_keys_, label_values, optional_label_keys_,
optional_values)] = t;
}
absl::optional<T> GetValue(
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = storage_.find(MakeLabelString(
label_keys_, label_values, optional_label_keys_, optional_values));
if (iter == storage_.end()) {
return absl::nullopt;
}
return iter->second;
}
private:
absl::string_view name_;
absl::string_view description_;
absl::string_view unit_;
std::vector<absl::string_view> label_keys_;
std::vector<absl::string_view> optional_label_keys_;
absl::flat_hash_map<std::string, T> storage_;
};
absl::AnyInvocable<bool(const ChannelScope& /*scope*/) const> channel_filter_;
// Instruments.
absl::flat_hash_map<uint32_t, Counter<uint64_t>> uint64_counters_;
absl::flat_hash_map<uint32_t, Counter<double>> double_counters_;
absl::flat_hash_map<uint32_t, Histogram<uint64_t>> uint64_histograms_;
absl::flat_hash_map<uint32_t, Histogram<double>> double_histograms_;
absl::flat_hash_map<uint32_t, Gauge<int64_t>> int64_gauges_;
absl::flat_hash_map<uint32_t, Gauge<double>> double_gauges_;
absl::flat_hash_map<uint32_t, Gauge<int64_t>> int64_callback_gauges_;
absl::flat_hash_map<uint32_t, Gauge<double>> double_callback_gauges_;
std::set<RegisteredMetricCallback*> callbacks_;
};
class FakeStatsPluginBuilder {
@ -492,6 +674,16 @@ class GlobalInstrumentsRegistryTestPeer {
FindUInt64HistogramHandleByName(absl::string_view name);
static absl::optional<GlobalInstrumentsRegistry::GlobalDoubleHistogramHandle>
FindDoubleHistogramHandleByName(absl::string_view name);
static absl::optional<GlobalInstrumentsRegistry::GlobalInt64GaugeHandle>
FindInt64GaugeHandleByName(absl::string_view name);
static absl::optional<GlobalInstrumentsRegistry::GlobalDoubleGaugeHandle>
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);

@ -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",

@ -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<FakeResolverResponseGeneratorWrapper>();
@ -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<std::string> 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<std::unique_ptr<ServerThread<MyTestServiceImpl>>> backends_;
std::string rls_server_target_;
std::unique_ptr<ServerThread<RlsServiceImpl>> rls_server_;
std::unique_ptr<FakeResolverResponseGeneratorWrapper>
resolver_response_generator_;
std::string target_uri_;
std::shared_ptr<grpc::Channel> channel_;
std::unique_ptr<grpc::testing::EchoTestService::Stub> 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<grpc_core::FakeStatsPlugin> 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

@ -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 \

@ -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 \

Loading…
Cancel
Save