/* * * Copyright 2020 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package adaptive import "time" // lookback implements a moving sum over an int64 timeline. type lookback struct { bins int64 // Number of bins to use for lookback. width time.Duration // Width of each bin. head int64 // Absolute bin index (time * bins / duration) of the current head bin. total int64 // Sum over all the values in buf, within the lookback window behind head. buf []int64 // Ring buffer for keeping track of the sum elements. } // newLookback creates a new lookback for the given duration with a set number // of bins. func newLookback(bins int64, duration time.Duration) *lookback { return &lookback{ bins: bins, width: duration / time.Duration(bins), buf: make([]int64, bins), } } // add is used to increment the lookback sum. func (l *lookback) add(t time.Time, v int64) { pos := l.advance(t) if (l.head - pos) >= l.bins { // Do not increment counters if pos is more than bins behind head. return } l.buf[pos%l.bins] += v l.total += v } // sum returns the sum of the lookback buffer at the given time or head, // whichever is greater. func (l *lookback) sum(t time.Time) int64 { l.advance(t) return l.total } // advance prepares the lookback buffer for calls to add() or sum() at time t. // If head is greater than t then the lookback buffer will be untouched. The // absolute bin index corresponding to t is returned. It will always be less // than or equal to head. func (l *lookback) advance(t time.Time) int64 { ch := l.head // Current head bin index. nh := t.UnixNano() / l.width.Nanoseconds() // New head bin index. if nh <= ch { // Either head unchanged or clock jitter (time has moved backwards). Do // not advance. return nh } jmax := min(l.bins, nh-ch) for j := int64(0); j < jmax; j++ { i := (ch + j + 1) % l.bins l.total -= l.buf[i] l.buf[i] = 0 } l.head = nh return nh } func min(x int64, y int64) int64 { if x < y { return x } return y }