statsd-instrument 2.6.0 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +79 -9
  3. data/benchmark/datagram-client +0 -1
  4. data/benchmark/send-metrics-to-dev-null-log +0 -1
  5. data/lib/statsd/instrument.rb +66 -292
  6. data/lib/statsd/instrument/assertions.rb +83 -93
  7. data/lib/statsd/instrument/client.rb +2 -2
  8. data/lib/statsd/instrument/datagram.rb +12 -3
  9. data/lib/statsd/instrument/environment.rb +9 -3
  10. data/lib/statsd/instrument/expectation.rb +93 -0
  11. data/lib/statsd/instrument/helpers.rb +29 -11
  12. data/lib/statsd/instrument/legacy_client.rb +301 -0
  13. data/lib/statsd/instrument/metric.rb +8 -8
  14. data/lib/statsd/instrument/rubocop.rb +18 -0
  15. data/lib/statsd/instrument/rubocop/singleton_configuration.rb +53 -0
  16. data/lib/statsd/instrument/version.rb +1 -1
  17. data/test/assertions_on_legacy_client_test.rb +376 -0
  18. data/test/assertions_test.rb +105 -39
  19. data/test/capture_sink_test.rb +0 -2
  20. data/test/client_test.rb +0 -2
  21. data/test/compatibility/dogstatsd_datagram_compatibility_test.rb +0 -1
  22. data/test/datagram_builder_test.rb +1 -3
  23. data/test/datagram_test.rb +14 -0
  24. data/test/dogstatsd_datagram_builder_test.rb +0 -2
  25. data/test/environment_test.rb +1 -1
  26. data/test/helpers_test.rb +17 -0
  27. data/test/log_sink_test.rb +0 -2
  28. data/test/logger_backend_test.rb +2 -2
  29. data/test/metric_test.rb +2 -2
  30. data/test/null_sink_test.rb +0 -2
  31. data/test/rubocop/singleton_configuration_test.rb +43 -0
  32. data/test/statsd_datagram_builder_test.rb +0 -2
  33. data/test/statsd_instrumentation_test.rb +4 -6
  34. data/test/statsd_test.rb +1 -1
  35. data/test/test_helper.rb +0 -2
  36. data/test/udp_backend_test.rb +1 -1
  37. data/test/udp_sink_test.rb +0 -2
  38. metadata +11 -3
  39. data/lib/statsd/instrument/metric_expectation.rb +0 -82
@@ -0,0 +1,301 @@
1
+ # frozen_string_literal: true
2
+
3
+ class StatsD::Instrument::LegacyClient
4
+ def self.singleton
5
+ @singleton ||= new
6
+ end
7
+
8
+ attr_accessor :default_sample_rate, :prefix
9
+ attr_writer :backend
10
+ attr_reader :default_tags
11
+
12
+ def default_tags=(tags)
13
+ @default_tags = StatsD::Instrument::Metric.normalize_tags(tags)
14
+ end
15
+
16
+ def backend
17
+ @backend ||= StatsD::Instrument::Environment.default_backend
18
+ end
19
+
20
+ # @!method measure(name, value = nil, sample_rate: nil, tags: nil, &block)
21
+ #
22
+ # Emits a timing metric
23
+ #
24
+ # @param [String] key The name of the metric.
25
+ # @param sample_rate (see #increment)
26
+ # @param tags (see #increment)
27
+ #
28
+ # @example Providing a value directly
29
+ # start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
30
+ # do_something
31
+ # stop = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
32
+ # http_response = StatsD.measure('HTTP.call.duration', stop - start)
33
+ #
34
+ # @example Providing a block to measure the duration of its execution
35
+ # http_response = StatsD.measure('HTTP.call.duration') do
36
+ # Net::HTTP.get(url)
37
+ # end
38
+ #
39
+ # @overload measure(key, value, sample_rate: nil, tags: nil)
40
+ # Emits a timing metric, by providing a duration in milliseconds.
41
+ #
42
+ # @param [Float] value The measured duration in milliseconds
43
+ # @return [void]
44
+ #
45
+ # @overload measure(key, sample_rate: nil, tags: nil, &block)
46
+ # Emits a timing metric, after measuring the execution duration of the
47
+ # block passed to this method.
48
+ #
49
+ # @yield `StatsD.measure` will yield the block and measure the duration. After the block
50
+ # returns, the duration in millisecond will be emitted as metric.
51
+ # @return The value that was returned by the block passed through.
52
+ def measure(
53
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
54
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
55
+ prefix: self.prefix, no_prefix: false, as_dist: false,
56
+ &block
57
+ )
58
+ # TODO: in the next version, hardcode this to :ms when the as_dist argument is dropped.
59
+ type = as_dist ? :d : :ms
60
+ prefix = nil if no_prefix
61
+ if block_given?
62
+ measure_latency(type, key, sample_rate: sample_rate, tags: tags, prefix: prefix, &block)
63
+ else
64
+ collect_metric(type, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
65
+ end
66
+ end
67
+
68
+ # @!method increment(name, value = 1, sample_rate: nil, tags: nil)
69
+ #
70
+ # Emits a counter metric.
71
+ #
72
+ # @param key [String] The name of the metric.
73
+ # @param value [Integer] The value to increment the counter by.
74
+ #
75
+ # You should not compensate for the sample rate using the counter increment. E.g., if
76
+ # your sample rate is 0.01, you should <b>not</b> use 100 as increment to compensate for it.
77
+ # The sample rate is part of the packet that is being sent to the server, and the server
78
+ # should know how to handle it.
79
+ #
80
+ # @param sample_rate [Float] (default: `StatsD.default_sample_rate`) The rate at which to sample
81
+ # this metric call. This value should be between 0 and 1. This value can be used to reduce
82
+ # the amount of network I/O (and CPU cycles) used for very frequent metrics.
83
+ #
84
+ # - A value of `0.1` means that only 1 out of 10 calls will be emitted; the other 9 will
85
+ # be short-circuited.
86
+ # - When set to `1`, every metric will be emitted.
87
+ # - If this parameter is not set, the default sample rate for this client will be used.
88
+ # @param tags [Array<String>, Hash<Symbol, String>] The tags to associate with this measurement.
89
+ # They can be provided as an array of strings, or a hash of key/value pairs.
90
+ # _Note:_ Tags are not supported by all implementations.
91
+ # @return [void]
92
+ def increment(
93
+ key, value_arg = 1, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
94
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
95
+ prefix: self.prefix, no_prefix: false
96
+ )
97
+ prefix = nil if no_prefix
98
+ collect_metric(:c, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
99
+ end
100
+
101
+ # @!method gauge(name, value, sample_rate: nil, tags: nil)
102
+ #
103
+ # Emits a gauge metric.
104
+ #
105
+ # @param key The name of the metric.
106
+ # @param value [Numeric] The current value to record.
107
+ # @param sample_rate (see #increment)
108
+ # @param tags (see #increment)
109
+ # @return [void]
110
+ def gauge(
111
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
112
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
113
+ prefix: self.prefix, no_prefix: false
114
+ )
115
+ prefix = nil if no_prefix
116
+ collect_metric(:g, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
117
+ end
118
+
119
+ # @!method set(name, value, sample_rate: nil, tags: nil)
120
+ #
121
+ # Emits a set metric, which counts the number of distinct values that have occurred.
122
+ #
123
+ # @example Couning the number of unique visitors
124
+ # StatsD.set('visitors.unique', Current.user.id)
125
+ #
126
+ # @param key [String] The name of the metric.
127
+ # @param value [Numeric] The value to record.
128
+ # @param sample_rate (see #increment)
129
+ # @param tags (see #increment)
130
+ # @return [void]
131
+ def set(
132
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
133
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
134
+ prefix: self.prefix, no_prefix: false
135
+ )
136
+ prefix = nil if no_prefix
137
+ collect_metric(:s, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
138
+ end
139
+
140
+ # @!method histogram(name, value, sample_rate: nil, tags: nil)
141
+ #
142
+ # Emits a histogram metric.
143
+ #
144
+ # @param key The name of the metric.
145
+ # @param value [Numeric] The value to record.
146
+ # @param sample_rate (see #increment)
147
+ # @param tags (see #increment)
148
+ # @return (see #collect_metric)
149
+ # @note Supported by the datadog implementation only.
150
+ def histogram(
151
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
152
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
153
+ prefix: self.prefix, no_prefix: false
154
+ )
155
+ prefix = nil if no_prefix
156
+ collect_metric(:h, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
157
+ end
158
+
159
+ # @!method distribution(name, value = nil, sample_rate: nil, tags: nil, &block)
160
+ #
161
+ # Emits a distribution metric.
162
+ #
163
+ # @param [String] key The name of the metric.
164
+ # @param sample_rate (see #increment)
165
+ # @param tags (see #increment)
166
+ #
167
+ # @note Supported by the datadog implementation only.
168
+ # @example
169
+ # http_response = StatsD.distribution('HTTP.call.duration') do
170
+ # Net::HTTP.get(url)
171
+ # end
172
+ #
173
+ # @overload distribution(name, value, sample_rate: nil, tags: nil)
174
+ #
175
+ # Emits a distribution metric, given a provided value to record.
176
+ #
177
+ # @param [Numeric] value The value to record.
178
+ # @return [void]
179
+ #
180
+ # @overload distribution(key, metric_options = {}, &block)
181
+ #
182
+ # Emits a distribution metric for the duration of the provided block, in milliseconds.
183
+ #
184
+ # @yield `StatsD.distribution` will yield the block and measure the duration. After
185
+ # the block returns, the duration in millisecond will be emitted as metric.
186
+ # @return The value that was returned by the block passed through.
187
+ def distribution(
188
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
189
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
190
+ prefix: self.prefix, no_prefix: false,
191
+ &block
192
+ )
193
+ prefix = nil if no_prefix
194
+ if block_given?
195
+ measure_latency(:d, key, sample_rate: sample_rate, tags: tags, prefix: prefix, &block)
196
+ else
197
+ collect_metric(:d, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
198
+ end
199
+ end
200
+
201
+ # @!method key_value(name, value)
202
+ #
203
+ # Emits a key/value metric.
204
+ #
205
+ # @param key [String] The name of the metric.
206
+ # @param value [Numeric] The value to record.
207
+ # @return [void]
208
+ #
209
+ # @note Supported by the statsite implementation only.
210
+ def key_value(
211
+ key, value_arg = nil, deprecated_sample_rate_arg = nil,
212
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, no_prefix: false
213
+ )
214
+ prefix = nil if no_prefix
215
+ collect_metric(:kv, key, value, sample_rate: sample_rate, prefix: prefix)
216
+ end
217
+
218
+ # @!method event(title, text, tags: nil, hostname: nil, timestamp: nil, aggregation_key: nil, priority: nil, source_type_name: nil, alert_type: nil) # rubocop:disable Metrics/LineLength
219
+ #
220
+ # Emits an event.
221
+ #
222
+ # @param title [String] Title of the event. A configured prefix may be applied to this title.
223
+ # @param text [String] Body of the event. Can contain newlines.
224
+ # @param [String] hostname The hostname to associate with the event.
225
+ # @param [Time] timestamp The moment the status of the service was checkes. Defaults to now.
226
+ # @param [String] aggregation_key A key to aggregate similar events into groups.
227
+ # @param [String] priority The event's priority, either `"low"` or `"normal"` (default).
228
+ # @param [String] source_type_name The source type.
229
+ # @param [String] alert_type The type of alert. Either `"info"` (default), `"warning"`, `"error"`, or `"success"`.
230
+ # @param tags (see #increment)
231
+ # @return [void]
232
+ #
233
+ # @note Supported by the Datadog implementation only.
234
+ def event(
235
+ title, text,
236
+ deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
237
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
238
+ prefix: self.prefix, no_prefix: false,
239
+ hostname: nil, date_happened: nil, timestamp: date_happened,
240
+ aggregation_key: nil, priority: nil, source_type_name: nil, alert_type: nil,
241
+ **_ignored
242
+ )
243
+ prefix = nil if no_prefix
244
+ collect_metric(:_e, title, text, sample_rate: sample_rate, tags: tags, prefix: prefix, metadata: {
245
+ hostname: hostname, timestamp: timestamp, aggregation_key: aggregation_key,
246
+ priority: priority, source_type_name: source_type_name, alert_type: alert_type
247
+ })
248
+ end
249
+
250
+ # @!method service_check(name, status, tags: nil, hostname: nil, timestamp: nil, message: nil)
251
+ #
252
+ # Emits a service check.
253
+ #
254
+ # @param [String] name Name of the service. A configured prefix may be applied to this title.
255
+ # @param [Symbol] status Current status of the service. Either `:ok`, `:warning`, `:critical`, or `:unknown`.
256
+ # @param [String] hostname The hostname to associate with the event.
257
+ # @param [Time] timestamp The moment the status of the service was checkes. Defaults to now.
258
+ # @param [String] message A message that describes the current status.
259
+ # @param tags (see #increment)
260
+ # @return [void]
261
+ #
262
+ # @note Supported by the Datadog implementation only.
263
+ def service_check(
264
+ name, status,
265
+ deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
266
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
267
+ prefix: self.prefix, no_prefix: false,
268
+ hostname: nil, timestamp: nil, message: nil, **_ignored
269
+ )
270
+ prefix = nil if no_prefix
271
+ collect_metric(:_sc, name, status, sample_rate: sample_rate, prefix: prefix, tags: tags, metadata: {
272
+ hostname: hostname, timestamp: timestamp, message: message
273
+ })
274
+ end
275
+
276
+ private
277
+
278
+ def measure_latency(type, key, sample_rate:, tags:, prefix:)
279
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
280
+ begin
281
+ yield
282
+ ensure
283
+ # Ensure catches both a raised exception and a return in the invoked block
284
+ value = 1000.0 * (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
285
+ collect_metric(type, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
286
+ end
287
+ end
288
+
289
+ # Instantiates a metric, and sends it to the backend for further processing.
290
+ # @param options (see StatsD::Instrument::Metric#initialize)
291
+ # @return [void]
292
+ def collect_metric(type, name, value, sample_rate:, tags: nil, prefix:, metadata: nil)
293
+ sample_rate ||= default_sample_rate
294
+ name = "#{prefix}.#{name}" if prefix
295
+
296
+ metric = StatsD::Instrument::Metric.new(type: type, name: name, value: value,
297
+ sample_rate: sample_rate, tags: tags, metadata: metadata)
298
+ backend.collect_metric(metric)
299
+ metric # TODO: return `nil` in the next major version
300
+ end
301
+ end
@@ -43,9 +43,9 @@ class StatsD::Instrument::Metric
43
43
  using RubyBackports
44
44
  end
45
45
 
46
- def self.new(
47
- type:, name:, value: default_value(type), sample_rate: StatsD.default_sample_rate, tags: nil, metadata: nil
48
- )
46
+ def self.new(type:, name:, value: default_value(type), tags: nil, metadata: nil,
47
+ sample_rate: StatsD.legacy_singleton_client.default_sample_rate)
48
+
49
49
  # pass keyword arguments as positional arguments for performance reasons,
50
50
  # since MRI's C implementation of new turns keyword arguments into a hash
51
51
  super(type, name, value, sample_rate, tags, metadata)
@@ -92,8 +92,8 @@ class StatsD::Instrument::Metric
92
92
  @value = value
93
93
  @sample_rate = sample_rate
94
94
  @tags = StatsD::Instrument::Metric.normalize_tags(tags)
95
- if StatsD.default_tags
96
- @tags = Array(@tags) + StatsD.default_tags
95
+ if StatsD.legacy_singleton_client.default_tags
96
+ @tags = Array(@tags) + StatsD.legacy_singleton_client.default_tags
97
97
  end
98
98
  @metadata = metadata
99
99
  end
@@ -101,9 +101,9 @@ class StatsD::Instrument::Metric
101
101
  # @private
102
102
  # @return [String]
103
103
  def to_s
104
- str = +"#{TYPES[type]} #{name}:#{value}"
105
- str << " @#{sample_rate}" if sample_rate != 1.0
106
- tags&.each { |tag| str << " ##{tag}" }
104
+ str = +"#{name}:#{value}|#{type}"
105
+ str << "|@#{sample_rate}" if sample_rate && sample_rate != 1.0
106
+ str << "|#" << tags.join(',') if tags && !tags.empty?
107
107
  str
108
108
  end
109
109
 
@@ -21,6 +21,17 @@ module RuboCop
21
21
  statsd_count
22
22
  }
23
23
 
24
+ SINGLETON_CONFIGURATION_METHODS = %i{
25
+ backend
26
+ backend=
27
+ prefix
28
+ prefix=
29
+ default_tags
30
+ default_tags=
31
+ default_sample_rate
32
+ default_sample_rate=
33
+ }
34
+
24
35
  private
25
36
 
26
37
  def metaprogramming_method?(node)
@@ -33,6 +44,12 @@ module RuboCop
33
44
  METRIC_METHODS.include?(node.method_name)
34
45
  end
35
46
 
47
+ def singleton_configuration_method?(node)
48
+ node.receiver&.type == :const &&
49
+ node.receiver&.const_name == "StatsD" &&
50
+ SINGLETON_CONFIGURATION_METHODS.include?(node.method_name)
51
+ end
52
+
36
53
  def has_keyword_argument?(node, sym)
37
54
  if (kwargs = keyword_arguments(node))
38
55
  kwargs.child_nodes.detect do |pair|
@@ -62,3 +79,4 @@ require_relative 'rubocop/positional_arguments'
62
79
  require_relative 'rubocop/splat_arguments'
63
80
  require_relative 'rubocop/measure_as_dist_argument'
64
81
  require_relative 'rubocop/metric_prefix_argument'
82
+ require_relative 'rubocop/singleton_configuration'
@@ -0,0 +1,53 @@
1
+ # frozen-string-literal: true
2
+
3
+ require_relative '../rubocop' unless defined?(RuboCop::Cop::StatsD)
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module StatsD
8
+ # This Rubocop will check for calls to StatsD singleton congfiguration methods
9
+ # (e.g. `StatsD.prefix`). The library is moving away from having just a single
10
+ # singleton client, so these methods are deprecated.
11
+ #
12
+ # Use the following Rubocop invocation to check your project's codebase:
13
+ #
14
+ # rubocop --require `bundle show statsd-instrument`/lib/statsd/instrument/rubocop.rb \
15
+ # --only StatsD/SingletonConfiguration
16
+ #
17
+ # This cop will not autocorrect violations. There are several ways of fixing the violation.
18
+ #
19
+ # - The best option is to configure the library using environment variables, like
20
+ # `STATSD_ADDR`, `STATSD_IMPLEMENTATION`, `STATSD_PREFIX`, and `STATSD_DEFAULT_TAGS`.
21
+ # Metric methods called on the StatsD singleton (e.g. `StatsD.increment`) will by default
22
+ # be delegated to a client that is configured using these environment variables.
23
+ # - Alternatively, you can instantiate your own client using `StatsD::Instrument::Client.new`,
24
+ # and assign it to `StatsD.singleton_client`. The client constructor accepts many of the
25
+ # same options.
26
+ # - If you have to, you can call the old methods on `StatsD.legacy_singleton_client`. Note
27
+ # that this option will go away in the next major version.
28
+ class SingletonConfiguration < Cop
29
+ include RuboCop::Cop::StatsD
30
+
31
+ MSG = <<~MESSAGE
32
+ Singleton methods to configure StatsD are deprecated.
33
+
34
+ - The best option is to configure the library using environment variables, like
35
+ `STATSD_ADDR`, `STATSD_IMPLEMENTATION`, `STATSD_PREFIX`, and `STATSD_DEFAULT_TAGS`.
36
+ Metric methods called on the StatsD singleton (e.g. `StatsD.increment`) will by default
37
+ be delegated to a client that is configured using these environment variables.
38
+ - Alternatively, you can instantiate your own client using `StatsD::Instrument::Client.new`,
39
+ and assign it to `StatsD.singleton_client`. The client constructor accepts many of the
40
+ same options.
41
+ - If you have to, you can call the old methods on `StatsD.legacy_singleton_client`. Note
42
+ that this option will go away in the next major version.
43
+ MESSAGE
44
+
45
+ def on_send(node)
46
+ if singleton_configuration_method?(node)
47
+ add_offense(node)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module StatsD
4
4
  module Instrument
5
- VERSION = "2.6.0"
5
+ VERSION = "2.7.0"
6
6
  end
7
7
  end
@@ -0,0 +1,376 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class AssertionsOnLegacyClientTest < Minitest::Test
6
+ def setup
7
+ @old_client = StatsD.singleton_client
8
+ StatsD.singleton_client = StatsD.legacy_singleton_client
9
+
10
+ test_class = Class.new(Minitest::Test)
11
+ test_class.send(:include, StatsD::Instrument::Assertions)
12
+ @test_case = test_class.new('fake')
13
+ end
14
+
15
+ def teardown
16
+ StatsD.singleton_client = @old_client
17
+ end
18
+
19
+ def test_assert_no_statsd_calls
20
+ @test_case.assert_no_statsd_calls('counter') do
21
+ # noop
22
+ end
23
+
24
+ @test_case.assert_no_statsd_calls('counter') do
25
+ StatsD.increment('other')
26
+ end
27
+
28
+ assertion = assert_raises(Minitest::Assertion) do
29
+ @test_case.assert_no_statsd_calls('counter') do
30
+ StatsD.increment('counter')
31
+ end
32
+ end
33
+ assert_equal assertion.message, "No StatsD calls for metric counter expected."
34
+
35
+ assertion = assert_raises(Minitest::Assertion) do
36
+ @test_case.assert_no_statsd_calls do
37
+ StatsD.increment('other')
38
+ end
39
+ end
40
+ assert_equal assertion.message, "No StatsD calls for metric other expected."
41
+
42
+ assertion = assert_raises(Minitest::Assertion) do
43
+ @test_case.assert_no_statsd_calls do
44
+ StatsD.increment('other')
45
+ StatsD.increment('another')
46
+ end
47
+ end
48
+ assert_equal assertion.message, "No StatsD calls for metric other, another expected."
49
+ end
50
+
51
+ def test_assert_statsd_call
52
+ @test_case.assert_statsd_increment('counter') do
53
+ StatsD.increment('counter')
54
+ end
55
+
56
+ @test_case.assert_statsd_increment('counter') do
57
+ StatsD.increment('counter')
58
+ StatsD.increment('other')
59
+ end
60
+
61
+ assert_raises(Minitest::Assertion) do
62
+ @test_case.assert_statsd_increment('counter') do
63
+ StatsD.increment('other')
64
+ end
65
+ end
66
+
67
+ assert_raises(Minitest::Assertion) do
68
+ @test_case.assert_statsd_increment('counter') do
69
+ StatsD.gauge('counter', 42)
70
+ end
71
+ end
72
+
73
+ assert_raises(Minitest::Assertion) do
74
+ @test_case.assert_statsd_increment('counter') do
75
+ StatsD.increment('counter')
76
+ StatsD.increment('counter')
77
+ end
78
+ end
79
+
80
+ @test_case.assert_statsd_increment('counter', times: 2) do
81
+ StatsD.increment('counter')
82
+ StatsD.increment('counter')
83
+ end
84
+
85
+ @test_case.assert_statsd_increment('counter', times: 2, tags: ['foo:1']) do
86
+ StatsD.increment('counter', tags: { foo: 1 })
87
+ StatsD.increment('counter', tags: { foo: 1 })
88
+ end
89
+
90
+ assert_raises(Minitest::Assertion) do
91
+ @test_case.assert_statsd_increment('counter', times: 2, tags: ['foo:1']) do
92
+ StatsD.increment('counter', tags: { foo: 1 })
93
+ StatsD.increment('counter', tags: { foo: 2 })
94
+ end
95
+ end
96
+
97
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a', 'b']) do
98
+ StatsD.increment('counter', sample_rate: 0.5, tags: ['a', 'b'])
99
+ end
100
+
101
+ assert_raises(Minitest::Assertion) do
102
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a', 'b'], ignore_tags: ['b']) do
103
+ StatsD.increment('counter', sample_rate: 0.5, tags: ['a'])
104
+ end
105
+ end
106
+
107
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a'], ignore_tags: ['b']) do
108
+ StatsD.increment('counter', sample_rate: 0.5, tags: ['a', 'b'])
109
+ end
110
+
111
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a'], ignore_tags: ['b']) do
112
+ StatsD.increment('counter', sample_rate: 0.5, tags: ['a'])
113
+ end
114
+
115
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1 }, ignore_tags: { b: 2 }) do
116
+ StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2 })
117
+ end
118
+
119
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1 }, ignore_tags: { b: 2 }) do
120
+ StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 3 })
121
+ end
122
+
123
+ assert_raises(Minitest::Assertion) do
124
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1, b: 3 }, ignore_tags: ['b']) do
125
+ StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2 })
126
+ end
127
+ end
128
+
129
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1 }, ignore_tags: ['b']) do
130
+ StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2 })
131
+ end
132
+
133
+ assert_raises(Minitest::Assertion) do
134
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a', 'b']) do
135
+ StatsD.increment('counter', sample_rate: 0.2, tags: ['c'])
136
+ end
137
+ end
138
+ end
139
+
140
+ def test_assert_statsd_gauge_call_with_numeric_value
141
+ @test_case.assert_statsd_gauge('gauge', value: 42) do
142
+ StatsD.gauge('gauge', 42)
143
+ end
144
+
145
+ @test_case.assert_statsd_gauge('gauge', value: '42') do
146
+ StatsD.gauge('gauge', 42)
147
+ end
148
+
149
+ assert_raises(Minitest::Assertion) do
150
+ @test_case.assert_statsd_gauge('gauge', value: 42) do
151
+ StatsD.gauge('gauge', 45)
152
+ end
153
+ end
154
+ end
155
+
156
+ def test_assert_statsd_set_call_with_string_value
157
+ @test_case.assert_statsd_set('set', value: 12345) do
158
+ StatsD.set('set', '12345')
159
+ end
160
+
161
+ @test_case.assert_statsd_set('set', value: '12345') do
162
+ StatsD.set('set', '12345')
163
+ end
164
+
165
+ @test_case.assert_statsd_set('set', value: 12345) do
166
+ StatsD.set('set', 12345)
167
+ end
168
+
169
+ @test_case.assert_statsd_set('set', value: '12345') do
170
+ StatsD.set('set', 12345)
171
+ end
172
+
173
+ assert_raises(Minitest::Assertion) do
174
+ @test_case.assert_statsd_set('set', value: '42') do
175
+ StatsD.set('set', 45)
176
+ end
177
+ end
178
+ end
179
+
180
+ def test_tags_will_match_subsets
181
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1 }) do
182
+ StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2 })
183
+ end
184
+
185
+ assert_raises(Minitest::Assertion) do
186
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1, b: 3 }) do
187
+ StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2, c: 4 })
188
+ end
189
+ end
190
+ end
191
+
192
+ def test_tags_friendly_error
193
+ assertion = assert_raises(Minitest::Assertion) do
194
+ @test_case.assert_statsd_increment('counter', tags: { class: "AnotherJob" }) do
195
+ StatsD.increment('counter', tags: { class: "MyJob" })
196
+ end
197
+ end
198
+
199
+ assert_includes assertion.message, "Captured metrics with the same key"
200
+ assert_includes assertion.message, "MyJob"
201
+ end
202
+
203
+ def test_multiple_metrics_are_not_order_dependent
204
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:1'])
205
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
206
+ @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
207
+ StatsD.increment('counter', tags: { foo: 1 })
208
+ StatsD.increment('counter', tags: { foo: 2 })
209
+ end
210
+
211
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:1'])
212
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
213
+ @test_case.assert_statsd_calls([foo_2_metric, foo_1_metric]) do
214
+ StatsD.increment('counter', tags: { foo: 1 })
215
+ StatsD.increment('counter', tags: { foo: 2 })
216
+ end
217
+
218
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
219
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
220
+ @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
221
+ StatsD.increment('counter', tags: { foo: 1 })
222
+ StatsD.increment('counter', tags: { foo: 1 })
223
+ StatsD.increment('counter', tags: { foo: 2 })
224
+ end
225
+
226
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
227
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
228
+ @test_case.assert_statsd_calls([foo_2_metric, foo_1_metric]) do
229
+ StatsD.increment('counter', tags: { foo: 1 })
230
+ StatsD.increment('counter', tags: { foo: 1 })
231
+ StatsD.increment('counter', tags: { foo: 2 })
232
+ end
233
+
234
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
235
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
236
+ @test_case.assert_statsd_calls([foo_2_metric, foo_1_metric]) do
237
+ StatsD.increment('counter', tags: { foo: 1 })
238
+ StatsD.increment('counter', tags: { foo: 2 })
239
+ StatsD.increment('counter', tags: { foo: 1 })
240
+ end
241
+ end
242
+
243
+ def test_assert_multiple_statsd_calls
244
+ assert_raises(Minitest::Assertion) do
245
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
246
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
247
+ @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
248
+ StatsD.increment('counter', tags: { foo: 1 })
249
+ StatsD.increment('counter', tags: { foo: 2 })
250
+ end
251
+ end
252
+
253
+ assert_raises(Minitest::Assertion) do
254
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
255
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
256
+ @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
257
+ StatsD.increment('counter', tags: { foo: 1 })
258
+ StatsD.increment('counter', tags: { foo: 1 })
259
+ StatsD.increment('counter', tags: { foo: 2 })
260
+ StatsD.increment('counter', tags: { foo: 2 })
261
+ end
262
+ end
263
+
264
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
265
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
266
+ @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
267
+ StatsD.increment('counter', tags: { foo: 1 })
268
+ StatsD.increment('counter', tags: { foo: 1 })
269
+ StatsD.increment('counter', tags: { foo: 2 })
270
+ end
271
+ end
272
+
273
+ def test_assert_statsd_call_with_tags
274
+ @test_case.assert_statsd_increment('counter', tags: ['a:b', 'c:d']) do
275
+ StatsD.increment('counter', tags: { a: 'b', c: 'd' })
276
+ end
277
+
278
+ @test_case.assert_statsd_increment('counter', tags: { a: 'b', c: 'd' }) do
279
+ StatsD.increment('counter', tags: ['a:b', 'c:d'])
280
+ end
281
+ end
282
+
283
+ def test_nested_assertions
284
+ @test_case.assert_statsd_increment('counter1') do
285
+ @test_case.assert_statsd_increment('counter2') do
286
+ StatsD.increment('counter1')
287
+ StatsD.increment('counter2')
288
+ end
289
+ end
290
+
291
+ @test_case.assert_statsd_increment('counter1') do
292
+ StatsD.increment('counter1')
293
+ @test_case.assert_statsd_increment('counter2') do
294
+ StatsD.increment('counter2')
295
+ end
296
+ end
297
+
298
+ assert_raises(Minitest::Assertion) do
299
+ @test_case.assert_statsd_increment('counter1') do
300
+ @test_case.assert_statsd_increment('counter2') do
301
+ StatsD.increment('counter2')
302
+ end
303
+ end
304
+ end
305
+
306
+ assert_raises(Minitest::Assertion) do
307
+ @test_case.assert_statsd_increment('counter1') do
308
+ @test_case.assert_statsd_increment('counter2') do
309
+ StatsD.increment('counter1')
310
+ end
311
+ StatsD.increment('counter2')
312
+ end
313
+ end
314
+ end
315
+
316
+ def test_assertion_block_with_expected_exceptions
317
+ @test_case.assert_statsd_increment('expected_happened') do
318
+ @test_case.assert_raises(RuntimeError) do
319
+ begin
320
+ raise "expected"
321
+ rescue
322
+ StatsD.increment('expected_happened')
323
+ raise
324
+ end
325
+ end
326
+ end
327
+
328
+ assertion = assert_raises(Minitest::Assertion) do
329
+ @test_case.assert_statsd_increment('counter') do
330
+ @test_case.assert_raises(RuntimeError) do
331
+ raise "expected"
332
+ end
333
+ end
334
+ end
335
+ assert_includes assertion.message, "No StatsD calls for metric counter of type c were made"
336
+ end
337
+
338
+ def test_assertion_block_with_unexpected_exceptions
339
+ assertion = assert_raises(Minitest::Assertion) do
340
+ @test_case.assert_statsd_increment('counter') do
341
+ StatsD.increment('counter')
342
+ raise "unexpected"
343
+ end
344
+ end
345
+ assert_includes assertion.message, "An exception occurred in the block provided to the StatsD assertion"
346
+
347
+ assertion = assert_raises(Minitest::Assertion) do
348
+ @test_case.assert_raises(RuntimeError) do
349
+ @test_case.assert_statsd_increment('counter') do
350
+ StatsD.increment('counter')
351
+ raise "unexpected"
352
+ end
353
+ end
354
+ end
355
+ assert_includes assertion.message, "An exception occurred in the block provided to the StatsD assertion"
356
+
357
+ assertion = assert_raises(Minitest::Assertion) do
358
+ @test_case.assert_raises(RuntimeError) do
359
+ @test_case.assert_no_statsd_calls do
360
+ raise "unexpected"
361
+ end
362
+ end
363
+ end
364
+ assert_includes assertion.message, "An exception occurred in the block provided to the StatsD assertion"
365
+ end
366
+
367
+ def test_assertion_block_with_other_assertion_failures
368
+ # If another assertion failure happens inside the block, that failrue should have priority
369
+ assertion = assert_raises(Minitest::Assertion) do
370
+ @test_case.assert_statsd_increment('counter') do
371
+ @test_case.flunk('other assertion failure')
372
+ end
373
+ end
374
+ assert_equal "other assertion failure", assertion.message
375
+ end
376
+ end