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