Skip to content

Commit 51a8d31

Browse files
feat: using metrics registry to get all metrics (#121)
* feat: using metrics registry to get all metrics
1 parent 899c18a commit 51a8d31

File tree

11 files changed

+371
-122
lines changed

11 files changed

+371
-122
lines changed

cmd/main.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/spf13/viper"
3131

3232
"github.com/optimizely/agent/config"
33+
"github.com/optimizely/agent/pkg/metrics"
3334
"github.com/optimizely/agent/pkg/optimizely"
3435
"github.com/optimizely/agent/pkg/routers"
3536
"github.com/optimizely/agent/pkg/server"
@@ -98,9 +99,12 @@ func main() {
9899
conf := loadConfig(v)
99100
initLogging(conf.Log)
100101

102+
agentMetricsRegistry := metrics.NewRegistry()
103+
sdkMetricsRegistry := optimizely.NewRegistry(agentMetricsRegistry)
104+
101105
ctx, cancel := context.WithCancel(context.Background()) // Create default service context
102106
sg := server.NewGroup(ctx, conf.Server) // Create a new server group to manage the individual http listeners
103-
optlyCache := optimizely.NewCache(ctx, conf.Optly)
107+
optlyCache := optimizely.NewCache(ctx, conf.Optly, sdkMetricsRegistry)
104108

105109
// goroutine to check for signals to gracefully shutdown listeners
106110
go func() {
@@ -114,7 +118,7 @@ func main() {
114118
}()
115119

116120
log.Info().Str("version", conf.Admin.Version).Msg("Starting services.")
117-
sg.GoListenAndServe("api", conf.API.Port, routers.NewDefaultAPIRouter(optlyCache, conf.API))
121+
sg.GoListenAndServe("api", conf.API.Port, routers.NewDefaultAPIRouter(optlyCache, conf.API, agentMetricsRegistry))
118122
sg.GoListenAndServe("webhook", conf.Webhook.Port, routers.NewWebhookRouter(optlyCache, conf.Webhook))
119123
sg.GoListenAndServe("admin", conf.Admin.Port, routers.NewAdminRouter(conf.Admin)) // Admin should be added last.
120124

pkg/metrics/metrics.go

Lines changed: 90 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,44 +20,83 @@ package metrics
2020
import (
2121
"sync"
2222

23-
"github.com/optimizely/go-sdk/pkg/metrics"
23+
go_kit_metrics "github.com/go-kit/kit/metrics"
2424

2525
go_kit_expvar "github.com/go-kit/kit/metrics/expvar"
2626
"github.com/rs/zerolog/log"
2727
)
2828

29+
// CounterPrefix stores the prefix for Counter
2930
const (
30-
counterPrefix = "counter"
31-
gaugePrefix = "gauge"
31+
CounterPrefix = "counter"
32+
GaugePrefix = "gauge"
33+
TimerPrefix = "timer"
3234
)
3335

36+
// Timer is the collection of some timers
37+
type Timer struct {
38+
hits go_kit_metrics.Counter
39+
totalTime go_kit_metrics.Counter
40+
histogram go_kit_metrics.Histogram
41+
}
42+
43+
// NewTimer constructs Timer
44+
func (m *Registry) NewTimer(key string) *Timer {
45+
if key == "" {
46+
log.Warn().Msg("metrics timer key is empty")
47+
return nil
48+
}
49+
combinedKey := TimerPrefix + "." + key
50+
51+
m.timerLock.Lock()
52+
defer m.timerLock.Unlock()
53+
if val, ok := m.metricsTimerVars[combinedKey]; ok {
54+
return val
55+
}
56+
57+
return m.createTimer(combinedKey)
58+
}
59+
60+
// Update timer components
61+
func (t *Timer) Update(delta float64) {
62+
t.hits.Add(1)
63+
t.totalTime.Add(delta)
64+
t.histogram.Observe(delta)
65+
}
66+
3467
// Registry initializes expvar metrics registry
3568
type Registry struct {
36-
metricsCounterVars map[string]*go_kit_expvar.Counter
37-
metricsGaugeVars map[string]*go_kit_expvar.Gauge
38-
39-
gaugeLock sync.RWMutex
40-
counterLock sync.RWMutex
69+
metricsCounterVars map[string]go_kit_metrics.Counter
70+
metricsGaugeVars map[string]go_kit_metrics.Gauge
71+
metricsHistogramVars map[string]go_kit_metrics.Histogram
72+
metricsTimerVars map[string]*Timer
73+
74+
gaugeLock sync.RWMutex
75+
counterLock sync.RWMutex
76+
histogramLock sync.RWMutex
77+
timerLock sync.RWMutex
4178
}
4279

4380
// NewRegistry initializes metrics registry
4481
func NewRegistry() *Registry {
4582

4683
return &Registry{
47-
metricsCounterVars: map[string]*go_kit_expvar.Counter{},
48-
metricsGaugeVars: map[string]*go_kit_expvar.Gauge{},
84+
metricsCounterVars: map[string]go_kit_metrics.Counter{},
85+
metricsGaugeVars: map[string]go_kit_metrics.Gauge{},
86+
metricsHistogramVars: map[string]go_kit_metrics.Histogram{},
87+
metricsTimerVars: map[string]*Timer{},
4988
}
5089
}
5190

5291
// GetCounter gets go-kit expvar Counter
53-
func (m *Registry) GetCounter(key string) metrics.Counter {
92+
func (m *Registry) GetCounter(key string) go_kit_metrics.Counter {
5493

5594
if key == "" {
5695
log.Warn().Msg("metrics counter key is empty")
57-
return &metrics.NoopCounter{}
96+
return nil
5897
}
5998

60-
combinedKey := counterPrefix + "." + key
99+
combinedKey := CounterPrefix + "." + key
61100

62101
m.counterLock.Lock()
63102
defer m.counterLock.Unlock()
@@ -69,14 +108,14 @@ func (m *Registry) GetCounter(key string) metrics.Counter {
69108
}
70109

71110
// GetGauge gets go-kit expvar Gauge
72-
func (m *Registry) GetGauge(key string) metrics.Gauge {
111+
func (m *Registry) GetGauge(key string) go_kit_metrics.Gauge {
73112

74113
if key == "" {
75114
log.Warn().Msg("metrics gauge key is empty")
76-
return &metrics.NoopGauge{}
115+
return nil
77116
}
78117

79-
combinedKey := gaugePrefix + "." + key
118+
combinedKey := GaugePrefix + "." + key
80119

81120
m.gaugeLock.Lock()
82121
defer m.gaugeLock.Unlock()
@@ -86,6 +125,22 @@ func (m *Registry) GetGauge(key string) metrics.Gauge {
86125
return m.createGauge(combinedKey)
87126
}
88127

128+
// GetHistogram gets go-kit Histogram
129+
func (m *Registry) GetHistogram(key string) go_kit_metrics.Histogram {
130+
131+
if key == "" {
132+
log.Warn().Msg("metrics gauge key is empty")
133+
return nil
134+
}
135+
136+
m.histogramLock.Lock()
137+
defer m.histogramLock.Unlock()
138+
if val, ok := m.metricsHistogramVars[key]; ok {
139+
return val
140+
}
141+
return m.createHistogram(key)
142+
}
143+
89144
func (m *Registry) createGauge(key string) *go_kit_expvar.Gauge {
90145
gaugeVar := go_kit_expvar.NewGauge(key)
91146
m.metricsGaugeVars[key] = gaugeVar
@@ -99,3 +154,22 @@ func (m *Registry) createCounter(key string) *go_kit_expvar.Counter {
99154
return counterVar
100155

101156
}
157+
158+
func (m *Registry) createHistogram(key string) *go_kit_expvar.Histogram {
159+
histogramVar := go_kit_expvar.NewHistogram(key, 50)
160+
m.metricsHistogramVars[key] = histogramVar
161+
return histogramVar
162+
163+
}
164+
165+
func (m *Registry) createTimer(key string) *Timer {
166+
timerVar := &Timer{
167+
hits: m.createCounter(key + ".hits"),
168+
totalTime: m.createCounter(key + ".responseTime"),
169+
histogram: m.createHistogram(key + ".responseTimeHist"),
170+
}
171+
172+
m.metricsTimerVars[key] = timerVar
173+
return timerVar
174+
175+
}

pkg/metrics/metrics_test.go

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,11 @@ import (
2323
"net/http/httptest"
2424
"testing"
2525

26-
"github.com/optimizely/go-sdk/pkg/metrics"
27-
2826
"github.com/stretchr/testify/assert"
2927
)
3028

3129
type JSON map[string]interface{}
3230

33-
var metricPrefix = "prefix"
34-
3531
func TestCounterValid(t *testing.T) {
3632

3733
rec := httptest.NewRecorder()
@@ -77,7 +73,7 @@ func TestCounterEmptyKey(t *testing.T) {
7773
metricsRegistry := NewRegistry()
7874
counter := metricsRegistry.GetCounter("")
7975

80-
assert.Equal(t, &metrics.NoopCounter{}, counter)
76+
assert.Nil(t, counter)
8177

8278
}
8379

@@ -126,6 +122,100 @@ func TestGaugeEmptyKey(t *testing.T) {
126122
metricsRegistry := NewRegistry()
127123
gauge := metricsRegistry.GetGauge("")
128124

129-
assert.Equal(t, &metrics.NoopGauge{}, gauge)
125+
assert.Nil(t, gauge)
126+
127+
}
128+
129+
func TestHistorgramValid(t *testing.T) {
130+
131+
rec := httptest.NewRecorder()
132+
req := httptest.NewRequest("GET", "/", nil)
133+
134+
metricsRegistry := NewRegistry()
135+
histogram := metricsRegistry.GetHistogram("metrics")
136+
histogram.Observe(12)
137+
histogram.Observe(23)
138+
139+
expvar.Handler().ServeHTTP(rec, req)
140+
141+
var expVarMap JSON
142+
err := json.Unmarshal(rec.Body.Bytes(), &expVarMap)
143+
assert.Nil(t, err)
144+
assert.Equal(t, 12.0, expVarMap["metrics.p50"])
145+
assert.Equal(t, 23.0, expVarMap["metrics.p99"])
146+
147+
}
148+
149+
func TestHistogramMultipleRetrievals(t *testing.T) {
150+
151+
rec := httptest.NewRecorder()
152+
req := httptest.NewRequest("GET", "/", nil)
153+
154+
metricsRegistry := NewRegistry()
155+
histogramKey := "next_histogram_metrics"
156+
histogram := metricsRegistry.GetHistogram(histogramKey)
157+
histogram.Observe(12)
158+
nextGauge := metricsRegistry.GetHistogram(histogramKey)
159+
nextGauge.Observe(23)
160+
161+
expvar.Handler().ServeHTTP(rec, req)
162+
163+
var expVarMap JSON
164+
err := json.Unmarshal(rec.Body.Bytes(), &expVarMap)
165+
assert.Nil(t, err)
166+
assert.Equal(t, 12.0, expVarMap["next_histogram_metrics.p50"])
167+
assert.Equal(t, 23.0, expVarMap["next_histogram_metrics.p99"])
168+
169+
}
170+
171+
func TestHistogramEmptyKey(t *testing.T) {
172+
173+
metricsRegistry := NewRegistry()
174+
histogram := metricsRegistry.GetHistogram("")
175+
176+
assert.Nil(t, histogram)
177+
178+
}
179+
func TestTimerValid(t *testing.T) {
180+
181+
rec := httptest.NewRecorder()
182+
req := httptest.NewRequest("GET", "/", nil)
183+
184+
metricsRegistry := NewRegistry()
185+
timer := metricsRegistry.NewTimer("metrics")
186+
timer.Update(12)
187+
timer.Update(23)
188+
189+
expvar.Handler().ServeHTTP(rec, req)
190+
191+
var expVarMap JSON
192+
err := json.Unmarshal(rec.Body.Bytes(), &expVarMap)
193+
assert.Nil(t, err)
194+
assert.Equal(t, 2.0, expVarMap["timer.metrics.hits"])
195+
assert.Equal(t, 35.0, expVarMap["timer.metrics.responseTime"])
196+
assert.Equal(t, 12.0, expVarMap["timer.metrics.responseTimeHist.p50"])
197+
assert.Equal(t, 23.0, expVarMap["timer.metrics.responseTimeHist.p99"])
198+
}
199+
200+
func TestTimerMultipleRetrievals(t *testing.T) {
201+
202+
rec := httptest.NewRecorder()
203+
req := httptest.NewRequest("GET", "/", nil)
204+
205+
metricsRegistry := NewRegistry()
206+
timerKey := "next_timer_metrics"
207+
timer := metricsRegistry.NewTimer(timerKey)
208+
timer.Update(12)
209+
nextTimer := metricsRegistry.NewTimer(timerKey)
210+
nextTimer.Update(23)
211+
212+
expvar.Handler().ServeHTTP(rec, req)
213+
214+
var expVarMap JSON
215+
err := json.Unmarshal(rec.Body.Bytes(), &expVarMap)
216+
assert.Nil(t, err)
217+
assert.Equal(t, 2.0, expVarMap["timer.next_timer_metrics.hits"])
218+
assert.Equal(t, 12.0, expVarMap["timer.next_timer_metrics.responseTimeHist.p50"])
219+
assert.Equal(t, 23.0, expVarMap["timer.next_timer_metrics.responseTimeHist.p99"])
130220

131221
}

pkg/middleware/metrics.go

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,53 +22,28 @@ import (
2222
"net/http"
2323
"time"
2424

25-
"github.com/go-kit/kit/metrics"
26-
"github.com/go-kit/kit/metrics/expvar"
25+
"github.com/optimizely/agent/pkg/metrics"
2726
)
2827

29-
const metricPrefix = "timers."
30-
3128
type contextString string
3229

3330
const responseTime = contextString("responseTime")
3431

35-
// Metrics struct contains url hit counts, response time and its histogram
36-
type Metrics struct {
37-
HitCounts metrics.Counter
38-
ResponseTime metrics.Counter
39-
ResponseTimeHistogram metrics.Histogram
40-
}
41-
42-
// NewMetrics initialized metrics
43-
func NewMetrics(key string) *Metrics {
44-
45-
uniqueName := metricPrefix + key
46-
47-
return &Metrics{
48-
HitCounts: expvar.NewCounter(uniqueName + ".counts"),
49-
ResponseTime: expvar.NewCounter(uniqueName + ".responseTime"),
50-
ResponseTimeHistogram: expvar.NewHistogram(uniqueName+".responseTimeHist", 50),
51-
}
52-
}
53-
5432
// Metricize updates counts, total response time, and response time histogram
5533
// for each URL hit, key being a combination of a method and route pattern
56-
func Metricize(key string) func(http.Handler) http.Handler {
57-
singleMetric := NewMetrics(key)
34+
func Metricize(key string, metricsRegistry *metrics.Registry) func(http.Handler) http.Handler {
35+
singleMetric := metricsRegistry.NewTimer(key)
5836

5937
f := func(h http.Handler) http.Handler {
6038

6139
fn := func(w http.ResponseWriter, r *http.Request) {
62-
63-
singleMetric.HitCounts.Add(1)
6440
ctx := r.Context()
6541
startTime, ok := ctx.Value(responseTime).(time.Time)
6642
if ok {
6743
defer func() {
6844
endTime := time.Now()
6945
timeDiff := endTime.Sub(startTime).Seconds() * 1000.0 // display time in milliseconds
70-
singleMetric.ResponseTime.Add(timeDiff)
71-
singleMetric.ResponseTimeHistogram.Observe(timeDiff)
46+
singleMetric.Update(timeDiff)
7247
}()
7348
}
7449

0 commit comments

Comments
 (0)