statsd-instrument 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/benchmark.yml +32 -0
  3. data/.github/workflows/ci.yml +24 -8
  4. data/.rubocop.yml +24 -0
  5. data/CHANGELOG.md +116 -3
  6. data/CONTRIBUTING.md +8 -6
  7. data/Gemfile +3 -0
  8. data/Rakefile +1 -1
  9. data/benchmark/README.md +29 -0
  10. data/benchmark/send-metrics-to-dev-null-log +47 -0
  11. data/benchmark/send-metrics-to-local-udp-receiver +57 -0
  12. data/lib/statsd/instrument.rb +126 -94
  13. data/lib/statsd/instrument/assertions.rb +69 -37
  14. data/lib/statsd/instrument/backends/capture_backend.rb +2 -0
  15. data/lib/statsd/instrument/helpers.rb +12 -8
  16. data/lib/statsd/instrument/metric.rb +56 -42
  17. data/lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb +46 -0
  18. data/lib/statsd/instrument/rubocop/metric_return_value.rb +31 -0
  19. data/lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb +45 -0
  20. data/lib/statsd/instrument/rubocop/positional_arguments.rb +99 -0
  21. data/lib/statsd/instrument/rubocop/splat_arguments.rb +37 -0
  22. data/lib/statsd/instrument/strict.rb +145 -0
  23. data/lib/statsd/instrument/version.rb +1 -1
  24. data/test/assertions_test.rb +37 -0
  25. data/test/benchmark/clock_gettime.rb +27 -0
  26. data/test/benchmark/default_tags.rb +1 -1
  27. data/test/deprecations_test.rb +86 -0
  28. data/test/helpers/rubocop_helper.rb +47 -0
  29. data/test/integration_test.rb +6 -2
  30. data/test/matchers_test.rb +9 -9
  31. data/test/metric_test.rb +3 -18
  32. data/test/rubocop/metaprogramming_positional_arguments_test.rb +58 -0
  33. data/test/rubocop/metric_return_value_test.rb +78 -0
  34. data/test/rubocop/metric_value_keyword_argument_test.rb +39 -0
  35. data/test/rubocop/positional_arguments_test.rb +110 -0
  36. data/test/rubocop/splat_arguments_test.rb +27 -0
  37. data/test/statsd_instrumentation_test.rb +77 -86
  38. data/test/statsd_test.rb +32 -65
  39. data/test/test_helper.rb +12 -1
  40. data/test/udp_backend_test.rb +8 -0
  41. metadata +28 -2
@@ -62,26 +62,26 @@ module StatsD
62
62
  metric_name.respond_to?(:call) ? metric_name.call(callee, args).gsub('::', '.') : metric_name.gsub('::', '.')
63
63
  end
64
64
 
65
- if Process.respond_to?(:clock_gettime)
66
- # @private
67
- def self.current_timestamp
68
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
69
- end
70
- else
71
- # @private
72
- def self.current_timestamp
73
- Time.now
74
- end
65
+ # Even though this method is considered private, and is no longer used internally,
66
+ # applications in the wild rely on it. As a result, we cannot remove this method
67
+ # until the next major version.
68
+ #
69
+ # @deprecated Use Process.clock_gettime(Process::CLOCK_MONOTONIC) instead.
70
+ def self.current_timestamp
71
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
75
72
  end
76
73
 
77
74
  # Even though this method is considered private, and is no longer used internally,
78
75
  # applications in the wild rely on it. As a result, we cannot remove this method
79
76
  # until the next major version.
80
- # @private
77
+ #
78
+ # @deprecated You can implement similar functionality yourself using
79
+ # `Process.clock_gettime(Process::CLOCK_MONOTONIC)`. Think about what will
80
+ # happen if an exception happens during the block execution though.
81
81
  def self.duration
82
- start = current_timestamp
82
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
83
83
  yield
84
- current_timestamp - start
84
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
85
85
  end
86
86
 
87
87
  # Adds execution duration instrumentation to a method as a timing.
@@ -91,11 +91,17 @@ module StatsD
91
91
  # callable to dynamically generate a metric name
92
92
  # @param metric_options (see StatsD#measure)
93
93
  # @return [void]
94
- def statsd_measure(method, name, *metric_options)
94
+ def statsd_measure(method, name, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil, as_dist: false,
95
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg, prefix: StatsD.prefix, no_prefix: false)
96
+
95
97
  add_to_method(method, name, :measure) do
96
98
  define_method(method) do |*args, &block|
97
- metric_name = StatsD::Instrument.generate_metric_name(name, self, *args)
98
- StatsD.measure(metric_name, *metric_options) { super(*args, &block) }
99
+ key = StatsD::Instrument.generate_metric_name(name, self, *args)
100
+ StatsD.measure(
101
+ key, sample_rate: sample_rate, tags: tags, prefix: prefix, no_prefix: no_prefix, as_dist: as_dist
102
+ ) do
103
+ super(*args, &block)
104
+ end
99
105
  end
100
106
  end
101
107
  end
@@ -108,11 +114,15 @@ module StatsD
108
114
  # @param metric_options (see StatsD#measure)
109
115
  # @return [void]
110
116
  # @note Supported by the datadog implementation only (in beta)
111
- def statsd_distribution(method, name, *metric_options)
117
+ def statsd_distribution(method, name, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
118
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg, prefix: StatsD.prefix, no_prefix: false)
119
+
112
120
  add_to_method(method, name, :distribution) do
113
121
  define_method(method) do |*args, &block|
114
- metric_name = StatsD::Instrument.generate_metric_name(name, self, *args)
115
- StatsD.distribution(metric_name, *metric_options) { super(*args, &block) }
122
+ key = StatsD::Instrument.generate_metric_name(name, self, *args)
123
+ StatsD.distribution(key, sample_rate: sample_rate, tags: tags, prefix: prefix, no_prefix: no_prefix) do
124
+ super(*args, &block)
125
+ end
116
126
  end
117
127
  end
118
128
  end
@@ -132,7 +142,9 @@ module StatsD
132
142
  # @yieldreturn [Boolean] Return true iff the return value is consisered a success, false otherwise.
133
143
  # @return [void]
134
144
  # @see #statsd_count_if
135
- def statsd_count_success(method, name, *metric_options)
145
+ def statsd_count_success(method, name, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
146
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg, prefix: StatsD.prefix, no_prefix: false)
147
+
136
148
  add_to_method(method, name, :count_success) do
137
149
  define_method(method) do |*args, &block|
138
150
  begin
@@ -151,8 +163,8 @@ module StatsD
151
163
  result
152
164
  ensure
153
165
  suffix = truthiness == false ? 'failure' : 'success'
154
- metric_name = "#{StatsD::Instrument.generate_metric_name(name, self, *args)}.#{suffix}"
155
- StatsD.increment(metric_name, 1, *metric_options)
166
+ key = "#{StatsD::Instrument.generate_metric_name(name, self, *args)}.#{suffix}"
167
+ StatsD.increment(key, sample_rate: sample_rate, tags: tags, prefix: prefix, no_prefix: no_prefix)
156
168
  end
157
169
  end
158
170
  end
@@ -165,13 +177,14 @@ module StatsD
165
177
  #
166
178
  # @param method (see #statsd_measure)
167
179
  # @param name (see #statsd_measure)
168
- # @param metric_options (see #statsd_measure)
169
180
  # @yield (see #statsd_count_success)
170
181
  # @yieldparam result (see #statsd_count_success)
171
182
  # @yieldreturn (see #statsd_count_success)
172
183
  # @return [void]
173
184
  # @see #statsd_count_success
174
- def statsd_count_if(method, name, *metric_options)
185
+ def statsd_count_if(method, name, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
186
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg, prefix: StatsD.prefix, no_prefix: false)
187
+
175
188
  add_to_method(method, name, :count_if) do
176
189
  define_method(method) do |*args, &block|
177
190
  begin
@@ -190,8 +203,8 @@ module StatsD
190
203
  result
191
204
  ensure
192
205
  if truthiness
193
- metric_name = StatsD::Instrument.generate_metric_name(name, self, *args)
194
- StatsD.increment(metric_name, *metric_options)
206
+ key = StatsD::Instrument.generate_metric_name(name, self, *args)
207
+ StatsD.increment(key, sample_rate: sample_rate, tags: tags, prefix: prefix, no_prefix: no_prefix)
195
208
  end
196
209
  end
197
210
  end
@@ -207,11 +220,13 @@ module StatsD
207
220
  # @param name (see #statsd_measure)
208
221
  # @param metric_options (see #statsd_measure)
209
222
  # @return [void]
210
- def statsd_count(method, name, *metric_options)
223
+ def statsd_count(method, name, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
224
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg, prefix: StatsD.prefix, no_prefix: false)
225
+
211
226
  add_to_method(method, name, :count) do
212
227
  define_method(method) do |*args, &block|
213
- metric_name = StatsD::Instrument.generate_metric_name(name, self, *args)
214
- StatsD.increment(metric_name, 1, *metric_options)
228
+ key = StatsD::Instrument.generate_metric_name(name, self, *args)
229
+ StatsD.increment(key, sample_rate: sample_rate, tags: tags, prefix: prefix, no_prefix: no_prefix)
215
230
  super(*args, &block)
216
231
  end
217
232
  end
@@ -346,19 +361,25 @@ module StatsD
346
361
  # http_response = StatsD.measure('HTTP.call.duration') do
347
362
  # HTTP.get(url)
348
363
  # end
349
- def measure(key, value = nil, *metric_options, &block)
350
- value, metric_options = parse_options(value, metric_options)
351
- type = (!metric_options.empty? && metric_options.first[:as_dist] ? :d : :ms)
352
-
353
- return collect_metric(type, key, value, metric_options) unless block_given?
364
+ def measure(
365
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
366
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
367
+ prefix: StatsD.prefix, no_prefix: false, as_dist: false,
368
+ &block
369
+ )
370
+ prefix = nil if no_prefix
371
+ type = as_dist ? :d : :ms
372
+ unless block_given?
373
+ return collect_metric(type, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix, &block)
374
+ end
354
375
 
355
- start = StatsD::Instrument.current_timestamp
376
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
356
377
  begin
357
378
  block.call
358
379
  ensure
359
380
  # Ensure catches both a raised exception and a return in the invoked block
360
- value = 1000 * (StatsD::Instrument.current_timestamp - start)
361
- collect_metric(type, key, value, metric_options)
381
+ value = 1000.0 * (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
382
+ collect_metric(type, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
362
383
  end
363
384
  end
364
385
 
@@ -372,9 +393,14 @@ module StatsD
372
393
  # should know how to handle it.
373
394
  #
374
395
  # @param metric_options [Hash] (default: {}) Metric options
375
- # @return (see #collect_metric)
376
- def increment(key, value = 1, *metric_options)
377
- collect_metric(:c, key, value, metric_options)
396
+ # @return [void]
397
+ def increment(
398
+ key, value_arg = 1, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
399
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
400
+ prefix: StatsD.prefix, no_prefix: false
401
+ )
402
+ prefix = nil if no_prefix
403
+ collect_metric(:c, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
378
404
  end
379
405
 
380
406
  # Emits a gauge metric.
@@ -382,8 +408,13 @@ module StatsD
382
408
  # @param value [Numeric] The current value to record.
383
409
  # @param metric_options [Hash] (default: {}) Metric options
384
410
  # @return (see #collect_metric)
385
- def gauge(key, value, *metric_options)
386
- collect_metric(:g, key, value, metric_options)
411
+ def gauge(
412
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
413
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
414
+ prefix: StatsD.prefix, no_prefix: false
415
+ )
416
+ prefix = nil if no_prefix
417
+ collect_metric(:g, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
387
418
  end
388
419
 
389
420
  # Emits a histogram metric.
@@ -392,8 +423,13 @@ module StatsD
392
423
  # @param metric_options [Hash] (default: {}) Metric options
393
424
  # @return (see #collect_metric)
394
425
  # @note Supported by the datadog implementation only.
395
- def histogram(key, value, *metric_options)
396
- collect_metric(:h, key, value, metric_options)
426
+ def histogram(
427
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
428
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
429
+ prefix: StatsD.prefix, no_prefix: false
430
+ )
431
+ prefix = nil if no_prefix
432
+ collect_metric(:h, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
397
433
  end
398
434
 
399
435
  # Emits a distribution metric.
@@ -416,18 +452,14 @@ module StatsD
416
452
  # http_response = StatsD.distribution('HTTP.call.duration') do
417
453
  # HTTP.get(url)
418
454
  # end
419
- def distribution(key, value = nil, *metric_options, &block)
420
- value, metric_options = parse_options(value, metric_options)
421
-
422
- return collect_metric(:d, key, value, metric_options) unless block_given?
423
-
424
- start = StatsD::Instrument.current_timestamp
425
- begin
426
- block.call
427
- ensure
428
- value = 1000 * (StatsD::Instrument.current_timestamp - start)
429
- collect_metric(:d, key, value, metric_options)
430
- end
455
+ def distribution(
456
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
457
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
458
+ prefix: StatsD.prefix, no_prefix: false,
459
+ &block
460
+ )
461
+ prefix = nil if no_prefix
462
+ measure(key, value, as_dist: true, sample_rate: sample_rate, tags: tags, prefix: prefix, &block)
431
463
  end
432
464
 
433
465
  # Emits a key/value metric.
@@ -436,8 +468,12 @@ module StatsD
436
468
  # @param metric_options [Hash] (default: {}) Metric options
437
469
  # @return (see #collect_metric)
438
470
  # @note Supported by the statsite implementation only.
439
- def key_value(key, value, *metric_options)
440
- collect_metric(:kv, key, value, metric_options)
471
+ def key_value(
472
+ key, value_arg = nil, deprecated_sample_rate_arg = nil,
473
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, no_prefix: false
474
+ )
475
+ prefix = nil if no_prefix
476
+ collect_metric(:kv, key, value, sample_rate: sample_rate, prefix: prefix)
441
477
  end
442
478
 
443
479
  # Emits a set metric.
@@ -446,8 +482,13 @@ module StatsD
446
482
  # @param metric_options [Hash] (default: {}) Metric options
447
483
  # @return (see #collect_metric)
448
484
  # @note Supported by the datadog implementation only.
449
- def set(key, value, *metric_options)
450
- collect_metric(:s, key, value, metric_options)
485
+ def set(
486
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
487
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
488
+ prefix: StatsD.prefix, no_prefix: false
489
+ )
490
+ prefix = nil if no_prefix
491
+ collect_metric(:s, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
451
492
  end
452
493
 
453
494
  # Emits an event metric.
@@ -456,8 +497,14 @@ module StatsD
456
497
  # @param metric_options [Hash] (default: {}) Metric options
457
498
  # @return (see #collect_metric)
458
499
  # @note Supported by the datadog implementation only.
459
- def event(title, text, *metric_options)
460
- collect_metric(:_e, title, text, metric_options)
500
+ def event(
501
+ title, text,
502
+ deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
503
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
504
+ prefix: StatsD.prefix, no_prefix: false, **metadata
505
+ )
506
+ prefix = nil if no_prefix
507
+ collect_metric(:_e, title, text, sample_rate: sample_rate, tags: tags, prefix: prefix, metadata: metadata)
461
508
  end
462
509
 
463
510
  # Emits a service check metric.
@@ -466,44 +513,29 @@ module StatsD
466
513
  # @param metric_options [Hash] (default: {}) Metric options
467
514
  # @return (see #collect_metric)
468
515
  # @note Supported by the datadog implementation only.
469
- def service_check(name, status, *metric_options)
470
- collect_metric(:_sc, name, status, metric_options)
516
+ def service_check(
517
+ name, status,
518
+ deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
519
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
520
+ prefix: StatsD.prefix, no_prefix: false, **metadata
521
+ )
522
+ prefix = nil if no_prefix
523
+ collect_metric(:_sc, name, status, sample_rate: sample_rate, tags: tags, prefix: prefix, metadata: metadata)
471
524
  end
472
525
 
473
526
  private
474
527
 
475
- # Converts old-style ordered arguments in an argument hash for backwards compatibility.
476
- # @param args [Array] The list of non-required arguments.
477
- # @return [Hash] The hash of optional arguments.
478
- def hash_argument(args)
479
- return {} if args.empty?
480
- return args.first if args.length == 1 && args.first.is_a?(Hash)
481
-
482
- order = [:sample_rate, :tags]
483
- hash = {}
484
- args.each_with_index do |value, index|
485
- hash[order[index]] = value
486
- end
487
- hash
488
- end
489
-
490
- def parse_options(value, metric_options)
491
- if value.is_a?(Hash) && metric_options.empty?
492
- metric_options = [value]
493
- value = value.fetch(:value, nil)
494
- end
495
- [value, metric_options]
496
- end
497
-
498
528
  # Instantiates a metric, and sends it to the backend for further processing.
499
529
  # @param options (see StatsD::Instrument::Metric#initialize)
500
- # @return [StatsD::Instrument::Metric] The metric that was sent to the backend.
501
- def collect_metric(type, name, value, metric_options)
502
- value, metric_options = parse_options(value, metric_options)
503
-
504
- options = hash_argument(metric_options).merge(type: type, name: name, value: value)
505
- backend.collect_metric(metric = StatsD::Instrument::Metric.new(options))
506
- metric
530
+ # @return [void]
531
+ def collect_metric(type, name, value, sample_rate:, tags: nil, prefix:, metadata: nil)
532
+ sample_rate ||= default_sample_rate
533
+ name = "#{prefix}.#{name}" if prefix
534
+
535
+ metric = StatsD::Instrument::Metric.new(type: type, name: name, value: value,
536
+ sample_rate: sample_rate, tags: tags, metadata: metadata)
537
+ backend.collect_metric(metric)
538
+ metric # TODO: return `nil` in the next major version
507
539
  end
508
540
  end
509
541
 
@@ -39,51 +39,83 @@ module StatsD::Instrument::Assertions
39
39
 
40
40
  # @private
41
41
  def assert_statsd_calls(expected_metrics, &block)
42
- unless block
43
- raise ArgumentError, "block must be given"
44
- end
45
-
46
- metrics = capture_statsd_calls(&block)
47
- matched_expected_metrics = []
48
-
49
- expected_metrics.each do |expected_metric|
50
- expected_metric_times = expected_metric.times
51
- expected_metric_times_remaining = expected_metric.times
52
- filtered_metrics = metrics.select { |m| m.type == expected_metric.type && m.name == expected_metric.name }
53
- refute(filtered_metrics.empty?,
54
- "No StatsD calls for metric #{expected_metric.name} of type #{expected_metric.type} were made.")
55
-
56
- filtered_metrics.each do |metric|
57
- next unless expected_metric.matches(metric)
58
-
59
- assert within_numeric_range?(metric.sample_rate),
60
- "Unexpected sample rate type for metric #{metric.name}, must be numeric"
61
-
62
- assert(expected_metric_times_remaining > 0,
63
- "Unexpected StatsD call; number of times this metric was expected exceeded: #{expected_metric.inspect}")
64
-
65
- expected_metric_times_remaining -= 1
66
- metrics.delete(metric)
67
- if expected_metric_times_remaining == 0
68
- matched_expected_metrics << expected_metric
42
+ raise ArgumentError, "block must be given" unless block_given?
43
+
44
+ capture_backend = StatsD::Instrument::Backends::CaptureBackend.new
45
+ with_capture_backend(capture_backend) do
46
+ exception_occurred = nil
47
+ begin
48
+ block.call
49
+ rescue => exception
50
+ exception_occurred = exception
51
+ raise
52
+ ensure
53
+ metrics = capture_backend.collected_metrics
54
+ matched_expected_metrics = []
55
+ expected_metrics.each do |expected_metric|
56
+ expected_metric_times = expected_metric.times
57
+ expected_metric_times_remaining = expected_metric.times
58
+ filtered_metrics = metrics.select { |m| m.type == expected_metric.type && m.name == expected_metric.name }
59
+
60
+ if filtered_metrics.empty?
61
+ flunk_with_exception_info(exception_occurred, "No StatsD calls for metric #{expected_metric.name} " \
62
+ "of type #{expected_metric.type} were made.")
63
+ end
64
+
65
+ filtered_metrics.each do |metric|
66
+ next unless expected_metric.matches(metric)
67
+
68
+ assert(within_numeric_range?(metric.sample_rate),
69
+ "Unexpected sample rate type for metric #{metric.name}, must be numeric")
70
+
71
+ if expected_metric_times_remaining == 0
72
+ flunk_with_exception_info(exception_occurred, "Unexpected StatsD call; number of times this metric " \
73
+ "was expected exceeded: #{expected_metric.inspect}")
74
+ end
75
+
76
+ expected_metric_times_remaining -= 1
77
+ metrics.delete(metric)
78
+ if expected_metric_times_remaining == 0
79
+ matched_expected_metrics << expected_metric
80
+ end
81
+ end
82
+
83
+ next if expected_metric_times_remaining == 0
84
+
85
+ msg = +"Metric expected #{expected_metric_times} times but seen " \
86
+ "#{expected_metric_times - expected_metric_times_remaining} " \
87
+ "times: #{expected_metric.inspect}."
88
+ msg << "\nCaptured metrics with the same key: #{filtered_metrics}" if filtered_metrics.any?
89
+ flunk_with_exception_info(exception_occurred, msg)
69
90
  end
70
- end
91
+ expected_metrics -= matched_expected_metrics
71
92
 
72
- msg = +"Metric expected #{expected_metric_times} times but seen " \
73
- "#{expected_metric_times - expected_metric_times_remaining} " \
74
- "times: #{expected_metric.inspect}."
75
- msg << "\nCaptured metrics with the same key: #{filtered_metrics}" if filtered_metrics.any?
93
+ unless expected_metrics.empty?
94
+ flunk_with_exception_info(exception_occurred, "Unexpected StatsD calls; the following metric expectations " \
95
+ "were not satisfied: #{expected_metrics.inspect}")
96
+ end
76
97
 
77
- assert(expected_metric_times_remaining == 0, msg)
98
+ pass
99
+ end
78
100
  end
79
- expected_metrics -= matched_expected_metrics
80
-
81
- assert(expected_metrics.empty?,
82
- "Unexpected StatsD calls; the following metric expectations were not satisfied: #{expected_metrics.inspect}")
83
101
  end
84
102
 
85
103
  private
86
104
 
105
+ def flunk_with_exception_info(exception, message)
106
+ if exception
107
+ flunk(<<~EXCEPTION)
108
+ #{message}
109
+
110
+ This could be due to the exception that occurred inside the block:
111
+ #{exception.class.name}: #{exception.message}
112
+ \t#{exception.backtrace.join("\n\t")}
113
+ EXCEPTION
114
+ else
115
+ flunk(message)
116
+ end
117
+ end
118
+
87
119
  def assert_statsd_call(metric_type, metric_name, options = {}, &block)
88
120
  options[:name] = metric_name
89
121
  options[:type] = metric_type