github.com/Psiphon-Labs/goarista@v0.0.0-20160825065156-d002785f4c67/monitor/stats/timeseries.go (about)

     1  package stats
     2  
     3  import (
     4  	"math"
     5  	"time"
     6  )
     7  
     8  // timeseries holds the history of a changing value over a predefined period of
     9  // time.
    10  type timeseries struct {
    11  	size       int           // The number of time slots. Equivalent to len(slots).
    12  	resolution time.Duration // The time resolution of each slot.
    13  	stepCount  int64         // The number of intervals seen since creation.
    14  	head       int           // The position of the current time in slots.
    15  	time       time.Time     // The time at the beginning of the current time slot.
    16  	slots      []int64       // A circular buffer of time slots.
    17  }
    18  
    19  // newTimeSeries returns a newly allocated timeseries that covers the requested
    20  // period with the given resolution.
    21  func newTimeSeries(initialTime time.Time, period, resolution time.Duration) *timeseries {
    22  	size := int(period.Nanoseconds()/resolution.Nanoseconds()) + 1
    23  	return &timeseries{
    24  		size:       size,
    25  		resolution: resolution,
    26  		stepCount:  1,
    27  		time:       initialTime,
    28  		slots:      make([]int64, size),
    29  	}
    30  }
    31  
    32  // advanceTimeWithFill moves the timeseries forward to time t and fills in any
    33  // slots that get skipped in the process with the given value. Values older than
    34  // the timeseries period are lost.
    35  func (ts *timeseries) advanceTimeWithFill(t time.Time, value int64) {
    36  	advanceTo := t.Truncate(ts.resolution)
    37  	if !advanceTo.After(ts.time) {
    38  		// This is shortcut for the most common case of a busy counter
    39  		// where updates come in many times per ts.resolution.
    40  		ts.time = advanceTo
    41  		return
    42  	}
    43  	steps := int(advanceTo.Sub(ts.time).Nanoseconds() / ts.resolution.Nanoseconds())
    44  	ts.stepCount += int64(steps)
    45  	if steps > ts.size {
    46  		steps = ts.size
    47  	}
    48  	for steps > 0 {
    49  		ts.head = (ts.head + 1) % ts.size
    50  		ts.slots[ts.head] = value
    51  		steps--
    52  	}
    53  	ts.time = advanceTo
    54  }
    55  
    56  // advanceTime moves the timeseries forward to time t and fills in any slots
    57  // that get skipped in the process with the head value. Values older than the
    58  // timeseries period are lost.
    59  func (ts *timeseries) advanceTime(t time.Time) {
    60  	ts.advanceTimeWithFill(t, ts.slots[ts.head])
    61  }
    62  
    63  // set sets the current value of the timeseries.
    64  func (ts *timeseries) set(value int64) {
    65  	ts.slots[ts.head] = value
    66  }
    67  
    68  // incr sets the current value of the timeseries.
    69  func (ts *timeseries) incr(delta int64) {
    70  	ts.slots[ts.head] += delta
    71  }
    72  
    73  // headValue returns the latest value from the timeseries.
    74  func (ts *timeseries) headValue() int64 {
    75  	return ts.slots[ts.head]
    76  }
    77  
    78  // headTime returns the time of the latest value from the timeseries.
    79  func (ts *timeseries) headTime() time.Time {
    80  	return ts.time
    81  }
    82  
    83  // tailValue returns the oldest value from the timeseries.
    84  func (ts *timeseries) tailValue() int64 {
    85  	if ts.stepCount < int64(ts.size) {
    86  		return 0
    87  	}
    88  	return ts.slots[(ts.head+1)%ts.size]
    89  }
    90  
    91  // tailTime returns the time of the oldest value from the timeseries.
    92  func (ts *timeseries) tailTime() time.Time {
    93  	size := int64(ts.size)
    94  	if ts.stepCount < size {
    95  		size = ts.stepCount
    96  	}
    97  	return ts.time.Add(-time.Duration(size-1) * ts.resolution)
    98  }
    99  
   100  // delta returns the difference between the newest and oldest values from the
   101  // timeseries.
   102  func (ts *timeseries) delta() int64 {
   103  	return ts.headValue() - ts.tailValue()
   104  }
   105  
   106  // rate returns the rate of change between the oldest and newest values from
   107  // the timeseries in units per second.
   108  func (ts *timeseries) rate() float64 {
   109  	deltaTime := ts.headTime().Sub(ts.tailTime()).Seconds()
   110  	if deltaTime == 0 {
   111  		return 0
   112  	}
   113  	return float64(ts.delta()) / deltaTime
   114  }
   115  
   116  // min returns the smallest value from the timeseries.
   117  func (ts *timeseries) min() int64 {
   118  	to := ts.size
   119  	if ts.stepCount < int64(ts.size) {
   120  		to = ts.head + 1
   121  	}
   122  	tail := (ts.head + 1) % ts.size
   123  	min := int64(math.MaxInt64)
   124  	for b := 0; b < to; b++ {
   125  		if b != tail && ts.slots[b] < min {
   126  			min = ts.slots[b]
   127  		}
   128  	}
   129  	return min
   130  }
   131  
   132  // max returns the largest value from the timeseries.
   133  func (ts *timeseries) max() int64 {
   134  	to := ts.size
   135  	if ts.stepCount < int64(ts.size) {
   136  		to = ts.head + 1
   137  	}
   138  	tail := (ts.head + 1) % ts.size
   139  	max := int64(math.MinInt64)
   140  	for b := 0; b < to; b++ {
   141  		if b != tail && ts.slots[b] > max {
   142  			max = ts.slots[b]
   143  		}
   144  	}
   145  	return max
   146  }
   147  
   148  // reset resets the timeseries to an empty state.
   149  func (ts *timeseries) reset(t time.Time) {
   150  	ts.head = 0
   151  	ts.time = t
   152  	ts.stepCount = 1
   153  	ts.slots = make([]int64, ts.size)
   154  }