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 array
16
17import (
18	"context"
19	"fmt"
20	"math"
21	"os"
22	"testing"
23	"unsafe"
24
25	"github.com/stretchr/testify/require"
26
27	"go.opentelemetry.io/otel/api/core"
28	ottest "go.opentelemetry.io/otel/internal/testing"
29	export "go.opentelemetry.io/otel/sdk/export/metric"
30	"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
31	"go.opentelemetry.io/otel/sdk/metric/aggregator/test"
32)
33
34// Ensure struct alignment prior to running tests.
35func TestMain(m *testing.M) {
36	fields := []ottest.FieldOffset{
37		{
38			Name:   "Aggregator.ckptSum",
39			Offset: unsafe.Offsetof(Aggregator{}.ckptSum),
40		},
41	}
42	if !ottest.Aligned8Byte(fields, os.Stderr) {
43		os.Exit(1)
44	}
45
46	os.Exit(m.Run())
47}
48
49type updateTest struct {
50	count    int
51	absolute bool
52}
53
54func (ut *updateTest) run(t *testing.T, profile test.Profile) {
55	descriptor := test.NewAggregatorTest(export.MeasureKind, profile.NumberKind, !ut.absolute)
56
57	agg := New()
58
59	all := test.NewNumbers(profile.NumberKind)
60
61	for i := 0; i < ut.count; i++ {
62		x := profile.Random(+1)
63		all.Append(x)
64		test.CheckedUpdate(t, agg, x, descriptor)
65
66		if !ut.absolute {
67			y := profile.Random(-1)
68			all.Append(y)
69			test.CheckedUpdate(t, agg, y, descriptor)
70		}
71	}
72
73	ctx := context.Background()
74	agg.Checkpoint(ctx, descriptor)
75
76	all.Sort()
77
78	sum, err := agg.Sum()
79	require.Nil(t, err)
80	allSum := all.Sum()
81	require.InEpsilon(t,
82		(&allSum).CoerceToFloat64(profile.NumberKind),
83		sum.CoerceToFloat64(profile.NumberKind),
84		0.0000001,
85		"Same sum - absolute")
86	count, err := agg.Count()
87	require.Nil(t, err)
88	require.Equal(t, all.Count(), count, "Same count - absolute")
89
90	min, err := agg.Min()
91	require.Nil(t, err)
92	require.Equal(t, all.Min(), min, "Same min - absolute")
93
94	max, err := agg.Max()
95	require.Nil(t, err)
96	require.Equal(t, all.Max(), max, "Same max - absolute")
97
98	qx, err := agg.Quantile(0.5)
99	require.Nil(t, err)
100	require.Equal(t, all.Median(), qx, "Same median - absolute")
101}
102
103func TestArrayUpdate(t *testing.T) {
104	// Test with an odd an even number of measurements
105	for count := 999; count <= 1000; count++ {
106		t.Run(fmt.Sprint("Odd=", count%2 == 1), func(t *testing.T) {
107			// Test absolute and non-absolute
108			for _, absolute := range []bool{false, true} {
109				t.Run(fmt.Sprint("Absolute=", absolute), func(t *testing.T) {
110					ut := updateTest{
111						count:    count,
112						absolute: absolute,
113					}
114
115					// Test integer and floating point
116					test.RunProfiles(t, ut.run)
117				})
118			}
119		})
120	}
121}
122
123type mergeTest struct {
124	count    int
125	absolute bool
126}
127
128func (mt *mergeTest) run(t *testing.T, profile test.Profile) {
129	ctx := context.Background()
130
131	descriptor := test.NewAggregatorTest(export.MeasureKind, profile.NumberKind, !mt.absolute)
132
133	agg1 := New()
134	agg2 := New()
135
136	all := test.NewNumbers(profile.NumberKind)
137
138	for i := 0; i < mt.count; i++ {
139		x1 := profile.Random(+1)
140		all.Append(x1)
141		test.CheckedUpdate(t, agg1, x1, descriptor)
142
143		x2 := profile.Random(+1)
144		all.Append(x2)
145		test.CheckedUpdate(t, agg2, x2, descriptor)
146
147		if !mt.absolute {
148			y1 := profile.Random(-1)
149			all.Append(y1)
150			test.CheckedUpdate(t, agg1, y1, descriptor)
151
152			y2 := profile.Random(-1)
153			all.Append(y2)
154			test.CheckedUpdate(t, agg2, y2, descriptor)
155		}
156	}
157
158	agg1.Checkpoint(ctx, descriptor)
159	agg2.Checkpoint(ctx, descriptor)
160
161	test.CheckedMerge(t, agg1, agg2, descriptor)
162
163	all.Sort()
164
165	sum, err := agg1.Sum()
166	require.Nil(t, err)
167	allSum := all.Sum()
168	require.InEpsilon(t,
169		(&allSum).CoerceToFloat64(profile.NumberKind),
170		sum.CoerceToFloat64(profile.NumberKind),
171		0.0000001,
172		"Same sum - absolute")
173	count, err := agg1.Count()
174	require.Nil(t, err)
175	require.Equal(t, all.Count(), count, "Same count - absolute")
176
177	min, err := agg1.Min()
178	require.Nil(t, err)
179	require.Equal(t, all.Min(), min, "Same min - absolute")
180
181	max, err := agg1.Max()
182	require.Nil(t, err)
183	require.Equal(t, all.Max(), max, "Same max - absolute")
184
185	qx, err := agg1.Quantile(0.5)
186	require.Nil(t, err)
187	require.Equal(t, all.Median(), qx, "Same median - absolute")
188}
189
190func TestArrayMerge(t *testing.T) {
191	// Test with an odd an even number of measurements
192	for count := 999; count <= 1000; count++ {
193		t.Run(fmt.Sprint("Odd=", count%2 == 1), func(t *testing.T) {
194			// Test absolute and non-absolute
195			for _, absolute := range []bool{false, true} {
196				t.Run(fmt.Sprint("Absolute=", absolute), func(t *testing.T) {
197					mt := mergeTest{
198						count:    count,
199						absolute: absolute,
200					}
201
202					// Test integer and floating point
203					test.RunProfiles(t, mt.run)
204				})
205			}
206		})
207	}
208}
209
210func TestArrayErrors(t *testing.T) {
211	test.RunProfiles(t, func(t *testing.T, profile test.Profile) {
212		agg := New()
213
214		_, err := agg.Max()
215		require.Error(t, err)
216		require.Equal(t, err, aggregator.ErrEmptyDataSet)
217
218		_, err = agg.Min()
219		require.Error(t, err)
220		require.Equal(t, err, aggregator.ErrEmptyDataSet)
221
222		_, err = agg.Quantile(0.1)
223		require.Error(t, err)
224		require.Equal(t, err, aggregator.ErrEmptyDataSet)
225
226		ctx := context.Background()
227
228		descriptor := test.NewAggregatorTest(export.MeasureKind, profile.NumberKind, false)
229
230		test.CheckedUpdate(t, agg, core.Number(0), descriptor)
231
232		if profile.NumberKind == core.Float64NumberKind {
233			test.CheckedUpdate(t, agg, core.NewFloat64Number(math.NaN()), descriptor)
234		}
235		agg.Checkpoint(ctx, descriptor)
236
237		count, err := agg.Count()
238		require.Equal(t, int64(1), count, "NaN value was not counted")
239		require.Nil(t, err)
240
241		num, err := agg.Quantile(0)
242		require.Nil(t, err)
243		require.Equal(t, num, core.Number(0))
244
245		_, err = agg.Quantile(-0.0001)
246		require.Error(t, err)
247		require.Equal(t, err, aggregator.ErrInvalidQuantile)
248
249		_, err = agg.Quantile(1.0001)
250		require.Error(t, err)
251		require.Equal(t, err, aggregator.ErrInvalidQuantile)
252	})
253}
254
255func TestArrayFloat64(t *testing.T) {
256	for _, absolute := range []bool{false, true} {
257		t.Run(fmt.Sprint("Absolute=", absolute), func(t *testing.T) {
258			descriptor := test.NewAggregatorTest(export.MeasureKind, core.Float64NumberKind, !absolute)
259
260			fpsf := func(sign int) []float64 {
261				// Check behavior of a bunch of odd floating
262				// points except for NaN, which is invalid.
263				return []float64{
264					0,
265					math.Inf(sign),
266					1 / math.Inf(sign),
267					1,
268					2,
269					1e100,
270					math.MaxFloat64,
271					math.SmallestNonzeroFloat64,
272					math.MaxFloat32,
273					math.SmallestNonzeroFloat32,
274					math.E,
275					math.Pi,
276					math.Phi,
277					math.Sqrt2,
278					math.SqrtE,
279					math.SqrtPi,
280					math.SqrtPhi,
281					math.Ln2,
282					math.Log2E,
283					math.Ln10,
284					math.Log10E,
285				}
286			}
287
288			all := test.NewNumbers(core.Float64NumberKind)
289
290			ctx := context.Background()
291			agg := New()
292
293			for _, f := range fpsf(1) {
294				all.Append(core.NewFloat64Number(f))
295				test.CheckedUpdate(t, agg, core.NewFloat64Number(f), descriptor)
296			}
297
298			if !absolute {
299				for _, f := range fpsf(-1) {
300					all.Append(core.NewFloat64Number(f))
301					test.CheckedUpdate(t, agg, core.NewFloat64Number(f), descriptor)
302				}
303			}
304
305			agg.Checkpoint(ctx, descriptor)
306
307			all.Sort()
308
309			sum, err := agg.Sum()
310			require.Nil(t, err)
311			allSum := all.Sum()
312			require.InEpsilon(t, (&allSum).AsFloat64(), sum.AsFloat64(), 0.0000001, "Same sum")
313
314			count, err := agg.Count()
315			require.Equal(t, all.Count(), count, "Same count")
316			require.Nil(t, err)
317
318			min, err := agg.Min()
319			require.Nil(t, err)
320			require.Equal(t, all.Min(), min, "Same min")
321
322			max, err := agg.Max()
323			require.Nil(t, err)
324			require.Equal(t, all.Max(), max, "Same max")
325
326			qx, err := agg.Quantile(0.5)
327			require.Nil(t, err)
328			require.Equal(t, all.Median(), qx, "Same median")
329
330			po, err := agg.Points()
331			require.Nil(t, err)
332			require.Equal(t, all.Len(), len(po), "Points() must have same length of updates")
333			for i := 0; i < len(po); i++ {
334				require.Equal(t, all.Points()[i], po[i], "Wrong point at position %d", i)
335			}
336		})
337	}
338}
339