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 }