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