histogram.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /*
  2. * SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
  3. * SPDX-License-Identifier: Apache-2.0
  4. */
  5. package z
  6. import (
  7. "fmt"
  8. "math"
  9. "strings"
  10. "github.com/dustin/go-humanize"
  11. )
  12. // Creates bounds for an histogram. The bounds are powers of two of the form
  13. // [2^min_exponent, ..., 2^max_exponent].
  14. func HistogramBounds(minExponent, maxExponent uint32) []float64 {
  15. var bounds []float64
  16. for i := minExponent; i <= maxExponent; i++ {
  17. bounds = append(bounds, float64(int(1)<<i))
  18. }
  19. return bounds
  20. }
  21. func Fibonacci(num int) []float64 {
  22. assert(num > 4)
  23. bounds := make([]float64, num)
  24. bounds[0] = 1
  25. bounds[1] = 2
  26. for i := 2; i < num; i++ {
  27. bounds[i] = bounds[i-1] + bounds[i-2]
  28. }
  29. return bounds
  30. }
  31. // HistogramData stores the information needed to represent the sizes of the keys and values
  32. // as a histogram.
  33. type HistogramData struct {
  34. Bounds []float64
  35. Count int64
  36. CountPerBucket []int64
  37. Min int64
  38. Max int64
  39. Sum int64
  40. }
  41. // NewHistogramData returns a new instance of HistogramData with properly initialized fields.
  42. func NewHistogramData(bounds []float64) *HistogramData {
  43. return &HistogramData{
  44. Bounds: bounds,
  45. CountPerBucket: make([]int64, len(bounds)+1),
  46. Max: 0,
  47. Min: math.MaxInt64,
  48. }
  49. }
  50. func (histogram *HistogramData) Copy() *HistogramData {
  51. if histogram == nil {
  52. return nil
  53. }
  54. return &HistogramData{
  55. Bounds: append([]float64{}, histogram.Bounds...),
  56. CountPerBucket: append([]int64{}, histogram.CountPerBucket...),
  57. Count: histogram.Count,
  58. Min: histogram.Min,
  59. Max: histogram.Max,
  60. Sum: histogram.Sum,
  61. }
  62. }
  63. // Update changes the Min and Max fields if value is less than or greater than the current values.
  64. func (histogram *HistogramData) Update(value int64) {
  65. if histogram == nil {
  66. return
  67. }
  68. if value > histogram.Max {
  69. histogram.Max = value
  70. }
  71. if value < histogram.Min {
  72. histogram.Min = value
  73. }
  74. histogram.Sum += value
  75. histogram.Count++
  76. for index := 0; index <= len(histogram.Bounds); index++ {
  77. // Allocate value in the last buckets if we reached the end of the Bounds array.
  78. if index == len(histogram.Bounds) {
  79. histogram.CountPerBucket[index]++
  80. break
  81. }
  82. if value < int64(histogram.Bounds[index]) {
  83. histogram.CountPerBucket[index]++
  84. break
  85. }
  86. }
  87. }
  88. // Mean returns the mean value for the histogram.
  89. func (histogram *HistogramData) Mean() float64 {
  90. if histogram.Count == 0 {
  91. return 0
  92. }
  93. return float64(histogram.Sum) / float64(histogram.Count)
  94. }
  95. // String converts the histogram data into human-readable string.
  96. func (histogram *HistogramData) String() string {
  97. if histogram == nil {
  98. return ""
  99. }
  100. var b strings.Builder
  101. b.WriteString("\n -- Histogram: \n")
  102. b.WriteString(fmt.Sprintf("Min value: %d \n", histogram.Min))
  103. b.WriteString(fmt.Sprintf("Max value: %d \n", histogram.Max))
  104. b.WriteString(fmt.Sprintf("Count: %d \n", histogram.Count))
  105. b.WriteString(fmt.Sprintf("50p: %.2f \n", histogram.Percentile(0.5)))
  106. b.WriteString(fmt.Sprintf("75p: %.2f \n", histogram.Percentile(0.75)))
  107. b.WriteString(fmt.Sprintf("90p: %.2f \n", histogram.Percentile(0.90)))
  108. numBounds := len(histogram.Bounds)
  109. var cum float64
  110. for index, count := range histogram.CountPerBucket {
  111. if count == 0 {
  112. continue
  113. }
  114. // The last bucket represents the bucket that contains the range from
  115. // the last bound up to infinity so it's processed differently than the
  116. // other buckets.
  117. if index == len(histogram.CountPerBucket)-1 {
  118. lowerBound := uint64(histogram.Bounds[numBounds-1])
  119. page := float64(count*100) / float64(histogram.Count)
  120. cum += page
  121. b.WriteString(fmt.Sprintf("[%s, %s) %d %.2f%% %.2f%%\n",
  122. humanize.IBytes(lowerBound), "infinity", count, page, cum))
  123. continue
  124. }
  125. upperBound := uint64(histogram.Bounds[index])
  126. lowerBound := uint64(0)
  127. if index > 0 {
  128. lowerBound = uint64(histogram.Bounds[index-1])
  129. }
  130. page := float64(count*100) / float64(histogram.Count)
  131. cum += page
  132. b.WriteString(fmt.Sprintf("[%d, %d) %d %.2f%% %.2f%%\n",
  133. lowerBound, upperBound, count, page, cum))
  134. }
  135. b.WriteString(" --\n")
  136. return b.String()
  137. }
  138. // Percentile returns the percentile value for the histogram.
  139. // value of p should be between [0.0-1.0]
  140. func (histogram *HistogramData) Percentile(p float64) float64 {
  141. if histogram == nil {
  142. return 0
  143. }
  144. if histogram.Count == 0 {
  145. // if no data return the minimum range
  146. return histogram.Bounds[0]
  147. }
  148. pval := int64(float64(histogram.Count) * p)
  149. for i, v := range histogram.CountPerBucket {
  150. pval = pval - v
  151. if pval <= 0 {
  152. if i == len(histogram.Bounds) {
  153. break
  154. }
  155. return histogram.Bounds[i]
  156. }
  157. }
  158. // default return should be the max range
  159. return histogram.Bounds[len(histogram.Bounds)-1]
  160. }
  161. // Clear reset the histogram. Helpful in situations where we need to reset the metrics
  162. func (histogram *HistogramData) Clear() {
  163. if histogram == nil {
  164. return
  165. }
  166. histogram.Count = 0
  167. histogram.CountPerBucket = make([]int64, len(histogram.Bounds)+1)
  168. histogram.Sum = 0
  169. histogram.Max = 0
  170. histogram.Min = math.MaxInt64
  171. }