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