go.temporal.io/server@v1.23.0/common/metrics/otel_metrics_handler_test.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package metrics 26 27 import ( 28 "context" 29 "errors" 30 "testing" 31 "time" 32 33 "github.com/golang/mock/gomock" 34 "github.com/google/go-cmp/cmp" 35 "github.com/google/go-cmp/cmp/cmpopts" 36 "github.com/stretchr/testify/assert" 37 "github.com/stretchr/testify/require" 38 "go.opentelemetry.io/otel/attribute" 39 "go.opentelemetry.io/otel/metric" 40 sdkmetrics "go.opentelemetry.io/otel/sdk/metric" 41 "go.opentelemetry.io/otel/sdk/metric/metricdata" 42 "go.temporal.io/server/common/log/tag" 43 44 "go.temporal.io/server/common/log" 45 ) 46 47 var ( 48 minLatency = float64(1248) 49 maxLatency = float64(5255) 50 testBytes = float64(1234567) 51 ) 52 53 type testProvider struct { 54 meter metric.Meter 55 } 56 57 func (t *testProvider) GetMeter() metric.Meter { 58 return t.meter 59 } 60 61 func (t *testProvider) Stop(log.Logger) {} 62 63 func TestMeter(t *testing.T) { 64 ctx := context.Background() 65 rdr := sdkmetrics.NewManualReader() 66 provider := sdkmetrics.NewMeterProvider( 67 sdkmetrics.WithReader(rdr), 68 sdkmetrics.WithView( 69 sdkmetrics.NewView( 70 sdkmetrics.Instrument{ 71 Kind: sdkmetrics.InstrumentKindHistogram, 72 Unit: "By", 73 }, 74 sdkmetrics.Stream{ 75 Aggregation: sdkmetrics.AggregationExplicitBucketHistogram{ 76 Boundaries: defaultConfig.PerUnitHistogramBoundaries["By"], 77 }, 78 }, 79 ), 80 sdkmetrics.NewView( 81 sdkmetrics.Instrument{ 82 Kind: sdkmetrics.InstrumentKindHistogram, 83 Unit: "1", 84 }, 85 sdkmetrics.Stream{ 86 Aggregation: sdkmetrics.AggregationExplicitBucketHistogram{ 87 Boundaries: defaultConfig.PerUnitHistogramBoundaries["1"], 88 }, 89 }, 90 ), 91 sdkmetrics.NewView( 92 sdkmetrics.Instrument{ 93 Kind: sdkmetrics.InstrumentKindHistogram, 94 Unit: "ms", 95 }, 96 sdkmetrics.Stream{ 97 Aggregation: sdkmetrics.AggregationExplicitBucketHistogram{ 98 Boundaries: defaultConfig.PerUnitHistogramBoundaries["ms"], 99 }, 100 }, 101 ), 102 ), 103 ) 104 105 p, err := NewOtelMetricsHandler( 106 log.NewTestLogger(), 107 &testProvider{meter: provider.Meter("test")}, 108 defaultConfig, 109 ) 110 require.NoError(t, err) 111 recordMetrics(p) 112 113 var got metricdata.ResourceMetrics 114 err = rdr.Collect(ctx, &got) 115 assert.Nil(t, err) 116 117 want := []metricdata.Metrics{ 118 { 119 Name: "hits", 120 Data: metricdata.Sum[int64]{ 121 DataPoints: []metricdata.DataPoint[int64]{ 122 { 123 Value: 8, 124 }, 125 }, 126 Temporality: metricdata.CumulativeTemporality, 127 IsMonotonic: true, 128 }, 129 }, 130 { 131 Name: "hits-tagged", 132 Data: metricdata.Sum[int64]{ 133 DataPoints: []metricdata.DataPoint[int64]{ 134 { 135 Attributes: attribute.NewSet(attribute.String("taskqueue", "__sticky__")), 136 Value: 11, 137 }, 138 }, 139 Temporality: metricdata.CumulativeTemporality, 140 IsMonotonic: true, 141 }, 142 }, 143 { 144 Name: "hits-tagged-excluded", 145 Data: metricdata.Sum[int64]{ 146 DataPoints: []metricdata.DataPoint[int64]{ 147 { 148 149 Attributes: attribute.NewSet(attribute.String("taskqueue", tagExcludedValue)), 150 Value: 14, 151 }, 152 }, 153 Temporality: metricdata.CumulativeTemporality, 154 IsMonotonic: true, 155 }, 156 }, 157 { 158 Name: "latency", 159 Data: metricdata.Histogram[int64]{ 160 DataPoints: []metricdata.HistogramDataPoint[int64]{ 161 { 162 Count: 2, 163 BucketCounts: []uint64{0, 0, 0, 1, 1, 0}, 164 Min: metricdata.NewExtrema[int64](int64(minLatency)), 165 Max: metricdata.NewExtrema[int64](int64(maxLatency)), 166 Sum: 6503, 167 }, 168 }, 169 Temporality: metricdata.CumulativeTemporality, 170 }, 171 Unit: "ms", 172 }, 173 { 174 Name: "temp", 175 Data: metricdata.Gauge[float64]{ 176 DataPoints: []metricdata.DataPoint[float64]{ 177 { 178 Attributes: attribute.NewSet(attribute.String("location", "Mare Imbrium")), 179 Value: 100, 180 }, 181 }, 182 }, 183 }, 184 { 185 Name: "transmission", 186 Data: metricdata.Histogram[int64]{ 187 DataPoints: []metricdata.HistogramDataPoint[int64]{ 188 { 189 Count: 1, 190 BucketCounts: []uint64{0, 0, 1}, 191 Min: metricdata.NewExtrema[int64](int64(testBytes)), 192 Max: metricdata.NewExtrema[int64](int64(testBytes)), 193 Sum: int64(testBytes), 194 }, 195 }, 196 Temporality: metricdata.CumulativeTemporality, 197 }, 198 Unit: "By", 199 }, 200 } 201 if diff := cmp.Diff(want, got.ScopeMetrics[0].Metrics, 202 cmp.Comparer(func(e1, e2 metricdata.Extrema[int64]) bool { 203 v1, ok1 := e1.Value() 204 v2, ok2 := e2.Value() 205 return ok1 && ok2 && v1 == v2 206 }), 207 cmp.Comparer(func(a1, a2 attribute.Set) bool { 208 return a1.Equals(&a2) 209 }), 210 cmpopts.SortSlices(func(x, y metricdata.Metrics) bool { 211 return x.Name < y.Name 212 }), 213 cmpopts.IgnoreFields(metricdata.DataPoint[int64]{}, "StartTime", "Time"), 214 cmpopts.IgnoreFields(metricdata.DataPoint[float64]{}, "StartTime", "Time"), 215 cmpopts.IgnoreFields(metricdata.HistogramDataPoint[int64]{}, "StartTime", "Time", "Bounds"), 216 ); diff != "" { 217 t.Errorf("mismatch (-want, +got):\n%s", diff) 218 } 219 } 220 221 func recordMetrics(mp Handler) { 222 hitsCounter := mp.Counter("hits") 223 gauge := mp.Gauge("temp") 224 225 timer := mp.Timer("latency") 226 histogram := mp.Histogram("transmission", Bytes) 227 hitsTaggedCounter := mp.Counter("hits-tagged") 228 hitsTaggedExcludedCounter := mp.Counter("hits-tagged-excluded") 229 230 hitsCounter.Record(8) 231 gauge.Record(100, StringTag("location", "Mare Imbrium")) 232 timer.Record(time.Duration(minLatency) * time.Millisecond) 233 timer.Record(time.Duration(maxLatency) * time.Millisecond) 234 histogram.Record(int64(testBytes)) 235 hitsTaggedCounter.Record(11, TaskQueueTag("__sticky__")) 236 hitsTaggedExcludedCounter.Record(14, TaskQueueTag("filtered")) 237 } 238 239 type erroneousMeter struct { 240 metric.Meter 241 err error 242 } 243 244 func (t erroneousMeter) Int64Counter(string, ...metric.Int64CounterOption) (metric.Int64Counter, error) { 245 return nil, t.err 246 } 247 248 func (t erroneousMeter) Int64Histogram(string, ...metric.Int64HistogramOption) (metric.Int64Histogram, error) { 249 return nil, t.err 250 } 251 252 func (t erroneousMeter) Float64ObservableGauge(string, ...metric.Float64ObservableGaugeOption) (metric.Float64ObservableGauge, error) { 253 return nil, t.err 254 } 255 256 var testErr = errors.New("test error") 257 258 func TestOtelMetricsHandler_Error(t *testing.T) { 259 t.Parallel() 260 261 ctrl := gomock.NewController(t) 262 logger := log.NewMockLogger(ctrl) 263 meter := erroneousMeter{err: testErr} 264 provider := &testProvider{meter: meter} 265 cfg := ClientConfig{} 266 handler, err := NewOtelMetricsHandler(logger, provider, cfg) 267 require.NoError(t, err) 268 msg := "error getting metric" 269 errTag := tag.Error(testErr) 270 271 logger.EXPECT().Error(msg, tag.NewStringTag("MetricName", "counter"), errTag) 272 handler.Counter("counter").Record(1) 273 logger.EXPECT().Error(msg, tag.NewStringTag("MetricName", "timer"), errTag) 274 handler.Timer("timer").Record(time.Second) 275 logger.EXPECT().Error(msg, tag.NewStringTag("MetricName", "gauge"), errTag) 276 handler.Gauge("gauge").Record(1.0) 277 logger.EXPECT().Error(msg, tag.NewStringTag("MetricName", "histogram"), errTag) 278 handler.Histogram("histogram", Bytes).Record(1) 279 }