statsd-instrument 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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