Skip to content

Commit 0a7d96b

Browse files
committed
Add unit tests for histogram metrics instrumentation
1 parent 1586249 commit 0a7d96b

File tree

4 files changed

+200
-34
lines changed

4 files changed

+200
-34
lines changed

Gopkg.lock

Lines changed: 14 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

driver/sql/metrics_nop.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ var NopMetrics Metrics = &nopMetrics{}
66
type nopMetrics struct{}
77

88
func (nm *nopMetrics) ReceivedNotification(isNotification bool) {}
9-
func (nm *nopMetrics) QueueNotification(notification *ProjectionNotification) {
10-
}
11-
func (nm *nopMetrics) StartNotificationProcessing(notification *ProjectionNotification) {
12-
13-
}
14-
func (nm *nopMetrics) FinishNotificationProcessing(notification *ProjectionNotification, success bool) {
15-
}
9+
func (nm *nopMetrics) QueueNotification(notification *ProjectionNotification) {}
10+
func (nm *nopMetrics) StartNotificationProcessing(notification *ProjectionNotification) {}
11+
func (nm *nopMetrics) FinishNotificationProcessing(notification *ProjectionNotification, success bool) {}

extension/prometheus/prometheus.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ const (
2020
// Metrics is an object for exposing prometheus metrics
2121
type Metrics struct {
2222
notificationCounter *prometheus.CounterVec
23-
notificationQueueDuration *prometheus.HistogramVec
24-
notificationProcessingDuration *prometheus.HistogramVec
23+
notificationQueueDuration prometheus.ObserverVec
24+
notificationProcessingDuration prometheus.ObserverVec
2525
notificationStartTimes sync.Map
2626
logger goengine.Logger
2727
}
@@ -59,9 +59,11 @@ func NewMetrics() *Metrics {
5959
},
6060
[]string{"success"},
6161
),
62+
logger: goengine.NopLogger,
6263
}
6364
}
6465

66+
// SetLogger sets the logger for metrics
6567
func (m *Metrics) SetLogger(logger goengine.Logger) {
6668
m.logger = logger
6769
}
@@ -128,7 +130,6 @@ func (m *Metrics) FinishNotificationProcessing(notification *sql.ProjectionNotif
128130
e.Any("notification", notification)
129131
})
130132
}
131-
132133
}
133134

134135
// storeStartTime stores the start time against each notification only if it's not already existent
Lines changed: 179 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,200 @@
11
// +build unit
22

3-
package prometheus_test
3+
package prometheus
44

55
import (
6+
"fmt"
7+
"strings"
68
"testing"
9+
"time"
710

8-
"github.com/hellofresh/goengine/extension/prometheus"
9-
"github.com/stretchr/testify/assert"
10-
11+
"github.com/hellofresh/goengine"
1112
"github.com/hellofresh/goengine/driver/sql"
13+
"github.com/hellofresh/goengine/extension/logrus"
14+
"github.com/prometheus/client_golang/prometheus"
15+
"github.com/prometheus/client_golang/prometheus/testutil"
16+
"github.com/stretchr/testify/assert"
1217
)
1318

14-
func TestPrometheusMetrics(t *testing.T) {
15-
metrics := prometheus.NewMetrics()
19+
var testSQLProjection = sql.ProjectionNotification{
20+
No: 1,
21+
AggregateID: "C56A4180-65AA-42EC-A945-5FD21DEC0538",
22+
}
23+
24+
// MockObserver is the mock object for Observer
25+
type MockObserver struct {
26+
observation float64
27+
}
28+
29+
func (o *MockObserver) Observe(value float64) {
30+
o.observation = value
31+
}
32+
33+
// MockedMetricObject is the mock object for ObserverVec
34+
type MockedMetricObject struct {
35+
observer *MockObserver
36+
}
37+
38+
func (m *MockedMetricObject) GetMetricWith(labels prometheus.Labels) (prometheus.Observer, error) {
39+
return &MockObserver{}, nil
40+
}
41+
42+
func (m *MockedMetricObject) GetMetricWithLabelValues(lvs ...string) (prometheus.Observer, error) {
43+
return &MockObserver{}, nil
44+
}
1645

17-
testSQLProjection := sql.ProjectionNotification{
18-
No: 1,
19-
AggregateID: "C56A4180-65AA-42EC-A945-5FD21DEC0538",
46+
func (m *MockedMetricObject) With(labels prometheus.Labels) prometheus.Observer {
47+
m.observer = &MockObserver{
48+
observation: 0.0,
2049
}
50+
return m.observer
51+
}
2152

22-
ok := metrics.FinishNotificationProcessing(&testSQLProjection, true)
23-
assert.False(t, ok)
53+
func (m *MockedMetricObject) WithLabelValues(lvs ...string) prometheus.Observer {
54+
return &MockObserver{}
55+
}
2456

25-
ok = metrics.QueueNotification(&testSQLProjection)
26-
assert.True(t, ok)
57+
func (m *MockedMetricObject) CurryWith(labels prometheus.Labels) (prometheus.ObserverVec, error) {
58+
return m, nil
59+
}
2760

28-
ok = metrics.QueueNotification(&testSQLProjection)
29-
assert.False(t, ok)
61+
func (m *MockedMetricObject) MustCurryWith(labels prometheus.Labels) prometheus.ObserverVec {
62+
return m
63+
}
3064

31-
ok = metrics.FinishNotificationProcessing(&testSQLProjection, true)
32-
assert.False(t, ok)
65+
func (m *MockedMetricObject) Describe(ch chan<- *prometheus.Desc) {}
3366

34-
ok = metrics.StartNotificationProcessing(&testSQLProjection)
35-
assert.True(t, ok)
67+
func (m *MockedMetricObject) Collect(ch chan<- prometheus.Metric) {}
68+
69+
func TestMetrics_NotificationStartTime(t *testing.T) {
70+
metrics := NewMetrics()
3671

37-
ok = metrics.StartNotificationProcessing(&testSQLProjection)
38-
assert.False(t, ok)
72+
metrics.QueueNotification(&testSQLProjection)
73+
metrics.StartNotificationProcessing(&testSQLProjection)
3974

40-
ok = metrics.FinishNotificationProcessing(&testSQLProjection, true)
75+
memAddress := fmt.Sprintf("%p", &testSQLProjection)
76+
queueStartTime, ok := metrics.notificationStartTimes.Load("q" + memAddress)
4177
assert.True(t, ok)
78+
assert.IsType(t, time.Time{}, queueStartTime)
79+
80+
processingStartTime, _ := metrics.notificationStartTimes.Load("p" + memAddress)
81+
assert.True(t, ok)
82+
assert.IsType(t, time.Time{}, processingStartTime)
83+
84+
}
85+
86+
func TestMetrics_FinishNotificationProcessingSuccess(t *testing.T) {
87+
88+
mockQueueMetric := new(MockedMetricObject)
89+
mockProcessMetric := new(MockedMetricObject)
90+
testMetrics := newMetricsWith(mockQueueMetric, mockProcessMetric)
4291

92+
testMetrics.QueueNotification(&testSQLProjection)
93+
testMetrics.StartNotificationProcessing(&testSQLProjection)
94+
testMetrics.FinishNotificationProcessing(&testSQLProjection, true)
95+
96+
assert.NotEqual(t, mockQueueMetric.observer.observation, 0.0)
97+
98+
}
99+
100+
func TestMetrics_FinishNotificationProcessingFailureForQueue(t *testing.T) {
101+
102+
mockQueueMetric := new(MockedMetricObject)
103+
mockProcessMetric := new(MockedMetricObject)
104+
testMetrics := newMetricsWith(mockQueueMetric, mockProcessMetric)
105+
106+
testMetrics.StartNotificationProcessing(&testSQLProjection)
107+
testMetrics.FinishNotificationProcessing(&testSQLProjection, true)
108+
109+
assert.Nil(t, mockQueueMetric.observer)
110+
111+
}
112+
113+
func TestMetrics_FinishNotificationProcessingFailureForProcessing(t *testing.T) {
114+
115+
mockQueueMetric := new(MockedMetricObject)
116+
mockProcessMetric := new(MockedMetricObject)
117+
testMetrics := newMetricsWith(mockQueueMetric, mockProcessMetric)
118+
119+
testMetrics.QueueNotification(&testSQLProjection)
120+
testMetrics.FinishNotificationProcessing(&testSQLProjection, true)
121+
122+
assert.Nil(t, mockProcessMetric.observer)
123+
124+
}
125+
126+
func TestMetrics_CollectAndCompareHistogramMetrics(t *testing.T) {
127+
128+
metrics := NewMetrics()
129+
metrics.SetLogger(logrus.StandardLogger())
130+
131+
inputs := []struct {
132+
name string
133+
collector prometheus.ObserverVec
134+
metadata string
135+
expect string
136+
observation float64
137+
}{
138+
{
139+
name: "Testing Queue Duration Metric Collector",
140+
collector: metrics.notificationQueueDuration,
141+
metadata: `
142+
# HELP goengine_queue_duration_seconds histogram of queue latencies
143+
# TYPE goengine_queue_duration_seconds histogram
144+
`,
145+
expect: `
146+
goengine_queue_duration_seconds_bucket{success="true",le="0.1"} 0
147+
goengine_queue_duration_seconds_bucket{success="true",le="0.5"} 0
148+
goengine_queue_duration_seconds_bucket{success="true",le="0.9"} 0
149+
goengine_queue_duration_seconds_bucket{success="true",le="0.99"} 1.0
150+
goengine_queue_duration_seconds_bucket{success="true",le="+Inf"} 1.0
151+
goengine_queue_duration_seconds_sum{success="true"} 0.99
152+
goengine_queue_duration_seconds_count{success="true"} 1.0
153+
154+
`,
155+
observation: 0.99,
156+
},
157+
{
158+
name: "Testing Notification Processing Duration Metric Collector",
159+
collector: metrics.notificationProcessingDuration,
160+
metadata: `
161+
# HELP goengine_notification_processing_duration_seconds histogram of notifications handled latencies
162+
# TYPE goengine_notification_processing_duration_seconds histogram
163+
`,
164+
expect: `
165+
goengine_notification_processing_duration_seconds_bucket{success="true",le="0.1"} 0
166+
goengine_notification_processing_duration_seconds_bucket{success="true",le="0.5"} 0
167+
goengine_notification_processing_duration_seconds_bucket{success="true",le="0.9"} 1.0
168+
goengine_notification_processing_duration_seconds_bucket{success="true",le="0.99"} 1.0
169+
goengine_notification_processing_duration_seconds_bucket{success="true",le="+Inf"} 1.0
170+
goengine_notification_processing_duration_seconds_sum{success="true"} 0.54
171+
goengine_notification_processing_duration_seconds_count{success="true"} 1.0
172+
173+
`,
174+
observation: 0.54,
175+
},
176+
}
177+
178+
labels := prometheus.Labels{"success": "true"}
179+
for _, input := range inputs {
180+
input.collector.With(labels).Observe(input.observation)
181+
t.Run(input.name, func(t *testing.T) {
182+
if err := testutil.CollectAndCompare(input.collector, strings.NewReader(input.metadata+input.expect)); err != nil {
183+
t.Errorf("unexpected collecting result:\n%s", err)
184+
}
185+
})
186+
187+
}
188+
}
189+
190+
// NewMetrics instantiate and return an object of Metrics
191+
func newMetricsWith(queueDuration prometheus.ObserverVec, processDuration prometheus.ObserverVec) *Metrics {
192+
return &Metrics{
193+
// queueDuration is used to expose 'queue_duration_seconds' metrics
194+
notificationQueueDuration: queueDuration,
195+
196+
// notificationProcessingDuration is used to expose 'notification_handle_duration_seconds' metrics
197+
notificationProcessingDuration: processDuration,
198+
logger: goengine.NopLogger,
199+
}
43200
}

0 commit comments

Comments
 (0)