github.com/loov/hrtime@v1.0.3/stopwatch.go (about)

     1  package hrtime
     2  
     3  import (
     4  	"sync"
     5  	"sync/atomic"
     6  	"time"
     7  )
     8  
     9  // Span defines a time.Duration span
    10  type Span struct {
    11  	Start  time.Duration
    12  	Finish time.Duration
    13  }
    14  
    15  // Duration returns the duration of the time span.
    16  func (span *Span) Duration() time.Duration {
    17  	return span.Finish - span.Start
    18  }
    19  
    20  // Stopwatch allows concurrent benchmarking using Now
    21  type Stopwatch struct {
    22  	nextLap      int32
    23  	lapsMeasured int32
    24  	spans        []Span
    25  	wait         sync.Mutex
    26  }
    27  
    28  // NewStopwatch creates a new concurrent benchmark using Now
    29  func NewStopwatch(count int) *Stopwatch {
    30  	if count <= 0 {
    31  		panic("must have count at least 1")
    32  	}
    33  
    34  	bench := &Stopwatch{
    35  		nextLap: 0,
    36  		spans:   make([]Span, count),
    37  	}
    38  	// lock mutex to ensure Wait() blocks until finalize is called
    39  	bench.wait.Lock()
    40  	return bench
    41  }
    42  
    43  // mustBeCompleted checks whether measurement has been completed.
    44  func (bench *Stopwatch) mustBeCompleted() {
    45  	if int(atomic.LoadInt32(&bench.lapsMeasured)) < len(bench.spans) {
    46  		panic("benchmarking incomplete")
    47  	}
    48  }
    49  
    50  // Start starts measuring a new lap.
    51  // It returns the lap number to pass in for Stop.
    52  // It will return -1, when all measurements have been made.
    53  //
    54  // Call to Stop with -1 is ignored.
    55  func (bench *Stopwatch) Start() int32 {
    56  	lap := atomic.AddInt32(&bench.nextLap, 1) - 1
    57  	if int(lap) > len(bench.spans) {
    58  		return -1
    59  	}
    60  	bench.spans[lap].Start = Now()
    61  	return lap
    62  }
    63  
    64  // Stop stops measuring the specified lap.
    65  //
    66  // Call to Stop with -1 is ignored.
    67  func (bench *Stopwatch) Stop(lap int32) {
    68  	if lap < 0 {
    69  		return
    70  	}
    71  	bench.spans[lap].Finish = Now()
    72  
    73  	lapsMeasured := atomic.AddInt32(&bench.lapsMeasured, 1)
    74  	if int(lapsMeasured) == len(bench.spans) {
    75  		bench.finalize()
    76  	} else if int(lapsMeasured) > len(bench.spans) {
    77  		panic("stop called too many times")
    78  	}
    79  }
    80  
    81  // finalize finalizes the stopwatch
    82  func (bench *Stopwatch) finalize() {
    83  	// release the initial lock such that Wait can proceed.
    84  	bench.wait.Unlock()
    85  }
    86  
    87  // Wait waits for all measurements to be completed.
    88  func (bench *Stopwatch) Wait() {
    89  	// lock waits for finalize to be called by the last measurement.
    90  	bench.wait.Lock()
    91  	_ = 1 // intentionally empty block, suppress staticcheck SA2001 warning
    92  	bench.wait.Unlock()
    93  }
    94  
    95  // Spans returns measured time-spans.
    96  func (bench *Stopwatch) Spans() []Span {
    97  	bench.mustBeCompleted()
    98  	return append(bench.spans[:0:0], bench.spans...)
    99  }
   100  
   101  // Durations returns measured durations.
   102  func (bench *Stopwatch) Durations() []time.Duration {
   103  	bench.mustBeCompleted()
   104  
   105  	durations := make([]time.Duration, len(bench.spans))
   106  	for i, span := range bench.spans {
   107  		durations[i] = span.Duration()
   108  	}
   109  
   110  	return durations
   111  }
   112  
   113  // Name returns name of the benchmark.
   114  func (bench *Stopwatch) Name() string { return "" }
   115  
   116  // Unit returns units it measures.
   117  func (bench *Stopwatch) Unit() string { return "ns" }
   118  
   119  // Float64s returns all measurements.
   120  func (bench *Stopwatch) Float64s() []float64 {
   121  	measurements := make([]float64, len(bench.spans))
   122  	for i := range measurements {
   123  		measurements[i] = float64(bench.spans[i].Duration().Nanoseconds())
   124  	}
   125  	return measurements
   126  }
   127  
   128  // Histogram creates an histogram of all the durations.
   129  //
   130  // It creates binCount bins to distribute the data and uses the
   131  // 99.9 percentile as the last bucket range. However, for a nicer output
   132  // it might choose a larger value.
   133  func (bench *Stopwatch) Histogram(binCount int) *Histogram {
   134  	bench.mustBeCompleted()
   135  
   136  	opts := defaultOptions
   137  	opts.BinCount = binCount
   138  
   139  	return NewDurationHistogram(bench.Durations(), &opts)
   140  }
   141  
   142  // HistogramClamp creates an historgram of all the durations clamping minimum and maximum time.
   143  //
   144  // It creates binCount bins to distribute the data and uses the
   145  // maximum as the last bucket.
   146  func (bench *Stopwatch) HistogramClamp(binCount int, min, max time.Duration) *Histogram {
   147  	bench.mustBeCompleted()
   148  
   149  	durations := make([]time.Duration, 0, len(bench.spans))
   150  	for _, span := range bench.spans {
   151  		duration := span.Duration()
   152  		if duration < min {
   153  			durations = append(durations, min)
   154  		} else {
   155  			durations = append(durations, duration)
   156  		}
   157  	}
   158  
   159  	opts := defaultOptions
   160  	opts.BinCount = binCount
   161  	opts.ClampMaximum = float64(max.Nanoseconds())
   162  	opts.ClampPercentile = 0
   163  
   164  	return NewDurationHistogram(durations, &opts)
   165  }