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 \