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