Skip to content

Commit 6ff2e00

Browse files
committed
Add unit tests for WRR Metrics
1 parent 9a1fb9b commit 6ff2e00

File tree

4 files changed

+149
-5
lines changed

4 files changed

+149
-5
lines changed

balancer/weightedroundrobin/balancer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ func (w *weightedSubConn) updateConnectivityState(cs connectivity.State) connect
595595
// account the parameters. Returns 0 for blacked out or expired data, which
596596
// will cause the backend weight to be treated as the mean of the weights of the
597597
// other backends. If forScheduler is set to true, this function will emit
598-
// metrics through the mtrics registry.
598+
// metrics through the metrics registry.
599599
func (w *weightedSubConn) weight(now time.Time, weightExpirationPeriod, blackoutPeriod time.Duration, recordMetrics bool) (weight float64) {
600600
w.mu.Lock()
601601
defer w.mu.Unlock()
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
*
3+
* Copyright 2024 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package weightedroundrobin
20+
21+
import (
22+
"testing"
23+
"time"
24+
25+
"google.golang.org/grpc/internal/grpctest"
26+
iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
27+
"google.golang.org/grpc/internal/testutils/stats"
28+
)
29+
30+
type s struct {
31+
grpctest.Tester
32+
}
33+
34+
func Test(t *testing.T) {
35+
grpctest.RunSubTests(t, s{})
36+
}
37+
38+
// TestWRR_Metrics_SubConnWeight tests different scenarios for the weight call
39+
// on a weighted SubConn, and expects certain metrics for each of these
40+
// scenarios.
41+
func (s) TestWRR_Metrics_SubConnWeight(t *testing.T) {
42+
tmr := stats.NewTestMetricsRecorder(t, []string{"grpc.lb.wrr.rr_fallback", "grpc.lb.wrr.endpoint_weight_not_yet_usable", "grpc.lb.wrr.endpoint_weight_stale", "grpc.lb.wrr.endpoint_weights"})
43+
44+
wsc := &weightedSubConn{
45+
metricsRecorder: tmr,
46+
weightVal: 3,
47+
}
48+
49+
// The weighted SubConn's lastUpdated field hasn't been set, so this
50+
// SubConn's weight is not yet usable. Thus, should emit that endpoint
51+
// weight is not yet usable, and 0 for weight.
52+
wsc.weight(time.Now(), time.Second, time.Second, true)
53+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weight_stale", 0) // The endpoint weight has not expired so this is 0.
54+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weight_not_yet_usable", 1)
55+
// Unusable, so no endpoint weight (i.e. 0).
56+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weights", 0)
57+
tmr.ClearMetrics()
58+
59+
// Setup a scenario where the SubConn's weight expires. Thus, should emit
60+
// that endpoint weight is stale, and 0 for weight.
61+
wsc.lastUpdated = time.Now()
62+
wsc.weight(time.Now().Add(100*time.Second), 2*time.Second, time.Second, true)
63+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weight_stale", 1)
64+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weight_not_yet_usable", 0)
65+
// Unusable, so no endpoint weight (i.e. 0).
66+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weights", 0)
67+
tmr.ClearMetrics()
68+
69+
// Setup a scenario where the SubConn's weight is in the blackout period.
70+
// Thus, should emit that endpoint weight is not yet usable, and 0 for
71+
// weight.
72+
wsc.weight(time.Now(), time.Minute, 10*time.Second, true)
73+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weight_stale", 0)
74+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weight_not_yet_usable", 1)
75+
// Unusable, so no endpoint weight (i.e. 0).
76+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weights", 0)
77+
tmr.ClearMetrics()
78+
79+
// Setup a scenario where SubConn's weight is what is persists in weight
80+
// field. This is triggered by last update being past blackout period and
81+
// before weight update period. Should thus emit that endpoint weight is 3,
82+
// and no other metrics.
83+
wsc.nonEmptySince = time.Now()
84+
wsc.weight(time.Now().Add(10*time.Second), time.Minute, time.Second, true)
85+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weight_stale", 0)
86+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weight_not_yet_usable", 0)
87+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weights", 3)
88+
tmr.ClearMetrics()
89+
90+
// Setup a scenario where a SubConn's weight both expires and is within the
91+
// blackout period. In this case, weight expiry should take precedence with
92+
// respect to metrics emitted. Thus, should emit that endpoint weight is not
93+
// yet usable, and 0 for weight.
94+
wsc.weight(time.Now().Add(10*time.Second), time.Second, time.Minute, true)
95+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weight_stale", 1)
96+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weight_not_yet_usable", 0)
97+
tmr.AssertDataForMetric("grpc.lb.wrr.endpoint_weights", 0)
98+
tmr.ClearMetrics()
99+
}
100+
101+
// TestWRR_Metrics_Scheduler_RR_Fallback tests the round robin fallback metric
102+
// for scheduler updates. It tests the case with one SubConn, and two SubConns
103+
// with no weights. Both of these should emit a count metric for round robin
104+
// fallback.
105+
func (s) TestWRR_Metrics_Scheduler_RR_Fallback(t *testing.T) {
106+
tmr := stats.NewTestMetricsRecorder(t, []string{"grpc.lb.wrr.rr_fallback", "grpc.lb.wrr.endpoint_weight_not_yet_usable", "grpc.lb.wrr.endpoint_weight_stale", "grpc.lb.wrr.endpoint_weights"})
107+
wsc := &weightedSubConn{
108+
metricsRecorder: tmr,
109+
weightVal: 0,
110+
}
111+
112+
p := &picker{
113+
cfg: &lbConfig{
114+
BlackoutPeriod: iserviceconfig.Duration(10 * time.Second),
115+
WeightExpirationPeriod: iserviceconfig.Duration(3 * time.Minute),
116+
},
117+
subConns: []*weightedSubConn{wsc},
118+
metricsRecorder: tmr,
119+
}
120+
// There is only one SubConn, so no matter if the SubConn has a weight or
121+
// not will fallback to round robin.
122+
p.regenerateScheduler()
123+
tmr.AssertDataForMetric("grpc.lb.wrr.rr_fallback", 1)
124+
tmr.ClearMetrics()
125+
126+
// With two SubConns, if neither of them have weights, it will also fallback
127+
// to round robin.
128+
wsc2 := &weightedSubConn{
129+
target: "target",
130+
metricsRecorder: tmr,
131+
weightVal: 0,
132+
}
133+
p.subConns = append(p.subConns, wsc2)
134+
p.regenerateScheduler()
135+
tmr.AssertDataForMetric("grpc.lb.wrr.rr_fallback", 1)
136+
}

internal/testutils/stats/test_metrics_recorder.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,16 @@ func (r *TestMetricsRecorder) PollForDataForMetric(ctx context.Context, metricNa
104104
r.t.Fatalf("Timeout waiting for data %v for metric %v", wantVal, metricName)
105105
}
106106

107+
// ClearMetrics clears the metrics data stores of the test metrics recorder by
108+
// setting all the data to 0.
109+
func (r *TestMetricsRecorder) ClearMetrics() {
110+
r.mu.Lock()
111+
defer r.mu.Unlock()
112+
for metric := range r.data {
113+
r.data[metric] = 0
114+
}
115+
}
116+
107117
type MetricsData struct {
108118
Handle *estats.MetricDescriptor
109119

stats/opentelemetry/e2e_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package opentelemetry_test
1919
import (
2020
"context"
2121
"fmt"
22-
"google.golang.org/grpc/internal/grpcsync"
2322

2423
"io"
2524
"testing"
@@ -39,6 +38,7 @@ import (
3938
"google.golang.org/grpc"
4039
"google.golang.org/grpc/credentials/insecure"
4140
"google.golang.org/grpc/encoding/gzip"
41+
"google.golang.org/grpc/internal/grpcsync"
4242
"google.golang.org/grpc/internal/grpctest"
4343
"google.golang.org/grpc/internal/stubserver"
4444
itestutils "google.golang.org/grpc/internal/testutils"
@@ -474,9 +474,7 @@ func (s) TestWRRMetrics(t *testing.T) {
474474
receivedExpectedMetrics := grpcsync.NewEvent()
475475
go func() {
476476
for i := 0; i < 100; i++ {
477-
if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
478-
t.Fatalf("EmptyCall() = %v, want <nil>", err)
479-
}
477+
client.EmptyCall(ctx, &testpb.Empty{})
480478
if receivedExpectedMetrics.HasFired() {
481479
break
482480
}

0 commit comments

Comments
 (0)