github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/instrument/methods.go (about)

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package instrument
    22  
    23  import (
    24  	"fmt"
    25  	"time"
    26  
    27  	"github.com/uber-go/tally"
    28  
    29  	"github.com/m3db/m3/src/x/unsafe"
    30  )
    31  
    32  var (
    33  	// no-op stopwatch must contain a valid recorder to prevent panics.
    34  	nullStopWatchStart = tally.NewStopwatch(time.Time{}, noopStopwatchRecorder{})
    35  )
    36  
    37  // TimerType is a type of timer, standard or histogram timer.
    38  type TimerType uint
    39  
    40  const (
    41  	// StandardTimerType is a standard timer type back by a regular timer.
    42  	StandardTimerType TimerType = iota
    43  	// HistogramTimerType is a histogram timer backed by a histogram.
    44  	HistogramTimerType
    45  )
    46  
    47  const _samplingPrecision = 1 << 24
    48  
    49  // TimerOptions is a set of timer options when creating a timer.
    50  type TimerOptions struct {
    51  	Type               TimerType
    52  	StandardSampleRate float64
    53  	HistogramBuckets   tally.Buckets
    54  }
    55  
    56  // NewTimer creates a new timer based on the timer options.
    57  func (o TimerOptions) NewTimer(scope tally.Scope, name string) tally.Timer {
    58  	return NewTimer(scope, name, o)
    59  }
    60  
    61  // SparseHistogramTimerHistogramBuckets returns a small spare set of
    62  // histogram timer histogram buckets, from 1ms up to 8m.
    63  func SparseHistogramTimerHistogramBuckets() tally.Buckets {
    64  	return tally.DurationBuckets{
    65  		time.Millisecond,
    66  		5 * time.Millisecond,
    67  		10 * time.Millisecond,
    68  		25 * time.Millisecond,
    69  		50 * time.Millisecond,
    70  		75 * time.Millisecond,
    71  		100 * time.Millisecond,
    72  		250 * time.Millisecond,
    73  		500 * time.Millisecond,
    74  		750 * time.Millisecond,
    75  		time.Second,
    76  		2*time.Second + 500*time.Millisecond,
    77  		5 * time.Second,
    78  		7*time.Second + 500*time.Millisecond,
    79  		10 * time.Second,
    80  		25 * time.Second,
    81  		50 * time.Second,
    82  		75 * time.Second,
    83  		100 * time.Second,
    84  		250 * time.Second,
    85  		500 * time.Second,
    86  	}
    87  }
    88  
    89  // DefaultHistogramTimerHistogramBuckets returns a set of default
    90  // histogram timer histogram buckets, from 2ms up to 1hr.
    91  func DefaultHistogramTimerHistogramBuckets() tally.Buckets {
    92  	return tally.DurationBuckets{
    93  		2 * time.Millisecond,
    94  		4 * time.Millisecond,
    95  		6 * time.Millisecond,
    96  		8 * time.Millisecond,
    97  		10 * time.Millisecond,
    98  		20 * time.Millisecond,
    99  		40 * time.Millisecond,
   100  		60 * time.Millisecond,
   101  		80 * time.Millisecond,
   102  		100 * time.Millisecond,
   103  		200 * time.Millisecond,
   104  		400 * time.Millisecond,
   105  		600 * time.Millisecond,
   106  		800 * time.Millisecond,
   107  		time.Second,
   108  		time.Second + 500*time.Millisecond,
   109  		2 * time.Second,
   110  		2*time.Second + 500*time.Millisecond,
   111  		3 * time.Second,
   112  		3*time.Second + 500*time.Millisecond,
   113  		4 * time.Second,
   114  		4*time.Second + 500*time.Millisecond,
   115  		5 * time.Second,
   116  		5*time.Second + 500*time.Millisecond,
   117  		6 * time.Second,
   118  		6*time.Second + 500*time.Millisecond,
   119  		7 * time.Second,
   120  		7*time.Second + 500*time.Millisecond,
   121  		8 * time.Second,
   122  		8*time.Second + 500*time.Millisecond,
   123  		9 * time.Second,
   124  		9*time.Second + 500*time.Millisecond,
   125  		10 * time.Second,
   126  		15 * time.Second,
   127  		20 * time.Second,
   128  		25 * time.Second,
   129  		30 * time.Second,
   130  		35 * time.Second,
   131  		40 * time.Second,
   132  		45 * time.Second,
   133  		50 * time.Second,
   134  		55 * time.Second,
   135  		60 * time.Second,
   136  		150 * time.Second,
   137  		300 * time.Second,
   138  		450 * time.Second,
   139  		600 * time.Second,
   140  		900 * time.Second,
   141  		1200 * time.Second,
   142  		1500 * time.Second,
   143  		1800 * time.Second,
   144  		2100 * time.Second,
   145  		2400 * time.Second,
   146  		2700 * time.Second,
   147  		3000 * time.Second,
   148  		3300 * time.Second,
   149  		3600 * time.Second,
   150  	}
   151  }
   152  
   153  // DefaultSummaryQuantileObjectives is a set of default summary
   154  // quantile objectives and allowed error thresholds.
   155  func DefaultSummaryQuantileObjectives() map[float64]float64 {
   156  	return map[float64]float64{
   157  		0.5:   0.01,
   158  		0.75:  0.001,
   159  		0.95:  0.001,
   160  		0.99:  0.001,
   161  		0.999: 0.0001,
   162  	}
   163  }
   164  
   165  // NewStandardTimerOptions returns a set of standard timer options for
   166  // standard timer types.
   167  func NewStandardTimerOptions() TimerOptions {
   168  	return TimerOptions{Type: StandardTimerType}
   169  }
   170  
   171  // HistogramTimerOptions is a set of histogram timer options.
   172  type HistogramTimerOptions struct {
   173  	HistogramBuckets tally.Buckets
   174  }
   175  
   176  // NewHistogramTimerOptions returns a set of histogram timer options
   177  // and if no histogram buckets are set it will use the default
   178  // histogram buckets defined.
   179  func NewHistogramTimerOptions(opts HistogramTimerOptions) TimerOptions {
   180  	result := TimerOptions{Type: HistogramTimerType}
   181  	if opts.HistogramBuckets != nil && opts.HistogramBuckets.Len() > 0 {
   182  		result.HistogramBuckets = opts.HistogramBuckets
   183  	} else {
   184  		result.HistogramBuckets = DefaultHistogramTimerHistogramBuckets()
   185  	}
   186  	return result
   187  }
   188  
   189  var _ tally.Timer = (*timer)(nil)
   190  
   191  // timer is a timer that can be backed by a timer or a histogram
   192  // depending on TimerOptions.
   193  type timer struct {
   194  	TimerOptions
   195  	timer     tally.Timer
   196  	histogram tally.Histogram
   197  }
   198  
   199  // NewTimer returns a new timer that is backed by a timer or a histogram
   200  // based on the timer options.
   201  func NewTimer(scope tally.Scope, name string, opts TimerOptions) tally.Timer {
   202  	t := &timer{TimerOptions: opts}
   203  	switch t.Type {
   204  	case HistogramTimerType:
   205  		t.histogram = scope.Histogram(name, opts.HistogramBuckets)
   206  	default:
   207  		t.timer = scope.Timer(name)
   208  		if rate := opts.StandardSampleRate; validRate(rate) {
   209  			t.timer = newSampledTimer(t.timer, rate)
   210  		}
   211  	}
   212  	return t
   213  }
   214  
   215  func (t *timer) Record(v time.Duration) {
   216  	switch t.Type {
   217  	case HistogramTimerType:
   218  		t.histogram.RecordDuration(v)
   219  	default:
   220  		t.timer.Record(v)
   221  	}
   222  }
   223  
   224  func (t *timer) Start() tally.Stopwatch {
   225  	return tally.NewStopwatch(time.Now(), t)
   226  }
   227  
   228  func (t *timer) RecordStopwatch(stopwatchStart time.Time) {
   229  	t.Record(time.Since(stopwatchStart))
   230  }
   231  
   232  // sampledTimer is a sampled timer that implements the tally timer interface.
   233  // NB(xichen): the sampling logic should eventually be implemented in tally.
   234  type sampledTimer struct {
   235  	tally.Timer
   236  
   237  	rate uint32
   238  }
   239  
   240  // NewSampledTimer creates a new sampled timer.
   241  func NewSampledTimer(base tally.Timer, rate float64) (tally.Timer, error) {
   242  	if !validRate(rate) {
   243  		return nil, fmt.Errorf("sampling rate %f must be between 0.0 and 1.0", rate)
   244  	}
   245  	return newSampledTimer(base, rate), nil
   246  }
   247  
   248  func validRate(rate float64) bool {
   249  	return rate > 0.0 && rate <= 1.0
   250  }
   251  
   252  func newSampledTimer(base tally.Timer, rate float64) tally.Timer {
   253  	if rate == 1.0 {
   254  		// Avoid the overhead of working out if should sample each time.
   255  		return base
   256  	}
   257  
   258  	r := uint32(rate * _samplingPrecision)
   259  	if rate >= 1.0 || r > _samplingPrecision {
   260  		r = _samplingPrecision // clamp to 100%
   261  	}
   262  
   263  	return &sampledTimer{
   264  		Timer: base,
   265  		rate:  _samplingPrecision - r,
   266  	}
   267  }
   268  
   269  // MustCreateSampledTimer creates a new sampled timer, and panics if an error
   270  // is encountered.
   271  func MustCreateSampledTimer(base tally.Timer, rate float64) tally.Timer {
   272  	t, err := NewSampledTimer(base, rate)
   273  	if err != nil {
   274  		panic(err)
   275  	}
   276  	return t
   277  }
   278  
   279  func (t *sampledTimer) shouldSample() bool {
   280  	return unsafe.Fastrandn(_samplingPrecision) >= t.rate
   281  }
   282  
   283  func (t *sampledTimer) Start() tally.Stopwatch {
   284  	if !t.shouldSample() {
   285  		return nullStopWatchStart
   286  	}
   287  	return t.Timer.Start()
   288  }
   289  
   290  func (t *sampledTimer) Stop(startTime tally.Stopwatch) {
   291  	if startTime == nullStopWatchStart {
   292  		// If startTime is nullStopWatchStart, do nothing.
   293  		return
   294  	}
   295  	startTime.Stop()
   296  }
   297  
   298  func (t *sampledTimer) Record(d time.Duration) {
   299  	if !t.shouldSample() {
   300  		return
   301  	}
   302  	t.Timer.Record(d)
   303  }
   304  
   305  // MethodMetrics is a bundle of common metrics with a uniform naming scheme.
   306  type MethodMetrics struct {
   307  	Errors         tally.Counter
   308  	Success        tally.Counter
   309  	ErrorsLatency  tally.Timer
   310  	SuccessLatency tally.Timer
   311  }
   312  
   313  // ReportSuccess reports a success.
   314  func (m *MethodMetrics) ReportSuccess(d time.Duration) {
   315  	m.Success.Inc(1)
   316  	m.SuccessLatency.Record(d)
   317  }
   318  
   319  // ReportError reports an error.
   320  func (m *MethodMetrics) ReportError(d time.Duration) {
   321  	m.Errors.Inc(1)
   322  	m.ErrorsLatency.Record(d)
   323  }
   324  
   325  // ReportSuccessOrError increments Error/Success counter dependending on the error.
   326  func (m *MethodMetrics) ReportSuccessOrError(e error, d time.Duration) {
   327  	if e != nil {
   328  		m.ReportError(d)
   329  	} else {
   330  		m.ReportSuccess(d)
   331  	}
   332  }
   333  
   334  // NewMethodMetrics returns a new Method metrics for the given method name.
   335  func NewMethodMetrics(scope tally.Scope, methodName string, opts TimerOptions) MethodMetrics {
   336  	return MethodMetrics{
   337  		Errors:         scope.Counter(methodName + ".errors"),
   338  		Success:        scope.Counter(methodName + ".success"),
   339  		ErrorsLatency:  NewTimer(scope, methodName+".errors-latency", opts),
   340  		SuccessLatency: NewTimer(scope, methodName+".success-latency", opts),
   341  	}
   342  }
   343  
   344  // BatchMethodMetrics is a bundle of common metrics for methods with batch semantics.
   345  type BatchMethodMetrics struct {
   346  	RetryableErrors    tally.Counter
   347  	NonRetryableErrors tally.Counter
   348  	Errors             tally.Counter
   349  	Success            tally.Counter
   350  	Latency            tally.Timer
   351  }
   352  
   353  // NewBatchMethodMetrics creates new batch method metrics.
   354  func NewBatchMethodMetrics(
   355  	scope tally.Scope,
   356  	methodName string,
   357  	opts TimerOptions,
   358  ) BatchMethodMetrics {
   359  	return BatchMethodMetrics{
   360  		RetryableErrors:    scope.Counter(methodName + ".retryable-errors"),
   361  		NonRetryableErrors: scope.Counter(methodName + ".non-retryable-errors"),
   362  		Errors:             scope.Counter(methodName + ".errors"),
   363  		Success:            scope.Counter(methodName + ".success"),
   364  		Latency:            NewTimer(scope, methodName+".latency", opts),
   365  	}
   366  }
   367  
   368  // ReportSuccess reports successess.
   369  func (m *BatchMethodMetrics) ReportSuccess(n int) {
   370  	m.Success.Inc(int64(n))
   371  }
   372  
   373  // ReportRetryableErrors reports retryable errors.
   374  func (m *BatchMethodMetrics) ReportRetryableErrors(n int) {
   375  	m.RetryableErrors.Inc(int64(n))
   376  	m.Errors.Inc(int64(n))
   377  }
   378  
   379  // ReportNonRetryableErrors reports non-retryable errors.
   380  func (m *BatchMethodMetrics) ReportNonRetryableErrors(n int) {
   381  	m.NonRetryableErrors.Inc(int64(n))
   382  	m.Errors.Inc(int64(n))
   383  }
   384  
   385  // ReportLatency reports latency.
   386  func (m *BatchMethodMetrics) ReportLatency(d time.Duration) {
   387  	m.Latency.Record(d)
   388  }
   389  
   390  type noopStopwatchRecorder struct{}
   391  
   392  func (noopStopwatchRecorder) RecordStopwatch(_ time.Time) {}