From 74381f916de287aa935d9a9deb21d7327b83c9d3 Mon Sep 17 00:00:00 2001 From: Iusupov Anton Date: Sat, 3 Aug 2024 13:30:01 +0300 Subject: [PATCH 1/3] feat: not sanitizing strings for metrics --- metrics/registry.go | 40 +++++++++++++++------------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/metrics/registry.go b/metrics/registry.go index 66c1362..3997510 100644 --- a/metrics/registry.go +++ b/metrics/registry.go @@ -1,7 +1,6 @@ package metrics import ( - "strings" "sync" "github.com/prometheus/client_golang/prometheus" @@ -14,7 +13,7 @@ type registry struct { PromRegistry *prometheus.Registry metricsMu sync.Mutex - counters map[string]*prometheus.CounterVec + counters map[string]prometheus.Counter histograms map[string]*prometheus.HistogramVec } @@ -23,7 +22,7 @@ func NewRegistry(subsystem, namespace string) Registry { Subsystem: subsystem, Namespace: namespace, PromRegistry: prometheus.NewRegistry(), - counters: make(map[string]*prometheus.CounterVec), + counters: make(map[string]prometheus.Counter), histograms: make(map[string]*prometheus.HistogramVec), } @@ -32,48 +31,39 @@ func NewRegistry(subsystem, namespace string) Registry { return r } -func (r *registry) sanitizeMetricName(name string) string { - return strings.ReplaceAll(name, "-", "_") -} - -func (r *registry) Inc(series Series, status string) { +func (r *registry) Inc(name string) { r.metricsMu.Lock() defer r.metricsMu.Unlock() - metricName, labels := series.SuccessLabels() - labels["status"] = status - sanitized := r.sanitizeMetricName(metricName) - counter, exists := r.counters[sanitized] + counter, exists := r.counters[name] if !exists { - counter = prometheus.NewCounterVec(prometheus.CounterOpts{ + counter = prometheus.NewCounter(prometheus.CounterOpts{ Subsystem: r.Subsystem, Namespace: r.Namespace, - Name: sanitized, - }, []string{"series_type", "name", "operation", "status"}) + Name: name, + }) r.PromRegistry.MustRegister(counter) - r.counters[sanitized] = counter + r.counters[name] = counter } - counter.With(labels).Inc() + counter.Inc() } -func (r *registry) RecordDuration(series Series, duration float64) { +func (r *registry) RecordDuration(name string, labels []string) *prometheus.HistogramVec { r.metricsMu.Lock() defer r.metricsMu.Unlock() - metricName, labels := series.DurationLabels() - sanitized := r.sanitizeMetricName(metricName) - histogram, exists := r.histograms[sanitized] + histogram, exists := r.histograms[name] if !exists { histogram = prometheus.NewHistogramVec(prometheus.HistogramOpts{ Subsystem: r.Subsystem, Namespace: r.Namespace, - Name: sanitized, + Name: name, Buckets: prometheus.DefBuckets, - }, []string{"series_type", "name", "operation"}) + }, labels) r.PromRegistry.MustRegister(histogram) - r.histograms[sanitized] = histogram + r.histograms[name] = histogram } - histogram.With(labels).Observe(duration) + return histogram } func (r *registry) PrometheusRegistry() *prometheus.Registry { From eeade04e73c853409b414597e86b83157eba89d4 Mon Sep 17 00:00:00 2001 From: Iusupov Anton Date: Sat, 3 Aug 2024 13:32:54 +0300 Subject: [PATCH 2/3] feat: not sanitizing dots in metrics names --- metrics/interface.go | 1 + metrics/registry.go | 33 ++++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/metrics/interface.go b/metrics/interface.go index bf51890..caacc37 100644 --- a/metrics/interface.go +++ b/metrics/interface.go @@ -2,6 +2,7 @@ package metrics import "github.com/prometheus/client_golang/prometheus" +// Registry defines the interface for managing metrics. type Registry interface { Inc(series Series, status string) RecordDuration(series Series, duration float64) diff --git a/metrics/registry.go b/metrics/registry.go index 3997510..2fdc3b6 100644 --- a/metrics/registry.go +++ b/metrics/registry.go @@ -1,6 +1,7 @@ package metrics import ( + "strings" "sync" "github.com/prometheus/client_golang/prometheus" @@ -13,16 +14,17 @@ type registry struct { PromRegistry *prometheus.Registry metricsMu sync.Mutex - counters map[string]prometheus.Counter + counters map[string]*prometheus.CounterVec histograms map[string]*prometheus.HistogramVec } +// NewRegistry creates a new metrics registry with the specified subsystem and namespace. func NewRegistry(subsystem, namespace string) Registry { r := ®istry{ Subsystem: subsystem, Namespace: namespace, PromRegistry: prometheus.NewRegistry(), - counters: make(map[string]prometheus.Counter), + counters: make(map[string]*prometheus.CounterVec), histograms: make(map[string]*prometheus.HistogramVec), } @@ -31,41 +33,50 @@ func NewRegistry(subsystem, namespace string) Registry { return r } +func (r *registry) sanitizeMetricName(name string) string { + return strings.ReplaceAll(name, "-", "_") +} + +// Inc increments a counter for the given metric name. func (r *registry) Inc(name string) { r.metricsMu.Lock() defer r.metricsMu.Unlock() - counter, exists := r.counters[name] + sanitized := r.sanitizeMetricName(name) + counter, exists := r.counters[sanitized] if !exists { - counter = prometheus.NewCounter(prometheus.CounterOpts{ + counter = prometheus.NewCounterVec(prometheus.CounterOpts{ Subsystem: r.Subsystem, Namespace: r.Namespace, - Name: name, - }) + Name: sanitized, + }, []string{"series_type", "name", "operation", "status"}) r.PromRegistry.MustRegister(counter) - r.counters[name] = counter + r.counters[sanitized] = counter } - counter.Inc() + counter.WithLabelValues().Inc() } +// RecordDuration records a duration for the given metric name and labels. func (r *registry) RecordDuration(name string, labels []string) *prometheus.HistogramVec { r.metricsMu.Lock() defer r.metricsMu.Unlock() - histogram, exists := r.histograms[name] + sanitized := r.sanitizeMetricName(name) + histogram, exists := r.histograms[sanitized] if !exists { histogram = prometheus.NewHistogramVec(prometheus.HistogramOpts{ Subsystem: r.Subsystem, Namespace: r.Namespace, - Name: name, + Name: sanitized, Buckets: prometheus.DefBuckets, }, labels) r.PromRegistry.MustRegister(histogram) - r.histograms[name] = histogram + r.histograms[sanitized] = histogram } return histogram } +// PrometheusRegistry returns the underlying Prometheus registry. func (r *registry) PrometheusRegistry() *prometheus.Registry { return r.PromRegistry } From d0393a78fdc1bb4a25afa28b0bad055edeee62d7 Mon Sep 17 00:00:00 2001 From: Iusupov Anton Date: Sat, 3 Aug 2024 18:02:23 +0300 Subject: [PATCH 3/3] feat: new levels approach in metrics --- metrics/interface.go | 4 +-- metrics/registry.go | 16 +++++----- metrics/series.go | 76 +++++++++++++++++++++----------------------- metrics/stub.go | 10 ++++-- 4 files changed, 53 insertions(+), 53 deletions(-) diff --git a/metrics/interface.go b/metrics/interface.go index caacc37..9bbc704 100644 --- a/metrics/interface.go +++ b/metrics/interface.go @@ -4,7 +4,7 @@ import "github.com/prometheus/client_golang/prometheus" // Registry defines the interface for managing metrics. type Registry interface { - Inc(series Series, status string) - RecordDuration(series Series, duration float64) + Inc(string, prometheus.Labels) + RecordDuration(string, prometheus.Labels, float64) PrometheusRegistry() *prometheus.Registry } diff --git a/metrics/registry.go b/metrics/registry.go index 2fdc3b6..3c02c11 100644 --- a/metrics/registry.go +++ b/metrics/registry.go @@ -37,8 +37,8 @@ func (r *registry) sanitizeMetricName(name string) string { return strings.ReplaceAll(name, "-", "_") } -// Inc increments a counter for the given metric name. -func (r *registry) Inc(name string) { +// Inc increments a counter for the given Series. +func (r *registry) Inc(name string, labels prometheus.Labels) { r.metricsMu.Lock() defer r.metricsMu.Unlock() @@ -49,15 +49,15 @@ func (r *registry) Inc(name string) { Subsystem: r.Subsystem, Namespace: r.Namespace, Name: sanitized, - }, []string{"series_type", "name", "operation", "status"}) + }, []string{"series_type", "sub_type", "operation", "status", "error_code"}) r.PromRegistry.MustRegister(counter) r.counters[sanitized] = counter } - counter.WithLabelValues().Inc() + counter.With(labels).Inc() } -// RecordDuration records a duration for the given metric name and labels. -func (r *registry) RecordDuration(name string, labels []string) *prometheus.HistogramVec { +// RecordDuration records a duration for the given Series. +func (r *registry) RecordDuration(name string, labels prometheus.Labels, duration float64) { r.metricsMu.Lock() defer r.metricsMu.Unlock() @@ -69,11 +69,11 @@ func (r *registry) RecordDuration(name string, labels []string) *prometheus.Hist Namespace: r.Namespace, Name: sanitized, Buckets: prometheus.DefBuckets, - }, labels) + }, []string{"series_type", "sub_type", "operation"}) r.PromRegistry.MustRegister(histogram) r.histograms[sanitized] = histogram } - return histogram + histogram.With(labels).Observe(duration) } // PrometheusRegistry returns the underlying Prometheus registry. diff --git a/metrics/series.go b/metrics/series.go index fb55643..368d949 100644 --- a/metrics/series.go +++ b/metrics/series.go @@ -2,6 +2,7 @@ package metrics import ( "context" + "github.com/prometheus/client_golang/prometheus" ) @@ -9,9 +10,10 @@ type ( SeriesType string Series struct { - st SeriesType - name string - operation string + seriesType SeriesType + subType string + operation string + status string } seriesContextKey struct{} @@ -30,14 +32,16 @@ const ( SeriesTypeDatabusConsumer SeriesType = "databus_consumer" ) -func NewSeries(st SeriesType, name string) Series { +// NewSeries creates a new Series instance with the given type and name. +func NewSeries(st SeriesType, subType string) Series { return Series{ - st: st, - name: name, - operation: "undefined", + seriesType: st, + subType: subType, + operation: "undefined", } } +// FromContext retrieves the Series from the context. func FromContext(ctx context.Context) Series { series, ok := ctx.Value(seriesContextKey{}).(Series) if !ok { @@ -47,73 +51,65 @@ func FromContext(ctx context.Context) Series { return series } +// WithOperation sets the operation name in the Series and returns an updated context. func (s Series) WithOperation(ctx context.Context, operation string) (context.Context, Series) { series := FromContext(ctx) - if s.st == series.st && - s.name == series.name { + if s.seriesType == series.seriesType && + s.subType == series.subType { series = Series{ - st: s.st, - name: s.name, - operation: series.appendOperation(operation), + seriesType: s.seriesType, + subType: s.subType, + operation: series.appendOperation(operation), } return series.ToContext(ctx), series } series = Series{ - st: s.st, - name: s.name, - operation: operation, + seriesType: s.seriesType, + subType: s.subType, + operation: operation, } return series.ToContext(ctx), series } +// ToContext adds the Series to the context. func (s Series) ToContext(ctx context.Context) context.Context { return context.WithValue(ctx, seriesContextKey{}, s) } -func (s Series) SuccessLabels() (string, prometheus.Labels) { - return "success_count", prometheus.Labels{ - "series_type": s.st.String(), - "name": s.name, +// Success returns the metric name and labels for a success event. +func (s Series) Success() (string, prometheus.Labels) { + return "operation_count", prometheus.Labels{ + "series_type": s.seriesType.String(), + "sub_type": s.subType, "operation": s.operation, "status": "success", } } -func (s Series) ErrorLabels(errCode string) (string, prometheus.Labels) { - return "error_count", prometheus.Labels{ - "series_type": s.st.String(), - "name": s.name, +// Error returns the metric name and labels for an error event. +func (s Series) Error(errCode string) (string, prometheus.Labels) { + return "operation_count", prometheus.Labels{ + "series_type": s.seriesType.String(), + "sub_type": s.subType, "operation": s.operation, "status": "error", "error_code": errCode, } } -func (s Series) DurationLabels() (string, prometheus.Labels) { +// Duration returns the metric name and labels for recording a duration. +func (s Series) Duration() (string, prometheus.Labels) { return "operation_duration_seconds", prometheus.Labels{ - "series_type": s.st.String(), - "name": s.name, + "series_type": s.seriesType.String(), + "sub_type": s.subType, "operation": s.operation, } } -func (s Series) InfoLabels(code string) (string, prometheus.Labels) { - return "info_events", prometheus.Labels{ - "series_type": s.st.String(), - "name": s.name, - "operation": s.operation, - "info_code": code, - } -} - -func (s Series) Operation() string { - return s.operation -} - func (s Series) appendOperation(operation string) string { - return s.operation + "." + operation + return s.operation + "_" + operation } diff --git a/metrics/stub.go b/metrics/stub.go index 069b1c9..813e052 100644 --- a/metrics/stub.go +++ b/metrics/stub.go @@ -4,17 +4,21 @@ import ( "github.com/prometheus/client_golang/prometheus" ) +// registryStub is a no-op implementation of the Registry interface. type registryStub struct{} +// NewRegistryStub creates a new instance of a no-op registry. func NewRegistryStub() Registry { return ®istryStub{} } -func (s *registryStub) Inc(_ Series, _ string) {} +// Inc is a no-op increment method for the stub registry. +func (s *registryStub) Inc(_ string, _ prometheus.Labels) {} -func (s *registryStub) RecordDuration(_ Series, _ float64) { -} +// RecordDuration is a no-op method for recording durations in the stub registry. +func (s *registryStub) RecordDuration(_ func() (string, prometheus.Labels), _ float64) {} +// PrometheusRegistry returns nil for the stub registry. func (s *registryStub) PrometheusRegistry() *prometheus.Registry { return nil }