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