statsd-instrument 2.3.2 → 2.6.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/workflows/benchmark.yml +32 -0
  4. data/.github/workflows/ci.yml +47 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +1027 -0
  7. data/.rubocop.yml +50 -0
  8. data/.yardopts +5 -0
  9. data/CHANGELOG.md +288 -2
  10. data/CONTRIBUTING.md +28 -6
  11. data/Gemfile +5 -0
  12. data/README.md +54 -46
  13. data/Rakefile +4 -2
  14. data/benchmark/README.md +29 -0
  15. data/benchmark/datagram-client +41 -0
  16. data/benchmark/send-metrics-to-dev-null-log +47 -0
  17. data/benchmark/send-metrics-to-local-udp-receiver +57 -0
  18. data/lib/statsd/instrument/assertions.rb +179 -30
  19. data/lib/statsd/instrument/backend.rb +3 -2
  20. data/lib/statsd/instrument/backends/capture_backend.rb +4 -1
  21. data/lib/statsd/instrument/backends/logger_backend.rb +3 -3
  22. data/lib/statsd/instrument/backends/null_backend.rb +2 -0
  23. data/lib/statsd/instrument/backends/udp_backend.rb +39 -45
  24. data/lib/statsd/instrument/capture_sink.rb +27 -0
  25. data/lib/statsd/instrument/client.rb +313 -0
  26. data/lib/statsd/instrument/datagram.rb +75 -0
  27. data/lib/statsd/instrument/datagram_builder.rb +101 -0
  28. data/lib/statsd/instrument/dogstatsd_datagram_builder.rb +71 -0
  29. data/lib/statsd/instrument/environment.rb +108 -29
  30. data/lib/statsd/instrument/helpers.rb +16 -8
  31. data/lib/statsd/instrument/log_sink.rb +24 -0
  32. data/lib/statsd/instrument/matchers.rb +14 -11
  33. data/lib/statsd/instrument/metric.rb +72 -45
  34. data/lib/statsd/instrument/metric_expectation.rb +32 -18
  35. data/lib/statsd/instrument/null_sink.rb +13 -0
  36. data/lib/statsd/instrument/railtie.rb +2 -1
  37. data/lib/statsd/instrument/rubocop/measure_as_dist_argument.rb +39 -0
  38. data/lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb +42 -0
  39. data/lib/statsd/instrument/rubocop/metric_prefix_argument.rb +37 -0
  40. data/lib/statsd/instrument/rubocop/metric_return_value.rb +32 -0
  41. data/lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb +36 -0
  42. data/lib/statsd/instrument/rubocop/positional_arguments.rb +99 -0
  43. data/lib/statsd/instrument/rubocop/splat_arguments.rb +31 -0
  44. data/lib/statsd/instrument/rubocop.rb +64 -0
  45. data/lib/statsd/instrument/statsd_datagram_builder.rb +14 -0
  46. data/lib/statsd/instrument/strict.rb +235 -0
  47. data/lib/statsd/instrument/udp_sink.rb +62 -0
  48. data/lib/statsd/instrument/version.rb +3 -1
  49. data/lib/statsd/instrument.rb +340 -163
  50. data/lib/statsd-instrument.rb +2 -0
  51. data/statsd-instrument.gemspec +13 -10
  52. data/test/assertions_test.rb +167 -156
  53. data/test/benchmark/clock_gettime.rb +27 -0
  54. data/test/benchmark/default_tags.rb +47 -0
  55. data/test/benchmark/metrics.rb +9 -8
  56. data/test/benchmark/tags.rb +5 -3
  57. data/test/capture_backend_test.rb +4 -2
  58. data/test/capture_sink_test.rb +44 -0
  59. data/test/client_test.rb +164 -0
  60. data/test/compatibility/dogstatsd_datagram_compatibility_test.rb +162 -0
  61. data/test/datagram_builder_test.rb +120 -0
  62. data/test/deprecations_test.rb +132 -0
  63. data/test/dogstatsd_datagram_builder_test.rb +32 -0
  64. data/test/environment_test.rb +75 -8
  65. data/test/helpers/rubocop_helper.rb +47 -0
  66. data/test/helpers_test.rb +2 -1
  67. data/test/integration_test.rb +31 -7
  68. data/test/log_sink_test.rb +37 -0
  69. data/test/logger_backend_test.rb +10 -8
  70. data/test/matchers_test.rb +42 -28
  71. data/test/metric_test.rb +18 -22
  72. data/test/null_sink_test.rb +13 -0
  73. data/test/rubocop/measure_as_dist_argument_test.rb +44 -0
  74. data/test/rubocop/metaprogramming_positional_arguments_test.rb +58 -0
  75. data/test/rubocop/metric_prefix_argument_test.rb +38 -0
  76. data/test/rubocop/metric_return_value_test.rb +78 -0
  77. data/test/rubocop/metric_value_keyword_argument_test.rb +39 -0
  78. data/test/rubocop/positional_arguments_test.rb +110 -0
  79. data/test/rubocop/splat_arguments_test.rb +27 -0
  80. data/test/statsd_datagram_builder_test.rb +22 -0
  81. data/test/statsd_instrumentation_test.rb +109 -100
  82. data/test/statsd_test.rb +113 -79
  83. data/test/test_helper.rb +12 -1
  84. data/test/udp_backend_test.rb +38 -36
  85. data/test/udp_sink_test.rb +85 -0
  86. metadata +85 -5
  87. data/.travis.yml +0 -12
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'benchmark/ips'
6
+ require 'logger'
7
+
8
+ revision = %x(git rev-parse HEAD).rstrip
9
+ master_revision = %x(git rev-parse origin/master).rstrip
10
+ branch = if revision == master_revision
11
+ 'master'
12
+ else
13
+ %x(git rev-parse --abbrev-ref HEAD).rstrip
14
+ end
15
+
16
+ intermediate_results_filename = "#{Dir.tmpdir}/statsd-instrument-benchmarks/#{File.basename($PROGRAM_NAME)}"
17
+ FileUtils.mkdir_p(File.dirname(intermediate_results_filename))
18
+
19
+ ENV['ENV'] = "development"
20
+ require 'statsd-instrument'
21
+ StatsD.logger = Logger.new(File::NULL)
22
+
23
+ report = Benchmark.ips do |bench|
24
+ bench.report("StatsD metrics to /dev/null log (branch: #{branch}, sha: #{revision[0, 7]})") do
25
+ StatsD.increment('StatsD.increment', 10, sample_rate: 15)
26
+ StatsD.measure('StatsD.measure') { 1 + 1 }
27
+ StatsD.gauge('StatsD.gauge', 12.0, tags: ["foo:bar", "quc"])
28
+ StatsD.set('StatsD.set', 'value', tags: { foo: 'bar', baz: 'quc' })
29
+ StatsD.event('StasD.event', "12345")
30
+ StatsD.service_check("StatsD.service_check", "ok")
31
+ end
32
+
33
+ # Store the results in between runs
34
+ bench.save!(intermediate_results_filename)
35
+ bench.compare!
36
+ end
37
+
38
+ if report.entries.length == 1
39
+ puts
40
+ puts "To compare the performance of this revision against another revision (e.g. master),"
41
+ puts "check out a different branch and run this benchmark script again."
42
+ elsif ENV['KEEP_RESULTS']
43
+ puts
44
+ puts "The intermediate results have been stored in #{intermediate_results_filename}"
45
+ else
46
+ File.unlink(intermediate_results_filename)
47
+ end
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'benchmark/ips'
6
+ require 'socket'
7
+
8
+ revision = %x(git rev-parse HEAD).rstrip
9
+ master_revision = %x(git rev-parse origin/master).rstrip
10
+ branch = if revision == master_revision
11
+ 'master'
12
+ else
13
+ %x(git rev-parse --abbrev-ref HEAD).rstrip
14
+ end
15
+
16
+ intermediate_results_filename = "#{Dir.tmpdir}/statsd-instrument-benchmarks/#{File.basename($PROGRAM_NAME)}"
17
+ FileUtils.mkdir_p(File.dirname(intermediate_results_filename))
18
+
19
+ # Set up an UDP listener to which we can send StatsD packets
20
+ receiver = UDPSocket.new
21
+ receiver.bind('localhost', 0)
22
+
23
+ ENV['ENV'] = "production"
24
+ ENV['STATSD_ADDR'] = "#{receiver.addr[2]}:#{receiver.addr[1]}"
25
+ ENV['STATSD_IMPLEMENTATION'] ||= 'datadog'
26
+
27
+ require 'statsd-instrument'
28
+
29
+ report = Benchmark.ips do |bench|
30
+ bench.report("StatsD metrics to local UDP receiver (branch: #{branch}, sha: #{revision[0, 7]})") do
31
+ StatsD.increment('StatsD.increment', 10)
32
+ StatsD.measure('StatsD.measure') { 1 + 1 }
33
+ StatsD.gauge('StatsD.gauge', 12.0, tags: ["foo:bar", "quc"])
34
+ StatsD.set('StatsD.set', 'value', tags: { foo: 'bar', baz: 'quc' })
35
+ if StatsD.backend.implementation == :datadog
36
+ StatsD.event('StasD.event', "12345")
37
+ StatsD.service_check("StatsD.service_check", "ok")
38
+ end
39
+ end
40
+
41
+ # Store the results in between runs
42
+ bench.save!(intermediate_results_filename)
43
+ bench.compare!
44
+ end
45
+
46
+ receiver.close
47
+
48
+ if report.entries.length == 1
49
+ puts
50
+ puts "To compare the performance of this revision against another revision (e.g. master),"
51
+ puts "check out a different branch and run this benchmark script again."
52
+ elsif ENV['KEEP_RESULTS']
53
+ puts
54
+ puts "The intermediate results have been stored in #{intermediate_results_filename}"
55
+ else
56
+ File.unlink(intermediate_results_filename)
57
+ end
@@ -1,79 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This module defines several assertion methods that can be used to verify that
4
+ # your application is emitting the right StatsD metrics.
5
+ #
6
+ # Every metric type has its own assertion method, like {#assert_statsd_increment}
7
+ # to assert `StatsD.increment` calls. You can also assert other properties of the
8
+ # metric that was emitted, lioke the sample rate or presence of tags.
9
+ # To check for the absence of metrics, use {#assert_no_statsd_calls}.
10
+ #
11
+ # @example Check for metric properties:
12
+ # assert_statsd_measure('foo', sample_rate: 0.1, tags: ["bar"]) do
13
+ # StatsD.measure('foo', sample_rate: 0.5, tags: ['bar','baz']) do
14
+ # some_code_to_measure
15
+ # end
16
+ # end
17
+ #
18
+ # @example Check for multiple occurrences:
19
+ # assert_statsd_increment('foo', times: 2) do
20
+ # StatsD.increment('foo')
21
+ # StatsD.increment('foo')
22
+ # end
23
+ #
24
+ # @example Absence of metrics
25
+ # assert_no_statsd_calls do
26
+ # foo
27
+ # end
28
+ #
29
+ # @example Handling exceptions
30
+ # assert_statsd_increment('foo.error') do
31
+ # # If we expect exceptions to occur, we have to handle them inside
32
+ # # the block we pass to assert_statsd_increment.
33
+ # assert_raises(RuntimeError) do
34
+ # begin
35
+ # attempt_foo
36
+ # rescue
37
+ # StatsD.increment('foo.error')
38
+ # raise 'foo failed'
39
+ # end
40
+ # end
41
+ # end
1
42
  module StatsD::Instrument::Assertions
2
43
  include StatsD::Instrument::Helpers
3
44
 
45
+ # Asserts no metric occurred during the execution of the provided block.
46
+ #
47
+ # @param [String] metric_name (default: nil) The metric name that is not allowed
48
+ # to happen inside the block. If this is set to `nil`, the assertion will fail
49
+ # if any metric occurs.
50
+ # @yield A block in which the specified metric should not occur. This block
51
+ # should not raise any exceptions.
52
+ # @return [void]
53
+ # @raise [Minitest::Assertion] If an exception occurs, or if any metric (with the
54
+ # provided name, or any), occurred during the execution of the provided block.
4
55
  def assert_no_statsd_calls(metric_name = nil, &block)
5
56
  metrics = capture_statsd_calls(&block)
6
57
  metrics.select! { |m| m.name == metric_name } if metric_name
7
- assert metrics.empty?, "No StatsD calls for metric #{metrics.map(&:name).join(', ')} expected."
58
+ assert(metrics.empty?, "No StatsD calls for metric #{metrics.map(&:name).join(', ')} expected.")
59
+ rescue => exception
60
+ flunk(<<~MESSAGE)
61
+ An exception occurred in the block provided to the StatsD assertion.
62
+
63
+ #{exception.class.name}: #{exception.message}
64
+ \t#{exception.backtrace.join("\n\t")}
65
+
66
+ If this exception is expected, make sure to handle it using `assert_raises`
67
+ inside the block provided to the StatsD assertion.
68
+ MESSAGE
8
69
  end
9
70
 
71
+ # Asserts that a given counter metric occurred inside the provided block.
72
+ #
73
+ # @param [String] metric_name The name of the metric that should occur.
74
+ # @param [Hash] options (see StatsD::Instrument::MetricExpectation.new)
75
+ # @yield A block in which the specified metric should occur. This block
76
+ # should not raise any exceptions.
77
+ # @return [void]
78
+ # @raise [Minitest::Assertion] If an exception occurs, or if the metric did
79
+ # not occur as specified during the execution the block.
10
80
  def assert_statsd_increment(metric_name, options = {}, &block)
11
81
  assert_statsd_call(:c, metric_name, options, &block)
12
82
  end
13
83
 
84
+ # Asserts that a given timing metric occurred inside the provided block.
85
+ #
86
+ # @param metric_name (see #assert_statsd_increment)
87
+ # @param options (see #assert_statsd_increment)
88
+ # @yield (see #assert_statsd_increment)
89
+ # @return [void]
90
+ # @raise (see #assert_statsd_increment)
14
91
  def assert_statsd_measure(metric_name, options = {}, &block)
15
92
  assert_statsd_call(:ms, metric_name, options, &block)
16
93
  end
17
94
 
95
+ # Asserts that a given gauge metric occurred inside the provided block.
96
+ #
97
+ # @param metric_name (see #assert_statsd_increment)
98
+ # @param options (see #assert_statsd_increment)
99
+ # @yield (see #assert_statsd_increment)
100
+ # @return [void]
101
+ # @raise (see #assert_statsd_increment)
18
102
  def assert_statsd_gauge(metric_name, options = {}, &block)
19
103
  assert_statsd_call(:g, metric_name, options, &block)
20
104
  end
21
105
 
106
+ # Asserts that a given histogram metric occurred inside the provided block.
107
+ #
108
+ # @param metric_name (see #assert_statsd_increment)
109
+ # @param options (see #assert_statsd_increment)
110
+ # @yield (see #assert_statsd_increment)
111
+ # @return [void]
112
+ # @raise (see #assert_statsd_increment)
22
113
  def assert_statsd_histogram(metric_name, options = {}, &block)
23
114
  assert_statsd_call(:h, metric_name, options, &block)
24
115
  end
25
116
 
117
+ # Asserts that a given distribution metric occurred inside the provided block.
118
+ #
119
+ # @param metric_name (see #assert_statsd_increment)
120
+ # @param options (see #assert_statsd_increment)
121
+ # @yield (see #assert_statsd_increment)
122
+ # @return [void]
123
+ # @raise (see #assert_statsd_increment)
26
124
  def assert_statsd_distribution(metric_name, options = {}, &block)
27
125
  assert_statsd_call(:d, metric_name, options, &block)
28
126
  end
29
127
 
128
+ # Asserts that a given set metric occurred inside the provided block.
129
+ #
130
+ # @param metric_name (see #assert_statsd_increment)
131
+ # @param options (see #assert_statsd_increment)
132
+ # @yield (see #assert_statsd_increment)
133
+ # @return [void]
134
+ # @raise (see #assert_statsd_increment)
30
135
  def assert_statsd_set(metric_name, options = {}, &block)
31
136
  assert_statsd_call(:s, metric_name, options, &block)
32
137
  end
33
138
 
139
+ # Asserts that a given key/value metric occurred inside the provided block.
140
+ #
141
+ # @param metric_name (see #assert_statsd_increment)
142
+ # @param options (see #assert_statsd_increment)
143
+ # @yield (see #assert_statsd_increment)
144
+ # @return [void]
145
+ # @raise (see #assert_statsd_increment)
34
146
  def assert_statsd_key_value(metric_name, options = {}, &block)
35
147
  assert_statsd_call(:kv, metric_name, options, &block)
36
148
  end
37
149
 
150
+ # Asserts that the set of provided metric expectations came true.
151
+ #
152
+ # Generally, it's recommended to use more specific assertion methods, like
153
+ # {#assert_statsd_increment} and others.
154
+ #
38
155
  # @private
39
- def assert_statsd_calls(expected_metrics, &block)
40
- unless block
41
- raise ArgumentError, "block must be given"
42
- end
156
+ # @param [Array<StatsD::Instrument::MetricExpectation>] expected_metrics The set of
157
+ # metric expectations to verify.
158
+ # @yield (see #assert_statsd_increment)
159
+ # @return [void]
160
+ # @raise (see #assert_statsd_increment)
161
+ def assert_statsd_calls(expected_metrics)
162
+ raise ArgumentError, "block must be given" unless block_given?
163
+
164
+ capture_backend = StatsD::Instrument::Backends::CaptureBackend.new
165
+ with_capture_backend(capture_backend) do
166
+ begin
167
+ yield
168
+ rescue => exception
169
+ flunk(<<~MESSAGE)
170
+ An exception occurred in the block provided to the StatsD assertion.
171
+
172
+ #{exception.class.name}: #{exception.message}
173
+ \t#{exception.backtrace.join("\n\t")}
174
+
175
+ If this exception is expected, make sure to handle it using `assert_raises`
176
+ inside the block provided to the StatsD assertion.
177
+ MESSAGE
178
+ end
179
+
180
+ metrics = capture_backend.collected_metrics
181
+ matched_expected_metrics = []
182
+ expected_metrics.each do |expected_metric|
183
+ expected_metric_times = expected_metric.times
184
+ expected_metric_times_remaining = expected_metric.times
185
+ filtered_metrics = metrics.select { |m| m.type == expected_metric.type && m.name == expected_metric.name }
186
+
187
+ if filtered_metrics.empty?
188
+ flunk("No StatsD calls for metric #{expected_metric.name} of type #{expected_metric.type} were made.")
189
+ end
190
+
191
+ filtered_metrics.each do |metric|
192
+ next unless expected_metric.matches(metric)
193
+
194
+ assert(within_numeric_range?(metric.sample_rate),
195
+ "Unexpected sample rate type for metric #{metric.name}, must be numeric")
196
+
197
+ if expected_metric_times_remaining == 0
198
+ flunk("Unexpected StatsD call; number of times this metric " \
199
+ "was expected exceeded: #{expected_metric.inspect}")
200
+ end
43
201
 
44
- metrics = capture_statsd_calls(&block)
45
- matched_expected_metrics = []
46
-
47
- expected_metrics.each do |expected_metric|
48
- expected_metric_times = expected_metric.times
49
- expected_metric_times_remaining = expected_metric.times
50
- filtered_metrics = metrics.select { |m| m.type == expected_metric.type && m.name == expected_metric.name }
51
- assert filtered_metrics.length > 0,
52
- "No StatsD calls for metric #{expected_metric.name} of type #{expected_metric.type} were made."
53
-
54
- filtered_metrics.each do |metric|
55
- assert within_numeric_range?(metric.sample_rate),
56
- "Unexpected sample rate type for metric #{metric.name}, must be numeric"
57
- if expected_metric.matches(metric)
58
- assert expected_metric_times_remaining > 0,
59
- "Unexpected StatsD call; number of times this metric was expected exceeded: #{expected_metric.inspect}"
60
202
  expected_metric_times_remaining -= 1
61
203
  metrics.delete(metric)
62
204
  if expected_metric_times_remaining == 0
63
205
  matched_expected_metrics << expected_metric
64
206
  end
65
207
  end
208
+
209
+ next if expected_metric_times_remaining == 0
210
+
211
+ msg = +"Metric expected #{expected_metric_times} times but seen " \
212
+ "#{expected_metric_times - expected_metric_times_remaining} " \
213
+ "times: #{expected_metric.inspect}."
214
+ msg << "\nCaptured metrics with the same key: #{filtered_metrics}" if filtered_metrics.any?
215
+ flunk(msg)
66
216
  end
217
+ expected_metrics -= matched_expected_metrics
67
218
 
68
- assert expected_metric_times_remaining == 0,
69
- "Metric expected #{expected_metric_times} times but seen"\
70
- " #{expected_metric_times-expected_metric_times_remaining}"\
71
- " times: #{expected_metric.inspect}"
72
- end
73
- expected_metrics -= matched_expected_metrics
219
+ unless expected_metrics.empty?
220
+ flunk("Unexpected StatsD calls; the following metric expectations " \
221
+ "were not satisfied: #{expected_metrics.inspect}")
222
+ end
74
223
 
75
- assert expected_metrics.empty?,
76
- "Unexpected StatsD calls; the following metric expectations were not satisfied: #{expected_metrics.inspect}"
224
+ pass
225
+ end
77
226
  end
78
227
 
79
228
  private
@@ -87,6 +236,6 @@ module StatsD::Instrument::Assertions
87
236
  end
88
237
 
89
238
  def within_numeric_range?(object)
90
- object.kind_of?(Numeric) && (0.0..1.0).cover?(object)
239
+ object.is_a?(Numeric) && (0.0..1.0).cover?(object)
91
240
  end
92
241
  end
@@ -1,12 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This abstract class specifies the interface a backend implementation should conform to.
2
4
  # @abstract
3
5
  class StatsD::Instrument::Backend
4
-
5
6
  # Collects a metric.
6
7
  #
7
8
  # @param metric [StatsD::Instrument::Metric] The metric to collect
8
9
  # @return [void]
9
- def collect_metric(metric)
10
+ def collect_metric(_metric)
10
11
  raise NotImplementedError, "Use a concerete backend implementation"
11
12
  end
12
13
  end
@@ -1,5 +1,6 @@
1
- module StatsD::Instrument::Backends
1
+ # frozen_string_literal: true
2
2
 
3
+ module StatsD::Instrument::Backends
3
4
  # The capture backend is used to capture the metrics that are collected, so you can
4
5
  # run assertions on them.
5
6
  #
@@ -8,6 +9,7 @@ module StatsD::Instrument::Backends
8
9
  # @see StatsD::Instrument::Assertions
9
10
  class CaptureBackend < StatsD::Instrument::Backend
10
11
  attr_reader :collected_metrics
12
+ attr_accessor :parent
11
13
 
12
14
  def initialize
13
15
  reset
@@ -17,6 +19,7 @@ module StatsD::Instrument::Backends
17
19
  # @param metric [StatsD::Instrument::Metric] The metric to collect.
18
20
  # @return [void]
19
21
  def collect_metric(metric)
22
+ parent&.collect_metric(metric)
20
23
  @collected_metrics << metric
21
24
  end
22
25
 
@@ -1,10 +1,10 @@
1
- module StatsD::Instrument::Backends
1
+ # frozen_string_literal: true
2
2
 
3
+ module StatsD::Instrument::Backends
3
4
  # The logger backend simply logs every metric to a logger
4
5
  # @!attribute logger
5
6
  # @return [Logger]
6
7
  class LoggerBackend < StatsD::Instrument::Backend
7
-
8
8
  attr_accessor :logger
9
9
 
10
10
  def initialize(logger)
@@ -14,7 +14,7 @@ module StatsD::Instrument::Backends
14
14
  # @param metric [StatsD::Instrument::Metric]
15
15
  # @return [void]
16
16
  def collect_metric(metric)
17
- logger.info "[StatsD] #{metric}"
17
+ logger.info("[StatsD] #{metric}")
18
18
  end
19
19
  end
20
20
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module StatsD::Instrument::Backends
2
4
  # The null backend does nothing when receiving a metric, effectively disabling the gem completely.
3
5
  class NullBackend < StatsD::Instrument::Backend
@@ -1,63 +1,56 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'monitor'
2
4
 
3
5
  module StatsD::Instrument::Backends
4
6
  class UDPBackend < StatsD::Instrument::Backend
5
-
6
7
  BASE_SUPPORTED_METRIC_TYPES = { c: true, ms: true, g: true, s: true }
7
8
 
8
9
  class DogStatsDProtocol
9
- EVENT_OPTIONS = {
10
- date_happened: 'd',
11
- hostname: 'h',
12
- aggregation_key: 'k',
13
- priority: 'p',
14
- source_type_name: 's',
15
- alert_type: 't',
16
- }
17
-
18
- SERVICE_CHECK_OPTIONS = {
19
- timestamp: 'd',
20
- hostname: 'h',
21
- message: 'm',
22
- }
23
-
24
10
  SUPPORTED_METRIC_TYPES = BASE_SUPPORTED_METRIC_TYPES.merge(h: true, _e: true, _sc: true, d: true)
25
11
 
12
+ SERVICE_CHECK_STATUSES = { ok: 0, warning: 1, critical: 2, unknown: 3 }
13
+
26
14
  def generate_packet(metric)
27
- packet = ""
15
+ packet = +""
28
16
 
29
17
  if metric.type == :_e
30
- escaped_title = metric.name.tr('\n', '\\n')
31
- escaped_text = metric.value.tr('\n', '\\n')
18
+ escaped_title = metric.name.gsub("\n", "\\n")
19
+ escaped_text = metric.value.gsub("\n", "\\n")
32
20
 
33
21
  packet << "_e{#{escaped_title.size},#{escaped_text.size}}:#{escaped_title}|#{escaped_text}"
34
- packet << generate_metadata(metric, EVENT_OPTIONS)
22
+ packet << "|h:#{metric.metadata[:hostname]}" if metric.metadata[:hostname]
23
+ packet << "|d:#{metric.metadata[:timestamp].to_i}" if metric.metadata[:timestamp]
24
+ packet << "|k:#{metric.metadata[:aggregation_key]}" if metric.metadata[:aggregation_key]
25
+ packet << "|p:#{metric.metadata[:priority]}" if metric.metadata[:priority]
26
+ packet << "|s:#{metric.metadata[:source_type_name]}" if metric.metadata[:source_type_name]
27
+ packet << "|t:#{metric.metadata[:alert_type]}" if metric.metadata[:alert_type]
28
+ packet << "|##{metric.tags.join(',')}" if metric.tags
29
+
35
30
  elsif metric.type == :_sc
36
- packet << "_sc|#{metric.name}|#{metric.value}"
37
- packet << generate_metadata(metric, SERVICE_CHECK_OPTIONS)
31
+ status = metric.value.is_a?(Integer) ? metric.value : SERVICE_CHECK_STATUSES.fetch(metric.value.to_sym)
32
+
33
+ packet << "_sc|#{metric.name}|#{status}"
34
+ packet << "|h:#{metric.metadata[:hostname]}" if metric.metadata[:hostname]
35
+ packet << "|d:#{metric.metadata[:timestamp].to_i}" if metric.metadata[:timestamp]
36
+ packet << "|##{metric.tags.join(',')}" if metric.tags
37
+ packet << "|m:#{metric.metadata[:message]}" if metric.metadata[:message]
38
+
38
39
  else
39
40
  packet << "#{metric.name}:#{metric.value}|#{metric.type}"
41
+ packet << "|@#{metric.sample_rate}" if metric.sample_rate < 1
42
+ packet << "|##{metric.tags.join(',')}" if metric.tags
40
43
  end
41
44
 
42
- packet << "|@#{metric.sample_rate}" if metric.sample_rate < 1
43
- packet << "|##{metric.tags.join(',')}" if metric.tags
44
45
  packet
45
46
  end
46
-
47
- private
48
-
49
- def generate_metadata(metric, options)
50
- (metric.metadata.keys & options.keys).map do |key|
51
- "|#{options[key]}:#{metric.metadata[key]}"
52
- end.join
53
- end
54
47
  end
55
48
 
56
49
  class StatsiteStatsDProtocol
57
50
  SUPPORTED_METRIC_TYPES = BASE_SUPPORTED_METRIC_TYPES.merge(kv: true)
58
51
 
59
52
  def generate_packet(metric)
60
- packet = "#{metric.name}:#{metric.value}|#{metric.type}"
53
+ packet = +"#{metric.name}:#{metric.value}|#{metric.type}"
61
54
  packet << "|@#{metric.sample_rate}" unless metric.sample_rate == 1
62
55
  packet << "\n"
63
56
  packet
@@ -68,7 +61,7 @@ module StatsD::Instrument::Backends
68
61
  SUPPORTED_METRIC_TYPES = BASE_SUPPORTED_METRIC_TYPES
69
62
 
70
63
  def generate_packet(metric)
71
- packet = "#{metric.name}:#{metric.value}|#{metric.type}"
64
+ packet = +"#{metric.name}:#{metric.value}|#{metric.type}"
72
65
  packet << "|@#{metric.sample_rate}" if metric.sample_rate < 1
73
66
  packet
74
67
  end
@@ -88,19 +81,20 @@ module StatsD::Instrument::Backends
88
81
 
89
82
  def implementation=(value)
90
83
  @packet_factory = case value
91
- when :datadog
92
- DogStatsDProtocol.new
93
- when :statsite
94
- StatsiteStatsDProtocol.new
95
- else
96
- StatsDProtocol.new
97
- end
84
+ when :datadog
85
+ DogStatsDProtocol.new
86
+ when :statsite
87
+ StatsiteStatsDProtocol.new
88
+ else
89
+ StatsDProtocol.new
90
+ end
98
91
  @implementation = value
99
92
  end
100
93
 
101
94
  def collect_metric(metric)
102
95
  unless @packet_factory.class::SUPPORTED_METRIC_TYPES[metric.type]
103
- StatsD.logger.warn("[StatsD] Metric type #{metric.type.inspect} not supported on #{implementation} implementation.")
96
+ StatsD.logger.warn("[StatsD] Metric type #{metric.type.inspect} is not supported " \
97
+ "on #{implementation} implementation.")
104
98
  return false
105
99
  end
106
100
 
@@ -139,13 +133,13 @@ module StatsD::Instrument::Backends
139
133
  synchronize do
140
134
  socket.send(command, 0) > 0
141
135
  end
142
- rescue ThreadError => e
136
+ rescue ThreadError
143
137
  # In cases where a TERM or KILL signal has been sent, and we send stats as
144
138
  # part of a signal handler, locks cannot be acquired, so we do our best
145
139
  # to try and send the command without a lock.
146
140
  socket.send(command, 0) > 0
147
- rescue SocketError, IOError, SystemCallError, Errno::ECONNREFUSED => e
148
- StatsD.logger.error "[StatsD] #{e.class.name}: #{e.message}"
141
+ rescue SocketError, IOError, SystemCallError => e
142
+ StatsD.logger.error("[StatsD] #{e.class.name}: #{e.message}")
149
143
  invalidate_socket
150
144
  end
151
145
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
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::CaptureSink
6
+ attr_reader :parent, :datagrams, :datagram_class
7
+
8
+ def initialize(parent:, datagram_class: StatsD::Instrument::Datagram)
9
+ @parent = parent
10
+ @datagram_class = datagram_class
11
+ @datagrams = []
12
+ end
13
+
14
+ def sample?(_sample_rate)
15
+ true
16
+ end
17
+
18
+ def <<(datagram)
19
+ @datagrams << datagram_class.new(datagram)
20
+ parent << datagram
21
+ self
22
+ end
23
+
24
+ def clear
25
+ @datagrams.clear
26
+ end
27
+ end