go.uber.org/cadence@v1.2.9/internal/common/metrics/scope.go (about)

     1  // Copyright (c) 2017 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 metrics
    22  
    23  import (
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/uber-go/tally"
    28  )
    29  
    30  type (
    31  	// Clock defines interface used as time source
    32  	Clock interface {
    33  		Now() time.Time
    34  	}
    35  
    36  	durationRecorder interface {
    37  		RecordDuration(duration time.Duration)
    38  	}
    39  
    40  	replayAwareScope struct {
    41  		isReplay *bool
    42  		scope    tally.Scope
    43  		clock    Clock
    44  	}
    45  
    46  	replayAwareCounter struct {
    47  		isReplay *bool
    48  		counter  tally.Counter
    49  	}
    50  
    51  	replayAwareGauge struct {
    52  		isReplay *bool
    53  		gauge    tally.Gauge
    54  	}
    55  
    56  	replayAwareTimer struct {
    57  		isReplay *bool
    58  		timer    tally.Timer
    59  		clock    Clock
    60  	}
    61  
    62  	replayAwareHistogram struct {
    63  		isReplay  *bool
    64  		histogram tally.Histogram
    65  		clock     Clock
    66  	}
    67  
    68  	replayAwareStopwatchRecorder struct {
    69  		isReplay *bool
    70  		recorder durationRecorder
    71  		clock    Clock
    72  	}
    73  
    74  	// TaggedScope provides metricScope with tags
    75  	TaggedScope struct {
    76  		tally.Scope
    77  		*sync.Map
    78  	}
    79  )
    80  
    81  // WrapScope wraps a scope and skip recording metrics when isReplay is true.
    82  // This is designed to be used by only by workflowEnvironmentImpl so we suppress metrics while replaying history events.
    83  // Parameter isReplay is a pointer to workflowEnvironmentImpl.isReplay which will be updated when replaying history events.
    84  func WrapScope(isReplay *bool, scope tally.Scope, clock Clock) tally.Scope {
    85  	return &replayAwareScope{isReplay, scope, clock}
    86  }
    87  
    88  // Inc increments the counter by a delta.
    89  func (c *replayAwareCounter) Inc(delta int64) {
    90  	if *c.isReplay {
    91  		return
    92  	}
    93  	c.counter.Inc(delta)
    94  }
    95  
    96  // Update sets the gauges absolute value.
    97  func (g *replayAwareGauge) Update(value float64) {
    98  	if *g.isReplay {
    99  		return
   100  	}
   101  	g.gauge.Update(value)
   102  }
   103  
   104  // Record a specific duration.
   105  func (t *replayAwareTimer) Record(value time.Duration) {
   106  	if *t.isReplay {
   107  		return
   108  	}
   109  	t.timer.Record(value)
   110  }
   111  
   112  // Record a specific duration.
   113  func (t *replayAwareTimer) RecordDuration(duration time.Duration) {
   114  	t.Record(duration)
   115  }
   116  
   117  // Start gives you back a specific point in time to report via Stop.
   118  func (t *replayAwareTimer) Start() tally.Stopwatch {
   119  	return tally.NewStopwatch(t.clock.Now(), &replayAwareStopwatchRecorder{t.isReplay, t, t.clock})
   120  }
   121  
   122  // RecordValue records a specific value directly. Will use the configured value buckets for the histogram.
   123  func (h *replayAwareHistogram) RecordValue(value float64) {
   124  	if *h.isReplay {
   125  		return
   126  	}
   127  	h.histogram.RecordValue(value)
   128  }
   129  
   130  // RecordDuration records a specific duration directly.
   131  // Will use the configured duration buckets for the histogram.
   132  func (h *replayAwareHistogram) RecordDuration(value time.Duration) {
   133  	if *h.isReplay {
   134  		return
   135  	}
   136  	h.histogram.RecordDuration(value)
   137  }
   138  
   139  // Start gives you a specific point in time to then record a duration.
   140  // Will use the configured duration buckets for the histogram.
   141  func (h *replayAwareHistogram) Start() tally.Stopwatch {
   142  	return tally.NewStopwatch(h.clock.Now(), &replayAwareStopwatchRecorder{h.isReplay, h, h.clock})
   143  }
   144  
   145  // StopwatchRecorder is a recorder that is called when a stopwatch is stopped with Stop().
   146  func (r *replayAwareStopwatchRecorder) RecordStopwatch(stopwatchStart time.Time) {
   147  	if *r.isReplay {
   148  		return
   149  	}
   150  	d := r.clock.Now().Sub(stopwatchStart)
   151  	r.recorder.RecordDuration(d)
   152  }
   153  
   154  // Counter returns the Counter object corresponding to the name.
   155  func (s *replayAwareScope) Counter(name string) tally.Counter {
   156  	return &replayAwareCounter{s.isReplay, s.scope.Counter(name)}
   157  }
   158  
   159  // Gauge returns the Gauge object corresponding to the name.
   160  func (s *replayAwareScope) Gauge(name string) tally.Gauge {
   161  	return &replayAwareGauge{s.isReplay, s.scope.Gauge(name)}
   162  }
   163  
   164  // Timer returns the Timer object corresponding to the name.
   165  func (s *replayAwareScope) Timer(name string) tally.Timer {
   166  	return &replayAwareTimer{s.isReplay, s.scope.Timer(name), s.clock}
   167  }
   168  
   169  // Histogram returns the Histogram object corresponding to the name.
   170  // To use default value and duration buckets configured for the scope
   171  // simply pass tally.DefaultBuckets or nil.
   172  // You can use tally.ValueBuckets{x, y, ...} for value buckets.
   173  // You can use tally.DurationBuckets{x, y, ...} for duration buckets.
   174  // You can use tally.MustMakeLinearValueBuckets(start, width, count) for linear values.
   175  // You can use tally.MustMakeLinearDurationBuckets(start, width, count) for linear durations.
   176  // You can use tally.MustMakeExponentialValueBuckets(start, factor, count) for exponential values.
   177  // You can use tally.MustMakeExponentialDurationBuckets(start, factor, count) for exponential durations.
   178  func (s *replayAwareScope) Histogram(name string, buckets tally.Buckets) tally.Histogram {
   179  	return &replayAwareHistogram{s.isReplay, s.scope.Histogram(name, buckets), s.clock}
   180  }
   181  
   182  // Tagged returns a new child scope with the given tags and current tags.
   183  func (s *replayAwareScope) Tagged(tags map[string]string) tally.Scope {
   184  	return &replayAwareScope{s.isReplay, s.scope.Tagged(tags), s.clock}
   185  }
   186  
   187  // SubScope returns a new child scope appending a further name prefix.
   188  func (s *replayAwareScope) SubScope(name string) tally.Scope {
   189  	return &replayAwareScope{s.isReplay, s.scope.SubScope(name), s.clock}
   190  }
   191  
   192  // Capabilities returns a description of metrics reporting capabilities.
   193  func (s *replayAwareScope) Capabilities() tally.Capabilities {
   194  	return s.scope.Capabilities()
   195  }
   196  
   197  // GetTaggedScope return a scope with one or multiple tags,
   198  // input should be key value pairs like: GetTaggedScope(tag1, val1, tag2, val2).
   199  func (ts *TaggedScope) GetTaggedScope(keyValueinPairs ...string) tally.Scope {
   200  	if len(keyValueinPairs)%2 != 0 {
   201  		panic("GetTaggedScope key value are not in pairs")
   202  	}
   203  	if ts.Map == nil {
   204  		ts.Map = &sync.Map{}
   205  	}
   206  
   207  	key := ""
   208  	tagsMap := map[string]string{}
   209  	for i := 0; i < len(keyValueinPairs); i += 2 {
   210  		tagName := keyValueinPairs[i]
   211  		tagValue := keyValueinPairs[i+1]
   212  		key = key + tagName + ":" + tagValue + "-" // used to prevent collision of tagValue (map key) for different tagName
   213  		tagsMap[tagName] = tagValue
   214  	}
   215  
   216  	taggedScope, ok := ts.Load(key)
   217  	if !ok {
   218  		ts.Store(key, ts.Scope.Tagged(tagsMap))
   219  		taggedScope, _ = ts.Load(key)
   220  	}
   221  	if taggedScope == nil {
   222  		panic("metric scope cannot be tagged") // This should never happen
   223  	}
   224  
   225  	return taggedScope.(tally.Scope)
   226  }
   227  
   228  // NewTaggedScope create a new TaggedScope
   229  func NewTaggedScope(scope tally.Scope) *TaggedScope {
   230  	if scope == nil {
   231  		scope = tally.NoopScope
   232  	}
   233  	return &TaggedScope{Scope: scope, Map: &sync.Map{}}
   234  }