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

feature(MetricsMiddleware): добавлена метрика cache_invalidates_total

refactor(MetricsMiddleware): изменена сигнатура создания MetricsMiddleware, теперь принимает имя подсистемы и метки, которые будут добавлены к метрикам
parent a44f49c0
No related branches found
No related tags found
No related merge requests found
......@@ -4,35 +4,50 @@ import (
"github.com/prometheus/client_golang/prometheus"
)
const badKey = "BADKEY"
type metricsMiddleware struct {
cache Cache
hitsTotal prometheus.Counter
missesTotal prometheus.Counter
cache Cache
hitsTotal prometheus.Counter
missesTotal prometheus.Counter
invalidatesTotal prometheus.Counter
}
// MetricsMiddleware возвращает обертку над кэшем, которая используется для отслеживания количества хитов и промахов в кэше.
//
// subsystem указывает подсистему, к которой принадлежат метрики.
// Значение должно быть уникальным, совпадение разрешено только при совпадении ключей labels. Пустое значение допустимо.
//
// labels - список меток, где каждый элемент метки соответствует парам ключ-значение. Отсутствие допустимо.
// Значения меток должны быть уникальными в рамках одной subsystem.
//
// Метрики записываются в prometheus.DefaultRegisterer
func MetricsMiddleware(cache Cache, serviceName string) Cache {
func MetricsMiddleware(cache Cache, subsystem string, labels ...string) Cache {
if cache == nil {
panic("cannot wrap metrics in cache, cache is nil")
}
middleware := &metricsMiddleware{
cache: cache,
hitsTotal: prometheus.NewCounter(prometheus.CounterOpts{
Name: "cache_hits_total",
Help: "Количество попаданий в кэш.",
ConstLabels: prometheus.Labels{"service_name": serviceName},
Subsystem: subsystem,
Name: "cache_hits_total",
Help: "Количество попаданий в кэш.",
}),
missesTotal: prometheus.NewCounter(prometheus.CounterOpts{
Name: "cache_misses_total",
Help: "Количество пропусков в кэш.",
ConstLabels: prometheus.Labels{"service_name": serviceName},
Subsystem: subsystem,
Name: "cache_misses_total",
Help: "Количество пропусков в кэш.",
}),
invalidatesTotal: prometheus.NewCounter(prometheus.CounterOpts{
Subsystem: subsystem,
Name: "cache_invalidates_total",
Help: "Количество инвалидаций кэша.",
}),
}
prometheus.MustRegister(
prometheus.WrapRegistererWith(argsToLabels(labels), prometheus.DefaultRegisterer).MustRegister(
middleware.hitsTotal,
middleware.missesTotal,
middleware.invalidatesTotal,
)
return middleware
}
......@@ -52,5 +67,25 @@ func (c *metricsMiddleware) Get(key any) (any, error) {
}
func (c *metricsMiddleware) Remove(key any) error {
c.invalidatesTotal.Inc()
return c.cache.Remove(key)
}
// argsToLabels преобразует массив строк args в метки типа prometheus.Labels.
//
// Функция ожидает, что каждое значение будет следовать за соответствующим ключом в массиве args,
// и возвращает метки, соответствующие парам ключ-значение.
func argsToLabels(args []string) prometheus.Labels {
labels := make(prometheus.Labels, len(args)/2)
for len(args) > 0 {
// если в массиве args остался только один элемент, он будет рассмотрен как значение с недопустимым ключом.
if len(args) == 1 {
labels[badKey] = args[0]
break
}
labels[args[0]] = args[1]
args = args[2:]
}
return labels
}
package cache
import (
"reflect"
"testing"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
)
func TestMetricsMiddleware_argsToLabels(t *testing.T) {
testcases := []struct {
name string
input []string
want prometheus.Labels
}{
{
name: "input is empty",
input: []string{},
want: prometheus.Labels{},
},
{
name: "input is nil",
input: nil,
want: prometheus.Labels{},
},
{
name: "valid",
input: []string{"key", "value"},
want: prometheus.Labels{"key": "value"},
},
{
name: "multi valid",
input: []string{"key", "value", "key1", "value1"},
want: prometheus.Labels{"key": "value", "key1": "value1"},
},
{
name: "bad key",
input: []string{"value"},
want: prometheus.Labels{badKey: "value"},
},
{
name: "multi bad key",
input: []string{"key", "value", "value1"},
want: prometheus.Labels{"key": "value", badKey: "value1"},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got := argsToLabels(tc.input)
require.True(t, reflect.DeepEqual(tc.want, got))
})
}
}
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