Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

internal/stats: Add metrics recorder list and usage in ClientConn #7428

Merged
merged 5 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 11 additions & 15 deletions internal/stats/metrics_recorder_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,50 +49,46 @@ func NewMetricsRecorderList(shs []stats.Handler) *MetricsRecorderList {
}
}

func (l *MetricsRecorderList) RecordInt64Count(handle *estats.Int64CountHandle, incr int64, labels ...string) {
if got, want := len(handle.Labels)+len(handle.OptionalLabels), len(labels); got != want {
logger.Infof("length of labels passed to RecordInt64Count incorrect got: %v, want: %v", got, want)
func verifyLabels(labels []string, optionalLabels []string, labelsRecv ...string) {
if got, want := len(labelsRecv), len(labels)+len(optionalLabels); got != want {
logger.Fatalf("Length of labels passed to Record incorrect, got: %v, want: %v.", got, want)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO delete the logger now and call panic(fmt.Sprintf) here.

Also if we pass the descriptor let's print the metric name with %q as well. Maybe lose the got, want formatting which I believe is unique to Go test assertions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got rid of got, want. Recorded the metric name with %q from desc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I didn't mean to remove the counts.. Just to turn it into a more english-y thing than a Go test error message. E.g.

Like Received %d labels in call to record metric %q, but expected %d

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok noted switched to that.

}
}

func (l *MetricsRecorderList) RecordInt64Count(handle *estats.Int64CountHandle, incr int64, labels ...string) {
verifyLabels(handle.Labels, handle.OptionalLabels, labels...)

for _, metricRecorder := range l.metricsRecorders {
metricRecorder.RecordInt64Count(handle, incr, labels...)
}
}

func (l *MetricsRecorderList) RecordFloat64Count(handle *estats.Float64CountHandle, incr float64, labels ...string) {
if got, want := len(handle.Labels)+len(handle.OptionalLabels), len(labels); got != want {
logger.Infof("length of labels passed to RecordFloat64Count incorrect got: %v, want: %v", got, want)
}
verifyLabels(handle.Labels, handle.OptionalLabels, labels...)

for _, metricRecorder := range l.metricsRecorders {
metricRecorder.RecordFloat64Count(handle, incr, labels...)
}
}

func (l *MetricsRecorderList) RecordInt64Histo(handle *estats.Int64HistoHandle, incr int64, labels ...string) {
if got, want := len(handle.Labels)+len(handle.OptionalLabels), len(labels); got != want {
logger.Infof("length of labels passed to RecordInt64Histo incorrect got: %v, want: %v", got, want)
}
verifyLabels(handle.Labels, handle.OptionalLabels, labels...)

for _, metricRecorder := range l.metricsRecorders {
metricRecorder.RecordInt64Histo(handle, incr, labels...)
}
}

func (l *MetricsRecorderList) RecordFloat64Histo(handle *estats.Float64HistoHandle, incr float64, labels ...string) {
if got, want := len(handle.Labels)+len(handle.OptionalLabels), len(labels); got != want {
logger.Infof("length of labels passed to RecordFloat64Histo incorrect got: %v, want: %v", got, want)
}
verifyLabels(handle.Labels, handle.OptionalLabels, labels...)

for _, metricRecorder := range l.metricsRecorders {
metricRecorder.RecordFloat64Histo(handle, incr, labels...)
}
}

func (l *MetricsRecorderList) RecordInt64Gauge(handle *estats.Int64GaugeHandle, incr int64, labels ...string) {
if got, want := len(handle.Labels)+len(handle.OptionalLabels), len(labels); got != want {
logger.Infof("length of labels passed to RecordInt64Gauge incorrect got: %v, want: %v", got, want)
}
verifyLabels(handle.Labels, handle.OptionalLabels, labels...)

for _, metricRecorder := range l.metricsRecorders {
metricRecorder.RecordInt64Gauge(handle, incr, labels...)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,27 @@
*
*/

// Package test implements an e2e test for the Metrics Recorder List component
// of the Client Conn, and a TestMetricsRecorder utility.
package test
// Package stats_test implements an e2e test for the Metrics Recorder List
// component of the Client Conn.
package stats_test

import (
"context"
"fmt"
"log"
"strings"
"testing"
"time"

"google.golang.org/grpc"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/pickfirst"
"google.golang.org/grpc/credentials/insecure"
estats "google.golang.org/grpc/experimental/stats"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/grpctest"
istats "google.golang.org/grpc/internal/stats"
"google.golang.org/grpc/internal/testutils/stats"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
testpb "google.golang.org/grpc/interop/grpc_testing"
"google.golang.org/grpc/resolver"
Expand All @@ -48,14 +54,85 @@ func Test(t *testing.T) {
grpctest.RunSubTests(t, s{})
}

var (
intCountHandle = estats.RegisterInt64Count(estats.MetricDescriptor{
Name: "simple counter",
Description: "sum of all emissions from tests",
Unit: "int",
Labels: []string{"int counter label"},
OptionalLabels: []string{"int counter optional label"},
Default: false,
})
floatCountHandle = estats.RegisterFloat64Count(estats.MetricDescriptor{
Name: "float counter",
Description: "sum of all emissions from tests",
Unit: "float",
Labels: []string{"float counter label"},
OptionalLabels: []string{"float counter optional label"},
Default: false,
})
intHistoHandle = estats.RegisterInt64Histo(estats.MetricDescriptor{
Name: "int histo",
Description: "sum of all emissions from tests",
Unit: "int",
Labels: []string{"int histo label"},
OptionalLabels: []string{"int histo optional label"},
Default: false,
})
floatHistoHandle = estats.RegisterFloat64Histo(estats.MetricDescriptor{
Name: "float histo",
Description: "sum of all emissions from tests",
Unit: "float",
Labels: []string{"float histo label"},
OptionalLabels: []string{"float histo optional label"},
Default: false,
})
intGaugeHandle = estats.RegisterInt64Gauge(estats.MetricDescriptor{
Name: "simple gauge",
Description: "the most recent int emitted by test",
Unit: "int",
Labels: []string{"int gauge label"},
OptionalLabels: []string{"int gauge optional label"},
Default: false,
})
)

func init() {
balancer.Register(recordingLoadBalancerBuilder{})
}

const recordingLoadBalancerName = "recording_load_balancer"

type recordingLoadBalancerBuilder struct{}

func (recordingLoadBalancerBuilder) Name() string {
return recordingLoadBalancerName
}

func (recordingLoadBalancerBuilder) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer {
intCountHandle.Record(bOpts.MetricsRecorder, 1, "int counter label val", "int counter optional label val")
floatCountHandle.Record(bOpts.MetricsRecorder, 2, "float counter label val", "float counter optional label val")
intHistoHandle.Record(bOpts.MetricsRecorder, 3, "int histo label val", "int histo optional label val")
floatHistoHandle.Record(bOpts.MetricsRecorder, 4, "float histo label val", "float histo optional label val")
intGaugeHandle.Record(bOpts.MetricsRecorder, 5, "int gauge label val", "int gauge optional label val")

return &recordingLoadBalancer{
Balancer: balancer.Get(pickfirst.Name).Build(cc, bOpts),
}
}

type recordingLoadBalancer struct {
balancer.Balancer
}

// TestMetricsRecorderList tests the metrics recorder list functionality of the
// ClientConn. It configures a global and local stats handler Dial Option. These
// stats handlers implement the MetricsRecorder interface. It also configures a
// balancer which registers metrics and records on metrics at build time. This
// test then asserts that the recorded metrics show up on both configured stats
// handlers, and that metrics calls with the incorrect number of labels do not
// make their way to stats handlers.
// handlers.
func (s) TestMetricsRecorderList(t *testing.T) {
internal.SnapshotMetricRegistryForTesting.(func(t *testing.T))(t)
mr := manual.NewBuilderWithScheme("test-metrics-recorder-list")
defer mr.Close()

Expand All @@ -67,8 +144,8 @@ func (s) TestMetricsRecorderList(t *testing.T) {

// Create two stats.Handlers which also implement MetricsRecorder, configure
// one as a global dial option and one as a local dial option.
mr1 := NewTestMetricsRecorder(t, []string{})
mr2 := NewTestMetricsRecorder(t, []string{})
mr1 := stats.NewTestMetricsRecorder(t, []string{})
mr2 := stats.NewTestMetricsRecorder(t, []string{})

defer internal.ClearGlobalDialOptions()
internal.AddGlobalDialOptions.(func(opt ...grpc.DialOption))(grpc.WithStatsHandler(mr1))
Expand All @@ -87,7 +164,7 @@ func (s) TestMetricsRecorderList(t *testing.T) {
// to record.
tsc.UnaryCall(ctx, &testpb.SimpleRequest{})

mdWant := MetricsData{
mdWant := stats.MetricsData{
Handle: (*estats.MetricDescriptor)(intCountHandle),
IntIncr: 1,
LabelKeys: []string{"int counter label", "int counter optional label"},
Expand All @@ -96,7 +173,7 @@ func (s) TestMetricsRecorderList(t *testing.T) {
mr1.WaitForInt64Count(ctx, mdWant)
mr2.WaitForInt64Count(ctx, mdWant)

mdWant = MetricsData{
mdWant = stats.MetricsData{
Handle: (*estats.MetricDescriptor)(floatCountHandle),
FloatIncr: 2,
LabelKeys: []string{"float counter label", "float counter optional label"},
Expand All @@ -105,7 +182,7 @@ func (s) TestMetricsRecorderList(t *testing.T) {
mr1.WaitForFloat64Count(ctx, mdWant)
mr2.WaitForFloat64Count(ctx, mdWant)

mdWant = MetricsData{
mdWant = stats.MetricsData{
Handle: (*estats.MetricDescriptor)(intHistoHandle),
IntIncr: 3,
LabelKeys: []string{"int histo label", "int histo optional label"},
Expand All @@ -114,20 +191,44 @@ func (s) TestMetricsRecorderList(t *testing.T) {
mr1.WaitForInt64Histo(ctx, mdWant)
mr2.WaitForInt64Histo(ctx, mdWant)

mdWant = MetricsData{
mdWant = stats.MetricsData{
Handle: (*estats.MetricDescriptor)(floatHistoHandle),
FloatIncr: 4,
LabelKeys: []string{"float histo label", "float histo optional label"},
LabelVals: []string{"float histo label val", "float histo optional label val"},
}
mr1.WaitForFloat64Histo(ctx, mdWant)
mr2.WaitForFloat64Histo(ctx, mdWant)
mdWant = MetricsData{
mdWant = stats.MetricsData{
Handle: (*estats.MetricDescriptor)(intGaugeHandle),
IntIncr: 5, // Should ignore the 7 metrics recording point because emits wrong number of labels.
IntIncr: 5,
LabelKeys: []string{"int gauge label", "int gauge optional label"},
LabelVals: []string{"int gauge label val", "int gauge optional label val"},
}
mr1.WaitForInt64Gauge(ctx, mdWant)
mr2.WaitForInt64Gauge(ctx, mdWant)
}

// TestMetricRecorderListPanic tests that the metrics recorder list panics if
// received the wrong number of labels for a particular metric.
func (s) TestMetricRecorderListPanic(t *testing.T) {
internal.SnapshotMetricRegistryForTesting.(func(t *testing.T))(t)
intCountHandle := estats.RegisterInt64Count(estats.MetricDescriptor{
Name: "simple counter",
Description: "sum of all emissions from tests",
Unit: "int",
Labels: []string{"int counter label"},
OptionalLabels: []string{"int counter optional label"},
Default: false,
})
mrl := istats.NewMetricsRecorderList(nil)

want := "Length of labels passed to Record incorrect, got: 1, want: 2."
defer func() {
if r := recover(); !strings.Contains(fmt.Sprint(r), want) {
t.Errorf("expected panic contains %q, got %q", want, r)
}
}()

intCountHandle.Record(mrl, 1, "only one label")
}
102 changes: 0 additions & 102 deletions internal/stats/test/recording_load_balancer.go

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@
*
*/

// Package test implements an e2e test for the Metrics Recorder List component
// of the Client Conn, and a TestMetricsRecorder utility.
package test
// Package stats implements a TestMetricsRecorder utility.
package stats

import (
"context"
Expand Down Expand Up @@ -62,15 +61,15 @@
}

for _, metric := range metrics {
desc := estats.DescriptorForMetric(estats.Metric(metric))
switch desc.Type {
case estats.MetricTypeIntCount:
case estats.MetricTypeIntHisto:
case estats.MetricTypeIntGauge:
tmr.intValues[desc] = 0
case estats.MetricTypeFloatCount:
case estats.MetricTypeFloatHisto:
tmr.floatValues[desc] = 0

Check warning on line 72 in internal/testutils/stats/test_metrics_recorder.go

View check run for this annotation

Codecov / codecov/patch

internal/testutils/stats/test_metrics_recorder.go#L64-L72

Added lines #L64 - L72 were not covered by tests
}
}
return tmr
Expand All @@ -91,11 +90,11 @@
func (r *TestMetricsRecorder) WaitForInt64Count(ctx context.Context, metricsDataWant MetricsData) {
got, err := r.intCountCh.Receive(ctx)
if err != nil {
r.t.Fatalf("timeout waiting for int64Count")

Check warning on line 93 in internal/testutils/stats/test_metrics_recorder.go

View check run for this annotation

Codecov / codecov/patch

internal/testutils/stats/test_metrics_recorder.go#L93

Added line #L93 was not covered by tests
}
metricsDataGot := got.(MetricsData)
if diff := cmp.Diff(metricsDataGot, metricsDataWant); diff != "" {
r.t.Fatalf("int64count metricsData received unexpected value (-got, +want): %v", diff)

Check warning on line 97 in internal/testutils/stats/test_metrics_recorder.go

View check run for this annotation

Codecov / codecov/patch

internal/testutils/stats/test_metrics_recorder.go#L97

Added line #L97 was not covered by tests
}
}

Expand All @@ -113,11 +112,11 @@
func (r *TestMetricsRecorder) WaitForFloat64Count(ctx context.Context, metricsDataWant MetricsData) {
got, err := r.floatCountCh.Receive(ctx)
if err != nil {
r.t.Fatalf("timeout waiting for float64Count")

Check warning on line 115 in internal/testutils/stats/test_metrics_recorder.go

View check run for this annotation

Codecov / codecov/patch

internal/testutils/stats/test_metrics_recorder.go#L115

Added line #L115 was not covered by tests
}
metricsDataGot := got.(MetricsData)
if diff := cmp.Diff(metricsDataGot, metricsDataWant); diff != "" {
r.t.Fatalf("float64count metricsData received unexpected value (-got, +want): %v", diff)

Check warning on line 119 in internal/testutils/stats/test_metrics_recorder.go

View check run for this annotation

Codecov / codecov/patch

internal/testutils/stats/test_metrics_recorder.go#L119

Added line #L119 was not covered by tests
}
}

Expand All @@ -135,11 +134,11 @@
func (r *TestMetricsRecorder) WaitForInt64Histo(ctx context.Context, metricsDataWant MetricsData) {
got, err := r.intHistoCh.Receive(ctx)
if err != nil {
r.t.Fatalf("timeout waiting for int64Histo")

Check warning on line 137 in internal/testutils/stats/test_metrics_recorder.go

View check run for this annotation

Codecov / codecov/patch

internal/testutils/stats/test_metrics_recorder.go#L137

Added line #L137 was not covered by tests
}
metricsDataGot := got.(MetricsData)
if diff := cmp.Diff(metricsDataGot, metricsDataWant); diff != "" {
r.t.Fatalf("int64Histo metricsData received unexpected value (-got, +want): %v", diff)

Check warning on line 141 in internal/testutils/stats/test_metrics_recorder.go

View check run for this annotation

Codecov / codecov/patch

internal/testutils/stats/test_metrics_recorder.go#L141

Added line #L141 was not covered by tests
}
}

Expand All @@ -157,11 +156,11 @@
func (r *TestMetricsRecorder) WaitForFloat64Histo(ctx context.Context, metricsDataWant MetricsData) {
got, err := r.floatHistoCh.Receive(ctx)
if err != nil {
r.t.Fatalf("timeout waiting for float64Histo")

Check warning on line 159 in internal/testutils/stats/test_metrics_recorder.go

View check run for this annotation

Codecov / codecov/patch

internal/testutils/stats/test_metrics_recorder.go#L159

Added line #L159 was not covered by tests
}
metricsDataGot := got.(MetricsData)
if diff := cmp.Diff(metricsDataGot, metricsDataWant); diff != "" {
r.t.Fatalf("float64Histo metricsData received unexpected value (-got, +want): %v", diff)

Check warning on line 163 in internal/testutils/stats/test_metrics_recorder.go

View check run for this annotation

Codecov / codecov/patch

internal/testutils/stats/test_metrics_recorder.go#L163

Added line #L163 was not covered by tests
}
}

Expand All @@ -178,11 +177,11 @@
func (r *TestMetricsRecorder) WaitForInt64Gauge(ctx context.Context, metricsDataWant MetricsData) {
got, err := r.intGaugeCh.Receive(ctx)
if err != nil {
r.t.Fatalf("timeout waiting for int64Gauge")

Check warning on line 180 in internal/testutils/stats/test_metrics_recorder.go

View check run for this annotation

Codecov / codecov/patch

internal/testutils/stats/test_metrics_recorder.go#L180

Added line #L180 was not covered by tests
}
metricsDataGot := got.(MetricsData)
if diff := cmp.Diff(metricsDataGot, metricsDataWant); diff != "" {
r.t.Fatalf("int64Gauge metricsData received unexpected value (-got, +want): %v", diff)

Check warning on line 184 in internal/testutils/stats/test_metrics_recorder.go

View check run for this annotation

Codecov / codecov/patch

internal/testutils/stats/test_metrics_recorder.go#L184

Added line #L184 was not covered by tests
}
}

Expand All @@ -205,8 +204,8 @@

func (r *TestMetricsRecorder) HandleRPC(context.Context, stats.RPCStats) {}

func (r *TestMetricsRecorder) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context {
return ctx

Check warning on line 208 in internal/testutils/stats/test_metrics_recorder.go

View check run for this annotation

Codecov / codecov/patch

internal/testutils/stats/test_metrics_recorder.go#L207-L208

Added lines #L207 - L208 were not covered by tests
}

func (r *TestMetricsRecorder) HandleConn(context.Context, stats.ConnStats) {}

Check warning on line 211 in internal/testutils/stats/test_metrics_recorder.go

View check run for this annotation

Codecov / codecov/patch

internal/testutils/stats/test_metrics_recorder.go#L211

Added line #L211 was not covered by tests