Skip to content

Commit f397493

Browse files
committed
review comments
1 parent 795c5d1 commit f397493

6 files changed

Lines changed: 117 additions & 41 deletions

File tree

internal/xds/clients/config.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ type MetricsReporter interface {
115115
// for the specific client (e.g. internal/xds/clients/xdsclient/metrics/metrics.go)
116116
// for the list of supported metrics. The returned function must be called
117117
// when the metrics are no longer needed, which will remove the reporter.
118+
// The function is expected to be idempotent.
118119
//
119120
// Once the returned cancel function is called, the Report method on the
120121
// registered reporter is guaranteed not to be called again.
@@ -123,14 +124,17 @@ type MetricsReporter interface {
123124

124125
// AsyncReporter records metrics asynchronously.
125126
// Implementations must be concurrent-safe.
127+
// The metric will be recorded once per collection cycle, rather than every time
128+
// its value changes.
126129
type AsyncReporter interface {
127130
// Report records metric values using the provided recorder.
128131
Report(AsyncMetricsRecorder) error
129132
}
130133

131-
// AsyncMetricsRecorder is a recorder for async metrics.
134+
// AsyncMetricsRecorder is a recorder for async metrics (i.e the metric will be
135+
// recorded once per collection cycle, rather than every time its value changes).
132136
type AsyncMetricsRecorder interface {
133137
// ReportMetric reports a metric. The metric will be one of the predefined
134-
// set of types in the metrics.go file.
138+
// set of types in the internal/xds/clients/xdsclient/metrics/metrics.go file.
135139
ReportMetric(metric any)
136140
}

internal/xds/clients/xdsclient/authority.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -935,36 +935,38 @@ func (a *authority) resourceWatchStateForTesting(rType ResourceType, resourceNam
935935
return state, err
936936
}
937937

938+
// resourceStats returns a snapshot of the current state of all resources watched
939+
// by this authority. The return value is a nested map where:
940+
// - The outer map's key is the resource type name (e.g., "ListenerResource").
941+
// - The inner map's key is the cache state of the resource (e.g., "requested",
942+
// "acked", "nacked", "does_not_exist").
943+
// - The inner map's value is the total count of resources in that specific state.
938944
func (a *authority) resourceStats() map[string]map[string]int {
939945
ret := make(chan map[string]map[string]int, 1)
940946
op := func(context.Context) {
941-
// Map: ResourceType (String) -> CacheState (String) -> Count (Int)
942947
summary := make(map[string]map[string]int)
943948
for rType, resourceMap := range a.resources {
944-
rName := rType.TypeName
945-
if _, ok := summary[rName]; !ok {
946-
summary[rName] = make(map[string]int)
949+
typeName := rType.TypeName
950+
if _, ok := summary[typeName]; !ok {
951+
summary[typeName] = make(map[string]int)
947952
}
948953
for _, state := range resourceMap {
949-
s := getCacheState(state)
950-
summary[rName][s]++
954+
s := cacheState(state)
955+
summary[typeName][s]++
951956
}
952957
}
953958

954959
ret <- summary
955960
}
956-
957-
// Schedule the operation.
958-
// If the serializer is closed/context canceled, the second func (onFailure) runs.
959961
a.xdsClientSerializer.ScheduleOr(op, func() {
960962
ret <- nil
961963
})
962964

963965
return <-ret
964966
}
965967

966-
// getCacheState determines the metrics label string for a given resource state.
967-
func getCacheState(r *resourceState) string {
968+
// cacheState determines the metrics label string for a given resource state.
969+
func cacheState(r *resourceState) string {
968970
switch r.md.Status {
969971
case xdsresource.ServiceStatusRequested:
970972
return "requested"

internal/xds/clients/xdsclient/metrics/metrics.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type ServerFailure struct {
4747
// XDSClientConnected reports the connectivity state of the ADS stream.
4848
// Per gRFC A78, Value is 1 if connected, 0 otherwise.
4949
// Labels: grpc.target, grpc.xds.server
50+
// grpc.target is added by asyncMetricsRecorderAdapter
5051
type XDSClientConnected struct {
5152
ServerURI string
5253
Value int64
@@ -55,6 +56,7 @@ type XDSClientConnected struct {
5556
// XDSClientResourceStats reports the current cache states of xDS resources
5657
// For label definitions, see gRFC A78.
5758
// Labels: grpc.target, grpc.xds.authority, grpc.xds.cache_state, grpc.xds.resource_type
59+
// grpc.target is added by asyncMetricsRecorderAdapter
5860
type XDSClientResourceStats struct {
5961
Authority string
6062
ResourceType string

internal/xds/clients/xdsclient/test/metrics_test.go

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ func (s) TestResourceMetrics(t *testing.T) {
479479
// Trigger async metrics.
480480
tmr.triggerAsyncMetrics()
481481
if err := tmr.waitForSpecificMetric(ctx, &metrics.XDSClientResourceStats{
482-
Authority: "", // Default authority
482+
Authority: "#old", // Default authority
483483
ResourceType: "ListenerResource",
484484
CacheState: "acked",
485485
Count: 1,
@@ -501,7 +501,7 @@ func (s) TestResourceMetrics(t *testing.T) {
501501

502502
tmr.triggerAsyncMetrics()
503503
if err := tmr.waitForSpecificMetric(ctx, &metrics.XDSClientResourceStats{
504-
Authority: "",
504+
Authority: "#old",
505505
ResourceType: "ListenerResource",
506506
CacheState: "nacked_but_cached",
507507
Count: 1,
@@ -594,7 +594,7 @@ func (s) TestResourceMetrics_Extended(t *testing.T) {
594594
// Verify "requested" count 2
595595
tmr.triggerAsyncMetrics()
596596
if err := tmr.waitForSpecificMetric(ctx, &metrics.XDSClientResourceStats{
597-
Authority: "",
597+
Authority: "#old",
598598
ResourceType: "ListenerResource",
599599
CacheState: "requested",
600600
Count: 2,
@@ -605,7 +605,7 @@ func (s) TestResourceMetrics_Extended(t *testing.T) {
605605
// Verify "nacked" count 2
606606
tmr.triggerAsyncMetrics()
607607
if err := tmr.waitForSpecificMetric(ctx, &metrics.XDSClientResourceStats{
608-
Authority: "",
608+
Authority: "#old",
609609
ResourceType: "ListenerResource",
610610
CacheState: "nacked",
611611
Count: 2,
@@ -635,7 +635,7 @@ func (s) TestResourceMetrics_Extended(t *testing.T) {
635635

636636
tmr.triggerAsyncMetrics()
637637
if err := tmr.waitForSpecificMetric(ctx, &metrics.XDSClientResourceStats{
638-
Authority: "",
638+
Authority: "#old",
639639
ResourceType: "ListenerResource",
640640
CacheState: "does_not_exist",
641641
Count: 1,
@@ -671,12 +671,10 @@ func (s) TestConnectedMetric_Reconnection(t *testing.T) {
671671
sendResponse := make(chan struct{})
672672
mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{
673673
Listener: lis,
674-
OnStreamOpen: func(ctx context.Context, streamID int64, typeURL string) error {
675-
t.Logf("ADS stream opened, streamID: %d", streamID)
674+
OnStreamOpen: func(_ context.Context, _ int64, _ string) error {
676675
return nil
677676
},
678-
OnStreamRequest: func(streamID int64, req *v3discoverypb.DiscoveryRequest) error {
679-
t.Logf("ADS stream request received, streamID: %d", streamID)
677+
OnStreamRequest: func(_ int64, _ *v3discoverypb.DiscoveryRequest) error {
680678
// For all streams, wait until we are told to send a response.
681679
<-sendResponse
682680
return nil
@@ -700,11 +698,11 @@ func (s) TestConnectedMetric_Reconnection(t *testing.T) {
700698
},
701699
MetricsReporter: tmr,
702700
}
703-
701+
704702
// 1. Initial Start - metric value 0
705703
// Keep the listener stopped initially so NewStream fails/blocks.
706704
lis.Stop()
707-
705+
708706
client, err := xdsclient.New(xdsClientConfig)
709707
if err != nil {
710708
t.Fatalf("Failed to create xDS client: %v", err)
@@ -731,7 +729,7 @@ func (s) TestConnectedMetric_Reconnection(t *testing.T) {
731729

732730
// 2. 1st NewStream OK - metric value 1
733731
lis.Restart()
734-
732+
735733
// Wait a bit for the stream to be created.
736734
time.Sleep(1 * time.Second)
737735

@@ -749,7 +747,7 @@ func (s) TestConnectedMetric_Reconnection(t *testing.T) {
749747

750748
// 3. Stream Fails - metric value 0
751749
lis.Stop()
752-
750+
753751
// Wait for disconnect to be detected.
754752
if err := tmr.waitForSpecificMetric(ctx, &metrics.ServerFailure{ServerURI: mgmtServer.Address}); err != nil {
755753
t.Fatal(err.Error())
@@ -799,3 +797,65 @@ func (s) TestConnectedMetric_Reconnection(t *testing.T) {
799797
t.Fatalf("Step 5 failed: Expected XDSClientConnected to be 1 after response, got: %v", err)
800798
}
801799
}
800+
801+
func (s) TestResourceMetrics_AuthorityOldStyle(t *testing.T) {
802+
mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{})
803+
nodeID := uuid.New().String()
804+
805+
resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType}
806+
si := clients.ServerIdentifier{
807+
ServerURI: mgmtServer.Address,
808+
Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"},
809+
}
810+
configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}}
811+
serverCfg := xdsclient.ServerConfig{ServerIdentifier: si}
812+
813+
tmr := newTestMetricsReporter()
814+
xdsClientConfig := xdsclient.Config{
815+
Servers: []xdsclient.ServerConfig{serverCfg},
816+
Node: clients.Node{ID: nodeID},
817+
TransportBuilder: grpctransport.NewBuilder(configs),
818+
ResourceTypes: resourceTypes,
819+
Authorities: map[string]xdsclient.Authority{
820+
"": {XDSServers: []xdsclient.ServerConfig{serverCfg}},
821+
},
822+
MetricsReporter: tmr,
823+
}
824+
825+
client, err := xdsclient.New(xdsClientConfig)
826+
if err != nil {
827+
t.Fatalf("Failed to create xDS client: %v", err)
828+
}
829+
defer client.Close()
830+
831+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
832+
defer cancel()
833+
834+
const listenerName = "test-listener"
835+
836+
client.WatchResource(listenerType.TypeURL, listenerName, &testWatcher{})
837+
838+
resources := e2e.UpdateOptions{
839+
NodeID: nodeID,
840+
Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(listenerName, "route-config")},
841+
SkipValidation: true,
842+
}
843+
844+
if err := mgmtServer.Update(ctx, resources); err != nil {
845+
t.Fatalf("Failed to update management server: %v", err)
846+
}
847+
848+
if err := tmr.waitForMetric(ctx, &metrics.ResourceUpdateValid{ServerURI: mgmtServer.Address, ResourceType: "ListenerResource"}); err != nil {
849+
t.Fatal(err.Error())
850+
}
851+
852+
tmr.triggerAsyncMetrics()
853+
if err := tmr.waitForSpecificMetric(ctx, &metrics.XDSClientResourceStats{
854+
Authority: "#old",
855+
ResourceType: "ListenerResource",
856+
CacheState: "acked",
857+
Count: 1,
858+
}); err != nil {
859+
t.Fatalf("Failed to observe grpc.xds.authority '#old' metric substitution: %v", err)
860+
}
861+
}

internal/xds/clients/xdsclient/xdsclient.go

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,12 @@ func resourceWatchStateForTesting(c *XDSClient, rType ResourceType, resourceName
452452

453453
}
454454

455+
// xdsClientMetricReporter is a wrapper around XDSClient used solely for
456+
// reporting metrics. We create this separate type to implement the
457+
// clients.AsyncReporter interface, preventing its Report method from
458+
// becoming part of the public XDSClient API. This is especially important
459+
// because the AsyncReporter interface is experimental, and we want to
460+
// avoid coupling experimental changes to the stable XDSClient API.
455461
type xdsClientMetricReporter struct {
456462
c *XDSClient
457463
}
@@ -484,25 +490,27 @@ func (c *XDSClient) reportConnectedState(rec clients.AsyncMetricsRecorder) {
484490

485491
// reportResourceStats handles the "grpc.xds_client.resources" metric.
486492
func (c *XDSClient) reportResourceStats(rec clients.AsyncMetricsRecorder) {
487-
reportForAuthority := func(auth *authority) {
488-
stats := auth.resourceStats()
489-
for resourceType, stateCounts := range stats {
490-
for cacheState, count := range stateCounts {
491-
if count > 0 {
492-
rec.ReportMetric(&metrics.XDSClientResourceStats{
493-
Authority: auth.name,
494-
ResourceType: resourceType,
493+
reportForAuthority := func(a *authority) {
494+
stats := a.resourceStats()
495+
for resourceType, stateCounts := range stats {
496+
for cacheState, count := range stateCounts {
497+
if count > 0 {
498+
authorityName := a.name
499+
if authorityName == "" {
500+
authorityName = "#old"
501+
}
502+
rec.ReportMetric(&metrics.XDSClientResourceStats{
503+
Authority: authorityName,
504+
ResourceType: resourceType,
495505
CacheState: cacheState,
496506
Count: int64(count),
497507
})
498508
}
499509
}
500510
}
501511
}
502-
if c.topLevelAuthority != nil {
503-
reportForAuthority(c.topLevelAuthority)
504-
}
505-
for _, auth := range c.authorities {
506-
reportForAuthority(auth)
512+
reportForAuthority(c.topLevelAuthority)
513+
for _, a := range c.authorities {
514+
reportForAuthority(a)
507515
}
508516
}

internal/xds/xdsclient/clientimpl.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,14 @@ var (
7777
})
7878
xdsClientConnectedMetric = estats.RegisterInt64AsyncGauge(estats.MetricDescriptor{
7979
Name: "grpc.xds_client.connected",
80-
Description: "Experimental. A metric that is 1 if the xDS Client is connected to an xDS server, 0 otherwise.",
80+
Description: "A metric that is 1 if the xDS Client is connected to an xDS server, 0 otherwise.",
8181
Unit: "{connected}",
8282
Type: estats.MetricTypeIntAsyncGauge,
8383
Labels: []string{"grpc.target", "grpc.xds.server"},
8484
})
8585
xdsClientResourcesMetric = estats.RegisterInt64AsyncGauge(estats.MetricDescriptor{
8686
Name: "grpc.xds_client.resources",
87-
Description: "Experimental. Number of xDS resources currently cached.",
87+
Description: "Counts of xDS resources.",
8888
Unit: "{resource}",
8989
Type: estats.MetricTypeIntAsyncGauge,
9090
Labels: []string{"grpc.target", "grpc.xds.authority", "grpc.xds.cache_state", "grpc.xds.resource_type"},

0 commit comments

Comments
 (0)