histogram.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /*
  2. *
  3. * Copyright 2017 gRPC authors.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. package stats
  19. import (
  20. "bytes"
  21. "fmt"
  22. "io"
  23. "log"
  24. "math"
  25. "strconv"
  26. "strings"
  27. )
  28. // Histogram accumulates values in the form of a histogram with
  29. // exponentially increased bucket sizes.
  30. type Histogram struct {
  31. // Count is the total number of values added to the histogram.
  32. Count int64
  33. // Sum is the sum of all the values added to the histogram.
  34. Sum int64
  35. // SumOfSquares is the sum of squares of all values.
  36. SumOfSquares int64
  37. // Min is the minimum of all the values added to the histogram.
  38. Min int64
  39. // Max is the maximum of all the values added to the histogram.
  40. Max int64
  41. // Buckets contains all the buckets of the histogram.
  42. Buckets []HistogramBucket
  43. opts HistogramOptions
  44. logBaseBucketSize float64
  45. oneOverLogOnePlusGrowthFactor float64
  46. }
  47. // HistogramOptions contains the parameters that define the histogram's buckets.
  48. // The first bucket of the created histogram (with index 0) contains [min, min+n)
  49. // where n = BaseBucketSize, min = MinValue.
  50. // Bucket i (i>=1) contains [min + n * m^(i-1), min + n * m^i), where m = 1+GrowthFactor.
  51. // The type of the values is int64.
  52. type HistogramOptions struct {
  53. // NumBuckets is the number of buckets.
  54. NumBuckets int
  55. // GrowthFactor is the growth factor of the buckets. A value of 0.1
  56. // indicates that bucket N+1 will be 10% larger than bucket N.
  57. GrowthFactor float64
  58. // BaseBucketSize is the size of the first bucket.
  59. BaseBucketSize float64
  60. // MinValue is the lower bound of the first bucket.
  61. MinValue int64
  62. }
  63. // HistogramBucket represents one histogram bucket.
  64. type HistogramBucket struct {
  65. // LowBound is the lower bound of the bucket.
  66. LowBound float64
  67. // Count is the number of values in the bucket.
  68. Count int64
  69. }
  70. // NewHistogram returns a pointer to a new Histogram object that was created
  71. // with the provided options.
  72. func NewHistogram(opts HistogramOptions) *Histogram {
  73. if opts.NumBuckets == 0 {
  74. opts.NumBuckets = 32
  75. }
  76. if opts.BaseBucketSize == 0.0 {
  77. opts.BaseBucketSize = 1.0
  78. }
  79. h := Histogram{
  80. Buckets: make([]HistogramBucket, opts.NumBuckets),
  81. Min: math.MaxInt64,
  82. Max: math.MinInt64,
  83. opts: opts,
  84. logBaseBucketSize: math.Log(opts.BaseBucketSize),
  85. oneOverLogOnePlusGrowthFactor: 1 / math.Log(1+opts.GrowthFactor),
  86. }
  87. m := 1.0 + opts.GrowthFactor
  88. delta := opts.BaseBucketSize
  89. h.Buckets[0].LowBound = float64(opts.MinValue)
  90. for i := 1; i < opts.NumBuckets; i++ {
  91. h.Buckets[i].LowBound = float64(opts.MinValue) + delta
  92. delta = delta * m
  93. }
  94. return &h
  95. }
  96. // Print writes textual output of the histogram values.
  97. func (h *Histogram) Print(w io.Writer) {
  98. h.PrintWithUnit(w, 1)
  99. }
  100. // PrintWithUnit writes textual output of the histogram values .
  101. // Data in histogram is divided by a Unit before print.
  102. func (h *Histogram) PrintWithUnit(w io.Writer, unit float64) {
  103. avg := float64(h.Sum) / float64(h.Count)
  104. fmt.Fprintf(w, "Count: %d Min: %5.1f Max: %5.1f Avg: %.2f\n", h.Count, float64(h.Min)/unit, float64(h.Max)/unit, avg/unit)
  105. fmt.Fprintf(w, "%s\n", strings.Repeat("-", 60))
  106. if h.Count <= 0 {
  107. return
  108. }
  109. maxBucketDigitLen := len(strconv.FormatFloat(h.Buckets[len(h.Buckets)-1].LowBound, 'f', 6, 64))
  110. if maxBucketDigitLen < 3 {
  111. // For "inf".
  112. maxBucketDigitLen = 3
  113. }
  114. maxCountDigitLen := len(strconv.FormatInt(h.Count, 10))
  115. percentMulti := 100 / float64(h.Count)
  116. accCount := int64(0)
  117. for i, b := range h.Buckets {
  118. fmt.Fprintf(w, "[%*f, ", maxBucketDigitLen, b.LowBound/unit)
  119. if i+1 < len(h.Buckets) {
  120. fmt.Fprintf(w, "%*f)", maxBucketDigitLen, h.Buckets[i+1].LowBound/unit)
  121. } else {
  122. fmt.Fprintf(w, "%*s)", maxBucketDigitLen, "inf")
  123. }
  124. accCount += b.Count
  125. fmt.Fprintf(w, " %*d %5.1f%% %5.1f%%", maxCountDigitLen, b.Count, float64(b.Count)*percentMulti, float64(accCount)*percentMulti)
  126. const barScale = 0.1
  127. barLength := int(float64(b.Count)*percentMulti*barScale + 0.5)
  128. fmt.Fprintf(w, " %s\n", strings.Repeat("#", barLength))
  129. }
  130. }
  131. // String returns the textual output of the histogram values as string.
  132. func (h *Histogram) String() string {
  133. var b bytes.Buffer
  134. h.Print(&b)
  135. return b.String()
  136. }
  137. // Clear resets all the content of histogram.
  138. func (h *Histogram) Clear() {
  139. h.Count = 0
  140. h.Sum = 0
  141. h.SumOfSquares = 0
  142. h.Min = math.MaxInt64
  143. h.Max = math.MinInt64
  144. for i := range h.Buckets {
  145. h.Buckets[i].Count = 0
  146. }
  147. }
  148. // Opts returns a copy of the options used to create the Histogram.
  149. func (h *Histogram) Opts() HistogramOptions {
  150. return h.opts
  151. }
  152. // Add adds a value to the histogram.
  153. func (h *Histogram) Add(value int64) error {
  154. bucket, err := h.findBucket(value)
  155. if err != nil {
  156. return err
  157. }
  158. h.Buckets[bucket].Count++
  159. h.Count++
  160. h.Sum += value
  161. h.SumOfSquares += value * value
  162. if value < h.Min {
  163. h.Min = value
  164. }
  165. if value > h.Max {
  166. h.Max = value
  167. }
  168. return nil
  169. }
  170. func (h *Histogram) findBucket(value int64) (int, error) {
  171. delta := float64(value - h.opts.MinValue)
  172. var b int
  173. if delta >= h.opts.BaseBucketSize {
  174. // b = log_{1+growthFactor} (delta / baseBucketSize) + 1
  175. // = log(delta / baseBucketSize) / log(1+growthFactor) + 1
  176. // = (log(delta) - log(baseBucketSize)) * (1 / log(1+growthFactor)) + 1
  177. b = int((math.Log(delta)-h.logBaseBucketSize)*h.oneOverLogOnePlusGrowthFactor + 1)
  178. }
  179. if b >= len(h.Buckets) {
  180. return 0, fmt.Errorf("no bucket for value: %d", value)
  181. }
  182. return b, nil
  183. }
  184. // Merge takes another histogram h2, and merges its content into h.
  185. // The two histograms must be created by equivalent HistogramOptions.
  186. func (h *Histogram) Merge(h2 *Histogram) {
  187. if h.opts != h2.opts {
  188. log.Fatalf("failed to merge histograms, created by inequivalent options")
  189. }
  190. h.Count += h2.Count
  191. h.Sum += h2.Sum
  192. h.SumOfSquares += h2.SumOfSquares
  193. if h2.Min < h.Min {
  194. h.Min = h2.Min
  195. }
  196. if h2.Max > h.Max {
  197. h.Max = h2.Max
  198. }
  199. for i, b := range h2.Buckets {
  200. h.Buckets[i].Count += b.Count
  201. }
  202. }