mirror of
https://github.com/luxfi/kms.git
synced 2026-07-27 03:38:31 +00:00
ghcr.io/luxfi/kms-operator has had no new image since v1.12.3. The Build KMS
Operator workflow failed on every tag since (v1.12.4 … v1.12.7) while its
sibling Build KMS succeeded on the same tag, so the tags looked published.
Dockerfile.operator:27 builds with -mod=vendor, and vendor/ had drifted from
go.mod in both directions:
- go.opentelemetry.io/otel/{metric,trace}: in vendor/modules.txt, not in go.mod
- github.com/luxfi/{crypto,geth,ids,keys,zap,address,age,cache,constants,
container,formatting,math,go-bip32,go-bip39}: in go.mod, not marked explicit
`go mod vendor` alone could not fix it — it aborted first on go.sum entries
missing for the k8s packages cmd/kms-operator imports (k8s.io/api/core/v1,
k8s.io/apimachinery/..., k8s.io/client-go/{dynamic,kubernetes,rest}). So:
go mod tidy (adds them, drops now-unused indirects) then go mod vendor.
Verified by stash control:
operator, -mod=vendor: rc=1 -> rc=0 ("inconsistent vendoring" 1 -> 0)
server, -mod=vendor: rc=1 -> rc=1 (unchanged)
The server's vendor build was already broken and stays broken, for an unrelated
reason: blst.h has never been vendored (confirmed absent from HEAD's tree, so it
could not have worked before either) — go mod vendor does not copy cgo headers
that live outside the package dir. It is not a regression and does not affect
CI: the server's Dockerfile builds with GOFLAGS=-mod=mod after `rm -f go.sum &&
go mod download`, i.e. from the module cache, never from vendor/. The operator
is the only -mod=vendor consumer, which is exactly why it was the only failure.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
1163 lines
31 KiB
Go
1163 lines
31 KiB
Go
// Package metric provides metrics collection.
|
|
// Designed for minimal allocations and standalone operation.
|
|
package metric
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
// metricCounter provides a counter.
|
|
type metricCounter struct {
|
|
value uint64 // atomic float64 bits
|
|
name string
|
|
help string
|
|
}
|
|
|
|
// newCounter creates a counter.
|
|
func newCounter(name, help string) *metricCounter {
|
|
return &metricCounter{
|
|
name: name,
|
|
help: help,
|
|
}
|
|
}
|
|
|
|
// Inc increments the counter by 1
|
|
func (vc *metricCounter) Inc() {
|
|
vc.Add(1)
|
|
}
|
|
|
|
// Add adds a value to the counter
|
|
func (vc *metricCounter) Add(val float64) {
|
|
for {
|
|
oldBits := atomic.LoadUint64(&vc.value)
|
|
oldVal := math.Float64frombits(oldBits)
|
|
newVal := oldVal + val
|
|
newBits := math.Float64bits(newVal)
|
|
if atomic.CompareAndSwapUint64(&vc.value, oldBits, newBits) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Value returns the current value
|
|
func (vc *metricCounter) Value() uint64 {
|
|
return uint64(vc.Get())
|
|
}
|
|
|
|
// Get returns the current value as float64.
|
|
func (vc *metricCounter) Get() float64 {
|
|
return math.Float64frombits(atomic.LoadUint64(&vc.value))
|
|
}
|
|
|
|
// String returns the counter in the metrics text format.
|
|
func (vc *metricCounter) String() string {
|
|
return fmt.Sprintf("# HELP %s %s\n# TYPE %s counter\n%s %g", vc.name, vc.help, vc.name, vc.name, vc.Get())
|
|
}
|
|
|
|
// metricGauge provides a gauge.
|
|
type metricGauge struct {
|
|
value int64 // Use int64 to handle negative values
|
|
name string
|
|
help string
|
|
}
|
|
|
|
// newGauge creates a gauge.
|
|
func newGauge(name, help string) *metricGauge {
|
|
return &metricGauge{
|
|
name: name,
|
|
help: help,
|
|
}
|
|
}
|
|
|
|
// Set sets the gauge value
|
|
func (vg *metricGauge) Set(val float64) {
|
|
atomic.StoreInt64(&vg.value, int64(math.Float64bits(val)))
|
|
}
|
|
|
|
// SetToCurrentTime sets the gauge to seconds-since-epoch. Matches
|
|
// prometheus/client_golang's Gauge.SetToCurrentTime.
|
|
func (vg *metricGauge) SetToCurrentTime() {
|
|
vg.Set(float64(time.Now().Unix()))
|
|
}
|
|
|
|
// Get returns the gauge value
|
|
func (vg *metricGauge) Get() float64 {
|
|
return math.Float64frombits(uint64(atomic.LoadInt64(&vg.value)))
|
|
}
|
|
|
|
// Inc increments the gauge by 1
|
|
func (vg *metricGauge) Inc() {
|
|
for {
|
|
oldVal := vg.Get()
|
|
newVal := oldVal + 1
|
|
oldBits := math.Float64bits(oldVal)
|
|
newBits := math.Float64bits(newVal)
|
|
if atomic.CompareAndSwapInt64(&vg.value, int64(oldBits), int64(newBits)) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dec decrements the gauge by 1
|
|
func (vg *metricGauge) Dec() {
|
|
for {
|
|
oldVal := vg.Get()
|
|
newVal := oldVal - 1
|
|
oldBits := math.Float64bits(oldVal)
|
|
newBits := math.Float64bits(newVal)
|
|
if atomic.CompareAndSwapInt64(&vg.value, int64(oldBits), int64(newBits)) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add adds a value to the gauge
|
|
func (vg *metricGauge) Add(val float64) {
|
|
for {
|
|
oldVal := vg.Get()
|
|
newVal := oldVal + val
|
|
oldBits := math.Float64bits(oldVal)
|
|
newBits := math.Float64bits(newVal)
|
|
if atomic.CompareAndSwapInt64(&vg.value, int64(oldBits), int64(newBits)) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sub subtracts a value from the gauge
|
|
func (vg *metricGauge) Sub(val float64) {
|
|
for {
|
|
oldVal := vg.Get()
|
|
newVal := oldVal - val
|
|
oldBits := math.Float64bits(oldVal)
|
|
newBits := math.Float64bits(newVal)
|
|
if atomic.CompareAndSwapInt64(&vg.value, int64(oldBits), int64(newBits)) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// String returns the gauge in the metrics text format.
|
|
func (vg *metricGauge) String() string {
|
|
return fmt.Sprintf("# HELP %s %s\n# TYPE %s gauge\n%s %f", vg.name, vg.help, vg.name, vg.name, vg.Get())
|
|
}
|
|
|
|
// Value returns the current value.
|
|
func (vg *metricGauge) Value() float64 {
|
|
return vg.Get()
|
|
}
|
|
|
|
// metricHistogram provides a histogram.
|
|
type metricHistogram struct {
|
|
name string
|
|
help string
|
|
buckets []float64
|
|
bucketCounts []uint64 // Count of values in each bucket
|
|
count uint64 // Total count of observations
|
|
sum float64 // Sum of all observations
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// newHistogram creates a histogram.
|
|
func newHistogram(name, help string, buckets []float64) *metricHistogram {
|
|
if len(buckets) == 0 {
|
|
buckets = DefBuckets
|
|
}
|
|
// Sort buckets to ensure they're in ascending order
|
|
sortedBuckets := make([]float64, len(buckets))
|
|
copy(sortedBuckets, buckets)
|
|
sort.Float64s(sortedBuckets)
|
|
|
|
return &metricHistogram{
|
|
name: name,
|
|
help: help,
|
|
buckets: sortedBuckets,
|
|
bucketCounts: make([]uint64, len(sortedBuckets)+1), // +1 for +Inf bucket
|
|
}
|
|
}
|
|
|
|
// Observe records a value in the histogram
|
|
func (vh *metricHistogram) Observe(val float64) {
|
|
vh.mu.Lock()
|
|
defer vh.mu.Unlock()
|
|
|
|
// Find the appropriate bucket
|
|
bucketIdx := len(vh.buckets) // Default to +Inf bucket
|
|
for i, bucket := range vh.buckets {
|
|
if val <= bucket {
|
|
bucketIdx = i
|
|
break
|
|
}
|
|
}
|
|
|
|
// Increment the appropriate bucket count
|
|
atomic.AddUint64(&vh.bucketCounts[bucketIdx], 1)
|
|
|
|
// Increment total count
|
|
atomic.AddUint64(&vh.count, 1)
|
|
|
|
// Add to sum
|
|
for {
|
|
oldSum := vh.sum
|
|
newSum := oldSum + val
|
|
if atomic.CompareAndSwapUint64((*uint64)(unsafe.Pointer(&vh.sum)), math.Float64bits(oldSum), math.Float64bits(newSum)) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetBucketCounts returns the current bucket counts
|
|
func (vh *metricHistogram) GetBucketCounts() []uint64 {
|
|
vh.mu.RLock()
|
|
defer vh.mu.RUnlock()
|
|
|
|
result := make([]uint64, len(vh.bucketCounts))
|
|
for i := range vh.bucketCounts {
|
|
result[i] = atomic.LoadUint64(&vh.bucketCounts[i])
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetCount returns the total count
|
|
func (vh *metricHistogram) GetCount() uint64 {
|
|
return atomic.LoadUint64(&vh.count)
|
|
}
|
|
|
|
// GetSum returns the sum.
|
|
func (vh *metricHistogram) GetSum() float64 {
|
|
return math.Float64frombits(atomic.LoadUint64((*uint64)(unsafe.Pointer(&vh.sum))))
|
|
}
|
|
|
|
// ToMetric returns a Metric representation for exposition.
|
|
func (vh *metricHistogram) ToMetric(labels []LabelPair) Metric {
|
|
vh.mu.RLock()
|
|
defer vh.mu.RUnlock()
|
|
|
|
var buckets []Bucket
|
|
var cumulative uint64
|
|
for i, upper := range vh.buckets {
|
|
cumulative += atomic.LoadUint64(&vh.bucketCounts[i])
|
|
buckets = append(buckets, Bucket{UpperBound: upper, CumulativeCount: cumulative})
|
|
}
|
|
// +Inf bucket
|
|
cumulative += atomic.LoadUint64(&vh.bucketCounts[len(vh.bucketCounts)-1])
|
|
buckets = append(buckets, Bucket{UpperBound: math.Inf(1), CumulativeCount: cumulative})
|
|
|
|
return Metric{
|
|
Labels: labels,
|
|
Value: MetricValue{
|
|
SampleCount: atomic.LoadUint64(&vh.count),
|
|
SampleSum: math.Float64frombits(atomic.LoadUint64((*uint64)(unsafe.Pointer(&vh.sum)))),
|
|
Buckets: buckets,
|
|
},
|
|
}
|
|
}
|
|
|
|
// String returns the histogram in the metrics text format.
|
|
func (vh *metricHistogram) String() string {
|
|
vh.mu.RLock()
|
|
defer vh.mu.RUnlock()
|
|
|
|
var sb strings.Builder
|
|
|
|
sb.WriteString(fmt.Sprintf("# HELP %s %s\n", vh.name, vh.help))
|
|
sb.WriteString(fmt.Sprintf("# TYPE %s histogram\n", vh.name))
|
|
|
|
// Write bucket counts
|
|
cumulative := uint64(0)
|
|
for i, bucket := range vh.buckets {
|
|
cumulative += atomic.LoadUint64(&vh.bucketCounts[i])
|
|
sb.WriteString(fmt.Sprintf("%s_bucket{le=\"%g\"} %d\n", vh.name, bucket, cumulative))
|
|
}
|
|
|
|
// Write +Inf bucket
|
|
cumulative += atomic.LoadUint64(&vh.bucketCounts[len(vh.buckets)])
|
|
sb.WriteString(fmt.Sprintf("%s_bucket{le=\"+Inf\"} %d\n", vh.name, cumulative))
|
|
|
|
// Write count and sum
|
|
sb.WriteString(fmt.Sprintf("%s_count %d\n", vh.name, atomic.LoadUint64(&vh.count)))
|
|
sb.WriteString(fmt.Sprintf("%s_sum %g\n", vh.name, math.Float64frombits(atomic.LoadUint64((*uint64)(unsafe.Pointer(&vh.sum))))))
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
// metricSummary provides a summary.
|
|
type metricSummary struct {
|
|
name string
|
|
help string
|
|
count uint64
|
|
sum float64
|
|
objectives []float64
|
|
samples []float64
|
|
sampleIdx int
|
|
maxSamples int
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// newSummary creates a summary.
|
|
func newSummary(name, help string, objectives map[float64]float64) *metricSummary {
|
|
objList := make([]float64, 0, len(objectives))
|
|
for q := range objectives {
|
|
objList = append(objList, q)
|
|
}
|
|
if len(objList) == 0 {
|
|
objList = []float64{0.5, 0.9, 0.99}
|
|
}
|
|
sort.Float64s(objList)
|
|
return &metricSummary{
|
|
name: name,
|
|
help: help,
|
|
objectives: objList,
|
|
maxSamples: 1024,
|
|
}
|
|
}
|
|
|
|
// Observe records a value in the summary
|
|
func (vs *metricSummary) Observe(val float64) {
|
|
vs.mu.Lock()
|
|
defer vs.mu.Unlock()
|
|
|
|
atomic.AddUint64(&vs.count, 1)
|
|
|
|
// Add to sum atomically
|
|
for {
|
|
oldSum := vs.sum
|
|
newSum := oldSum + val
|
|
if atomic.CompareAndSwapUint64((*uint64)(unsafe.Pointer(&vs.sum)), math.Float64bits(oldSum), math.Float64bits(newSum)) {
|
|
break
|
|
}
|
|
}
|
|
|
|
if vs.maxSamples <= 0 {
|
|
return
|
|
}
|
|
if len(vs.samples) < vs.maxSamples {
|
|
vs.samples = append(vs.samples, val)
|
|
return
|
|
}
|
|
vs.samples[vs.sampleIdx] = val
|
|
vs.sampleIdx = (vs.sampleIdx + 1) % vs.maxSamples
|
|
}
|
|
|
|
// GetCount returns the total count
|
|
func (vs *metricSummary) GetCount() uint64 {
|
|
return atomic.LoadUint64(&vs.count)
|
|
}
|
|
|
|
// GetSum returns the sum.
|
|
func (vs *metricSummary) GetSum() float64 {
|
|
return math.Float64frombits(atomic.LoadUint64((*uint64)(unsafe.Pointer(&vs.sum))))
|
|
}
|
|
|
|
// ToMetric returns a Metric representation for exposition.
|
|
func (vs *metricSummary) ToMetric(labels []LabelPair) Metric {
|
|
vs.mu.RLock()
|
|
defer vs.mu.RUnlock()
|
|
|
|
return Metric{
|
|
Labels: labels,
|
|
Value: MetricValue{
|
|
SampleCount: atomic.LoadUint64(&vs.count),
|
|
SampleSum: math.Float64frombits(atomic.LoadUint64((*uint64)(unsafe.Pointer(&vs.sum)))),
|
|
Quantiles: quantilesFromSamples(vs.samples, vs.objectives),
|
|
},
|
|
}
|
|
}
|
|
|
|
func quantilesFromSamples(samples []float64, objectives []float64) []Quantile {
|
|
if len(samples) == 0 || len(objectives) == 0 {
|
|
return nil
|
|
}
|
|
data := append([]float64(nil), samples...)
|
|
sort.Float64s(data)
|
|
quantiles := make([]Quantile, 0, len(objectives))
|
|
for _, q := range objectives {
|
|
if q <= 0 {
|
|
quantiles = append(quantiles, Quantile{Quantile: q, Value: data[0]})
|
|
continue
|
|
}
|
|
if q >= 1 {
|
|
quantiles = append(quantiles, Quantile{Quantile: q, Value: data[len(data)-1]})
|
|
continue
|
|
}
|
|
idx := int(math.Ceil(q*float64(len(data)))) - 1
|
|
if idx < 0 {
|
|
idx = 0
|
|
} else if idx >= len(data) {
|
|
idx = len(data) - 1
|
|
}
|
|
quantiles = append(quantiles, Quantile{Quantile: q, Value: data[idx]})
|
|
}
|
|
return quantiles
|
|
}
|
|
|
|
// String returns the summary in the metrics text format.
|
|
func (vs *metricSummary) String() string {
|
|
vs.mu.RLock()
|
|
defer vs.mu.RUnlock()
|
|
|
|
var sb strings.Builder
|
|
|
|
sb.WriteString(fmt.Sprintf("# HELP %s %s\n", vs.name, vs.help))
|
|
sb.WriteString(fmt.Sprintf("# TYPE %s summary\n", vs.name))
|
|
|
|
// Write count and sum
|
|
sb.WriteString(fmt.Sprintf("%s_count %d\n", vs.name, atomic.LoadUint64(&vs.count)))
|
|
sb.WriteString(fmt.Sprintf("%s_sum %g\n", vs.name, math.Float64frombits(atomic.LoadUint64((*uint64)(unsafe.Pointer(&vs.sum))))))
|
|
|
|
// Write quantiles
|
|
for _, q := range quantilesFromSamples(vs.samples, vs.objectives) {
|
|
sb.WriteString(fmt.Sprintf("%s{quantile=\"%g\"} %g\n", vs.name, q.Quantile, q.Value))
|
|
}
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
// timingMetric provides timing functionality for histogram-backed timing.
|
|
type timingMetric struct {
|
|
histogram Histogram
|
|
start time.Time
|
|
}
|
|
|
|
// newTimingMetric creates a new timing metric.
|
|
func newTimingMetric(histogram Histogram) *timingMetric {
|
|
return &timingMetric{
|
|
histogram: histogram,
|
|
start: time.Now(),
|
|
}
|
|
}
|
|
|
|
// Stop stops the timing and records the duration
|
|
func (vtm *timingMetric) Stop() {
|
|
duration := time.Since(vtm.start).Seconds()
|
|
vtm.histogram.Observe(duration)
|
|
}
|
|
|
|
// Reset resets the timing
|
|
func (vtm *timingMetric) Reset() {
|
|
vtm.start = time.Now()
|
|
}
|
|
|
|
// Duration returns the current duration
|
|
func (vtm *timingMetric) Duration() time.Duration {
|
|
return time.Since(vtm.start)
|
|
}
|
|
|
|
// Start starts the timer and returns a function to stop it
|
|
func (vtm *timingMetric) Start() func() {
|
|
vtm.start = time.Now()
|
|
return vtm.Stop
|
|
}
|
|
|
|
// ObserveTime observes the given duration
|
|
func (vtm *timingMetric) ObserveTime(d time.Duration) {
|
|
vtm.histogram.Observe(d.Seconds())
|
|
}
|
|
|
|
// factory creates metrics.
|
|
type factory struct {
|
|
registry Registry
|
|
}
|
|
|
|
// NewFactory creates a factory that produces metrics.
|
|
func NewFactory() Factory {
|
|
return &factory{registry: NewRegistry()}
|
|
}
|
|
|
|
// NewFactoryWithRegistry creates a factory using an existing registry when possible.
|
|
func NewFactoryWithRegistry(reg Registry) Factory {
|
|
if reg == nil {
|
|
return NewFactory()
|
|
}
|
|
return &factory{registry: reg}
|
|
}
|
|
|
|
// New creates a new metrics instance with the given namespace.
|
|
func (hpf *factory) New(namespace string) Metrics {
|
|
return &metrics{
|
|
namespace: namespace,
|
|
registry: hpf.registry,
|
|
}
|
|
}
|
|
|
|
// NewWithRegistry creates a new metrics instance using the provided registry when possible.
|
|
func (hpf *factory) NewWithRegistry(namespace string, registry Registry) Metrics {
|
|
if registry == nil {
|
|
registry = hpf.registry
|
|
}
|
|
return &metrics{
|
|
namespace: namespace,
|
|
registry: registry,
|
|
}
|
|
}
|
|
|
|
// NewCounter creates a counter.
|
|
func (hpf *factory) NewCounter(name, help string) Counter {
|
|
return hpf.registry.NewCounter(name, help)
|
|
}
|
|
|
|
// NewGauge creates a gauge.
|
|
func (hpf *factory) NewGauge(name, help string) Gauge {
|
|
return hpf.registry.NewGauge(name, help)
|
|
}
|
|
|
|
// NewHistogram creates a histogram.
|
|
func (hpf *factory) NewHistogram(name, help string, buckets []float64) Histogram {
|
|
return hpf.registry.NewHistogram(name, help, buckets)
|
|
}
|
|
|
|
// NewSummary creates a summary.
|
|
func (hpf *factory) NewSummary(name, help string, objectives map[float64]float64) Summary {
|
|
return hpf.registry.NewSummary(name, help, objectives)
|
|
}
|
|
|
|
type metrics struct {
|
|
namespace string
|
|
registry Registry
|
|
}
|
|
|
|
func (m *metrics) NewCounter(name, help string) Counter {
|
|
return m.registry.NewCounter(prefixedName(m.namespace, name), help)
|
|
}
|
|
|
|
func (m *metrics) NewCounterVec(name, help string, labelNames []string) CounterVec {
|
|
return m.registry.NewCounterVec(prefixedName(m.namespace, name), help, labelNames)
|
|
}
|
|
|
|
func (m *metrics) NewGauge(name, help string) Gauge {
|
|
return m.registry.NewGauge(prefixedName(m.namespace, name), help)
|
|
}
|
|
|
|
func (m *metrics) NewGaugeVec(name, help string, labelNames []string) GaugeVec {
|
|
return m.registry.NewGaugeVec(prefixedName(m.namespace, name), help, labelNames)
|
|
}
|
|
|
|
func (m *metrics) NewHistogram(name, help string, buckets []float64) Histogram {
|
|
return m.registry.NewHistogram(prefixedName(m.namespace, name), help, buckets)
|
|
}
|
|
|
|
func (m *metrics) NewHistogramVec(name, help string, labelNames []string, buckets []float64) HistogramVec {
|
|
return m.registry.NewHistogramVec(prefixedName(m.namespace, name), help, labelNames, buckets)
|
|
}
|
|
|
|
func (m *metrics) NewSummary(name, help string, objectives map[float64]float64) Summary {
|
|
return m.registry.NewSummary(prefixedName(m.namespace, name), help, objectives)
|
|
}
|
|
|
|
func (m *metrics) NewSummaryVec(name, help string, labelNames []string, objectives map[float64]float64) SummaryVec {
|
|
return m.registry.NewSummaryVec(prefixedName(m.namespace, name), help, labelNames, objectives)
|
|
}
|
|
|
|
func (m *metrics) Registry() Registry {
|
|
return m.registry
|
|
}
|
|
|
|
func prefixedName(namespace, name string) string {
|
|
if namespace == "" {
|
|
return name
|
|
}
|
|
return namespace + "_" + name
|
|
}
|
|
|
|
// registry collects metrics and exposes them via Gather.
|
|
type registry struct {
|
|
mu sync.RWMutex
|
|
counters map[string]map[string]*labeledCounter
|
|
gauges map[string]map[string]*labeledGauge
|
|
histograms map[string]map[string]*labeledHistogram
|
|
summaries map[string]map[string]*labeledSummary
|
|
registered map[string]MetricType
|
|
}
|
|
|
|
type labeledCounter struct {
|
|
labels Labels
|
|
counter *metricCounter
|
|
}
|
|
|
|
type labeledGauge struct {
|
|
labels Labels
|
|
gauge *metricGauge
|
|
}
|
|
|
|
type labeledHistogram struct {
|
|
labels Labels
|
|
histogram *metricHistogram
|
|
}
|
|
|
|
type labeledSummary struct {
|
|
labels Labels
|
|
summary *metricSummary
|
|
}
|
|
|
|
// newRegistry creates an empty registry.
|
|
func newRegistry() *registry {
|
|
return ®istry{
|
|
counters: make(map[string]map[string]*labeledCounter),
|
|
gauges: make(map[string]map[string]*labeledGauge),
|
|
histograms: make(map[string]map[string]*labeledHistogram),
|
|
summaries: make(map[string]map[string]*labeledSummary),
|
|
registered: make(map[string]MetricType),
|
|
}
|
|
}
|
|
|
|
// RegisterCounter registers a counter without labels.
|
|
func (hpr *registry) RegisterCounter(name string, counter *metricCounter) {
|
|
hpr.RegisterLabeledCounter(name, nil, counter)
|
|
}
|
|
|
|
// RegisterGauge registers a gauge without labels.
|
|
func (hpr *registry) RegisterGauge(name string, gauge *metricGauge) {
|
|
hpr.RegisterLabeledGauge(name, nil, gauge)
|
|
}
|
|
|
|
// RegisterHistogram registers a histogram without labels.
|
|
func (hpr *registry) RegisterHistogram(name string, histogram *metricHistogram) {
|
|
hpr.RegisterLabeledHistogram(name, nil, histogram)
|
|
}
|
|
|
|
// RegisterSummary registers a summary without labels.
|
|
func (hpr *registry) RegisterSummary(name string, summary *metricSummary) {
|
|
hpr.RegisterLabeledSummary(name, nil, summary)
|
|
}
|
|
|
|
// RegisterLabeledCounter registers a counter with labels.
|
|
func (hpr *registry) RegisterLabeledCounter(name string, labels Labels, counter *metricCounter) {
|
|
hpr.mu.Lock()
|
|
defer hpr.mu.Unlock()
|
|
key := labelsKeyFromLabels(labels)
|
|
if hpr.counters[name] == nil {
|
|
hpr.counters[name] = make(map[string]*labeledCounter)
|
|
}
|
|
hpr.counters[name][key] = &labeledCounter{labels: cloneLabels(labels), counter: counter}
|
|
}
|
|
|
|
// RegisterLabeledGauge registers a gauge with labels.
|
|
func (hpr *registry) RegisterLabeledGauge(name string, labels Labels, gauge *metricGauge) {
|
|
hpr.mu.Lock()
|
|
defer hpr.mu.Unlock()
|
|
key := labelsKeyFromLabels(labels)
|
|
if hpr.gauges[name] == nil {
|
|
hpr.gauges[name] = make(map[string]*labeledGauge)
|
|
}
|
|
hpr.gauges[name][key] = &labeledGauge{labels: cloneLabels(labels), gauge: gauge}
|
|
}
|
|
|
|
// RegisterLabeledHistogram registers a histogram with labels.
|
|
func (hpr *registry) RegisterLabeledHistogram(name string, labels Labels, histogram *metricHistogram) {
|
|
hpr.mu.Lock()
|
|
defer hpr.mu.Unlock()
|
|
key := labelsKeyFromLabels(labels)
|
|
if hpr.histograms[name] == nil {
|
|
hpr.histograms[name] = make(map[string]*labeledHistogram)
|
|
}
|
|
hpr.histograms[name][key] = &labeledHistogram{labels: cloneLabels(labels), histogram: histogram}
|
|
}
|
|
|
|
// RegisterLabeledSummary registers a summary with labels.
|
|
func (hpr *registry) RegisterLabeledSummary(name string, labels Labels, summary *metricSummary) {
|
|
hpr.mu.Lock()
|
|
defer hpr.mu.Unlock()
|
|
key := labelsKeyFromLabels(labels)
|
|
if hpr.summaries[name] == nil {
|
|
hpr.summaries[name] = make(map[string]*labeledSummary)
|
|
}
|
|
hpr.summaries[name][key] = &labeledSummary{labels: cloneLabels(labels), summary: summary}
|
|
}
|
|
|
|
// deregisterLabeled drops all label-permutation children for the named
|
|
// metric across counter/gauge/histogram/summary registries. Called by
|
|
// {Counter,Gauge,Histogram,Summary}Vec.Reset() to mirror prometheus
|
|
// vec semantics.
|
|
func (hpr *registry) deregisterLabeled(name string) {
|
|
hpr.mu.Lock()
|
|
defer hpr.mu.Unlock()
|
|
delete(hpr.counters, name)
|
|
delete(hpr.gauges, name)
|
|
delete(hpr.histograms, name)
|
|
delete(hpr.summaries, name)
|
|
}
|
|
|
|
// NewCounter creates and registers a counter.
|
|
func (hpr *registry) NewCounter(name, help string) Counter {
|
|
counter := newCounter(name, help)
|
|
hpr.RegisterCounter(name, counter)
|
|
return counter
|
|
}
|
|
|
|
// NewCounterVec creates and registers a counter vec.
|
|
func (hpr *registry) NewCounterVec(name, help string, labelNames []string) CounterVec {
|
|
return newCounterVec(hpr, name, help, labelNames)
|
|
}
|
|
|
|
// NewGauge creates and registers a gauge.
|
|
func (hpr *registry) NewGauge(name, help string) Gauge {
|
|
gauge := newGauge(name, help)
|
|
hpr.RegisterGauge(name, gauge)
|
|
return gauge
|
|
}
|
|
|
|
// NewGaugeVec creates and registers a gauge vec.
|
|
func (hpr *registry) NewGaugeVec(name, help string, labelNames []string) GaugeVec {
|
|
return newGaugeVec(hpr, name, help, labelNames)
|
|
}
|
|
|
|
// NewHistogram creates and registers a histogram.
|
|
func (hpr *registry) NewHistogram(name, help string, buckets []float64) Histogram {
|
|
histogram := newHistogram(name, help, buckets)
|
|
hpr.RegisterHistogram(name, histogram)
|
|
return histogram
|
|
}
|
|
|
|
// NewHistogramVec creates and registers a histogram vec.
|
|
func (hpr *registry) NewHistogramVec(name, help string, labelNames []string, buckets []float64) HistogramVec {
|
|
return newHistogramVec(hpr, name, help, labelNames, buckets)
|
|
}
|
|
|
|
// NewSummary creates and registers a summary.
|
|
func (hpr *registry) NewSummary(name, help string, objectives map[float64]float64) Summary {
|
|
summary := newSummary(name, help, objectives)
|
|
hpr.RegisterSummary(name, summary)
|
|
return summary
|
|
}
|
|
|
|
// NewSummaryVec creates and registers a summary vec.
|
|
func (hpr *registry) NewSummaryVec(name, help string, labelNames []string, objectives map[float64]float64) SummaryVec {
|
|
return newSummaryVec(hpr, name, help, labelNames, objectives)
|
|
}
|
|
|
|
// Registry returns the registry itself.
|
|
func (hpr *registry) Registry() Registry {
|
|
return hpr
|
|
}
|
|
|
|
// Register is a compatibility no-op. Metrics are registered on creation.
|
|
func (hpr *registry) Register(c Collector) error {
|
|
name, typ, ok := collectorIdentity(c)
|
|
if !ok {
|
|
return fmt.Errorf("unsupported collector type %T", c)
|
|
}
|
|
if err := hpr.registerName(name, typ); err != nil {
|
|
return err
|
|
}
|
|
switch v := c.(type) {
|
|
case *metricCounter:
|
|
hpr.RegisterCounter(name, v)
|
|
case *metricGauge:
|
|
hpr.RegisterGauge(name, v)
|
|
case *metricHistogram:
|
|
hpr.RegisterHistogram(name, v)
|
|
case *metricSummary:
|
|
hpr.RegisterSummary(name, v)
|
|
case *counterVec:
|
|
v.registry = hpr
|
|
case *gaugeVec:
|
|
v.registry = hpr
|
|
case *histogramVec:
|
|
v.registry = hpr
|
|
case *summaryVec:
|
|
v.registry = hpr
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MustRegister registers collectors and panics on error.
|
|
func (hpr *registry) MustRegister(cs ...Collector) {
|
|
for _, c := range cs {
|
|
if err := hpr.Register(c); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unregister drops the collector's metric family (all label permutations) and
|
|
// frees its name for re-registration. Returns true if the name was registered.
|
|
// Mirrors prometheus.Registerer.Unregister.
|
|
func (hpr *registry) Unregister(c Collector) bool {
|
|
name, _, ok := collectorIdentity(c)
|
|
if !ok {
|
|
return false
|
|
}
|
|
hpr.mu.Lock()
|
|
defer hpr.mu.Unlock()
|
|
_, had := hpr.registered[name]
|
|
delete(hpr.registered, name)
|
|
delete(hpr.counters, name)
|
|
delete(hpr.gauges, name)
|
|
delete(hpr.histograms, name)
|
|
delete(hpr.summaries, name)
|
|
return had
|
|
}
|
|
|
|
// Gather returns metric families for all registered metrics.
|
|
func (hpr *registry) Gather() ([]*MetricFamily, error) {
|
|
hpr.mu.RLock()
|
|
defer hpr.mu.RUnlock()
|
|
|
|
var families []*MetricFamily
|
|
for name, entries := range hpr.counters {
|
|
help := ""
|
|
for _, entry := range entries {
|
|
help = entry.counter.help
|
|
break
|
|
}
|
|
family := &MetricFamily{Name: name, Help: help, Type: MetricTypeCounter}
|
|
for _, entry := range entries {
|
|
family.Metrics = append(family.Metrics, Metric{
|
|
Labels: labelsToLabelPairs(entry.labels),
|
|
Value: MetricValue{Value: entry.counter.Get()},
|
|
})
|
|
}
|
|
families = append(families, family)
|
|
}
|
|
for name, entries := range hpr.gauges {
|
|
help := ""
|
|
for _, entry := range entries {
|
|
help = entry.gauge.help
|
|
break
|
|
}
|
|
family := &MetricFamily{Name: name, Help: help, Type: MetricTypeGauge}
|
|
for _, entry := range entries {
|
|
family.Metrics = append(family.Metrics, Metric{
|
|
Labels: labelsToLabelPairs(entry.labels),
|
|
Value: MetricValue{Value: entry.gauge.Get()},
|
|
})
|
|
}
|
|
families = append(families, family)
|
|
}
|
|
for name, entries := range hpr.histograms {
|
|
help := ""
|
|
for _, entry := range entries {
|
|
help = entry.histogram.help
|
|
break
|
|
}
|
|
family := &MetricFamily{Name: name, Help: help, Type: MetricTypeHistogram}
|
|
for _, entry := range entries {
|
|
family.Metrics = append(family.Metrics, entry.histogram.ToMetric(labelsToLabelPairs(entry.labels)))
|
|
}
|
|
families = append(families, family)
|
|
}
|
|
for name, entries := range hpr.summaries {
|
|
help := ""
|
|
for _, entry := range entries {
|
|
help = entry.summary.help
|
|
break
|
|
}
|
|
family := &MetricFamily{Name: name, Help: help, Type: MetricTypeSummary}
|
|
for _, entry := range entries {
|
|
family.Metrics = append(family.Metrics, entry.summary.ToMetric(labelsToLabelPairs(entry.labels)))
|
|
}
|
|
families = append(families, family)
|
|
}
|
|
return families, nil
|
|
}
|
|
|
|
// counterVec is a labeled counter collection.
|
|
type counterVec struct {
|
|
registry *registry
|
|
name string
|
|
help string
|
|
labelNames []string
|
|
mu sync.Mutex
|
|
counters map[string]Counter
|
|
}
|
|
|
|
func newCounterVec(registry *registry, name, help string, labelNames []string) *counterVec {
|
|
return &counterVec{
|
|
registry: registry,
|
|
name: name,
|
|
help: help,
|
|
labelNames: append([]string(nil), labelNames...),
|
|
counters: make(map[string]Counter),
|
|
}
|
|
}
|
|
|
|
func (v *counterVec) With(labels Labels) Counter {
|
|
return v.getOrCreate(labels)
|
|
}
|
|
|
|
func (v *counterVec) WithLabelValues(values ...string) Counter {
|
|
labels := labelsFromValues(v.labelNames, values)
|
|
return v.getOrCreate(labels)
|
|
}
|
|
|
|
func (v *counterVec) getOrCreate(labels Labels) Counter {
|
|
key := labelsKeyFromLabels(labels)
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
if c, ok := v.counters[key]; ok {
|
|
return c
|
|
}
|
|
counter := newCounter(v.name, v.help)
|
|
v.registry.RegisterLabeledCounter(v.name, labels, counter)
|
|
v.counters[key] = counter
|
|
return counter
|
|
}
|
|
|
|
// Reset drops every label-permutation child from this vec. Mirrors
|
|
// prometheus/client_golang.CounterVec.Reset semantics.
|
|
func (v *counterVec) Reset() {
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
v.registry.deregisterLabeled(v.name)
|
|
v.counters = make(map[string]Counter)
|
|
}
|
|
|
|
// gaugeVec is a labeled gauge collection.
|
|
type gaugeVec struct {
|
|
registry *registry
|
|
name string
|
|
help string
|
|
labelNames []string
|
|
mu sync.Mutex
|
|
gauges map[string]Gauge
|
|
}
|
|
|
|
func newGaugeVec(registry *registry, name, help string, labelNames []string) *gaugeVec {
|
|
return &gaugeVec{
|
|
registry: registry,
|
|
name: name,
|
|
help: help,
|
|
labelNames: append([]string(nil), labelNames...),
|
|
gauges: make(map[string]Gauge),
|
|
}
|
|
}
|
|
|
|
func (v *gaugeVec) With(labels Labels) Gauge {
|
|
return v.getOrCreate(labels)
|
|
}
|
|
|
|
func (v *gaugeVec) WithLabelValues(values ...string) Gauge {
|
|
labels := labelsFromValues(v.labelNames, values)
|
|
return v.getOrCreate(labels)
|
|
}
|
|
|
|
func (v *gaugeVec) getOrCreate(labels Labels) Gauge {
|
|
key := labelsKeyFromLabels(labels)
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
if g, ok := v.gauges[key]; ok {
|
|
return g
|
|
}
|
|
gauge := newGauge(v.name, v.help)
|
|
v.registry.RegisterLabeledGauge(v.name, labels, gauge)
|
|
v.gauges[key] = gauge
|
|
return gauge
|
|
}
|
|
|
|
// Reset drops every label-permutation child from this vec.
|
|
func (v *gaugeVec) Reset() {
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
v.registry.deregisterLabeled(v.name)
|
|
v.gauges = make(map[string]Gauge)
|
|
}
|
|
|
|
// histogramVec is a labeled histogram collection.
|
|
type histogramVec struct {
|
|
registry *registry
|
|
name string
|
|
help string
|
|
labelNames []string
|
|
buckets []float64
|
|
mu sync.Mutex
|
|
histograms map[string]Histogram
|
|
}
|
|
|
|
func newHistogramVec(registry *registry, name, help string, labelNames []string, buckets []float64) *histogramVec {
|
|
return &histogramVec{
|
|
registry: registry,
|
|
name: name,
|
|
help: help,
|
|
labelNames: append([]string(nil), labelNames...),
|
|
buckets: append([]float64(nil), buckets...),
|
|
histograms: make(map[string]Histogram),
|
|
}
|
|
}
|
|
|
|
func (v *histogramVec) With(labels Labels) Histogram {
|
|
return v.getOrCreate(labels)
|
|
}
|
|
|
|
func (v *histogramVec) WithLabelValues(values ...string) Histogram {
|
|
labels := labelsFromValues(v.labelNames, values)
|
|
return v.getOrCreate(labels)
|
|
}
|
|
|
|
func (v *histogramVec) getOrCreate(labels Labels) Histogram {
|
|
key := labelsKeyFromLabels(labels)
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
if h, ok := v.histograms[key]; ok {
|
|
return h
|
|
}
|
|
histogram := newHistogram(v.name, v.help, v.buckets)
|
|
v.registry.RegisterLabeledHistogram(v.name, labels, histogram)
|
|
v.histograms[key] = histogram
|
|
return histogram
|
|
}
|
|
|
|
// Reset drops every label-permutation child from this vec.
|
|
func (v *histogramVec) Reset() {
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
v.registry.deregisterLabeled(v.name)
|
|
v.histograms = make(map[string]Histogram)
|
|
}
|
|
|
|
// summaryVec is a labeled summary collection.
|
|
type summaryVec struct {
|
|
registry *registry
|
|
name string
|
|
help string
|
|
labelNames []string
|
|
objectives map[float64]float64
|
|
mu sync.Mutex
|
|
summaries map[string]Summary
|
|
}
|
|
|
|
func newSummaryVec(registry *registry, name, help string, labelNames []string, objectives map[float64]float64) *summaryVec {
|
|
objCopy := make(map[float64]float64, len(objectives))
|
|
for k, v := range objectives {
|
|
objCopy[k] = v
|
|
}
|
|
return &summaryVec{
|
|
registry: registry,
|
|
name: name,
|
|
help: help,
|
|
labelNames: append([]string(nil), labelNames...),
|
|
objectives: objCopy,
|
|
summaries: make(map[string]Summary),
|
|
}
|
|
}
|
|
|
|
func (v *summaryVec) With(labels Labels) Summary {
|
|
return v.getOrCreate(labels)
|
|
}
|
|
|
|
func (v *summaryVec) WithLabelValues(values ...string) Summary {
|
|
labels := labelsFromValues(v.labelNames, values)
|
|
return v.getOrCreate(labels)
|
|
}
|
|
|
|
func (v *summaryVec) getOrCreate(labels Labels) Summary {
|
|
key := labelsKeyFromLabels(labels)
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
if s, ok := v.summaries[key]; ok {
|
|
return s
|
|
}
|
|
summary := newSummary(v.name, v.help, v.objectives)
|
|
v.registry.RegisterLabeledSummary(v.name, labels, summary)
|
|
v.summaries[key] = summary
|
|
return summary
|
|
}
|
|
|
|
// Reset drops every label-permutation child from this vec.
|
|
func (v *summaryVec) Reset() {
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
v.registry.deregisterLabeled(v.name)
|
|
v.summaries = make(map[string]Summary)
|
|
}
|
|
|
|
func labelsFromValues(labelNames []string, values []string) Labels {
|
|
labels := make(Labels, len(labelNames))
|
|
for i, name := range labelNames {
|
|
if i < len(values) {
|
|
labels[name] = values[i]
|
|
}
|
|
}
|
|
return labels
|
|
}
|
|
|
|
func labelsKeyFromLabels(labels Labels) string {
|
|
if len(labels) == 0 {
|
|
return ""
|
|
}
|
|
keys := make([]string, 0, len(labels))
|
|
for k := range labels {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
var sb strings.Builder
|
|
sb.WriteString("{")
|
|
for i, k := range keys {
|
|
if i > 0 {
|
|
sb.WriteString(",")
|
|
}
|
|
sb.WriteString(k)
|
|
sb.WriteString("=\"")
|
|
sb.WriteString(labels[k])
|
|
sb.WriteString("\"")
|
|
}
|
|
sb.WriteString("}")
|
|
return sb.String()
|
|
}
|
|
|
|
func cloneLabels(labels Labels) Labels {
|
|
if len(labels) == 0 {
|
|
return nil
|
|
}
|
|
clone := make(Labels, len(labels))
|
|
for k, v := range labels {
|
|
clone[k] = v
|
|
}
|
|
return clone
|
|
}
|
|
|
|
func labelsToLabelPairs(labels Labels) []LabelPair {
|
|
if len(labels) == 0 {
|
|
return nil
|
|
}
|
|
keys := make([]string, 0, len(labels))
|
|
for k := range labels {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
res := make([]LabelPair, 0, len(keys))
|
|
for _, k := range keys {
|
|
res = append(res, LabelPair{Name: k, Value: labels[k]})
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (hpr *registry) registerName(name string, typ MetricType) error {
|
|
hpr.mu.Lock()
|
|
defer hpr.mu.Unlock()
|
|
if existing, ok := hpr.registered[name]; ok {
|
|
return fmt.Errorf("metric %q already registered as %s", name, existing.String())
|
|
}
|
|
hpr.registered[name] = typ
|
|
return nil
|
|
}
|
|
|
|
func collectorIdentity(c Collector) (string, MetricType, bool) {
|
|
switch v := c.(type) {
|
|
case *metricCounter:
|
|
return v.name, MetricTypeCounter, true
|
|
case *metricGauge:
|
|
return v.name, MetricTypeGauge, true
|
|
case *metricHistogram:
|
|
return v.name, MetricTypeHistogram, true
|
|
case *metricSummary:
|
|
return v.name, MetricTypeSummary, true
|
|
case *counterVec:
|
|
return v.name, MetricTypeCounter, true
|
|
case *gaugeVec:
|
|
return v.name, MetricTypeGauge, true
|
|
case *histogramVec:
|
|
return v.name, MetricTypeHistogram, true
|
|
case *summaryVec:
|
|
return v.name, MetricTypeSummary, true
|
|
default:
|
|
return "", MetricTypeUntyped, false
|
|
}
|
|
}
|