github.com/tirogen/go-ethereum@v1.10.12-0.20221226051715-250cfede41b6/metrics/runtimehistogram.go (about)

     1  package metrics
     2  
     3  import (
     4  	"math"
     5  	"runtime/metrics"
     6  	"sort"
     7  	"sync/atomic"
     8  )
     9  
    10  func getOrRegisterRuntimeHistogram(name string, scale float64, r Registry) *runtimeHistogram {
    11  	if r == nil {
    12  		r = DefaultRegistry
    13  	}
    14  	constructor := func() Histogram { return newRuntimeHistogram(scale) }
    15  	return r.GetOrRegister(name, constructor).(*runtimeHistogram)
    16  }
    17  
    18  // runtimeHistogram wraps a runtime/metrics histogram.
    19  type runtimeHistogram struct {
    20  	v           atomic.Value
    21  	scaleFactor float64
    22  }
    23  
    24  func newRuntimeHistogram(scale float64) *runtimeHistogram {
    25  	h := &runtimeHistogram{scaleFactor: scale}
    26  	h.update(&metrics.Float64Histogram{})
    27  	return h
    28  }
    29  
    30  func (h *runtimeHistogram) update(mh *metrics.Float64Histogram) {
    31  	if mh == nil {
    32  		// The update value can be nil if the current Go version doesn't support a
    33  		// requested metric. It's just easier to handle nil here than putting
    34  		// conditionals everywhere.
    35  		return
    36  	}
    37  
    38  	s := runtimeHistogramSnapshot{
    39  		Counts:  make([]uint64, len(mh.Counts)),
    40  		Buckets: make([]float64, len(mh.Buckets)),
    41  	}
    42  	copy(s.Counts, mh.Counts)
    43  	copy(s.Buckets, mh.Buckets)
    44  	for i, b := range s.Buckets {
    45  		s.Buckets[i] = b * h.scaleFactor
    46  	}
    47  	h.v.Store(&s)
    48  }
    49  
    50  func (h *runtimeHistogram) load() *runtimeHistogramSnapshot {
    51  	return h.v.Load().(*runtimeHistogramSnapshot)
    52  }
    53  
    54  func (h *runtimeHistogram) Clear() {
    55  	panic("runtimeHistogram does not support Clear")
    56  }
    57  func (h *runtimeHistogram) Update(int64) {
    58  	panic("runtimeHistogram does not support Update")
    59  }
    60  func (h *runtimeHistogram) Sample() Sample {
    61  	return NilSample{}
    62  }
    63  
    64  // Snapshot returns a non-changing cop of the histogram.
    65  func (h *runtimeHistogram) Snapshot() Histogram {
    66  	return h.load()
    67  }
    68  
    69  // Count returns the sample count.
    70  func (h *runtimeHistogram) Count() int64 {
    71  	return h.load().Count()
    72  }
    73  
    74  // Mean returns an approximation of the mean.
    75  func (h *runtimeHistogram) Mean() float64 {
    76  	return h.load().Mean()
    77  }
    78  
    79  // StdDev approximates the standard deviation of the histogram.
    80  func (h *runtimeHistogram) StdDev() float64 {
    81  	return h.load().StdDev()
    82  }
    83  
    84  // Variance approximates the variance of the histogram.
    85  func (h *runtimeHistogram) Variance() float64 {
    86  	return h.load().Variance()
    87  }
    88  
    89  // Percentile computes the p'th percentile value.
    90  func (h *runtimeHistogram) Percentile(p float64) float64 {
    91  	return h.load().Percentile(p)
    92  }
    93  
    94  // Percentiles computes all requested percentile values.
    95  func (h *runtimeHistogram) Percentiles(ps []float64) []float64 {
    96  	return h.load().Percentiles(ps)
    97  }
    98  
    99  // Max returns the highest sample value.
   100  func (h *runtimeHistogram) Max() int64 {
   101  	return h.load().Max()
   102  }
   103  
   104  // Min returns the lowest sample value.
   105  func (h *runtimeHistogram) Min() int64 {
   106  	return h.load().Min()
   107  }
   108  
   109  // Sum returns the sum of all sample values.
   110  func (h *runtimeHistogram) Sum() int64 {
   111  	return h.load().Sum()
   112  }
   113  
   114  type runtimeHistogramSnapshot metrics.Float64Histogram
   115  
   116  func (h *runtimeHistogramSnapshot) Clear() {
   117  	panic("runtimeHistogram does not support Clear")
   118  }
   119  func (h *runtimeHistogramSnapshot) Update(int64) {
   120  	panic("runtimeHistogram does not support Update")
   121  }
   122  func (h *runtimeHistogramSnapshot) Sample() Sample {
   123  	return NilSample{}
   124  }
   125  
   126  func (h *runtimeHistogramSnapshot) Snapshot() Histogram {
   127  	return h
   128  }
   129  
   130  // Count returns the sample count.
   131  func (h *runtimeHistogramSnapshot) Count() int64 {
   132  	var count int64
   133  	for _, c := range h.Counts {
   134  		count += int64(c)
   135  	}
   136  	return count
   137  }
   138  
   139  // Mean returns an approximation of the mean.
   140  func (h *runtimeHistogramSnapshot) Mean() float64 {
   141  	if len(h.Counts) == 0 {
   142  		return 0
   143  	}
   144  	mean, _ := h.mean()
   145  	return mean
   146  }
   147  
   148  // mean computes the mean and also the total sample count.
   149  func (h *runtimeHistogramSnapshot) mean() (mean, totalCount float64) {
   150  	var sum float64
   151  	for i, c := range h.Counts {
   152  		midpoint := h.midpoint(i)
   153  		sum += midpoint * float64(c)
   154  		totalCount += float64(c)
   155  	}
   156  	return sum / totalCount, totalCount
   157  }
   158  
   159  func (h *runtimeHistogramSnapshot) midpoint(bucket int) float64 {
   160  	high := h.Buckets[bucket+1]
   161  	low := h.Buckets[bucket]
   162  	if math.IsInf(high, 1) {
   163  		// The edge of the highest bucket can be +Inf, and it's supposed to mean that this
   164  		// bucket contains all remaining samples > low. We can't get the middle of an
   165  		// infinite range, so just return the lower bound of this bucket instead.
   166  		return low
   167  	}
   168  	if math.IsInf(low, -1) {
   169  		// Similarly, we can get -Inf in the left edge of the lowest bucket,
   170  		// and it means the bucket contains all remaining values < high.
   171  		return high
   172  	}
   173  	return (low + high) / 2
   174  }
   175  
   176  // StdDev approximates the standard deviation of the histogram.
   177  func (h *runtimeHistogramSnapshot) StdDev() float64 {
   178  	return math.Sqrt(h.Variance())
   179  }
   180  
   181  // Variance approximates the variance of the histogram.
   182  func (h *runtimeHistogramSnapshot) Variance() float64 {
   183  	if len(h.Counts) == 0 {
   184  		return 0
   185  	}
   186  
   187  	mean, totalCount := h.mean()
   188  	if totalCount <= 1 {
   189  		// There is no variance when there are zero or one items.
   190  		return 0
   191  	}
   192  
   193  	var sum float64
   194  	for i, c := range h.Counts {
   195  		midpoint := h.midpoint(i)
   196  		d := midpoint - mean
   197  		sum += float64(c) * (d * d)
   198  	}
   199  	return sum / (totalCount - 1)
   200  }
   201  
   202  // Percentile computes the p'th percentile value.
   203  func (h *runtimeHistogramSnapshot) Percentile(p float64) float64 {
   204  	threshold := float64(h.Count()) * p
   205  	values := [1]float64{threshold}
   206  	h.computePercentiles(values[:])
   207  	return values[0]
   208  }
   209  
   210  // Percentiles computes all requested percentile values.
   211  func (h *runtimeHistogramSnapshot) Percentiles(ps []float64) []float64 {
   212  	// Compute threshold values. We need these to be sorted
   213  	// for the percentile computation, but restore the original
   214  	// order later, so keep the indexes as well.
   215  	count := float64(h.Count())
   216  	thresholds := make([]float64, len(ps))
   217  	indexes := make([]int, len(ps))
   218  	for i, percentile := range ps {
   219  		thresholds[i] = count * math.Max(0, math.Min(1.0, percentile))
   220  		indexes[i] = i
   221  	}
   222  	sort.Sort(floatsAscendingKeepingIndex{thresholds, indexes})
   223  
   224  	// Now compute. The result is stored back into the thresholds slice.
   225  	h.computePercentiles(thresholds)
   226  
   227  	// Put the result back into the requested order.
   228  	sort.Sort(floatsByIndex{thresholds, indexes})
   229  	return thresholds
   230  }
   231  
   232  func (h *runtimeHistogramSnapshot) computePercentiles(thresh []float64) {
   233  	var totalCount float64
   234  	for i, count := range h.Counts {
   235  		totalCount += float64(count)
   236  
   237  		for len(thresh) > 0 && thresh[0] < totalCount {
   238  			thresh[0] = h.Buckets[i]
   239  			thresh = thresh[1:]
   240  		}
   241  		if len(thresh) == 0 {
   242  			return
   243  		}
   244  	}
   245  }
   246  
   247  // Note: runtime/metrics.Float64Histogram is a collection of float64s, but the methods
   248  // below need to return int64 to satisfy the interface. The histogram provided by runtime
   249  // also doesn't keep track of individual samples, so results are approximated.
   250  
   251  // Max returns the highest sample value.
   252  func (h *runtimeHistogramSnapshot) Max() int64 {
   253  	for i := len(h.Counts) - 1; i >= 0; i-- {
   254  		count := h.Counts[i]
   255  		if count > 0 {
   256  			edge := h.Buckets[i+1]
   257  			if math.IsInf(edge, 1) {
   258  				edge = h.Buckets[i]
   259  			}
   260  			return int64(math.Ceil(edge))
   261  		}
   262  	}
   263  	return 0
   264  }
   265  
   266  // Min returns the lowest sample value.
   267  func (h *runtimeHistogramSnapshot) Min() int64 {
   268  	for i, count := range h.Counts {
   269  		if count > 0 {
   270  			return int64(math.Floor(h.Buckets[i]))
   271  		}
   272  	}
   273  	return 0
   274  }
   275  
   276  // Sum returns the sum of all sample values.
   277  func (h *runtimeHistogramSnapshot) Sum() int64 {
   278  	var sum float64
   279  	for i := range h.Counts {
   280  		sum += h.Buckets[i] * float64(h.Counts[i])
   281  	}
   282  	return int64(math.Ceil(sum))
   283  }
   284  
   285  type floatsAscendingKeepingIndex struct {
   286  	values  []float64
   287  	indexes []int
   288  }
   289  
   290  func (s floatsAscendingKeepingIndex) Len() int {
   291  	return len(s.values)
   292  }
   293  
   294  func (s floatsAscendingKeepingIndex) Less(i, j int) bool {
   295  	return s.values[i] < s.values[j]
   296  }
   297  
   298  func (s floatsAscendingKeepingIndex) Swap(i, j int) {
   299  	s.values[i], s.values[j] = s.values[j], s.values[i]
   300  	s.indexes[i], s.indexes[j] = s.indexes[j], s.indexes[i]
   301  }
   302  
   303  type floatsByIndex struct {
   304  	values  []float64
   305  	indexes []int
   306  }
   307  
   308  func (s floatsByIndex) Len() int {
   309  	return len(s.values)
   310  }
   311  
   312  func (s floatsByIndex) Less(i, j int) bool {
   313  	return s.indexes[i] < s.indexes[j]
   314  }
   315  
   316  func (s floatsByIndex) Swap(i, j int) {
   317  	s.values[i], s.values[j] = s.values[j], s.values[i]
   318  	s.indexes[i], s.indexes[j] = s.indexes[j], s.indexes[i]
   319  }