github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/workload/histogram/histogram.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package histogram 12 13 import ( 14 "encoding/json" 15 "fmt" 16 "io" 17 "os" 18 "sort" 19 "sync" 20 "time" 21 22 "github.com/cockroachdb/cockroach/pkg/util/syncutil" 23 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 24 "github.com/codahale/hdrhistogram" 25 ) 26 27 const ( 28 sigFigs = 1 29 minLatency = 100 * time.Microsecond 30 ) 31 32 // NamedHistogram is a named histogram for use in Operations. It is threadsafe 33 // but intended to be thread-local. 34 type NamedHistogram struct { 35 name string 36 mu struct { 37 syncutil.Mutex 38 current *hdrhistogram.Histogram 39 } 40 } 41 42 func newNamedHistogram(reg *Registry, name string) *NamedHistogram { 43 w := &NamedHistogram{name: name} 44 w.mu.current = reg.newHistogram() 45 return w 46 } 47 48 // Record saves a new datapoint and should be called once per logical operation. 49 func (w *NamedHistogram) Record(elapsed time.Duration) { 50 maxLatency := time.Duration(w.mu.current.HighestTrackableValue()) 51 if elapsed < minLatency { 52 elapsed = minLatency 53 } else if elapsed > maxLatency { 54 elapsed = maxLatency 55 } 56 57 w.mu.Lock() 58 err := w.mu.current.RecordValue(elapsed.Nanoseconds()) 59 w.mu.Unlock() 60 61 if err != nil { 62 // Note that a histogram only drops recorded values that are out of range, 63 // but we clamp the latency value to the configured range to prevent such 64 // drops. This code path should never happen. 65 panic(fmt.Sprintf(`%s: recording value: %s`, w.name, err)) 66 } 67 } 68 69 // tick resets the current histogram to a new "period". The old one's data 70 // should be saved via the closure argument. 71 func (w *NamedHistogram) tick( 72 newHistogram *hdrhistogram.Histogram, fn func(h *hdrhistogram.Histogram), 73 ) { 74 w.mu.Lock() 75 h := w.mu.current 76 w.mu.current = newHistogram 77 w.mu.Unlock() 78 fn(h) 79 } 80 81 // Registry is a thread-safe enclosure for a (possibly large) number of 82 // named histograms. It allows for "tick"ing them periodically to reset the 83 // counts and also supports aggregations. 84 type Registry struct { 85 mu struct { 86 syncutil.Mutex 87 registered map[string][]*NamedHistogram 88 } 89 90 start time.Time 91 cumulative map[string]*hdrhistogram.Histogram 92 prevTick map[string]time.Time 93 histogramPool *sync.Pool 94 } 95 96 // NewRegistry returns an initialized Registry. maxLat is the maximum time that 97 // queries are expected to take to execute which is needed to initialize the 98 // pool of histograms. 99 func NewRegistry(maxLat time.Duration) *Registry { 100 r := &Registry{ 101 start: timeutil.Now(), 102 cumulative: make(map[string]*hdrhistogram.Histogram), 103 prevTick: make(map[string]time.Time), 104 histogramPool: &sync.Pool{ 105 New: func() interface{} { 106 return hdrhistogram.New(minLatency.Nanoseconds(), maxLat.Nanoseconds(), sigFigs) 107 }, 108 }, 109 } 110 r.mu.registered = make(map[string][]*NamedHistogram) 111 return r 112 } 113 114 func (w *Registry) newHistogram() *hdrhistogram.Histogram { 115 h := w.histogramPool.Get().(*hdrhistogram.Histogram) 116 return h 117 } 118 119 // GetHandle returns a thread-local handle for creating and registering 120 // NamedHistograms. 121 func (w *Registry) GetHandle() *Histograms { 122 hists := &Histograms{reg: w} 123 hists.mu.hists = make(map[string]*NamedHistogram) 124 return hists 125 } 126 127 // Tick aggregates all registered histograms, grouped by name. It is expected to 128 // be called periodically from one goroutine. 129 func (w *Registry) Tick(fn func(Tick)) { 130 merged := make(map[string]*hdrhistogram.Histogram) 131 var names []string 132 var wg sync.WaitGroup 133 134 w.mu.Lock() 135 for name, nameRegistered := range w.mu.registered { 136 wg.Add(1) 137 registered := append([]*NamedHistogram(nil), nameRegistered...) 138 merged[name] = w.newHistogram() 139 names = append(names, name) 140 go func(registered []*NamedHistogram, merged *hdrhistogram.Histogram) { 141 for _, hist := range registered { 142 hist.tick(w.newHistogram(), func(h *hdrhistogram.Histogram) { 143 merged.Merge(h) 144 h.Reset() 145 w.histogramPool.Put(h) 146 }) 147 } 148 wg.Done() 149 }(registered, merged[name]) 150 } 151 w.mu.Unlock() 152 153 wg.Wait() 154 155 now := timeutil.Now() 156 sort.Strings(names) 157 for _, name := range names { 158 mergedHist := merged[name] 159 if _, ok := w.cumulative[name]; !ok { 160 w.cumulative[name] = w.newHistogram() 161 } 162 w.cumulative[name].Merge(mergedHist) 163 164 prevTick, ok := w.prevTick[name] 165 if !ok { 166 prevTick = w.start 167 } 168 w.prevTick[name] = now 169 fn(Tick{ 170 Name: name, 171 Hist: mergedHist, 172 Cumulative: w.cumulative[name], 173 Elapsed: now.Sub(prevTick), 174 Now: now, 175 }) 176 mergedHist.Reset() 177 w.histogramPool.Put(mergedHist) 178 } 179 } 180 181 // Histograms is a thread-local handle for creating and registering 182 // NamedHistograms. 183 type Histograms struct { 184 reg *Registry 185 mu struct { 186 syncutil.RWMutex 187 hists map[string]*NamedHistogram 188 } 189 } 190 191 // Get returns a NamedHistogram with the given name, creating and registering it 192 // if necessary. The result is cached, so no need to cache it in the workload. 193 func (w *Histograms) Get(name string) *NamedHistogram { 194 // Fast path for existing histograms, which is the common case by far. 195 w.mu.RLock() 196 hist, ok := w.mu.hists[name] 197 if ok { 198 w.mu.RUnlock() 199 return hist 200 } 201 w.mu.RUnlock() 202 203 w.mu.Lock() 204 hist, ok = w.mu.hists[name] 205 if !ok { 206 hist = newNamedHistogram(w.reg, name) 207 w.mu.hists[name] = hist 208 } 209 w.mu.Unlock() 210 211 if !ok { 212 w.reg.mu.Lock() 213 w.reg.mu.registered[name] = append(w.reg.mu.registered[name], hist) 214 w.reg.mu.Unlock() 215 } 216 217 return hist 218 } 219 220 // Copy makes a new histogram which is a copy of h. 221 func Copy(h *hdrhistogram.Histogram) *hdrhistogram.Histogram { 222 dup := hdrhistogram.New(h.LowestTrackableValue(), h.HighestTrackableValue(), 223 int(h.SignificantFigures())) 224 dup.Merge(h) 225 return dup 226 } 227 228 // Tick is an aggregation of ticking all histograms in a 229 // Registry with a given name. 230 type Tick struct { 231 // Name is the name given to the histograms represented by this tick. 232 Name string 233 // Hist is the merged result of the represented histograms for this tick. 234 // Hist.TotalCount() is the number of operations that occurred for this tick. 235 Hist *hdrhistogram.Histogram 236 // Cumulative is the merged result of the represented histograms for all 237 // time. Cumulative.TotalCount() is the total number of operations that have 238 // occurred over all time. 239 Cumulative *hdrhistogram.Histogram 240 // Elapsed is the amount of time since the last tick. 241 Elapsed time.Duration 242 // Now is the time at which the tick was gathered. It covers the period 243 // [Now-Elapsed,Now). 244 Now time.Time 245 } 246 247 // Snapshot creates a SnapshotTick from the receiver. 248 func (t Tick) Snapshot() SnapshotTick { 249 return SnapshotTick{ 250 Name: t.Name, 251 Elapsed: t.Elapsed, 252 Now: t.Now, 253 Hist: t.Hist.Export(), 254 } 255 } 256 257 // SnapshotTick parallels Tick but replace the histogram with a 258 // snapshot that is suitable for serialization. Additionally, it only contains 259 // the per-tick histogram, not the cumulative histogram. (The cumulative 260 // histogram can be computed by aggregating all of the per-tick histograms). 261 type SnapshotTick struct { 262 Name string 263 Hist *hdrhistogram.Snapshot 264 Elapsed time.Duration 265 Now time.Time 266 } 267 268 // DecodeSnapshots decodes a file with SnapshotTicks into a series. 269 func DecodeSnapshots(path string) (map[string][]SnapshotTick, error) { 270 f, err := os.Open(path) 271 if err != nil { 272 return nil, err 273 } 274 defer func() { _ = f.Close() }() 275 dec := json.NewDecoder(f) 276 ret := make(map[string][]SnapshotTick) 277 for { 278 var tick SnapshotTick 279 if err := dec.Decode(&tick); err == io.EOF { 280 break 281 } else if err != nil { 282 return nil, err 283 } 284 ret[tick.Name] = append(ret[tick.Name], tick) 285 } 286 return ret, nil 287 }