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  }