github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cli/systembench/disk_bench.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 "io/ioutil" 17 "math/rand" 18 "os" 19 "os/signal" 20 "sync/atomic" 21 "time" 22 23 "github.com/cockroachdb/cockroach/pkg/util/log" 24 "github.com/cockroachdb/cockroach/pkg/util/sysutil" 25 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 26 "github.com/cockroachdb/errors" 27 "golang.org/x/sync/errgroup" 28 ) 29 30 const ( 31 maxFileSize = 1 << 30 // A gigabyte 32 ) 33 34 // DiskBenchmarkType represents an I/O benchmark. 35 type DiskBenchmarkType int 36 37 const ( 38 // SeqWriteTest identifies a sequential write I/O 39 // benchmark. 40 SeqWriteTest DiskBenchmarkType = iota 41 ) 42 43 // DiskOptions holds parameters for the test. 44 type DiskOptions struct { 45 Dir string 46 Concurrency int 47 Duration time.Duration 48 WriteSize int64 49 SyncInterval int64 50 51 Type DiskBenchmarkType 52 } 53 54 // workerSeqWrite holds a temp file and random byte array to write to 55 // the temp file. 56 type workerSeqWrite struct { 57 latency *namedHistogram 58 data []byte 59 60 tempFileDir string 61 syncInterval int64 62 } 63 64 // newWorkerSeqWrite creates a worker that writes writeSize sized 65 // sequential blocks with fsync every syncInterval bytes to a file. 66 func newWorkerSeqWrite( 67 ctx context.Context, diskOptions *DiskOptions, registry *histogramRegistry, 68 ) worker { 69 data := make([]byte, diskOptions.WriteSize) 70 _, err := rand.Read(data) 71 if err != nil { 72 log.Fatalf(ctx, "Failed to fill byte array with random bytes %s", err) 73 } 74 75 w := &workerSeqWrite{data: data, 76 tempFileDir: diskOptions.Dir, 77 syncInterval: diskOptions.SyncInterval, 78 } 79 w.latency = registry.Register("ops") 80 return w 81 } 82 83 // workerSeqWrite implements the worker interface. 84 func (w *workerSeqWrite) run(ctx context.Context) error { 85 writtenPreSync := int64(0) 86 writtenInFile := int64(0) 87 88 tempFile, err := newTempFile(w.tempFileDir) 89 if err != nil { 90 return err 91 } 92 93 writeSize := int64(len(w.data)) 94 defer func() { 95 _ = os.Remove(tempFile.Name()) 96 }() 97 98 for { 99 if ctx.Err() != nil { 100 return ctx.Err() 101 } 102 start := timeutil.Now() 103 if _, err := tempFile.Write(w.data); err != nil { 104 tempFile.Close() 105 return err 106 } 107 108 writtenPreSync += writeSize 109 writtenInFile += writeSize 110 111 if writtenPreSync >= w.syncInterval { 112 if err := tempFile.Sync(); err != nil { 113 return err 114 } 115 writtenPreSync = int64(0) 116 } 117 118 if writtenInFile >= maxFileSize { 119 if err := tempFile.Truncate(0); err != nil { 120 return err 121 } 122 123 if _, err := tempFile.Seek(0, 0); err != nil { 124 return err 125 } 126 } 127 128 bytes := uint64(writeSize) 129 atomic.AddUint64(&numOps, 1) 130 atomic.AddUint64(&numBytes, bytes) 131 w.latency.Record(timeutil.Since(start)) 132 } 133 } 134 135 // workerSeqWrite implements the worker interface. 136 func (w *workerSeqWrite) getLatencyHistogram() *namedHistogram { 137 return w.latency 138 } 139 140 // newTempFile creates a temporary file, closes it and 141 // reopens in append mode. 142 func newTempFile(dir string) (*os.File, error) { 143 tempFile, err := ioutil.TempFile(dir, "") 144 if err != nil { 145 return nil, err 146 } 147 148 tempFileName := tempFile.Name() 149 if err := tempFile.Close(); err != nil { 150 return nil, err 151 } 152 153 return os.OpenFile(tempFileName, 154 os.O_RDWR|os.O_APPEND, 0640) 155 } 156 157 // Run runs I/O benchmarks specified by diskOpts. 158 func Run(diskOpts DiskOptions) error { 159 ctx := context.Background() 160 reg := newHistogramRegistry() 161 162 // Check if the directory exists. 163 _, err := os.Stat(diskOpts.Dir) 164 if err != nil { 165 return errors.Errorf("error: supplied path '%s' must exist", diskOpts.Dir) 166 } 167 168 log.Infof(ctx, "writing to %s\n", diskOpts.Dir) 169 170 workers := make([]worker, diskOpts.Concurrency) 171 var workerCreator func(ctx context.Context, diskOptions *DiskOptions, registry *histogramRegistry) worker 172 173 switch diskOpts.Type { 174 case SeqWriteTest: 175 workerCreator = newWorkerSeqWrite 176 default: 177 return errors.Errorf("Please specify a valid subtest.") 178 } 179 180 g, ctx := errgroup.WithContext(ctx) 181 182 var cancel func() 183 ctx, cancel = context.WithCancel(ctx) 184 defer cancel() 185 186 for i := range workers { 187 workers[i] = workerCreator(ctx, &diskOpts, reg) 188 } 189 190 for i := range workers { 191 g.Go(func() error { 192 return workers[i].run(ctx) 193 }) 194 } 195 196 ticker := time.NewTicker(time.Second) 197 defer ticker.Stop() 198 199 errs := make(chan error, 1) 200 done := make(chan os.Signal, 3) 201 signal.Notify(done, os.Interrupt) 202 203 go func() { 204 if err := g.Wait(); err != nil { 205 errs <- err 206 } else { 207 done <- sysutil.Signal(0) 208 } 209 }() 210 211 if diskOpts.Duration > 0 { 212 go func() { 213 time.Sleep(diskOpts.Duration) 214 done <- sysutil.Signal(0) 215 }() 216 } 217 218 start := timeutil.Now() 219 lastNow := start 220 var lastOps uint64 221 var lastBytes uint64 222 223 for i := 0; ; i++ { 224 select { 225 case <-ticker.C: 226 now := timeutil.Now() 227 elapsed := now.Sub(lastNow) 228 ops := atomic.LoadUint64(&numOps) 229 bytes := atomic.LoadUint64(&numBytes) 230 231 if i%20 == 0 { 232 fmt.Println("_elapsed____ops/sec___mb/sec__p50(ms)__p95(ms)__p99(ms)_pMax(ms)") 233 } 234 reg.Tick(func(tick histogramTick) { 235 h := tick.Hist 236 fmt.Printf("%8s %10.1f %8.1f %8.1f %8.1f %8.1f %8.1f\n", 237 time.Duration(timeutil.Since(start).Seconds()+0.5)*time.Second, 238 float64(ops-lastOps)/elapsed.Seconds(), 239 float64(bytes-lastBytes)/(1024.0*1024.0)/elapsed.Seconds(), 240 time.Duration(h.ValueAtQuantile(50)).Seconds()*1000, 241 time.Duration(h.ValueAtQuantile(95)).Seconds()*1000, 242 time.Duration(h.ValueAtQuantile(99)).Seconds()*1000, 243 time.Duration(h.ValueAtQuantile(100)).Seconds()*1000, 244 ) 245 }) 246 lastNow = now 247 lastOps = ops 248 lastBytes = bytes 249 250 case <-done: 251 cancel() 252 startElapsed := timeutil.Since(start) 253 const totalHeader = "\n_elapsed____ops(total)__mb(total)__avg(ms)__p50(ms)__p95(ms)__p99(ms)_pMax(ms)" 254 fmt.Println(totalHeader + `__total`) 255 reg.Tick(func(tick histogramTick) { 256 h := tick.Cumulative 257 fmt.Printf("%8s %13d %10.1f %8.1f %8.1f %8.1f %8.1f %8.1f\n", 258 time.Duration(startElapsed.Seconds())*time.Second, 259 atomic.LoadUint64(&numOps), 260 float64(atomic.LoadUint64(&numBytes)/(1024.0*1024.0)), 261 time.Duration(h.Mean()).Seconds()*1000, 262 time.Duration(h.ValueAtQuantile(50)).Seconds()*1000, 263 time.Duration(h.ValueAtQuantile(95)).Seconds()*1000, 264 time.Duration(h.ValueAtQuantile(99)).Seconds()*1000, 265 time.Duration(h.ValueAtQuantile(100)).Seconds()*1000) 266 }) 267 return nil 268 case err := <-errs: 269 return err 270 } 271 } 272 }