vitess.io/vitess@v0.16.2/go/stats/timings.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 "encoding/json" 21 "fmt" 22 "sync" 23 "time" 24 25 "vitess.io/vitess/go/sync2" 26 ) 27 28 // Timings is meant to tracks timing data 29 // by named categories as well as histograms. 30 type Timings struct { 31 totalCount sync2.AtomicInt64 32 totalTime sync2.AtomicInt64 33 34 mu sync.RWMutex 35 histograms map[string]*Histogram 36 37 name string 38 help string 39 label string 40 labelCombined bool 41 } 42 43 // NewTimings creates a new Timings object, and publishes it if name is set. 44 // categories is an optional list of categories to initialize to 0. 45 // Categories that aren't initialized will be missing from the map until the 46 // first time they are updated. 47 func NewTimings(name, help, label string, categories ...string) *Timings { 48 t := &Timings{ 49 histograms: make(map[string]*Histogram), 50 name: name, 51 help: help, 52 label: label, 53 labelCombined: IsDimensionCombined(label), 54 } 55 for _, cat := range categories { 56 t.histograms[cat] = NewGenericHistogram("", "", bucketCutoffs, bucketLabels, "Count", "Time") 57 } 58 if name != "" { 59 publish(name, t) 60 } 61 62 return t 63 } 64 65 // Reset will clear histograms: used during testing 66 func (t *Timings) Reset() { 67 t.mu.RLock() 68 t.histograms = make(map[string]*Histogram) 69 t.mu.RUnlock() 70 } 71 72 // Add will add a new value to the named histogram. 73 func (t *Timings) Add(name string, elapsed time.Duration) { 74 if t.labelCombined { 75 name = StatsAllStr 76 } 77 // Get existing Histogram. 78 t.mu.RLock() 79 hist, ok := t.histograms[name] 80 t.mu.RUnlock() 81 82 // Create Histogram if it does not exist. 83 if !ok { 84 t.mu.Lock() 85 hist, ok = t.histograms[name] 86 if !ok { 87 hist = NewGenericHistogram("", "", bucketCutoffs, bucketLabels, "Count", "Time") 88 t.histograms[name] = hist 89 } 90 t.mu.Unlock() 91 } 92 if defaultStatsdHook.timerHook != nil && t.name != "" { 93 defaultStatsdHook.timerHook(t.name, name, elapsed.Milliseconds(), t) 94 } 95 96 elapsedNs := int64(elapsed) 97 hist.Add(elapsedNs) 98 t.totalCount.Add(1) 99 t.totalTime.Add(elapsedNs) 100 } 101 102 // Record is a convenience function that records completion 103 // timing data based on the provided start time of an event. 104 func (t *Timings) Record(name string, startTime time.Time) { 105 if t.labelCombined { 106 name = StatsAllStr 107 } 108 t.Add(name, time.Since(startTime)) 109 } 110 111 // String is for expvar. 112 func (t *Timings) String() string { 113 t.mu.RLock() 114 defer t.mu.RUnlock() 115 116 tm := struct { 117 TotalCount int64 118 TotalTime int64 119 Histograms map[string]*Histogram 120 }{ 121 t.totalCount.Get(), 122 t.totalTime.Get(), 123 t.histograms, 124 } 125 126 data, err := json.Marshal(tm) 127 if err != nil { 128 data, _ = json.Marshal(err.Error()) 129 } 130 return string(data) 131 } 132 133 // Histograms returns a map pointing at the histograms. 134 func (t *Timings) Histograms() (h map[string]*Histogram) { 135 t.mu.RLock() 136 defer t.mu.RUnlock() 137 h = make(map[string]*Histogram, len(t.histograms)) 138 for k, v := range t.histograms { 139 h[k] = v 140 } 141 return 142 } 143 144 // Count returns the total count for all values. 145 func (t *Timings) Count() int64 { 146 return t.totalCount.Get() 147 } 148 149 // Time returns the total time elapsed for all values. 150 func (t *Timings) Time() int64 { 151 return t.totalTime.Get() 152 } 153 154 // Counts returns the total count for each value. 155 func (t *Timings) Counts() map[string]int64 { 156 t.mu.RLock() 157 defer t.mu.RUnlock() 158 159 counts := make(map[string]int64, len(t.histograms)+1) 160 for k, v := range t.histograms { 161 counts[k] = v.Count() 162 } 163 counts["All"] = t.totalCount.Get() 164 return counts 165 } 166 167 // Cutoffs returns the cutoffs used in the component histograms. 168 // Do not change the returned slice. 169 func (t *Timings) Cutoffs() []int64 { 170 return bucketCutoffs 171 } 172 173 // Help returns the help string. 174 func (t *Timings) Help() string { 175 return t.help 176 } 177 178 // Label returns the label name. 179 func (t *Timings) Label() string { 180 return t.label 181 } 182 183 var bucketCutoffs = []int64{5e5, 1e6, 5e6, 1e7, 5e7, 1e8, 5e8, 1e9, 5e9, 1e10} 184 185 var bucketLabels []string 186 187 func init() { 188 bucketLabels = make([]string, len(bucketCutoffs)+1) 189 for i, v := range bucketCutoffs { 190 bucketLabels[i] = fmt.Sprintf("%d", v) 191 } 192 bucketLabels[len(bucketLabels)-1] = "inf" 193 } 194 195 // MultiTimings is meant to tracks timing data by categories as well 196 // as histograms. The names of the categories are compound names made 197 // with joining multiple strings with '.'. 198 type MultiTimings struct { 199 Timings 200 labels []string 201 combinedLabels []bool 202 } 203 204 // NewMultiTimings creates a new MultiTimings object. 205 func NewMultiTimings(name string, help string, labels []string) *MultiTimings { 206 combinedLabels := make([]bool, len(labels)) 207 for i, label := range labels { 208 combinedLabels[i] = IsDimensionCombined(label) 209 } 210 t := &MultiTimings{ 211 Timings: Timings{ 212 histograms: make(map[string]*Histogram), 213 name: name, 214 help: help, 215 label: safeJoinLabels(labels, combinedLabels), 216 }, 217 labels: labels, 218 combinedLabels: combinedLabels, 219 } 220 if name != "" { 221 publish(name, t) 222 } 223 224 return t 225 } 226 227 // Labels returns descriptions of the parts of each compound category name. 228 func (mt *MultiTimings) Labels() []string { 229 return mt.labels 230 } 231 232 // Add will add a new value to the named histogram. 233 func (mt *MultiTimings) Add(names []string, elapsed time.Duration) { 234 if len(names) != len(mt.labels) { 235 panic("MultiTimings: wrong number of values in Add") 236 } 237 mt.Timings.Add(safeJoinLabels(names, mt.combinedLabels), elapsed) 238 } 239 240 // Record is a convenience function that records completion 241 // timing data based on the provided start time of an event. 242 func (mt *MultiTimings) Record(names []string, startTime time.Time) { 243 if len(names) != len(mt.labels) { 244 panic("MultiTimings: wrong number of values in Record") 245 } 246 mt.Timings.Record(safeJoinLabels(names, mt.combinedLabels), startTime) 247 } 248 249 // Cutoffs returns the cutoffs used in the component histograms. 250 // Do not change the returned slice. 251 func (mt *MultiTimings) Cutoffs() []int64 { 252 return bucketCutoffs 253 }