...

Source file src/golang.conradwood.net/go-easyops/utils/sliding_average.go

Documentation: golang.conradwood.net/go-easyops/utils

     1  package utils
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  )
     9  
    10  const (
    11  	COUNTERS = 10
    12  )
    13  
    14  var (
    15  	sliding_avg_debug = flag.Bool("ge_debug_sliding_average", false, "debug sliding avg code")
    16  )
    17  
    18  /*
    19  it is often useful to take the most recent period of time average, for example last minute average.
    20  however, it is useful to double-buffer this so that there is always a full sample available.
    21  that is where this struct helps.
    22  That is, it is guaranteed that the average is always calculated over at least MinAge. Periodically old average numbers are "dropped", so the average is also reflective of fresh values.
    23  
    24  The InitialAge value may be set to start providing averages faster than MinAge upon startup.
    25  */
    26  type SlidingAverage struct {
    27  	lock                sync.Mutex
    28  	calc1               *sacalc
    29  	calc2               *sacalc
    30  	InitialAge          time.Duration // time to wait before providing the first averages (after startup)
    31  	MinAge              time.Duration // minimum age before a counter is valid
    32  	MinSamples          uint64        // minimum number of samples before a counter is valid
    33  	updating_number_one bool          // if true updates calc1, otherwise calc2
    34  	created             time.Time
    35  	created_via_new     bool // helper to detect instantiation via New()
    36  	switched            bool // true if switched at least once
    37  }
    38  
    39  func NewSlidingAverage() *SlidingAverage {
    40  	res := &SlidingAverage{
    41  		InitialAge:      time.Duration(24) * time.Hour,
    42  		created_via_new: true,
    43  		created:         time.Now(),
    44  		MinAge:          time.Duration(10) * time.Second,
    45  		MinSamples:      10,
    46  	}
    47  	return res
    48  }
    49  
    50  // a tracker of an average
    51  type sacalc struct {
    52  	debug_name string
    53  	is_fresh   bool // reset each time it is modified and set when it is cleared or new
    54  	started    time.Time
    55  	counts     []uint64
    56  	counter    []uint64
    57  }
    58  
    59  func new_sacalc(debug_name string) *sacalc {
    60  	res := &sacalc{
    61  		debug_name: debug_name,
    62  		is_fresh:   true,
    63  		counts:     make([]uint64, COUNTERS),
    64  		counter:    make([]uint64, COUNTERS),
    65  	}
    66  	return res
    67  }
    68  
    69  /**************************** double-buffering stuff ******************************/
    70  // if the to_be_updated one meets criteria, then switch it to be the read one and make the other buffer updateable
    71  func (sa *SlidingAverage) check_for_switch() {
    72  	up := sa.to_be_updated()
    73  	rd := sa.to_be_read()
    74  	if sa.meetsCriteria(up) {
    75  		sa.updating_number_one = !sa.updating_number_one
    76  		sa.switched = true
    77  		if rd != nil {
    78  			rd.make_fresh()
    79  		}
    80  	}
    81  }
    82  func (sa *SlidingAverage) meetsCriteria(sc *sacalc) bool {
    83  	if time.Since(sc.started) < sa.MinAge {
    84  		return false
    85  	}
    86  	samples := uint64(0)
    87  	for i := 0; i < COUNTERS; i++ {
    88  		samples = samples + sc.counts[i]
    89  	}
    90  	if samples < sa.MinSamples {
    91  		return false
    92  	}
    93  
    94  	return true
    95  }
    96  func (sc *sacalc) make_fresh() {
    97  	for i := 0; i < COUNTERS; i++ {
    98  		sc.counts[i] = 0
    99  		sc.counter[i] = 0
   100  	}
   101  	sc.is_fresh = true
   102  }
   103  
   104  /**************************** update counter stuff ******************************/
   105  func (sa *SlidingAverage) to_be_updated() *sacalc {
   106  	if sa.updating_number_one {
   107  		if sa.calc1 == nil {
   108  			sa.calc1 = new_sacalc("sacalc-1")
   109  		}
   110  		return sa.calc1
   111  	}
   112  	if sa.calc2 == nil {
   113  		sa.calc2 = new_sacalc("sacalc-2")
   114  	}
   115  	return sa.calc2
   116  }
   117  func (sa *SlidingAverage) to_be_read() *sacalc {
   118  	var alt_res *sacalc
   119  	// got at least one full buf
   120  	if sa.updating_number_one {
   121  		alt_res = sa.calc2
   122  	} else {
   123  		alt_res = sa.calc1
   124  	}
   125  	if sa.switched || time.Since(sa.created) < sa.InitialAge {
   126  		return alt_res
   127  	}
   128  	if sa.calc1 == nil && sa.calc2 != nil {
   129  		return sa.calc2
   130  	}
   131  	if sa.calc2 == nil && sa.calc1 != nil {
   132  		return sa.calc1
   133  	}
   134  	if sa.calc1 == nil && sa.calc2 == nil {
   135  		return nil
   136  	}
   137  
   138  	if sa.calc1.is_fresh && !sa.calc2.is_fresh {
   139  		alt_res = sa.calc2
   140  	}
   141  
   142  	if sa.calc2.is_fresh && !sa.calc1.is_fresh {
   143  		alt_res = sa.calc1
   144  	}
   145  
   146  	return alt_res
   147  
   148  }
   149  
   150  // get number of counts
   151  func (sa *SlidingAverage) GetCounts(counter int) uint64 {
   152  	if !sa.created_via_new {
   153  		panic("[go-easyops] SlidingAverage must be created with function NewSlidingAverage()")
   154  	}
   155  	sa.lock.Lock()
   156  	defer sa.lock.Unlock()
   157  	sc := sa.to_be_read()
   158  	if sc == nil {
   159  		return 0
   160  	}
   161  	return sc.getCounts(counter)
   162  }
   163  
   164  // get a counter
   165  func (sa *SlidingAverage) GetCounter(counter int) uint64 {
   166  	if !sa.created_via_new {
   167  		panic("[go-easyops] SlidingAverage must be created with function NewSlidingAverage()")
   168  	}
   169  	sa.lock.Lock()
   170  	defer sa.lock.Unlock()
   171  	sc := sa.to_be_read()
   172  	if sc == nil {
   173  		return 0
   174  	}
   175  	return sc.getCounter(counter)
   176  }
   177  func (sa *SlidingAverage) GetAverage(counter int) float64 {
   178  	num := sa.GetCounter(counter)
   179  	counts := sa.GetCounts(counter)
   180  	if counts == 0 || num == 0 {
   181  		return 0
   182  	}
   183  	res := float64(num) / float64(counts)
   184  	sa.printf("result: num=%0.1f, counts=%0.1f -> %0.1f\n", num, counts, res)
   185  	return res
   186  }
   187  
   188  // per second added rate
   189  func (sa *SlidingAverage) GetRate(counter int) float64 {
   190  	sa.lock.Lock()
   191  	defer sa.lock.Unlock()
   192  	sc := sa.to_be_read()
   193  	if sc == nil {
   194  		return 0.0
   195  	}
   196  	num := float64(sc.getCounter(counter))
   197  	secs := time.Since(sc.started).Seconds()
   198  	if num == 0 || secs == 0 {
   199  		return 0.0
   200  	}
   201  	res := num / secs
   202  	return res
   203  }
   204  
   205  func (sa *SlidingAverage) Add(counter int, a uint64) {
   206  	if !sa.created_via_new {
   207  		panic("[go-easyops] SlidingAverage must be created with function NewSlidingAverage()")
   208  	}
   209  	sa.lock.Lock()
   210  	defer sa.lock.Unlock()
   211  	sa.to_be_updated().Add(counter, a)
   212  	sa.check_for_switch()
   213  
   214  }
   215  
   216  func (sc *sacalc) Add(counter int, a uint64) {
   217  	sc.counts[counter]++
   218  	sc.counter[counter] = sc.counter[counter] + a
   219  	if sc.is_fresh {
   220  		sc.started = time.Now()
   221  		sc.is_fresh = false
   222  	}
   223  	sc.Printf("added: %d to counter #%d, now %d\n", a, counter, sc.counter[counter])
   224  }
   225  func (sc *sacalc) getCounter(counter int) uint64 {
   226  	if sc == nil {
   227  		return 0
   228  	}
   229  	return sc.counter[counter]
   230  }
   231  func (sc *sacalc) getCounts(counter int) uint64 {
   232  	if sc == nil {
   233  		return 0
   234  	}
   235  	return sc.counts[counter]
   236  }
   237  
   238  func (sc *sacalc) Printf(format string, args ...interface{}) {
   239  	if !*sliding_avg_debug {
   240  		return
   241  	}
   242  	s := "[go-easyops " + sc.debug_name + "] " + format
   243  
   244  	fmt.Printf(s, args...)
   245  }
   246  func (sa *SlidingAverage) printf(format string, args ...interface{}) {
   247  	if !*sliding_avg_debug {
   248  		return
   249  	}
   250  	s := "[go-easyops slidingavg] " + format
   251  	fmt.Printf(s, args...)
   252  }
   253  

View as plain text