-
Notifications
You must be signed in to change notification settings - Fork 4.5k
/
Copy pathmetricsregistry_test.go
311 lines (292 loc) · 11.5 KB
/
metricsregistry_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
/*
* Copyright 2024 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package opentelemetry
import (
"context"
"testing"
"time"
estats "google.golang.org/grpc/experimental/stats"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/grpctest"
"go.opentelemetry.io/otel/attribute"
otelmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
)
var defaultTestTimeout = 5 * time.Second
type s struct {
grpctest.Tester
}
func Test(t *testing.T) {
grpctest.RunSubTests(t, s{})
}
type metricsRecorderForTest interface {
estats.MetricsRecorder
initializeMetrics()
}
func newClientStatsHandler(options MetricsOptions) metricsRecorderForTest {
return &clientMetricsHandler{options: Options{MetricsOptions: options}}
}
func newServerStatsHandler(options MetricsOptions) metricsRecorderForTest {
return &serverMetricsHandler{options: Options{MetricsOptions: options}}
}
// TestMetricsRegistryMetrics tests the OpenTelemetry behavior with respect to
// registered metrics. It registers metrics in the metrics registry. It then
// creates an OpenTelemetry client and server stats handler This test then makes
// measurements on those instruments using one of the stats handlers, then tests
// the expected metrics emissions, which includes default metrics and optional
// label assertions.
func (s) TestMetricsRegistryMetrics(t *testing.T) {
cleanup := internal.SnapshotMetricRegistryForTesting()
defer cleanup()
intCountHandle1 := estats.RegisterInt64Count(estats.MetricDescriptor{
Name: "int-counter-1",
Description: "Sum of calls from test",
Unit: "int",
Labels: []string{"int counter 1 label key"},
OptionalLabels: []string{"int counter 1 optional label key"},
Default: true,
})
// A non default metric. If not specified in OpenTelemetry constructor, this
// will become a no-op, so measurements recorded on it won't show up in
// emitted metrics.
intCountHandle2 := estats.RegisterInt64Count(estats.MetricDescriptor{
Name: "int-counter-2",
Description: "Sum of calls from test",
Unit: "int",
Labels: []string{"int counter 2 label key"},
OptionalLabels: []string{"int counter 2 optional label key"},
Default: false,
})
// Register another non default metric. This will get added to the default
// metrics set in the OpenTelemetry constructor options, so metrics recorded
// on this should show up in metrics emissions.
intCountHandle3 := estats.RegisterInt64Count(estats.MetricDescriptor{
Name: "int-counter-3",
Description: "sum of calls from test",
Unit: "int",
Labels: []string{"int counter 3 label key"},
OptionalLabels: []string{"int counter 3 optional label key"},
Default: false,
})
floatCountHandle := estats.RegisterFloat64Count(estats.MetricDescriptor{
Name: "float-counter",
Description: "sum of calls from test",
Unit: "float",
Labels: []string{"float counter label key"},
OptionalLabels: []string{"float counter optional label key"},
Default: true,
})
bounds := []float64{0, 5, 10}
intHistoHandle := estats.RegisterInt64Histo(estats.MetricDescriptor{
Name: "int-histo",
Description: "histogram of call values from tests",
Unit: "int",
Labels: []string{"int histo label key"},
OptionalLabels: []string{"int histo optional label key"},
Default: true,
Bounds: bounds,
})
floatHistoHandle := estats.RegisterFloat64Histo(estats.MetricDescriptor{
Name: "float-histo",
Description: "histogram of call values from tests",
Unit: "float",
Labels: []string{"float histo label key"},
OptionalLabels: []string{"float histo optional label key"},
Default: true,
Bounds: bounds,
})
intGaugeHandle := estats.RegisterInt64Gauge(estats.MetricDescriptor{
Name: "simple-gauge",
Description: "the most recent int emitted by test",
Unit: "int",
Labels: []string{"int gauge label key"},
OptionalLabels: []string{"int gauge optional label key"},
Default: true,
})
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
// Only float optional labels are configured, so only float optional labels should show up.
// All required labels should show up.
wantMetrics := []metricdata.Metrics{
{
Name: "int-counter-1",
Description: "Sum of calls from test",
Unit: "int",
Data: metricdata.Sum[int64]{
DataPoints: []metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("int counter 1 label key", "int counter 1 label value")), // No optional label, not float.
Value: 1,
},
},
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
},
},
{
Name: "int-counter-3",
Description: "sum of calls from test",
Unit: "int",
Data: metricdata.Sum[int64]{
DataPoints: []metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("int counter 3 label key", "int counter 3 label value")), // No optional label, not float.
Value: 4,
},
},
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
},
},
{
Name: "float-counter",
Description: "sum of calls from test",
Unit: "float",
Data: metricdata.Sum[float64]{
DataPoints: []metricdata.DataPoint[float64]{
{
Attributes: attribute.NewSet(attribute.String("float counter label key", "float counter label value"), attribute.String("float counter optional label key", "float counter optional label value")),
Value: 1.2,
},
},
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
},
},
{
Name: "int-histo",
Description: "histogram of call values from tests",
Unit: "int",
Data: metricdata.Histogram[int64]{
DataPoints: []metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("int histo label key", "int histo label value")), // No optional label, not float.
Count: 1,
Bounds: bounds,
BucketCounts: []uint64{0, 1, 0, 0},
Min: metricdata.NewExtrema(int64(3)),
Max: metricdata.NewExtrema(int64(3)),
Sum: 3,
},
},
Temporality: metricdata.CumulativeTemporality,
},
},
{
Name: "float-histo",
Description: "histogram of call values from tests",
Unit: "float",
Data: metricdata.Histogram[float64]{
DataPoints: []metricdata.HistogramDataPoint[float64]{
{
Attributes: attribute.NewSet(attribute.String("float histo label key", "float histo label value"), attribute.String("float histo optional label key", "float histo optional label value")),
Count: 1,
Bounds: bounds,
BucketCounts: []uint64{0, 1, 0, 0},
Min: metricdata.NewExtrema(float64(4.3)),
Max: metricdata.NewExtrema(float64(4.3)),
Sum: 4.3,
},
},
Temporality: metricdata.CumulativeTemporality,
},
},
{
Name: "simple-gauge",
Description: "the most recent int emitted by test",
Unit: "int",
Data: metricdata.Gauge[int64]{
DataPoints: []metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("int gauge label key", "int gauge label value")), // No optional label, not float.
Value: 8,
},
},
},
},
}
for _, test := range []struct {
name string
constructor func(options MetricsOptions) metricsRecorderForTest
}{
{
name: "client stats handler",
constructor: newClientStatsHandler,
},
{
name: "server stats handler",
constructor: newServerStatsHandler,
},
} {
t.Run(test.name, func(t *testing.T) {
reader := otelmetric.NewManualReader()
provider := otelmetric.NewMeterProvider(otelmetric.WithReader(reader))
// This configures the defaults alongside int counter 3. All the instruments
// registered except int counter 2 and 3 are default, so all measurements
// recorded should show up in reader collected metrics except those for int
// counter 2.
// This also only toggles the float count and float histo optional labels,
// so only those should show up in metrics emissions. All the required
// labels should show up in metrics emissions.
mo := MetricsOptions{
Metrics: DefaultMetrics().Add("int-counter-3"),
OptionalLabels: []string{"float counter optional label key", "float histo optional label key"},
MeterProvider: provider,
}
mr := test.constructor(mo)
mr.initializeMetrics()
// These Record calls are guaranteed at a layer underneath OpenTelemetry for
// labels emitted to match the length of labels + optional labels.
intCountHandle1.Record(mr, 1, []string{"int counter 1 label value", "int counter 1 optional label value"}...)
// int-counter-2 is not part of metrics specified (not default), so this
// record call shouldn't show up in the reader.
intCountHandle2.Record(mr, 2, []string{"int counter 2 label value", "int counter 2 optional label value"}...)
// int-counter-3 is part of metrics specified, so this call should show up
// in the reader.
intCountHandle3.Record(mr, 4, []string{"int counter 3 label value", "int counter 3 optional label value"}...)
// All future recording points should show up in emissions as all of these are defaults.
floatCountHandle.Record(mr, 1.2, []string{"float counter label value", "float counter optional label value"}...)
intHistoHandle.Record(mr, 3, []string{"int histo label value", "int histo optional label value"}...)
floatHistoHandle.Record(mr, 4.3, []string{"float histo label value", "float histo optional label value"}...)
intGaugeHandle.Record(mr, 7, []string{"int gauge label value", "int gauge optional label value"}...)
// This second gauge call should take the place of the previous gauge call.
intGaugeHandle.Record(mr, 8, []string{"int gauge label value", "int gauge optional label value"}...)
rm := &metricdata.ResourceMetrics{}
reader.Collect(ctx, rm)
gotMetrics := map[string]metricdata.Metrics{}
for _, sm := range rm.ScopeMetrics {
for _, m := range sm.Metrics {
gotMetrics[m.Name] = m
}
}
for _, metric := range wantMetrics {
val, ok := gotMetrics[metric.Name]
if !ok {
t.Fatalf("Metric %v not present in recorded metrics", metric.Name)
}
if !metricdatatest.AssertEqual(t, metric, val, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars()) {
t.Fatalf("Metrics data type not equal for metric: %v", metric.Name)
}
}
// int-counter-2 is not a default metric and wasn't specified in
// constructor, so emissions should not show up.
if _, ok := gotMetrics["int-counter-2"]; ok {
t.Fatalf("Metric int-counter-2 present in recorded metrics, was not configured")
}
})
}
}