github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cli/syncbench/syncbench.go (about) 1 // Copyright 2017 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 syncbench 12 13 import ( 14 "context" 15 "fmt" 16 "math/rand" 17 "os" 18 "os/signal" 19 "sync" 20 "sync/atomic" 21 "time" 22 23 "github.com/cockroachdb/cockroach/pkg/base" 24 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 25 "github.com/cockroachdb/cockroach/pkg/storage" 26 "github.com/cockroachdb/cockroach/pkg/util/encoding" 27 "github.com/cockroachdb/cockroach/pkg/util/log" 28 "github.com/cockroachdb/cockroach/pkg/util/syncutil" 29 "github.com/cockroachdb/cockroach/pkg/util/sysutil" 30 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 31 "github.com/cockroachdb/errors" 32 "github.com/codahale/hdrhistogram" 33 ) 34 35 var numOps uint64 36 var numBytes uint64 37 38 const ( 39 minLatency = 100 * time.Microsecond 40 maxLatency = 10 * time.Second 41 ) 42 43 func clampLatency(d, min, max time.Duration) time.Duration { 44 if d < min { 45 return min 46 } 47 if d > max { 48 return max 49 } 50 return d 51 } 52 53 type worker struct { 54 db storage.Engine 55 latency struct { 56 syncutil.Mutex 57 *hdrhistogram.WindowedHistogram 58 } 59 logOnly bool 60 } 61 62 func newWorker(db storage.Engine) *worker { 63 w := &worker{db: db} 64 w.latency.WindowedHistogram = hdrhistogram.NewWindowed(1, 65 minLatency.Nanoseconds(), maxLatency.Nanoseconds(), 1) 66 return w 67 } 68 69 func (w *worker) run(wg *sync.WaitGroup) { 70 defer wg.Done() 71 72 ctx := context.Background() 73 rand := rand.New(rand.NewSource(timeutil.Now().UnixNano())) 74 var buf []byte 75 76 randBlock := func(min, max int) []byte { 77 data := make([]byte, rand.Intn(max-min)+min) 78 for i := range data { 79 data[i] = byte(rand.Int() & 0xff) 80 } 81 return data 82 } 83 84 for { 85 start := timeutil.Now() 86 b := w.db.NewBatch() 87 if w.logOnly { 88 block := randBlock(300, 400) 89 if err := b.LogData(block); err != nil { 90 log.Fatalf(ctx, "%v", err) 91 } 92 } else { 93 for j := 0; j < 5; j++ { 94 block := randBlock(60, 80) 95 key := encoding.EncodeUint32Ascending(buf, rand.Uint32()) 96 if err := b.Put(storage.MakeMVCCMetadataKey(key), block); err != nil { 97 log.Fatalf(ctx, "%v", err) 98 } 99 buf = key[:0] 100 } 101 } 102 bytes := uint64(b.Len()) 103 if err := b.Commit(true); err != nil { 104 log.Fatalf(ctx, "%v", err) 105 } 106 atomic.AddUint64(&numOps, 1) 107 atomic.AddUint64(&numBytes, bytes) 108 elapsed := clampLatency(timeutil.Since(start), minLatency, maxLatency) 109 w.latency.Lock() 110 if err := w.latency.Current.RecordValue(elapsed.Nanoseconds()); err != nil { 111 log.Fatalf(ctx, "%v", err) 112 } 113 w.latency.Unlock() 114 } 115 } 116 117 // Options holds parameters for the test. 118 type Options struct { 119 Dir string 120 Concurrency int 121 Duration time.Duration 122 LogOnly bool 123 } 124 125 // Run a test of writing synchronously to the RocksDB WAL. 126 // 127 // TODO(tschottdorf): this should receive a RocksDB instance so that the caller 128 // in cli can use OpenEngine (which in turn allows to use encryption, etc). 129 func Run(opts Options) error { 130 // Check if the directory exists. 131 _, err := os.Stat(opts.Dir) 132 if err == nil { 133 return errors.Errorf("error: supplied path '%s' must not exist", opts.Dir) 134 } 135 136 defer func() { 137 _ = os.RemoveAll(opts.Dir) 138 }() 139 140 fmt.Printf("writing to %s\n", opts.Dir) 141 142 db, err := storage.NewDefaultEngine( 143 0, 144 base.StorageConfig{ 145 Settings: cluster.MakeTestingClusterSettings(), 146 Dir: opts.Dir, 147 }) 148 if err != nil { 149 return err 150 } 151 152 workers := make([]*worker, opts.Concurrency) 153 154 var wg sync.WaitGroup 155 for i := range workers { 156 wg.Add(1) 157 workers[i] = newWorker(db) 158 workers[i].logOnly = opts.LogOnly 159 go workers[i].run(&wg) 160 } 161 162 ticker := time.NewTicker(time.Second) 163 defer ticker.Stop() 164 165 done := make(chan os.Signal, 3) 166 signal.Notify(done, os.Interrupt) 167 168 go func() { 169 wg.Wait() 170 done <- sysutil.Signal(0) 171 }() 172 173 if opts.Duration > 0 { 174 go func() { 175 time.Sleep(opts.Duration) 176 done <- sysutil.Signal(0) 177 }() 178 } 179 180 start := timeutil.Now() 181 lastNow := start 182 var lastOps uint64 183 var lastBytes uint64 184 185 for i := 0; ; i++ { 186 select { 187 case <-ticker.C: 188 var h *hdrhistogram.Histogram 189 for _, w := range workers { 190 w.latency.Lock() 191 m := w.latency.Merge() 192 w.latency.Rotate() 193 w.latency.Unlock() 194 if h == nil { 195 h = m 196 } else { 197 h.Merge(m) 198 } 199 } 200 201 p50 := h.ValueAtQuantile(50) 202 p95 := h.ValueAtQuantile(95) 203 p99 := h.ValueAtQuantile(99) 204 pMax := h.ValueAtQuantile(100) 205 206 now := timeutil.Now() 207 elapsed := now.Sub(lastNow) 208 ops := atomic.LoadUint64(&numOps) 209 bytes := atomic.LoadUint64(&numBytes) 210 211 if i%20 == 0 { 212 fmt.Println("_elapsed____ops/sec___mb/sec__p50(ms)__p95(ms)__p99(ms)_pMax(ms)") 213 } 214 fmt.Printf("%8s %10.1f %8.1f %8.1f %8.1f %8.1f %8.1f\n", 215 time.Duration(timeutil.Since(start).Seconds()+0.5)*time.Second, 216 float64(ops-lastOps)/elapsed.Seconds(), 217 float64(bytes-lastBytes)/(1024.0*1024.0)/elapsed.Seconds(), 218 time.Duration(p50).Seconds()*1000, 219 time.Duration(p95).Seconds()*1000, 220 time.Duration(p99).Seconds()*1000, 221 time.Duration(pMax).Seconds()*1000, 222 ) 223 lastNow = now 224 lastOps = ops 225 lastBytes = bytes 226 227 case <-done: 228 return nil 229 } 230 } 231 }