github.com/theQRL/go-zond@v0.1.1/metrics/sample.go (about)

     1  package metrics
     2  
     3  import (
     4  	"math"
     5  	"math/rand"
     6  	"sync"
     7  	"time"
     8  
     9  	"golang.org/x/exp/slices"
    10  )
    11  
    12  const rescaleThreshold = time.Hour
    13  
    14  type SampleSnapshot interface {
    15  	Count() int64
    16  	Max() int64
    17  	Mean() float64
    18  	Min() int64
    19  	Percentile(float64) float64
    20  	Percentiles([]float64) []float64
    21  	Size() int
    22  	StdDev() float64
    23  	Sum() int64
    24  	Variance() float64
    25  }
    26  
    27  // Samples maintain a statistically-significant selection of values from
    28  // a stream.
    29  type Sample interface {
    30  	Snapshot() SampleSnapshot
    31  	Clear()
    32  	Update(int64)
    33  }
    34  
    35  // ExpDecaySample is an exponentially-decaying sample using a forward-decaying
    36  // priority reservoir.  See Cormode et al's "Forward Decay: A Practical Time
    37  // Decay Model for Streaming Systems".
    38  //
    39  // <http://dimacs.rutgers.edu/~graham/pubs/papers/fwddecay.pdf>
    40  type ExpDecaySample struct {
    41  	alpha         float64
    42  	count         int64
    43  	mutex         sync.Mutex
    44  	reservoirSize int
    45  	t0, t1        time.Time
    46  	values        *expDecaySampleHeap
    47  	rand          *rand.Rand
    48  }
    49  
    50  // NewExpDecaySample constructs a new exponentially-decaying sample with the
    51  // given reservoir size and alpha.
    52  func NewExpDecaySample(reservoirSize int, alpha float64) Sample {
    53  	if !Enabled {
    54  		return NilSample{}
    55  	}
    56  	s := &ExpDecaySample{
    57  		alpha:         alpha,
    58  		reservoirSize: reservoirSize,
    59  		t0:            time.Now(),
    60  		values:        newExpDecaySampleHeap(reservoirSize),
    61  	}
    62  	s.t1 = s.t0.Add(rescaleThreshold)
    63  	return s
    64  }
    65  
    66  // SetRand sets the random source (useful in tests)
    67  func (s *ExpDecaySample) SetRand(prng *rand.Rand) Sample {
    68  	s.rand = prng
    69  	return s
    70  }
    71  
    72  // Clear clears all samples.
    73  func (s *ExpDecaySample) Clear() {
    74  	s.mutex.Lock()
    75  	defer s.mutex.Unlock()
    76  	s.count = 0
    77  	s.t0 = time.Now()
    78  	s.t1 = s.t0.Add(rescaleThreshold)
    79  	s.values.Clear()
    80  }
    81  
    82  // Snapshot returns a read-only copy of the sample.
    83  func (s *ExpDecaySample) Snapshot() SampleSnapshot {
    84  	s.mutex.Lock()
    85  	defer s.mutex.Unlock()
    86  	var (
    87  		samples       = s.values.Values()
    88  		values        = make([]int64, len(samples))
    89  		max     int64 = math.MinInt64
    90  		min     int64 = math.MaxInt64
    91  		sum     int64
    92  	)
    93  	for i, item := range samples {
    94  		v := item.v
    95  		values[i] = v
    96  		sum += v
    97  		if v > max {
    98  			max = v
    99  		}
   100  		if v < min {
   101  			min = v
   102  		}
   103  	}
   104  	return newSampleSnapshotPrecalculated(s.count, values, min, max, sum)
   105  }
   106  
   107  // Update samples a new value.
   108  func (s *ExpDecaySample) Update(v int64) {
   109  	s.update(time.Now(), v)
   110  }
   111  
   112  // update samples a new value at a particular timestamp.  This is a method all
   113  // its own to facilitate testing.
   114  func (s *ExpDecaySample) update(t time.Time, v int64) {
   115  	s.mutex.Lock()
   116  	defer s.mutex.Unlock()
   117  	s.count++
   118  	if s.values.Size() == s.reservoirSize {
   119  		s.values.Pop()
   120  	}
   121  	var f64 float64
   122  	if s.rand != nil {
   123  		f64 = s.rand.Float64()
   124  	} else {
   125  		f64 = rand.Float64()
   126  	}
   127  	s.values.Push(expDecaySample{
   128  		k: math.Exp(t.Sub(s.t0).Seconds()*s.alpha) / f64,
   129  		v: v,
   130  	})
   131  	if t.After(s.t1) {
   132  		values := s.values.Values()
   133  		t0 := s.t0
   134  		s.values.Clear()
   135  		s.t0 = t
   136  		s.t1 = s.t0.Add(rescaleThreshold)
   137  		for _, v := range values {
   138  			v.k = v.k * math.Exp(-s.alpha*s.t0.Sub(t0).Seconds())
   139  			s.values.Push(v)
   140  		}
   141  	}
   142  }
   143  
   144  // NilSample is a no-op Sample.
   145  type NilSample struct{}
   146  
   147  func (NilSample) Clear()                   {}
   148  func (NilSample) Snapshot() SampleSnapshot { return (*emptySnapshot)(nil) }
   149  func (NilSample) Update(v int64)           {}
   150  
   151  // SamplePercentiles returns an arbitrary percentile of the slice of int64.
   152  func SamplePercentile(values []int64, p float64) float64 {
   153  	return CalculatePercentiles(values, []float64{p})[0]
   154  }
   155  
   156  // CalculatePercentiles returns a slice of arbitrary percentiles of the slice of
   157  // int64. This method returns interpolated results, so e.g if there are only two
   158  // values, [0, 10], a 50% percentile will land between them.
   159  //
   160  // Note: As a side-effect, this method will also sort the slice of values.
   161  // Note2: The input format for percentiles is NOT percent! To express 50%, use 0.5, not 50.
   162  func CalculatePercentiles(values []int64, ps []float64) []float64 {
   163  	scores := make([]float64, len(ps))
   164  	size := len(values)
   165  	if size == 0 {
   166  		return scores
   167  	}
   168  	slices.Sort(values)
   169  	for i, p := range ps {
   170  		pos := p * float64(size+1)
   171  
   172  		if pos < 1.0 {
   173  			scores[i] = float64(values[0])
   174  		} else if pos >= float64(size) {
   175  			scores[i] = float64(values[size-1])
   176  		} else {
   177  			lower := float64(values[int(pos)-1])
   178  			upper := float64(values[int(pos)])
   179  			scores[i] = lower + (pos-math.Floor(pos))*(upper-lower)
   180  		}
   181  	}
   182  	return scores
   183  }
   184  
   185  // sampleSnapshot is a read-only copy of another Sample.
   186  type sampleSnapshot struct {
   187  	count  int64
   188  	values []int64
   189  
   190  	max      int64
   191  	min      int64
   192  	mean     float64
   193  	sum      int64
   194  	variance float64
   195  }
   196  
   197  // newSampleSnapshotPrecalculated creates a read-only sampleSnapShot, using
   198  // precalculated sums to avoid iterating the values
   199  func newSampleSnapshotPrecalculated(count int64, values []int64, min, max, sum int64) *sampleSnapshot {
   200  	if len(values) == 0 {
   201  		return &sampleSnapshot{
   202  			count:  count,
   203  			values: values,
   204  		}
   205  	}
   206  	return &sampleSnapshot{
   207  		count:  count,
   208  		values: values,
   209  		max:    max,
   210  		min:    min,
   211  		mean:   float64(sum) / float64(len(values)),
   212  		sum:    sum,
   213  	}
   214  }
   215  
   216  // newSampleSnapshot creates a read-only sampleSnapShot, and calculates some
   217  // numbers.
   218  func newSampleSnapshot(count int64, values []int64) *sampleSnapshot {
   219  	var (
   220  		max int64 = math.MinInt64
   221  		min int64 = math.MaxInt64
   222  		sum int64
   223  	)
   224  	for _, v := range values {
   225  		sum += v
   226  		if v > max {
   227  			max = v
   228  		}
   229  		if v < min {
   230  			min = v
   231  		}
   232  	}
   233  	return newSampleSnapshotPrecalculated(count, values, min, max, sum)
   234  }
   235  
   236  // Count returns the count of inputs at the time the snapshot was taken.
   237  func (s *sampleSnapshot) Count() int64 { return s.count }
   238  
   239  // Max returns the maximal value at the time the snapshot was taken.
   240  func (s *sampleSnapshot) Max() int64 { return s.max }
   241  
   242  // Mean returns the mean value at the time the snapshot was taken.
   243  func (s *sampleSnapshot) Mean() float64 { return s.mean }
   244  
   245  // Min returns the minimal value at the time the snapshot was taken.
   246  func (s *sampleSnapshot) Min() int64 { return s.min }
   247  
   248  // Percentile returns an arbitrary percentile of values at the time the
   249  // snapshot was taken.
   250  func (s *sampleSnapshot) Percentile(p float64) float64 {
   251  	return SamplePercentile(s.values, p)
   252  }
   253  
   254  // Percentiles returns a slice of arbitrary percentiles of values at the time
   255  // the snapshot was taken.
   256  func (s *sampleSnapshot) Percentiles(ps []float64) []float64 {
   257  	return CalculatePercentiles(s.values, ps)
   258  }
   259  
   260  // Size returns the size of the sample at the time the snapshot was taken.
   261  func (s *sampleSnapshot) Size() int { return len(s.values) }
   262  
   263  // Snapshot returns the snapshot.
   264  func (s *sampleSnapshot) Snapshot() SampleSnapshot { return s }
   265  
   266  // StdDev returns the standard deviation of values at the time the snapshot was
   267  // taken.
   268  func (s *sampleSnapshot) StdDev() float64 {
   269  	if s.variance == 0.0 {
   270  		s.variance = SampleVariance(s.mean, s.values)
   271  	}
   272  	return math.Sqrt(s.variance)
   273  }
   274  
   275  // Sum returns the sum of values at the time the snapshot was taken.
   276  func (s *sampleSnapshot) Sum() int64 { return s.sum }
   277  
   278  // Values returns a copy of the values in the sample.
   279  func (s *sampleSnapshot) Values() []int64 {
   280  	values := make([]int64, len(s.values))
   281  	copy(values, s.values)
   282  	return values
   283  }
   284  
   285  // Variance returns the variance of values at the time the snapshot was taken.
   286  func (s *sampleSnapshot) Variance() float64 {
   287  	if s.variance == 0.0 {
   288  		s.variance = SampleVariance(s.mean, s.values)
   289  	}
   290  	return s.variance
   291  }
   292  
   293  // SampleVariance returns the variance of the slice of int64.
   294  func SampleVariance(mean float64, values []int64) float64 {
   295  	if len(values) == 0 {
   296  		return 0.0
   297  	}
   298  	var sum float64
   299  	for _, v := range values {
   300  		d := float64(v) - mean
   301  		sum += d * d
   302  	}
   303  	return sum / float64(len(values))
   304  }
   305  
   306  // A uniform sample using Vitter's Algorithm R.
   307  //
   308  // <http://www.cs.umd.edu/~samir/498/vitter.pdf>
   309  type UniformSample struct {
   310  	count         int64
   311  	mutex         sync.Mutex
   312  	reservoirSize int
   313  	values        []int64
   314  	rand          *rand.Rand
   315  }
   316  
   317  // NewUniformSample constructs a new uniform sample with the given reservoir
   318  // size.
   319  func NewUniformSample(reservoirSize int) Sample {
   320  	if !Enabled {
   321  		return NilSample{}
   322  	}
   323  	return &UniformSample{
   324  		reservoirSize: reservoirSize,
   325  		values:        make([]int64, 0, reservoirSize),
   326  	}
   327  }
   328  
   329  // SetRand sets the random source (useful in tests)
   330  func (s *UniformSample) SetRand(prng *rand.Rand) Sample {
   331  	s.rand = prng
   332  	return s
   333  }
   334  
   335  // Clear clears all samples.
   336  func (s *UniformSample) Clear() {
   337  	s.mutex.Lock()
   338  	defer s.mutex.Unlock()
   339  	s.count = 0
   340  	s.values = make([]int64, 0, s.reservoirSize)
   341  }
   342  
   343  // Snapshot returns a read-only copy of the sample.
   344  func (s *UniformSample) Snapshot() SampleSnapshot {
   345  	s.mutex.Lock()
   346  	values := make([]int64, len(s.values))
   347  	copy(values, s.values)
   348  	count := s.count
   349  	s.mutex.Unlock()
   350  	return newSampleSnapshot(count, values)
   351  }
   352  
   353  // Update samples a new value.
   354  func (s *UniformSample) Update(v int64) {
   355  	s.mutex.Lock()
   356  	defer s.mutex.Unlock()
   357  	s.count++
   358  	if len(s.values) < s.reservoirSize {
   359  		s.values = append(s.values, v)
   360  	} else {
   361  		var r int64
   362  		if s.rand != nil {
   363  			r = s.rand.Int63n(s.count)
   364  		} else {
   365  			r = rand.Int63n(s.count)
   366  		}
   367  		if r < int64(len(s.values)) {
   368  			s.values[int(r)] = v
   369  		}
   370  	}
   371  }
   372  
   373  // expDecaySample represents an individual sample in a heap.
   374  type expDecaySample struct {
   375  	k float64
   376  	v int64
   377  }
   378  
   379  func newExpDecaySampleHeap(reservoirSize int) *expDecaySampleHeap {
   380  	return &expDecaySampleHeap{make([]expDecaySample, 0, reservoirSize)}
   381  }
   382  
   383  // expDecaySampleHeap is a min-heap of expDecaySamples.
   384  // The internal implementation is copied from the standard library's container/heap
   385  type expDecaySampleHeap struct {
   386  	s []expDecaySample
   387  }
   388  
   389  func (h *expDecaySampleHeap) Clear() {
   390  	h.s = h.s[:0]
   391  }
   392  
   393  func (h *expDecaySampleHeap) Push(s expDecaySample) {
   394  	n := len(h.s)
   395  	h.s = h.s[0 : n+1]
   396  	h.s[n] = s
   397  	h.up(n)
   398  }
   399  
   400  func (h *expDecaySampleHeap) Pop() expDecaySample {
   401  	n := len(h.s) - 1
   402  	h.s[0], h.s[n] = h.s[n], h.s[0]
   403  	h.down(0, n)
   404  
   405  	n = len(h.s)
   406  	s := h.s[n-1]
   407  	h.s = h.s[0 : n-1]
   408  	return s
   409  }
   410  
   411  func (h *expDecaySampleHeap) Size() int {
   412  	return len(h.s)
   413  }
   414  
   415  func (h *expDecaySampleHeap) Values() []expDecaySample {
   416  	return h.s
   417  }
   418  
   419  func (h *expDecaySampleHeap) up(j int) {
   420  	for {
   421  		i := (j - 1) / 2 // parent
   422  		if i == j || !(h.s[j].k < h.s[i].k) {
   423  			break
   424  		}
   425  		h.s[i], h.s[j] = h.s[j], h.s[i]
   426  		j = i
   427  	}
   428  }
   429  
   430  func (h *expDecaySampleHeap) down(i, n int) {
   431  	for {
   432  		j1 := 2*i + 1
   433  		if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
   434  			break
   435  		}
   436  		j := j1 // left child
   437  		if j2 := j1 + 1; j2 < n && !(h.s[j1].k < h.s[j2].k) {
   438  			j = j2 // = 2*i + 2  // right child
   439  		}
   440  		if !(h.s[j].k < h.s[i].k) {
   441  			break
   442  		}
   443  		h.s[i], h.s[j] = h.s[j], h.s[i]
   444  		i = j
   445  	}
   446  }