github.com/ethereum/go-ethereum@v1.16.1/metrics/sample.go (about)

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