Skip to content

Commit a340721

Browse files
authored
metrics: use sync.map in registry (#27159)
1 parent a143018 commit a340721

File tree

2 files changed

+69
-55
lines changed

2 files changed

+69
-55
lines changed

metrics/registry.go

Lines changed: 44 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,17 @@ type Registry interface {
4545

4646
// Unregister the metric with the given name.
4747
Unregister(string)
48-
49-
// Unregister all metrics. (Mostly for testing.)
50-
UnregisterAll()
5148
}
5249

53-
// The standard implementation of a Registry is a mutex-protected map
50+
// The standard implementation of a Registry uses sync.map
5451
// of names to metrics.
5552
type StandardRegistry struct {
56-
metrics map[string]interface{}
57-
mutex sync.Mutex
53+
metrics sync.Map
5854
}
5955

6056
// Create a new registry.
6157
func NewRegistry() Registry {
62-
return &StandardRegistry{metrics: make(map[string]interface{})}
58+
return &StandardRegistry{}
6359
}
6460

6561
// Call the given function for each registered metric.
@@ -71,45 +67,57 @@ func (r *StandardRegistry) Each(f func(string, interface{})) {
7167

7268
// Get the metric by the given name or nil if none is registered.
7369
func (r *StandardRegistry) Get(name string) interface{} {
74-
r.mutex.Lock()
75-
defer r.mutex.Unlock()
76-
return r.metrics[name]
70+
item, _ := r.metrics.Load(name)
71+
return item
7772
}
7873

7974
// Gets an existing metric or creates and registers a new one. Threadsafe
8075
// alternative to calling Get and Register on failure.
8176
// The interface can be the metric to register if not found in registry,
8277
// or a function returning the metric for lazy instantiation.
8378
func (r *StandardRegistry) GetOrRegister(name string, i interface{}) interface{} {
84-
r.mutex.Lock()
85-
defer r.mutex.Unlock()
86-
if metric, ok := r.metrics[name]; ok {
87-
return metric
79+
// fast path
80+
cached, ok := r.metrics.Load(name)
81+
if ok {
82+
return cached
8883
}
8984
if v := reflect.ValueOf(i); v.Kind() == reflect.Func {
9085
i = v.Call(nil)[0].Interface()
9186
}
92-
r.register(name, i)
93-
return i
87+
item, _, ok := r.loadOrRegister(name, i)
88+
if !ok {
89+
return i
90+
}
91+
return item
9492
}
9593

9694
// Register the given metric under the given name. Returns a DuplicateMetric
9795
// if a metric by the given name is already registered.
9896
func (r *StandardRegistry) Register(name string, i interface{}) error {
99-
r.mutex.Lock()
100-
defer r.mutex.Unlock()
101-
return r.register(name, i)
97+
// fast path
98+
_, ok := r.metrics.Load(name)
99+
if ok {
100+
return DuplicateMetric(name)
101+
}
102+
103+
if v := reflect.ValueOf(i); v.Kind() == reflect.Func {
104+
i = v.Call(nil)[0].Interface()
105+
}
106+
_, loaded, _ := r.loadOrRegister(name, i)
107+
if loaded {
108+
return DuplicateMetric(name)
109+
}
110+
return nil
102111
}
103112

104113
// Run all registered healthchecks.
105114
func (r *StandardRegistry) RunHealthchecks() {
106-
r.mutex.Lock()
107-
defer r.mutex.Unlock()
108-
for _, i := range r.metrics {
109-
if h, ok := i.(Healthcheck); ok {
115+
r.metrics.Range(func(key, value any) bool {
116+
if h, ok := value.(Healthcheck); ok {
110117
h.Check()
111118
}
112-
}
119+
return true
120+
})
113121
}
114122

115123
// GetAll metrics in the Registry
@@ -177,45 +185,31 @@ func (r *StandardRegistry) GetAll() map[string]map[string]interface{} {
177185

178186
// Unregister the metric with the given name.
179187
func (r *StandardRegistry) Unregister(name string) {
180-
r.mutex.Lock()
181-
defer r.mutex.Unlock()
182188
r.stop(name)
183-
delete(r.metrics, name)
184-
}
185-
186-
// Unregister all metrics. (Mostly for testing.)
187-
func (r *StandardRegistry) UnregisterAll() {
188-
r.mutex.Lock()
189-
defer r.mutex.Unlock()
190-
for name := range r.metrics {
191-
r.stop(name)
192-
delete(r.metrics, name)
193-
}
189+
r.metrics.LoadAndDelete(name)
194190
}
195191

196-
func (r *StandardRegistry) register(name string, i interface{}) error {
197-
if _, ok := r.metrics[name]; ok {
198-
return DuplicateMetric(name)
199-
}
192+
func (r *StandardRegistry) loadOrRegister(name string, i interface{}) (interface{}, bool, bool) {
200193
switch i.(type) {
201194
case Counter, CounterFloat64, Gauge, GaugeFloat64, Healthcheck, Histogram, Meter, Timer, ResettingTimer:
202-
r.metrics[name] = i
195+
default:
196+
return nil, false, false
203197
}
204-
return nil
198+
item, loaded := r.metrics.LoadOrStore(name, i)
199+
return item, loaded, true
205200
}
206201

207202
func (r *StandardRegistry) registered() map[string]interface{} {
208-
r.mutex.Lock()
209-
defer r.mutex.Unlock()
210-
metrics := make(map[string]interface{}, len(r.metrics))
211-
for name, i := range r.metrics {
212-
metrics[name] = i
213-
}
203+
metrics := make(map[string]interface{})
204+
r.metrics.Range(func(key, value any) bool {
205+
metrics[key.(string)] = value
206+
return true
207+
})
214208
return metrics
215209
}
216210

217211
func (r *StandardRegistry) stop(name string) {
218-
if i, ok := r.metrics[name]; ok {
212+
if i, ok := r.metrics.Load(name); ok {
219213
if s, ok := i.(Stoppable); ok {
220214
s.Stop()
221215
}
@@ -308,11 +302,6 @@ func (r *PrefixedRegistry) Unregister(name string) {
308302
r.underlying.Unregister(realName)
309303
}
310304

311-
// Unregister all metrics. (Mostly for testing.)
312-
func (r *PrefixedRegistry) UnregisterAll() {
313-
r.underlying.UnregisterAll()
314-
}
315-
316305
var (
317306
DefaultRegistry = NewRegistry()
318307
EphemeralRegistry = NewRegistry()

metrics/registry_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package metrics
22

33
import (
4+
"sync"
45
"testing"
56
)
67

@@ -13,6 +14,30 @@ func BenchmarkRegistry(b *testing.B) {
1314
}
1415
}
1516

17+
func BenchmarkRegistryGetOrRegisterParallel_8(b *testing.B) {
18+
benchmarkRegistryGetOrRegisterParallel(b, 8)
19+
}
20+
21+
func BenchmarkRegistryGetOrRegisterParallel_32(b *testing.B) {
22+
benchmarkRegistryGetOrRegisterParallel(b, 32)
23+
}
24+
25+
func benchmarkRegistryGetOrRegisterParallel(b *testing.B, amount int) {
26+
r := NewRegistry()
27+
b.ResetTimer()
28+
var wg sync.WaitGroup
29+
for i := 0; i < amount; i++ {
30+
wg.Add(1)
31+
go func() {
32+
for i := 0; i < b.N; i++ {
33+
r.GetOrRegister("foo", NewMeter)
34+
}
35+
wg.Done()
36+
}()
37+
}
38+
wg.Wait()
39+
}
40+
1641
func TestRegistry(t *testing.T) {
1742
r := NewRegistry()
1843
r.Register("foo", NewCounter())

0 commit comments

Comments
 (0)