From 6c6808f067be4ece256d26df22cd90841934b60b Mon Sep 17 00:00:00 2001 From: ensiouel <ensiouel@gmail.com> Date: Thu, 21 Dec 2023 14:26:33 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20=D0=BC=D0=B5=D1=82=D1=80=D0=B8?= =?UTF-8?q?=D0=BA=D0=B8=20prometheus=20=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=BD=D0=B0=20otel=20metric?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/cache/metrics_middleware.go | 27 ++++++++-------- pkg/metrics/cache.go | 48 +++++++++++++--------------- pkg/metrics/request.go | 50 ++++++++++++------------------ pkg/metrics/utils.go | 22 +++++++++++++ pkg/metrics/utils_test.go | 55 +++++++++++++++++++++++++++++++++ pkg/optional/optional.go | 7 +++++ 6 files changed, 139 insertions(+), 70 deletions(-) create mode 100644 pkg/metrics/utils.go create mode 100644 pkg/metrics/utils_test.go diff --git a/pkg/cache/metrics_middleware.go b/pkg/cache/metrics_middleware.go index 9c4ec424..6c220498 100644 --- a/pkg/cache/metrics_middleware.go +++ b/pkg/cache/metrics_middleware.go @@ -1,41 +1,42 @@ package cache import ( + "context" + "git.perx.ru/perxis/perxis-go/pkg/metrics" - "github.com/prometheus/client_golang/prometheus" + metricotel "go.opentelemetry.io/otel/metric" ) type metricsMiddleware struct { - cache Cache + next Cache cacheMetrics *metrics.CacheMetrics - serviceName string + attributes metricotel.MeasurementOption } // MetricsMiddleware возвращает обертку над кэшем, которая используется для отслеживания количества хитов и промахов в кэше. -func MetricsMiddleware(cache Cache, cacheMetrics *metrics.CacheMetrics, serviceName string) Cache { +func MetricsMiddleware(next Cache, cacheMetrics *metrics.CacheMetrics, attributes ...string) Cache { return &metricsMiddleware{ - cache: cache, + next: next, cacheMetrics: cacheMetrics, - serviceName: serviceName, + attributes: metricotel.WithAttributes(metrics.AttributesFromKV(attributes)...), } } func (c *metricsMiddleware) Set(key, value any) error { - return c.cache.Set(key, value) + return c.next.Set(key, value) } func (c *metricsMiddleware) Get(key any) (any, error) { - labels := prometheus.Labels{"service": c.serviceName} - value, err := c.cache.Get(key) + value, err := c.next.Get(key) if err != nil { - c.cacheMetrics.MissesTotal.With(labels).Inc() + c.cacheMetrics.MissesTotal.Add(context.TODO(), 1, c.attributes) return nil, err } - c.cacheMetrics.HitsTotal.With(labels).Inc() + c.cacheMetrics.HitsTotal.Add(context.TODO(), 1, c.attributes) return value, nil } func (c *metricsMiddleware) Remove(key any) error { - c.cacheMetrics.InvalidatesTotal.With(prometheus.Labels{"service": c.serviceName}).Inc() - return c.cache.Remove(key) + c.cacheMetrics.InvalidatesTotal.Add(context.TODO(), 1, c.attributes) + return c.next.Remove(key) } diff --git a/pkg/metrics/cache.go b/pkg/metrics/cache.go index fdd2689e..d4979e0f 100644 --- a/pkg/metrics/cache.go +++ b/pkg/metrics/cache.go @@ -1,38 +1,32 @@ package metrics -import "github.com/prometheus/client_golang/prometheus" +import ( + "git.perx.ru/perxis/perxis-go/pkg/optional" + "go.opentelemetry.io/otel" + metricotel "go.opentelemetry.io/otel/metric" +) type CacheMetrics struct { - HitsTotal *prometheus.CounterVec - MissesTotal *prometheus.CounterVec - InvalidatesTotal *prometheus.CounterVec + HitsTotal metricotel.Int64Counter + MissesTotal metricotel.Int64Counter + InvalidatesTotal metricotel.Int64Counter } func NewCacheMetrics(subsystem string) *CacheMetrics { - labelNames := []string{ - "service", - } + meter := otel.Meter(subsystem) metrics := &CacheMetrics{ - HitsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ - Subsystem: subsystem, - Name: "cache_hits_total", - Help: "Количество найденных в кэше значений", - }, labelNames), - MissesTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ - Subsystem: subsystem, - Name: "cache_misses_total", - Help: "Количество не найденных в кэше значений", - }, labelNames), - InvalidatesTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ - Subsystem: subsystem, - Name: "cache_invalidates_total", - Help: "Количество инвалидаций кэша", - }, labelNames), + HitsTotal: optional.Must(meter.Int64Counter( + "cache_hits_total", + metricotel.WithDescription("Количество найденных в кэше значений"), + )), + MissesTotal: optional.Must(meter.Int64Counter( + "cache_misses_total", + metricotel.WithDescription("Количество не найденных в кэше значений"), + )), + InvalidatesTotal: optional.Must(meter.Int64Counter( + "cache_invalidates_total", + metricotel.WithDescription("Количество инвалидаций кэша"), + )), } - prometheus.MustRegister( - metrics.HitsTotal, - metrics.MissesTotal, - metrics.InvalidatesTotal, - ) return metrics } diff --git a/pkg/metrics/request.go b/pkg/metrics/request.go index 3ad04152..98fbc420 100644 --- a/pkg/metrics/request.go +++ b/pkg/metrics/request.go @@ -1,44 +1,34 @@ package metrics import ( - "github.com/prometheus/client_golang/prometheus" + "git.perx.ru/perxis/perxis-go/pkg/optional" + "go.opentelemetry.io/otel" + metricotel "go.opentelemetry.io/otel/metric" ) type RequestMetrics struct { - Total *prometheus.CounterVec - FailedTotal *prometheus.CounterVec - DurationSeconds *prometheus.HistogramVec + Total metricotel.Int64Counter + FailedTotal metricotel.Int64Counter + DurationMilliseconds metricotel.Int64Histogram } // NewRequestMetrics возвращает метрики для подсчета количества удачных/неудачных запросов, а так же длительности ответов. -// Метрики записываются в prometheus.DefaultRegisterer func NewRequestMetrics(subsystem string) *RequestMetrics { - labelNames := []string{ - "service", - "method", - } + meter := otel.Meter(subsystem) metrics := &RequestMetrics{ - Total: prometheus.NewCounterVec(prometheus.CounterOpts{ - Subsystem: subsystem, - Name: "requests_total", - Help: "Количество запросов.", - }, labelNames), - FailedTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ - Subsystem: subsystem, - Name: "requests_failed_total", - Help: "Количество запросов, вернувших ошибку.", - }, labelNames), - DurationSeconds: prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Subsystem: subsystem, - Name: "request_duration_seconds", - Help: "Длительность обработки запроса.", - Buckets: prometheus.DefBuckets, - }, labelNames), + Total: optional.Must(meter.Int64Counter( + "requests_total", + metricotel.WithDescription("Количество запросов"), + )), + FailedTotal: optional.Must(meter.Int64Counter( + "requests_failed_total", + metricotel.WithDescription("Количество запросов, вернувших ошибку"), + )), + DurationMilliseconds: optional.Must(meter.Int64Histogram( + "request_duration", + metricotel.WithDescription("Длительность обработки запроса"), + metricotel.WithUnit("ms"), + )), } - prometheus.MustRegister( - metrics.Total, - metrics.FailedTotal, - metrics.DurationSeconds, - ) return metrics } diff --git a/pkg/metrics/utils.go b/pkg/metrics/utils.go new file mode 100644 index 00000000..62cb3963 --- /dev/null +++ b/pkg/metrics/utils.go @@ -0,0 +1,22 @@ +package metrics + +import ( + "go.opentelemetry.io/otel/attribute" +) + +// AttributesFromKV преобразует массив строк args в []attribute.KeyValue. +// +// Функция ожидает, что каждое значение будет следовать за соответствующим ключом в массиве args, +// и возвращает метки, соответствующие парам ключ-значение. +func AttributesFromKV(args []string) []attribute.KeyValue { + labels := make([]attribute.KeyValue, 0, len(args)/2) + for len(args) > 0 { + // если в массиве args остался только один элемент, он будет проигнорирован + if len(args) == 1 { + break + } + labels = append(labels, attribute.Key(args[0]).String(args[1])) + args = args[2:] + } + return labels +} diff --git a/pkg/metrics/utils_test.go b/pkg/metrics/utils_test.go new file mode 100644 index 00000000..7d9b6b31 --- /dev/null +++ b/pkg/metrics/utils_test.go @@ -0,0 +1,55 @@ +package metrics + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/attribute" +) + +func TestAttributesFromKV(t *testing.T) { + testcases := []struct { + name string + input []string + want []attribute.KeyValue + }{ + { + name: "input is empty", + input: []string{}, + want: []attribute.KeyValue{}, + }, + { + name: "input is nil", + input: nil, + want: []attribute.KeyValue{}, + }, + { + name: "valid", + input: []string{"key", "value"}, + want: []attribute.KeyValue{attribute.Key("key").String("value")}, + }, + { + name: "multi valid", + input: []string{"key", "value", "key1", "value1"}, + want: []attribute.KeyValue{attribute.Key("key").String("value"), attribute.Key("key1").String("value1")}, + }, + { + name: "bad key", + input: []string{"value"}, + want: []attribute.KeyValue{}, + }, + { + name: "multi bad key", + input: []string{"key", "value", "value1"}, + want: []attribute.KeyValue{attribute.Key("key").String("value")}, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + got := AttributesFromKV(tc.input) + require.True(t, reflect.DeepEqual(tc.want, got)) + }) + } +} diff --git a/pkg/optional/optional.go b/pkg/optional/optional.go index b33d76b9..31079009 100644 --- a/pkg/optional/optional.go +++ b/pkg/optional/optional.go @@ -12,3 +12,10 @@ func Bool(v bool) *bool { func Ptr[T any](v T) *T { return &v } + +func Must[T any](t T, err error) T { + if err != nil { + panic(err) + } + return t +} -- GitLab