1 package gmeasure 2 3 import "time" 4 5 /* 6 Stopwatch provides a convenient abstraction for recording durations. There are two ways to make a Stopwatch: 7 8 You can make a Stopwatch from an Experiment via experiment.NewStopwatch(). This is how you first get a hold of a Stopwatch. 9 10 You can subsequently call stopwatch.NewStopwatch() to get a fresh Stopwatch. 11 This is only necessary if you need to record durations on a different goroutine as a single Stopwatch is not considered thread-safe. 12 13 The Stopwatch starts as soon as it is created. You can Pause() the stopwatch and Reset() it as needed. 14 15 Stopwatches refer back to their parent Experiment. They use this reference to record any measured durations back with the Experiment. 16 */ 17 type Stopwatch struct { 18 Experiment *Experiment 19 t time.Time 20 pauseT time.Time 21 pauseDuration time.Duration 22 running bool 23 } 24 25 func newStopwatch(experiment *Experiment) *Stopwatch { 26 return &Stopwatch{ 27 Experiment: experiment, 28 t: time.Now(), 29 running: true, 30 } 31 } 32 33 /* 34 NewStopwatch returns a new Stopwatch pointing to the same Experiment as this Stopwatch 35 */ 36 func (s *Stopwatch) NewStopwatch() *Stopwatch { 37 return newStopwatch(s.Experiment) 38 } 39 40 /* 41 Record captures the amount of time that has passed since the Stopwatch was created or most recently Reset(). It records the duration on it's associated Experiment in a Measurement with the passed-in name. 42 43 Record takes all the decorators that experiment.RecordDuration takes (e.g. Annotation("...") can be used to annotate this duration) 44 45 Note that Record does not Reset the Stopwatch. It does, however, return the Stopwatch so the following pattern is common: 46 47 stopwatch := experiment.NewStopwatch() 48 // first expensive operation 49 stopwatch.Record("first operation").Reset() //records the duration of the first operation and resets the stopwatch. 50 // second expensive operation 51 stopwatch.Record("second operation").Reset() //records the duration of the second operation and resets the stopwatch. 52 53 omitting the Reset() after the first operation would cause the duration recorded for the second operation to include the time elapsed by both the first _and_ second operations. 54 55 The Stopwatch must be running (i.e. not paused) when Record is called. 56 */ 57 func (s *Stopwatch) Record(name string, args ...interface{}) *Stopwatch { 58 if !s.running { 59 panic("stopwatch is not running - call Resume or Reset before calling Record") 60 } 61 duration := time.Since(s.t) - s.pauseDuration 62 s.Experiment.RecordDuration(name, duration, args...) 63 return s 64 } 65 66 /* 67 Reset resets the Stopwatch. Subsequent recorded durations will measure the time elapsed from the moment Reset was called. 68 If the Stopwatch was Paused it is unpaused after calling Reset. 69 */ 70 func (s *Stopwatch) Reset() *Stopwatch { 71 s.running = true 72 s.t = time.Now() 73 s.pauseDuration = 0 74 return s 75 } 76 77 /* 78 Pause pauses the Stopwatch. While pasued the Stopwatch does not accumulate elapsed time. This is useful for ignoring expensive operations that are incidental to the behavior you are attempting to characterize. 79 Note: You must call Resume() before you can Record() subsequent measurements. 80 81 For example: 82 83 stopwatch := experiment.NewStopwatch() 84 // first expensive operation 85 stopwatch.Record("first operation").Reset() 86 // second expensive operation - part 1 87 stopwatch.Pause() 88 // something expensive that we don't care about 89 stopwatch.Resume() 90 // second expensive operation - part 2 91 stopwatch.Record("second operation").Reset() // the recorded duration captures the time elapsed during parts 1 and 2 of the second expensive operation, but not the bit in between 92 93 94 The Stopwatch must be running when Pause is called. 95 */ 96 func (s *Stopwatch) Pause() *Stopwatch { 97 if !s.running { 98 panic("stopwatch is not running - call Resume or Reset before calling Pause") 99 } 100 s.running = false 101 s.pauseT = time.Now() 102 return s 103 } 104 105 /* 106 Resume resumes a paused Stopwatch. Any time that elapses after Resume is called will be accumulated as elapsed time when a subsequent duration is Recorded. 107 108 The Stopwatch must be Paused when Resume is called 109 */ 110 func (s *Stopwatch) Resume() *Stopwatch { 111 if s.running { 112 panic("stopwatch is running - call Pause before calling Resume") 113 } 114 s.running = true 115 s.pauseDuration = s.pauseDuration + time.Since(s.pauseT) 116 return s 117 } 118