// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package timeseries import ( "math" "testing" "time" ) func isNear(x *Float, y float64, tolerance float64) bool { return math.Abs(x.Value()-y) < tolerance } func isApproximate(x *Float, y float64) bool { return isNear(x, y, 1e-2) } func checkApproximate(t *testing.T, o Observable, y float64) { x := o.(*Float) if !isApproximate(x, y) { t.Errorf("Wanted %g, got %g", y, x.Value()) } } func checkNear(t *testing.T, o Observable, y, tolerance float64) { x := o.(*Float) if !isNear(x, y, tolerance) { t.Errorf("Wanted %g +- %g, got %g", y, tolerance, x.Value()) } } var baseTime = time.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC) func tu(s int64) time.Time { return baseTime.Add(time.Duration(s) * time.Second) } func tu2(s int64, ns int64) time.Time { return baseTime.Add(time.Duration(s)*time.Second + time.Duration(ns)*time.Nanosecond) } func TestBasicTimeSeries(t *testing.T) { ts := NewTimeSeries(NewFloat) fo := new(Float) *fo = Float(10) ts.AddWithTime(fo, tu(1)) ts.AddWithTime(fo, tu(1)) ts.AddWithTime(fo, tu(1)) ts.AddWithTime(fo, tu(1)) checkApproximate(t, ts.Range(tu(0), tu(1)), 40) checkApproximate(t, ts.Total(), 40) ts.AddWithTime(fo, tu(3)) ts.AddWithTime(fo, tu(3)) ts.AddWithTime(fo, tu(3)) checkApproximate(t, ts.Range(tu(0), tu(2)), 40) checkApproximate(t, ts.Range(tu(2), tu(4)), 30) checkApproximate(t, ts.Total(), 70) ts.AddWithTime(fo, tu(1)) ts.AddWithTime(fo, tu(1)) checkApproximate(t, ts.Range(tu(0), tu(2)), 60) checkApproximate(t, ts.Range(tu(2), tu(4)), 30) checkApproximate(t, ts.Total(), 90) *fo = Float(100) ts.AddWithTime(fo, tu(100)) checkApproximate(t, ts.Range(tu(99), tu(100)), 100) checkApproximate(t, ts.Range(tu(0), tu(4)), 36) checkApproximate(t, ts.Total(), 190) *fo = Float(10) ts.AddWithTime(fo, tu(1)) ts.AddWithTime(fo, tu(1)) checkApproximate(t, ts.Range(tu(0), tu(4)), 44) checkApproximate(t, ts.Range(tu(37), tu2(100, 100e6)), 100) checkApproximate(t, ts.Range(tu(50), tu2(100, 100e6)), 100) checkApproximate(t, ts.Range(tu(99), tu2(100, 100e6)), 100) checkApproximate(t, ts.Total(), 210) for i, l := range ts.ComputeRange(tu(36), tu(100), 64) { if i == 63 { checkApproximate(t, l, 100) } else { checkApproximate(t, l, 0) } } checkApproximate(t, ts.Range(tu(0), tu(100)), 210) checkApproximate(t, ts.Range(tu(10), tu(100)), 100) for i, l := range ts.ComputeRange(tu(0), tu(100), 100) { if i < 10 { checkApproximate(t, l, 11) } else if i >= 90 { checkApproximate(t, l, 10) } else { checkApproximate(t, l, 0) } } } func TestFloat(t *testing.T) { f := Float(1) if g, w := f.String(), "1"; g != w { t.Errorf("Float(1).String = %q; want %q", g, w) } f2 := Float(2) var o Observable = &f2 f.Add(o) if g, w := f.Value(), 3.0; g != w { t.Errorf("Float post-add = %v; want %v", g, w) } f.Multiply(2) if g, w := f.Value(), 6.0; g != w { t.Errorf("Float post-multiply = %v; want %v", g, w) } f.Clear() if g, w := f.Value(), 0.0; g != w { t.Errorf("Float post-clear = %v; want %v", g, w) } f.CopyFrom(&f2) if g, w := f.Value(), 2.0; g != w { t.Errorf("Float post-CopyFrom = %v; want %v", g, w) } } type mockClock struct { time time.Time } func (m *mockClock) Time() time.Time { return m.time } func (m *mockClock) Set(t time.Time) { m.time = t } const buckets = 6 var testResolutions = []time.Duration{ 10 * time.Second, // level holds one minute of observations 100 * time.Second, // level holds ten minutes of observations 10 * time.Minute, // level holds one hour of observations } // TestTimeSeries uses a small number of buckets to force a higher // error rate on approximations from the timeseries. type TestTimeSeries struct { timeSeries } func TestExpectedErrorRate(t *testing.T) { ts := new(TestTimeSeries) fake := new(mockClock) fake.Set(time.Now()) ts.timeSeries.init(testResolutions, NewFloat, buckets, fake) for i := 1; i <= 61*61; i++ { fake.Set(fake.Time().Add(1 * time.Second)) ob := Float(1) ts.AddWithTime(&ob, fake.Time()) // The results should be accurate within one missing bucket (1/6) of the observations recorded. checkNear(t, ts.Latest(0, buckets), min(float64(i), 60), 10) checkNear(t, ts.Latest(1, buckets), min(float64(i), 600), 100) checkNear(t, ts.Latest(2, buckets), min(float64(i), 3600), 600) } } func min(a, b float64) float64 { if a < b { return a } return b }