github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/monitor/stats/timeseries.go (about)

     1  /*
     2   *
     3   * Copyright 2017 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  // Copyright (c) 2015 Arista Networks, Inc.
    20  // Use of this source code is governed by the Apache License 2.0
    21  // that can be found in the COPYING file.
    22  
    23  package stats
    24  
    25  import (
    26  	"math"
    27  	"time"
    28  )
    29  
    30  // timeseries holds the history of a changing value over a predefined period of
    31  // time.
    32  type timeseries struct {
    33  	size       int           // The number of time slots. Equivalent to len(slots).
    34  	resolution time.Duration // The time resolution of each slot.
    35  	stepCount  int64         // The number of intervals seen since creation.
    36  	head       int           // The position of the current time in slots.
    37  	time       time.Time     // The time at the beginning of the current time slot.
    38  	slots      []int64       // A circular buffer of time slots.
    39  }
    40  
    41  // newTimeSeries returns a newly allocated timeseries that covers the requested
    42  // period with the given resolution.
    43  func newTimeSeries(initialTime time.Time, period, resolution time.Duration) *timeseries {
    44  	size := int(period.Nanoseconds()/resolution.Nanoseconds()) + 1
    45  	return &timeseries{
    46  		size:       size,
    47  		resolution: resolution,
    48  		stepCount:  1,
    49  		time:       initialTime,
    50  		slots:      make([]int64, size),
    51  	}
    52  }
    53  
    54  // advanceTimeWithFill moves the timeseries forward to time t and fills in any
    55  // slots that get skipped in the process with the given value. Values older than
    56  // the timeseries period are lost.
    57  func (ts *timeseries) advanceTimeWithFill(t time.Time, value int64) {
    58  	advanceTo := t.Truncate(ts.resolution)
    59  	if !advanceTo.After(ts.time) {
    60  		// This is shortcut for the most common case of a busy counter
    61  		// where updates come in many times per ts.resolution.
    62  		ts.time = advanceTo
    63  		return
    64  	}
    65  	steps := int(advanceTo.Sub(ts.time).Nanoseconds() / ts.resolution.Nanoseconds())
    66  	ts.stepCount += int64(steps)
    67  	if steps > ts.size {
    68  		steps = ts.size
    69  	}
    70  	for steps > 0 {
    71  		ts.head = (ts.head + 1) % ts.size
    72  		ts.slots[ts.head] = value
    73  		steps--
    74  	}
    75  	ts.time = advanceTo
    76  }
    77  
    78  // advanceTime moves the timeseries forward to time t and fills in any slots
    79  // that get skipped in the process with the head value. Values older than the
    80  // timeseries period are lost.
    81  func (ts *timeseries) advanceTime(t time.Time) {
    82  	ts.advanceTimeWithFill(t, ts.slots[ts.head])
    83  }
    84  
    85  // set sets the current value of the timeseries.
    86  func (ts *timeseries) set(value int64) {
    87  	ts.slots[ts.head] = value
    88  }
    89  
    90  // incr sets the current value of the timeseries.
    91  func (ts *timeseries) incr(delta int64) {
    92  	ts.slots[ts.head] += delta
    93  }
    94  
    95  // headValue returns the latest value from the timeseries.
    96  func (ts *timeseries) headValue() int64 {
    97  	return ts.slots[ts.head]
    98  }
    99  
   100  // headTime returns the time of the latest value from the timeseries.
   101  func (ts *timeseries) headTime() time.Time {
   102  	return ts.time
   103  }
   104  
   105  // tailValue returns the oldest value from the timeseries.
   106  func (ts *timeseries) tailValue() int64 {
   107  	if ts.stepCount < int64(ts.size) {
   108  		return 0
   109  	}
   110  	return ts.slots[(ts.head+1)%ts.size]
   111  }
   112  
   113  // tailTime returns the time of the oldest value from the timeseries.
   114  func (ts *timeseries) tailTime() time.Time {
   115  	size := int64(ts.size)
   116  	if ts.stepCount < size {
   117  		size = ts.stepCount
   118  	}
   119  	return ts.time.Add(-time.Duration(size-1) * ts.resolution)
   120  }
   121  
   122  // delta returns the difference between the newest and oldest values from the
   123  // timeseries.
   124  func (ts *timeseries) delta() int64 {
   125  	return ts.headValue() - ts.tailValue()
   126  }
   127  
   128  // rate returns the rate of change between the oldest and newest values from
   129  // the timeseries in units per second.
   130  func (ts *timeseries) rate() float64 {
   131  	deltaTime := ts.headTime().Sub(ts.tailTime()).Seconds()
   132  	if deltaTime == 0 {
   133  		return 0
   134  	}
   135  	return float64(ts.delta()) / deltaTime
   136  }
   137  
   138  // min returns the smallest value from the timeseries.
   139  func (ts *timeseries) min() int64 {
   140  	to := ts.size
   141  	if ts.stepCount < int64(ts.size) {
   142  		to = ts.head + 1
   143  	}
   144  	tail := (ts.head + 1) % ts.size
   145  	min := int64(math.MaxInt64)
   146  	for b := 0; b < to; b++ {
   147  		if b != tail && ts.slots[b] < min {
   148  			min = ts.slots[b]
   149  		}
   150  	}
   151  	return min
   152  }
   153  
   154  // max returns the largest value from the timeseries.
   155  func (ts *timeseries) max() int64 {
   156  	to := ts.size
   157  	if ts.stepCount < int64(ts.size) {
   158  		to = ts.head + 1
   159  	}
   160  	tail := (ts.head + 1) % ts.size
   161  	max := int64(math.MinInt64)
   162  	for b := 0; b < to; b++ {
   163  		if b != tail && ts.slots[b] > max {
   164  			max = ts.slots[b]
   165  		}
   166  	}
   167  	return max
   168  }
   169  
   170  // reset resets the timeseries to an empty state.
   171  func (ts *timeseries) reset(t time.Time) {
   172  	ts.head = 0
   173  	ts.time = t
   174  	ts.stepCount = 1
   175  	ts.slots = make([]int64, ts.size)
   176  }