mirror of https://github.com/grpc/grpc-go.git
internal/stats: Add metrics recorder list and usage in ClientConn (#7428)
This commit is contained in:
parent
47be8a6808
commit
84a4ef1623
|
@ -30,6 +30,7 @@ import (
|
|||
"google.golang.org/grpc/channelz"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
estats "google.golang.org/grpc/experimental/stats"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/internal"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
@ -256,6 +257,10 @@ type BuildOptions struct {
|
|||
// same resolver.Target as passed to the resolver. See the documentation for
|
||||
// the resolver.Target type for details about what it contains.
|
||||
Target resolver.Target
|
||||
// MetricsRecorder is the metrics recorder that balancers can use to record
|
||||
// metrics. Balancer implementations which do not register metrics on
|
||||
// metrics registry and record on them can ignore this field.
|
||||
MetricsRecorder estats.MetricsRecorder
|
||||
}
|
||||
|
||||
// Builder creates a balancer.
|
||||
|
|
|
@ -82,6 +82,7 @@ func newCCBalancerWrapper(cc *ClientConn) *ccBalancerWrapper {
|
|||
CustomUserAgent: cc.dopts.copts.UserAgent,
|
||||
ChannelzParent: cc.channelz,
|
||||
Target: cc.parsedTarget,
|
||||
MetricsRecorder: cc.metricsRecorderList,
|
||||
},
|
||||
serializer: grpcsync.NewCallbackSerializer(ctx),
|
||||
serializerCancel: cancel,
|
||||
|
|
|
@ -40,6 +40,7 @@ import (
|
|||
"google.golang.org/grpc/internal/grpcsync"
|
||||
"google.golang.org/grpc/internal/idle"
|
||||
iresolver "google.golang.org/grpc/internal/resolver"
|
||||
"google.golang.org/grpc/internal/stats"
|
||||
"google.golang.org/grpc/internal/transport"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/resolver"
|
||||
|
@ -195,8 +196,11 @@ func NewClient(target string, opts ...DialOption) (conn *ClientConn, err error)
|
|||
cc.csMgr = newConnectivityStateManager(cc.ctx, cc.channelz)
|
||||
cc.pickerWrapper = newPickerWrapper(cc.dopts.copts.StatsHandlers)
|
||||
|
||||
cc.metricsRecorderList = stats.NewMetricsRecorderList(cc.dopts.copts.StatsHandlers)
|
||||
|
||||
cc.initIdleStateLocked() // Safe to call without the lock, since nothing else has a reference to cc.
|
||||
cc.idlenessMgr = idle.NewManager((*idler)(cc), cc.dopts.idleTimeout)
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
|
@ -591,13 +595,14 @@ type ClientConn struct {
|
|||
cancel context.CancelFunc // Cancelled on close.
|
||||
|
||||
// The following are initialized at dial time, and are read-only after that.
|
||||
target string // User's dial target.
|
||||
parsedTarget resolver.Target // See initParsedTargetAndResolverBuilder().
|
||||
authority string // See initAuthority().
|
||||
dopts dialOptions // Default and user specified dial options.
|
||||
channelz *channelz.Channel // Channelz object.
|
||||
resolverBuilder resolver.Builder // See initParsedTargetAndResolverBuilder().
|
||||
idlenessMgr *idle.Manager
|
||||
target string // User's dial target.
|
||||
parsedTarget resolver.Target // See initParsedTargetAndResolverBuilder().
|
||||
authority string // See initAuthority().
|
||||
dopts dialOptions // Default and user specified dial options.
|
||||
channelz *channelz.Channel // Channelz object.
|
||||
resolverBuilder resolver.Builder // See initParsedTargetAndResolverBuilder().
|
||||
idlenessMgr *idle.Manager
|
||||
metricsRecorderList *stats.MetricsRecorderList
|
||||
|
||||
// The following provide their own synchronization, and therefore don't
|
||||
// require cc.mu to be held to access them.
|
||||
|
|
|
@ -81,6 +81,12 @@ const (
|
|||
// on.
|
||||
type Int64CountHandle MetricDescriptor
|
||||
|
||||
// Descriptor returns the int64 count handle typecast to a pointer to a
|
||||
// MetricDescriptor.
|
||||
func (h *Int64CountHandle) Descriptor() *MetricDescriptor {
|
||||
return (*MetricDescriptor)(h)
|
||||
}
|
||||
|
||||
// Record records the int64 count value on the metrics recorder provided.
|
||||
func (h *Int64CountHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) {
|
||||
recorder.RecordInt64Count(h, incr, labels...)
|
||||
|
@ -90,6 +96,12 @@ func (h *Int64CountHandle) Record(recorder MetricsRecorder, incr int64, labels .
|
|||
// passed at the recording point in order to know which metric to record on.
|
||||
type Float64CountHandle MetricDescriptor
|
||||
|
||||
// Descriptor returns the float64 count handle typecast to a pointer to a
|
||||
// MetricDescriptor.
|
||||
func (h *Float64CountHandle) Descriptor() *MetricDescriptor {
|
||||
return (*MetricDescriptor)(h)
|
||||
}
|
||||
|
||||
// Record records the float64 count value on the metrics recorder provided.
|
||||
func (h *Float64CountHandle) Record(recorder MetricsRecorder, incr float64, labels ...string) {
|
||||
recorder.RecordFloat64Count(h, incr, labels...)
|
||||
|
@ -99,6 +111,12 @@ func (h *Float64CountHandle) Record(recorder MetricsRecorder, incr float64, labe
|
|||
// is passed at the recording point in order to know which metric to record on.
|
||||
type Int64HistoHandle MetricDescriptor
|
||||
|
||||
// Descriptor returns the int64 histo handle typecast to a pointer to a
|
||||
// MetricDescriptor.
|
||||
func (h *Int64HistoHandle) Descriptor() *MetricDescriptor {
|
||||
return (*MetricDescriptor)(h)
|
||||
}
|
||||
|
||||
// Record records the int64 histo value on the metrics recorder provided.
|
||||
func (h *Int64HistoHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) {
|
||||
recorder.RecordInt64Histo(h, incr, labels...)
|
||||
|
@ -109,6 +127,12 @@ func (h *Int64HistoHandle) Record(recorder MetricsRecorder, incr int64, labels .
|
|||
// record on.
|
||||
type Float64HistoHandle MetricDescriptor
|
||||
|
||||
// Descriptor returns the float64 histo handle typecast to a pointer to a
|
||||
// MetricDescriptor.
|
||||
func (h *Float64HistoHandle) Descriptor() *MetricDescriptor {
|
||||
return (*MetricDescriptor)(h)
|
||||
}
|
||||
|
||||
// Record records the float64 histo value on the metrics recorder provided.
|
||||
func (h *Float64HistoHandle) Record(recorder MetricsRecorder, incr float64, labels ...string) {
|
||||
recorder.RecordFloat64Histo(h, incr, labels...)
|
||||
|
@ -118,6 +142,12 @@ func (h *Float64HistoHandle) Record(recorder MetricsRecorder, incr float64, labe
|
|||
// passed at the recording point in order to know which metric to record on.
|
||||
type Int64GaugeHandle MetricDescriptor
|
||||
|
||||
// Descriptor returns the int64 gauge handle typecast to a pointer to a
|
||||
// MetricDescriptor.
|
||||
func (h *Int64GaugeHandle) Descriptor() *MetricDescriptor {
|
||||
return (*MetricDescriptor)(h)
|
||||
}
|
||||
|
||||
// Record records the int64 histo value on the metrics recorder provided.
|
||||
func (h *Int64GaugeHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) {
|
||||
recorder.RecordInt64Gauge(h, incr, labels...)
|
||||
|
|
|
@ -112,27 +112,27 @@ func (s) TestMetricRegistry(t *testing.T) {
|
|||
// The Metric Descriptor in the handle should be able to identify the metric
|
||||
// information. This is the key passed to metrics recorder to identify
|
||||
// metric.
|
||||
if got := fmr.intValues[(*MetricDescriptor)(intCountHandle1)]; got != 1 {
|
||||
if got := fmr.intValues[intCountHandle1.Descriptor()]; got != 1 {
|
||||
t.Fatalf("fmr.intValues[intCountHandle1.MetricDescriptor] got %v, want: %v", got, 1)
|
||||
}
|
||||
|
||||
floatCountHandle1.Record(fmr, 1.2, []string{"some label value", "some optional label value"}...)
|
||||
if got := fmr.floatValues[(*MetricDescriptor)(floatCountHandle1)]; got != 1.2 {
|
||||
if got := fmr.floatValues[floatCountHandle1.Descriptor()]; got != 1.2 {
|
||||
t.Fatalf("fmr.floatValues[floatCountHandle1.MetricDescriptor] got %v, want: %v", got, 1.2)
|
||||
}
|
||||
|
||||
intHistoHandle1.Record(fmr, 3, []string{"some label value", "some optional label value"}...)
|
||||
if got := fmr.intValues[(*MetricDescriptor)(intHistoHandle1)]; got != 3 {
|
||||
if got := fmr.intValues[intHistoHandle1.Descriptor()]; got != 3 {
|
||||
t.Fatalf("fmr.intValues[intHistoHandle1.MetricDescriptor] got %v, want: %v", got, 3)
|
||||
}
|
||||
|
||||
floatHistoHandle1.Record(fmr, 4.3, []string{"some label value", "some optional label value"}...)
|
||||
if got := fmr.floatValues[(*MetricDescriptor)(floatHistoHandle1)]; got != 4.3 {
|
||||
if got := fmr.floatValues[floatHistoHandle1.Descriptor()]; got != 4.3 {
|
||||
t.Fatalf("fmr.floatValues[floatHistoHandle1.MetricDescriptor] got %v, want: %v", got, 4.3)
|
||||
}
|
||||
|
||||
intGaugeHandle1.Record(fmr, 7, []string{"some label value", "some optional label value"}...)
|
||||
if got := fmr.intValues[(*MetricDescriptor)(intGaugeHandle1)]; got != 7 {
|
||||
if got := fmr.intValues[intGaugeHandle1.Descriptor()]; got != 7 {
|
||||
t.Fatalf("fmr.intValues[intGaugeHandle1.MetricDescriptor] got %v, want: %v", got, 7)
|
||||
}
|
||||
}
|
||||
|
@ -170,28 +170,28 @@ func TestNumerousIntCounts(t *testing.T) {
|
|||
fmr := newFakeMetricsRecorder(t)
|
||||
|
||||
intCountHandle1.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
|
||||
got := []int64{fmr.intValues[(*MetricDescriptor)(intCountHandle1)], fmr.intValues[(*MetricDescriptor)(intCountHandle2)], fmr.intValues[(*MetricDescriptor)(intCountHandle3)]}
|
||||
got := []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
|
||||
want := []int64{1, 0, 0}
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Fatalf("fmr.intValues (-got, +want): %v", diff)
|
||||
}
|
||||
|
||||
intCountHandle2.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
|
||||
got = []int64{fmr.intValues[(*MetricDescriptor)(intCountHandle1)], fmr.intValues[(*MetricDescriptor)(intCountHandle2)], fmr.intValues[(*MetricDescriptor)(intCountHandle3)]}
|
||||
got = []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
|
||||
want = []int64{1, 1, 0}
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Fatalf("fmr.intValues (-got, +want): %v", diff)
|
||||
}
|
||||
|
||||
intCountHandle3.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
|
||||
got = []int64{fmr.intValues[(*MetricDescriptor)(intCountHandle1)], fmr.intValues[(*MetricDescriptor)(intCountHandle2)], fmr.intValues[(*MetricDescriptor)(intCountHandle3)]}
|
||||
got = []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
|
||||
want = []int64{1, 1, 1}
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Fatalf("fmr.intValues (-got, +want): %v", diff)
|
||||
}
|
||||
|
||||
intCountHandle3.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
|
||||
got = []int64{fmr.intValues[(*MetricDescriptor)(intCountHandle1)], fmr.intValues[(*MetricDescriptor)(intCountHandle2)], fmr.intValues[(*MetricDescriptor)(intCountHandle3)]}
|
||||
got = []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
|
||||
want = []int64{1, 1, 2}
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Fatalf("fmr.intValues (-got, +want): %v", diff)
|
||||
|
@ -236,26 +236,26 @@ func verifyLabels(t *testing.T, labelsWant []string, optionalLabelsWant []string
|
|||
}
|
||||
|
||||
func (r *fakeMetricsRecorder) RecordInt64Count(handle *Int64CountHandle, incr int64, labels ...string) {
|
||||
verifyLabels(r.t, (*MetricDescriptor)(handle).Labels, (*MetricDescriptor)(handle).OptionalLabels, labels)
|
||||
r.intValues[(*MetricDescriptor)(handle)] += incr
|
||||
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
|
||||
r.intValues[handle.Descriptor()] += incr
|
||||
}
|
||||
|
||||
func (r *fakeMetricsRecorder) RecordFloat64Count(handle *Float64CountHandle, incr float64, labels ...string) {
|
||||
verifyLabels(r.t, (*MetricDescriptor)(handle).Labels, (*MetricDescriptor)(handle).OptionalLabels, labels)
|
||||
r.floatValues[(*MetricDescriptor)(handle)] += incr
|
||||
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
|
||||
r.floatValues[handle.Descriptor()] += incr
|
||||
}
|
||||
|
||||
func (r *fakeMetricsRecorder) RecordInt64Histo(handle *Int64HistoHandle, incr int64, labels ...string) {
|
||||
verifyLabels(r.t, (*MetricDescriptor)(handle).Labels, (*MetricDescriptor)(handle).OptionalLabels, labels)
|
||||
r.intValues[(*MetricDescriptor)(handle)] += incr
|
||||
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
|
||||
r.intValues[handle.Descriptor()] += incr
|
||||
}
|
||||
|
||||
func (r *fakeMetricsRecorder) RecordFloat64Histo(handle *Float64HistoHandle, incr float64, labels ...string) {
|
||||
verifyLabels(r.t, (*MetricDescriptor)(handle).Labels, (*MetricDescriptor)(handle).OptionalLabels, labels)
|
||||
r.floatValues[(*MetricDescriptor)(handle)] += incr
|
||||
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
|
||||
r.floatValues[handle.Descriptor()] += incr
|
||||
}
|
||||
|
||||
func (r *fakeMetricsRecorder) RecordInt64Gauge(handle *Int64GaugeHandle, incr int64, labels ...string) {
|
||||
verifyLabels(r.t, (*MetricDescriptor)(handle).Labels, (*MetricDescriptor)(handle).OptionalLabels, labels)
|
||||
r.intValues[(*MetricDescriptor)(handle)] += incr
|
||||
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
|
||||
r.intValues[handle.Descriptor()] += incr
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package stats
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
estats "google.golang.org/grpc/experimental/stats"
|
||||
"google.golang.org/grpc/stats"
|
||||
)
|
||||
|
||||
// MetricsRecorderList forwards Record calls to all of its metricsRecorders.
|
||||
//
|
||||
// It eats any record calls where the label values provided do not match the
|
||||
// number of label keys.
|
||||
type MetricsRecorderList struct {
|
||||
// metricsRecorders are the metrics recorders this list will forward to.
|
||||
metricsRecorders []estats.MetricsRecorder
|
||||
}
|
||||
|
||||
// NewMetricsRecorderList creates a new metric recorder list with all the stats
|
||||
// handlers provided which implement the MetricsRecorder interface.
|
||||
// If no stats handlers provided implement the MetricsRecorder interface,
|
||||
// the MetricsRecorder list returned is a no-op.
|
||||
func NewMetricsRecorderList(shs []stats.Handler) *MetricsRecorderList {
|
||||
var mrs []estats.MetricsRecorder
|
||||
for _, sh := range shs {
|
||||
if mr, ok := sh.(estats.MetricsRecorder); ok {
|
||||
mrs = append(mrs, mr)
|
||||
}
|
||||
}
|
||||
return &MetricsRecorderList{
|
||||
metricsRecorders: mrs,
|
||||
}
|
||||
}
|
||||
|
||||
func verifyLabels(desc *estats.MetricDescriptor, labelsRecv ...string) {
|
||||
if got, want := len(labelsRecv), len(desc.Labels)+len(desc.OptionalLabels); got != want {
|
||||
panic(fmt.Sprintf("Received %d labels in call to record metric %q, but expected %d.", got, desc.Name, want))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *MetricsRecorderList) RecordInt64Count(handle *estats.Int64CountHandle, incr int64, labels ...string) {
|
||||
verifyLabels(handle.Descriptor(), labels...)
|
||||
|
||||
for _, metricRecorder := range l.metricsRecorders {
|
||||
metricRecorder.RecordInt64Count(handle, incr, labels...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *MetricsRecorderList) RecordFloat64Count(handle *estats.Float64CountHandle, incr float64, labels ...string) {
|
||||
verifyLabels(handle.Descriptor(), labels...)
|
||||
|
||||
for _, metricRecorder := range l.metricsRecorders {
|
||||
metricRecorder.RecordFloat64Count(handle, incr, labels...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *MetricsRecorderList) RecordInt64Histo(handle *estats.Int64HistoHandle, incr int64, labels ...string) {
|
||||
verifyLabels(handle.Descriptor(), labels...)
|
||||
|
||||
for _, metricRecorder := range l.metricsRecorders {
|
||||
metricRecorder.RecordInt64Histo(handle, incr, labels...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *MetricsRecorderList) RecordFloat64Histo(handle *estats.Float64HistoHandle, incr float64, labels ...string) {
|
||||
verifyLabels(handle.Descriptor(), labels...)
|
||||
|
||||
for _, metricRecorder := range l.metricsRecorders {
|
||||
metricRecorder.RecordFloat64Histo(handle, incr, labels...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *MetricsRecorderList) RecordInt64Gauge(handle *estats.Int64GaugeHandle, incr int64, labels ...string) {
|
||||
verifyLabels(handle.Descriptor(), labels...)
|
||||
|
||||
for _, metricRecorder := range l.metricsRecorders {
|
||||
metricRecorder.RecordInt64Gauge(handle, incr, labels...)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// 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"
|
||||
"google.golang.org/grpc/resolver/manual"
|
||||
"google.golang.org/grpc/serviceconfig"
|
||||
)
|
||||
|
||||
var defaultTestTimeout = 5 * time.Second
|
||||
|
||||
type s struct {
|
||||
grpctest.Tester
|
||||
}
|
||||
|
||||
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.
|
||||
func (s) TestMetricsRecorderList(t *testing.T) {
|
||||
internal.SnapshotMetricRegistryForTesting.(func(t *testing.T))(t)
|
||||
mr := manual.NewBuilderWithScheme("test-metrics-recorder-list")
|
||||
defer mr.Close()
|
||||
|
||||
json := `{"loadBalancingConfig": [{"recording_load_balancer":{}}]}`
|
||||
sc := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(json)
|
||||
mr.InitialState(resolver.State{
|
||||
ServiceConfig: sc,
|
||||
})
|
||||
|
||||
// Create two stats.Handlers which also implement MetricsRecorder, configure
|
||||
// one as a global dial option and one as a local dial option.
|
||||
mr1 := stats.NewTestMetricsRecorder(t, []string{})
|
||||
mr2 := stats.NewTestMetricsRecorder(t, []string{})
|
||||
|
||||
defer internal.ClearGlobalDialOptions()
|
||||
internal.AddGlobalDialOptions.(func(opt ...grpc.DialOption))(grpc.WithStatsHandler(mr1))
|
||||
|
||||
cc, err := grpc.NewClient(mr.Scheme()+":///", grpc.WithResolvers(mr), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(mr2))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to dial: %v", err)
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
tsc := testgrpc.NewTestServiceClient(cc)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Trigger the recording_load_balancer to build, which will trigger metrics
|
||||
// to record.
|
||||
tsc.UnaryCall(ctx, &testpb.SimpleRequest{})
|
||||
|
||||
mdWant := stats.MetricsData{
|
||||
Handle: intCountHandle.Descriptor(),
|
||||
IntIncr: 1,
|
||||
LabelKeys: []string{"int counter label", "int counter optional label"},
|
||||
LabelVals: []string{"int counter label val", "int counter optional label val"},
|
||||
}
|
||||
mr1.WaitForInt64Count(ctx, mdWant)
|
||||
mr2.WaitForInt64Count(ctx, mdWant)
|
||||
|
||||
mdWant = stats.MetricsData{
|
||||
Handle: floatCountHandle.Descriptor(),
|
||||
FloatIncr: 2,
|
||||
LabelKeys: []string{"float counter label", "float counter optional label"},
|
||||
LabelVals: []string{"float counter label val", "float counter optional label val"},
|
||||
}
|
||||
mr1.WaitForFloat64Count(ctx, mdWant)
|
||||
mr2.WaitForFloat64Count(ctx, mdWant)
|
||||
|
||||
mdWant = stats.MetricsData{
|
||||
Handle: intHistoHandle.Descriptor(),
|
||||
IntIncr: 3,
|
||||
LabelKeys: []string{"int histo label", "int histo optional label"},
|
||||
LabelVals: []string{"int histo label val", "int histo optional label val"},
|
||||
}
|
||||
mr1.WaitForInt64Histo(ctx, mdWant)
|
||||
mr2.WaitForInt64Histo(ctx, mdWant)
|
||||
|
||||
mdWant = stats.MetricsData{
|
||||
Handle: floatHistoHandle.Descriptor(),
|
||||
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 = stats.MetricsData{
|
||||
Handle: intGaugeHandle.Descriptor(),
|
||||
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 := `Received 1 labels in call to record metric "simple counter", but expected 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")
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package stats implements a TestMetricsRecorder utility.
|
||||
package stats
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
estats "google.golang.org/grpc/experimental/stats"
|
||||
"google.golang.org/grpc/internal/testutils"
|
||||
"google.golang.org/grpc/stats"
|
||||
)
|
||||
|
||||
// TestMetricsRecorder is a MetricsRecorder to be used in tests. It sends
|
||||
// recording events on channels and provides helpers to check if certain events
|
||||
// have taken place. It also persists metrics data keyed on the metrics
|
||||
// descriptor.
|
||||
type TestMetricsRecorder struct {
|
||||
t *testing.T
|
||||
|
||||
intCountCh *testutils.Channel
|
||||
floatCountCh *testutils.Channel
|
||||
intHistoCh *testutils.Channel
|
||||
floatHistoCh *testutils.Channel
|
||||
intGaugeCh *testutils.Channel
|
||||
}
|
||||
|
||||
func NewTestMetricsRecorder(t *testing.T, metrics []string) *TestMetricsRecorder {
|
||||
return &TestMetricsRecorder{
|
||||
t: t,
|
||||
|
||||
intCountCh: testutils.NewChannelWithSize(10),
|
||||
floatCountCh: testutils.NewChannelWithSize(10),
|
||||
intHistoCh: testutils.NewChannelWithSize(10),
|
||||
floatHistoCh: testutils.NewChannelWithSize(10),
|
||||
intGaugeCh: testutils.NewChannelWithSize(10),
|
||||
}
|
||||
}
|
||||
|
||||
type MetricsData struct {
|
||||
Handle *estats.MetricDescriptor
|
||||
|
||||
// Only set based on the type of metric. So only one of IntIncr or FloatIncr
|
||||
// is set.
|
||||
IntIncr int64
|
||||
FloatIncr float64
|
||||
|
||||
LabelKeys []string
|
||||
LabelVals []string
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
metricsDataGot := got.(MetricsData)
|
||||
if diff := cmp.Diff(metricsDataGot, metricsDataWant); diff != "" {
|
||||
r.t.Fatalf("int64count metricsData received unexpected value (-got, +want): %v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TestMetricsRecorder) RecordInt64Count(handle *estats.Int64CountHandle, incr int64, labels ...string) {
|
||||
r.intCountCh.Send(MetricsData{
|
||||
Handle: handle.Descriptor(),
|
||||
IntIncr: incr,
|
||||
LabelKeys: append(handle.Labels, handle.OptionalLabels...),
|
||||
LabelVals: labels,
|
||||
})
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
metricsDataGot := got.(MetricsData)
|
||||
if diff := cmp.Diff(metricsDataGot, metricsDataWant); diff != "" {
|
||||
r.t.Fatalf("float64count metricsData received unexpected value (-got, +want): %v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TestMetricsRecorder) RecordFloat64Count(handle *estats.Float64CountHandle, incr float64, labels ...string) {
|
||||
r.floatCountCh.Send(MetricsData{
|
||||
Handle: handle.Descriptor(),
|
||||
FloatIncr: incr,
|
||||
LabelKeys: append(handle.Labels, handle.OptionalLabels...),
|
||||
LabelVals: labels,
|
||||
})
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
metricsDataGot := got.(MetricsData)
|
||||
if diff := cmp.Diff(metricsDataGot, metricsDataWant); diff != "" {
|
||||
r.t.Fatalf("int64Histo metricsData received unexpected value (-got, +want): %v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TestMetricsRecorder) RecordInt64Histo(handle *estats.Int64HistoHandle, incr int64, labels ...string) {
|
||||
r.intHistoCh.Send(MetricsData{
|
||||
Handle: handle.Descriptor(),
|
||||
IntIncr: incr,
|
||||
LabelKeys: append(handle.Labels, handle.OptionalLabels...),
|
||||
LabelVals: labels,
|
||||
})
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
metricsDataGot := got.(MetricsData)
|
||||
if diff := cmp.Diff(metricsDataGot, metricsDataWant); diff != "" {
|
||||
r.t.Fatalf("float64Histo metricsData received unexpected value (-got, +want): %v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TestMetricsRecorder) RecordFloat64Histo(handle *estats.Float64HistoHandle, incr float64, labels ...string) {
|
||||
r.floatHistoCh.Send(MetricsData{
|
||||
Handle: handle.Descriptor(),
|
||||
FloatIncr: incr,
|
||||
LabelKeys: append(handle.Labels, handle.OptionalLabels...),
|
||||
LabelVals: labels,
|
||||
})
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
metricsDataGot := got.(MetricsData)
|
||||
if diff := cmp.Diff(metricsDataGot, metricsDataWant); diff != "" {
|
||||
r.t.Fatalf("int64Gauge metricsData received unexpected value (-got, +want): %v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TestMetricsRecorder) RecordInt64Gauge(handle *estats.Int64GaugeHandle, incr int64, labels ...string) {
|
||||
r.intGaugeCh.Send(MetricsData{
|
||||
Handle: handle.Descriptor(),
|
||||
IntIncr: incr,
|
||||
LabelKeys: append(handle.Labels, handle.OptionalLabels...),
|
||||
LabelVals: labels,
|
||||
})
|
||||
}
|
||||
|
||||
// To implement a stats.Handler, which allows it to be set as a dial option:
|
||||
|
||||
func (r *TestMetricsRecorder) TagRPC(ctx context.Context, _ *stats.RPCTagInfo) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (r *TestMetricsRecorder) HandleRPC(context.Context, stats.RPCStats) {}
|
||||
|
||||
func (r *TestMetricsRecorder) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (r *TestMetricsRecorder) HandleConn(context.Context, stats.ConnStats) {}
|
|
@ -329,7 +329,7 @@ func (rm *registryMetrics) registerMetrics(metrics *estats.Metrics, meter otelme
|
|||
}
|
||||
|
||||
func (rm *registryMetrics) RecordInt64Count(handle *estats.Int64CountHandle, incr int64, labels ...string) {
|
||||
desc := (*estats.MetricDescriptor)(handle)
|
||||
desc := handle.Descriptor()
|
||||
ao := optionFromLabels(desc.Labels, desc.OptionalLabels, rm.optionalLabels, labels...)
|
||||
|
||||
if ic, ok := rm.intCounts[desc]; ok {
|
||||
|
@ -338,7 +338,7 @@ func (rm *registryMetrics) RecordInt64Count(handle *estats.Int64CountHandle, inc
|
|||
}
|
||||
|
||||
func (rm *registryMetrics) RecordFloat64Count(handle *estats.Float64CountHandle, incr float64, labels ...string) {
|
||||
desc := (*estats.MetricDescriptor)(handle)
|
||||
desc := handle.Descriptor()
|
||||
ao := optionFromLabels(desc.Labels, desc.OptionalLabels, rm.optionalLabels, labels...)
|
||||
if fc, ok := rm.floatCounts[desc]; ok {
|
||||
fc.Add(context.TODO(), incr, ao)
|
||||
|
@ -346,7 +346,7 @@ func (rm *registryMetrics) RecordFloat64Count(handle *estats.Float64CountHandle,
|
|||
}
|
||||
|
||||
func (rm *registryMetrics) RecordInt64Histo(handle *estats.Int64HistoHandle, incr int64, labels ...string) {
|
||||
desc := (*estats.MetricDescriptor)(handle)
|
||||
desc := handle.Descriptor()
|
||||
ao := optionFromLabels(desc.Labels, desc.OptionalLabels, rm.optionalLabels, labels...)
|
||||
if ih, ok := rm.intHistos[desc]; ok {
|
||||
ih.Record(context.TODO(), incr, ao)
|
||||
|
@ -354,7 +354,7 @@ func (rm *registryMetrics) RecordInt64Histo(handle *estats.Int64HistoHandle, inc
|
|||
}
|
||||
|
||||
func (rm *registryMetrics) RecordFloat64Histo(handle *estats.Float64HistoHandle, incr float64, labels ...string) {
|
||||
desc := (*estats.MetricDescriptor)(handle)
|
||||
desc := handle.Descriptor()
|
||||
ao := optionFromLabels(desc.Labels, desc.OptionalLabels, rm.optionalLabels, labels...)
|
||||
if fh, ok := rm.floatHistos[desc]; ok {
|
||||
fh.Record(context.TODO(), incr, ao)
|
||||
|
@ -362,7 +362,7 @@ func (rm *registryMetrics) RecordFloat64Histo(handle *estats.Float64HistoHandle,
|
|||
}
|
||||
|
||||
func (rm *registryMetrics) RecordInt64Gauge(handle *estats.Int64GaugeHandle, incr int64, labels ...string) {
|
||||
desc := (*estats.MetricDescriptor)(handle)
|
||||
desc := handle.Descriptor()
|
||||
ao := optionFromLabels(desc.Labels, desc.OptionalLabels, rm.optionalLabels, labels...)
|
||||
if ig, ok := rm.intGauges[desc]; ok {
|
||||
ig.Record(context.TODO(), incr, ao)
|
||||
|
|
Loading…
Reference in New Issue