Skip to content
Snippets Groups Projects
Commit 6c6808f0 authored by ensiouel's avatar ensiouel
Browse files

refactor: метрики prometheus заменены на otel metric

parent e492790b
No related branches found
No related tags found
No related merge requests found
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)
}
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
}
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
}
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
}
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))
})
}
}
......@@ -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
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment