From 1f8c2563131456529f8a9db8e7d2de1cf5f54b53 Mon Sep 17 00:00:00 2001 From: "d.reznichenko" Date: Thu, 18 Apr 2024 10:26:34 +0200 Subject: [PATCH] add metric decorator --- go.mod | 4 +- go.sum | 5 ++ metrics.go | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 metrics.go diff --git a/go.mod b/go.mod index 630a3eb..c9b70b0 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/caarlos0/env/v10 v10.0.0 github.com/dmitrorezn/golang-lru/v2 v2.0.2 github.com/redis/go-redis/v9 v9.5.1 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 golang.org/x/sync v0.6.0 ) @@ -17,6 +17,8 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/vmihailenco/go-tinylfu v0.2.2 // indirect + go.opentelemetry.io/otel v1.25.0 // indirect + go.opentelemetry.io/otel/metric v1.25.0 // indirect go.uber.org/multierr v1.10.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 91570cc..372ce8e 100644 --- a/go.sum +++ b/go.sum @@ -26,8 +26,13 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vmihailenco/go-tinylfu v0.2.2 h1:H1eiG6HM36iniK6+21n9LLpzx1G9R3DJa2UjUjbynsI= github.com/vmihailenco/go-tinylfu v0.2.2/go.mod h1:CutYi2Q9puTxfcolkliPq4npPuofg9N9t8JVrjzwa3Q= +go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= +go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= +go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= diff --git a/metrics.go b/metrics.go new file mode 100644 index 0000000..bce6627 --- /dev/null +++ b/metrics.go @@ -0,0 +1,141 @@ +package wredis + +import ( + "context" + "sync" + "sync/atomic" + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + + "github.com/redis/go-redis/v9" +) + +type MetricsClient struct { + UniversalClient + meter *Meter +} + +func NewMetricsClient( + name string, + client UniversalClient, + metricProvider metric.MeterProvider, +) *MetricsClient { + return &MetricsClient{ + UniversalClient: client, + meter: NewMeter(name, metricProvider.Meter("wredis")), + } +} + +type Meter struct { + name string + + cmu sync.RWMutex + counters map[string]metric.Int64Counter + + hmu sync.RWMutex + histograms map[string]metric.Float64Histogram + + gauge map[string]atomic.Int64 + + meter metric.Meter +} + +func NewMeter(name string, meter metric.Meter) *Meter { + return &Meter{ + name: name, + meter: meter, + } +} + +func (m *Meter) Observe(ctx context.Context, cmd string) func(err error) { + var ( + start = time.Now() + attrOpt = metric.WithAttributes( + attribute.String("name", m.name), + attribute.String("cmd", cmd), + ) + ) + m.cmu.RLock() + cnt, ok := m.counters[cmd] + m.cmu.RUnlock() + var err error + if !ok { + if cnt, err = m.meter.Int64Counter(cmd); err == nil { + m.cmu.Lock() + m.counters[cmd] = cnt + m.cmu.Unlock() + } + } + + gauge, ok := m.gauge[cmd] + if !ok { + _, _ = m.meter.Int64ObservableGauge(cmd, metric.WithInt64Callback(func(ctx context.Context, observer metric.Int64Observer) error { + observer.Observe(gauge.Load(), attrOpt) + return nil + })) + } + gauge.Add(1) + + m.hmu.RLock() + histogram, ok := m.histograms[cmd] + m.hmu.RUnlock() + if !ok { + if histogram, err = m.meter.Float64Histogram(cmd); err == nil { + m.cmu.Lock() + m.histograms[cmd] = histogram + m.cmu.Unlock() + } + } + + return func(err error) { + cnt.Add(ctx, 1, attrOpt, metric.WithAttributes(attribute.Bool("error", err != nil))) + + histogram.Record( + ctx, + time.Since(start).Seconds(), + attrOpt, + metric.WithAttributes( + attribute.Bool("error", err != nil), + ), + ) + + gauge.Add(-1) + } +} + +func (s *MetricsClient) Get(ctx context.Context, key string) (cmd *redis.StringCmd) { + defer s.meter.Observe(ctx, "Get")(cmd.Err()) + + return s.UniversalClient.Get(ctx, key) +} + +func (s *MetricsClient) HGet(ctx context.Context, key, field string) (cmd *redis.StringCmd) { + defer s.meter.Observe(ctx, "HGet")(cmd.Err()) + + return s.UniversalClient.HGet(ctx, key, field) +} + +func (s *MetricsClient) GetDel(ctx context.Context, key string) (cmd *redis.StringCmd) { + defer s.meter.Observe(ctx, "GetDel")(cmd.Err()) + + return s.UniversalClient.GetDel(ctx, key) +} + +func (s *MetricsClient) HMGet(ctx context.Context, key string, field ...string) (cmd *redis.SliceCmd) { + defer s.meter.Observe(ctx, "HMGet")(cmd.Err()) + + return s.UniversalClient.HMGet(ctx, key, field...) +} + +func (s *MetricsClient) HGetAll(ctx context.Context, key string) (cmd *redis.MapStringStringCmd) { + defer s.meter.Observe(ctx, "HGetAll")(cmd.Err()) + + return s.UniversalClient.HGetAll(ctx, key) +} + +/* + TO DO + .... +*/