Skip to content

Commit 968337a

Browse files
authored
stats: make the stat name of endpoint configurable (envoyproxy#45044)
1 parent 404174b commit 968337a

14 files changed

Lines changed: 147 additions & 22 deletions

File tree

api/envoy/config/endpoint/v3/endpoint_components.proto

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
2525
// [#protodoc-title: Endpoints]
2626

2727
// Upstream host identifier.
28+
// [#next-free-field: 6]
2829
message Endpoint {
2930
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.endpoint.Endpoint";
3031

@@ -97,6 +98,20 @@ message Endpoint {
9798
// sorted by preference order of the addresses. This will only be supported
9899
// for STATIC and EDS clusters.
99100
repeated AdditionalAddress additional_addresses = 4;
101+
102+
// Optional alternative stat name for this endpoint. If not specified, the main address will be used
103+
// as the stat name and be extracted as ``envoy.endpoint_address`` tag value in generated stats.
104+
// If specified, the ``observability_name`` here will be used to replace the main address.
105+
//
106+
// .. note::
107+
//
108+
// This field is ignored for logical DNS host implementation..
109+
//
110+
// This is useful when there are duplicate addresses in the cluster, for example when multiple
111+
// endpoints share the same address but have different hostnames or metadata.
112+
// In this case, the observability name can be used to differentiate between these endpoints in
113+
// stats and logs.
114+
string observability_name = 5;
100115
}
101116

102117
// An Endpoint that Envoy can route traffic to.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Added :ref:`observability_name <envoy_v3_api_field_config.endpoint.v3.Endpoint.observability_name>` to
2+
:ref:`Endpoint <envoy_v3_api_msg_config.endpoint.v3.Endpoint>` so endpoints can override the
3+
observability name used in per-endpoint stats. This allows clusters with duplicate
4+
endpoint addresses to expose distinct per-endpoint stats.

envoy/upstream/host_description.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,15 @@ class HostDescription {
207207
*/
208208
virtual const std::string& hostname() const PURE;
209209

210+
/**
211+
* @return the observability name associated with the host. Used in per-endpoint stats and other
212+
* observability surfaces. This is configured with
213+
* :ref:`stat_name <envoy_v3_api_field_config.endpoint.v3.Endpoint.stat_name>`. If this method
214+
* returns an empty string view, then the host's address should be used as fallback for the
215+
* observability name.
216+
*/
217+
virtual absl::string_view observabilityName() const PURE;
218+
210219
/**
211220
* @return the transport socket factory responsible for this host.
212221
*/

source/common/upstream/health_discovery_service.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,8 @@ HdsCluster::HdsCluster(Server::Configuration::ServerFactoryContext& server_conte
393393
HostImpl::create(info_, "", std::move(address_or_error.value()), nullptr, nullptr, 1,
394394
const_locality_shared_pool->getObject(locality_endpoints.locality()),
395395
host.endpoint().health_check_config(), 0,
396-
envoy::config::core::v3::UNKNOWN),
396+
envoy::config::core::v3::UNKNOWN, {},
397+
host.endpoint().observability_name()),
397398
std::unique_ptr<HostImpl>));
398399
// Add this host/endpoint pointer to our flat list of endpoints for health checking.
399400
hosts_->push_back(endpoint);
@@ -512,7 +513,8 @@ void HdsCluster::updateHosts(
512513
HostImpl::create(info_, "", std::move(address_or_error.value()), nullptr, nullptr, 1,
513514
const_locality_shared_pool->getObject(endpoints.locality()),
514515
endpoint.endpoint().health_check_config(), 0,
515-
envoy::config::core::v3::UNKNOWN),
516+
envoy::config::core::v3::UNKNOWN, {},
517+
endpoint.endpoint().observability_name()),
516518
std::unique_ptr<HostImpl>));
517519

518520
// Set the initial health status as in HdsCluster::initialize.

source/common/upstream/host_utility.cc

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,20 @@ void HostUtility::forEachHostMetric(
186186

187187
for (auto& host_set : cluster.prioritySet().hostSetsPerPriority()) {
188188
for (auto& host : host_set->hosts()) {
189+
absl::string_view endpoint_observability_name = host->observabilityName();
190+
Network::Address::InstanceConstSharedPtr address;
191+
if (endpoint_observability_name.empty()) {
192+
// Only logical host will have empty observability name for now.
193+
address = host->address();
194+
endpoint_observability_name = address->asStringView();
195+
}
189196

190197
Stats::TagVector tags;
191198
tags.reserve(fixed_tags.size() + 3);
192199
tags.insert(tags.end(), fixed_tags.begin(), fixed_tags.end());
193200
tags.emplace_back(Stats::Tag{Envoy::Config::TagNames::get().CLUSTER_NAME, cluster_name});
194-
tags.emplace_back(Stats::Tag{"envoy.endpoint_address", host->address()->asString()});
201+
tags.emplace_back(
202+
Stats::Tag{"envoy.endpoint_address", std::string(endpoint_observability_name)});
195203

196204
const auto& hostname = host->hostname();
197205
if (!hostname.empty()) {
@@ -200,10 +208,9 @@ void HostUtility::forEachHostMetric(
200208

201209
auto set_metric_metadata = [&](absl::string_view metric_name,
202210
Stats::PrimitiveMetricMetadata& metric) {
203-
metric.setName(
204-
absl::StrCat("cluster.", cluster_name, ".endpoint.",
205-
Stats::Utility::sanitizeStatsName(host->address()->asStringView()),
206-
".", metric_name));
211+
metric.setName(absl::StrCat(
212+
"cluster.", cluster_name, ".endpoint.",
213+
Stats::Utility::sanitizeStatsName(endpoint_observability_name), ".", metric_name));
207214
metric.setTagExtractedName(absl::StrCat("cluster.endpoint.", metric_name));
208215
metric.setTags(tags);
209216

source/common/upstream/upstream_impl.cc

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -474,11 +474,11 @@ absl::StatusOr<std::unique_ptr<HostDescriptionImpl>> HostDescriptionImpl::create
474474
MetadataConstSharedPtr locality_metadata,
475475
std::shared_ptr<const envoy::config::core::v3::Locality> locality,
476476
const envoy::config::endpoint::v3::Endpoint::HealthCheckConfig& health_check_config,
477-
uint32_t priority, const AddressVector& address_list) {
477+
uint32_t priority, const AddressVector& address_list, absl::string_view stat_name) {
478478
absl::Status creation_status = absl::OkStatus();
479479
auto ret = std::unique_ptr<HostDescriptionImpl>(new HostDescriptionImpl(
480480
creation_status, cluster, hostname, dest_address, endpoint_metadata, locality_metadata,
481-
locality, health_check_config, priority, address_list));
481+
locality, health_check_config, priority, address_list, stat_name));
482482
RETURN_IF_NOT_OK(creation_status);
483483
return ret;
484484
}
@@ -489,12 +489,13 @@ HostDescriptionImpl::HostDescriptionImpl(
489489
MetadataConstSharedPtr locality_metadata,
490490
std::shared_ptr<const envoy::config::core::v3::Locality> locality,
491491
const envoy::config::endpoint::v3::Endpoint::HealthCheckConfig& health_check_config,
492-
uint32_t priority, const AddressVector& address_list)
492+
uint32_t priority, const AddressVector& address_list, absl::string_view stat_name)
493493
: HostDescriptionImplBase(cluster, hostname, dest_address, endpoint_metadata, locality_metadata,
494494
locality, health_check_config, priority, creation_status),
495495
address_(dest_address),
496496
address_list_or_null_(makeAddressListOrNull(dest_address, address_list)),
497-
health_check_address_(resolveHealthCheckAddress(health_check_config, dest_address)) {}
497+
health_check_address_(resolveHealthCheckAddress(health_check_config, dest_address)),
498+
observability_name_(stat_name) {}
498499

499500
HostDescriptionImplBase::HostDescriptionImplBase(
500501
ClusterInfoConstSharedPtr cluster, const std::string& hostname,
@@ -737,11 +738,12 @@ absl::StatusOr<std::unique_ptr<HostImpl>> HostImpl::create(
737738
std::shared_ptr<const envoy::config::core::v3::Locality> locality,
738739
const envoy::config::endpoint::v3::Endpoint::HealthCheckConfig& health_check_config,
739740
uint32_t priority, const envoy::config::core::v3::HealthStatus health_status,
740-
const AddressVector& address_list) {
741+
const AddressVector& address_list, absl::string_view stat_name) {
741742
absl::Status creation_status = absl::OkStatus();
742-
auto ret = std::unique_ptr<HostImpl>(new HostImpl(
743-
creation_status, cluster, hostname, address, endpoint_metadata, locality_metadata,
744-
initial_weight, locality, health_check_config, priority, health_status, address_list));
743+
auto ret = std::unique_ptr<HostImpl>(
744+
new HostImpl(creation_status, cluster, hostname, address, endpoint_metadata,
745+
locality_metadata, initial_weight, locality, health_check_config, priority,
746+
health_status, address_list, stat_name));
745747
RETURN_IF_NOT_OK(creation_status);
746748
return ret;
747749
}
@@ -2220,7 +2222,7 @@ void PriorityStateManager::registerHostForPriority(
22202222
lb_endpoint.load_balancing_weight().value(),
22212223
parent_.constLocalitySharedPool()->getObject(locality_lb_endpoint.locality()),
22222224
lb_endpoint.endpoint().health_check_config(), locality_lb_endpoint.priority(),
2223-
lb_endpoint.health_status(), address_list),
2225+
lb_endpoint.health_status(), address_list, lb_endpoint.endpoint().observability_name()),
22242226
std::unique_ptr<HostImpl>));
22252227
registerHostForPriority(host, locality_lb_endpoint);
22262228
}

source/common/upstream/upstream_impl.h

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,8 @@ class HostDescriptionImpl : public HostDescriptionImplBase {
317317
MetadataConstSharedPtr endpoint_metadata, MetadataConstSharedPtr locality_metadata,
318318
std::shared_ptr<const envoy::config::core::v3::Locality> locality,
319319
const envoy::config::endpoint::v3::Endpoint::HealthCheckConfig& health_check_config,
320-
uint32_t priority, const AddressVector& address_list = {});
320+
uint32_t priority, const AddressVector& address_list = {},
321+
absl::string_view stat_name = {});
321322

322323
// HostDescription
323324
Network::Address::InstanceConstSharedPtr address() const override { return address_; }
@@ -328,6 +329,13 @@ class HostDescriptionImpl : public HostDescriptionImplBase {
328329
return address_;
329330
}
330331
SharedConstAddressVector addressListOrNull() const override { return address_list_or_null_; }
332+
absl::string_view observabilityName() const override {
333+
if (observability_name_.empty()) {
334+
ASSERT(address_ != nullptr);
335+
return address_->asStringView();
336+
}
337+
return observability_name_;
338+
}
331339

332340
protected:
333341
HostDescriptionImpl(
@@ -336,7 +344,7 @@ class HostDescriptionImpl : public HostDescriptionImplBase {
336344
MetadataConstSharedPtr endpoint_metadata, MetadataConstSharedPtr locality_metadata,
337345
std::shared_ptr<const envoy::config::core::v3::Locality> locality,
338346
const envoy::config::endpoint::v3::Endpoint::HealthCheckConfig& health_check_config,
339-
uint32_t priority, const AddressVector& address_list = {});
347+
uint32_t priority, const AddressVector& address_list = {}, absl::string_view stat_name = {});
340348

341349
private:
342350
// No locks are required in this implementation: all address-related member
@@ -346,6 +354,7 @@ class HostDescriptionImpl : public HostDescriptionImplBase {
346354
const Network::Address::InstanceConstSharedPtr address_;
347355
const SharedConstAddressVector address_list_or_null_;
348356
const Network::Address::InstanceConstSharedPtr health_check_address_;
357+
const std::string observability_name_;
349358
};
350359

351360
/**
@@ -521,7 +530,7 @@ class HostImpl : public HostImplBase, public HostDescriptionImpl {
521530
std::shared_ptr<const envoy::config::core::v3::Locality> locality,
522531
const envoy::config::endpoint::v3::Endpoint::HealthCheckConfig& health_check_config,
523532
uint32_t priority, const envoy::config::core::v3::HealthStatus health_status,
524-
const AddressVector& address_list = {});
533+
const AddressVector& address_list = {}, absl::string_view stat_name = {});
525534

526535
protected:
527536
HostImpl(absl::Status& creation_status, ClusterInfoConstSharedPtr cluster,
@@ -531,11 +540,11 @@ class HostImpl : public HostImplBase, public HostDescriptionImpl {
531540
std::shared_ptr<const envoy::config::core::v3::Locality> locality,
532541
const envoy::config::endpoint::v3::Endpoint::HealthCheckConfig& health_check_config,
533542
uint32_t priority, const envoy::config::core::v3::HealthStatus health_status,
534-
const AddressVector& address_list = {})
543+
const AddressVector& address_list = {}, absl::string_view stat_name = {})
535544
: HostImplBase(initial_weight, health_check_config, health_status),
536545
HostDescriptionImpl(creation_status, cluster, hostname, address, endpoint_metadata,
537546
locality_metadata, locality, health_check_config, priority,
538-
address_list) {}
547+
address_list, stat_name) {}
539548
};
540549

541550
class HostsPerLocalityImpl : public HostsPerLocality {

source/extensions/clusters/common/logical_host.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class LogicalHost : public HostImplBase, public HostDescriptionImplBase {
5555
SharedConstAddressVector addressListOrNull() const override;
5656
Network::Address::InstanceConstSharedPtr address() const override;
5757
Network::Address::InstanceConstSharedPtr healthCheckAddress() const override;
58+
absl::string_view observabilityName() const override { return {}; }
5859
Network::Address::InstanceConstSharedPtr orcaReportingAddress() const override;
5960

6061
protected:
@@ -112,6 +113,7 @@ class RealHostDescription : public HostDescription {
112113
return logical_host_->hostnameForHealthChecks();
113114
}
114115
const std::string& hostname() const override { return logical_host_->hostname(); }
116+
absl::string_view observabilityName() const override { return {}; }
115117
Network::Address::InstanceConstSharedPtr address() const override { return address_; }
116118
SharedConstAddressVector addressListOrNull() const override {
117119
return logical_host_->addressListOrNull();

test/common/upstream/host_utility_test.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,32 @@ TEST_F(PerEndpointMetricsTest, Tags) {
441441
}));
442442
}
443443

444+
TEST_F(PerEndpointMetricsTest, CustomEndpointStatName) {
445+
auto& cluster = makeCluster("cluster1", 0);
446+
auto& host = addHost(cluster);
447+
host.observability_name_ = "shared.backend/a";
448+
449+
auto [counters, gauges] = run();
450+
451+
EXPECT_THAT(metricNamesAndValues(counters),
452+
UnorderedElementsAreArray({
453+
std::make_pair("cluster.cluster1.endpoint.shared.backend/a.c1", 11),
454+
std::make_pair("cluster.cluster1.endpoint.shared.backend/a.c2", 12),
455+
}));
456+
EXPECT_THAT(metricNamesAndValues(gauges),
457+
UnorderedElementsAreArray({
458+
std::make_pair("cluster.cluster1.endpoint.shared.backend/a.g1", 13),
459+
std::make_pair("cluster.cluster1.endpoint.shared.backend/a.g2", 14),
460+
std::make_pair("cluster.cluster1.endpoint.shared.backend/a.healthy", 1),
461+
}));
462+
463+
EXPECT_THAT(getMetric("cluster.cluster1.endpoint.shared.backend/a.c1", counters).tags(),
464+
UnorderedElementsAreArray({
465+
Stats::Tag{"envoy.cluster_name", "cluster1"},
466+
Stats::Tag{"envoy.endpoint_address", "shared.backend/a"},
467+
}));
468+
}
469+
444470
TEST_F(PerEndpointMetricsTest, FixedTags) {
445471
auto& cluster = makeCluster("cluster1", 1);
446472
Stats::TagVector fixed_tags{{"fixed1", "value1"}, {"fixed2", "value2"}};

test/common/upstream/upstream_impl_test.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2573,6 +2573,36 @@ TEST_F(StaticClusterImplTest, LoadAssignmentNonEmptyHostnameWithHealthChecks) {
25732573
EXPECT_FALSE(cluster->info()->addedViaApi());
25742574
}
25752575

2576+
TEST_F(StaticClusterImplTest, LoadAssignmentEndpointStatName) {
2577+
const std::string yaml = R"EOF(
2578+
name: staticcluster
2579+
connect_timeout: 0.25s
2580+
type: STATIC
2581+
lb_policy: ROUND_ROBIN
2582+
load_assignment:
2583+
endpoints:
2584+
- lb_endpoints:
2585+
- endpoint:
2586+
observability_name: shared.backend/a
2587+
address:
2588+
socket_address:
2589+
address: 10.0.0.1
2590+
port_value: 443
2591+
)EOF";
2592+
2593+
envoy::config::cluster::v3::Cluster cluster_config = parseClusterFromV3Yaml(yaml);
2594+
2595+
Envoy::Upstream::ClusterFactoryContextImpl factory_context(server_context_, nullptr, nullptr,
2596+
false);
2597+
std::shared_ptr<StaticClusterImpl> cluster = createCluster(cluster_config, factory_context);
2598+
2599+
cluster->initialize([] { return absl::OkStatus(); });
2600+
2601+
ASSERT_EQ(1UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size());
2602+
EXPECT_EQ("shared.backend/a",
2603+
cluster->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->observabilityName());
2604+
}
2605+
25762606
TEST_F(StaticClusterImplTest, LoadAssignmentMultiplePriorities) {
25772607
const std::string yaml = R"EOF(
25782608
name: staticcluster

0 commit comments

Comments
 (0)