[XdsClient] implement non-per-call metrics (#36020)

As per gRFC A78 (https://github.com/grpc/proposal/pull/419).

Closes #36020

COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/36020 from markdroth:non_per_call_metrics_xds c1d9b464bf
PiperOrigin-RevId: 617320495
pull/36153/head
Mark D. Roth 11 months ago committed by Copybara-Service
parent bfcbcd6eaa
commit 733e904491
  1. 1
      BUILD
  2. 1
      CMakeLists.txt
  3. 1
      Package.swift
  4. 4
      build_autogenerated.yaml
  5. 2
      gRPC-C++.podspec
  6. 2
      gRPC-Core.podspec
  7. 1
      grpc.gemspec
  8. 1
      package.xml
  9. 1
      src/core/BUILD
  10. 92
      src/core/ext/xds/xds_client.cc
  11. 27
      src/core/ext/xds/xds_client.h
  12. 210
      src/core/ext/xds/xds_client_grpc.cc
  13. 16
      src/core/ext/xds/xds_client_grpc.h
  14. 39
      src/core/ext/xds/xds_metrics.h
  15. 5
      src/core/ext/xds/xds_server_config_fetcher.cc
  16. 10
      src/core/lib/channel/metrics.cc
  17. 4
      src/core/lib/channel/metrics.h
  18. 14
      test/core/xds/BUILD
  19. 47
      test/core/xds/xds_client_fuzzer.cc
  20. 7
      test/core/xds/xds_client_fuzzer.proto
  21. 982
      test/core/xds/xds_client_test.cc
  22. 80
      test/core/xds/xds_client_test_peer.h
  23. 3
      test/core/xds/xds_cluster_resource_type_test.cc
  24. 3
      test/core/xds/xds_common_types_test.cc
  25. 3
      test/core/xds/xds_endpoint_resource_type_test.cc
  26. 3
      test/core/xds/xds_http_filters_test.cc
  27. 3
      test/core/xds/xds_listener_resource_type_test.cc
  28. 3
      test/core/xds/xds_route_config_resource_type_test.cc
  29. 1
      test/cpp/end2end/xds/BUILD
  30. 167
      test/cpp/end2end/xds/xds_core_end2end_test.cc
  31. 1
      tools/doxygen/Doxyfile.c++.internal
  32. 1
      tools/doxygen/Doxyfile.core.internal

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

1
CMakeLists.txt generated

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

1
Package.swift generated

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

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

2
gRPC-C++.podspec generated

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

2
gRPC-Core.podspec generated

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

1
grpc.gemspec generated

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

1
package.xml generated

@ -1098,6 +1098,7 @@
<file baseinstalldir="/" name="src/core/ext/xds/xds_lb_policy_registry.h" role="src" />
<file baseinstalldir="/" name="src/core/ext/xds/xds_listener.cc" role="src" />
<file baseinstalldir="/" name="src/core/ext/xds/xds_listener.h" role="src" />
<file baseinstalldir="/" name="src/core/ext/xds/xds_metrics.h" role="src" />
<file baseinstalldir="/" name="src/core/ext/xds/xds_resource_type.h" role="src" />
<file baseinstalldir="/" name="src/core/ext/xds/xds_resource_type_impl.h" role="src" />
<file baseinstalldir="/" name="src/core/ext/xds/xds_route_config.cc" role="src" />

@ -5060,6 +5060,7 @@ grpc_cc_library(
"load_file",
"match",
"metadata_batch",
"metrics",
"pollset_set",
"protobuf_any_upb",
"protobuf_duration_upb",

@ -150,7 +150,8 @@ class XdsClient::XdsChannel::AdsCall : public InternallyRefCounted<AdsCall> {
std::vector<std::string> errors;
std::map<std::string /*authority*/, std::set<XdsResourceKey>>
resources_seen;
bool have_valid_resources = false;
uint64_t num_valid_resources = 0;
uint64_t num_invalid_resources = 0;
RefCountedPtr<ReadDelayHandle> 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<XdsBootstrap> bootstrap,
OrphanablePtr<XdsTransportFactory> transport_factory,
std::shared_ptr<grpc_event_engine::experimental::EventEngine> engine,
std::unique_ptr<XdsMetricsReporter> metrics_reporter,
std::string user_agent_name, std::string user_agent_version,
Duration resource_request_timeout)
: DualRefCounted<XdsClient>(
@ -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::XdsResourceName> 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::XdsResourceName> 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<void(const ResourceCountLabels&, uint64_t)> 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<absl::string_view, uint64_t> 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<void(absl::string_view, bool)> func) {
for (const auto& p : xds_channel_map_) {
func(p.second->server_uri(), p.second->status().ok());
}
}
} // namespace grpc_core

@ -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<XdsClient> {
public:
// The authority reported for old-style (non-xdstp) resource names.
static constexpr absl::string_view kOldStyleAuthority = "#old";
class ReadDelayHandle : public RefCounted<ReadDelayHandle> {
public:
static RefCountedPtr<ReadDelayHandle> NoWait() { return nullptr; }
@ -87,6 +91,7 @@ class XdsClient : public DualRefCounted<XdsClient> {
std::unique_ptr<XdsBootstrap> bootstrap,
OrphanablePtr<XdsTransportFactory> transport_factory,
std::shared_ptr<grpc_event_engine::experimental::EventEngine> engine,
std::unique_ptr<XdsMetricsReporter> 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<XdsClient> {
}
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<XdsClient> {
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<void(const ResourceCountLabels&, uint64_t)> 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<void(absl::string_view /*xds_server*/, bool)> func)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(&mu_);
private:
friend testing::XdsClientTestPeer;
@ -221,6 +243,8 @@ class XdsClient : public DualRefCounted<XdsClient> {
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<XdsClient> {
XdsApi api_;
WorkSerializer work_serializer_;
std::shared_ptr<grpc_event_engine::experimental::EventEngine> engine_;
std::unique_ptr<XdsMetricsReporter> metrics_reporter_;
Mutex mu_;

@ -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<std::map<absl::string_view, GrpcXdsClient*>> g_xds_client_map
ABSL_GUARDED_BY(*g_mu);
char* g_fallback_bootstrap_config ABSL_GUARDED_BY(*g_mu) = nullptr;
} // namespace
namespace {
absl::StatusOr<std::string> GetBootstrapContents(const char* fallback_config) {
// First, try GRPC_XDS_BOOTSTRAP env var.
auto path = GetEnv("GRPC_XDS_BOOTSTRAP");
@ -138,19 +213,6 @@ absl::StatusOr<std::string> GetBootstrapContents(const char* fallback_config) {
"not defined");
}
std::vector<RefCountedPtr<GrpcXdsClient>> GetAllXdsClients() {
MutexLock lock(g_mu);
std::vector<RefCountedPtr<GrpcXdsClient>> 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<GrpcXdsClient>());
}
}
return xds_clients;
}
} // namespace
absl::StatusOr<RefCountedPtr<GrpcXdsClient>> GrpcXdsClient::GetOrCreate(
@ -201,36 +263,20 @@ absl::StatusOr<RefCountedPtr<GrpcXdsClient>> 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<std::string> 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<GrpcXdsBootstrap> 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<MetricsReporter>(*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<CertificateProviderStore>(
static_cast<const GrpcXdsBootstrap&>(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<RefCountedPtr<GrpcXdsClient>> GetAllXdsClients() {
MutexLock lock(g_mu);
std::vector<RefCountedPtr<GrpcXdsClient>> 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<GrpcXdsClient>());
}
}
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<std::string> 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) {

@ -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<RefCountedPtr<GrpcXdsClient>> 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<CertificateProviderStore> certificate_provider_store_;
GlobalStatsPluginRegistry::StatsPluginGroup stats_plugin_group_;
std::unique_ptr<RegisteredMetricCallback> registered_metric_callback_;
};
namespace internal {

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

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

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

@ -310,8 +310,10 @@ class GlobalStatsPluginRegistry {
};
static void RegisterStatsPlugin(std::shared_ptr<StatsPlugin> 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.

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

@ -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<std::string> 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<XdsClient>(
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(),

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

File diff suppressed because it is too large Load Diff

@ -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 <grpc/support/port_platform.h>
#include <set>
#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<std::string> 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<void(const ResourceCountLabels&, uint64_t)> 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<void(absl::string_view, bool)> 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

@ -118,7 +118,8 @@ class XdsClusterTest : public ::testing::Test {
}
return MakeRefCounted<XdsClient>(std::move(*bootstrap),
/*transport_factory=*/nullptr,
/*event_engine=*/nullptr, "foo agent",
/*event_engine=*/nullptr,
/*metrics_reporter=*/nullptr, "foo agent",
"foo version");
}

@ -104,7 +104,8 @@ class XdsCommonTypesTest : public ::testing::Test {
}
return MakeRefCounted<XdsClient>(std::move(*bootstrap),
/*transport_factory=*/nullptr,
/*event_engine=*/nullptr, "foo agent",
/*event_engine=*/nullptr,
/*metrics_reporter=*/nullptr, "foo agent",
"foo version");
}

@ -95,7 +95,8 @@ class XdsEndpointTest : public ::testing::Test {
}
return MakeRefCounted<XdsClient>(std::move(*bootstrap),
/*transport_factory=*/nullptr,
/*event_engine=*/nullptr, "foo agent",
/*event_engine=*/nullptr,
/*metrics_reporter=*/nullptr, "foo agent",
"foo version");
}

@ -117,7 +117,8 @@ class XdsHttpFilterTest : public ::testing::Test {
}
return MakeRefCounted<XdsClient>(std::move(*bootstrap),
/*transport_factory=*/nullptr,
/*event_engine=*/nullptr, "foo agent",
/*event_engine=*/nullptr,
/*metrics_reporter=*/nullptr, "foo agent",
"foo version");
}

@ -118,7 +118,8 @@ class XdsListenerTest : public ::testing::Test {
}
return MakeRefCounted<XdsClient>(std::move(*bootstrap),
/*transport_factory=*/nullptr,
/*event_engine=*/nullptr, "foo agent",
/*event_engine=*/nullptr,
/*metrics_reporter=*/nullptr, "foo agent",
"foo version");
}

@ -107,7 +107,8 @@ class XdsRouteConfigTest : public ::testing::Test {
}
return MakeRefCounted<XdsClient>(std::move(*bootstrap),
/*transport_factory=*/nullptr,
/*event_engine=*/nullptr, "foo agent",
/*event_engine=*/nullptr,
/*metrics_reporter=*/nullptr, "foo agent",
"foo version");
}

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

@ -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<grpc_core::FakeStatsPlugin> 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
//

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

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

Loading…
Cancel
Save