1// Copyright 2019, OpenTelemetry Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package metric_test 16 17import ( 18 "context" 19 "fmt" 20 "math" 21 "testing" 22 23 "github.com/stretchr/testify/require" 24 25 "go.opentelemetry.io/otel/api/core" 26 "go.opentelemetry.io/otel/api/key" 27 "go.opentelemetry.io/otel/api/metric" 28 export "go.opentelemetry.io/otel/sdk/export/metric" 29 "go.opentelemetry.io/otel/sdk/export/metric/aggregator" 30 sdk "go.opentelemetry.io/otel/sdk/metric" 31 "go.opentelemetry.io/otel/sdk/metric/aggregator/array" 32 "go.opentelemetry.io/otel/sdk/metric/aggregator/counter" 33 "go.opentelemetry.io/otel/sdk/metric/aggregator/gauge" 34) 35 36type correctnessBatcher struct { 37 t *testing.T 38 agg export.Aggregator 39 records []export.Record 40} 41 42type testLabelEncoder struct{} 43 44func (cb *correctnessBatcher) AggregatorFor(*export.Descriptor) export.Aggregator { 45 return cb.agg 46} 47 48func (cb *correctnessBatcher) CheckpointSet() export.CheckpointSet { 49 cb.t.Fatal("Should not be called") 50 return nil 51} 52 53func (*correctnessBatcher) FinishedCollection() { 54} 55 56func (cb *correctnessBatcher) Process(_ context.Context, record export.Record) error { 57 cb.records = append(cb.records, record) 58 return nil 59} 60 61func (testLabelEncoder) Encode(labels []core.KeyValue) string { 62 return fmt.Sprint(labels) 63} 64 65func TestInputRangeTestCounter(t *testing.T) { 66 ctx := context.Background() 67 cagg := counter.New() 68 batcher := &correctnessBatcher{ 69 t: t, 70 agg: cagg, 71 } 72 sdk := sdk.New(batcher, sdk.NewDefaultLabelEncoder()) 73 74 var sdkErr error 75 sdk.SetErrorHandler(func(handleErr error) { 76 sdkErr = handleErr 77 }) 78 79 counter := sdk.NewInt64Counter("counter.name", metric.WithMonotonic(true)) 80 81 counter.Add(ctx, -1, sdk.Labels()) 82 require.Equal(t, aggregator.ErrNegativeInput, sdkErr) 83 sdkErr = nil 84 85 sdk.Collect(ctx) 86 sum, err := cagg.Sum() 87 require.Equal(t, int64(0), sum.AsInt64()) 88 require.Nil(t, err) 89 90 counter.Add(ctx, 1, sdk.Labels()) 91 checkpointed := sdk.Collect(ctx) 92 93 sum, err = cagg.Sum() 94 require.Equal(t, int64(1), sum.AsInt64()) 95 require.Equal(t, 1, checkpointed) 96 require.Nil(t, err) 97 require.Nil(t, sdkErr) 98} 99 100func TestInputRangeTestMeasure(t *testing.T) { 101 ctx := context.Background() 102 magg := array.New() 103 batcher := &correctnessBatcher{ 104 t: t, 105 agg: magg, 106 } 107 sdk := sdk.New(batcher, sdk.NewDefaultLabelEncoder()) 108 109 var sdkErr error 110 sdk.SetErrorHandler(func(handleErr error) { 111 sdkErr = handleErr 112 }) 113 114 measure := sdk.NewFloat64Measure("measure.name", metric.WithAbsolute(true)) 115 116 measure.Record(ctx, -1, sdk.Labels()) 117 require.Equal(t, aggregator.ErrNegativeInput, sdkErr) 118 sdkErr = nil 119 120 sdk.Collect(ctx) 121 count, err := magg.Count() 122 require.Equal(t, int64(0), count) 123 require.Nil(t, err) 124 125 measure.Record(ctx, 1, sdk.Labels()) 126 measure.Record(ctx, 2, sdk.Labels()) 127 checkpointed := sdk.Collect(ctx) 128 129 count, err = magg.Count() 130 require.Equal(t, int64(2), count) 131 require.Equal(t, 1, checkpointed) 132 require.Nil(t, sdkErr) 133 require.Nil(t, err) 134} 135 136func TestDisabledInstrument(t *testing.T) { 137 ctx := context.Background() 138 batcher := &correctnessBatcher{ 139 t: t, 140 agg: nil, 141 } 142 sdk := sdk.New(batcher, sdk.NewDefaultLabelEncoder()) 143 measure := sdk.NewFloat64Measure("measure.name", metric.WithAbsolute(true)) 144 145 measure.Record(ctx, -1, sdk.Labels()) 146 checkpointed := sdk.Collect(ctx) 147 148 require.Equal(t, 0, checkpointed) 149} 150 151func TestRecordNaN(t *testing.T) { 152 ctx := context.Background() 153 batcher := &correctnessBatcher{ 154 t: t, 155 agg: gauge.New(), 156 } 157 sdk := sdk.New(batcher, sdk.NewDefaultLabelEncoder()) 158 159 var sdkErr error 160 sdk.SetErrorHandler(func(handleErr error) { 161 sdkErr = handleErr 162 }) 163 g := sdk.NewFloat64Gauge("gauge.name") 164 165 require.Nil(t, sdkErr) 166 g.Set(ctx, math.NaN(), sdk.Labels()) 167 require.Error(t, sdkErr) 168} 169 170func TestSDKLabelEncoder(t *testing.T) { 171 ctx := context.Background() 172 cagg := counter.New() 173 batcher := &correctnessBatcher{ 174 t: t, 175 agg: cagg, 176 } 177 sdk := sdk.New(batcher, testLabelEncoder{}) 178 179 measure := sdk.NewFloat64Measure("measure") 180 measure.Record(ctx, 1, sdk.Labels(key.String("A", "B"), key.String("C", "D"))) 181 182 sdk.Collect(ctx) 183 184 require.Equal(t, 1, len(batcher.records)) 185 186 labels := batcher.records[0].Labels() 187 require.Equal(t, `[{A {8 0 B}} {C {8 0 D}}]`, labels.Encoded()) 188} 189 190func TestDefaultLabelEncoder(t *testing.T) { 191 encoder := sdk.NewDefaultLabelEncoder() 192 encoded := encoder.Encode([]core.KeyValue{key.String("A", "B"), key.String("C", "D")}) 193 require.Equal(t, `A=B,C=D`, encoded) 194} 195