histogram.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /*
  2. Copyright 2019 The Vitess Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package stats
  14. import (
  15. "bytes"
  16. "fmt"
  17. "git.nspix.com/golang/micro/sync"
  18. )
  19. // Histogram tracks counts and totals while
  20. // splitting the counts under different buckets
  21. // using specified cutoffs.
  22. type Histogram struct {
  23. name string
  24. help string
  25. cutoffs []int64
  26. labels []string
  27. countLabel string
  28. totalLabel string
  29. hook func(int64)
  30. buckets []sync.AtomicInt64
  31. total sync.AtomicInt64
  32. }
  33. // NewHistogram creates a histogram with auto-generated labels
  34. // based on the cutoffs. The buckets are categorized using the
  35. // following criterion: cutoff[i-1] < value <= cutoff[i]. Anything
  36. // higher than the highest cutoff is labeled as "inf".
  37. func NewHistogram(name, help string, cutoffs []int64) *Histogram {
  38. labels := make([]string, len(cutoffs)+1)
  39. for i, v := range cutoffs {
  40. labels[i] = fmt.Sprintf("%d", v)
  41. }
  42. labels[len(labels)-1] = "inf"
  43. return NewGenericHistogram(name, help, cutoffs, labels, "Count", "Total")
  44. }
  45. // NewGenericHistogram creates a histogram where all the labels are
  46. // supplied by the caller. The number of labels has to be one more than
  47. // the number of cutoffs because the last label captures everything that
  48. // exceeds the highest cutoff.
  49. func NewGenericHistogram(name, help string, cutoffs []int64, labels []string, countLabel, totalLabel string) *Histogram {
  50. if len(cutoffs) != len(labels)-1 {
  51. panic("mismatched cutoff and label lengths")
  52. }
  53. h := &Histogram{
  54. name: name,
  55. help: help,
  56. cutoffs: cutoffs,
  57. labels: labels,
  58. countLabel: countLabel,
  59. totalLabel: totalLabel,
  60. buckets: make([]sync.AtomicInt64, len(labels)),
  61. }
  62. if name != "" {
  63. publish(name, h)
  64. }
  65. return h
  66. }
  67. // Add adds a new measurement to the Histogram.
  68. func (h *Histogram) Add(value int64) {
  69. for i := range h.labels {
  70. if i == len(h.labels)-1 || value <= h.cutoffs[i] {
  71. h.buckets[i].Add(1)
  72. h.total.Add(value)
  73. break
  74. }
  75. }
  76. if h.hook != nil {
  77. h.hook(value)
  78. }
  79. if defaultStatsdHook.histogramHook != nil && h.name != "" {
  80. defaultStatsdHook.histogramHook(h.name, value)
  81. }
  82. }
  83. // String returns a string representation of the Histogram.
  84. // Note that sum of all buckets may not be equal to the total temporarily,
  85. // because Add() increments bucket and total with two atomic operations.
  86. func (h *Histogram) String() string {
  87. b, _ := h.MarshalJSON()
  88. return string(b)
  89. }
  90. // MarshalJSON returns a JSON representation of the Histogram.
  91. // Note that sum of all buckets may not be equal to the total temporarily,
  92. // because Add() increments bucket and total with two atomic operations.
  93. func (h *Histogram) MarshalJSON() ([]byte, error) {
  94. b := bytes.NewBuffer(make([]byte, 0, 4096))
  95. fmt.Fprintf(b, "{")
  96. totalCount := int64(0)
  97. for i, label := range h.labels {
  98. count := h.buckets[i].Get()
  99. totalCount += count
  100. fmt.Fprintf(b, "\"%v\": %v, ", label, count)
  101. }
  102. fmt.Fprintf(b, "\"%s\": %v, ", h.countLabel, totalCount)
  103. fmt.Fprintf(b, "\"%s\": %v", h.totalLabel, h.total.Get())
  104. fmt.Fprintf(b, "}")
  105. return b.Bytes(), nil
  106. }
  107. // Counts returns a map from labels to the current count in the Histogram for that label.
  108. func (h *Histogram) Counts() map[string]int64 {
  109. counts := make(map[string]int64, len(h.labels))
  110. for i, label := range h.labels {
  111. counts[label] = h.buckets[i].Get()
  112. }
  113. return counts
  114. }
  115. // CountLabel returns the count label that was set when this Histogram was created.
  116. func (h *Histogram) CountLabel() string {
  117. return h.countLabel
  118. }
  119. // Count returns the number of times Add has been called.
  120. func (h *Histogram) Count() (count int64) {
  121. for i := range h.buckets {
  122. count += h.buckets[i].Get()
  123. }
  124. return
  125. }
  126. // TotalLabel returns the total label that was set when this Histogram was created.
  127. func (h *Histogram) TotalLabel() string {
  128. return h.totalLabel
  129. }
  130. // Total returns the sum of all values that have been added to this Histogram.
  131. func (h *Histogram) Total() (total int64) {
  132. return h.total.Get()
  133. }
  134. // Labels returns the labels that were set when this Histogram was created.
  135. func (h *Histogram) Labels() []string {
  136. return h.labels
  137. }
  138. // Cutoffs returns the cutoffs that were set when this Histogram was created.
  139. func (h *Histogram) Cutoffs() []int64 {
  140. return h.cutoffs
  141. }
  142. // Buckets returns a snapshot of the current values in all buckets.
  143. func (h *Histogram) Buckets() []int64 {
  144. buckets := make([]int64, len(h.buckets))
  145. for i := range h.buckets {
  146. buckets[i] = h.buckets[i].Get()
  147. }
  148. return buckets
  149. }
  150. // Help returns the help string.
  151. func (h *Histogram) Help() string {
  152. return h.help
  153. }