From 733e904491e48a8edd4e49d12288be1797d69abc Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Tue, 19 Mar 2024 15:57:41 -0700 Subject: [PATCH] [XdsClient] implement non-per-call metrics (#36020) As per gRFC A78 (https://github.com/grpc/proposal/pull/419). Closes #36020 COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/36020 from markdroth:non_per_call_metrics_xds c1d9b464bf6249aa8ed6bc1a46142a9b09c2bf01 PiperOrigin-RevId: 617320495 --- BUILD | 1 + CMakeLists.txt | 1 + Package.swift | 1 + build_autogenerated.yaml | 4 + gRPC-C++.podspec | 2 + gRPC-Core.podspec | 2 + grpc.gemspec | 1 + package.xml | 1 + src/core/BUILD | 1 + src/core/ext/xds/xds_client.cc | 92 +- src/core/ext/xds/xds_client.h | 27 +- src/core/ext/xds/xds_client_grpc.cc | 210 +++- src/core/ext/xds/xds_client_grpc.h | 16 +- src/core/ext/xds/xds_metrics.h | 39 + src/core/ext/xds/xds_server_config_fetcher.cc | 5 +- src/core/lib/channel/metrics.cc | 10 + src/core/lib/channel/metrics.h | 4 +- test/core/xds/BUILD | 14 + test/core/xds/xds_client_fuzzer.cc | 47 +- test/core/xds/xds_client_fuzzer.proto | 7 + test/core/xds/xds_client_test.cc | 982 +++++++++++++++++- test/core/xds/xds_client_test_peer.h | 80 ++ .../xds/xds_cluster_resource_type_test.cc | 3 +- test/core/xds/xds_common_types_test.cc | 3 +- .../xds/xds_endpoint_resource_type_test.cc | 3 +- test/core/xds/xds_http_filters_test.cc | 3 +- .../xds/xds_listener_resource_type_test.cc | 3 +- .../xds_route_config_resource_type_test.cc | 3 +- test/cpp/end2end/xds/BUILD | 1 + test/cpp/end2end/xds/xds_core_end2end_test.cc | 167 +++ tools/doxygen/Doxyfile.c++.internal | 1 + tools/doxygen/Doxyfile.core.internal | 1 + 32 files changed, 1636 insertions(+), 99 deletions(-) create mode 100644 src/core/ext/xds/xds_metrics.h create mode 100644 test/core/xds/xds_client_test_peer.h diff --git a/BUILD b/BUILD index 3fca11ff4c7..bf8daacde4b 100644 --- a/BUILD +++ b/BUILD @@ -4274,6 +4274,7 @@ grpc_cc_library( "//src/core:ext/xds/xds_channel_args.h", "//src/core:ext/xds/xds_client.h", "//src/core:ext/xds/xds_client_stats.h", + "//src/core:ext/xds/xds_metrics.h", "//src/core:ext/xds/xds_resource_type.h", "//src/core:ext/xds/xds_resource_type_impl.h", "//src/core:ext/xds/xds_transport.h", diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ef168cffd9..e022789d29a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33582,6 +33582,7 @@ if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.grpc.pb.cc ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.pb.h ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.grpc.pb.h + test/core/util/fake_stats_plugin.cc test/cpp/end2end/test_service_impl.cc test/cpp/end2end/xds/xds_core_end2end_test.cc test/cpp/end2end/xds/xds_end2end_test_lib.cc diff --git a/Package.swift b/Package.swift index 0465d912aa5..f7b1930f099 100644 --- a/Package.swift +++ b/Package.swift @@ -1110,6 +1110,7 @@ let package = Package( "src/core/ext/xds/xds_lb_policy_registry.h", "src/core/ext/xds/xds_listener.cc", "src/core/ext/xds/xds_listener.h", + "src/core/ext/xds/xds_metrics.h", "src/core/ext/xds/xds_resource_type.h", "src/core/ext/xds/xds_resource_type_impl.h", "src/core/ext/xds/xds_route_config.cc", diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index adb59a57a16..973ec51a9ce 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -800,6 +800,7 @@ libs: - src/core/ext/xds/xds_http_stateful_session_filter.h - src/core/ext/xds/xds_lb_policy_registry.h - src/core/ext/xds/xds_listener.h + - src/core/ext/xds/xds_metrics.h - src/core/ext/xds/xds_resource_type.h - src/core/ext/xds/xds_resource_type_impl.h - src/core/ext/xds/xds_route_config.h @@ -20211,6 +20212,7 @@ targets: language: c++ headers: - test/core/util/scoped_env_var.h + - test/core/xds/xds_client_test_peer.h - test/core/xds/xds_transport_fake.h src: - src/proto/grpc/testing/xds/v3/base.proto @@ -20417,6 +20419,7 @@ targets: run: false language: c++ headers: + - test/core/util/fake_stats_plugin.h - test/core/util/scoped_env_var.h - test/cpp/end2end/counted_service.h - test/cpp/end2end/test_service_impl.h @@ -20456,6 +20459,7 @@ targets: - src/proto/grpc/testing/xds/v3/route.proto - src/proto/grpc/testing/xds/v3/router.proto - src/proto/grpc/testing/xds/v3/string.proto + - test/core/util/fake_stats_plugin.cc - test/cpp/end2end/test_service_impl.cc - test/cpp/end2end/xds/xds_core_end2end_test.cc - test/cpp/end2end/xds/xds_end2end_test_lib.cc diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec index b8e6e3d892b..e4e0db337dc 100644 --- a/gRPC-C++.podspec +++ b/gRPC-C++.podspec @@ -881,6 +881,7 @@ Pod::Spec.new do |s| 'src/core/ext/xds/xds_http_stateful_session_filter.h', 'src/core/ext/xds/xds_lb_policy_registry.h', 'src/core/ext/xds/xds_listener.h', + 'src/core/ext/xds/xds_metrics.h', 'src/core/ext/xds/xds_resource_type.h', 'src/core/ext/xds/xds_resource_type_impl.h', 'src/core/ext/xds/xds_route_config.h', @@ -2145,6 +2146,7 @@ Pod::Spec.new do |s| 'src/core/ext/xds/xds_http_stateful_session_filter.h', 'src/core/ext/xds/xds_lb_policy_registry.h', 'src/core/ext/xds/xds_listener.h', + 'src/core/ext/xds/xds_metrics.h', 'src/core/ext/xds/xds_resource_type.h', 'src/core/ext/xds/xds_resource_type_impl.h', 'src/core/ext/xds/xds_route_config.h', diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec index 67f256a60e3..73c31d9c6ed 100644 --- a/gRPC-Core.podspec +++ b/gRPC-Core.podspec @@ -1226,6 +1226,7 @@ Pod::Spec.new do |s| 'src/core/ext/xds/xds_lb_policy_registry.h', 'src/core/ext/xds/xds_listener.cc', 'src/core/ext/xds/xds_listener.h', + 'src/core/ext/xds/xds_metrics.h', 'src/core/ext/xds/xds_resource_type.h', 'src/core/ext/xds/xds_resource_type_impl.h', 'src/core/ext/xds/xds_route_config.cc', @@ -2927,6 +2928,7 @@ Pod::Spec.new do |s| 'src/core/ext/xds/xds_http_stateful_session_filter.h', 'src/core/ext/xds/xds_lb_policy_registry.h', 'src/core/ext/xds/xds_listener.h', + 'src/core/ext/xds/xds_metrics.h', 'src/core/ext/xds/xds_resource_type.h', 'src/core/ext/xds/xds_resource_type_impl.h', 'src/core/ext/xds/xds_route_config.h', diff --git a/grpc.gemspec b/grpc.gemspec index 1016f7ed774..4c7b75d8e43 100644 --- a/grpc.gemspec +++ b/grpc.gemspec @@ -1116,6 +1116,7 @@ Gem::Specification.new do |s| s.files += %w( src/core/ext/xds/xds_lb_policy_registry.h ) s.files += %w( src/core/ext/xds/xds_listener.cc ) s.files += %w( src/core/ext/xds/xds_listener.h ) + s.files += %w( src/core/ext/xds/xds_metrics.h ) s.files += %w( src/core/ext/xds/xds_resource_type.h ) s.files += %w( src/core/ext/xds/xds_resource_type_impl.h ) s.files += %w( src/core/ext/xds/xds_route_config.cc ) diff --git a/package.xml b/package.xml index c31c71f176b..834f8b1aa74 100644 --- a/package.xml +++ b/package.xml @@ -1098,6 +1098,7 @@ + diff --git a/src/core/BUILD b/src/core/BUILD index 857484df4f1..bbc92c0bb8e 100644 --- a/src/core/BUILD +++ b/src/core/BUILD @@ -5060,6 +5060,7 @@ grpc_cc_library( "load_file", "match", "metadata_batch", + "metrics", "pollset_set", "protobuf_any_upb", "protobuf_duration_upb", diff --git a/src/core/ext/xds/xds_client.cc b/src/core/ext/xds/xds_client.cc index c9c65ab3e51..a30c6ceafb0 100644 --- a/src/core/ext/xds/xds_client.cc +++ b/src/core/ext/xds/xds_client.cc @@ -150,7 +150,8 @@ class XdsClient::XdsChannel::AdsCall : public InternallyRefCounted { std::vector errors; std::map> resources_seen; - bool have_valid_resources = false; + uint64_t num_valid_resources = 0; + uint64_t num_invalid_resources = 0; RefCountedPtr read_delay_handle; }; @@ -783,6 +784,7 @@ void XdsClient::XdsChannel::AdsCall::AdsResponseParser::ParseResource( result_.errors.emplace_back( absl::StrCat(error_prefix, "incorrect resource type \"", type_url, "\" (should be \"", result_.type_url, "\")")); + ++result_.num_invalid_resources; return; } // Parse the resource. @@ -803,6 +805,7 @@ void XdsClient::XdsChannel::AdsCall::AdsResponseParser::ParseResource( // there's nothing more we can do here. result_.errors.emplace_back(absl::StrCat( error_prefix, decode_result.resource.status().ToString())); + ++result_.num_invalid_resources; return; } } @@ -818,6 +821,7 @@ void XdsClient::XdsChannel::AdsCall::AdsResponseParser::ParseResource( if (!parsed_resource_name.ok()) { result_.errors.emplace_back( absl::StrCat(error_prefix, "Cannot parse xDS resource name")); + ++result_.num_invalid_resources; return; } // Cancel resource-does-not-exist timer, if needed. @@ -877,10 +881,11 @@ void XdsClient::XdsChannel::AdsCall::AdsResponseParser::ParseResource( result_.read_delay_handle); UpdateResourceMetadataNacked(result_.version, decode_status.ToString(), update_time_, &resource_state.meta); + ++result_.num_invalid_resources; return; } // Resource is valid. - result_.have_valid_resources = true; + ++result_.num_valid_resources; // If it didn't change, ignore it. if (resource_state.resource != nullptr && result_.type->ResourcesEqual(resource_state.resource.get(), @@ -914,6 +919,7 @@ void XdsClient::XdsChannel::AdsCall::AdsResponseParser:: ResourceWrapperParsingFailed(size_t idx, absl::string_view message) { result_.errors.emplace_back( absl::StrCat("resource index ", idx, ": ", message)); + ++result_.num_invalid_resources; } // @@ -1157,13 +1163,19 @@ void XdsClient::XdsChannel::AdsCall::OnRecvMessage(absl::string_view payload) { } } // If we had valid resources or the update was empty, update the version. - if (result.have_valid_resources || result.errors.empty()) { + if (result.num_valid_resources > 0 || result.errors.empty()) { xds_channel()->resource_type_version_map_[result.type] = std::move(result.version); } // Send ACK or NACK. SendMessageLocked(result.type); } + // Update metrics. + if (xds_client()->metrics_reporter_ != nullptr) { + xds_client()->metrics_reporter_->ReportResourceUpdates( + xds_channel()->server_.server_uri(), result.type_url, + result.num_valid_resources, result.num_invalid_resources); + } } xds_client()->work_serializer_.DrainQueue(); } @@ -1484,10 +1496,13 @@ bool XdsClient::XdsChannel::LrsCall::IsCurrentCallOnChannel() const { // XdsClient // +constexpr absl::string_view XdsClient::kOldStyleAuthority; + XdsClient::XdsClient( std::unique_ptr bootstrap, OrphanablePtr transport_factory, std::shared_ptr engine, + std::unique_ptr metrics_reporter, std::string user_agent_name, std::string user_agent_version, Duration resource_request_timeout) : DualRefCounted( @@ -1500,7 +1515,8 @@ XdsClient::XdsClient( api_(this, &grpc_xds_client_trace, bootstrap_->node(), &def_pool_, std::move(user_agent_name), std::move(user_agent_version)), work_serializer_(engine), - engine_(std::move(engine)) { + engine_(std::move(engine)), + metrics_reporter_(std::move(metrics_reporter)) { if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) { gpr_log(GPR_INFO, "[xds_client %p] creating xds client", this); } @@ -1575,12 +1591,12 @@ void XdsClient::WatchResource(const XdsResourceType* type, } // Find server to use. const XdsBootstrap::XdsServer* xds_server = nullptr; - absl::string_view authority_name = resource_name->authority; - if (absl::ConsumePrefix(&authority_name, "xdstp:")) { - auto* authority = bootstrap_->LookupAuthority(std::string(authority_name)); + if (resource_name->authority != kOldStyleAuthority) { + auto* authority = + bootstrap_->LookupAuthority(std::string(resource_name->authority)); if (authority == nullptr) { fail(absl::UnavailableError( - absl::StrCat("authority \"", authority_name, + absl::StrCat("authority \"", resource_name->authority, "\" not present in bootstrap config"))); return; } @@ -1738,10 +1754,11 @@ const XdsResourceType* XdsClient::GetResourceTypeLocked( absl::StatusOr XdsClient::ParseXdsResourceName( absl::string_view name, const XdsResourceType* type) { // Old-style names use the empty string for authority. - // authority is prefixed with "old:" to indicate that it's an old-style - // name. + // authority is set to kOldStyleAuthority to indicate that it's an + // old-style name. if (!xds_federation_enabled_ || !absl::StartsWith(name, "xdstp:")) { - return XdsResourceName{"old:", {std::string(name), {}}}; + return XdsResourceName{std::string(kOldStyleAuthority), + {std::string(name), {}}}; } // New style name. Parse URI. auto uri = URI::Parse(name); @@ -1760,14 +1777,14 @@ absl::StatusOr XdsClient::ParseXdsResourceName( URI::QueryParam{std::string(p.first), std::string(p.second)}); } return XdsResourceName{ - absl::StrCat("xdstp:", uri->authority()), + uri->authority(), {std::string(path_parts.second), std::move(query_params)}}; } std::string XdsClient::ConstructFullXdsResourceName( absl::string_view authority, absl::string_view resource_type, const XdsResourceKey& key) { - if (absl::ConsumePrefix(&authority, "xdstp:")) { + if (authority != kOldStyleAuthority) { auto uri = URI::Create("xdstp", std::string(authority), absl::StrCat("/", resource_type, "/", key.id), key.query_params, /*fragment=*/""); @@ -2121,4 +2138,53 @@ void XdsClient::DumpClientConfig( } } +namespace { + +absl::string_view CacheStateForEntry(const XdsApi::ResourceMetadata& metadata, + bool resource_cached) { + switch (metadata.client_status) { + case XdsApi::ResourceMetadata::REQUESTED: + return "requested"; + case XdsApi::ResourceMetadata::DOES_NOT_EXIST: + return "does_not_exist"; + case XdsApi::ResourceMetadata::ACKED: + return "acked"; + case XdsApi::ResourceMetadata::NACKED: + return resource_cached ? "nacked_but_cached" : "nacked"; + } + Crash("unknown resource state"); +} + +} // namespace + +void XdsClient::ReportResourceCounts( + absl::FunctionRef func) { + ResourceCountLabels labels; + for (const auto& a : authority_state_map_) { // authority + labels.xds_authority = a.first; + for (const auto& t : a.second.resource_map) { // type + labels.resource_type = t.first->type_url(); + // Count the number of entries in each state. + std::map counts; + for (const auto& r : t.second) { // resource id + absl::string_view cache_state = + CacheStateForEntry(r.second.meta, r.second.resource != nullptr); + ++counts[cache_state]; + } + // Report the count for each state. + for (const auto& c : counts) { + labels.cache_state = c.first; + func(labels, c.second); + } + } + } +} + +void XdsClient::ReportServerConnections( + absl::FunctionRef func) { + for (const auto& p : xds_channel_map_) { + func(p.second->server_uri(), p.second->status().ok()); + } +} + } // namespace grpc_core diff --git a/src/core/ext/xds/xds_client.h b/src/core/ext/xds/xds_client.h index 038abfe5f29..a9ee6573e82 100644 --- a/src/core/ext/xds/xds_client.h +++ b/src/core/ext/xds/xds_client.h @@ -37,6 +37,7 @@ #include "src/core/ext/xds/xds_api.h" #include "src/core/ext/xds/xds_bootstrap.h" #include "src/core/ext/xds/xds_client_stats.h" +#include "src/core/ext/xds/xds_metrics.h" #include "src/core/ext/xds/xds_resource_type.h" #include "src/core/ext/xds/xds_transport.h" #include "src/core/lib/debug/trace.h" @@ -60,6 +61,9 @@ extern TraceFlag grpc_xds_client_refcount_trace; class XdsClient : public DualRefCounted { public: + // The authority reported for old-style (non-xdstp) resource names. + static constexpr absl::string_view kOldStyleAuthority = "#old"; + class ReadDelayHandle : public RefCounted { public: static RefCountedPtr NoWait() { return nullptr; } @@ -87,6 +91,7 @@ class XdsClient : public DualRefCounted { std::unique_ptr bootstrap, OrphanablePtr transport_factory, std::shared_ptr engine, + std::unique_ptr metrics_reporter, std::string user_agent_name, std::string user_agent_version, Duration resource_request_timeout = Duration::Seconds(15)); ~XdsClient() override; @@ -156,6 +161,8 @@ class XdsClient : public DualRefCounted { } protected: + Mutex* mu() ABSL_LOCK_RETURNED(&mu_) { return &mu_; } + // Dumps the active xDS config to the provided // envoy.service.status.v3.ClientConfig message including the config status // (e.g., CLIENT_REQUESTED, CLIENT_ACKED, CLIENT_NACKED). @@ -163,7 +170,22 @@ class XdsClient : public DualRefCounted { envoy_service_status_v3_ClientConfig* client_config) ABSL_EXCLUSIVE_LOCKS_REQUIRED(&mu_); - Mutex* mu() ABSL_LOCK_RETURNED(&mu_) { return &mu_; } + // Invokes func once for each combination of labels to report the + // resource count for those labels. + struct ResourceCountLabels { + absl::string_view xds_authority; + absl::string_view resource_type; + absl::string_view cache_state; + }; + void ReportResourceCounts( + absl::FunctionRef func) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(&mu_); + + // Invokes func once for each xDS server to report whether the + // connection to that server is working. + void ReportServerConnections( + absl::FunctionRef func) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(&mu_); private: friend testing::XdsClientTestPeer; @@ -221,6 +243,8 @@ class XdsClient : public DualRefCounted { bool delay_unsubscription) ABSL_EXCLUSIVE_LOCKS_REQUIRED(&XdsClient::mu_); + absl::string_view server_uri() const { return server_.server_uri(); } + private: void OnConnectivityFailure(absl::Status status); @@ -327,6 +351,7 @@ class XdsClient : public DualRefCounted { XdsApi api_; WorkSerializer work_serializer_; std::shared_ptr engine_; + std::unique_ptr metrics_reporter_; Mutex mu_; diff --git a/src/core/ext/xds/xds_client_grpc.cc b/src/core/ext/xds/xds_client_grpc.cc index d5e4447cc02..cbfee8e71fd 100644 --- a/src/core/ext/xds/xds_client_grpc.cc +++ b/src/core/ext/xds/xds_client_grpc.cc @@ -49,6 +49,7 @@ #include "src/core/ext/xds/xds_transport.h" #include "src/core/ext/xds/xds_transport_grpc.h" #include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/metrics.h" #include "src/core/lib/debug/trace.h" #include "src/core/lib/event_engine/default_event_engine.h" #include "src/core/lib/gprpp/debug_location.h" @@ -64,8 +65,6 @@ #include "src/core/lib/slice/slice_internal.h" #include "src/core/lib/transport/error_utils.h" -namespace grpc_core { - // If gRPC is built with -DGRPC_XDS_USER_AGENT_NAME_SUFFIX="...", that string // will be appended to the user agent name reported to the xDS server. #ifdef GRPC_XDS_USER_AGENT_NAME_SUFFIX @@ -84,10 +83,90 @@ namespace grpc_core { #define GRPC_XDS_USER_AGENT_VERSION_SUFFIX_STRING "" #endif +namespace grpc_core { + +namespace { + +// Metric labels. +constexpr absl::string_view kMetricLabelXdsServer = "grpc.xds.server"; +constexpr absl::string_view kMetricLabelXdsAuthority = "grpc.xds.authority"; +constexpr absl::string_view kMetricLabelXdsResourceType = + "grpc.xds.resource_type"; +constexpr absl::string_view kMetricLabelXdsCacheState = "grpc.xds.cache_state"; + +const auto kMetricResourceUpdatesValid = + GlobalInstrumentsRegistry::RegisterUInt64Counter( + "grpc.xds_client.resource_updates_valid", + "EXPERIMENTAL. A counter of resources received that were considered " + "valid. The counter will be incremented even for resources that " + "have not changed.", + "{resource}", + {kMetricLabelTarget, kMetricLabelXdsServer, + kMetricLabelXdsResourceType}, + {}, false); + +const auto kMetricResourceUpdatesInvalid = + GlobalInstrumentsRegistry::RegisterUInt64Counter( + "grpc.xds_client.resource_updates_invalid", + "EXPERIMENTAL. A counter of resources received that were considered " + "invalid.", + "{resource}", + {kMetricLabelTarget, kMetricLabelXdsServer, + kMetricLabelXdsResourceType}, + {}, false); + +const auto kMetricConnected = + GlobalInstrumentsRegistry::RegisterCallbackInt64Gauge( + "grpc.xds_client.connected", + "EXPERIMENTAL. Whether or not the xDS client currently has a " + "working ADS stream to the xDS server. For a given server, this " + "will be set to 0 when we have a connectivity failure or when the " + "ADS stream fails without seeing a response message, as per gRFC " + "A57. It will be set to 1 when we receive the first response on " + "an ADS stream.", + "{bool}", {kMetricLabelTarget, kMetricLabelXdsServer}, {}, false); + +const auto kMetricResources = + GlobalInstrumentsRegistry::RegisterCallbackInt64Gauge( + "grpc.xds_client.resources", "EXPERIMENTAL. Number of xDS resources.", + "{resource}", + {kMetricLabelTarget, kMetricLabelXdsAuthority, + kMetricLabelXdsResourceType, kMetricLabelXdsCacheState}, + {}, false); + +} // namespace + +// +// GrpcXdsClient::MetricsReporter +// + +class GrpcXdsClient::MetricsReporter : public XdsMetricsReporter { + public: + explicit MetricsReporter(GrpcXdsClient& xds_client) + : xds_client_(xds_client) {} + + void ReportResourceUpdates(absl::string_view xds_server, + absl::string_view resource_type, + uint64_t num_valid_resources, + uint64_t num_invalid_resources) override { + xds_client_.stats_plugin_group_.AddCounter( + kMetricResourceUpdatesValid, num_valid_resources, + {xds_client_.key_, xds_server, resource_type}, {}); + xds_client_.stats_plugin_group_.AddCounter( + kMetricResourceUpdatesInvalid, num_invalid_resources, + {xds_client_.key_, xds_server, resource_type}, {}); + } + + private: + GrpcXdsClient& xds_client_; +}; + // // GrpcXdsClient // +constexpr absl::string_view GrpcXdsClient::kServerKey; + namespace { Mutex* g_mu = new Mutex; @@ -97,10 +176,6 @@ NoDestruct> g_xds_client_map ABSL_GUARDED_BY(*g_mu); char* g_fallback_bootstrap_config ABSL_GUARDED_BY(*g_mu) = nullptr; -} // namespace - -namespace { - absl::StatusOr GetBootstrapContents(const char* fallback_config) { // First, try GRPC_XDS_BOOTSTRAP env var. auto path = GetEnv("GRPC_XDS_BOOTSTRAP"); @@ -138,19 +213,6 @@ absl::StatusOr GetBootstrapContents(const char* fallback_config) { "not defined"); } -std::vector> GetAllXdsClients() { - MutexLock lock(g_mu); - std::vector> xds_clients; - for (const auto& key_client : *g_xds_client_map) { - auto xds_client = - key_client.second->RefIfNonZero(DEBUG_LOCATION, "DumpAllClientConfigs"); - if (xds_client != nullptr) { - xds_clients.emplace_back(xds_client.TakeAsSubclass()); - } - } - return xds_clients; -} - } // namespace absl::StatusOr> GrpcXdsClient::GetOrCreate( @@ -201,36 +263,20 @@ absl::StatusOr> GrpcXdsClient::GetOrCreate( return xds_client; } -// ABSL_NO_THREAD_SAFETY_ANALYSIS because we have to manually manage locks for -// individual XdsClients and compiler struggles with checking the validity -grpc_slice GrpcXdsClient::DumpAllClientConfigs() - ABSL_NO_THREAD_SAFETY_ANALYSIS { - auto xds_clients = GetAllXdsClients(); - upb::Arena arena; - // Contains strings that should survive till serialization - std::set string_pool; - auto response = envoy_service_status_v3_ClientStatusResponse_new(arena.ptr()); - // We lock each XdsClient mutex till we are done with the serialization to - // ensure that all data referenced from the UPB proto message stays alive. - for (const auto& xds_client : xds_clients) { - auto client_config = - envoy_service_status_v3_ClientStatusResponse_add_config(response, - arena.ptr()); - xds_client->mu()->Lock(); - xds_client->DumpClientConfig(&string_pool, arena.ptr(), client_config); - envoy_service_status_v3_ClientConfig_set_client_scope( - client_config, StdStringToUpbString(xds_client->key())); - } - // Serialize the upb message to bytes - size_t output_length; - char* output = envoy_service_status_v3_ClientStatusResponse_serialize( - response, arena.ptr(), &output_length); - for (const auto& xds_client : xds_clients) { - xds_client->mu()->Unlock(); +namespace { + +GlobalStatsPluginRegistry::StatsPluginGroup GetStatsPluginGroupForKey( + absl::string_view key) { + if (key == GrpcXdsClient::kServerKey) { + return GlobalStatsPluginRegistry::GetAllStatsPlugins(); } - return grpc_slice_from_cpp_string(std::string(output, output_length)); + // TODO(roth): How do we set the authority here? + StatsPlugin::ChannelScope scope(key, ""); + return GlobalStatsPluginRegistry::GetStatsPluginsForChannel(scope); } +} // namespace + GrpcXdsClient::GrpcXdsClient( absl::string_view key, std::unique_ptr bootstrap, const ChannelArgs& args, @@ -238,6 +284,7 @@ GrpcXdsClient::GrpcXdsClient( : XdsClient( std::move(bootstrap), std::move(transport_factory), grpc_event_engine::experimental::GetDefaultEventEngine(), + std::make_unique(*this), absl::StrCat("gRPC C-core ", GPR_PLATFORM_STRING, GRPC_XDS_USER_AGENT_NAME_SUFFIX_STRING), absl::StrCat("C-core ", grpc_version_string(), @@ -250,15 +297,22 @@ GrpcXdsClient::GrpcXdsClient( key_(key), certificate_provider_store_(MakeOrphanable( static_cast(this->bootstrap()) - .certificate_providers())) {} + .certificate_providers())), + stats_plugin_group_(GetStatsPluginGroupForKey(key_)), + registered_metric_callback_(stats_plugin_group_.RegisterCallback( + [this](CallbackMetricReporter& reporter) { + ReportCallbackMetrics(reporter); + }, + {kMetricConnected, kMetricResources})) {} void GrpcXdsClient::Orphan() { + registered_metric_callback_.reset(); + XdsClient::Orphan(); MutexLock lock(g_mu); auto it = g_xds_client_map->find(key_); if (it != g_xds_client_map->end() && it->second == this) { g_xds_client_map->erase(it); } - XdsClient::Orphan(); } grpc_pollset_set* GrpcXdsClient::interested_parties() const { @@ -266,6 +320,66 @@ grpc_pollset_set* GrpcXdsClient::interested_parties() const { ->interested_parties(); } +namespace { + +std::vector> GetAllXdsClients() { + MutexLock lock(g_mu); + std::vector> xds_clients; + for (const auto& key_client : *g_xds_client_map) { + auto xds_client = + key_client.second->RefIfNonZero(DEBUG_LOCATION, "DumpAllClientConfigs"); + if (xds_client != nullptr) { + xds_clients.emplace_back(xds_client.TakeAsSubclass()); + } + } + return xds_clients; +} + +} // namespace + +// ABSL_NO_THREAD_SAFETY_ANALYSIS because we have to manually manage locks for +// individual XdsClients and compiler struggles with checking the validity +grpc_slice GrpcXdsClient::DumpAllClientConfigs() + ABSL_NO_THREAD_SAFETY_ANALYSIS { + auto xds_clients = GetAllXdsClients(); + upb::Arena arena; + // Contains strings that should survive till serialization + std::set string_pool; + auto response = envoy_service_status_v3_ClientStatusResponse_new(arena.ptr()); + // We lock each XdsClient mutex till we are done with the serialization to + // ensure that all data referenced from the UPB proto message stays alive. + for (const auto& xds_client : xds_clients) { + auto client_config = + envoy_service_status_v3_ClientStatusResponse_add_config(response, + arena.ptr()); + xds_client->mu()->Lock(); + xds_client->DumpClientConfig(&string_pool, arena.ptr(), client_config); + envoy_service_status_v3_ClientConfig_set_client_scope( + client_config, StdStringToUpbString(xds_client->key())); + } + // Serialize the upb message to bytes + size_t output_length; + char* output = envoy_service_status_v3_ClientStatusResponse_serialize( + response, arena.ptr(), &output_length); + for (const auto& xds_client : xds_clients) { + xds_client->mu()->Unlock(); + } + return grpc_slice_from_cpp_string(std::string(output, output_length)); +} + +void GrpcXdsClient::ReportCallbackMetrics(CallbackMetricReporter& reporter) { + MutexLock lock(mu()); + ReportResourceCounts([&](const ResourceCountLabels& labels, uint64_t count) { + reporter.Report( + kMetricResources, count, + {key_, labels.xds_authority, labels.resource_type, labels.cache_state}, + {}); + }); + ReportServerConnections([&](absl::string_view xds_server, bool connected) { + reporter.Report(kMetricConnected, connected, {key_, xds_server}, {}); + }); +} + namespace internal { void SetXdsChannelArgsForTest(grpc_channel_args* args) { diff --git a/src/core/ext/xds/xds_client_grpc.h b/src/core/ext/xds/xds_client_grpc.h index 72df525ab6e..ba6377a5c2d 100644 --- a/src/core/ext/xds/xds_client_grpc.h +++ b/src/core/ext/xds/xds_client_grpc.h @@ -31,6 +31,7 @@ #include "src/core/ext/xds/xds_client.h" #include "src/core/ext/xds/xds_transport.h" #include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/metrics.h" #include "src/core/lib/gpr/useful.h" #include "src/core/lib/gprpp/orphanable.h" #include "src/core/lib/gprpp/ref_counted_ptr.h" @@ -41,13 +42,13 @@ namespace grpc_core { class GrpcXdsClient : public XdsClient { public: + // The key to pass to GetOrCreate() for gRPC servers. + static constexpr absl::string_view kServerKey = "#server"; + // Factory function to get or create the global XdsClient instance. static absl::StatusOr> GetOrCreate( absl::string_view key, const ChannelArgs& args, const char* reason); - // Builds ClientStatusResponse containing all resources from all XdsClients - static grpc_slice DumpAllClientConfigs(); - // Do not instantiate directly -- use GetOrCreate() instead. // TODO(roth): The transport factory is injectable here to support // tests that want to use a fake transport factory with code that @@ -83,9 +84,18 @@ class GrpcXdsClient : public XdsClient { absl::string_view key() const { return key_; } + // Builds ClientStatusResponse containing all resources from all XdsClients + static grpc_slice DumpAllClientConfigs(); + private: + class MetricsReporter; + + void ReportCallbackMetrics(CallbackMetricReporter& reporter); + std::string key_; OrphanablePtr certificate_provider_store_; + GlobalStatsPluginRegistry::StatsPluginGroup stats_plugin_group_; + std::unique_ptr registered_metric_callback_; }; namespace internal { diff --git a/src/core/ext/xds/xds_metrics.h b/src/core/ext/xds/xds_metrics.h new file mode 100644 index 00000000000..f30e9dbe962 --- /dev/null +++ b/src/core/ext/xds/xds_metrics.h @@ -0,0 +1,39 @@ +// +// 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_EXT_XDS_XDS_METRICS_H +#define GRPC_SRC_CORE_EXT_XDS_XDS_METRICS_H + +#include + +#include "absl/strings/string_view.h" + +namespace grpc_core { + +// An interface for XdsClient to report metrics. +class XdsMetricsReporter { + public: + virtual ~XdsMetricsReporter() = default; + + virtual void ReportResourceUpdates(absl::string_view xds_server, + absl::string_view resource_type, + uint64_t num_valid, + uint64_t num_invalid) = 0; +}; + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_EXT_XDS_XDS_METRICS_H diff --git a/src/core/ext/xds/xds_server_config_fetcher.cc b/src/core/ext/xds/xds_server_config_fetcher.cc index 6e406843a64..07747f54d50 100644 --- a/src/core/ext/xds/xds_server_config_fetcher.cc +++ b/src/core/ext/xds/xds_server_config_fetcher.cc @@ -94,8 +94,6 @@ namespace { using ReadDelayHandle = XdsClient::ReadDelayHandle; -constexpr absl::string_view kServerXdsClientKey = "#server"; - TraceFlag grpc_xds_server_config_fetcher_trace(false, "xds_server_config_fetcher"); @@ -1374,7 +1372,8 @@ grpc_server_config_fetcher* grpc_server_config_fetcher_xds_create( "update=%p, user_data=%p}, args=%p)", 3, (notifier.on_serving_status_update, notifier.user_data, args)); auto xds_client = grpc_core::GrpcXdsClient::GetOrCreate( - grpc_core::kServerXdsClientKey, channel_args, "XdsServerConfigFetcher"); + grpc_core::GrpcXdsClient::kServerKey, channel_args, + "XdsServerConfigFetcher"); if (!xds_client.ok()) { gpr_log(GPR_ERROR, "Failed to create xds client: %s", xds_client.status().ToString().c_str()); diff --git a/src/core/lib/channel/metrics.cc b/src/core/lib/channel/metrics.cc index 5a038db187b..813eeb02cd4 100644 --- a/src/core/lib/channel/metrics.cc +++ b/src/core/lib/channel/metrics.cc @@ -311,6 +311,16 @@ void GlobalStatsPluginRegistry::RegisterStatsPlugin( plugins_->push_back(std::move(plugin)); } +GlobalStatsPluginRegistry::StatsPluginGroup +GlobalStatsPluginRegistry::GetAllStatsPlugins() { + MutexLock lock(&*mutex_); + StatsPluginGroup group; + for (const auto& plugin : *plugins_) { + group.push_back(plugin); + } + return group; +} + GlobalStatsPluginRegistry::StatsPluginGroup GlobalStatsPluginRegistry::GetStatsPluginsForChannel( const StatsPlugin::ChannelScope& scope) { diff --git a/src/core/lib/channel/metrics.h b/src/core/lib/channel/metrics.h index a93f0037cb3..c4315e721ee 100644 --- a/src/core/lib/channel/metrics.h +++ b/src/core/lib/channel/metrics.h @@ -310,8 +310,10 @@ class GlobalStatsPluginRegistry { }; static void RegisterStatsPlugin(std::shared_ptr plugin); - // The following two functions can be invoked to get a StatsPluginGroup for + + // The following functions can be invoked to get a StatsPluginGroup for // a specified scope. + static StatsPluginGroup GetAllStatsPlugins(); static StatsPluginGroup GetStatsPluginsForChannel( const StatsPlugin::ChannelScope& scope); // TODO(yijiem): Implement this. diff --git a/test/core/xds/BUILD b/test/core/xds/BUILD index 26030f839f5..581db547721 100644 --- a/test/core/xds/BUILD +++ b/test/core/xds/BUILD @@ -162,6 +162,18 @@ grpc_cc_library( ], ) +grpc_cc_library( + name = "xds_client_test_peer", + hdrs = ["xds_client_test_peer.h"], + external_deps = [ + "absl/functional:function_ref", + ], + language = "C++", + deps = [ + "//:xds_client", + ], +) + grpc_cc_test( name = "xds_client_test", srcs = ["xds_client_test.cc"], @@ -171,6 +183,7 @@ grpc_cc_test( uses_event_engine = True, uses_polling = False, deps = [ + ":xds_client_test_peer", ":xds_transport_fake", "//:xds_client", "//src/proto/grpc/testing/xds/v3:discovery_proto", @@ -192,6 +205,7 @@ grpc_proto_fuzzer( uses_event_engine = False, uses_polling = False, deps = [ + ":xds_client_test_peer", ":xds_transport_fake", "//src/core:grpc_xds_client", "//test/core/util:grpc_test_util", diff --git a/test/core/xds/xds_client_fuzzer.cc b/test/core/xds/xds_client_fuzzer.cc index 4d6994a1381..5b9dc709969 100644 --- a/test/core/xds/xds_client_fuzzer.cc +++ b/test/core/xds/xds_client_fuzzer.cc @@ -43,30 +43,11 @@ #include "src/libfuzzer/libfuzzer_macro.h" #include "src/proto/grpc/testing/xds/v3/discovery.pb.h" #include "test/core/xds/xds_client_fuzzer.pb.h" +#include "test/core/xds/xds_client_test_peer.h" #include "test/core/xds/xds_transport_fake.h" namespace grpc_core { -namespace testing { - -class XdsClientTestPeer { - public: - explicit XdsClientTestPeer(XdsClient* xds_client) : xds_client_(xds_client) {} - - void TestDumpClientConfig() { - upb::Arena arena; - auto client_config = envoy_service_status_v3_ClientConfig_new(arena.ptr()); - std::set string_pool; - MutexLock lock(xds_client_->mu()); - xds_client_->DumpClientConfig(&string_pool, arena.ptr(), client_config); - } - - private: - XdsClient* xds_client_; -}; - -} // namespace testing - class Fuzzer { public: explicit Fuzzer(absl::string_view bootstrap_json) { @@ -84,8 +65,8 @@ class Fuzzer { transport_factory_ = transport_factory.get(); xds_client_ = MakeRefCounted( std::move(*bootstrap), std::move(transport_factory), - grpc_event_engine::experimental::GetDefaultEventEngine(), "foo agent", - "foo version"); + grpc_event_engine::experimental::GetDefaultEventEngine(), + /*metrics_reporter=*/nullptr, "foo agent", "foo version"); } void Act(const xds_client_fuzzer::Action& action) { @@ -135,6 +116,28 @@ class Fuzzer { case xds_client_fuzzer::Action::kDumpCsdsData: testing::XdsClientTestPeer(xds_client_.get()).TestDumpClientConfig(); break; + case xds_client_fuzzer::Action::kReportResourceCounts: + testing::XdsClientTestPeer(xds_client_.get()) + .TestReportResourceCounts( + [](const testing::XdsClientTestPeer::ResourceCountLabels& + labels, + uint64_t count) { + gpr_log(GPR_INFO, + "xds_authority=\"%s\", resource_type=\"%s\", " + "cache_state=\"%s\" count=%" PRIu64, + std::string(labels.xds_authority).c_str(), + std::string(labels.resource_type).c_str(), + std::string(labels.cache_state).c_str(), count); + }); + break; + case xds_client_fuzzer::Action::kReportServerConnections: + testing::XdsClientTestPeer(xds_client_.get()) + .TestReportServerConnections( + [](absl::string_view xds_server, bool connected) { + gpr_log(GPR_INFO, "xds_server=\"%s\" connected=%d", + std::string(xds_server).c_str(), connected); + }); + break; case xds_client_fuzzer::Action::kTriggerConnectionFailure: TriggerConnectionFailure( action.trigger_connection_failure().authority(), diff --git a/test/core/xds/xds_client_fuzzer.proto b/test/core/xds/xds_client_fuzzer.proto index 2738e76b3d4..6d1274eb9cc 100644 --- a/test/core/xds/xds_client_fuzzer.proto +++ b/test/core/xds/xds_client_fuzzer.proto @@ -59,6 +59,10 @@ message StopWatch { message DumpCsdsData {} +message ReportResourceCounts {} + +message ReportServerConnections {} + // // interactions with fake transport // @@ -96,12 +100,15 @@ message SendStatusToClient { Status status = 2; } +// Next free field: 10 message Action { oneof action_type { // interactions with XdsClient API StartWatch start_watch = 1; StopWatch stop_watch = 2; DumpCsdsData dump_csds_data = 3; + ReportResourceCounts report_resource_counts = 8; + ReportServerConnections report_server_connections = 9; // interactions with fake transport TriggerConnectionFailure trigger_connection_failure = 4; ReadMessageFromClient read_message_from_client = 5; diff --git a/test/core/xds/xds_client_test.cc b/test/core/xds/xds_client_test.cc index 741e2022362..b6036e35e96 100644 --- a/test/core/xds/xds_client_test.cc +++ b/test/core/xds/xds_client_test.cc @@ -59,6 +59,7 @@ #include "src/proto/grpc/testing/xds/v3/discovery.pb.h" #include "test/core/util/scoped_env_var.h" #include "test/core/util/test_config.h" +#include "test/core/xds/xds_client_test_peer.h" #include "test/core/xds/xds_transport_fake.h" // IWYU pragma: no_include @@ -594,6 +595,65 @@ class XdsClientTest : public ::testing::Test { DiscoveryResponse response_; }; + class MetricsReporter : public XdsMetricsReporter { + public: + using ResourceUpdateMap = std::map< + std::pair, + uint64_t>; + + const ResourceUpdateMap& resource_updates_valid() const { + return resource_updates_valid_; + } + const ResourceUpdateMap& resource_updates_invalid() const { + return resource_updates_invalid_; + } + + private: + void ReportResourceUpdates(absl::string_view xds_server, + absl::string_view resource_type, + uint64_t num_resources_valid, + uint64_t num_resources_invalid) override { + auto key = + std::make_pair(std::string(xds_server), std::string(resource_type)); + if (num_resources_valid > 0) { + resource_updates_valid_[key] += num_resources_valid; + } + if (num_resources_invalid > 0) { + resource_updates_invalid_[key] += num_resources_invalid; + } + } + + ResourceUpdateMap resource_updates_valid_; + ResourceUpdateMap resource_updates_invalid_; + }; + + using ResourceCounts = + std::vector>; + ResourceCounts GetResourceCounts() { + ResourceCounts resource_counts; + XdsClientTestPeer(xds_client_.get()) + .TestReportResourceCounts( + [&](const XdsClientTestPeer::ResourceCountLabels& labels, + uint64_t count) { + resource_counts.emplace_back(labels, count); + }); + return resource_counts; + } + + using ServerConnectionMap = std::map; + ServerConnectionMap GetServerConnections() { + ServerConnectionMap server_connection_map; + XdsClientTestPeer(xds_client_.get()) + .TestReportServerConnections( + [&](absl::string_view xds_server, bool connected) { + std::string server(xds_server); + EXPECT_EQ(server_connection_map.find(server), + server_connection_map.end()); + server_connection_map[std::move(server)] = connected; + }); + return server_connection_map; + } + // Sets transport_factory_ and initializes xds_client_ with the // specified bootstrap config. void InitXdsClient( @@ -603,10 +663,13 @@ class XdsClientTest : public ::testing::Test { []() { FAIL() << "Multiple concurrent reads"; }); transport_factory_ = transport_factory->Ref().TakeAsSubclass(); + auto metrics_reporter = std::make_unique(); + metrics_reporter_ = metrics_reporter.get(); xds_client_ = MakeRefCounted( bootstrap_builder.Build(), std::move(transport_factory), - grpc_event_engine::experimental::GetDefaultEventEngine(), "foo agent", - "foo version", resource_request_timeout * grpc_test_slowdown_factor()); + grpc_event_engine::experimental::GetDefaultEventEngine(), + std::move(metrics_reporter), "foo agent", "foo version", + resource_request_timeout * grpc_test_slowdown_factor()); } // Starts and cancels a watch for a Foo resource. @@ -764,12 +827,45 @@ class XdsClientTest : public ::testing::Test { RefCountedPtr transport_factory_; RefCountedPtr xds_client_; + MetricsReporter* metrics_reporter_ = nullptr; }; +MATCHER_P3(ResourceCountLabelsEq, xds_authority, resource_type, cache_state, + "equals ResourceCountLabels") { + bool ok = true; + ok &= ::testing::ExplainMatchResult(xds_authority, arg.xds_authority, + result_listener); + ok &= ::testing::ExplainMatchResult(resource_type, arg.resource_type, + result_listener); + ok &= ::testing::ExplainMatchResult(cache_state, arg.cache_state, + result_listener); + return ok; +} + TEST_F(XdsClientTest, BasicWatch) { InitXdsClient(); + // Metrics should initially be empty. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), ::testing::ElementsAre()); + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre()); // Start a watch for "foo1". auto watcher = StartFooWatch("foo1"); + // Check metrics. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", true))); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 1))); // Watcher should initially not see any resource reported. EXPECT_FALSE(watcher->HasEvent()); // XdsClient should have created an ADS stream. @@ -795,6 +891,22 @@ TEST_F(XdsClientTest, BasicWatch) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", true))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -805,6 +917,16 @@ TEST_F(XdsClientTest, BasicWatch) { // Cancel watch. CancelFooWatch(watcher.get(), "foo1"); EXPECT_TRUE(stream->Orphaned()); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), ::testing::ElementsAre()); + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre()); } TEST_F(XdsClientTest, UpdateFromServer) { @@ -836,6 +958,20 @@ TEST_F(XdsClientTest, UpdateFromServer) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -855,6 +991,20 @@ TEST_F(XdsClientTest, UpdateFromServer) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 9); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 2))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -896,6 +1046,20 @@ TEST_F(XdsClientTest, MultipleWatchersForSameResource) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -929,6 +1093,20 @@ TEST_F(XdsClientTest, MultipleWatchersForSameResource) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 9); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 2))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -951,6 +1129,17 @@ TEST_F(XdsClientTest, SubscribeToMultipleResources) { auto watcher = StartFooWatch("foo1"); // Watcher should initially not see any resource reported. EXPECT_FALSE(watcher->HasEvent()); + // Check metrics. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 1))); // XdsClient should have created an ADS stream. auto stream = WaitForAdsStream(); ASSERT_TRUE(stream != nullptr); @@ -974,6 +1163,20 @@ TEST_F(XdsClientTest, SubscribeToMultipleResources) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -983,6 +1186,19 @@ TEST_F(XdsClientTest, SubscribeToMultipleResources) { /*resource_names=*/{"foo1"}); // Start a watch for "foo2". auto watcher2 = StartFooWatch("foo2"); + // Check metric data. + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre( + ::testing::Pair(ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1), + ::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 1))); // XdsClient should have sent a subscription request on the ADS stream. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1002,6 +1218,20 @@ TEST_F(XdsClientTest, SubscribeToMultipleResources) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo2"); EXPECT_EQ(resource->value, 7); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 2))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 2))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1011,6 +1241,13 @@ TEST_F(XdsClientTest, SubscribeToMultipleResources) { /*resource_names=*/{"foo1", "foo2"}); // Cancel watch for "foo1". CancelFooWatch(watcher.get(), "foo1"); + // Check metric data. + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should send an unsubscription request. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1079,6 +1316,20 @@ TEST_F(XdsClientTest, UpdateContainsOnlyChangedResource) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo2"); EXPECT_EQ(resource->value, 7); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 2))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 2))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1098,6 +1349,20 @@ TEST_F(XdsClientTest, UpdateContainsOnlyChangedResource) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 9); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 3))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 2))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1122,6 +1387,17 @@ TEST_F(XdsClientTest, ResourceValidationFailure) { InitXdsClient(); // Start a watch for "foo1". auto watcher = StartFooWatch("foo1"); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 1))); // Watcher should initially not see any resource reported. EXPECT_FALSE(watcher->HasEvent()); // XdsClient should have created an ADS stream. @@ -1151,6 +1427,20 @@ TEST_F(XdsClientTest, ResourceValidationFailure) { "invalid resource: INVALID_ARGUMENT: errors validating JSON: " "[field:value error:is not a number] (node ID:xds_client_test)") << *error; + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "nacked"), + 1))); // XdsClient should NACK the update. // Note that version_info is not populated in the request. request = WaitForRequest(stream.get()); @@ -1190,6 +1480,23 @@ TEST_F(XdsClientTest, ResourceValidationFailure) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 9); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1209,6 +1516,17 @@ TEST_F(XdsClientTest, ResourceValidationFailureMultipleResources) { auto watcher = StartFooWatch("foo1"); // Watcher should initially not see any resource reported. EXPECT_FALSE(watcher->HasEvent()); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 1))); // XdsClient should have created an ADS stream. auto stream = WaitForAdsStream(); ASSERT_TRUE(stream != nullptr); @@ -1222,6 +1540,17 @@ TEST_F(XdsClientTest, ResourceValidationFailureMultipleResources) { CheckRequestNode(*request); // Should be present on the first request. // Before the server responds, add a watch for another resource. auto watcher2 = StartFooWatch("foo2"); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 2))); // Client should send another request. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1231,6 +1560,17 @@ TEST_F(XdsClientTest, ResourceValidationFailureMultipleResources) { /*resource_names=*/{"foo1", "foo2"}); // Add a watch for a third resource. auto watcher3 = StartFooWatch("foo3"); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 3))); // Client should send another request. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1240,6 +1580,17 @@ TEST_F(XdsClientTest, ResourceValidationFailureMultipleResources) { /*resource_names=*/{"foo1", "foo2", "foo3"}); // Add a watch for a fourth resource. auto watcher4 = StartFooWatch("foo4"); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 4))); // Client should send another request. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1298,6 +1649,36 @@ TEST_F(XdsClientTest, ResourceValidationFailureMultipleResources) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo4"); EXPECT_EQ(resource->value, 5); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 5))); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre( + // foo4 + ::testing::Pair(ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1), + // foo1 and foo3 + ::testing::Pair(ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "nacked"), + 2), + // did not recognize response for foo2 + ::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 1))); // XdsClient should NACK the update. // There was one good resource, so the version will be updated. request = WaitForRequest(stream.get()); @@ -1363,6 +1744,20 @@ TEST_F(XdsClientTest, ResourceValidationFailureForCachedResource) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1386,6 +1781,23 @@ TEST_F(XdsClientTest, ResourceValidationFailureForCachedResource) { "invalid resource: INVALID_ARGUMENT: errors validating JSON: " "[field:value error:is not a number] (node ID:xds_client_test)") << *error; + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "nacked_but_cached"), + 1))); // XdsClient should NACK the update. // Note that version_info is set to the previous version in this request, // because there were no valid resources in it. @@ -1450,6 +1862,26 @@ TEST_F(XdsClientTest, WildcardCapableResponseWithEmptyResource) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "wc1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT( + metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsWildcardCapableResourceType::Get()->type_url()), + 1))); + EXPECT_THAT( + metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsWildcardCapableResourceType::Get()->type_url()), + 1))); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsWildcardCapableResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should NACK the update. // There was one good resource, so the version will be updated. request = WaitForRequest(stream.get()); @@ -1500,6 +1932,22 @@ TEST_F(XdsClientTest, ResourceDeletion) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "wc1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT( + metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsWildcardCapableResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsWildcardCapableResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1516,6 +1964,22 @@ TEST_F(XdsClientTest, ResourceDeletion) { .Serialize()); // Watcher should see the does-not-exist event. EXPECT_TRUE(watcher->WaitForDoesNotExist(absl::Seconds(1))); + // Check metric data. + EXPECT_THAT( + metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsWildcardCapableResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsWildcardCapableResourceType::Get()->type_url(), + "does_not_exist"), + 1))); // Start a new watcher for the same resource. It should immediately // receive the same does-not-exist notification. auto watcher2 = StartWildcardCapableWatch("wc1"); @@ -1543,6 +2007,22 @@ TEST_F(XdsClientTest, ResourceDeletion) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "wc1"); EXPECT_EQ(resource->value, 7); + // Check metric data. + EXPECT_THAT( + metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsWildcardCapableResourceType::Get()->type_url()), + 2))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsWildcardCapableResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1587,6 +2067,22 @@ TEST_F(XdsClientTest, ResourceDeletionIgnoredWhenConfigured) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "wc1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT( + metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsWildcardCapableResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsWildcardCapableResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1604,6 +2100,22 @@ TEST_F(XdsClientTest, ResourceDeletionIgnoredWhenConfigured) { // Watcher should not see any update, since we should have ignored the // deletion. EXPECT_TRUE(watcher->ExpectNoEvent(absl::Seconds(1))); + // Check metric data. + EXPECT_THAT( + metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsWildcardCapableResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsWildcardCapableResourceType::Get()->type_url(), "acked"), + 1))); // Start a new watcher for the same resource. It should immediately // receive the cached resource. auto watcher2 = StartWildcardCapableWatch("wc1"); @@ -1634,6 +2146,22 @@ TEST_F(XdsClientTest, ResourceDeletionIgnoredWhenConfigured) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "wc1"); EXPECT_EQ(resource->value, 7); + // Check metric data. + EXPECT_THAT( + metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsWildcardCapableResourceType::Get()->type_url()), + 2))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsWildcardCapableResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1649,8 +2177,13 @@ TEST_F(XdsClientTest, ResourceDeletionIgnoredWhenConfigured) { TEST_F(XdsClientTest, StreamClosedByServer) { InitXdsClient(); + // Metrics should initially be empty. + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre()); // Start a watch for "foo1". auto watcher = StartFooWatch("foo1"); + // Check metric data. + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", true))); // Watcher should initially not see any resource reported. EXPECT_FALSE(watcher->HasEvent()); // XdsClient should have created an ADS stream. @@ -1689,6 +2222,9 @@ TEST_F(XdsClientTest, StreamClosedByServer) { // response on the stream before it failed. // Stream should be orphaned. EXPECT_TRUE(stream->Orphaned()); + // Check metric data. + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", true))); // XdsClient should create a new stream. stream = WaitForAdsStream(); ASSERT_TRUE(stream != nullptr); @@ -1734,8 +2270,13 @@ TEST_F(XdsClientTest, StreamClosedByServer) { TEST_F(XdsClientTest, StreamClosedByServerWithoutSeeingResponse) { InitXdsClient(); + // Metrics should initially be empty. + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre()); // Start a watch for "foo1". auto watcher = StartFooWatch("foo1"); + // Check metric data. + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", true))); // Watcher should initially not see any resource reported. EXPECT_FALSE(watcher->HasEvent()); // XdsClient should have created an ADS stream. @@ -1751,6 +2292,9 @@ TEST_F(XdsClientTest, StreamClosedByServerWithoutSeeingResponse) { CheckRequestNode(*request); // Should be present on the first request. // Server closes the stream without sending a response. stream->MaybeSendStatusToClient(absl::UnavailableError("ugh")); + // Check metric data. + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", false))); // XdsClient should report an error to the watcher. auto error = watcher->WaitForNextError(); ASSERT_TRUE(error.has_value()); @@ -1771,6 +2315,9 @@ TEST_F(XdsClientTest, StreamClosedByServerWithoutSeeingResponse) { /*error_detail=*/absl::OkStatus(), /*resource_names=*/{"foo1"}); CheckRequestNode(*request); // Should be present on the first request. + // Connection still reported as unhappy until we get a response. + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", false))); // Server now sends the requested resource. stream->SendMessageToClient( ResponseBuilder(XdsFooResourceType::Get()->type_url()) @@ -1783,6 +2330,9 @@ TEST_F(XdsClientTest, StreamClosedByServerWithoutSeeingResponse) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Connection now reported as happy. + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", true))); // XdsClient sends an ACK. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1802,8 +2352,13 @@ TEST_F(XdsClientTest, ConnectionFails) { // Tell transport to let us manually trigger completion of the // send_message ops to XdsClient. transport_factory_->SetAutoCompleteMessagesFromClient(false); + // Metrics should initially be empty. + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre()); // Start a watch for "foo1". auto watcher = StartFooWatch("foo1"); + // Check metric data. + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", true))); // Watcher should initially not see any resource reported. EXPECT_FALSE(watcher->HasEvent()); // XdsClient should have created an ADS stream. @@ -1828,6 +2383,9 @@ TEST_F(XdsClientTest, ConnectionFails) { "xDS channel for server default_xds_server: " "connection failed (node ID:xds_client_test)") << *error; + // Connection reported as unhappy. + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", false))); // We should not see a resource-does-not-exist event, because the // timer should not be running while the channel is disconnected. EXPECT_TRUE(watcher->ExpectNoEvent(absl::Seconds(4))); @@ -1853,6 +2411,9 @@ TEST_F(XdsClientTest, ConnectionFails) { .set_nonce("A") .AddFooResource(XdsFooResource("foo1", 6)) .Serialize()); + // Connection now reported as happy. + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", true))); // XdsClient should have delivered the response to the watchers. auto resource = watcher->WaitForNextResource(); ASSERT_NE(resource, nullptr); @@ -1882,6 +2443,17 @@ TEST_F(XdsClientTest, ResourceDoesNotExistUponTimeout) { auto watcher = StartFooWatch("foo1"); // Watcher should initially not see any resource reported. EXPECT_FALSE(watcher->HasEvent()); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 1))); // XdsClient should have created an ADS stream. auto stream = WaitForAdsStream(); ASSERT_TRUE(stream != nullptr); @@ -1896,6 +2468,17 @@ TEST_F(XdsClientTest, ResourceDoesNotExistUponTimeout) { // Do not send a response, but wait for the resource to be reported as // not existing. EXPECT_TRUE(watcher->WaitForDoesNotExist(absl::Seconds(5))); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "does_not_exist"), + 1))); // Start a new watcher for the same resource. It should immediately // receive the same does-not-exist notification. auto watcher2 = StartFooWatch("foo1"); @@ -1916,6 +2499,20 @@ TEST_F(XdsClientTest, ResourceDoesNotExistUponTimeout) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -1932,8 +2529,25 @@ TEST_F(XdsClientTest, ResourceDoesNotExistUponTimeout) { TEST_F(XdsClientTest, ResourceDoesNotExistAfterStreamRestart) { // Lower resources-does-not-exist timeout so test finishes faster. InitXdsClient(FakeXdsBootstrap::Builder(), Duration::Seconds(3)); + // Metrics should initially be empty. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), ::testing::ElementsAre()); // Start a watch for "foo1". auto watcher = StartFooWatch("foo1"); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 1))); // Watcher should initially not see any resource reported. EXPECT_FALSE(watcher->HasEvent()); // XdsClient should have created an ADS stream. @@ -1958,6 +2572,17 @@ TEST_F(XdsClientTest, ResourceDoesNotExistAfterStreamRestart) { "with no responses received; status: UNAVAILABLE: ugh " "(node ID:xds_client_test)") << *error; + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 1))); // XdsClient should create a new stream. stream = WaitForAdsStream(); ASSERT_TRUE(stream != nullptr); @@ -1972,6 +2597,17 @@ TEST_F(XdsClientTest, ResourceDoesNotExistAfterStreamRestart) { // Server does NOT send a response immediately. // Client should receive a resource does-not-exist. ASSERT_TRUE(watcher->WaitForDoesNotExist(absl::Seconds(4))); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "does_not_exist"), + 1))); // Server now sends the requested resource. stream->SendMessageToClient( ResponseBuilder(XdsFooResourceType::Get()->type_url()) @@ -1984,6 +2620,20 @@ TEST_F(XdsClientTest, ResourceDoesNotExistAfterStreamRestart) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient sends an ACK. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -2022,12 +2672,26 @@ TEST_F(XdsClientTest, DoesNotExistTimerNotStartedUntilSendCompletes) { // We should not see a resource-does-not-exist event, because the // timer should not be running while the channel is disconnected. EXPECT_TRUE(watcher->ExpectNoEvent(absl::Seconds(4))); + // Check metric data. + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 1))); // The ADS stream uses wait_for_ready inside the XdsTransport interface, // so when the channel connects, the already-started stream will proceed. stream->CompleteSendMessageFromClient(); // Server does NOT send a response. // Watcher should see a does-not-exist event. EXPECT_TRUE(watcher->WaitForDoesNotExist(absl::Seconds(4))); + // Check metric data. + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "does_not_exist"), + 1))); // Now server sends a response. stream->SendMessageToClient( ResponseBuilder(XdsFooResourceType::Get()->type_url()) @@ -2040,6 +2704,13 @@ TEST_F(XdsClientTest, DoesNotExistTimerNotStartedUntilSendCompletes) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -2093,6 +2764,20 @@ TEST_F(XdsClientTest, ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -2105,6 +2790,19 @@ TEST_F(XdsClientTest, auto watcher2 = StartFooWatch("foo2"); // Watcher should initially not see any resource reported. EXPECT_FALSE(watcher2->HasEvent()); + // Check metric data. + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre( + ::testing::Pair(ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1), + ::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 1))); // XdsClient sends a request to subscribe to the new resource. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -2116,7 +2814,19 @@ TEST_F(XdsClientTest, // complete. // Unsubscribe from foo1 and then re-subscribe to it. CancelFooWatch(watcher.get(), "foo1"); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 1))); watcher = StartFooWatch("foo1"); + EXPECT_THAT(GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), + "requested"), + 2))); // Now send a response from the server containing both foo1 and foo2. stream->SendMessageToClient( ResponseBuilder(XdsFooResourceType::Get()->type_url()) @@ -2137,6 +2847,20 @@ TEST_F(XdsClientTest, ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo2"); EXPECT_EQ(resource->value, 7); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 3))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 2))); // Now we finally tell XdsClient that its previous send_message op is // complete. stream->CompleteSendMessageFromClient(); @@ -2189,6 +2913,20 @@ TEST_F(XdsClientTest, DoNotSendDoesNotExistForCachedResource) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -2216,6 +2954,20 @@ TEST_F(XdsClientTest, DoNotSendDoesNotExistForCachedResource) { // resource was already cached, so the server can optimize by not // resending it. EXPECT_TRUE(watcher->ExpectNoEvent(absl::Seconds(4))); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // Now server sends a response. stream->SendMessageToClient( ResponseBuilder(XdsFooResourceType::Get()->type_url()) @@ -2225,6 +2977,22 @@ TEST_F(XdsClientTest, DoNotSendDoesNotExistForCachedResource) { .Serialize()); // Watcher will not see any update, since the resource is unchanged. EXPECT_TRUE(watcher->ExpectNoEvent(absl::Seconds(1))); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 2))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", true))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -2267,6 +3035,20 @@ TEST_F(XdsClientTest, ResourceWrappedInResourceMessage) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -2308,6 +3090,20 @@ TEST_F(XdsClientTest, MultipleResourceTypes) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -2338,6 +3134,30 @@ TEST_F(XdsClientTest, MultipleResourceTypes) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource2->name, "bar1"); EXPECT_EQ(resource2->value, "whee"); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre( + ::testing::Pair( + ::testing::Pair("default_xds_server", + XdsBarResourceType::Get()->type_url()), + 1), + ::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::UnorderedElementsAre( + ::testing::Pair(ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsBarResourceType::Get()->type_url(), "acked"), + 1), + ::testing::Pair(ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -2368,6 +3188,13 @@ TEST_F(XdsClientTest, Federation) { authority.set_server(authority_server); InitXdsClient( FakeXdsBootstrap::Builder().AddAuthority(kAuthority, authority)); + // Metrics should initially be empty. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre()); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT(GetResourceCounts(), ::testing::ElementsAre()); + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre()); // Start a watch for "foo1". auto watcher = StartFooWatch("foo1"); // Watcher should initially not see any resource reported. @@ -2395,6 +3222,22 @@ TEST_F(XdsClientTest, Federation) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", true))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -2406,6 +3249,22 @@ TEST_F(XdsClientTest, Federation) { auto watcher2 = StartFooWatch(kXdstpResourceName); // Watcher should initially not see any resource reported. EXPECT_FALSE(watcher2->HasEvent()); + // Check metric data. + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre( + ::testing::Pair(ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1), + ::testing::Pair(ResourceCountLabelsEq( + kAuthority, XdsFooResourceType::Get()->type_url(), + "requested"), + 1))); + EXPECT_THAT(GetServerConnections(), + ::testing::ElementsAre( + ::testing::Pair("default_xds_server", true), + ::testing::Pair(authority_server.server_uri(), true))); // XdsClient will create a new stream to the server for this authority. auto stream2 = WaitForAdsStream(authority_server); ASSERT_TRUE(stream2 != nullptr); @@ -2431,6 +3290,34 @@ TEST_F(XdsClientTest, Federation) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, kXdstpResourceName); EXPECT_EQ(resource->value, 3); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre( + ::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1), + ::testing::Pair( + ::testing::Pair(authority_server.server_uri(), + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre( + ::testing::Pair(ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1), + ::testing::Pair( + ResourceCountLabelsEq( + kAuthority, XdsFooResourceType::Get()->type_url(), "acked"), + 1))); + EXPECT_THAT(GetServerConnections(), + ::testing::ElementsAre( + ::testing::Pair("default_xds_server", true), + ::testing::Pair(authority_server.server_uri(), true))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream2.get()); ASSERT_TRUE(request.has_value()); @@ -2481,6 +3368,22 @@ TEST_F(XdsClientTest, FederationAuthorityDefaultsToTopLevelXdsServer) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", true))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -2513,6 +3416,27 @@ TEST_F(XdsClientTest, FederationAuthorityDefaultsToTopLevelXdsServer) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, kXdstpResourceName); EXPECT_EQ(resource->value, 3); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 2))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre( + ::testing::Pair(ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1), + ::testing::Pair( + ResourceCountLabelsEq( + kAuthority, XdsFooResourceType::Get()->type_url(), "acked"), + 1))); + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", true))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -2651,6 +3575,22 @@ TEST_F(XdsClientTest, FederationChannelFailureReportedToWatchers) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, "foo1"); EXPECT_EQ(resource->value, 6); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre(::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre(::testing::Pair( + ResourceCountLabelsEq(XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1))); + EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair( + "default_xds_server", true))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream.get()); ASSERT_TRUE(request.has_value()); @@ -2660,6 +3600,11 @@ TEST_F(XdsClientTest, FederationChannelFailureReportedToWatchers) { /*resource_names=*/{"foo1"}); // Start a watch for the xdstp resource name. auto watcher2 = StartFooWatch(kXdstpResourceName); + // Check metric data. + EXPECT_THAT(GetServerConnections(), + ::testing::ElementsAre( + ::testing::Pair("default_xds_server", true), + ::testing::Pair(authority_server.server_uri(), true))); // Watcher should initially not see any resource reported. EXPECT_FALSE(watcher2->HasEvent()); // XdsClient will create a new stream to the server for this authority. @@ -2687,6 +3632,34 @@ TEST_F(XdsClientTest, FederationChannelFailureReportedToWatchers) { ASSERT_NE(resource, nullptr); EXPECT_EQ(resource->name, kXdstpResourceName); EXPECT_EQ(resource->value, 3); + // Check metric data. + EXPECT_THAT(metrics_reporter_->resource_updates_valid(), + ::testing::ElementsAre( + ::testing::Pair( + ::testing::Pair("default_xds_server", + XdsFooResourceType::Get()->type_url()), + 1), + ::testing::Pair( + ::testing::Pair(authority_server.server_uri(), + XdsFooResourceType::Get()->type_url()), + 1))); + EXPECT_THAT(metrics_reporter_->resource_updates_invalid(), + ::testing::ElementsAre()); + EXPECT_THAT( + GetResourceCounts(), + ::testing::ElementsAre( + ::testing::Pair(ResourceCountLabelsEq( + XdsClient::kOldStyleAuthority, + XdsFooResourceType::Get()->type_url(), "acked"), + 1), + ::testing::Pair( + ResourceCountLabelsEq( + kAuthority, XdsFooResourceType::Get()->type_url(), "acked"), + 1))); + EXPECT_THAT(GetServerConnections(), + ::testing::ElementsAre( + ::testing::Pair("default_xds_server", true), + ::testing::Pair(authority_server.server_uri(), true))); // XdsClient should have sent an ACK message to the xDS server. request = WaitForRequest(stream2.get()); ASSERT_TRUE(request.has_value()); @@ -2707,6 +3680,11 @@ TEST_F(XdsClientTest, FederationChannelFailureReportedToWatchers) { << *error; // The watcher for "foo1" should not see any error. EXPECT_FALSE(watcher->HasEvent()); + // Check metric data. + EXPECT_THAT(GetServerConnections(), + ::testing::ElementsAre( + ::testing::Pair("default_xds_server", true), + ::testing::Pair(authority_server.server_uri(), false))); // Cancel watch for "foo1". CancelFooWatch(watcher.get(), "foo1"); EXPECT_TRUE(stream->Orphaned()); diff --git a/test/core/xds/xds_client_test_peer.h b/test/core/xds/xds_client_test_peer.h new file mode 100644 index 00000000000..64cbd841499 --- /dev/null +++ b/test/core/xds/xds_client_test_peer.h @@ -0,0 +1,80 @@ +// +// Copyright 2022 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_TEST_CORE_XDS_XDS_CLIENT_TEST_PEER_H +#define GRPC_TEST_CORE_XDS_XDS_CLIENT_TEST_PEER_H + +#include + +#include + +#include "absl/functional/function_ref.h" +#include "absl/strings/str_cat.h" + +#include "src/core/ext/xds/xds_client.h" + +namespace grpc_core { +namespace testing { + +class XdsClientTestPeer { + public: + explicit XdsClientTestPeer(XdsClient* xds_client) : xds_client_(xds_client) {} + + void TestDumpClientConfig() { + upb::Arena arena; + auto client_config = envoy_service_status_v3_ClientConfig_new(arena.ptr()); + std::set string_pool; + MutexLock lock(xds_client_->mu()); + xds_client_->DumpClientConfig(&string_pool, arena.ptr(), client_config); + } + + struct ResourceCountLabels { + std::string xds_authority; + std::string resource_type; + std::string cache_state; + + std::string ToString() const { + return absl::StrCat("xds_authority=\"", xds_authority, + "\" resource_type=\"", resource_type, + "\" cache_state=\"", cache_state, "\""); + } + }; + void TestReportResourceCounts( + absl::FunctionRef func) { + MutexLock lock(xds_client_->mu()); + xds_client_->ReportResourceCounts( + [&](const XdsClient::ResourceCountLabels& labels, uint64_t count) { + ResourceCountLabels labels_copy = {std::string(labels.xds_authority), + std::string(labels.resource_type), + std::string(labels.cache_state)}; + func(labels_copy, count); + }); + } + + void TestReportServerConnections( + absl::FunctionRef func) { + MutexLock lock(xds_client_->mu()); + xds_client_->ReportServerConnections(func); + } + + private: + XdsClient* xds_client_; +}; + +} // namespace testing +} // namespace grpc_core + +#endif // GRPC_TEST_CORE_XDS_XDS_CLIENT_TEST_PEER_H diff --git a/test/core/xds/xds_cluster_resource_type_test.cc b/test/core/xds/xds_cluster_resource_type_test.cc index a30dd8bc253..2cb04833eec 100644 --- a/test/core/xds/xds_cluster_resource_type_test.cc +++ b/test/core/xds/xds_cluster_resource_type_test.cc @@ -118,7 +118,8 @@ class XdsClusterTest : public ::testing::Test { } return MakeRefCounted(std::move(*bootstrap), /*transport_factory=*/nullptr, - /*event_engine=*/nullptr, "foo agent", + /*event_engine=*/nullptr, + /*metrics_reporter=*/nullptr, "foo agent", "foo version"); } diff --git a/test/core/xds/xds_common_types_test.cc b/test/core/xds/xds_common_types_test.cc index ab049d954d6..4a5be5da3d7 100644 --- a/test/core/xds/xds_common_types_test.cc +++ b/test/core/xds/xds_common_types_test.cc @@ -104,7 +104,8 @@ class XdsCommonTypesTest : public ::testing::Test { } return MakeRefCounted(std::move(*bootstrap), /*transport_factory=*/nullptr, - /*event_engine=*/nullptr, "foo agent", + /*event_engine=*/nullptr, + /*metrics_reporter=*/nullptr, "foo agent", "foo version"); } diff --git a/test/core/xds/xds_endpoint_resource_type_test.cc b/test/core/xds/xds_endpoint_resource_type_test.cc index 8538dcd15c0..9371d50a780 100644 --- a/test/core/xds/xds_endpoint_resource_type_test.cc +++ b/test/core/xds/xds_endpoint_resource_type_test.cc @@ -95,7 +95,8 @@ class XdsEndpointTest : public ::testing::Test { } return MakeRefCounted(std::move(*bootstrap), /*transport_factory=*/nullptr, - /*event_engine=*/nullptr, "foo agent", + /*event_engine=*/nullptr, + /*metrics_reporter=*/nullptr, "foo agent", "foo version"); } diff --git a/test/core/xds/xds_http_filters_test.cc b/test/core/xds/xds_http_filters_test.cc index b8585d319d6..bdbabc45283 100644 --- a/test/core/xds/xds_http_filters_test.cc +++ b/test/core/xds/xds_http_filters_test.cc @@ -117,7 +117,8 @@ class XdsHttpFilterTest : public ::testing::Test { } return MakeRefCounted(std::move(*bootstrap), /*transport_factory=*/nullptr, - /*event_engine=*/nullptr, "foo agent", + /*event_engine=*/nullptr, + /*metrics_reporter=*/nullptr, "foo agent", "foo version"); } diff --git a/test/core/xds/xds_listener_resource_type_test.cc b/test/core/xds/xds_listener_resource_type_test.cc index eac9cb55df7..86a1b299b0a 100644 --- a/test/core/xds/xds_listener_resource_type_test.cc +++ b/test/core/xds/xds_listener_resource_type_test.cc @@ -118,7 +118,8 @@ class XdsListenerTest : public ::testing::Test { } return MakeRefCounted(std::move(*bootstrap), /*transport_factory=*/nullptr, - /*event_engine=*/nullptr, "foo agent", + /*event_engine=*/nullptr, + /*metrics_reporter=*/nullptr, "foo agent", "foo version"); } diff --git a/test/core/xds/xds_route_config_resource_type_test.cc b/test/core/xds/xds_route_config_resource_type_test.cc index 8703efdfcb2..e6df7684377 100644 --- a/test/core/xds/xds_route_config_resource_type_test.cc +++ b/test/core/xds/xds_route_config_resource_type_test.cc @@ -107,7 +107,8 @@ class XdsRouteConfigTest : public ::testing::Test { } return MakeRefCounted(std::move(*bootstrap), /*transport_factory=*/nullptr, - /*event_engine=*/nullptr, "foo agent", + /*event_engine=*/nullptr, + /*metrics_reporter=*/nullptr, "foo agent", "foo version"); } diff --git a/test/cpp/end2end/xds/BUILD b/test/cpp/end2end/xds/BUILD index 30f165067c4..cc36de6519d 100644 --- a/test/cpp/end2end/xds/BUILD +++ b/test/cpp/end2end/xds/BUILD @@ -223,6 +223,7 @@ grpc_cc_test( "//:gpr", "//:grpc", "//:grpc++", + "//test/core/util:fake_stats_plugin", "//test/core/util:grpc_test_util", "//test/core/util:scoped_env_var", ], diff --git a/test/cpp/end2end/xds/xds_core_end2end_test.cc b/test/cpp/end2end/xds/xds_core_end2end_test.cc index 752ed01fe55..df5c480b091 100644 --- a/test/cpp/end2end/xds/xds_core_end2end_test.cc +++ b/test/cpp/end2end/xds/xds_core_end2end_test.cc @@ -27,6 +27,7 @@ #include "src/core/client_channel/backup_poller.h" #include "src/core/lib/config/config_vars.h" #include "src/proto/grpc/testing/xds/v3/listener.pb.h" +#include "test/core/util/fake_stats_plugin.h" #include "test/core/util/resolve_localhost_ip46.h" #include "test/core/util/scoped_env_var.h" #include "test/cpp/end2end/xds/xds_end2end_test_lib.h" @@ -1071,6 +1072,172 @@ TEST_P(XdsFederationTest, FederationServer) { WaitForAllBackends(DEBUG_LOCATION); } +// +// XdsMetricsTest - tests xDS metrics +// + +class XdsMetricsTest : public XdsEnd2endTest { + protected: + void SetUp() override { + stats_plugin_ = grpc_core::FakeStatsPluginBuilder() + .UseDisabledByDefaultMetrics(true) + .BuildAndRegister(); + InitClient(); + } + + std::shared_ptr stats_plugin_; +}; + +// Runs with RDS so that we know all resource types work properly. +INSTANTIATE_TEST_SUITE_P( + XdsTest, XdsMetricsTest, + ::testing::Values(XdsTestType().set_enable_rds_testing()), + &XdsTestType::Name); + +TEST_P(XdsMetricsTest, MetricDefinitionResourceUpdatesValid) { + const auto* descriptor = + grpc_core::GlobalInstrumentsRegistryTestPeer::FindMetricDescriptorByName( + "grpc.xds_client.resource_updates_valid"); + 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.xds_client.resource_updates_valid"); + EXPECT_EQ(descriptor->unit, "{resource}"); + EXPECT_THAT(descriptor->label_keys, + ::testing::ElementsAre("grpc.target", "grpc.xds.server", + "grpc.xds.resource_type")); + EXPECT_THAT(descriptor->optional_label_keys, ::testing::ElementsAre()); +} + +TEST_P(XdsMetricsTest, MetricDefinitionResourceUpdatesInvalid) { + const auto* descriptor = + grpc_core::GlobalInstrumentsRegistryTestPeer::FindMetricDescriptorByName( + "grpc.xds_client.resource_updates_invalid"); + 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.xds_client.resource_updates_invalid"); + EXPECT_EQ(descriptor->unit, "{resource}"); + EXPECT_THAT(descriptor->label_keys, + ::testing::ElementsAre("grpc.target", "grpc.xds.server", + "grpc.xds.resource_type")); + EXPECT_THAT(descriptor->optional_label_keys, ::testing::ElementsAre()); +} + +TEST_P(XdsMetricsTest, MetricDefinitionConnected) { + const auto* descriptor = + grpc_core::GlobalInstrumentsRegistryTestPeer::FindMetricDescriptorByName( + "grpc.xds_client.connected"); + 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.xds_client.connected"); + EXPECT_EQ(descriptor->unit, "{bool}"); + EXPECT_THAT(descriptor->label_keys, + ::testing::ElementsAre("grpc.target", "grpc.xds.server")); + EXPECT_THAT(descriptor->optional_label_keys, ::testing::ElementsAre()); +} + +TEST_P(XdsMetricsTest, MetricDefinitionResources) { + const auto* descriptor = + grpc_core::GlobalInstrumentsRegistryTestPeer::FindMetricDescriptorByName( + "grpc.xds_client.resources"); + 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.xds_client.resources"); + EXPECT_EQ(descriptor->unit, "{resource}"); + EXPECT_THAT( + descriptor->label_keys, + ::testing::ElementsAre("grpc.target", "grpc.xds.authority", + "grpc.xds.resource_type", "grpc.xds.cache_state")); + EXPECT_THAT(descriptor->optional_label_keys, ::testing::ElementsAre()); +} + +TEST_P(XdsMetricsTest, MetricValues) { + const auto kMetricResourceUpdatesValid = + grpc_core::GlobalInstrumentsRegistryTestPeer:: + FindUInt64CounterHandleByName( + "grpc.xds_client.resource_updates_valid") + .value(); + const auto kMetricResourceUpdatesInvalid = + grpc_core::GlobalInstrumentsRegistryTestPeer:: + FindUInt64CounterHandleByName( + "grpc.xds_client.resource_updates_invalid") + .value(); + const auto kMetricConnected = + grpc_core::GlobalInstrumentsRegistryTestPeer:: + FindCallbackInt64GaugeHandleByName("grpc.xds_client.connected") + .value(); + const auto kMetricResources = + grpc_core::GlobalInstrumentsRegistryTestPeer:: + FindCallbackInt64GaugeHandleByName("grpc.xds_client.resources") + .value(); + const std::string kTarget = absl::StrCat("xds:", kServerName); + const std::string kXdsServer = absl::StrCat("localhost:", balancer_->port()); + CreateAndStartBackends(1, /*xds_enabled=*/true); + EdsResourceArgs args = + EdsResourceArgs({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + CheckRpcSendOk(DEBUG_LOCATION); + stats_plugin_->TriggerCallbacks(); + // Check client metrics. + EXPECT_THAT(stats_plugin_->GetCallbackGaugeValue(kMetricConnected, + {kTarget, kXdsServer}, {}), + ::testing::Optional(1)); + for (absl::string_view type_url : + {"envoy.config.listener.v3.Listener", + "envoy.config.route.v3.RouteConfiguration", + "envoy.config.cluster.v3.Cluster", + "envoy.config.endpoint.v3.ClusterLoadAssignment"}) { + EXPECT_THAT( + stats_plugin_->GetCounterValue(kMetricResourceUpdatesValid, + {kTarget, kXdsServer, type_url}, {}), + ::testing::Optional(1)); + EXPECT_THAT( + stats_plugin_->GetCounterValue(kMetricResourceUpdatesInvalid, + {kTarget, kXdsServer, type_url}, {}), + ::testing::Optional(0)); + EXPECT_THAT(stats_plugin_->GetCallbackGaugeValue( + kMetricResources, {kTarget, "#old", type_url, "acked"}, {}), + ::testing::Optional(1)); + } + // Check server metrics. + EXPECT_THAT(stats_plugin_->GetCallbackGaugeValue(kMetricConnected, + {"#server", kXdsServer}, {}), + ::testing::Optional(1)); + for (absl::string_view type_url : + {"envoy.config.listener.v3.Listener", + "envoy.config.route.v3.RouteConfiguration"}) { + EXPECT_THAT( + stats_plugin_->GetCounterValue(kMetricResourceUpdatesValid, + {"#server", kXdsServer, type_url}, {}), + ::testing::Optional(1)); + EXPECT_THAT( + stats_plugin_->GetCounterValue(kMetricResourceUpdatesInvalid, + {"#server", kXdsServer, type_url}, {}), + ::testing::Optional(0)); + EXPECT_THAT( + stats_plugin_->GetCallbackGaugeValue( + kMetricResources, {"#server", "#old", type_url, "acked"}, {}), + ::testing::Optional(1)); + } +} + // // XdsFederationDisabledTest // diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal index ba4cbaec282..f855fd03361 100644 --- a/tools/doxygen/Doxyfile.c++.internal +++ b/tools/doxygen/Doxyfile.c++.internal @@ -2115,6 +2115,7 @@ src/core/ext/xds/xds_lb_policy_registry.cc \ src/core/ext/xds/xds_lb_policy_registry.h \ src/core/ext/xds/xds_listener.cc \ src/core/ext/xds/xds_listener.h \ +src/core/ext/xds/xds_metrics.h \ src/core/ext/xds/xds_resource_type.h \ src/core/ext/xds/xds_resource_type_impl.h \ src/core/ext/xds/xds_route_config.cc \ diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal index 8796adfd4c6..a61ab574132 100644 --- a/tools/doxygen/Doxyfile.core.internal +++ b/tools/doxygen/Doxyfile.core.internal @@ -1885,6 +1885,7 @@ src/core/ext/xds/xds_lb_policy_registry.cc \ src/core/ext/xds/xds_lb_policy_registry.h \ src/core/ext/xds/xds_listener.cc \ src/core/ext/xds/xds_listener.h \ +src/core/ext/xds/xds_metrics.h \ src/core/ext/xds/xds_resource_type.h \ src/core/ext/xds/xds_resource_type_impl.h \ src/core/ext/xds/xds_route_config.cc \