Files
Dave Henderson ced7a84f8b chore(metrics): Add metrics & traces to DB migration (#97181)
Signed-off-by: Dave Henderson <dave.henderson@grafana.com>
2024-12-05 17:22:19 -05:00

122 lines
3.3 KiB
Go

package metricutil
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/trace"
)
// SanitizeLabelName removes all invalid chars from the label name.
// If the label name is empty or contains only invalid chars, it
// will return an error.
func SanitizeLabelName(name string) (string, error) {
if len(name) == 0 {
return "", errors.New("label name cannot be empty")
}
out := strings.Builder{}
for i, b := range name {
if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0) {
out.WriteRune(b)
} else if b == ' ' {
out.WriteRune('_')
}
}
if out.Len() == 0 {
return "", fmt.Errorf("label name only contains invalid chars: %q", name)
}
return out.String(), nil
}
// NewCounterStartingAtZero initializes a new Prometheus counter with an initial
// observation of zero. Used for to guarantee the existence of the specific metric.
func NewCounterStartingAtZero(opts prometheus.CounterOpts) prometheus.Counter {
counter := prometheus.NewCounter(opts)
counter.Add(0)
return counter
}
// NewCounterVecStartingAtZero initializes a new Prometheus counter with an initial
// observation of zero for every possible value of each label. Used for the sake of
// consistency among all the possible labels and values.
func NewCounterVecStartingAtZero(opts prometheus.CounterOpts, labels []string, labelValues map[string][]string) *prometheus.CounterVec {
counter := prometheus.NewCounterVec(opts, labels)
for _, ls := range buildLabelSets(labels, labelValues) {
counter.With(ls).Add(0)
}
return counter
}
func buildLabelSets(labels []string, labelValues map[string][]string) []prometheus.Labels {
var labelSets []prometheus.Labels
var n func(i int, ls prometheus.Labels)
n = func(i int, ls prometheus.Labels) {
if i == len(labels) {
labelSets = append(labelSets, ls)
return
}
label := labels[i]
values := labelValues[label]
for _, v := range values {
lsCopy := copyLabelSet(ls)
lsCopy[label] = v
n(i+1, lsCopy)
}
}
n(0, prometheus.Labels{})
return labelSets
}
func copyLabelSet(ls prometheus.Labels) prometheus.Labels {
newLs := make(prometheus.Labels, len(ls))
for l, v := range ls {
newLs[l] = v
}
return newLs
}
// wraps prometheus.NewHistogram, adding a central native histogram config
func NewHistogramVec(opts prometheus.HistogramOpts, labels []string) *prometheus.HistogramVec {
if opts.NativeHistogramBucketFactor == 0 {
// Enable native histograms, with the factor suggested in the docs
opts.NativeHistogramBucketFactor = 1.1
}
if opts.NativeHistogramMaxBucketNumber == 0 {
// OTel default
opts.NativeHistogramMaxBucketNumber = 160
}
if opts.NativeHistogramMinResetDuration == 0 {
// Reset buckets every hour by default - override this if you want to
// keep buckets around for longer
opts.NativeHistogramMinResetDuration = 1 * time.Hour
}
return prometheus.NewHistogramVec(opts, labels)
}
func ObserveWithExemplar(ctx context.Context, histogram prometheus.Observer, value float64) {
traceID := trace.SpanContextFromContext(ctx).TraceID()
if traceID.IsValid() {
histogram.(prometheus.ExemplarObserver).ObserveWithExemplar(
value,
prometheus.Labels{"traceID": traceID.String()},
)
return
}
histogram.Observe(value)
}