1package circonusllhist
2
3import (
4	"bytes"
5	"fmt"
6	"math"
7	"math/rand"
8	"testing"
9	"time"
10)
11
12func TestCreate(t *testing.T) {
13	h := New()
14	/*
15		for j := 0; j < 100000; j++ {
16			h.RecordIntScale(rand.Intn(1000), 0)
17		}
18	*/
19	h.RecordIntScales(99, 0, int64(rand.Intn(2))+1)
20	buf := bytes.NewBuffer([]byte{})
21	if err := h.Serialize(buf); err != nil {
22		t.Error(err)
23	}
24	h2, err := Deserialize(buf)
25	if err != nil {
26		t.Error(err)
27	}
28	for j := uint16(0); j < h2.used; j++ {
29		if h2.bvs[j].exp < 1 && (h2.bvs[j].val%10) != 0 {
30			t.Error(fmt.Errorf("bad bin[%v] %ve%v", j, float64(h2.bvs[j].val)/10.0, h2.bvs[j].exp))
31		}
32	}
33}
34
35func TestSerialize(t *testing.T) {
36	h, err := NewFromStrings([]string{
37		"H[0.0e+00]=1",
38		"H[1.0e+01]=1",
39		"H[2.0e+02]=1",
40	}, false)
41	if err != nil {
42		t.Error("could not read from strings for test")
43	}
44
45	buf := bytes.NewBuffer([]byte{})
46	if err := h.Serialize(buf); err != nil {
47		t.Error(err)
48	}
49
50	h2, err := Deserialize(buf)
51	if err != nil {
52		t.Error(h2, err)
53	}
54	if !h.Equals(h2) {
55		t.Log(h.DecStrings())
56		t.Log(h2.DecStrings())
57		t.Error("histograms do not match")
58	}
59}
60
61func helpTestBin(t *testing.T, v float64, val, exp int8) {
62	b := newBinFromFloat64(v)
63	if b.val != val || b.exp != exp {
64		t.Errorf("%v -> [%v,%v] expected, but got [%v,%v]", v, val, exp, b.val, b.exp)
65	}
66}
67
68func fuzzy_equals(expected, actual float64) bool {
69	delta := math.Abs(expected / 100000.0)
70	if actual >= expected-delta && actual <= expected+delta {
71		return true
72	}
73	return false
74}
75
76func TestBins(t *testing.T) {
77	helpTestBin(t, 0.0, 0, 0)
78	helpTestBin(t, 100, 10, 2)
79	helpTestBin(t, 9.9999e-129, 0, 0)
80	helpTestBin(t, 1e-128, 10, -128)
81	helpTestBin(t, 1.00001e-128, 10, -128)
82	helpTestBin(t, 1.09999e-128, 10, -128)
83	helpTestBin(t, 1.1e-128, 11, -128)
84	helpTestBin(t, 1e127, 10, 127)
85	helpTestBin(t, 9.999e127, 99, 127)
86	helpTestBin(t, 1e128, -1, 0)
87	helpTestBin(t, -9.9999e-129, 0, 0)
88	helpTestBin(t, -1e-128, -10, -128)
89	helpTestBin(t, -1.00001e-128, -10, -128)
90	helpTestBin(t, -1.09999e-128, -10, -128)
91	helpTestBin(t, -1.1e-128, -11, -128)
92	helpTestBin(t, -1e127, -10, 127)
93	helpTestBin(t, -9.999e127, -99, 127)
94	helpTestBin(t, -1e128, -1, 0)
95	helpTestBin(t, 9.999e127, 99, 127)
96
97	h := New()
98	h.RecordIntScale(100, 0)
99	if h.bvs[0].val != 10 || h.bvs[0].exp != 2 {
100		t.Errorf("100 not added correctly")
101	}
102
103	h = New()
104	h.RecordValue(100.0)
105	if h.bvs[0].val != 10 || h.bvs[0].exp != 2 {
106		t.Errorf("100.0 not added correctly")
107	}
108}
109
110func TestRecordDuration(t *testing.T) {
111	tests := []struct {
112		input      []time.Duration
113		inputUnit  time.Duration
114		approxSum  time.Duration
115		approxMean time.Duration
116		tolerance  time.Duration
117	}{
118		{
119			input:      []time.Duration{time.Nanosecond},
120			approxSum:  time.Nanosecond,
121			approxMean: time.Nanosecond,
122		},
123		{
124			input:      []time.Duration{3 * time.Nanosecond},
125			approxSum:  3 * time.Nanosecond,
126			approxMean: 3 * time.Nanosecond,
127		},
128		{
129			input:      []time.Duration{1000 * time.Second},
130			approxSum:  1000 * time.Second,
131			approxMean: 1000 * time.Second,
132		},
133		{
134			input: []time.Duration{
135				4 * time.Second,
136				8 * time.Second,
137			},
138			approxSum:  12.0 * time.Second,
139			approxMean: 6.0 * time.Second,
140		},
141	}
142
143	fuzzyEquals := func(expected, actual time.Duration) bool {
144		diff := math.Abs(float64(expected) - float64(actual))
145		if (diff / math.Max(float64(expected), float64(actual))) > 0.05 {
146			return false
147		}
148		return true
149	}
150
151	for n, test := range tests {
152		test := test
153		t.Run(fmt.Sprintf("%d", n), func(t *testing.T) {
154			h := New()
155			for _, dur := range test.input {
156				h.RecordDuration(dur)
157			}
158
159			if v := time.Duration(1000000000.0 * h.ApproxSum()); !fuzzyEquals(v, test.approxSum) {
160				t.Fatalf("%v approx sum bad: have=%v want=%v", test.input, h.ApproxSum(), test.approxSum)
161			}
162
163			if v := time.Duration(1000000000.0 * h.ApproxMean()); !fuzzyEquals(v, test.approxMean) {
164				t.Fatalf("%v approx mean bad: have=%v want=%v", test.input, v, test.approxMean)
165			}
166		})
167	}
168}
169
170func helpTestVB(t *testing.T, v, b, w float64) {
171	bin := newBinFromFloat64(v)
172	out := bin.value()
173	interval := bin.binWidth()
174	if out < 0 {
175		interval *= -1.0
176	}
177	if !fuzzy_equals(b, out) {
178		t.Errorf("%v -> %v != %v\n", v, out, b)
179	}
180	if !fuzzy_equals(w, interval) {
181		t.Errorf("%v -> [%v] != [%v]\n", v, interval, w)
182	}
183}
184
185func TestBinSizes(t *testing.T) {
186	helpTestVB(t, 43.3, 43.0, 1.0)
187	helpTestVB(t, 99.9, 99.0, 1.0)
188	helpTestVB(t, 10.0, 10.0, 1.0)
189	helpTestVB(t, 1.0, 1.0, 0.1)
190	helpTestVB(t, 0.0002, 0.0002, 0.00001)
191	helpTestVB(t, 0.003, 0.003, 0.0001)
192	helpTestVB(t, 0.3201, 0.32, 0.01)
193	helpTestVB(t, 0.0035, 0.0035, 0.0001)
194	helpTestVB(t, -1.0, -1.0, -0.1)
195	helpTestVB(t, -0.00123, -0.0012, -0.0001)
196	helpTestVB(t, -987324, -980000, -10000)
197}
198