statsd-instrument 2.9.2 → 3.0.2

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