github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/timeutil/stopwatch.go (about)

     1  // Copyright 2019 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 timeutil
    12  
    13  import (
    14  	"time"
    15  
    16  	"github.com/cockroachdb/cockroachdb-parser/pkg/util/grunning"
    17  	"github.com/cockroachdb/cockroachdb-parser/pkg/util/syncutil"
    18  )
    19  
    20  // StopWatch is a utility stop watch that can be safely started and stopped
    21  // multiple times and can be used concurrently.
    22  type StopWatch struct {
    23  	mu struct {
    24  		syncutil.Mutex
    25  		// started is true if the stop watch has been started and haven't been
    26  		// stopped after that.
    27  		started bool
    28  		// startedAt is the time when the stop watch was started.
    29  		startedAt time.Time
    30  		// elapsed is the total time measured by the stop watch (i.e. between
    31  		// all Starts and Stops).
    32  		elapsed time.Duration
    33  		// timeSource is the source of time used by the stop watch. It is always
    34  		// timeutil.Now except for tests.
    35  		timeSource func() time.Time
    36  		// cpuStopWatch is used to track CPU usage. It may be nil, in which case any
    37  		// operations on it are no-ops.
    38  		cpuStopWatch *cpuStopWatch
    39  	}
    40  }
    41  
    42  // NewStopWatch creates a new StopWatch.
    43  func NewStopWatch() *StopWatch {
    44  	return newStopWatch(Now)
    45  }
    46  
    47  // NewStopWatchWithCPU creates a new StopWatch that will track CPU usage in
    48  // addition to wall-clock time.
    49  func NewStopWatchWithCPU() *StopWatch {
    50  	w := newStopWatch(Now)
    51  	if grunning.Supported() {
    52  		w.mu.cpuStopWatch = &cpuStopWatch{}
    53  	}
    54  	return w
    55  }
    56  
    57  // NewTestStopWatch create a new StopWatch with the given time source. It is
    58  // used for testing only.
    59  func NewTestStopWatch(timeSource func() time.Time) *StopWatch {
    60  	return newStopWatch(timeSource)
    61  }
    62  
    63  func newStopWatch(timeSource func() time.Time) *StopWatch {
    64  	w := &StopWatch{}
    65  	w.mu.timeSource = timeSource
    66  	return w
    67  }
    68  
    69  // Start starts the stop watch if it hasn't already been started.
    70  func (w *StopWatch) Start() {
    71  	w.mu.Lock()
    72  	defer w.mu.Unlock()
    73  	if !w.mu.started {
    74  		w.mu.started = true
    75  		w.mu.startedAt = w.mu.timeSource()
    76  		w.mu.cpuStopWatch.start()
    77  	}
    78  }
    79  
    80  // Stop stops the stop watch if it hasn't already been stopped and accumulates
    81  // the duration that elapsed since it was started. If the stop watch has
    82  // already been stopped, it is a noop.
    83  func (w *StopWatch) Stop() {
    84  	w.mu.Lock()
    85  	defer w.mu.Unlock()
    86  	if w.mu.started {
    87  		w.mu.started = false
    88  		w.mu.elapsed += w.mu.timeSource().Sub(w.mu.startedAt)
    89  		w.mu.cpuStopWatch.stop()
    90  	}
    91  }
    92  
    93  // Elapsed returns the total time measured by the stop watch so far.
    94  func (w *StopWatch) Elapsed() time.Duration {
    95  	w.mu.Lock()
    96  	defer w.mu.Unlock()
    97  	return w.mu.elapsed
    98  }
    99  
   100  // ElapsedCPU returns the total CPU time measured by the stop watch so far. It
   101  // returns zero if cpuStopWatch is nil (which is the case if NewStopWatchWithCPU
   102  // was not called or the platform does not support grunning).
   103  func (w *StopWatch) ElapsedCPU() time.Duration {
   104  	w.mu.Lock()
   105  	defer w.mu.Unlock()
   106  	return w.mu.cpuStopWatch.elapsed()
   107  }
   108  
   109  // LastStartedAt returns the time the stopwatch was last started, and a bool
   110  // indicating if the stopwatch is currently started.
   111  func (w *StopWatch) LastStartedAt() (startedAt time.Time, started bool) {
   112  	w.mu.Lock()
   113  	defer w.mu.Unlock()
   114  	return w.mu.startedAt, w.mu.started
   115  }
   116  
   117  // TestTimeSource is a source of time that remembers when it was created (in
   118  // terms of the real time) and returns the time based on its creation time and
   119  // the number of "advances" it has had. It is used for testing only.
   120  type TestTimeSource struct {
   121  	initTime time.Time
   122  	counter  int64
   123  }
   124  
   125  // NewTestTimeSource create a new TestTimeSource.
   126  func NewTestTimeSource() *TestTimeSource {
   127  	return &TestTimeSource{initTime: Now()}
   128  }
   129  
   130  // Now tells the current time according to t.
   131  func (t *TestTimeSource) Now() time.Time {
   132  	return t.initTime.Add(time.Duration(t.counter))
   133  }
   134  
   135  // Advance advances the current time according to t by 1 nanosecond.
   136  func (t *TestTimeSource) Advance() {
   137  	t.counter++
   138  }
   139  
   140  // Elapsed returns how much time has passed since t has been created. Note that
   141  // it is equal to the number of advances in nanoseconds.
   142  func (t *TestTimeSource) Elapsed() time.Duration {
   143  	return time.Duration(t.counter)
   144  }