Commit a19b75ac authored by David Symonds's avatar David Symonds

internal/timeseries: New package.

This implements a time series data structure.
It is not a general purpose package, but will be used
by the upcoming trace package.

Change-Id: I3aa547b2f76582fea246b2b35b465b35499f3fda
Reviewed-on: https://go-review.googlesource.com/10720Reviewed-by: 's avatarAndrew Gerrand <adg@golang.org>
parent 84afb0af
This diff is collapsed.
// 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
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment