github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cli/systembench/tests.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 systembench 12 13 import ( 14 "context" 15 "fmt" 16 "os" 17 "os/signal" 18 "sort" 19 "time" 20 21 "github.com/cockroachdb/cockroach/pkg/util/syncutil" 22 "github.com/cockroachdb/cockroach/pkg/util/sysutil" 23 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 24 "github.com/codahale/hdrhistogram" 25 "golang.org/x/sync/errgroup" 26 ) 27 28 const ( 29 minLatency = 10 * time.Microsecond 30 maxLatency = 10 * time.Second 31 ) 32 33 func newHistogram() *hdrhistogram.Histogram { 34 return hdrhistogram.New(minLatency.Nanoseconds(), maxLatency.Nanoseconds(), 1) 35 } 36 37 type namedHistogram struct { 38 name string 39 mu struct { 40 syncutil.Mutex 41 current *hdrhistogram.Histogram 42 } 43 } 44 45 func newNamedHistogram(name string) *namedHistogram { 46 w := &namedHistogram{name: name} 47 w.mu.current = newHistogram() 48 return w 49 } 50 51 func (w *namedHistogram) Record(elapsed time.Duration) { 52 if elapsed < minLatency { 53 elapsed = minLatency 54 } else if elapsed > maxLatency { 55 elapsed = maxLatency 56 } 57 58 w.mu.Lock() 59 err := w.mu.current.RecordValue(elapsed.Nanoseconds()) 60 w.mu.Unlock() 61 62 if err != nil { 63 // Note that a histogram only drops recorded values that are out of range, 64 // but we clamp the latency value to the configured range to prevent such 65 // drops. This code path should never happen. 66 panic(fmt.Sprintf(`%s: recording value: %s`, w.name, err)) 67 } 68 } 69 70 func (w *namedHistogram) tick(fn func(h *hdrhistogram.Histogram)) { 71 w.mu.Lock() 72 defer w.mu.Unlock() 73 h := w.mu.current 74 w.mu.current = newHistogram() 75 fn(h) 76 } 77 78 type histogramTick struct { 79 // Name is the name given to the histograms represented by this tick. 80 Name string 81 // Hist is the merged result of the represented histograms for this tick. 82 // Hist.TotalCount() is the number of operations that occurred for this tick. 83 Hist *hdrhistogram.Histogram 84 // Cumulative is the merged result of the represented histograms for all 85 // time. Cumulative.TotalCount() is the total number of operations that have 86 // occurred over all time. 87 Cumulative *hdrhistogram.Histogram 88 // Elapsed is the amount of time since the last tick. 89 Elapsed time.Duration 90 // Now is the time at which the tick was gathered. It covers the period 91 // [Now-Elapsed,Now). 92 Now time.Time 93 } 94 95 type histogramRegistry struct { 96 mu struct { 97 syncutil.Mutex 98 registered []*namedHistogram 99 } 100 101 start time.Time 102 cumulative map[string]*hdrhistogram.Histogram 103 prevTick map[string]time.Time 104 } 105 106 func newHistogramRegistry() *histogramRegistry { 107 return &histogramRegistry{ 108 start: timeutil.Now(), 109 cumulative: make(map[string]*hdrhistogram.Histogram), 110 prevTick: make(map[string]time.Time), 111 } 112 } 113 114 func (w *histogramRegistry) Register(name string) *namedHistogram { 115 hist := newNamedHistogram(name) 116 117 w.mu.Lock() 118 w.mu.registered = append(w.mu.registered, hist) 119 w.mu.Unlock() 120 121 return hist 122 } 123 124 func (w *histogramRegistry) Tick(fn func(histogramTick)) { 125 w.mu.Lock() 126 registered := append([]*namedHistogram(nil), w.mu.registered...) 127 w.mu.Unlock() 128 129 merged := make(map[string]*hdrhistogram.Histogram) 130 var names []string 131 for _, hist := range registered { 132 hist.tick(func(h *hdrhistogram.Histogram) { 133 if m, ok := merged[hist.name]; ok { 134 m.Merge(h) 135 } else { 136 merged[hist.name] = h 137 names = append(names, hist.name) 138 } 139 }) 140 } 141 142 now := timeutil.Now() 143 sort.Strings(names) 144 for _, name := range names { 145 mergedHist := merged[name] 146 if _, ok := w.cumulative[name]; !ok { 147 w.cumulative[name] = newHistogram() 148 } 149 w.cumulative[name].Merge(mergedHist) 150 151 prevTick, ok := w.prevTick[name] 152 if !ok { 153 prevTick = w.start 154 } 155 w.prevTick[name] = now 156 fn(histogramTick{ 157 Name: name, 158 Hist: merged[name], 159 Cumulative: w.cumulative[name], 160 Elapsed: now.Sub(prevTick), 161 Now: now, 162 }) 163 } 164 } 165 166 type test struct { 167 init func(g *errgroup.Group) 168 tick func(elapsed time.Duration, i int) 169 done func(elapsed time.Duration) 170 } 171 172 func runTest(ctx context.Context, t test, duration time.Duration) error { 173 g, ctx := errgroup.WithContext(ctx) 174 175 var cancel func() 176 _, cancel = context.WithCancel(ctx) 177 defer cancel() 178 179 t.init(g) 180 181 ticker := time.NewTicker(time.Second) 182 defer ticker.Stop() 183 184 errs := make(chan error, 1) 185 done := make(chan os.Signal, 3) 186 signal.Notify(done, os.Interrupt) 187 188 go func() { 189 if err := g.Wait(); err != nil { 190 errs <- err 191 } else { 192 done <- sysutil.Signal(0) 193 } 194 }() 195 196 if duration > 0 { 197 go func() { 198 time.Sleep(duration) 199 done <- sysutil.Signal(0) 200 }() 201 } 202 203 start := timeutil.Now() 204 for i := 0; ; i++ { 205 select { 206 case <-ticker.C: 207 t.tick(timeutil.Since(start), i) 208 209 case <-done: 210 cancel() 211 t.done(timeutil.Since(start)) 212 return nil 213 case err := <-errs: 214 return err 215 } 216 } 217 }