From a0354256bc721c8a687626151356f90916271e9e Mon Sep 17 00:00:00 2001 From: Hyeonho Kim Date: Thu, 14 Dec 2023 05:00:57 -0800 Subject: [PATCH] feat: add built-in metrics (#421) * feat: add built-in metrics * refactor: rename buckets * refactor: integrate with otelclient * refactor: remove sync.once * style: change simple * refactor: change default dial fn * refactor: change to cas * refactor: rename histogramoption * refactor: change client option as required --- go.mod | 16 +-- go.sum | 16 +++ rueidisotel/metrics.go | 159 +++++++++++++++++++++++++ rueidisotel/metrics_test.go | 223 ++++++++++++++++++++++++++++++++++++ rueidisotel/trace.go | 46 +++----- 5 files changed, 423 insertions(+), 37 deletions(-) create mode 100644 rueidisotel/metrics.go create mode 100644 rueidisotel/metrics_test.go diff --git a/go.mod b/go.mod index 510e3a61..0ffb36f3 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.20 require ( github.com/oklog/ulid/v2 v2.1.0 - go.opentelemetry.io/otel v1.19.0 - go.opentelemetry.io/otel/metric v1.19.0 - go.opentelemetry.io/otel/sdk v1.19.0 - go.opentelemetry.io/otel/sdk/metric v1.19.0 - go.opentelemetry.io/otel/trace v1.19.0 + go.opentelemetry.io/otel v1.21.0 + go.opentelemetry.io/otel/metric v1.21.0 + go.opentelemetry.io/otel/sdk v1.21.0 + go.opentelemetry.io/otel/sdk/metric v1.21.0 + go.opentelemetry.io/otel/trace v1.21.0 ) require ( @@ -18,15 +18,15 @@ require ( ) require ( - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/kr/text v0.2.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/sys v0.14.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.12.0 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect diff --git a/go.sum b/go.sum index 09663389..fdf637d8 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -12,6 +14,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4 github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -34,14 +38,24 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= +go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= +go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= @@ -49,6 +63,8 @@ golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= diff --git a/rueidisotel/metrics.go b/rueidisotel/metrics.go new file mode 100644 index 00000000..4f510f40 --- /dev/null +++ b/rueidisotel/metrics.go @@ -0,0 +1,159 @@ +package rueidisotel + +import ( + "context" + "crypto/tls" + "net" + "sync/atomic" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" + + "github.com/redis/rueidis" +) + +var ( + DefaultHistogramDefaultBuckets = []float64{ + .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, + } + DefaultDialFn = func(dst string, dialer *net.Dialer, cfg *tls.Config) (conn net.Conn, err error) { + if cfg != nil { + return tls.DialWithDialer(dialer, "tcp", dst, cfg) + } + return dialer.Dial("tcp", dst) + } +) + +type HistogramOption struct { + Buckets []float64 +} + +// WithHistogramOption sets the HistogramOption. +// If not set, DefaultHistogramDefaultBuckets will be used. +func WithHistogramOption(histogramOption HistogramOption) Option { + return func(cli *otelclient) { + cli.histogramOption = histogramOption + } +} + +// NewClient creates a new Client. +// The following metrics are recorded: +// - rueidis_dial_attempt: number of dial attempts +// - rueidis_dial_success: number of successful dials +// - rueidis_dial_conns: number of active connections +// - rueidis_dial_latency: dial latency in seconds +func NewClient(clientOption rueidis.ClientOption, opts ...Option) (rueidis.Client, error) { + oclient := newClient(opts...) + + if clientOption.DialFn == nil { + clientOption.DialFn = DefaultDialFn + } + + attempt, err := oclient.meter.Int64Counter("rueidis_dial_attempt") + if err != nil { + return nil, err + } + oclient.attempt = attempt + + success, err := oclient.meter.Int64Counter("rueidis_dial_success") + if err != nil { + return nil, err + } + oclient.success = success + + conns, err := oclient.meter.Int64UpDownCounter("rueidis_dial_conns") + if err != nil { + return nil, err + } + oclient.conns = conns + + dialLatency, err := oclient.meter.Float64Histogram( + "rueidis_dial_latency", + metric.WithUnit("s"), + metric.WithExplicitBucketBoundaries(oclient.histogramOption.Buckets...), + ) + if err != nil { + return nil, err + } + oclient.dialLatency = dialLatency + + clientOption.DialFn = trackDialing( + attempt, success, conns, dialLatency, clientOption.DialFn, + ) + cli, err := rueidis.NewClient(clientOption) + if err != nil { + return nil, err + } + oclient.client = cli + + return oclient, nil +} + +func newClient(opts ...Option) *otelclient { + cli := &otelclient{} + for _, opt := range opts { + opt(cli) + } + if cli.histogramOption.Buckets == nil { + cli.histogramOption.Buckets = DefaultHistogramDefaultBuckets + } + if cli.meterProvider == nil { + cli.meterProvider = otel.GetMeterProvider() // Default to global MeterProvider + } + if cli.tracerProvider == nil { + cli.tracerProvider = otel.GetTracerProvider() // Default to global TracerProvider + } + + // Now that we have the meterProvider and tracerProvider, get the Meter and Tracer + cli.meter = cli.meterProvider.Meter(name) + cli.tracer = cli.tracerProvider.Tracer(name) + // Now create the counters using the meter + cli.cscMiss, _ = cli.meter.Int64Counter("rueidis_do_cache_miss") + cli.cscHits, _ = cli.meter.Int64Counter("rueidis_do_cache_hits") + return cli +} + +func trackDialing( + attempt metric.Int64Counter, + success metric.Int64Counter, + conns metric.Int64UpDownCounter, + dialLatency metric.Float64Histogram, + dialFn func(string, *net.Dialer, *tls.Config) (conn net.Conn, err error), +) func(string, *net.Dialer, *tls.Config) (conn net.Conn, err error) { + return func(network string, dialer *net.Dialer, tlsConfig *tls.Config) (conn net.Conn, err error) { + ctx := context.Background() + attempt.Add(ctx, 1) + + start := time.Now() + + conn, err = dialFn(network, dialer, tlsConfig) + if err != nil { + return nil, err + } + + dialLatency.Record(ctx, time.Since(start).Seconds()) + success.Add(ctx, 1) + conns.Add(ctx, 1) + + return &connTracker{ + Conn: conn, + conns: conns, + once: 0, + }, nil + } +} + +type connTracker struct { + net.Conn + conns metric.Int64UpDownCounter + once int32 +} + +func (t *connTracker) Close() error { + if atomic.CompareAndSwapInt32(&t.once, 0, 1) { + t.conns.Add(context.Background(), -1) + } + + return t.Conn.Close() +} diff --git a/rueidisotel/metrics_test.go b/rueidisotel/metrics_test.go new file mode 100644 index 00000000..bf24c162 --- /dev/null +++ b/rueidisotel/metrics_test.go @@ -0,0 +1,223 @@ +package rueidisotel + +import ( + "context" + "crypto/tls" + "net" + "testing" + + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + + "github.com/redis/rueidis" +) + +func TestNewClient(t *testing.T) { + t.Run("client option only", func(t *testing.T) { + c, err := NewClient(rueidis.ClientOption{ + InitAddress: []string{"127.0.0.1:6379"}, + DialFn: func(dst string, dialer *net.Dialer, _ *tls.Config) (conn net.Conn, err error) { + return dialer.Dial("tcp", dst) + }, + }) + if err != nil { + t.Fatal(err) + } + defer c.Close() + }) + + t.Run("meter provider", func(t *testing.T) { + mr := metric.NewManualReader() + meterProvider := metric.NewMeterProvider(metric.WithReader(mr)) + c, err := NewClient( + rueidis.ClientOption{ + InitAddress: []string{"127.0.0.1:6379"}, + DialFn: func(dst string, dialer *net.Dialer, _ *tls.Config) (conn net.Conn, err error) { + return dialer.Dial("tcp", dst) + }, + }, + WithMeterProvider(meterProvider), + ) + if err != nil { + t.Fatal(err) + } + defer c.Close() + }) + + t.Run("dial latency histogram option", func(t *testing.T) { + c, err := NewClient( + rueidis.ClientOption{ + InitAddress: []string{"127.0.0.1:6379"}, + DialFn: func(dst string, dialer *net.Dialer, _ *tls.Config) (conn net.Conn, err error) { + return dialer.Dial("tcp", dst) + }, + }, + WithHistogramOption(HistogramOption{ + Buckets: []float64{1, 2, 3}, + }), + ) + if err != nil { + t.Fatal(err) + } + defer c.Close() + }) +} + +func TestNewClientError(t *testing.T) { + t.Run("invalid client option", func(t *testing.T) { + _, err := NewClient(rueidis.ClientOption{ + InitAddress: []string{""}, + DialFn: func(dst string, dialer *net.Dialer, _ *tls.Config) (conn net.Conn, err error) { + return dialer.Dial("tcp", dst) + }, + }) + if err == nil { + t.Error(err) + } + }) +} + +func TestTrackDialing(t *testing.T) { + t.Run("success", func(t *testing.T) { + mr := metric.NewManualReader() + meterProvider := metric.NewMeterProvider(metric.WithReader(mr)) + c, err := NewClient( + rueidis.ClientOption{ + InitAddress: []string{"127.0.0.1:6379"}, + DialFn: func(dst string, dialer *net.Dialer, _ *tls.Config) (conn net.Conn, err error) { + return dialer.Dial("tcp", dst) + }, + }, + WithMeterProvider(meterProvider), + ) + if err != nil { + t.Fatal(err) + } + + metrics := metricdata.ResourceMetrics{} + if err := mr.Collect(context.Background(), &metrics); err != nil { + t.Fatal(err) + } + attempt := int64CountMetric(metrics, "rueidis_dial_attempt") + if attempt != 1 { + t.Errorf("attempt: got %d, want 1", attempt) + } + success := int64CountMetric(metrics, "rueidis_dial_success") + if success != 1 { + t.Errorf("success: got %d, want 1", success) + } + conns := int64CountMetric(metrics, "rueidis_dial_conns") + if conns != 1 { + t.Errorf("conns: got %d, want 1", conns) + } + dialLatency := float64HistogramMetric(metrics, "rueidis_dial_latency") + if dialLatency == 0 { + t.Error("dial latency: got 0, want > 0") + } + + c.Close() + + metrics = metricdata.ResourceMetrics{} + if err := mr.Collect(context.Background(), &metrics); err != nil { + t.Fatal(err) + } + conns = int64CountMetric(metrics, "rueidis_dial_conns") + if conns != 0 { + t.Errorf("conns: got %d, want 0", conns) + } + }) + + t.Run("deduplicated closed connection conns metric", func(t *testing.T) { + mr := metric.NewManualReader() + meterProvider := metric.NewMeterProvider(metric.WithReader(mr)) + c, err := NewClient( + rueidis.ClientOption{ + InitAddress: []string{"127.0.0.1:6379"}, + DialFn: func(dst string, dialer *net.Dialer, _ *tls.Config) (conn net.Conn, err error) { + return dialer.Dial("tcp", dst) + }, + }, + WithMeterProvider(meterProvider), + ) + if err != nil { + t.Fatal(err) + } + + c.Close() + c.Close() + + metrics := metricdata.ResourceMetrics{} + if err := mr.Collect(context.Background(), &metrics); err != nil { + t.Fatal(err) + } + conns := int64CountMetric(metrics, "rueidis_dial_conns") + if conns != 0 { + t.Errorf("conns: got %d, want 0", conns) + } + }) + + t.Run("failed to dial", func(t *testing.T) { + mr := metric.NewManualReader() + meterProvider := metric.NewMeterProvider(metric.WithReader(mr)) + _, err := NewClient( + rueidis.ClientOption{ + InitAddress: []string{""}, + DialFn: func(dst string, dialer *net.Dialer, _ *tls.Config) (conn net.Conn, err error) { + return dialer.Dial("tcp", dst) + }, + }, + WithMeterProvider(meterProvider), + ) + if err == nil { + t.Fatal(err) + } + + metrics := metricdata.ResourceMetrics{} + if err := mr.Collect(context.Background(), &metrics); err != nil { + t.Fatal(err) + } + attempt := int64CountMetric(metrics, "rueidis_dial_attempt") + if attempt != 1 { + t.Errorf("attempt: got %d, want 1", attempt) + } + success := int64CountMetric(metrics, "rueidis_dial_success") + if success != 0 { + t.Errorf("success: got %d, want 0", success) + } + conns := int64CountMetric(metrics, "rueidis_dial_conns") + if conns != 0 { + t.Errorf("conns: got %d, want 0", conns) + } + dialLatency := float64HistogramMetric(metrics, "rueidis_dial_latency") + if dialLatency != 0 { + t.Error("dial latency: got 0, want 0") + } + }) +} + +func int64CountMetric(metrics metricdata.ResourceMetrics, name string) int64 { + for _, sm := range metrics.ScopeMetrics { + for _, m := range sm.Metrics { + if m.Name == name { + data, ok := m.Data.(metricdata.Sum[int64]) + if !ok { + return 0 + } + return data.DataPoints[0].Value + } + } + } + return 0 +} + +func float64HistogramMetric(metrics metricdata.ResourceMetrics, name string) float64 { + for _, sm := range metrics.ScopeMetrics { + for _, m := range sm.Metrics { + if m.Name == name { + data := m.Data.(metricdata.Histogram[float64]) + return data.DataPoints[0].Sum + } + } + } + return 0 +} diff --git a/rueidisotel/trace.go b/rueidisotel/trace.go index ac011ccd..d62a997b 100644 --- a/rueidisotel/trace.go +++ b/rueidisotel/trace.go @@ -5,7 +5,6 @@ import ( "strings" "time" - "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" @@ -24,25 +23,9 @@ var _ rueidis.Client = (*otelclient)(nil) // WithClient creates a new rueidis.Client with OpenTelemetry tracing enabled. func WithClient(client rueidis.Client, opts ...Option) rueidis.Client { - o := &otelclient{ - client: client, - } - for _, opt := range opts { - opt(o) - } - if o.meterProvider == nil { - o.meterProvider = otel.GetMeterProvider() // Default to global MeterProvider - } - if o.tracerProvider == nil { - o.tracerProvider = otel.GetTracerProvider() // Default to global TracerProvider - } - // Now that we have the meterProvider and tracerProvider, get the Meter and Tracer - o.meter = o.meterProvider.Meter(name) - o.tracer = o.tracerProvider.Tracer(name) - // Now create the counters using the meter - o.cscMiss, _ = o.meter.Int64Counter("rueidis_do_cache_miss") - o.cscHits, _ = o.meter.Int64Counter("rueidis_do_cache_hits") - return o + cli := newClient(opts...) + cli.client = client + return cli } // Option is the Functional Options interface @@ -77,15 +60,20 @@ func WithTracerProvider(provider trace.TracerProvider) Option { } type otelclient struct { - client rueidis.Client - meterProvider metric.MeterProvider - tracerProvider trace.TracerProvider - tracer trace.Tracer - meter metric.Meter - cscMiss metric.Int64Counter - cscHits metric.Int64Counter - mAttrs []attribute.KeyValue - tAttrs []attribute.KeyValue + client rueidis.Client + meterProvider metric.MeterProvider + tracerProvider trace.TracerProvider + tracer trace.Tracer + meter metric.Meter + cscMiss metric.Int64Counter + cscHits metric.Int64Counter + mAttrs []attribute.KeyValue + tAttrs []attribute.KeyValue + histogramOption HistogramOption + attempt metric.Int64Counter + success metric.Int64Counter + conns metric.Int64UpDownCounter + dialLatency metric.Float64Histogram } func (o *otelclient) B() rueidis.Builder {