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  }