155 lines
4.2 KiB
Go
155 lines
4.2 KiB
Go
|
package stats
|
||
|
|
||
|
import (
|
||
|
"math"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// timeseries holds the history of a changing value over a predefined period of
|
||
|
// time.
|
||
|
type timeseries struct {
|
||
|
size int // The number of time slots. Equivalent to len(slots).
|
||
|
resolution time.Duration // The time resolution of each slot.
|
||
|
stepCount int64 // The number of intervals seen since creation.
|
||
|
head int // The position of the current time in slots.
|
||
|
time time.Time // The time at the beginning of the current time slot.
|
||
|
slots []int64 // A circular buffer of time slots.
|
||
|
}
|
||
|
|
||
|
// newTimeSeries returns a newly allocated timeseries that covers the requested
|
||
|
// period with the given resolution.
|
||
|
func newTimeSeries(initialTime time.Time, period, resolution time.Duration) *timeseries {
|
||
|
size := int(period.Nanoseconds()/resolution.Nanoseconds()) + 1
|
||
|
return ×eries{
|
||
|
size: size,
|
||
|
resolution: resolution,
|
||
|
stepCount: 1,
|
||
|
time: initialTime,
|
||
|
slots: make([]int64, size),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// advanceTimeWithFill moves the timeseries forward to time t and fills in any
|
||
|
// slots that get skipped in the process with the given value. Values older than
|
||
|
// the timeseries period are lost.
|
||
|
func (ts *timeseries) advanceTimeWithFill(t time.Time, value int64) {
|
||
|
advanceTo := t.Truncate(ts.resolution)
|
||
|
if !advanceTo.After(ts.time) {
|
||
|
// This is shortcut for the most common case of a busy counter
|
||
|
// where updates come in many times per ts.resolution.
|
||
|
ts.time = advanceTo
|
||
|
return
|
||
|
}
|
||
|
steps := int(advanceTo.Sub(ts.time).Nanoseconds() / ts.resolution.Nanoseconds())
|
||
|
ts.stepCount += int64(steps)
|
||
|
if steps > ts.size {
|
||
|
steps = ts.size
|
||
|
}
|
||
|
for steps > 0 {
|
||
|
ts.head = (ts.head + 1) % ts.size
|
||
|
ts.slots[ts.head] = value
|
||
|
steps--
|
||
|
}
|
||
|
ts.time = advanceTo
|
||
|
}
|
||
|
|
||
|
// advanceTime moves the timeseries forward to time t and fills in any slots
|
||
|
// that get skipped in the process with the head value. Values older than the
|
||
|
// timeseries period are lost.
|
||
|
func (ts *timeseries) advanceTime(t time.Time) {
|
||
|
ts.advanceTimeWithFill(t, ts.slots[ts.head])
|
||
|
}
|
||
|
|
||
|
// set sets the current value of the timeseries.
|
||
|
func (ts *timeseries) set(value int64) {
|
||
|
ts.slots[ts.head] = value
|
||
|
}
|
||
|
|
||
|
// incr sets the current value of the timeseries.
|
||
|
func (ts *timeseries) incr(delta int64) {
|
||
|
ts.slots[ts.head] += delta
|
||
|
}
|
||
|
|
||
|
// headValue returns the latest value from the timeseries.
|
||
|
func (ts *timeseries) headValue() int64 {
|
||
|
return ts.slots[ts.head]
|
||
|
}
|
||
|
|
||
|
// headTime returns the time of the latest value from the timeseries.
|
||
|
func (ts *timeseries) headTime() time.Time {
|
||
|
return ts.time
|
||
|
}
|
||
|
|
||
|
// tailValue returns the oldest value from the timeseries.
|
||
|
func (ts *timeseries) tailValue() int64 {
|
||
|
if ts.stepCount < int64(ts.size) {
|
||
|
return 0
|
||
|
}
|
||
|
return ts.slots[(ts.head+1)%ts.size]
|
||
|
}
|
||
|
|
||
|
// tailTime returns the time of the oldest value from the timeseries.
|
||
|
func (ts *timeseries) tailTime() time.Time {
|
||
|
size := int64(ts.size)
|
||
|
if ts.stepCount < size {
|
||
|
size = ts.stepCount
|
||
|
}
|
||
|
return ts.time.Add(-time.Duration(size-1) * ts.resolution)
|
||
|
}
|
||
|
|
||
|
// delta returns the difference between the newest and oldest values from the
|
||
|
// timeseries.
|
||
|
func (ts *timeseries) delta() int64 {
|
||
|
return ts.headValue() - ts.tailValue()
|
||
|
}
|
||
|
|
||
|
// rate returns the rate of change between the oldest and newest values from
|
||
|
// the timeseries in units per second.
|
||
|
func (ts *timeseries) rate() float64 {
|
||
|
deltaTime := ts.headTime().Sub(ts.tailTime()).Seconds()
|
||
|
if deltaTime == 0 {
|
||
|
return 0
|
||
|
}
|
||
|
return float64(ts.delta()) / deltaTime
|
||
|
}
|
||
|
|
||
|
// min returns the smallest value from the timeseries.
|
||
|
func (ts *timeseries) min() int64 {
|
||
|
to := ts.size
|
||
|
if ts.stepCount < int64(ts.size) {
|
||
|
to = ts.head + 1
|
||
|
}
|
||
|
tail := (ts.head + 1) % ts.size
|
||
|
min := int64(math.MaxInt64)
|
||
|
for b := 0; b < to; b++ {
|
||
|
if b != tail && ts.slots[b] < min {
|
||
|
min = ts.slots[b]
|
||
|
}
|
||
|
}
|
||
|
return min
|
||
|
}
|
||
|
|
||
|
// max returns the largest value from the timeseries.
|
||
|
func (ts *timeseries) max() int64 {
|
||
|
to := ts.size
|
||
|
if ts.stepCount < int64(ts.size) {
|
||
|
to = ts.head + 1
|
||
|
}
|
||
|
tail := (ts.head + 1) % ts.size
|
||
|
max := int64(math.MinInt64)
|
||
|
for b := 0; b < to; b++ {
|
||
|
if b != tail && ts.slots[b] > max {
|
||
|
max = ts.slots[b]
|
||
|
}
|
||
|
}
|
||
|
return max
|
||
|
}
|
||
|
|
||
|
// reset resets the timeseries to an empty state.
|
||
|
func (ts *timeseries) reset(t time.Time) {
|
||
|
ts.head = 0
|
||
|
ts.time = t
|
||
|
ts.stepCount = 1
|
||
|
ts.slots = make([]int64, ts.size)
|
||
|
}
|