├── .gitignore ├── LICENSE ├── README.md ├── timeseries.go └── timeseries_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sangwon Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## go-timeseries 2 | Golang Timeseries Data Bucket 3 | 4 | ## Get Package 5 | ```bash 6 | $ go get github.com/sangwonl/go-timeseries 7 | ``` 8 | 9 | ## Import Package 10 | ```go 11 | import "github.com/sangwonl/go-timeseries" 12 | ``` 13 | 14 | ## Create a TimeSeries Struct 15 | ```go 16 | ts := timeseries.NewTimeSeries(, ) 17 | ``` 18 | 19 | ## Add Data with Time 20 | ```go 21 | i := Integer(1) 22 | ts.Add(&i, time.Now()) 23 | ``` 24 | 25 | ## Extract Data in Time Range 26 | ```go 27 | rangeVals = ts.Range(resolutionIdx, beginTime, endTime) 28 | firstBucketVal = rangeVals[0].(*Integer).Value() 29 | secondBucketVal = rangeVals[1].(*Integer).Value() 30 | ``` 31 | 32 | ## Example 33 | ```go 34 | import ( 35 | "time" 36 | "github.com/sangwonl/go-timeseries" 37 | ) 38 | 39 | func TestTimeSeries() { 40 | primitiveCreator := timeseries.NewInteger 41 | resolutions := []time.Duration{ 42 | timeseries.ResolutionOneSecond, 43 | timeseries.ResolutionTenSeconds, 44 | } 45 | 46 | ts := timeseries.NewTimeSeries(primitiveCreator, resolutions) 47 | assertNotNil(t, ts) 48 | assertEqual(t, len(ts.dataStreams), 2) 49 | assertEqual(t, ts.dataStreams[0].NumBuckets(), 0) 50 | 51 | i := Integer(1) 52 | ts.Add(&i, asTime(1)) 53 | ts.Add(&i, asTime(1)) 54 | ts.Add(&i, asTime(1)) 55 | ts.Add(&i, asTime(2)) 56 | ts.Add(&i, asTime(2)) 57 | ts.Add(&i, asTime(3)) 58 | ts.Add(&i, asTime(3)) 59 | ts.Add(&i, asTime(3)) 60 | ts.Add(&i, asTime(3)) 61 | ts.Add(&i, asTime(3)) 62 | assertEqual(t, ts.dataStreams[0].NumBuckets(), 3) 63 | 64 | total := ts.Total().(*Integer) 65 | assertEqual(t, total.Value(), 10) 66 | 67 | resolutionIdx := 0 68 | var rangeVals []timeseries.Primitive 69 | var bucketVal int 70 | 71 | rangeVals = ts.Range(resolutionIdx, asTime(1), asTime(1)) 72 | assertEqual(t, len(rangeVals), 1) 73 | 74 | bucketVal = rangeVals[0].(*Integer).Value() 75 | assertEqual(t, bucketVal, 3) 76 | 77 | rangeVals = ts.Range(resolutionIdx, asTime(1), asTime(2)) 78 | assertEqual(t, len(rangeVals), 2) 79 | 80 | bucketVal = rangeVals[0].(*Integer).Value() 81 | bucketVal += rangeVals[1].(*Integer).Value() 82 | assertEqual(t, bucketVal, 5) 83 | 84 | rangeVals = ts.Range(resolutionIdx, asTime(2), asTime(2)) 85 | assertEqual(t, len(rangeVals), 1) 86 | 87 | bucketVal = rangeVals[0].(*Integer).Value() 88 | assertEqual(t, bucketVal, 2) 89 | 90 | rangeVals = ts.Range(resolutionIdx, asTime(2), asTime(3)) 91 | assertEqual(t, len(rangeVals), 2) 92 | 93 | bucketVal = rangeVals[0].(*Integer).Value() 94 | bucketVal += rangeVals[1].(*Integer).Value() 95 | assertEqual(t, bucketVal, 7) 96 | 97 | resolutionIdx = 1 98 | rangeVals = ts.Range(resolutionIdx, asTime(1), asTime(3)) 99 | assertEqual(t, len(rangeVals), 1) 100 | 101 | bucketVal = rangeVals[0].(*Integer).Value() 102 | assertEqual(t, bucketVal, 10) 103 | } 104 | ``` 105 | -------------------------------------------------------------------------------- /timeseries.go: -------------------------------------------------------------------------------- 1 | package timeseries 2 | 3 | import ( 4 | "container/list" 5 | "time" 6 | ) 7 | 8 | func Abs(a int) int { 9 | if a < 0 { 10 | return -a 11 | } 12 | return a 13 | } 14 | 15 | func minTime(a, b time.Time) time.Time { 16 | if a.Before(b) { 17 | return a 18 | } 19 | return b 20 | } 21 | 22 | func maxTime(a, b time.Time) time.Time { 23 | if a.After(b) { 24 | return a 25 | } 26 | return b 27 | } 28 | 29 | type Primitive interface { 30 | Add(other Primitive) 31 | CopyFrom(other Primitive) 32 | Reset() 33 | Ts() time.Time 34 | SetTs(t time.Time) 35 | } 36 | 37 | type Integer struct { 38 | val int 39 | ts time.Time 40 | } 41 | 42 | func NewInteger() Primitive { i := Integer{0, time.Time{}}; return &i } 43 | func (i *Integer) Value() int { return i.val } 44 | func (i *Integer) SetValue(v int) { i.val = v } 45 | func (i *Integer) Add(other Primitive) { i.val += other.(*Integer).val } 46 | func (i *Integer) CopyFrom(other Primitive) { i.val = other.(*Integer).val } 47 | func (i *Integer) Reset() { i.val = 0 } 48 | func (i *Integer) Ts() time.Time { return i.ts } 49 | func (i *Integer) SetTs(t time.Time) { i.ts = t } 50 | 51 | const ( 52 | ResolutionOneSecond = 1 * time.Second 53 | ResolutionTenSeconds = 10 * time.Second 54 | ResolutionOneMinute = 1 * time.Minute 55 | ResolutionTenMinutes = 10 * time.Minute 56 | ResolutionOneHour = 1 * time.Hour 57 | ResolutionSixHours = 6 * time.Hour 58 | ResolutionOneDay = 24 * time.Hour 59 | ResolutionOneWeek = 7 * 24 * time.Hour 60 | ResolutionFourWeeks = 4 * 7 * 24 * time.Hour 61 | ) 62 | 63 | type dataStream struct { 64 | primitiveFunc func() Primitive 65 | buckets *list.List 66 | resolution time.Duration 67 | beginTime time.Time 68 | endTime time.Time 69 | } 70 | 71 | func (ds *dataStream) initialize(p func() Primitive, resolution time.Duration) { 72 | ds.primitiveFunc = p 73 | ds.resolution = resolution 74 | ds.buckets = list.New() 75 | } 76 | 77 | func (ds *dataStream) NumBuckets() int { 78 | return ds.buckets.Len() 79 | } 80 | 81 | func (ds *dataStream) reset() { 82 | ds.beginTime = time.Time{} 83 | ds.endTime = time.Time{} 84 | 85 | var next *list.Element 86 | for e := ds.buckets.Front(); e != nil; e = next { 87 | next = e.Next() 88 | ds.buckets.Remove(e) 89 | } 90 | } 91 | 92 | type TimeSeries struct { 93 | primitiveFunc func() Primitive 94 | dataStreams []*dataStream 95 | total Primitive 96 | } 97 | 98 | func NewTimeSeries(p func() Primitive, resolutions []time.Duration) *TimeSeries { 99 | timeSeries := new(TimeSeries) 100 | timeSeries.initialize(p, resolutions) 101 | return timeSeries 102 | } 103 | 104 | func (ts *TimeSeries) initialize(p func() Primitive, resolutions []time.Duration) { 105 | ts.primitiveFunc = p 106 | ts.total = ts.primitiveFunc() 107 | ts.dataStreams = make([]*dataStream, len(resolutions)) 108 | for i := range resolutions { 109 | ts.dataStreams[i] = new(dataStream) 110 | ts.dataStreams[i].initialize(p, resolutions[i]) 111 | } 112 | ts.reset() 113 | } 114 | 115 | func (ts *TimeSeries) reset() { 116 | ts.total.Reset() 117 | for i := range ts.dataStreams { 118 | ts.dataStreams[i].reset() 119 | } 120 | } 121 | 122 | func (ts *TimeSeries) Add(d Primitive, t time.Time) { 123 | for _, ds := range ts.dataStreams { 124 | isFirstAdd := ds.buckets.Len() == 0 125 | if isFirstAdd { 126 | ds.beginTime = t 127 | ds.endTime = t 128 | 129 | first := ds.primitiveFunc() 130 | first.SetTs(ds.beginTime) 131 | ds.buckets.PushBack(first) 132 | } 133 | 134 | bucketIdxAtEnd := int(ds.endTime.Sub(ds.beginTime) / ds.resolution) 135 | bucketIdxFromBegin := int(t.Sub(ds.beginTime) / ds.resolution) 136 | for i := 0; i < bucketIdxFromBegin-bucketIdxAtEnd; i++ { 137 | bucketTimeDelta := time.Duration(bucketIdxAtEnd+i+1) * ds.resolution 138 | bucketTime := ds.beginTime.Add(bucketTimeDelta) 139 | p := ds.primitiveFunc() 140 | p.SetTs(bucketTime) 141 | ds.buckets.PushBack(p) 142 | } 143 | 144 | lastBucket := ds.buckets.Back().Value.(Primitive) 145 | lastBucket.Add(d) 146 | 147 | // update begin and end time 148 | ds.beginTime = minTime(ds.beginTime, t) 149 | ds.endTime = maxTime(ds.endTime, t) 150 | } 151 | ts.total.Add(d) 152 | } 153 | 154 | func (ts *TimeSeries) Total() Primitive { 155 | return ts.total 156 | } 157 | 158 | func (ts *TimeSeries) All(resolutionIdx int) []Primitive { 159 | ds := ts.dataStreams[resolutionIdx] 160 | return primitivesAsArray(ds.buckets) 161 | } 162 | 163 | func (ts *TimeSeries) Range(resolutionIdx int, fromTime, toTime time.Time) []Primitive { 164 | ds := ts.dataStreams[resolutionIdx] 165 | filtered := filterBucket(ds, fromTime, toTime) 166 | return primitivesAsArray(filtered) 167 | } 168 | 169 | func filterBucket(ds *dataStream, fromTime, toTime time.Time) *list.List { 170 | beginBucketIdx := int(fromTime.Sub(ds.beginTime) / ds.resolution) 171 | endBucketIdx := int(toTime.Sub(ds.beginTime) / ds.resolution) 172 | filteredBuckets := list.New() 173 | iterIdx := 0 174 | for e := ds.buckets.Front(); e != nil; e = e.Next() { 175 | if beginBucketIdx <= iterIdx && iterIdx <= endBucketIdx { 176 | filteredBuckets.PushBack(e.Value) 177 | } 178 | iterIdx++ 179 | } 180 | return filteredBuckets 181 | } 182 | 183 | func primitivesAsArray(buckets *list.List) []Primitive { 184 | primitives := make([]Primitive, buckets.Len()) 185 | insertIdx := 0 186 | for e := buckets.Front(); e != nil; e = e.Next() { 187 | primitives[insertIdx] = e.Value.(Primitive) 188 | insertIdx++ 189 | } 190 | return primitives 191 | } 192 | -------------------------------------------------------------------------------- /timeseries_test.go: -------------------------------------------------------------------------------- 1 | package timeseries 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func assertEqual(t *testing.T, a, b int) { 9 | if a != b { 10 | t.Errorf("a(=%v) should be b(=%v)", a, b) 11 | } 12 | } 13 | 14 | func assertNotNil(t *testing.T, o interface{}) { 15 | if o == nil { 16 | t.Errorf("o(=%q) should not be nil", o) 17 | } 18 | } 19 | 20 | func asTime(s int64) time.Time { 21 | utcZeroTime := time.Time{} 22 | return utcZeroTime.Add(time.Duration(s) * time.Second) 23 | } 24 | 25 | func asTimeMin(s int64) time.Time { 26 | utcZeroTime := time.Time{} 27 | return utcZeroTime.Add(time.Duration(s) * time.Minute) 28 | } 29 | 30 | func TestTimeSeriesPrimitive(t *testing.T) { 31 | i := Integer{} 32 | i.SetValue(1) 33 | assertEqual(t, i.Value(), 1) 34 | 35 | i2 := Integer{} 36 | i2.SetValue(2) 37 | i.Add(&i2) 38 | assertEqual(t, i.Value(), 3) 39 | 40 | i.CopyFrom(&i2) 41 | assertEqual(t, i.Value(), 2) 42 | 43 | i.Reset() 44 | assertEqual(t, i.Value(), 0) 45 | } 46 | 47 | func TestTimeSereis(t *testing.T) { 48 | primitiveFunc := NewInteger 49 | resolutions := []time.Duration{ 50 | ResolutionOneSecond, 51 | ResolutionTenSeconds, 52 | } 53 | 54 | timeSeries := NewTimeSeries(primitiveFunc, resolutions) 55 | assertNotNil(t, timeSeries) 56 | assertEqual(t, len(timeSeries.dataStreams), 2) 57 | assertEqual(t, timeSeries.dataStreams[0].NumBuckets(), 0) 58 | 59 | i := Integer{} 60 | i.SetValue(1) 61 | timeSeries.Add(&i, asTime(1)) 62 | timeSeries.Add(&i, asTime(1)) 63 | timeSeries.Add(&i, asTime(1)) 64 | timeSeries.Add(&i, asTime(2)) 65 | timeSeries.Add(&i, asTime(2)) 66 | timeSeries.Add(&i, asTime(3)) 67 | timeSeries.Add(&i, asTime(3)) 68 | timeSeries.Add(&i, asTime(3)) 69 | timeSeries.Add(&i, asTime(3)) 70 | timeSeries.Add(&i, asTime(3)) 71 | assertEqual(t, timeSeries.dataStreams[0].NumBuckets(), 3) 72 | 73 | total := timeSeries.Total().(*Integer) 74 | assertEqual(t, total.Value(), 10) 75 | 76 | resolutionIdx := 0 77 | var rangeVals []Primitive 78 | var bucketVal int 79 | 80 | rangeVals = timeSeries.Range(resolutionIdx, asTime(1), asTime(1)) 81 | assertEqual(t, len(rangeVals), 1) 82 | 83 | bucketVal = rangeVals[0].(*Integer).Value() 84 | assertEqual(t, bucketVal, 3) 85 | 86 | rangeVals = timeSeries.Range(resolutionIdx, asTime(1), asTime(2)) 87 | assertEqual(t, len(rangeVals), 2) 88 | 89 | bucketVal = rangeVals[0].(*Integer).Value() 90 | bucketVal += rangeVals[1].(*Integer).Value() 91 | assertEqual(t, bucketVal, 5) 92 | 93 | rangeVals = timeSeries.Range(resolutionIdx, asTime(2), asTime(2)) 94 | assertEqual(t, len(rangeVals), 1) 95 | 96 | bucketVal = rangeVals[0].(*Integer).Value() 97 | assertEqual(t, bucketVal, 2) 98 | 99 | rangeVals = timeSeries.Range(resolutionIdx, asTime(2), asTime(3)) 100 | assertEqual(t, len(rangeVals), 2) 101 | 102 | bucketVal = rangeVals[0].(*Integer).Value() 103 | bucketVal += rangeVals[1].(*Integer).Value() 104 | assertEqual(t, bucketVal, 7) 105 | 106 | resolutionIdx = 1 107 | rangeVals = timeSeries.Range(resolutionIdx, asTime(1), asTime(3)) 108 | assertEqual(t, len(rangeVals), 1) 109 | 110 | bucketVal = rangeVals[0].(*Integer).Value() 111 | assertEqual(t, bucketVal, 10) 112 | 113 | rangeVals = timeSeries.Range(resolutionIdx, asTime(0), asTime(10000)) 114 | assertEqual(t, len(rangeVals), 1) 115 | 116 | bucketVal = rangeVals[0].(*Integer).Value() 117 | assertEqual(t, bucketVal, 10) 118 | 119 | bucketTs := rangeVals[0].Ts() 120 | assertEqual(t, bucketTs.Second(), 1) 121 | } 122 | 123 | func TestTimeSereisWithWideResolution(t *testing.T) { 124 | primitiveFunc := NewInteger 125 | resolutions := []time.Duration{ResolutionTenMinutes} 126 | 127 | timeSeries := NewTimeSeries(primitiveFunc, resolutions) 128 | assertNotNil(t, timeSeries) 129 | assertEqual(t, len(timeSeries.dataStreams), 1) 130 | assertEqual(t, timeSeries.dataStreams[0].NumBuckets(), 0) 131 | 132 | i := Integer{} 133 | i.SetValue(1) 134 | timeSeries.Add(&i, asTimeMin(10)) 135 | timeSeries.Add(&i, asTimeMin(10)) 136 | timeSeries.Add(&i, asTimeMin(10)) 137 | timeSeries.Add(&i, asTimeMin(21)) 138 | timeSeries.Add(&i, asTimeMin(21)) 139 | timeSeries.Add(&i, asTimeMin(21)) 140 | timeSeries.Add(&i, asTimeMin(41)) 141 | timeSeries.Add(&i, asTimeMin(41)) 142 | timeSeries.Add(&i, asTimeMin(41)) 143 | timeSeries.Add(&i, asTimeMin(41)) 144 | assertEqual(t, timeSeries.dataStreams[0].NumBuckets(), 4) 145 | 146 | total := timeSeries.Total().(*Integer) 147 | assertEqual(t, total.Value(), 10) 148 | 149 | resolutionIdx := 0 150 | var rangeVals []Primitive 151 | var bucketVal int 152 | 153 | rangeVals = timeSeries.Range(resolutionIdx, asTimeMin(20), asTimeMin(29)) 154 | assertEqual(t, len(rangeVals), 1) 155 | 156 | bucketVal = rangeVals[0].(*Integer).Value() 157 | assertEqual(t, bucketVal, 3) 158 | 159 | bucketTs := rangeVals[0].Ts() 160 | assertEqual(t, bucketTs.Minute(), 20) 161 | 162 | rangeVals = timeSeries.All(resolutionIdx) 163 | assertEqual(t, len(rangeVals), 4) 164 | 165 | bucketVal = rangeVals[0].(*Integer).Value() 166 | assertEqual(t, bucketVal, 3) 167 | 168 | bucketTs = rangeVals[0].Ts() 169 | assertEqual(t, bucketTs.Minute(), 10) 170 | } 171 | --------------------------------------------------------------------------------