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