internal/telemetry/registry.go
Rama main · réplica de lectura del repositorio original.
package telemetry
import (
"math"
"slices"
"sync"
)
const (
namePrefix = "spinoza_"
seriesLimit = 200
otherValue = "other"
keyJoin = "\x00"
)
const (
counterKind = "counter"
gaugeKind = "gauge"
histogramKind = "histogram"
)
type Registry struct {
mu sync.Mutex
vectors map[string]*vector
folded *Counter
rejected *Counter
}
func New() *Registry {
reg := &Registry{vectors: map[string]*vector{}}
reg.folded = reg.Counter(
"spinoza_telemetry_series_folded_total",
"writes folded into the other series because a metric ran out of room",
"metric",
)
reg.rejected = reg.Counter(
"spinoza_telemetry_writes_rejected_total",
"writes refused because they did not match what the metric declared",
"metric",
)
reg.folded.vec.silent = true
reg.rejected.vec.silent = true
return reg
}
func (r *Registry) Counter(name, help string, labels ...string) *Counter {
return &Counter{vec: r.declare(name, help, counterKind, nil, labels)}
}
func (r *Registry) Gauge(name, help string, labels ...string) *Gauge {
return &Gauge{vec: r.declare(name, help, gaugeKind, nil, labels)}
}
func (r *Registry) Histogram(name, help string, buckets []float64, labels ...string) *Histogram {
return &Histogram{vec: r.declare(name, help, histogramKind, buckets, labels)}
}
func (r *Registry) declare(name, help, kind string, buckets []float64, labels []string) *vector {
checkName(name)
checkLabels(labels)
made := &vector{
name: name,
help: help,
kind: kind,
labels: slices.Clone(labels),
buckets: bounds(kind, buckets),
series: map[string]*series{},
reg: r,
}
if len(made.labels) == 0 {
made.series[""] = made.blank(nil)
}
r.mu.Lock()
defer r.mu.Unlock()
_, taken := r.vectors[name]
if taken {
panic("telemetry: " + name + " is declared twice")
}
r.vectors[name] = made
return made
}
func checkName(name string) {
if !validName(name) {
panic("telemetry: " + name + " is not a usable metric name")
}
if len(name) <= len(namePrefix) || name[:len(namePrefix)] != namePrefix {
panic("telemetry: " + name + " does not start with " + namePrefix)
}
}
func checkLabels(labels []string) {
seen := map[string]bool{}
for _, label := range labels {
if !validName(label) {
panic("telemetry: " + label + " is not a usable label name")
}
if label == "le" {
panic("telemetry: le is reserved for histogram buckets")
}
if seen[label] {
panic("telemetry: " + label + " is declared twice on one metric")
}
seen[label] = true
}
}
func validName(name string) bool {
if name == "" {
return false
}
for at, letter := range name {
if letter >= 'a' && letter <= 'z' {
continue
}
if letter >= 'A' && letter <= 'Z' {
continue
}
if letter == '_' {
continue
}
if at > 0 && letter >= '0' && letter <= '9' {
continue
}
return false
}
return true
}
func bounds(kind string, given []float64) []float64 {
if kind != histogramKind {
if len(given) > 0 {
panic("telemetry: only a histogram has buckets")
}
return nil
}
if len(given) == 0 {
panic("telemetry: a histogram needs at least one bucket")
}
out := slices.Compact(slices.Sorted(slices.Values(given)))
for _, bound := range out {
if math.IsNaN(bound) || math.IsInf(bound, 0) {
panic("telemetry: a bucket bound has to be a finite number")
}
}
return out
}