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 }