github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/x/metrics/test_reporter.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 xmetrics
    22  
    23  import (
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/uber-go/tally"
    28  )
    29  
    30  // TestStatsReporter is a test reporter that collects metrics and makes
    31  // them accessible for testing purposes
    32  type TestStatsReporter interface {
    33  	tally.StatsReporter
    34  	Counters() map[string]int64
    35  	Gauges() map[string]float64
    36  	Timers() map[string][]time.Duration
    37  	Events() []TestStatsReporterEvent
    38  }
    39  
    40  // TestStatsReporterEvent is an event that was reported to the test reporter
    41  type TestStatsReporterEvent interface {
    42  	Name() string
    43  	Tags() map[string]string
    44  	IsCounter() bool
    45  	IsGauge() bool
    46  	IsTimer() bool
    47  	Value() int64
    48  	TimerValue() time.Duration
    49  }
    50  
    51  // testStatsReporter should probably be moved to the tally project for better testing
    52  type testStatsReporter struct {
    53  	sync.RWMutex
    54  	counters       map[string]int64
    55  	gauges         map[string]float64
    56  	timers         map[string][]time.Duration
    57  	events         []*event
    58  	timersDisabled bool
    59  	captureEvents  bool
    60  }
    61  
    62  // NewTestStatsReporter returns a new TestStatsReporter
    63  func NewTestStatsReporter(opts TestStatsReporterOptions) TestStatsReporter {
    64  	return &testStatsReporter{
    65  		counters:       make(map[string]int64),
    66  		gauges:         make(map[string]float64),
    67  		timers:         make(map[string][]time.Duration),
    68  		timersDisabled: opts.TimersDisabled(),
    69  		captureEvents:  opts.CaptureEvents(),
    70  	}
    71  }
    72  
    73  func (r *testStatsReporter) Flush() {}
    74  
    75  func (r *testStatsReporter) ReportCounter(name string, tags map[string]string, value int64) {
    76  	r.Lock()
    77  	r.counters[name] += value
    78  	if r.captureEvents {
    79  		r.events = append(r.events, &event{
    80  			eventType: eventTypeCounter,
    81  			name:      name,
    82  			tags:      tags,
    83  			value:     value,
    84  		})
    85  	}
    86  	r.Unlock()
    87  }
    88  
    89  func (r *testStatsReporter) ReportGauge(name string, tags map[string]string, value float64) {
    90  	r.Lock()
    91  	r.gauges[name] = value
    92  	if r.captureEvents {
    93  		r.events = append(r.events, &event{
    94  			eventType: eventTypeGauge,
    95  			name:      name,
    96  			tags:      tags,
    97  			value:     int64(value),
    98  		})
    99  	}
   100  	r.Unlock()
   101  }
   102  
   103  func (r *testStatsReporter) ReportTimer(name string, tags map[string]string, interval time.Duration) {
   104  	r.Lock()
   105  	if r.captureEvents {
   106  		r.events = append(r.events, &event{
   107  			eventType:  eventTypeTimer,
   108  			name:       name,
   109  			tags:       tags,
   110  			timerValue: interval,
   111  		})
   112  	}
   113  	if r.timersDisabled {
   114  		r.Unlock()
   115  		return
   116  	}
   117  	if _, ok := r.timers[name]; !ok {
   118  		r.timers[name] = make([]time.Duration, 0, 1)
   119  	}
   120  	r.timers[name] = append(r.timers[name], interval)
   121  	r.Unlock()
   122  }
   123  
   124  func (r *testStatsReporter) ReportHistogramValueSamples(
   125  	name string,
   126  	tags map[string]string,
   127  	buckets tally.Buckets,
   128  	bucketLowerBound,
   129  	bucketUpperBound float64,
   130  	samples int64,
   131  ) {
   132  	// TODO: implement
   133  }
   134  
   135  func (r *testStatsReporter) ReportHistogramDurationSamples(
   136  	name string,
   137  	tags map[string]string,
   138  	buckets tally.Buckets,
   139  	bucketLowerBound,
   140  	bucketUpperBound time.Duration,
   141  	samples int64,
   142  ) {
   143  	// TODO: implement
   144  }
   145  
   146  func (r *testStatsReporter) Capabilities() tally.Capabilities {
   147  	return r
   148  }
   149  
   150  func (r *testStatsReporter) Reporting() bool {
   151  	return true
   152  }
   153  
   154  func (r *testStatsReporter) Tagging() bool {
   155  	return true
   156  }
   157  
   158  func (r *testStatsReporter) Counters() map[string]int64 {
   159  	r.RLock()
   160  	result := make(map[string]int64, len(r.counters))
   161  	for k, v := range r.counters {
   162  		result[k] = v
   163  	}
   164  	r.RUnlock()
   165  	return result
   166  }
   167  
   168  func (r *testStatsReporter) Gauges() map[string]float64 {
   169  	r.RLock()
   170  	result := make(map[string]float64, len(r.gauges))
   171  	for k, v := range r.gauges {
   172  		result[k] = v
   173  	}
   174  	r.RUnlock()
   175  	return result
   176  }
   177  
   178  func (r *testStatsReporter) Timers() map[string][]time.Duration {
   179  	r.RLock()
   180  	result := make(map[string][]time.Duration, len(r.timers))
   181  	for k, v := range r.timers {
   182  		result[k] = v
   183  	}
   184  	r.RUnlock()
   185  	return result
   186  }
   187  
   188  func (r *testStatsReporter) Events() []TestStatsReporterEvent {
   189  	r.RLock()
   190  	events := make([]TestStatsReporterEvent, len(r.events))
   191  	for i := range r.events {
   192  		events[i] = r.events[i]
   193  	}
   194  	r.RUnlock()
   195  	return events
   196  }
   197  
   198  type event struct {
   199  	eventType  eventType
   200  	name       string
   201  	tags       map[string]string
   202  	value      int64
   203  	timerValue time.Duration
   204  }
   205  
   206  type eventType int
   207  
   208  const (
   209  	eventTypeCounter eventType = iota
   210  	eventTypeGauge
   211  	eventTypeTimer
   212  )
   213  
   214  func (e *event) Name() string {
   215  	return e.name
   216  }
   217  
   218  func (e *event) Tags() map[string]string {
   219  	return e.tags
   220  }
   221  
   222  func (e *event) IsCounter() bool {
   223  	return e.eventType == eventTypeCounter
   224  }
   225  
   226  func (e *event) IsGauge() bool {
   227  	return e.eventType == eventTypeGauge
   228  }
   229  
   230  func (e *event) IsTimer() bool {
   231  	return e.eventType == eventTypeTimer
   232  }
   233  
   234  func (e *event) Value() int64 {
   235  	return e.value
   236  }
   237  
   238  func (e *event) TimerValue() time.Duration {
   239  	return e.timerValue
   240  }
   241  
   242  // TestStatsReporterOptions is a set of options for a test stats reporter
   243  type TestStatsReporterOptions interface {
   244  	// SetTimersDisable sets whether to disable timers
   245  	SetTimersDisable(value bool) TestStatsReporterOptions
   246  
   247  	// SetTimersDisable returns whether to disable timers
   248  	TimersDisabled() bool
   249  
   250  	// SetCaptureEvents sets whether to capture each event
   251  	SetCaptureEvents(value bool) TestStatsReporterOptions
   252  
   253  	// SetCaptureEvents returns whether to capture each event
   254  	CaptureEvents() bool
   255  }
   256  
   257  type testStatsReporterOptions struct {
   258  	timersDisabled bool
   259  	captureEvents  bool
   260  }
   261  
   262  // NewTestStatsReporterOptions creates a new set of options for a test stats reporter
   263  func NewTestStatsReporterOptions() TestStatsReporterOptions {
   264  	return &testStatsReporterOptions{
   265  		timersDisabled: true,
   266  		captureEvents:  false,
   267  	}
   268  }
   269  
   270  func (o *testStatsReporterOptions) SetTimersDisable(value bool) TestStatsReporterOptions {
   271  	opts := *o
   272  	opts.timersDisabled = value
   273  	return &opts
   274  }
   275  
   276  func (o *testStatsReporterOptions) TimersDisabled() bool {
   277  	return o.timersDisabled
   278  }
   279  
   280  func (o *testStatsReporterOptions) SetCaptureEvents(value bool) TestStatsReporterOptions {
   281  	opts := *o
   282  	opts.captureEvents = value
   283  	return &opts
   284  }
   285  
   286  func (o *testStatsReporterOptions) CaptureEvents() bool {
   287  	return o.captureEvents
   288  }