From ad459f4fac247e066d7b0514a215b32b400d4cf0 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 6 Feb 2026 13:26:31 +0100 Subject: [PATCH] metrics: reduce allocations for metrics (#33699) --- metrics/counter.go | 5 ++++- metrics/counter_float64.go | 5 ++++- metrics/gauge.go | 5 ++++- metrics/gauge_float64.go | 5 ++++- metrics/gauge_info.go | 5 ++++- metrics/histogram.go | 10 ++++++++-- metrics/meter.go | 5 ++++- metrics/registry.go | 7 ------- metrics/registry_test.go | 27 ++++++++++++++++++++++++++- metrics/resetting_timer.go | 5 ++++- metrics/runtimehistogram.go | 6 ++++-- metrics/timer.go | 5 ++++- 12 files changed, 70 insertions(+), 20 deletions(-) diff --git a/metrics/counter.go b/metrics/counter.go index c884e9a178..0e4d93bfbc 100644 --- a/metrics/counter.go +++ b/metrics/counter.go @@ -7,7 +7,10 @@ import ( // GetOrRegisterCounter returns an existing Counter or constructs and registers // a new Counter. func GetOrRegisterCounter(name string, r Registry) *Counter { - return getOrRegister(name, NewCounter, r) + if r == nil { + r = DefaultRegistry + } + return r.GetOrRegister(name, func() any { return NewCounter() }).(*Counter) } // NewCounter constructs a new Counter. diff --git a/metrics/counter_float64.go b/metrics/counter_float64.go index 6cc73d89a2..caaaeb7be7 100644 --- a/metrics/counter_float64.go +++ b/metrics/counter_float64.go @@ -8,7 +8,10 @@ import ( // GetOrRegisterCounterFloat64 returns an existing *CounterFloat64 or constructs and registers // a new CounterFloat64. func GetOrRegisterCounterFloat64(name string, r Registry) *CounterFloat64 { - return getOrRegister(name, NewCounterFloat64, r) + if r == nil { + r = DefaultRegistry + } + return r.GetOrRegister(name, func() any { return NewCounterFloat64() }).(*CounterFloat64) } // NewCounterFloat64 constructs a new CounterFloat64. diff --git a/metrics/gauge.go b/metrics/gauge.go index 20de95255b..39d5ccb3bc 100644 --- a/metrics/gauge.go +++ b/metrics/gauge.go @@ -11,7 +11,10 @@ func (g GaugeSnapshot) Value() int64 { return int64(g) } // GetOrRegisterGauge returns an existing Gauge or constructs and registers a // new Gauge. func GetOrRegisterGauge(name string, r Registry) *Gauge { - return getOrRegister(name, NewGauge, r) + if r == nil { + r = DefaultRegistry + } + return r.GetOrRegister(name, func() any { return NewGauge() }).(*Gauge) } // NewGauge constructs a new Gauge. diff --git a/metrics/gauge_float64.go b/metrics/gauge_float64.go index 48524e4c3f..c7a1df0a23 100644 --- a/metrics/gauge_float64.go +++ b/metrics/gauge_float64.go @@ -8,7 +8,10 @@ import ( // GetOrRegisterGaugeFloat64 returns an existing GaugeFloat64 or constructs and registers a // new GaugeFloat64. func GetOrRegisterGaugeFloat64(name string, r Registry) *GaugeFloat64 { - return getOrRegister(name, NewGaugeFloat64, r) + if r == nil { + r = DefaultRegistry + } + return r.GetOrRegister(name, func() any { return NewGaugeFloat64() }).(*GaugeFloat64) } // GaugeFloat64Snapshot is a read-only copy of a GaugeFloat64. diff --git a/metrics/gauge_info.go b/metrics/gauge_info.go index 34ac917919..30c5114085 100644 --- a/metrics/gauge_info.go +++ b/metrics/gauge_info.go @@ -16,7 +16,10 @@ func (val GaugeInfoValue) String() string { // GetOrRegisterGaugeInfo returns an existing GaugeInfo or constructs and registers a // new GaugeInfo. func GetOrRegisterGaugeInfo(name string, r Registry) *GaugeInfo { - return getOrRegister(name, NewGaugeInfo, r) + if r == nil { + r = DefaultRegistry + } + return r.GetOrRegister(name, func() any { return NewGaugeInfo() }).(*GaugeInfo) } // NewGaugeInfo constructs a new GaugeInfo. diff --git a/metrics/histogram.go b/metrics/histogram.go index 18bf6e3d2b..467457abdb 100644 --- a/metrics/histogram.go +++ b/metrics/histogram.go @@ -23,13 +23,19 @@ type Histogram interface { // GetOrRegisterHistogram returns an existing Histogram or constructs and // registers a new StandardHistogram. func GetOrRegisterHistogram(name string, r Registry, s Sample) Histogram { - return getOrRegister(name, func() Histogram { return NewHistogram(s) }, r) + if r == nil { + r = DefaultRegistry + } + return r.GetOrRegister(name, func() any { return NewHistogram(s) }).(Histogram) } // GetOrRegisterHistogramLazy returns an existing Histogram or constructs and // registers a new StandardHistogram. func GetOrRegisterHistogramLazy(name string, r Registry, s func() Sample) Histogram { - return getOrRegister(name, func() Histogram { return NewHistogram(s()) }, r) + if r == nil { + r = DefaultRegistry + } + return r.GetOrRegister(name, func() any { return NewHistogram(s()) }).(Histogram) } // NewHistogram constructs a new StandardHistogram from a Sample. diff --git a/metrics/meter.go b/metrics/meter.go index ee23af10eb..2829774d8c 100644 --- a/metrics/meter.go +++ b/metrics/meter.go @@ -12,7 +12,10 @@ import ( // Be sure to unregister the meter from the registry once it is of no use to // allow for garbage collection. func GetOrRegisterMeter(name string, r Registry) *Meter { - return getOrRegister(name, NewMeter, r) + if r == nil { + r = DefaultRegistry + } + return r.GetOrRegister(name, func() any { return NewMeter() }).(*Meter) } // NewMeter constructs a new Meter and launches a goroutine. diff --git a/metrics/registry.go b/metrics/registry.go index 0294c5c44b..298598a9aa 100644 --- a/metrics/registry.go +++ b/metrics/registry.go @@ -345,13 +345,6 @@ func GetOrRegister(name string, i func() interface{}) interface{} { return DefaultRegistry.GetOrRegister(name, i) } -func getOrRegister[T any](name string, ctor func() T, r Registry) T { - if r == nil { - r = DefaultRegistry - } - return r.GetOrRegister(name, func() any { return ctor() }).(T) -} - // Register the given metric under the given name. Returns a ErrDuplicateMetric // if a metric by the given name is already registered. func Register(name string, i interface{}) error { diff --git a/metrics/registry_test.go b/metrics/registry_test.go index 6af0796da9..1aad7a0028 100644 --- a/metrics/registry_test.go +++ b/metrics/registry_test.go @@ -14,6 +14,31 @@ func BenchmarkRegistry(b *testing.B) { } } +func BenchmarkRegistryGetOrRegister(b *testing.B) { + sample := func() Sample { return nil } + tests := []struct { + name string + ctor func() any + }{ + {name: "counter", ctor: func() any { return GetOrRegisterCounter("counter", DefaultRegistry) }}, + {name: "gauge", ctor: func() any { return GetOrRegisterGauge("gauge", DefaultRegistry) }}, + {name: "gaugefloat64", ctor: func() any { return GetOrRegisterGaugeFloat64("gaugefloat64", DefaultRegistry) }}, + {name: "histogram", ctor: func() any { return GetOrRegisterHistogram("histogram", DefaultRegistry, sample()) }}, + {name: "meter", ctor: func() any { return GetOrRegisterMeter("meter", DefaultRegistry) }}, + {name: "timer", ctor: func() any { return GetOrRegisterTimer("timer", DefaultRegistry) }}, + {name: "gaugeinfo", ctor: func() any { return GetOrRegisterGaugeInfo("gaugeinfo", DefaultRegistry) }}, + {name: "resettingtimer", ctor: func() any { return GetOrRegisterResettingTimer("resettingtimer", DefaultRegistry) }}, + {name: "runtimehistogramlazy", ctor: func() any { return GetOrRegisterHistogramLazy("runtimehistogramlazy", DefaultRegistry, sample) }}, + } + for _, test := range tests { + b.Run(test.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + test.ctor() + } + }) + } +} + func BenchmarkRegistryGetOrRegisterParallel_8(b *testing.B) { benchmarkRegistryGetOrRegisterParallel(b, 8) } @@ -268,7 +293,7 @@ func TestPrefixedChildRegistryGet(t *testing.T) { } func TestChildPrefixedRegistryRegister(t *testing.T) { - r := NewPrefixedChildRegistry(DefaultRegistry, "prefix.") + r := NewPrefixedChildRegistry(NewRegistry(), "prefix.") err := r.Register("foo", NewCounter()) c := NewCounter() Register("bar", c) diff --git a/metrics/resetting_timer.go b/metrics/resetting_timer.go index 8aa7dc1488..a3f46e52e0 100644 --- a/metrics/resetting_timer.go +++ b/metrics/resetting_timer.go @@ -8,7 +8,10 @@ import ( // GetOrRegisterResettingTimer returns an existing ResettingTimer or constructs and registers a // new ResettingTimer. func GetOrRegisterResettingTimer(name string, r Registry) *ResettingTimer { - return getOrRegister(name, NewResettingTimer, r) + if r == nil { + r = DefaultRegistry + } + return r.GetOrRegister(name, func() any { return NewResettingTimer() }).(*ResettingTimer) } // NewRegisteredResettingTimer constructs and registers a new ResettingTimer. diff --git a/metrics/runtimehistogram.go b/metrics/runtimehistogram.go index efbed498af..e975a570a4 100644 --- a/metrics/runtimehistogram.go +++ b/metrics/runtimehistogram.go @@ -8,8 +8,10 @@ import ( ) func getOrRegisterRuntimeHistogram(name string, scale float64, r Registry) *runtimeHistogram { - constructor := func() Histogram { return newRuntimeHistogram(scale) } - return getOrRegister(name, constructor, r).(*runtimeHistogram) + if r == nil { + r = DefaultRegistry + } + return r.GetOrRegister(name, func() any { return newRuntimeHistogram(scale) }).(*runtimeHistogram) } // runtimeHistogram wraps a runtime/metrics histogram. diff --git a/metrics/timer.go b/metrics/timer.go index 894bdfc327..8082b31947 100644 --- a/metrics/timer.go +++ b/metrics/timer.go @@ -10,7 +10,10 @@ import ( // Be sure to unregister the meter from the registry once it is of no use to // allow for garbage collection. func GetOrRegisterTimer(name string, r Registry) *Timer { - return getOrRegister(name, NewTimer, r) + if r == nil { + r = DefaultRegistry + } + return r.GetOrRegister(name, func() any { return NewTimer() }).(*Timer) } // NewCustomTimer constructs a new Timer from a Histogram and a Meter.