golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/internal/timeseries/timeseries.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package timeseries implements a time series structure for stats collection.
     6  package timeseries // import "golang.org/x/net/internal/timeseries"
     7  
     8  import (
     9  	"fmt"
    10  	"log"
    11  	"time"
    12  )
    13  
    14  const (
    15  	timeSeriesNumBuckets       = 64
    16  	minuteHourSeriesNumBuckets = 60
    17  )
    18  
    19  var timeSeriesResolutions = []time.Duration{
    20  	1 * time.Second,
    21  	10 * time.Second,
    22  	1 * time.Minute,
    23  	10 * time.Minute,
    24  	1 * time.Hour,
    25  	6 * time.Hour,
    26  	24 * time.Hour,          // 1 day
    27  	7 * 24 * time.Hour,      // 1 week
    28  	4 * 7 * 24 * time.Hour,  // 4 weeks
    29  	16 * 7 * 24 * time.Hour, // 16 weeks
    30  }
    31  
    32  var minuteHourSeriesResolutions = []time.Duration{
    33  	1 * time.Second,
    34  	1 * time.Minute,
    35  }
    36  
    37  // An Observable is a kind of data that can be aggregated in a time series.
    38  type Observable interface {
    39  	Multiply(ratio float64)    // Multiplies the data in self by a given ratio
    40  	Add(other Observable)      // Adds the data from a different observation to self
    41  	Clear()                    // Clears the observation so it can be reused.
    42  	CopyFrom(other Observable) // Copies the contents of a given observation to self
    43  }
    44  
    45  // Float attaches the methods of Observable to a float64.
    46  type Float float64
    47  
    48  // NewFloat returns a Float.
    49  func NewFloat() Observable {
    50  	f := Float(0)
    51  	return &f
    52  }
    53  
    54  // String returns the float as a string.
    55  func (f *Float) String() string { return fmt.Sprintf("%g", f.Value()) }
    56  
    57  // Value returns the float's value.
    58  func (f *Float) Value() float64 { return float64(*f) }
    59  
    60  func (f *Float) Multiply(ratio float64) { *f *= Float(ratio) }
    61  
    62  func (f *Float) Add(other Observable) {
    63  	o := other.(*Float)
    64  	*f += *o
    65  }
    66  
    67  func (f *Float) Clear() { *f = 0 }
    68  
    69  func (f *Float) CopyFrom(other Observable) {
    70  	o := other.(*Float)
    71  	*f = *o
    72  }
    73  
    74  // A Clock tells the current time.
    75  type Clock interface {
    76  	Time() time.Time
    77  }
    78  
    79  type defaultClock int
    80  
    81  var defaultClockInstance defaultClock
    82  
    83  func (defaultClock) Time() time.Time { return time.Now() }
    84  
    85  // Information kept per level. Each level consists of a circular list of
    86  // observations. The start of the level may be derived from end and the
    87  // len(buckets) * sizeInMillis.
    88  type tsLevel struct {
    89  	oldest   int               // index to oldest bucketed Observable
    90  	newest   int               // index to newest bucketed Observable
    91  	end      time.Time         // end timestamp for this level
    92  	size     time.Duration     // duration of the bucketed Observable
    93  	buckets  []Observable      // collections of observations
    94  	provider func() Observable // used for creating new Observable
    95  }
    96  
    97  func (l *tsLevel) Clear() {
    98  	l.oldest = 0
    99  	l.newest = len(l.buckets) - 1
   100  	l.end = time.Time{}
   101  	for i := range l.buckets {
   102  		if l.buckets[i] != nil {
   103  			l.buckets[i].Clear()
   104  			l.buckets[i] = nil
   105  		}
   106  	}
   107  }
   108  
   109  func (l *tsLevel) InitLevel(size time.Duration, numBuckets int, f func() Observable) {
   110  	l.size = size
   111  	l.provider = f
   112  	l.buckets = make([]Observable, numBuckets)
   113  }
   114  
   115  // Keeps a sequence of levels. Each level is responsible for storing data at
   116  // a given resolution. For example, the first level stores data at a one
   117  // minute resolution while the second level stores data at a one hour
   118  // resolution.
   119  
   120  // Each level is represented by a sequence of buckets. Each bucket spans an
   121  // interval equal to the resolution of the level. New observations are added
   122  // to the last bucket.
   123  type timeSeries struct {
   124  	provider    func() Observable // make more Observable
   125  	numBuckets  int               // number of buckets in each level
   126  	levels      []*tsLevel        // levels of bucketed Observable
   127  	lastAdd     time.Time         // time of last Observable tracked
   128  	total       Observable        // convenient aggregation of all Observable
   129  	clock       Clock             // Clock for getting current time
   130  	pending     Observable        // observations not yet bucketed
   131  	pendingTime time.Time         // what time are we keeping in pending
   132  	dirty       bool              // if there are pending observations
   133  }
   134  
   135  // init initializes a level according to the supplied criteria.
   136  func (ts *timeSeries) init(resolutions []time.Duration, f func() Observable, numBuckets int, clock Clock) {
   137  	ts.provider = f
   138  	ts.numBuckets = numBuckets
   139  	ts.clock = clock
   140  	ts.levels = make([]*tsLevel, len(resolutions))
   141  
   142  	for i := range resolutions {
   143  		if i > 0 && resolutions[i-1] >= resolutions[i] {
   144  			log.Print("timeseries: resolutions must be monotonically increasing")
   145  			break
   146  		}
   147  		newLevel := new(tsLevel)
   148  		newLevel.InitLevel(resolutions[i], ts.numBuckets, ts.provider)
   149  		ts.levels[i] = newLevel
   150  	}
   151  
   152  	ts.Clear()
   153  }
   154  
   155  // Clear removes all observations from the time series.
   156  func (ts *timeSeries) Clear() {
   157  	ts.lastAdd = time.Time{}
   158  	ts.total = ts.resetObservation(ts.total)
   159  	ts.pending = ts.resetObservation(ts.pending)
   160  	ts.pendingTime = time.Time{}
   161  	ts.dirty = false
   162  
   163  	for i := range ts.levels {
   164  		ts.levels[i].Clear()
   165  	}
   166  }
   167  
   168  // Add records an observation at the current time.
   169  func (ts *timeSeries) Add(observation Observable) {
   170  	ts.AddWithTime(observation, ts.clock.Time())
   171  }
   172  
   173  // AddWithTime records an observation at the specified time.
   174  func (ts *timeSeries) AddWithTime(observation Observable, t time.Time) {
   175  
   176  	smallBucketDuration := ts.levels[0].size
   177  
   178  	if t.After(ts.lastAdd) {
   179  		ts.lastAdd = t
   180  	}
   181  
   182  	if t.After(ts.pendingTime) {
   183  		ts.advance(t)
   184  		ts.mergePendingUpdates()
   185  		ts.pendingTime = ts.levels[0].end
   186  		ts.pending.CopyFrom(observation)
   187  		ts.dirty = true
   188  	} else if t.After(ts.pendingTime.Add(-1 * smallBucketDuration)) {
   189  		// The observation is close enough to go into the pending bucket.
   190  		// This compensates for clock skewing and small scheduling delays
   191  		// by letting the update stay in the fast path.
   192  		ts.pending.Add(observation)
   193  		ts.dirty = true
   194  	} else {
   195  		ts.mergeValue(observation, t)
   196  	}
   197  }
   198  
   199  // mergeValue inserts the observation at the specified time in the past into all levels.
   200  func (ts *timeSeries) mergeValue(observation Observable, t time.Time) {
   201  	for _, level := range ts.levels {
   202  		index := (ts.numBuckets - 1) - int(level.end.Sub(t)/level.size)
   203  		if 0 <= index && index < ts.numBuckets {
   204  			bucketNumber := (level.oldest + index) % ts.numBuckets
   205  			if level.buckets[bucketNumber] == nil {
   206  				level.buckets[bucketNumber] = level.provider()
   207  			}
   208  			level.buckets[bucketNumber].Add(observation)
   209  		}
   210  	}
   211  	ts.total.Add(observation)
   212  }
   213  
   214  // mergePendingUpdates applies the pending updates into all levels.
   215  func (ts *timeSeries) mergePendingUpdates() {
   216  	if ts.dirty {
   217  		ts.mergeValue(ts.pending, ts.pendingTime)
   218  		ts.pending = ts.resetObservation(ts.pending)
   219  		ts.dirty = false
   220  	}
   221  }
   222  
   223  // advance cycles the buckets at each level until the latest bucket in
   224  // each level can hold the time specified.
   225  func (ts *timeSeries) advance(t time.Time) {
   226  	if !t.After(ts.levels[0].end) {
   227  		return
   228  	}
   229  	for i := 0; i < len(ts.levels); i++ {
   230  		level := ts.levels[i]
   231  		if !level.end.Before(t) {
   232  			break
   233  		}
   234  
   235  		// If the time is sufficiently far, just clear the level and advance
   236  		// directly.
   237  		if !t.Before(level.end.Add(level.size * time.Duration(ts.numBuckets))) {
   238  			for _, b := range level.buckets {
   239  				ts.resetObservation(b)
   240  			}
   241  			level.end = time.Unix(0, (t.UnixNano()/level.size.Nanoseconds())*level.size.Nanoseconds())
   242  		}
   243  
   244  		for t.After(level.end) {
   245  			level.end = level.end.Add(level.size)
   246  			level.newest = level.oldest
   247  			level.oldest = (level.oldest + 1) % ts.numBuckets
   248  			ts.resetObservation(level.buckets[level.newest])
   249  		}
   250  
   251  		t = level.end
   252  	}
   253  }
   254  
   255  // Latest returns the sum of the num latest buckets from the level.
   256  func (ts *timeSeries) Latest(level, num int) Observable {
   257  	now := ts.clock.Time()
   258  	if ts.levels[0].end.Before(now) {
   259  		ts.advance(now)
   260  	}
   261  
   262  	ts.mergePendingUpdates()
   263  
   264  	result := ts.provider()
   265  	l := ts.levels[level]
   266  	index := l.newest
   267  
   268  	for i := 0; i < num; i++ {
   269  		if l.buckets[index] != nil {
   270  			result.Add(l.buckets[index])
   271  		}
   272  		if index == 0 {
   273  			index = ts.numBuckets
   274  		}
   275  		index--
   276  	}
   277  
   278  	return result
   279  }
   280  
   281  // LatestBuckets returns a copy of the num latest buckets from level.
   282  func (ts *timeSeries) LatestBuckets(level, num int) []Observable {
   283  	if level < 0 || level > len(ts.levels) {
   284  		log.Print("timeseries: bad level argument: ", level)
   285  		return nil
   286  	}
   287  	if num < 0 || num >= ts.numBuckets {
   288  		log.Print("timeseries: bad num argument: ", num)
   289  		return nil
   290  	}
   291  
   292  	results := make([]Observable, num)
   293  	now := ts.clock.Time()
   294  	if ts.levels[0].end.Before(now) {
   295  		ts.advance(now)
   296  	}
   297  
   298  	ts.mergePendingUpdates()
   299  
   300  	l := ts.levels[level]
   301  	index := l.newest
   302  
   303  	for i := 0; i < num; i++ {
   304  		result := ts.provider()
   305  		results[i] = result
   306  		if l.buckets[index] != nil {
   307  			result.CopyFrom(l.buckets[index])
   308  		}
   309  
   310  		if index == 0 {
   311  			index = ts.numBuckets
   312  		}
   313  		index -= 1
   314  	}
   315  	return results
   316  }
   317  
   318  // ScaleBy updates observations by scaling by factor.
   319  func (ts *timeSeries) ScaleBy(factor float64) {
   320  	for _, l := range ts.levels {
   321  		for i := 0; i < ts.numBuckets; i++ {
   322  			l.buckets[i].Multiply(factor)
   323  		}
   324  	}
   325  
   326  	ts.total.Multiply(factor)
   327  	ts.pending.Multiply(factor)
   328  }
   329  
   330  // Range returns the sum of observations added over the specified time range.
   331  // If start or finish times don't fall on bucket boundaries of the same
   332  // level, then return values are approximate answers.
   333  func (ts *timeSeries) Range(start, finish time.Time) Observable {
   334  	return ts.ComputeRange(start, finish, 1)[0]
   335  }
   336  
   337  // Recent returns the sum of observations from the last delta.
   338  func (ts *timeSeries) Recent(delta time.Duration) Observable {
   339  	now := ts.clock.Time()
   340  	return ts.Range(now.Add(-delta), now)
   341  }
   342  
   343  // Total returns the total of all observations.
   344  func (ts *timeSeries) Total() Observable {
   345  	ts.mergePendingUpdates()
   346  	return ts.total
   347  }
   348  
   349  // ComputeRange computes a specified number of values into a slice using
   350  // the observations recorded over the specified time period. The return
   351  // values are approximate if the start or finish times don't fall on the
   352  // bucket boundaries at the same level or if the number of buckets spanning
   353  // the range is not an integral multiple of num.
   354  func (ts *timeSeries) ComputeRange(start, finish time.Time, num int) []Observable {
   355  	if start.After(finish) {
   356  		log.Printf("timeseries: start > finish, %v>%v", start, finish)
   357  		return nil
   358  	}
   359  
   360  	if num < 0 {
   361  		log.Printf("timeseries: num < 0, %v", num)
   362  		return nil
   363  	}
   364  
   365  	results := make([]Observable, num)
   366  
   367  	for _, l := range ts.levels {
   368  		if !start.Before(l.end.Add(-l.size * time.Duration(ts.numBuckets))) {
   369  			ts.extract(l, start, finish, num, results)
   370  			return results
   371  		}
   372  	}
   373  
   374  	// Failed to find a level that covers the desired range. So just
   375  	// extract from the last level, even if it doesn't cover the entire
   376  	// desired range.
   377  	ts.extract(ts.levels[len(ts.levels)-1], start, finish, num, results)
   378  
   379  	return results
   380  }
   381  
   382  // RecentList returns the specified number of values in slice over the most
   383  // recent time period of the specified range.
   384  func (ts *timeSeries) RecentList(delta time.Duration, num int) []Observable {
   385  	if delta < 0 {
   386  		return nil
   387  	}
   388  	now := ts.clock.Time()
   389  	return ts.ComputeRange(now.Add(-delta), now, num)
   390  }
   391  
   392  // extract returns a slice of specified number of observations from a given
   393  // level over a given range.
   394  func (ts *timeSeries) extract(l *tsLevel, start, finish time.Time, num int, results []Observable) {
   395  	ts.mergePendingUpdates()
   396  
   397  	srcInterval := l.size
   398  	dstInterval := finish.Sub(start) / time.Duration(num)
   399  	dstStart := start
   400  	srcStart := l.end.Add(-srcInterval * time.Duration(ts.numBuckets))
   401  
   402  	srcIndex := 0
   403  
   404  	// Where should scanning start?
   405  	if dstStart.After(srcStart) {
   406  		advance := int(dstStart.Sub(srcStart) / srcInterval)
   407  		srcIndex += advance
   408  		srcStart = srcStart.Add(time.Duration(advance) * srcInterval)
   409  	}
   410  
   411  	// The i'th value is computed as show below.
   412  	// interval = (finish/start)/num
   413  	// i'th value = sum of observation in range
   414  	//   [ start + i       * interval,
   415  	//     start + (i + 1) * interval )
   416  	for i := 0; i < num; i++ {
   417  		results[i] = ts.resetObservation(results[i])
   418  		dstEnd := dstStart.Add(dstInterval)
   419  		for srcIndex < ts.numBuckets && srcStart.Before(dstEnd) {
   420  			srcEnd := srcStart.Add(srcInterval)
   421  			if srcEnd.After(ts.lastAdd) {
   422  				srcEnd = ts.lastAdd
   423  			}
   424  
   425  			if !srcEnd.Before(dstStart) {
   426  				srcValue := l.buckets[(srcIndex+l.oldest)%ts.numBuckets]
   427  				if !srcStart.Before(dstStart) && !srcEnd.After(dstEnd) {
   428  					// dst completely contains src.
   429  					if srcValue != nil {
   430  						results[i].Add(srcValue)
   431  					}
   432  				} else {
   433  					// dst partially overlaps src.
   434  					overlapStart := maxTime(srcStart, dstStart)
   435  					overlapEnd := minTime(srcEnd, dstEnd)
   436  					base := srcEnd.Sub(srcStart)
   437  					fraction := overlapEnd.Sub(overlapStart).Seconds() / base.Seconds()
   438  
   439  					used := ts.provider()
   440  					if srcValue != nil {
   441  						used.CopyFrom(srcValue)
   442  					}
   443  					used.Multiply(fraction)
   444  					results[i].Add(used)
   445  				}
   446  
   447  				if srcEnd.After(dstEnd) {
   448  					break
   449  				}
   450  			}
   451  			srcIndex++
   452  			srcStart = srcStart.Add(srcInterval)
   453  		}
   454  		dstStart = dstStart.Add(dstInterval)
   455  	}
   456  }
   457  
   458  // resetObservation clears the content so the struct may be reused.
   459  func (ts *timeSeries) resetObservation(observation Observable) Observable {
   460  	if observation == nil {
   461  		observation = ts.provider()
   462  	} else {
   463  		observation.Clear()
   464  	}
   465  	return observation
   466  }
   467  
   468  // TimeSeries tracks data at granularities from 1 second to 16 weeks.
   469  type TimeSeries struct {
   470  	timeSeries
   471  }
   472  
   473  // NewTimeSeries creates a new TimeSeries using the function provided for creating new Observable.
   474  func NewTimeSeries(f func() Observable) *TimeSeries {
   475  	return NewTimeSeriesWithClock(f, defaultClockInstance)
   476  }
   477  
   478  // NewTimeSeriesWithClock creates a new TimeSeries using the function provided for creating new Observable and the clock for
   479  // assigning timestamps.
   480  func NewTimeSeriesWithClock(f func() Observable, clock Clock) *TimeSeries {
   481  	ts := new(TimeSeries)
   482  	ts.timeSeries.init(timeSeriesResolutions, f, timeSeriesNumBuckets, clock)
   483  	return ts
   484  }
   485  
   486  // MinuteHourSeries tracks data at granularities of 1 minute and 1 hour.
   487  type MinuteHourSeries struct {
   488  	timeSeries
   489  }
   490  
   491  // NewMinuteHourSeries creates a new MinuteHourSeries using the function provided for creating new Observable.
   492  func NewMinuteHourSeries(f func() Observable) *MinuteHourSeries {
   493  	return NewMinuteHourSeriesWithClock(f, defaultClockInstance)
   494  }
   495  
   496  // NewMinuteHourSeriesWithClock creates a new MinuteHourSeries using the function provided for creating new Observable and the clock for
   497  // assigning timestamps.
   498  func NewMinuteHourSeriesWithClock(f func() Observable, clock Clock) *MinuteHourSeries {
   499  	ts := new(MinuteHourSeries)
   500  	ts.timeSeries.init(minuteHourSeriesResolutions, f,
   501  		minuteHourSeriesNumBuckets, clock)
   502  	return ts
   503  }
   504  
   505  func (ts *MinuteHourSeries) Minute() Observable {
   506  	return ts.timeSeries.Latest(0, 60)
   507  }
   508  
   509  func (ts *MinuteHourSeries) Hour() Observable {
   510  	return ts.timeSeries.Latest(1, 60)
   511  }
   512  
   513  func minTime(a, b time.Time) time.Time {
   514  	if a.Before(b) {
   515  		return a
   516  	}
   517  	return b
   518  }
   519  
   520  func maxTime(a, b time.Time) time.Time {
   521  	if a.After(b) {
   522  		return a
   523  	}
   524  	return b
   525  }