statsd-instrument 2.9.2 → 3.0.2

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +16 -23
  3. data/.rubocop.yml +3 -13
  4. data/CHANGELOG.md +33 -0
  5. data/Gemfile +8 -0
  6. data/README.md +3 -3
  7. data/Rakefile +1 -1
  8. data/benchmark/send-metrics-to-dev-null-log +5 -2
  9. data/benchmark/send-metrics-to-local-udp-receiver +8 -6
  10. data/bin/rake +29 -0
  11. data/bin/rubocop +29 -0
  12. data/lib/statsd/instrument.rb +80 -144
  13. data/lib/statsd/instrument/assertions.rb +200 -208
  14. data/lib/statsd/instrument/capture_sink.rb +23 -19
  15. data/lib/statsd/instrument/client.rb +414 -320
  16. data/lib/statsd/instrument/datagram.rb +69 -65
  17. data/lib/statsd/instrument/datagram_builder.rb +81 -77
  18. data/lib/statsd/instrument/dogstatsd_datagram.rb +76 -72
  19. data/lib/statsd/instrument/dogstatsd_datagram_builder.rb +68 -64
  20. data/lib/statsd/instrument/environment.rb +79 -98
  21. data/lib/statsd/instrument/expectation.rb +96 -96
  22. data/lib/statsd/instrument/helpers.rb +10 -35
  23. data/lib/statsd/instrument/log_sink.rb +20 -16
  24. data/lib/statsd/instrument/matchers.rb +86 -71
  25. data/lib/statsd/instrument/null_sink.rb +12 -8
  26. data/lib/statsd/instrument/railtie.rb +11 -11
  27. data/lib/statsd/instrument/statsd_datagram_builder.rb +12 -8
  28. data/lib/statsd/instrument/strict.rb +12 -123
  29. data/lib/statsd/instrument/udp_sink.rb +50 -46
  30. data/lib/statsd/instrument/version.rb +1 -1
  31. data/statsd-instrument.gemspec +2 -8
  32. data/test/assertions_test.rb +46 -12
  33. data/test/capture_sink_test.rb +8 -8
  34. data/test/client_test.rb +62 -51
  35. data/test/datagram_builder_test.rb +29 -29
  36. data/test/datagram_test.rb +1 -1
  37. data/test/dogstatsd_datagram_builder_test.rb +28 -28
  38. data/test/environment_test.rb +10 -46
  39. data/test/helpers/rubocop_helper.rb +11 -8
  40. data/test/helpers_test.rb +5 -5
  41. data/test/integration_test.rb +10 -25
  42. data/test/log_sink_test.rb +2 -2
  43. data/test/matchers_test.rb +36 -36
  44. data/test/null_sink_test.rb +2 -2
  45. data/test/rubocop/metric_return_value_test.rb +3 -3
  46. data/test/rubocop/metric_value_keyword_argument_test.rb +1 -1
  47. data/test/rubocop/positional_arguments_test.rb +10 -10
  48. data/test/statsd_instrumentation_test.rb +97 -122
  49. data/test/statsd_test.rb +50 -75
  50. data/test/test_helper.rb +7 -5
  51. data/test/udp_sink_test.rb +8 -8
  52. metadata +7 -125
  53. data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +0 -1027
  54. data/benchmark/datagram-client +0 -40
  55. data/lib/statsd/instrument/backend.rb +0 -18
  56. data/lib/statsd/instrument/backends/capture_backend.rb +0 -32
  57. data/lib/statsd/instrument/backends/logger_backend.rb +0 -20
  58. data/lib/statsd/instrument/backends/null_backend.rb +0 -9
  59. data/lib/statsd/instrument/backends/udp_backend.rb +0 -152
  60. data/lib/statsd/instrument/legacy_client.rb +0 -301
  61. data/lib/statsd/instrument/metric.rb +0 -155
  62. data/test/assertions_on_legacy_client_test.rb +0 -344
  63. data/test/capture_backend_test.rb +0 -26
  64. data/test/compatibility/dogstatsd_datagram_compatibility_test.rb +0 -161
  65. data/test/deprecations_test.rb +0 -139
  66. data/test/logger_backend_test.rb +0 -22
  67. data/test/metric_test.rb +0 -47
  68. data/test/udp_backend_test.rb +0 -228
@@ -1,40 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module StatsD::Instrument::Helpers
4
- def capture_statsd_datagrams(client: nil, &block)
5
- client ||= StatsD.singleton_client
6
- case client
7
- when StatsD.legacy_singleton_client
8
- capture_statsd_metrics_on_legacy_client(&block)
9
- when StatsD::Instrument::Client
10
- client.capture(&block)
11
- else
12
- raise ArgumentError, "Don't know how to capture StatsD datagrams from #{client.inspect}!"
13
- end
14
- end
15
-
16
- # For backwards compatibility
17
- alias_method :capture_statsd_calls, :capture_statsd_datagrams
18
-
19
- def capture_statsd_metrics_on_legacy_client(&block)
20
- capture_backend = StatsD::Instrument::Backends::CaptureBackend.new
21
- with_capture_backend(capture_backend, &block)
22
- capture_backend.collected_metrics
23
- end
24
-
25
- private
26
-
27
- def with_capture_backend(backend)
28
- if StatsD.legacy_singleton_client.backend.is_a?(StatsD::Instrument::Backends::CaptureBackend)
29
- backend.parent = StatsD.legacy_singleton_client.backend
30
- end
31
-
32
- old_backend = StatsD.legacy_singleton_client.backend
33
- begin
34
- StatsD.legacy_singleton_client.backend = backend
35
- yield
36
- ensure
37
- StatsD.legacy_singleton_client.backend = old_backend
3
+ module StatsD
4
+ module Instrument
5
+ module Helpers
6
+ def capture_statsd_datagrams(client: nil, &block)
7
+ client ||= StatsD.singleton_client
8
+ client.capture(&block)
9
+ end
10
+
11
+ # For backwards compatibility
12
+ alias_method :capture_statsd_calls, :capture_statsd_datagrams
38
13
  end
39
14
  end
40
15
  end
@@ -1,24 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # @note This class is part of the new Client implementation that is intended
4
- # to become the new default in the next major release of this library.
5
- class StatsD::Instrument::LogSink
6
- attr_reader :logger, :severity
3
+ module StatsD
4
+ module Instrument
5
+ # @note This class is part of the new Client implementation that is intended
6
+ # to become the new default in the next major release of this library.
7
+ class LogSink
8
+ attr_reader :logger, :severity
7
9
 
8
- def initialize(logger, severity: Logger::DEBUG)
9
- @logger = logger
10
- @severity = severity
11
- end
10
+ def initialize(logger, severity: Logger::DEBUG)
11
+ @logger = logger
12
+ @severity = severity
13
+ end
12
14
 
13
- def sample?(_sample_rate)
14
- true
15
- end
15
+ def sample?(_sample_rate)
16
+ true
17
+ end
16
18
 
17
- def <<(datagram)
18
- # Some implementations require a newline at the end of datagrams.
19
- # When logging, we make sure those newlines are removed using chomp.
19
+ def <<(datagram)
20
+ # Some implementations require a newline at the end of datagrams.
21
+ # When logging, we make sure those newlines are removed using chomp.
20
22
 
21
- logger.add(severity, "[StatsD] #{datagram.chomp}")
22
- self
23
+ logger.add(severity, "[StatsD] #{datagram.chomp}")
24
+ self
25
+ end
26
+ end
23
27
  end
24
28
  end
@@ -3,98 +3,113 @@
3
3
  require 'rspec/expectations'
4
4
  require 'rspec/core/version'
5
5
 
6
- module StatsD::Instrument::Matchers
7
- CUSTOM_MATCHERS = {
8
- increment: :c,
9
- measure: :ms,
10
- gauge: :g,
11
- histogram: :h,
12
- distribution: :d,
13
- set: :s,
14
- key_value: :kv,
15
- }
16
-
17
- class Matcher
18
- include RSpec::Matchers::Composable if RSpec::Core::Version::STRING.start_with?('3')
19
- include StatsD::Instrument::Helpers
20
-
21
- def initialize(metric_type, metric_name, options = {})
22
- @metric_type = metric_type
23
- @metric_name = metric_name
24
- @options = options
25
- end
6
+ module StatsD
7
+ module Instrument
8
+ module Matchers
9
+ class Matcher
10
+ include(RSpec::Matchers::Composable) if RSpec::Core::Version::STRING.start_with?('3')
11
+ include StatsD::Instrument::Helpers
12
+
13
+ def initialize(metric_type, metric_name, options = {})
14
+ @metric_type = metric_type
15
+ @metric_name = metric_name
16
+ @options = options
17
+ end
26
18
 
27
- def matches?(block)
28
- expect_statsd_call(@metric_type, @metric_name, @options, &block)
29
- rescue RSpec::Expectations::ExpectationNotMetError => e
30
- @message = e.message
31
- false
32
- end
19
+ def matches?(block)
20
+ expect_statsd_call(@metric_type, @metric_name, @options, &block)
21
+ rescue RSpec::Expectations::ExpectationNotMetError => e
22
+ @message = e.message
23
+ false
24
+ end
33
25
 
34
- def failure_message
35
- @message
36
- end
26
+ def failure_message
27
+ @message
28
+ end
37
29
 
38
- def failure_message_when_negated
39
- "No StatsD calls for metric #{@metric_name} expected."
40
- end
30
+ def failure_message_when_negated
31
+ "No StatsD calls for metric #{@metric_name} expected."
32
+ end
41
33
 
42
- def supports_block_expectations?
43
- true
44
- end
34
+ def supports_block_expectations?
35
+ true
36
+ end
45
37
 
46
- private
38
+ private
47
39
 
48
- def expect_statsd_call(metric_type, metric_name, options, &block)
49
- metrics = capture_statsd_calls(&block)
50
- metrics = metrics.select { |m| m.type == metric_type && m.name == metric_name }
40
+ def expect_statsd_call(metric_type, metric_name, options, &block)
41
+ metrics = capture_statsd_calls(&block)
42
+ metrics = metrics.select { |m| m.type == metric_type && m.name == metric_name }
51
43
 
52
- if metrics.empty?
53
- raise RSpec::Expectations::ExpectationNotMetError, "No StatsD calls for metric #{metric_name} were made."
54
- elsif options[:times] && options[:times] != metrics.length
55
- raise RSpec::Expectations::ExpectationNotMetError, "The numbers of StatsD calls for metric #{metric_name} " \
56
- "was unexpected. Expected #{options[:times].inspect}, got #{metrics.length}"
57
- end
44
+ if metrics.empty?
45
+ raise RSpec::Expectations::ExpectationNotMetError, "No StatsD calls for metric #{metric_name} were made."
46
+ elsif options[:times] && options[:times] != metrics.length
47
+ raise RSpec::Expectations::ExpectationNotMetError, "The numbers of StatsD calls for metric " \
48
+ "#{metric_name} was unexpected. Expected #{options[:times].inspect}, got #{metrics.length}"
49
+ end
50
+
51
+ [:sample_rate, :value, :tags].each do |expectation|
52
+ next unless options[expectation]
53
+
54
+ num_matches = metrics.count do |m|
55
+ matcher = RSpec::Matchers::BuiltIn::Match.new(options[expectation])
56
+ matcher.matches?(m.public_send(expectation))
57
+ end
58
58
 
59
- [:sample_rate, :value, :tags].each do |expectation|
60
- next unless options[expectation]
59
+ found = options[:times] ? num_matches == options[:times] : num_matches > 0
61
60
 
62
- num_matches = metrics.count do |m|
63
- matcher = RSpec::Matchers::BuiltIn::Match.new(options[expectation])
64
- matcher.matches?(m.public_send(expectation))
61
+ unless found
62
+ message = metric_information(metric_name, options, metrics, expectation)
63
+ raise RSpec::Expectations::ExpectationNotMetError, message
64
+ end
65
+ end
66
+
67
+ true
65
68
  end
66
69
 
67
- found = options[:times] ? num_matches == options[:times] : num_matches > 0
70
+ def metric_information(metric_name, options, metrics, expectation)
71
+ message = "expected StatsD #{expectation.inspect} for metric '#{metric_name}' to be called"
72
+
73
+ message += "\n "
74
+ message += options[:times] ? "exactly #{options[:times]} times" : "at least once"
75
+ message += " with: #{options[expectation]}"
68
76
 
69
- unless found
70
- message = metric_information(metric_name, options, metrics, expectation)
71
- raise RSpec::Expectations::ExpectationNotMetError, message
77
+ message += "\n captured metric values: #{metrics.map(&expectation).join(', ')}"
78
+
79
+ message
72
80
  end
73
81
  end
74
82
 
75
- true
76
- end
83
+ Increment = Class.new(Matcher)
84
+ Measure = Class.new(Matcher)
85
+ Gauge = Class.new(Matcher)
86
+ Set = Class.new(Matcher)
87
+ Histogram = Class.new(Matcher)
88
+ Distribution = Class.new(Matcher)
77
89
 
78
- def metric_information(metric_name, options, metrics, expectation)
79
- message = "expected StatsD #{expectation.inspect} for metric '#{metric_name}' to be called"
90
+ def trigger_statsd_increment(metric_name, options = {})
91
+ Increment.new(:c, metric_name, options)
92
+ end
80
93
 
81
- message += "\n "
82
- message += options[:times] ? "exactly #{options[:times]} times" : "at least once"
83
- message += " with: #{options[expectation]}"
94
+ def trigger_statsd_measure(metric_name, options = {})
95
+ Measure.new(:ms, metric_name, options)
96
+ end
84
97
 
85
- message += "\n captured metric values: #{metrics.map(&expectation).join(', ')}"
98
+ def trigger_statsd_gauge(metric_name, options = {})
99
+ Gauge.new(:g, metric_name, options)
100
+ end
86
101
 
87
- message
88
- end
89
- end
102
+ def trigger_statsd_set(metric_name, options = {})
103
+ Set.new(:s, metric_name, options)
104
+ end
90
105
 
91
- CUSTOM_MATCHERS.each do |method_name, metric_type|
92
- klass = Class.new(Matcher)
106
+ def trigger_statsd_histogram(metric_name, options = {})
107
+ Histogram.new(:h, metric_name, options)
108
+ end
93
109
 
94
- define_method "trigger_statsd_#{method_name}" do |metric_name, options = {}|
95
- klass.new(metric_type, metric_name, options)
110
+ def trigger_statsd_distribution(metric_name, options = {})
111
+ Distribution.new(:d, metric_name, options)
112
+ end
96
113
  end
97
-
98
- StatsD::Instrument::Matchers.const_set(method_name.capitalize, klass)
99
114
  end
100
115
  end
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # @note This class is part of the new Client implementation that is intended
4
- # to become the new default in the next major release of this library.
5
- class StatsD::Instrument::NullSink
6
- def sample?(_sample_rate)
7
- true
8
- end
3
+ module StatsD
4
+ module Instrument
5
+ # @note This class is part of the new Client implementation that is intended
6
+ # to become the new default in the next major release of this library.
7
+ class NullSink
8
+ def sample?(_sample_rate)
9
+ true
10
+ end
9
11
 
10
- def <<(_datagram)
11
- self # noop
12
+ def <<(_datagram)
13
+ self # noop
14
+ end
15
+ end
12
16
  end
13
17
  end
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # This Railtie runs some initializers that will set the logger to <tt>Rails#logger</tt>,
4
- # and will initialize the {StatsD#backend} based on the Rails environment.
5
- #
6
- # @see StatsD::Instrument::Environment
7
- class StatsD::Instrument::Railtie < Rails::Railtie
8
- initializer 'statsd-instrument.use_rails_logger' do
9
- ::StatsD.logger = Rails.logger
10
- end
11
-
12
- initializer 'statsd-instrument.setup_backend', after: 'statsd-instrument.use_rails_logger' do
13
- ::StatsD.backend = ::StatsD::Instrument::Environment.default_backend
3
+ module StatsD
4
+ module Instrument
5
+ # This Railtie runs some initializers that will set the logger to <tt>Rails#logger</tt>,
6
+ # and will initialize the {StatsD#backend} based on the Rails environment.
7
+ #
8
+ # @see StatsD::Instrument::Environment
9
+ class Railtie < Rails::Railtie
10
+ initializer 'statsd-instrument.use_rails_logger' do
11
+ ::StatsD.logger = Rails.logger
12
+ end
13
+ end
14
14
  end
15
15
  end
@@ -1,14 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # @note This class is part of the new Client implementation that is intended
4
- # to become the new default in the next major release of this library.
5
- class StatsD::Instrument::StatsDDatagramBuilder < StatsD::Instrument::DatagramBuilder
6
- unsupported_datagram_types :h, :d, :kv
3
+ module StatsD
4
+ module Instrument
5
+ # @note This class is part of the new Client implementation that is intended
6
+ # to become the new default in the next major release of this library.
7
+ class StatsDDatagramBuilder < StatsD::Instrument::DatagramBuilder
8
+ unsupported_datagram_types :h, :d, :kv
7
9
 
8
- protected
10
+ protected
9
11
 
10
- def normalize_tags(tags)
11
- raise NotImplementedError, "#{self.class.name} does not support tags" if tags
12
- super
12
+ def normalize_tags(tags)
13
+ raise NotImplementedError, "#{self.class.name} does not support tags" if tags
14
+ super
15
+ end
16
+ end
13
17
  end
14
18
  end
@@ -55,9 +55,7 @@ module StatsD
55
55
  super
56
56
  end
57
57
 
58
- def service_check(name, status, tags: nil, no_prefix: false,
59
- hostname: nil, timestamp: nil, message: nil)
60
-
58
+ def service_check(name, status, tags: nil, no_prefix: false, hostname: nil, timestamp: nil, message: nil)
61
59
  super
62
60
  end
63
61
 
@@ -81,11 +79,6 @@ module StatsD
81
79
  super
82
80
  end
83
81
 
84
- def key_value(*)
85
- raise NotImplementedError, "The key_value metric type will be removed " \
86
- "from the next major version of statsd-instrument"
87
- end
88
-
89
82
  private
90
83
 
91
84
  def check_block_or_numeric_value(value)
@@ -106,133 +99,30 @@ module StatsD
106
99
  end
107
100
  end
108
101
 
109
- module VoidCollectMetric
110
- protected
111
-
112
- def collect_metric(type, name, value, sample_rate:, tags: nil, prefix:, metadata: nil)
113
- super
114
- StatsD::Instrument::VOID
115
- end
116
- end
117
-
118
102
  module StrictMetaprogramming
119
- def statsd_measure(method, name, sample_rate: nil, tags: nil,
120
- no_prefix: false, client: StatsD.singleton_client)
121
-
103
+ def statsd_measure(method, name, sample_rate: nil, tags: nil, no_prefix: false, client: nil)
122
104
  check_method_and_metric_name(method, name)
123
-
124
- # Unfortunately, we have to inline the new method implementation because we have to fix the
125
- # Stats.measure call to not use the `as_dist` and `prefix` arguments.
126
- add_to_method(method, name, :measure) do
127
- define_method(method) do |*args, &block|
128
- key = StatsD::Instrument.generate_metric_name(nil, name, self, *args)
129
- client.measure(key, sample_rate: sample_rate, tags: tags, no_prefix: no_prefix) do
130
- super(*args, &block)
131
- end
132
- end
133
- end
105
+ super
134
106
  end
135
107
 
136
- def statsd_distribution(method, name, sample_rate: nil, tags: nil,
137
- no_prefix: false, client: StatsD.singleton_client)
138
-
108
+ def statsd_distribution(method, name, sample_rate: nil, tags: nil, no_prefix: false, client: nil)
139
109
  check_method_and_metric_name(method, name)
140
-
141
- # Unfortunately, we have to inline the new method implementation because we have to fix the
142
- # Stats.distribution call to not use the `prefix` argument.
143
-
144
- add_to_method(method, name, :distribution) do
145
- define_method(method) do |*args, &block|
146
- key = StatsD::Instrument.generate_metric_name(nil, name, self, *args)
147
- client.distribution(key, sample_rate: sample_rate, tags: tags, no_prefix: no_prefix) do
148
- super(*args, &block)
149
- end
150
- end
151
- end
110
+ super
152
111
  end
153
112
 
154
- def statsd_count_success(method, name, sample_rate: nil, tags: nil,
155
- no_prefix: false, client: StatsD.singleton_client)
156
-
113
+ def statsd_count_success(method, name, sample_rate: nil, tags: nil, no_prefix: false, client: nil)
157
114
  check_method_and_metric_name(method, name)
158
-
159
- # Unfortunately, we have to inline the new method implementation because we have to fix the
160
- # Stats.increment call to not use the `prefix` argument.
161
-
162
- add_to_method(method, name, :count_success) do
163
- define_method(method) do |*args, &block|
164
- begin
165
- truthiness = result = super(*args, &block)
166
- rescue
167
- truthiness = false
168
- raise
169
- else
170
- if block_given?
171
- begin
172
- truthiness = yield(result)
173
- rescue
174
- truthiness = false
175
- end
176
- end
177
- result
178
- ensure
179
- suffix = truthiness == false ? 'failure' : 'success'
180
- key = "#{StatsD::Instrument.generate_metric_name(nil, name, self, *args)}.#{suffix}"
181
- client.increment(key, sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
182
- end
183
- end
184
- end
115
+ super
185
116
  end
186
117
 
187
- def statsd_count_if(method, name, sample_rate: nil, tags: nil,
188
- no_prefix: false, client: StatsD.singleton_client)
189
-
118
+ def statsd_count_if(method, name, sample_rate: nil, tags: nil, no_prefix: false, client: nil)
190
119
  check_method_and_metric_name(method, name)
191
-
192
- # Unfortunately, we have to inline the new method implementation because we have to fix the
193
- # Stats.increment call to not use the `prefix` argument.
194
-
195
- add_to_method(method, name, :count_if) do
196
- define_method(method) do |*args, &block|
197
- begin
198
- truthiness = result = super(*args, &block)
199
- rescue
200
- truthiness = false
201
- raise
202
- else
203
- if block_given?
204
- begin
205
- truthiness = yield(result)
206
- rescue
207
- truthiness = false
208
- end
209
- end
210
- result
211
- ensure
212
- if truthiness
213
- key = StatsD::Instrument.generate_metric_name(nil, name, self, *args)
214
- client.increment(key, sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
215
- end
216
- end
217
- end
218
- end
120
+ super
219
121
  end
220
122
 
221
- def statsd_count(method, name, sample_rate: nil, tags: nil,
222
- no_prefix: false, client: StatsD.singleton_client)
223
-
123
+ def statsd_count(method, name, sample_rate: nil, tags: nil, no_prefix: false, client: nil)
224
124
  check_method_and_metric_name(method, name)
225
-
226
- # Unfortunately, we have to inline the new method implementation because we have to fix the
227
- # Stats.increment call to not use the `prefix` argument.
228
-
229
- add_to_method(method, name, :count) do
230
- define_method(method) do |*args, &block|
231
- key = StatsD::Instrument.generate_metric_name(nil, name, self, *args)
232
- client.increment(key, sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
233
- super(*args, &block)
234
- end
235
- end
125
+ super
236
126
  end
237
127
 
238
128
  private
@@ -250,6 +140,5 @@ module StatsD
250
140
  end
251
141
  end
252
142
 
253
- StatsD.singleton_class.prepend(StatsD::Instrument::Strict)
254
- StatsD::Instrument::LegacyClient.prepend(StatsD::Instrument::VoidCollectMetric)
143
+ StatsD::Instrument::Client.prepend(StatsD::Instrument::Strict)
255
144
  StatsD::Instrument.prepend(StatsD::Instrument::StrictMetaprogramming)