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
data/test/statsd_test.rb CHANGED
@@ -1,166 +1,177 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  class StatsDTest < Minitest::Test
4
6
  include StatsD::Instrument::Assertions
5
7
 
8
+ def teardown
9
+ StatsD.default_tags = nil
10
+ end
11
+
6
12
  def test_statsd_passed_collections_to_backend
7
13
  StatsD.backend.expects(:collect_metric).with(instance_of(StatsD::Instrument::Metric))
8
14
  StatsD.increment('test')
9
15
  end
10
16
 
11
17
  def test_statsd_measure_with_explicit_value
12
- result = nil
13
- metric = capture_statsd_call { result = StatsD.measure('values.foobar', 42) }
14
- assert_equal metric, result
18
+ metric = capture_statsd_call { StatsD.measure('values.foobar', 42) }
15
19
  assert_equal 'values.foobar', metric.name
16
20
  assert_equal 42, metric.value
17
21
  assert_equal :ms, metric.type
18
22
  end
19
23
 
20
- def test_statsd_measure_with_explicit_value_and_distribution_override
21
- metric = capture_statsd_call { result = StatsD.measure('values.foobar', 42, as_dist: true) }
22
- assert_equal :d, metric.type
23
- end
24
-
25
- def test_statsd_measure_with_explicit_value_as_keyword_argument
26
- result = nil
27
- metric = capture_statsd_call { result = StatsD.measure('values.foobar', value: 42) }
28
- assert_equal metric, result
29
- assert_equal 'values.foobar', metric.name
30
- assert_equal 42, metric.value
31
- assert_equal :ms, metric.type
32
- end
33
-
34
- def test_statsd_measure_with_explicit_value_keyword_and_distribution_override
35
- metric = capture_statsd_call { result = StatsD.measure('values.foobar', value: 42, as_dist: true) }
36
- assert_equal :d, metric.type
37
- end
38
-
39
24
  def test_statsd_measure_without_value_or_block
40
25
  assert_raises(ArgumentError) { StatsD.measure('values.foobar', tags: 123) }
41
26
  end
42
27
 
43
28
  def test_statsd_measure_with_explicit_value_and_sample_rate
44
- metric = capture_statsd_call { StatsD.measure('values.foobar', 42, :sample_rate => 0.1) }
29
+ metric = capture_statsd_call { StatsD.measure('values.foobar', 42, sample_rate: 0.1) }
45
30
  assert_equal 0.1, metric.sample_rate
46
31
  end
47
32
 
48
33
  def test_statsd_measure_with_benchmarked_block_duration
49
- StatsD::Instrument.stubs(:duration).returns(1.12)
34
+ Process.stubs(:clock_gettime).returns(5.0, 5.0 + 1.12)
50
35
  metric = capture_statsd_call do
51
36
  StatsD.measure('values.foobar') { 'foo' }
52
37
  end
53
38
  assert_equal 1120.0, metric.value
54
39
  end
55
40
 
56
- def test_statsd_measure_use_distribution_override_for_a_block
57
- metric = capture_statsd_call do
58
- StatsD.measure('values.foobar', as_dist: true) { 'foo' }
59
- end
60
- assert_equal :d, metric.type
61
- end
62
-
63
41
  def test_statsd_measure_returns_return_value_of_block
64
42
  return_value = StatsD.measure('values.foobar') { 'sarah' }
65
43
  assert_equal 'sarah', return_value
66
44
  end
67
45
 
68
- def test_statsd_measure_returns_return_value_of_block_even_if_nil
69
- return_value = StatsD.measure('values.foobar', as_dist: true) { nil }
70
- assert_nil return_value
46
+ def test_statsd_measure_with_return_in_block_still_captures
47
+ Process.stubs(:clock_gettime).returns(5.0, 6.12)
48
+ result = nil
49
+ metric = capture_statsd_call do
50
+ lambda = -> do
51
+ StatsD.measure('values.foobar') { return 'from lambda' }
52
+ end
53
+
54
+ result = lambda.call
55
+ end
56
+
57
+ assert_equal 'from lambda', result
58
+ assert_equal 1120.0, metric.value
71
59
  end
72
60
 
73
- def test_statsd_increment
61
+ def test_statsd_measure_with_exception_in_block_still_captures
62
+ Process.stubs(:clock_gettime).returns(5.0, 6.12)
74
63
  result = nil
75
- metric = capture_statsd_call { result = StatsD.increment('values.foobar', 3) }
76
- assert_equal metric, result
64
+ metric = capture_statsd_call do
65
+ lambda = -> do
66
+ StatsD.measure('values.foobar') { raise 'from lambda' }
67
+ end
68
+
69
+ begin
70
+ result = lambda.call
71
+ rescue # rubocop:disable Lint/HandleExceptions:
72
+ end
73
+ end
74
+
75
+ assert_nil result
76
+ assert_equal 1120.0, metric.value
77
+ end
78
+
79
+ def test_statsd_increment
80
+ metric = capture_statsd_call { StatsD.increment('values.foobar', 3) }
77
81
  assert_equal :c, metric.type
78
82
  assert_equal 'values.foobar', metric.name
79
83
  assert_equal 3, metric.value
80
84
  end
81
85
 
82
86
  def test_statsd_increment_with_hash_argument
83
- metric = capture_statsd_call { StatsD.increment('values.foobar', :tags => ['test']) }
87
+ metric = capture_statsd_call { StatsD.increment('values.foobar', tags: ['test']) }
84
88
  assert_equal StatsD.default_sample_rate, metric.sample_rate
85
89
  assert_equal ['test'], metric.tags
86
90
  assert_equal 1, metric.value
87
91
  end
88
92
 
89
- def test_statsd_increment_with_value_as_keyword_argument
90
- metric = capture_statsd_call { StatsD.increment('values.foobar', :value => 2) }
91
- assert_equal StatsD.default_sample_rate, metric.sample_rate
92
- assert_equal 2, metric.value
93
- end
94
-
95
- def test_statsd_increment_with_multiple_arguments
96
- metric = capture_statsd_call { StatsD.increment('values.foobar', 12, nil, ['test']) }
97
- assert_equal StatsD.default_sample_rate, metric.sample_rate
98
- assert_equal ['test'], metric.tags
99
- assert_equal 12, metric.value
100
- end
101
-
102
93
  def test_statsd_gauge
103
- result = nil
104
- metric = capture_statsd_call { result = StatsD.gauge('values.foobar', 12) }
105
- assert_equal metric, result
94
+ metric = capture_statsd_call { StatsD.gauge('values.foobar', 12) }
106
95
  assert_equal :g, metric.type
107
96
  assert_equal 'values.foobar', metric.name
108
97
  assert_equal 12, metric.value
109
98
  end
110
99
 
111
- def test_statsd_gauge_with_keyword_argument
112
- result = nil
113
- metric = capture_statsd_call { result = StatsD.gauge('values.foobar', value: 13) }
114
- assert_equal metric, result
115
- assert_equal :g, metric.type
116
- assert_equal 'values.foobar', metric.name
117
- assert_equal 13, metric.value
118
- end
119
-
120
100
  def test_statsd_gauge_without_value
121
101
  assert_raises(ArgumentError) { StatsD.gauge('values.foobar', tags: 123) }
122
102
  end
123
103
 
124
104
  def test_statsd_set
125
- result = nil
126
- metric = capture_statsd_call { result = StatsD.set('values.foobar', 'unique_identifier') }
127
- assert_equal metric, result
105
+ metric = capture_statsd_call { StatsD.set('values.foobar', 'unique_identifier') }
128
106
  assert_equal :s, metric.type
129
107
  assert_equal 'values.foobar', metric.name
130
108
  assert_equal 'unique_identifier', metric.value
131
109
  end
132
110
 
133
111
  def test_statsd_histogram
134
- result = nil
135
- metric = capture_statsd_call { result = StatsD.histogram('values.foobar', 42) }
136
- assert_equal metric, result
112
+ metric = capture_statsd_call { StatsD.histogram('values.foobar', 42) }
137
113
  assert_equal :h, metric.type
138
114
  assert_equal 'values.foobar', metric.name
139
115
  assert_equal 42, metric.value
140
116
  end
141
117
 
142
118
  def test_statsd_distribution
143
- result = nil
144
- metric = capture_statsd_call { result = StatsD.distribution('values.foobar', 42) }
145
- assert_equal metric, result
119
+ metric = capture_statsd_call { StatsD.distribution('values.foobar', 42) }
146
120
  assert_equal :d, metric.type
147
121
  assert_equal 'values.foobar', metric.name
148
122
  assert_equal 42, metric.value
149
123
  end
150
124
 
151
125
  def test_statsd_distribution_with_benchmarked_block_duration
152
- StatsD::Instrument.stubs(:duration).returns(1.12)
126
+ Process.stubs(:clock_gettime).returns(5.0, 5.0 + 1.12)
153
127
  metric = capture_statsd_call do
154
- StatsD.distribution('values.foobar') { 'foo' }
128
+ result = StatsD.distribution('values.foobar') { 'foo' }
129
+ assert_equal 'foo', result
155
130
  end
156
131
  assert_equal :d, metric.type
157
132
  assert_equal 1120.0, metric.value
158
133
  end
159
134
 
135
+ def test_statsd_distribution_with_return_in_block_still_captures
136
+ Process.stubs(:clock_gettime).returns(5.0, 5.0 + 1.12)
137
+ result = nil
138
+ metric = capture_statsd_call do
139
+ lambda = -> do
140
+ StatsD.distribution('values.foobar') { return 'from lambda' }
141
+ flunk("This code should not be reached")
142
+ end
143
+
144
+ result = lambda.call
145
+ end
146
+
147
+ assert_equal 'from lambda', result
148
+ assert_equal :d, metric.type
149
+ assert_equal 1120.0, metric.value
150
+ end
151
+
152
+ def test_statsd_distribution_with_exception_in_block_still_captures
153
+ Process.stubs(:clock_gettime).returns(5.0, 5.0 + 1.12)
154
+ result = nil
155
+ metric = capture_statsd_call do
156
+ lambda = -> do
157
+ StatsD.distribution('values.foobar') { raise 'from lambda' }
158
+ end
159
+
160
+ begin
161
+ result = lambda.call
162
+ rescue # rubocop:disable Lint/HandleExceptions
163
+ end
164
+ end
165
+
166
+ assert_nil result
167
+ assert_equal :d, metric.type
168
+ assert_equal 1120.0, metric.value
169
+ end
170
+
160
171
  def test_statsd_distribution_with_block_and_options
161
- StatsD::Instrument.stubs(:duration).returns(1.12)
172
+ Process.stubs(:clock_gettime).returns(5.0, 5.0 + 1.12)
162
173
  metric = capture_statsd_call do
163
- StatsD.distribution('values.foobar', :tags => ['test'], :sample_rate => 0.9) { 'foo' }
174
+ StatsD.distribution('values.foobar', tags: ['test'], sample_rate: 0.9) { 'foo' }
164
175
  end
165
176
  assert_equal 1120.0, metric.value
166
177
  assert_equal 'values.foobar', metric.name
@@ -179,14 +190,37 @@ class StatsDTest < Minitest::Test
179
190
  end
180
191
 
181
192
  def test_statsd_key_value
182
- result = nil
183
- metric = capture_statsd_call { result = StatsD.key_value('values.foobar', 42) }
184
- assert_equal metric, result
193
+ metric = capture_statsd_call { StatsD.key_value('values.foobar', 42) }
185
194
  assert_equal :kv, metric.type
186
195
  assert_equal 'values.foobar', metric.name
187
196
  assert_equal 42, metric.value
188
197
  end
189
198
 
199
+ def test_statsd_durarion_returns_time_in_seconds
200
+ duration = StatsD::Instrument.duration {}
201
+ assert_kind_of Float, duration
202
+ end
203
+
204
+ def test_statsd_durarion_does_not_swallow_exceptions
205
+ assert_raises(RuntimeError) do
206
+ StatsD::Instrument.duration { raise "Foo" }
207
+ end
208
+ end
209
+
210
+ def test_statsd_default_tags_get_normalized
211
+ StatsD.default_tags = { first_tag: 'first_value', second_tag: 'second_value' }
212
+ assert_equal ['first_tag:first_value', 'second_tag:second_value'], StatsD.default_tags
213
+ end
214
+
215
+ def test_name_prefix
216
+ StatsD.stubs(:prefix).returns('prefix')
217
+ m = capture_statsd_call { StatsD.increment('counter') }
218
+ assert_equal 'prefix.counter', m.name
219
+
220
+ m = capture_statsd_call { StatsD.increment('counter', no_prefix: true) }
221
+ assert_equal 'counter', m.name
222
+ end
223
+
190
224
  protected
191
225
 
192
226
  def capture_statsd_call(&block)
data/test/test_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV['ENV'] = 'test'
2
4
 
3
5
  require 'minitest/autorun'
@@ -7,4 +9,13 @@ require 'set'
7
9
  require 'logger'
8
10
  require 'statsd-instrument'
9
11
 
10
- StatsD.logger = Logger.new('/dev/null')
12
+ require_relative 'helpers/rubocop_helper'
13
+
14
+ module StatsD::Instrument
15
+ def self.strict_mode_enabled?
16
+ StatsD::Instrument.const_defined?(:Strict) &&
17
+ StatsD.singleton_class.ancestors.include?(StatsD::Instrument::Strict)
18
+ end
19
+ end
20
+
21
+ StatsD.logger = Logger.new(File::NULL)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  class UDPBackendTest < Minitest::Test
@@ -76,7 +78,7 @@ class UDPBackendTest < Minitest::Test
76
78
  StatsD.histogram('fooh', 42.4)
77
79
  end
78
80
 
79
- def test_distribution_syntax_on_datadog
81
+ def test_distribution_syntax_on_datadog
80
82
  @backend.implementation = :datadog
81
83
  @backend.expects(:write_packet).with('fooh:42.4|d')
82
84
  StatsD.distribution('fooh', 42.4)
@@ -84,20 +86,25 @@ class UDPBackendTest < Minitest::Test
84
86
 
85
87
  def test_event_on_datadog
86
88
  @backend.implementation = :datadog
87
- @backend.expects(:write_packet).with('_e{4,3}:fooh|baz|h:localhost:3000|@0.01|#foo')
88
- StatsD.event('fooh', 'baz', hostname: 'localhost:3000', sample_rate: 0.01, tags: ["foo"])
89
+ @backend.expects(:write_packet).with('_e{4,3}:fooh|baz|h:localhost|#foo')
90
+ StatsD.event('fooh', 'baz', hostname: 'localhost', tags: ["foo"])
89
91
  end
90
92
 
91
93
  def test_event_on_datadog_escapes_newlines
92
94
  @backend.implementation = :datadog
93
- @backend.expects(:write_packet).with('_e{8,5}:fooh\\n\\n|baz\\n')
94
- StatsD.event('fooh\n\n', 'baz\n')
95
+ @backend.expects(:write_packet).with("_e{8,5}:fooh\\n\\n|baz\\n")
96
+ StatsD.event("fooh\n\n", "baz\n")
95
97
  end
96
98
 
97
99
  def test_event_on_datadog_ignores_invalid_metadata
98
100
  @backend.implementation = :datadog
99
- @backend.expects(:write_packet).with('_e{4,3}:fooh|baz')
100
- StatsD.event('fooh', 'baz', i_am_not_supported: 'not-supported')
101
+ if StatsD::Instrument.strict_mode_enabled?
102
+ assert_raises(ArgumentError) { StatsD.event('fooh', 'baz', sample_rate: 0.01) }
103
+ assert_raises(ArgumentError) { StatsD.event('fooh', 'baz', unsupported: 'foo') }
104
+ else
105
+ @backend.expects(:write_packet).with('_e{4,3}:fooh|baz')
106
+ StatsD.event('fooh', 'baz', sample_rate: 0.01, i_am_not_supported: 'not-supported')
107
+ end
101
108
  end
102
109
 
103
110
  def test_event_warns_when_not_using_datadog
@@ -109,14 +116,26 @@ class UDPBackendTest < Minitest::Test
109
116
 
110
117
  def test_service_check_on_datadog
111
118
  @backend.implementation = :datadog
112
- @backend.expects(:write_packet).with('_sc|fooh|baz|h:localhost:3000|@0.01|#foo')
113
- StatsD.service_check('fooh', 'baz', hostname: 'localhost:3000', sample_rate: 0.01, tags: ["foo"])
119
+ @backend.expects(:write_packet).with('_sc|fooh|0|h:localhost|#foo')
120
+ StatsD.service_check('fooh', 0, hostname: 'localhost', tags: ["foo"])
114
121
  end
115
122
 
116
123
  def test_service_check_on_datadog_ignores_invalid_metadata
117
124
  @backend.implementation = :datadog
118
- @backend.expects(:write_packet).with('_sc|fooh|baz')
119
- StatsD.service_check('fooh', 'baz', i_am_not_supported: 'not-supported')
125
+ if StatsD::Instrument.strict_mode_enabled?
126
+ assert_raises(ArgumentError) { StatsD.service_check('fooh', "warning", sample_rate: 0.01) }
127
+ assert_raises(ArgumentError) { StatsD.service_check('fooh', "warning", unsupported: 'foo') }
128
+ else
129
+ @backend.expects(:write_packet).with('_sc|fooh|1')
130
+ StatsD.service_check('fooh', "warning", sample_rate: 0.01, i_am_not_supported: 'not-supported')
131
+ end
132
+ end
133
+
134
+ def test_service_check_on_datadog_will_append_message_as_final_metadata_field
135
+ @backend.implementation = :datadog
136
+ @backend.expects(:write_packet).with('_sc|fooh|0|d:1230768000|#quc|m:Everything OK')
137
+ StatsD.service_check('fooh', :ok, message: "Everything OK",
138
+ timestamp: Time.parse('2009-01-01T00:00:00Z'), tags: ['quc'])
120
139
  end
121
140
 
122
141
  def test_service_check_warns_when_not_using_datadog
@@ -146,11 +165,19 @@ class UDPBackendTest < Minitest::Test
146
165
  StatsD.key_value('fooy', 42)
147
166
  end
148
167
 
168
+ # For key_value metrics (only supported by statsite), the sample rate
169
+ # part of the datagram format is (ab)used to be set to a timestamp instead.
170
+ # Changing that to `sample_rate: timestamp` does not make sense, so we
171
+ # disable the rubocop rule for positional arguments for now,
172
+ # until we figure out how we want to handle this.
173
+
174
+ # rubocop:disable StatsD/PositionalArguments
149
175
  def test_supports_key_value_with_timestamp_on_statsite
150
176
  @backend.implementation = :statsite
151
177
  @backend.expects(:write_packet).with("fooy:42|kv|@123456\n")
152
178
  StatsD.key_value('fooy', 42, 123456)
153
179
  end
180
+ # rubocop:enable StatsD/PositionalArguments
154
181
 
155
182
  def test_warn_when_using_key_value_and_not_on_statsite
156
183
  @backend.implementation = :other
@@ -183,31 +210,6 @@ class UDPBackendTest < Minitest::Test
183
210
  StatsD.increment('fail')
184
211
  end
185
212
 
186
- def test_synchronize_in_exit_handler_handles_thread_error_and_exits_cleanly
187
- pid = fork do
188
- Signal.trap('TERM') do
189
- $sent_packet = false
190
-
191
- class << @backend.socket
192
- def send(command, *args)
193
- $sent_packet = true if command == 'exiting:1|c'
194
- command.length
195
- end
196
- end
197
-
198
- StatsD.increment('exiting')
199
- Process.exit!($sent_packet)
200
- end
201
-
202
- sleep 100
203
- end
204
-
205
- Process.kill('TERM', pid)
206
- Process.waitpid(pid)
207
-
208
- assert $?.success?, 'socket did not write on exit'
209
- end
210
-
211
213
  def test_socket_error_should_invalidate_socket
212
214
  seq = sequence('fail_then_succeed')
213
215
 
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ require 'statsd/instrument/client'
6
+
7
+ class UDPSinktest < Minitest::Test
8
+ def setup
9
+ @receiver = UDPSocket.new
10
+ @receiver.bind('localhost', 0)
11
+ @host = @receiver.addr[2]
12
+ @port = @receiver.addr[1]
13
+ end
14
+
15
+ def teardown
16
+ @receiver.close
17
+ end
18
+
19
+ def test_udp_sink_sends_data_over_udp
20
+ udp_sink = StatsD::Instrument::UDPSink.new(@host, @port)
21
+ udp_sink << 'foo:1|c'
22
+
23
+ datagram, _source = @receiver.recvfrom(100)
24
+ assert_equal 'foo:1|c', datagram
25
+ end
26
+
27
+ def test_sample?
28
+ udp_sink = StatsD::Instrument::UDPSink.new(@host, @port)
29
+ assert udp_sink.sample?(1)
30
+ refute udp_sink.sample?(0)
31
+
32
+ udp_sink.stubs(:rand).returns(0.3)
33
+ assert udp_sink.sample?(0.5)
34
+
35
+ udp_sink.stubs(:rand).returns(0.7)
36
+ refute udp_sink.sample?(0.5)
37
+ end
38
+
39
+ def test_parallelism
40
+ udp_sink = StatsD::Instrument::UDPSink.new(@host, @port)
41
+ 50.times { |i| Thread.new { udp_sink << "foo:#{i}|c" << "bar:#{i}|c" } }
42
+ datagrams = []
43
+ 100.times do
44
+ datagram, _source = @receiver.recvfrom(100)
45
+ datagrams << datagram
46
+ end
47
+
48
+ assert_equal 100, datagrams.size
49
+ end
50
+
51
+ def test_socket_error_should_invalidate_socket
52
+ UDPSocket.stubs(:new).returns(socket = mock('socket'))
53
+
54
+ seq = sequence('connect_fail_connect_succeed')
55
+ socket.expects(:connect).with('localhost', 8125).in_sequence(seq)
56
+ socket.expects(:send).raises(Errno::EDESTADDRREQ).in_sequence(seq)
57
+ socket.expects(:connect).with('localhost', 8125).in_sequence(seq)
58
+ socket.expects(:send).returns(1).in_sequence(seq)
59
+
60
+ udp_sink = StatsD::Instrument::UDPSink.new('localhost', 8125)
61
+ udp_sink << 'foo:1|c'
62
+ udp_sink << 'bar:1|c'
63
+ end
64
+
65
+ def test_sends_datagram_in_signal_handler
66
+ udp_sink = StatsD::Instrument::UDPSink.new(@host, @port)
67
+ pid = fork do
68
+ Signal.trap('TERM') do
69
+ udp_sink << "exiting:1|c"
70
+ Process.exit!(0)
71
+ end
72
+
73
+ sleep(10)
74
+ end
75
+
76
+ Process.kill('TERM', pid)
77
+ _, exit_status = Process.waitpid2(pid)
78
+
79
+ assert_equal 0, exit_status, "The forked process did not exit cleanly"
80
+ assert_equal "exiting:1|c", @receiver.recvfrom_nonblock(100).first
81
+
82
+ rescue NotImplementedError
83
+ pass("Fork is not implemented on #{RUBY_PLATFORM}")
84
+ end
85
+ end