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

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