statsd-instrument 2.9.0 → 3.0.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +14 -21
  3. data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +15 -15
  4. data/CHANGELOG.md +44 -0
  5. data/benchmark/send-metrics-to-dev-null-log +5 -2
  6. data/benchmark/send-metrics-to-local-udp-receiver +8 -6
  7. data/lib/statsd/instrument.rb +79 -146
  8. data/lib/statsd/instrument/assertions.rb +12 -24
  9. data/lib/statsd/instrument/client.rb +125 -35
  10. data/lib/statsd/instrument/environment.rb +1 -23
  11. data/lib/statsd/instrument/expectation.rb +12 -16
  12. data/lib/statsd/instrument/helpers.rb +1 -30
  13. data/lib/statsd/instrument/matchers.rb +0 -1
  14. data/lib/statsd/instrument/railtie.rb +0 -4
  15. data/lib/statsd/instrument/strict.rb +12 -123
  16. data/lib/statsd/instrument/version.rb +1 -1
  17. data/test/assertions_test.rb +21 -9
  18. data/test/client_test.rb +11 -0
  19. data/test/environment_test.rb +1 -37
  20. data/test/integration_test.rb +9 -24
  21. data/test/statsd_instrumentation_test.rb +25 -50
  22. data/test/statsd_test.rb +6 -31
  23. data/test/test_helper.rb +1 -1
  24. metadata +2 -24
  25. data/benchmark/datagram-client +0 -40
  26. data/lib/statsd/instrument/backend.rb +0 -18
  27. data/lib/statsd/instrument/backends/capture_backend.rb +0 -32
  28. data/lib/statsd/instrument/backends/logger_backend.rb +0 -20
  29. data/lib/statsd/instrument/backends/null_backend.rb +0 -9
  30. data/lib/statsd/instrument/backends/udp_backend.rb +0 -152
  31. data/lib/statsd/instrument/legacy_client.rb +0 -301
  32. data/lib/statsd/instrument/metric.rb +0 -155
  33. data/test/assertions_on_legacy_client_test.rb +0 -344
  34. data/test/capture_backend_test.rb +0 -26
  35. data/test/compatibility/dogstatsd_datagram_compatibility_test.rb +0 -161
  36. data/test/deprecations_test.rb +0 -139
  37. data/test/logger_backend_test.rb +0 -22
  38. data/test/metric_test.rb +0 -47
  39. data/test/udp_backend_test.rb +0 -228
@@ -1,155 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # The Metric class represents a metric sample to be send by a backend.
4
- #
5
- # @!attribute type
6
- # @return [Symbol] The metric type. Must be one of {StatsD::Instrument::Metric::TYPES}
7
- # @!attribute name
8
- # @return [String] The name of the metric. {StatsD#prefix} will automatically be applied
9
- # to the metric in the constructor, unless the <tt>:no_prefix</tt> option is set or is
10
- # overridden by the <tt>:prefix</tt> option. Note that <tt>:no_prefix</tt> has greater
11
- # precedence than <tt>:prefix</tt>.
12
- # @!attribute value
13
- # @see #default_value
14
- # @return [Numeric, String] The value to collect for the metric. Depending on the metric
15
- # type, <tt>value</tt> can be a string, integer, or float.
16
- # @!attribute sample_rate
17
- # The sample rate to use for the metric. How the sample rate is handled differs per backend.
18
- # The UDP backend will actually sample metric submissions based on the sample rate, while
19
- # the logger backend will just include the sample rate in its output for debugging purposes.
20
- # @see StatsD#default_sample_rate
21
- # @return [Float] The sample rate to use for this metric. This should be a value between
22
- # 0 and 1. If not set, it will use the default sample rate set to {StatsD#default_sample_rate}.
23
- # @!attribute tags
24
- # The tags to associate with the metric.
25
- # @note Only the Datadog implementation supports tags.
26
- # @see .normalize_tags
27
- # @return [Array<String>, Hash<String, String>, nil] the tags to associate with the metric.
28
- # You can either specify the tags as an array of strings, or a Hash of key/value pairs.
29
- #
30
- # @see StatsD The StatsD module contains methods that generate metric instances.
31
- # @see StatsD::Instrument::Backend A StatsD::Instrument::Backend is used to collect metrics.
32
- #
33
- class StatsD::Instrument::Metric
34
- unless Regexp.method_defined?(:match?) # for ruby 2.3
35
- module RubyBackports
36
- refine Regexp do
37
- def match?(str)
38
- (self =~ str) != nil
39
- end
40
- end
41
- end
42
-
43
- using RubyBackports
44
- end
45
-
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
- # pass keyword arguments as positional arguments for performance reasons,
50
- # since MRI's C implementation of new turns keyword arguments into a hash
51
- super(type, name, value, sample_rate, tags, metadata)
52
- end
53
-
54
- # The default value for this metric, which will be used if it is not set.
55
- #
56
- # A default value is only defined for counter metrics (<tt>1</tt>). For all other
57
- # metric types, this method will raise an <tt>ArgumentError</tt>.
58
- #
59
- #
60
- # A default value is only defined for counter metrics (<tt>1</tt>). For all other
61
- # metric types, this method will raise an <tt>ArgumentError</tt>.
62
- #
63
- # @return [Numeric, String] The default value for this metric.
64
- # @raise ArgumentError if the metric type doesn't have a default value
65
- def self.default_value(type)
66
- case type
67
- when :c then 1
68
- else raise ArgumentError, "A value is required for metric type #{type.inspect}."
69
- end
70
- end
71
-
72
- attr_accessor :type, :name, :value, :sample_rate, :tags, :metadata
73
-
74
- # Initializes a new metric instance.
75
- # Normally, you don't want to call this method directly, but use one of the metric collection
76
- # methods on the {StatsD} module.
77
- #
78
- # @param type [Symbol] The type of the metric.
79
- # @option name [String] :name The name of the metric without prefix.
80
- # @option value [Numeric, String, nil] The value to collect for the metric.
81
- # @option sample_rate [Numeric, nil] The sample rate to use. If not set, it will use
82
- # {StatsD#default_sample_rate}.
83
- # @option tags [Array<String>, Hash<String, String>, nil] :tags The tags to apply to this metric.
84
- # See {.normalize_tags} for more information.
85
- def initialize(type, name, value, sample_rate, tags, metadata) # rubocop:disable Metrics/ParameterLists
86
- raise ArgumentError, "Metric :type is required." unless type
87
- raise ArgumentError, "Metric :name is required." unless name
88
- raise ArgumentError, "Metric :value is required." unless value
89
-
90
- @type = type
91
- @name = normalize_name(name)
92
- @value = value
93
- @sample_rate = sample_rate
94
- @tags = StatsD::Instrument::Metric.normalize_tags(tags)
95
- if StatsD.legacy_singleton_client.default_tags
96
- @tags = Array(@tags) + StatsD.legacy_singleton_client.default_tags
97
- end
98
- @metadata = metadata
99
- end
100
-
101
- # @private
102
- # @return [String]
103
- def to_s
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
- str
108
- end
109
-
110
- # @private
111
- # @return [String]
112
- def inspect
113
- "#<StatsD::Instrument::Metric #{self}>"
114
- end
115
-
116
- # The metric types that are supported by this library. Note that every StatsD server
117
- # implementation only supports a subset of them.
118
- TYPES = {
119
- c: 'increment',
120
- ms: 'measure',
121
- g: 'gauge',
122
- h: 'histogram',
123
- d: 'distribution',
124
- kv: 'key/value',
125
- s: 'set',
126
- }
127
-
128
- # Strip metric names of special characters used by StatsD line protocol, replace with underscore
129
- #
130
- # @param name [String]
131
- # @return [String]
132
- def normalize_name(name)
133
- # fast path when no normalization is needed to avoid copying the string
134
- return name unless /[:|@]/.match?(name)
135
-
136
- name.tr(':|@', '_')
137
- end
138
-
139
- # Utility function to convert tags to the canonical form.
140
- #
141
- # - Tags specified as key value pairs will be converted into an array
142
- # - Tags are normalized to only use word characters and underscores.
143
- #
144
- # @param tags [Array<String>, Hash<String, String>, nil] Tags specified in any form.
145
- # @return [Array<String>, nil] the list of tags in canonical form.
146
- def self.normalize_tags(tags)
147
- return unless tags
148
- tags = tags.map { |k, v| k.to_s + ":" + v.to_s } if tags.is_a?(Hash)
149
-
150
- # fast path when no string replacement is needed
151
- return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
152
-
153
- tags.map { |tag| tag.tr('|,', '') }
154
- end
155
- end
@@ -1,344 +0,0 @@
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']) do
103
- StatsD.increment('counter', sample_rate: 0.2, tags: ['c'])
104
- end
105
- end
106
- end
107
-
108
- def test_assert_statsd_gauge_call_with_numeric_value
109
- @test_case.assert_statsd_gauge('gauge', value: 42) do
110
- StatsD.gauge('gauge', 42)
111
- end
112
-
113
- @test_case.assert_statsd_gauge('gauge', value: '42') do
114
- StatsD.gauge('gauge', 42)
115
- end
116
-
117
- assert_raises(Minitest::Assertion) do
118
- @test_case.assert_statsd_gauge('gauge', value: 42) do
119
- StatsD.gauge('gauge', 45)
120
- end
121
- end
122
- end
123
-
124
- def test_assert_statsd_set_call_with_string_value
125
- @test_case.assert_statsd_set('set', value: 12345) do
126
- StatsD.set('set', '12345')
127
- end
128
-
129
- @test_case.assert_statsd_set('set', value: '12345') do
130
- StatsD.set('set', '12345')
131
- end
132
-
133
- @test_case.assert_statsd_set('set', value: 12345) do
134
- StatsD.set('set', 12345)
135
- end
136
-
137
- @test_case.assert_statsd_set('set', value: '12345') do
138
- StatsD.set('set', 12345)
139
- end
140
-
141
- assert_raises(Minitest::Assertion) do
142
- @test_case.assert_statsd_set('set', value: '42') do
143
- StatsD.set('set', 45)
144
- end
145
- end
146
- end
147
-
148
- def test_tags_will_match_subsets
149
- @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1 }) do
150
- StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2 })
151
- end
152
-
153
- assert_raises(Minitest::Assertion) do
154
- @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1, b: 3 }) do
155
- StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2, c: 4 })
156
- end
157
- end
158
- end
159
-
160
- def test_tags_friendly_error
161
- assertion = assert_raises(Minitest::Assertion) do
162
- @test_case.assert_statsd_increment('counter', tags: { class: "AnotherJob" }) do
163
- StatsD.increment('counter', tags: { class: "MyJob" })
164
- end
165
- end
166
-
167
- assert_includes assertion.message, "Captured metrics with the same key"
168
- assert_includes assertion.message, "MyJob"
169
- end
170
-
171
- def test_multiple_metrics_are_not_order_dependent
172
- foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:1'])
173
- foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
174
- @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
175
- StatsD.increment('counter', tags: { foo: 1 })
176
- StatsD.increment('counter', tags: { foo: 2 })
177
- end
178
-
179
- foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:1'])
180
- foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
181
- @test_case.assert_statsd_calls([foo_2_metric, foo_1_metric]) do
182
- StatsD.increment('counter', tags: { foo: 1 })
183
- StatsD.increment('counter', tags: { foo: 2 })
184
- end
185
-
186
- foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
187
- foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
188
- @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
189
- StatsD.increment('counter', tags: { foo: 1 })
190
- StatsD.increment('counter', tags: { foo: 1 })
191
- StatsD.increment('counter', tags: { foo: 2 })
192
- end
193
-
194
- foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
195
- foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
196
- @test_case.assert_statsd_calls([foo_2_metric, foo_1_metric]) do
197
- StatsD.increment('counter', tags: { foo: 1 })
198
- StatsD.increment('counter', tags: { foo: 1 })
199
- StatsD.increment('counter', tags: { foo: 2 })
200
- end
201
-
202
- foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
203
- foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
204
- @test_case.assert_statsd_calls([foo_2_metric, foo_1_metric]) do
205
- StatsD.increment('counter', tags: { foo: 1 })
206
- StatsD.increment('counter', tags: { foo: 2 })
207
- StatsD.increment('counter', tags: { foo: 1 })
208
- end
209
- end
210
-
211
- def test_assert_multiple_statsd_calls
212
- assert_raises(Minitest::Assertion) do
213
- foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
214
- foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
215
- @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
216
- StatsD.increment('counter', tags: { foo: 1 })
217
- StatsD.increment('counter', tags: { foo: 2 })
218
- end
219
- end
220
-
221
- assert_raises(Minitest::Assertion) do
222
- foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
223
- foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
224
- @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
225
- StatsD.increment('counter', tags: { foo: 1 })
226
- StatsD.increment('counter', tags: { foo: 1 })
227
- StatsD.increment('counter', tags: { foo: 2 })
228
- StatsD.increment('counter', tags: { foo: 2 })
229
- end
230
- end
231
-
232
- foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
233
- foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
234
- @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
235
- StatsD.increment('counter', tags: { foo: 1 })
236
- StatsD.increment('counter', tags: { foo: 1 })
237
- StatsD.increment('counter', tags: { foo: 2 })
238
- end
239
- end
240
-
241
- def test_assert_statsd_call_with_tags
242
- @test_case.assert_statsd_increment('counter', tags: ['a:b', 'c:d']) do
243
- StatsD.increment('counter', tags: { a: 'b', c: 'd' })
244
- end
245
-
246
- @test_case.assert_statsd_increment('counter', tags: { a: 'b', c: 'd' }) do
247
- StatsD.increment('counter', tags: ['a:b', 'c:d'])
248
- end
249
- end
250
-
251
- def test_nested_assertions
252
- @test_case.assert_statsd_increment('counter1') do
253
- @test_case.assert_statsd_increment('counter2') do
254
- StatsD.increment('counter1')
255
- StatsD.increment('counter2')
256
- end
257
- end
258
-
259
- @test_case.assert_statsd_increment('counter1') do
260
- StatsD.increment('counter1')
261
- @test_case.assert_statsd_increment('counter2') do
262
- StatsD.increment('counter2')
263
- end
264
- end
265
-
266
- assert_raises(Minitest::Assertion) do
267
- @test_case.assert_statsd_increment('counter1') do
268
- @test_case.assert_statsd_increment('counter2') do
269
- StatsD.increment('counter2')
270
- end
271
- end
272
- end
273
-
274
- assert_raises(Minitest::Assertion) do
275
- @test_case.assert_statsd_increment('counter1') do
276
- @test_case.assert_statsd_increment('counter2') do
277
- StatsD.increment('counter1')
278
- end
279
- StatsD.increment('counter2')
280
- end
281
- end
282
- end
283
-
284
- def test_assertion_block_with_expected_exceptions
285
- @test_case.assert_statsd_increment('expected_happened') do
286
- @test_case.assert_raises(RuntimeError) do
287
- begin
288
- raise "expected"
289
- rescue
290
- StatsD.increment('expected_happened')
291
- raise
292
- end
293
- end
294
- end
295
-
296
- assertion = assert_raises(Minitest::Assertion) do
297
- @test_case.assert_statsd_increment('counter') do
298
- @test_case.assert_raises(RuntimeError) do
299
- raise "expected"
300
- end
301
- end
302
- end
303
- assert_includes assertion.message, "No StatsD calls for metric counter of type c were made"
304
- end
305
-
306
- def test_assertion_block_with_unexpected_exceptions
307
- assertion = assert_raises(Minitest::Assertion) do
308
- @test_case.assert_statsd_increment('counter') do
309
- StatsD.increment('counter')
310
- raise "unexpected"
311
- end
312
- end
313
- assert_includes assertion.message, "An exception occurred in the block provided to the StatsD assertion"
314
-
315
- assertion = assert_raises(Minitest::Assertion) do
316
- @test_case.assert_raises(RuntimeError) do
317
- @test_case.assert_statsd_increment('counter') do
318
- StatsD.increment('counter')
319
- raise "unexpected"
320
- end
321
- end
322
- end
323
- assert_includes assertion.message, "An exception occurred in the block provided to the StatsD assertion"
324
-
325
- assertion = assert_raises(Minitest::Assertion) do
326
- @test_case.assert_raises(RuntimeError) do
327
- @test_case.assert_no_statsd_calls do
328
- raise "unexpected"
329
- end
330
- end
331
- end
332
- assert_includes assertion.message, "An exception occurred in the block provided to the StatsD assertion"
333
- end
334
-
335
- def test_assertion_block_with_other_assertion_failures
336
- # If another assertion failure happens inside the block, that failure should have priority
337
- assertion = assert_raises(Minitest::Assertion) do
338
- @test_case.assert_statsd_increment('counter') do
339
- @test_case.flunk('other assertion failure')
340
- end
341
- end
342
- assert_equal "other assertion failure", assertion.message
343
- end
344
- end