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

     1  // Copyright 2022 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  // CPUStopWatch is a wrapper around cpuStopWatch that is safe to use
    21  // concurrently. If CPUStopWatch is nil, all operations are no-ops and no
    22  // locks are acquired.
    23  type CPUStopWatch struct {
    24  	mu struct {
    25  		syncutil.Mutex
    26  		cpuStopWatch cpuStopWatch
    27  	}
    28  }
    29  
    30  // NewCPUStopWatch returns a new CPUStopWatch if the grunning library is
    31  // supported. Otherwise, it returns nil.
    32  func NewCPUStopWatch() *CPUStopWatch {
    33  	if grunning.Supported() {
    34  		return &CPUStopWatch{}
    35  	}
    36  	return nil
    37  }
    38  
    39  // Start starts the CPU stop watch if it hasn't already been started.
    40  func (w *CPUStopWatch) Start() {
    41  	if w == nil {
    42  		return
    43  	}
    44  	w.mu.Lock()
    45  	defer w.mu.Unlock()
    46  	w.mu.cpuStopWatch.start()
    47  }
    48  
    49  // Stop stops the CPU stop watch if it hasn't already been stopped and
    50  // accumulates the CPU time that has been spent since it was started. If the
    51  // CPU stop watch has already been stopped, it is a noop.
    52  func (w *CPUStopWatch) Stop() {
    53  	if w == nil {
    54  		return
    55  	}
    56  	w.mu.Lock()
    57  	defer w.mu.Unlock()
    58  	w.mu.cpuStopWatch.stop()
    59  }
    60  
    61  // Elapsed returns the total CPU time measured by the stop watch so far.
    62  func (w *CPUStopWatch) Elapsed() time.Duration {
    63  	if w == nil {
    64  		return 0
    65  	}
    66  	w.mu.Lock()
    67  	defer w.mu.Unlock()
    68  	return w.mu.cpuStopWatch.elapsed()
    69  }
    70  
    71  // cpuStopWatch is a utility stop watch for measuring CPU time spent by a
    72  // component. It can be safely started and stopped multiple times, but is
    73  // not safe to use concurrently. If cpuStopWatch is nil, all operations are
    74  // no-ops.
    75  //
    76  // Note that the grunning library uses a non-monotonic clock, so the measured
    77  // duration between clock start and stop can come out as negative. This can lead
    78  // to discrepancies in the measured CPU time - for example, a child stopwatch
    79  // that is started and stopped while a parent stopwatch is running can rarely
    80  // measure a larger CPU time duration than the parent. Users must be prepared to
    81  // handle this case.
    82  type cpuStopWatch struct {
    83  	startCPU time.Duration
    84  	totalCPU time.Duration
    85  }
    86  
    87  func (w *cpuStopWatch) start() {
    88  	if w == nil {
    89  		return
    90  	}
    91  	w.startCPU = grunning.Time()
    92  }
    93  
    94  func (w *cpuStopWatch) stop() {
    95  	if w == nil {
    96  		return
    97  	}
    98  	w.totalCPU += grunning.Elapsed(w.startCPU, grunning.Time())
    99  }
   100  
   101  func (w *cpuStopWatch) elapsed() time.Duration {
   102  	if w == nil {
   103  		return 0
   104  	}
   105  	return w.totalCPU
   106  }