Skip to content

Commit 920005c

Browse files
committed
Fix a race condition for prometheus metrics
1 parent 31eeeab commit 920005c

1 file changed

Lines changed: 74 additions & 28 deletions

File tree

pkg/smokescreen/metrics/prometheus_metrics.go

Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@ package metrics
22

33
import (
44
"fmt"
5-
"github.com/prometheus/client_golang/prometheus"
6-
"github.com/prometheus/client_golang/prometheus/promauto"
7-
"github.com/prometheus/client_golang/prometheus/promhttp"
85
"net/http"
96
"strings"
7+
"sync"
108
"sync/atomic"
119
"time"
10+
11+
"github.com/prometheus/client_golang/prometheus"
12+
"github.com/prometheus/client_golang/prometheus/promauto"
13+
"github.com/prometheus/client_golang/prometheus/promhttp"
1214
)
1315

1416
// PrometheusMetricsClient attempts to replicate the functionality of the StatsdMetricsClient, but exposing
1517
// the metrics via a http endpoint
1618
type PrometheusMetricsClient struct {
1719
endpoint string
1820
metricsTags map[string]map[string]string
21+
mu sync.RWMutex
1922
started atomic.Value
2023

2124
counters map[string]prometheus.CounterVec
@@ -150,62 +153,105 @@ var _ MetricsClientInterface = &PrometheusMetricsClient{}
150153
func (mc *PrometheusMetricsClient) incrementPrometheusCounter(
151154
metric string,
152155
tags map[string]string) {
153-
if existingCounter, ok := mc.counters[metric]; ok {
154-
existingCounter.With(tags).Inc()
155-
} else {
156-
counter := promauto.NewCounterVec(prometheus.CounterOpts{
156+
mc.mu.RLock()
157+
counter, ok := mc.counters[metric]
158+
mc.mu.RUnlock()
159+
160+
if ok {
161+
counter.With(tags).Inc()
162+
return
163+
}
164+
165+
mc.mu.Lock()
166+
// double check just in case it was created between the RLock and Lock
167+
if counter, ok = mc.counters[metric]; !ok {
168+
counter = *promauto.NewCounterVec(prometheus.CounterOpts{
157169
Name: metric,
158170
}, mapKeys(tags))
159-
counter.With(tags).Inc()
160-
mc.counters[metric] = *counter
171+
mc.counters[metric] = counter
161172
}
173+
mc.mu.Unlock()
174+
175+
counter.With(tags).Inc()
162176
}
163177

164178
func (mc *PrometheusMetricsClient) updatePrometheusGauge(
165179
metric string,
166180
value float64,
167181
tags map[string]string) {
168-
if existingGauge, ok := mc.gauges[metric]; ok {
169-
existingGauge.With(tags).Add(value)
170-
} else {
171-
gauge := promauto.NewGaugeVec(prometheus.GaugeOpts{
182+
mc.mu.RLock()
183+
gauge, ok := mc.gauges[metric]
184+
mc.mu.RUnlock()
185+
186+
if ok {
187+
gauge.With(tags).Add(value)
188+
return
189+
}
190+
191+
mc.mu.Lock()
192+
// double check just in case it was created between the RLock and Lock
193+
if gauge, ok = mc.gauges[metric]; !ok {
194+
gauge = *promauto.NewGaugeVec(prometheus.GaugeOpts{
172195
Name: metric,
173196
}, mapKeys(tags))
174-
gauge.With(tags).Add(value)
175-
mc.gauges[metric] = *gauge
197+
mc.gauges[metric] = gauge
176198
}
199+
mc.mu.Unlock()
200+
201+
gauge.With(tags).Add(value)
177202
}
178203

179204
func (mc *PrometheusMetricsClient) observeValuePrometheusHistogram(
180205
metric string,
181206
value float64,
182207
tags map[string]string) {
183-
if existingHistogram, ok := mc.histograms[metric]; ok {
184-
existingHistogram.With(tags).Observe(value)
185-
} else {
186-
histogram := promauto.NewHistogramVec(prometheus.HistogramOpts{
208+
mc.mu.RLock()
209+
histogram, ok := mc.histograms[metric]
210+
mc.mu.RUnlock()
211+
212+
if ok {
213+
histogram.With(tags).Observe(value)
214+
return
215+
}
216+
217+
mc.mu.Lock()
218+
// double check just in case it was created between the RLock and Lock
219+
if histogram, ok = mc.histograms[metric]; !ok {
220+
histogram = *promauto.NewHistogramVec(prometheus.HistogramOpts{
187221
Name: metric,
188222
}, mapKeys(tags))
189-
histogram.With(tags).Observe(value)
190-
mc.histograms[metric] = *histogram
223+
mc.histograms[metric] = histogram
191224
}
225+
mc.mu.Unlock()
226+
227+
histogram.With(tags).Observe(value)
192228
}
193229

194230
func (mc *PrometheusMetricsClient) observeValuePrometheusTimer(
195231
metric string,
196232
duration time.Duration,
197233
tags map[string]string) {
198234
timerMetric := metric + "_timer"
199-
if existingHistogram, ok := mc.timings[timerMetric]; ok {
200-
existingHistogram.With(tags).Observe(float64(duration.Milliseconds()))
201-
} else {
202-
histogram := promauto.NewHistogramVec(prometheus.HistogramOpts{
203-
Name: timerMetric,
204-
}, mapKeys(tags))
235+
mc.mu.RLock()
236+
histogram, ok := mc.timings[timerMetric]
237+
mc.mu.RUnlock()
205238

239+
if ok {
206240
histogram.With(tags).Observe(float64(duration.Milliseconds()))
207-
mc.timings[timerMetric] = *histogram
241+
return
242+
}
243+
244+
mc.mu.Lock()
245+
// double check just in case it was created between the RLock and Lock
246+
if histogram, ok = mc.timings[timerMetric]; !ok {
247+
histogram = *promauto.NewHistogramVec(prometheus.HistogramOpts{
248+
Name: timerMetric,
249+
}, mapKeys(tags))
250+
mc.timings[timerMetric] = histogram
208251
}
252+
mc.mu.Unlock()
253+
254+
histogram.With(tags).Observe(float64(duration.Milliseconds()))
209255
}
210256

211257
func mapKeys[T comparable, U any](inputMap map[T]U) []T {

0 commit comments

Comments
 (0)