statsd-instrument 2.3.2 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'statsd-instrument'
2
4
  require 'benchmark/ips'
3
5
 
4
6
  Benchmark.ips do |bench|
5
7
  bench.report("normalized tags with simple hash") do
6
- StatsD::Instrument::Metric.normalize_tags(:tag => 'value')
8
+ StatsD::Instrument::Metric.normalize_tags(tag: 'value')
7
9
  end
8
10
 
9
11
  bench.report("normalized tags with simple array") do
@@ -11,14 +13,14 @@ Benchmark.ips do |bench|
11
13
  end
12
14
 
13
15
  bench.report("normalized tags with large hash") do
14
- StatsD::Instrument::Metric.normalize_tags({
16
+ StatsD::Instrument::Metric.normalize_tags(
15
17
  mobile: true,
16
18
  pod: "1",
17
19
  protocol: "https",
18
20
  country: "Langbortistan",
19
21
  complete: true,
20
22
  shop: "omg shop that has a longer name",
21
- })
23
+ )
22
24
  end
23
25
 
24
26
  bench.report("normalized tags with large array") do
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  class CaptureBackendTest < Minitest::Test
4
6
  def setup
5
7
  @backend = StatsD::Instrument::Backends::CaptureBackend.new
6
- @metric1 = StatsD::Instrument::Metric::new(type: :c, name: 'mock.counter')
7
- @metric2 = StatsD::Instrument::Metric::new(type: :ms, name: 'mock.measure', value: 123)
8
+ @metric1 = StatsD::Instrument::Metric.new(type: :c, name: 'mock.counter')
9
+ @metric2 = StatsD::Instrument::Metric.new(type: :ms, name: 'mock.measure', value: 123)
8
10
  end
9
11
 
10
12
  def test_collecting_metric
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ require 'statsd/instrument/client'
6
+
7
+ class CaptureSinktest < Minitest::Test
8
+ def test_capture_sink_captures_datagram_instances
9
+ capture_sink = StatsD::Instrument::CaptureSink.new(parent: [])
10
+ capture_sink << 'foo:1|c'
11
+
12
+ assert_equal 1, capture_sink.datagrams.length
13
+ assert_kind_of StatsD::Instrument::Datagram, capture_sink.datagrams.first
14
+ assert_equal 'foo:1|c', capture_sink.datagrams.first.source
15
+ end
16
+
17
+ def test_capture_sink_sends_datagrams_to_parent
18
+ parent = []
19
+ capture_sink = StatsD::Instrument::CaptureSink.new(parent: parent)
20
+ capture_sink << 'foo:1|c' << 'bar:1|c'
21
+
22
+ assert_equal ['foo:1|c', 'bar:1|c'], parent
23
+ end
24
+
25
+ def test_nesting_capture_sink_instances
26
+ null_sink = StatsD::Instrument::NullSink.new
27
+ outer_capture_sink = StatsD::Instrument::CaptureSink.new(parent: null_sink)
28
+ inner_capture_sink = StatsD::Instrument::CaptureSink.new(parent: outer_capture_sink)
29
+
30
+ outer_capture_sink << 'foo:1|c'
31
+ inner_capture_sink << 'bar:1|c'
32
+
33
+ assert_equal ['foo:1|c', 'bar:1|c'], outer_capture_sink.datagrams.map(&:source)
34
+ assert_equal ['bar:1|c'], inner_capture_sink.datagrams.map(&:source)
35
+ end
36
+
37
+ def test_using_a_different_datagram_class
38
+ sink = StatsD::Instrument::CaptureSink.new(parent: [], datagram_class: String)
39
+ sink << 'foo:1|c'
40
+
41
+ assert sink.datagrams.all? { |datagram| datagram.is_a?(String) }
42
+ assert_equal ['foo:1|c'], sink.datagrams
43
+ end
44
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ require 'statsd/instrument/client'
6
+
7
+ class ClientTest < Minitest::Test
8
+ def setup
9
+ @client = StatsD::Instrument::Client.new(datagram_builder_class: StatsD::Instrument::StatsDDatagramBuilder)
10
+ @dogstatsd_client = StatsD::Instrument::Client.new(
11
+ datagram_builder_class: StatsD::Instrument::DogStatsDDatagramBuilder,
12
+ )
13
+ end
14
+
15
+ def test_capture
16
+ inner_datagrams = nil
17
+
18
+ @client.increment('foo')
19
+ outer_datagrams = @client.capture do
20
+ @client.increment('bar')
21
+ inner_datagrams = @client.capture do
22
+ @client.increment('baz')
23
+ end
24
+ end
25
+ @client.increment('quc')
26
+
27
+ assert_equal ['bar', 'baz'], outer_datagrams.map(&:name)
28
+ assert_equal ['baz'], inner_datagrams.map(&:name)
29
+ end
30
+
31
+ def test_metric_methods_return_nil
32
+ assert_nil @client.increment('foo')
33
+ assert_nil @client.measure('bar', 122.54)
34
+ assert_nil @client.set('baz', 123)
35
+ assert_nil @client.gauge('baz', 12.3)
36
+ end
37
+
38
+ def test_increment_with_default_value
39
+ datagrams = @client.capture { @client.increment('foo') }
40
+ assert_equal 1, datagrams.size
41
+ assert_equal 'foo:1|c', datagrams.first.source
42
+ end
43
+
44
+ def test_measure_with_value
45
+ datagrams = @client.capture { @client.measure('foo', 122.54) }
46
+ assert_equal 1, datagrams.size
47
+ assert_equal 'foo:122.54|ms', datagrams.first.source
48
+ end
49
+
50
+ def test_measure_with_block
51
+ Process.stubs(:clock_gettime).with(Process::CLOCK_MONOTONIC).returns(0.1, 0.2)
52
+ datagrams = @client.capture do
53
+ @client.measure('foo') {}
54
+ end
55
+ assert_equal 1, datagrams.size
56
+ assert_equal 'foo:100.0|ms', datagrams.first.source
57
+ end
58
+
59
+ def test_gauge
60
+ datagrams = @client.capture { @client.gauge('foo', 123) }
61
+ assert_equal 1, datagrams.size
62
+ assert_equal 'foo:123|g', datagrams.first.source
63
+ end
64
+
65
+ def test_set
66
+ datagrams = @client.capture { @client.set('foo', 12345) }
67
+ assert_equal 1, datagrams.size
68
+ assert_equal 'foo:12345|s', datagrams.first.source
69
+ end
70
+
71
+ def test_histogram
72
+ datagrams = @dogstatsd_client.capture { @dogstatsd_client.histogram('foo', 12.44) }
73
+ assert_equal 1, datagrams.size
74
+ assert_equal 'foo:12.44|h', datagrams.first.source
75
+ end
76
+
77
+ def test_distribution_with_value
78
+ datagrams = @dogstatsd_client.capture { @dogstatsd_client.distribution('foo', 12.44) }
79
+ assert_equal 1, datagrams.size
80
+ assert_equal 'foo:12.44|d', datagrams.first.source
81
+ end
82
+
83
+ def test_distribution_with_block
84
+ Process.stubs(:clock_gettime).with(Process::CLOCK_MONOTONIC).returns(0.1, 0.2)
85
+ datagrams = @dogstatsd_client.capture do
86
+ @dogstatsd_client.distribution('foo') {}
87
+ end
88
+ assert_equal 1, datagrams.size
89
+ assert_equal "foo:100.0|d", datagrams.first.source
90
+ end
91
+
92
+ def test_latency_emits_ms_metric
93
+ Process.stubs(:clock_gettime).with(Process::CLOCK_MONOTONIC).returns(0.1, 0.2)
94
+ datagrams = @client.capture do
95
+ @client.latency('foo') {}
96
+ end
97
+ assert_equal 1, datagrams.size
98
+ assert_equal "foo:100.0|ms", datagrams.first.source
99
+ end
100
+
101
+ def test_latency_on_dogstatsd_prefers_distribution_metric_type
102
+ Process.stubs(:clock_gettime).with(Process::CLOCK_MONOTONIC).returns(0.1, 0.2)
103
+ datagrams = @dogstatsd_client.capture do
104
+ @dogstatsd_client.latency('foo') {}
105
+ end
106
+ assert_equal 1, datagrams.size
107
+ assert_equal "foo:100.0|d", datagrams.first.source
108
+ end
109
+
110
+ def test_latency_calls_block_even_when_not_sending_a_sample
111
+ called = false
112
+ @client.capture do
113
+ @client.latency('foo', sample_rate: 0) { called = true }
114
+ end
115
+ assert called, "The block should have been called"
116
+ end
117
+
118
+ def test_service_check
119
+ datagrams = @dogstatsd_client.capture { @dogstatsd_client.service_check('service', :ok) }
120
+ assert_equal 1, datagrams.size
121
+ assert_equal "_sc|service|0", datagrams.first.source
122
+ end
123
+
124
+ def test_event
125
+ datagrams = @dogstatsd_client.capture { @dogstatsd_client.event('service', "event\ndescription") }
126
+ assert_equal 1, datagrams.size
127
+ assert_equal "_e{7,18}:service|event\\ndescription", datagrams.first.source
128
+ end
129
+
130
+ def test_no_prefix
131
+ client = StatsD::Instrument::Client.new(prefix: 'foo')
132
+ datagrams = client.capture do
133
+ client.increment('bar')
134
+ client.increment('bar', no_prefix: true)
135
+ end
136
+
137
+ assert_equal 2, datagrams.size
138
+ assert_equal "foo.bar", datagrams[0].name
139
+ assert_equal "bar", datagrams[1].name
140
+ end
141
+
142
+ def test_sampling
143
+ mock_sink = mock('sink')
144
+ mock_sink.stubs(:sample?).returns(false, true, false, false, true)
145
+ mock_sink.expects(:<<).twice
146
+
147
+ client = StatsD::Instrument::Client.new(sink: mock_sink)
148
+ 5.times { client.increment('metric') }
149
+ end
150
+
151
+ def test_clone_with_prefix_option
152
+ # Both clients will use the same sink.
153
+ mock_sink = mock('sink')
154
+ mock_sink.stubs(:sample?).returns(true)
155
+ mock_sink.expects(:<<).with("metric:1|c").returns(mock_sink)
156
+ mock_sink.expects(:<<).with("foo.metric:1|c").returns(mock_sink)
157
+
158
+ original_client = StatsD::Instrument::Client.new(sink: mock_sink)
159
+ client_with_other_options = original_client.clone_with_options(prefix: 'foo')
160
+
161
+ original_client.increment('metric')
162
+ client_with_other_options.increment('metric')
163
+ end
164
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+ require 'statsd/instrument/client'
5
+
6
+ module Compatibility
7
+ class DogStatsDDatagramCompatibilityTest < Minitest::Test
8
+ def setup
9
+ StatsD::Instrument::UDPSink.any_instance.stubs(:sample?).returns(true)
10
+ StatsD::Instrument::Backends::UDPBackend.any_instance.stubs(:rand).returns(0)
11
+
12
+ @server = UDPSocket.new
13
+ @server.bind('localhost', 0)
14
+ @host = @server.addr[2]
15
+ @port = @server.addr[1]
16
+ end
17
+
18
+ def teardown
19
+ @server.close
20
+ end
21
+
22
+ def test_increment_compatibility
23
+ assert_equal_datagrams { |client| client.increment('counter') }
24
+ assert_equal_datagrams { |client| client.increment('counter', no_prefix: true) }
25
+ assert_equal_datagrams { |client| client.increment('counter', 12) }
26
+ assert_equal_datagrams { |client| client.increment('counter', sample_rate: 0.1) }
27
+ assert_equal_datagrams { |client| client.increment('counter', tags: ['foo', 'bar']) }
28
+ assert_equal_datagrams { |client| client.increment('counter', tags: { foo: 'bar' }) }
29
+ assert_equal_datagrams { |client| client.increment('counter', sample_rate: 0.1, tags: ['quc']) }
30
+ end
31
+
32
+ def test_measure_compatibility
33
+ assert_equal_datagrams { |client| client.measure('timing', 12.34) }
34
+ assert_equal_datagrams { |client| client.measure('timing', 0.01, no_prefix: true) }
35
+ assert_equal_datagrams { |client| client.measure('timing', 0.12, sample_rate: 0.1) }
36
+ assert_equal_datagrams { |client| client.measure('timing', 0.12, tags: ['foo', 'bar']) }
37
+ end
38
+
39
+ def test_measure_with_block_compatibility
40
+ Process.stubs(:clock_gettime).with(Process::CLOCK_MONOTONIC).returns(12.1)
41
+ assert_equal_datagrams do |client|
42
+ return_value = client.measure('timing', tags: ['foo'], sample_rate: 0.1) { 'foo' }
43
+ assert_equal 'foo', return_value
44
+ end
45
+ end
46
+
47
+ def test_gauge_compatibility
48
+ assert_equal_datagrams { |client| client.gauge('current', 1234) }
49
+ assert_equal_datagrams { |client| client.gauge('current', 1234, no_prefix: true) }
50
+ assert_equal_datagrams { |client| client.gauge('current', 1234, sample_rate: 0.1) }
51
+ assert_equal_datagrams { |client| client.gauge('current', 1234, tags: ['foo', 'bar']) }
52
+ assert_equal_datagrams { |client| client.gauge('current', 1234, tags: { foo: 'bar' }) }
53
+ assert_equal_datagrams { |client| client.gauge('current', 1234, sample_rate: 0.1, tags: ['quc']) }
54
+ end
55
+
56
+ def test_set_compatibility
57
+ assert_equal_datagrams { |client| client.set('unique', 'foo') }
58
+ assert_equal_datagrams { |client| client.set('unique', 'foo', no_prefix: true) }
59
+ assert_equal_datagrams { |client| client.set('unique', 'foo', sample_rate: 0.1) }
60
+ assert_equal_datagrams { |client| client.set('unique', '1234', tags: ['foo', 'bar']) }
61
+ assert_equal_datagrams { |client| client.set('unique', '1234', tags: { foo: 'bar' }) }
62
+ assert_equal_datagrams { |client| client.set('unique', '1234', sample_rate: 0.1, tags: ['quc']) }
63
+ end
64
+
65
+ def test_histogram_compatibility
66
+ assert_equal_datagrams { |client| client.histogram('sample', 12.44) }
67
+ assert_equal_datagrams { |client| client.histogram('sample', 12.44, no_prefix: true) }
68
+ assert_equal_datagrams { |client| client.histogram('sample', 12.44, sample_rate: 0.1) }
69
+ assert_equal_datagrams { |client| client.histogram('sample', 12.44, tags: ['foo', 'bar']) }
70
+ assert_equal_datagrams { |client| client.histogram('sample', 12.44, tags: { foo: 'bar' }) }
71
+ assert_equal_datagrams { |client| client.histogram('sample', 12.44, sample_rate: 0.1, tags: ['quc']) }
72
+ end
73
+
74
+ def test_distribution_compatibility
75
+ assert_equal_datagrams { |client| client.distribution('sample', 12.44) }
76
+ assert_equal_datagrams { |client| client.distribution('sample', 12.44, no_prefix: true) }
77
+ assert_equal_datagrams { |client| client.distribution('sample', 12.44, sample_rate: 0.1) }
78
+ assert_equal_datagrams { |client| client.distribution('sample', 12.44, tags: ['foo', 'bar']) }
79
+ assert_equal_datagrams { |client| client.distribution('sample', 12.44, tags: { foo: 'bar' }) }
80
+ assert_equal_datagrams { |client| client.distribution('sample', 12.44, sample_rate: 0.1, tags: ['quc']) }
81
+ end
82
+
83
+ def test_distribution_with_block_compatibility
84
+ Process.stubs(:clock_gettime).with(Process::CLOCK_MONOTONIC).returns(12.1)
85
+ assert_equal_datagrams do |client|
86
+ return_value = client.distribution('timing', tags: ['foo'], sample_rate: 0.1) { 'foo' }
87
+ assert_equal 'foo', return_value
88
+ end
89
+ end
90
+
91
+ def test_service_check_compatibility
92
+ assert_equal_datagrams { |client| client.service_check('service', 0) }
93
+ assert_equal_datagrams { |client| client.service_check('service', :critical, no_prefix: true) }
94
+ assert_equal_datagrams { |client| client.event('foo', "bar\nbaz") }
95
+ assert_equal_datagrams do |client|
96
+ client.service_check('service', "ok", timestamp: Time.parse('2019-09-09T04:22:17Z'),
97
+ hostname: 'localhost', tags: ['foo'], message: 'bar')
98
+ end
99
+ end
100
+
101
+ def test_event_compatibility
102
+ assert_equal_datagrams { |client| client.event('foo', "bar\nbaz") }
103
+ assert_equal_datagrams { |client| client.event('foo', "bar\nbaz", no_prefix: true) }
104
+ assert_equal_datagrams do |client|
105
+ client.event('Something happend', "And it's not good", timestamp: Time.parse('2019-09-09T04:22:17Z'),
106
+ hostname: 'localhost', tags: ['foo'], alert_type: 'warning', priority: 'low',
107
+ aggregation_key: 'foo', source_type_name: 'logs')
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ MODES = [:normal, :with_prefix, :with_default_tags]
114
+
115
+ def assert_equal_datagrams(&block)
116
+ MODES.each do |mode|
117
+ legacy_datagram = with_legacy_client(mode) { |client| read_datagram(client, &block) }
118
+ new_datagram = with_new_client(mode) { |client| read_datagram(client, &block) }
119
+
120
+ assert_equal legacy_datagram, new_datagram, "The datagrams emitted were not the same in #{mode} mode"
121
+ end
122
+ end
123
+
124
+ def with_legacy_client(mode)
125
+ old_prefix = StatsD.prefix
126
+ StatsD.prefix = 'prefix' if mode == :with_prefix
127
+
128
+ old_default_tags = StatsD.default_tags
129
+ StatsD.default_tags = { key: 'value' } if mode == :with_default_tags
130
+
131
+ old_backend = StatsD.backend
132
+ new_backend = StatsD::Instrument::Backends::UDPBackend.new("#{@host}:#{@port}", :datadog)
133
+ StatsD.backend = new_backend
134
+
135
+ yield(StatsD)
136
+ ensure
137
+ new_backend.socket.close if new_backend&.socket
138
+ StatsD.backend = old_backend
139
+ StatsD.prefix = old_prefix
140
+ StatsD.default_tags = old_default_tags
141
+ end
142
+
143
+ def with_new_client(mode)
144
+ prefix = mode == :with_prefix ? 'prefix' : nil
145
+ default_tags = mode == :with_default_tags ? { key: 'value' } : nil
146
+ client = StatsD::Instrument::Client.new(
147
+ sink: StatsD::Instrument::UDPSink.new(@host, @port),
148
+ datagram_builder_class: StatsD::Instrument::DogStatsDDatagramBuilder,
149
+ prefix: prefix,
150
+ default_tags: default_tags
151
+ )
152
+
153
+ yield(client)
154
+ end
155
+
156
+ def read_datagram(client)
157
+ yield(client)
158
+ data, _origin = @server.recvfrom(100)
159
+ data
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ require 'statsd/instrument/client'
6
+
7
+ class DatagramBuilderTest < Minitest::Test
8
+ def setup
9
+ @datagram_builder = StatsD::Instrument::DatagramBuilder.new
10
+ end
11
+
12
+ def test_normalize_name
13
+ assert_equal 'foo', @datagram_builder.send(:normalize_name, 'foo')
14
+ assert_equal 'fo_o', @datagram_builder.send(:normalize_name, 'fo|o')
15
+ assert_equal 'fo_o', @datagram_builder.send(:normalize_name, 'fo@o')
16
+ assert_equal 'fo_o', @datagram_builder.send(:normalize_name, 'fo:o')
17
+ end
18
+
19
+ def test_normalize_unsupported_tag_names
20
+ assert_equal ['ignored'], @datagram_builder.send(:normalize_tags, ['igno|re,d'])
21
+ # Note: how this is interpreted by the backend is undefined.
22
+ # We rely on the user to not do stuff like this if they don't want to be surprised.
23
+ # We do not want to take the performance hit of normaling this.
24
+ assert_equal ['lol::class:omg::lol'], @datagram_builder.send(:normalize_tags, "lol::class" => "omg::lol")
25
+ end
26
+
27
+ def test_normalize_tags_converts_hash_to_array
28
+ assert_equal ['tag:value'], @datagram_builder.send(:normalize_tags, tag: 'value')
29
+ assert_equal ['tag1:v1', 'tag2:v2'], @datagram_builder.send(:normalize_tags, tag1: 'v1', tag2: 'v2')
30
+ end
31
+
32
+ def test_c
33
+ datagram = @datagram_builder.c('foo', 1, nil, nil)
34
+ assert_equal "foo:1|c", datagram
35
+
36
+ datagram = @datagram_builder.c('fo:o', 10, 0.1, nil)
37
+ assert_equal "fo_o:10|c|@0.1", datagram
38
+ end
39
+
40
+ def test_ms
41
+ datagram = @datagram_builder.ms('foo', 1, nil, nil)
42
+ assert_equal "foo:1|ms", datagram
43
+
44
+ datagram = @datagram_builder.ms('fo:o', 10, 0.1, nil)
45
+ assert_equal "fo_o:10|ms|@0.1", datagram
46
+ end
47
+
48
+ def test_g
49
+ datagram = @datagram_builder.g('foo', 1, nil, nil)
50
+ assert_equal "foo:1|g", datagram
51
+
52
+ datagram = @datagram_builder.g('fo|o', 10, 0.01, nil)
53
+ assert_equal "fo_o:10|g|@0.01", datagram
54
+ end
55
+
56
+ def test_s
57
+ datagram = @datagram_builder.s('foo', 1, nil, nil)
58
+ assert_equal "foo:1|s", datagram
59
+
60
+ datagram = @datagram_builder.s('fo@o', 10, 0.01, nil)
61
+ assert_equal "fo_o:10|s|@0.01", datagram
62
+ end
63
+
64
+ def test_h
65
+ datagram = @datagram_builder.h('foo', 1, nil, nil)
66
+ assert_equal "foo:1|h", datagram
67
+
68
+ datagram = @datagram_builder.h('fo@o', 10, 0.01, nil)
69
+ assert_equal "fo_o:10|h|@0.01", datagram
70
+ end
71
+
72
+ def test_d
73
+ datagram = @datagram_builder.d('foo', 1, nil, nil)
74
+ assert_equal "foo:1|d", datagram
75
+
76
+ datagram = @datagram_builder.d('fo@o', 10, 0.01, nil)
77
+ assert_equal "fo_o:10|d|@0.01", datagram
78
+ end
79
+
80
+ def test_tags
81
+ datagram = @datagram_builder.d('foo', 10, nil, ['foo', 'bar'])
82
+ assert_equal "foo:10|d|#foo,bar", datagram
83
+
84
+ datagram = @datagram_builder.d('foo', 10, 0.1, ['foo:bar'])
85
+ assert_equal "foo:10|d|@0.1|#foo:bar", datagram
86
+
87
+ datagram = @datagram_builder.d('foo', 10, 1, foo: 'bar', baz: 'quc')
88
+ assert_equal "foo:10|d|#foo:bar,baz:quc", datagram
89
+ end
90
+
91
+ def test_prefix
92
+ datagram_builder = StatsD::Instrument::DatagramBuilder.new(prefix: 'foo')
93
+ datagram = datagram_builder.c('bar', 1, nil, nil)
94
+ assert_equal 'foo.bar:1|c', datagram
95
+
96
+ # The prefix should also be normalized
97
+ datagram_builder = StatsD::Instrument::DatagramBuilder.new(prefix: 'foo|bar')
98
+ datagram = datagram_builder.c('baz', 1, nil, nil)
99
+ assert_equal 'foo_bar.baz:1|c', datagram
100
+ end
101
+
102
+ def test_default_tags
103
+ datagram_builder = StatsD::Instrument::DatagramBuilder.new(default_tags: ['foo'])
104
+ datagram = datagram_builder.c('bar', 1, nil, nil)
105
+ assert_equal 'bar:1|c|#foo', datagram
106
+
107
+ datagram = datagram_builder.c('bar', 1, nil, a: 'b')
108
+ assert_equal 'bar:1|c|#a:b,foo', datagram
109
+
110
+ # We do not filter out duplicates, because detecting dupes is too time consuming.
111
+ # We let the server deal with the situation
112
+ datagram = datagram_builder.c('bar', 1, nil, ['foo'])
113
+ assert_equal 'bar:1|c|#foo,foo', datagram
114
+
115
+ # Default tags are also normalized
116
+ datagram_builder = StatsD::Instrument::DatagramBuilder.new(default_tags: ['f,o|o'])
117
+ datagram = datagram_builder.c('bar', 1, nil, nil)
118
+ assert_equal 'bar:1|c|#foo', datagram
119
+ end
120
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class DeprecationsTest < Minitest::Test
6
+ unless StatsD::Instrument.strict_mode_enabled?
7
+ class InstrumentedClass
8
+ extend StatsD::Instrument
9
+ def foo; end
10
+ statsd_count :foo, 'frequency', 0.5, ['tag'] # rubocop:disable StatsD/MetaprogrammingPositionalArguments
11
+ statsd_measure :foo, 'latency', as_dist: true # rubocop:disable StatsD/MeasureAsDistArgument
12
+ statsd_count_success :foo, 'frequency', prefix: 'foo' # rubocop:disable StatsD/MetricPrefixArgument
13
+ end
14
+ end
15
+
16
+ include StatsD::Instrument::Assertions
17
+
18
+ def setup
19
+ skip("Deprecation are not supported in strict mode") if StatsD::Instrument.strict_mode_enabled?
20
+ end
21
+
22
+ def test__deprecated__metaprogramming_method_with_positional_arguments
23
+ metrics = capture_statsd_calls { InstrumentedClass.new.foo }
24
+ metric = metrics[0]
25
+ assert_equal :c, metric.type
26
+ assert_equal 'frequency', metric.name
27
+ assert_equal 1, metric.value
28
+ assert_equal 0.5, metric.sample_rate
29
+ assert_equal ["tag"], metric.tags
30
+ end
31
+
32
+ def test__deprecated__metaprogramming_statsd_measure_with_as_dist
33
+ metrics = capture_statsd_calls { InstrumentedClass.new.foo }
34
+ metric = metrics[1]
35
+ assert_equal :d, metric.type
36
+ assert_equal 'latency', metric.name
37
+ end
38
+
39
+ def test__deprecated__metaprogramming_statsd_count_with_prefix
40
+ metrics = capture_statsd_calls { InstrumentedClass.new.foo }
41
+ metric = metrics[2]
42
+ assert_equal :c, metric.type
43
+ assert_equal 'foo.frequency.success', metric.name
44
+ end
45
+
46
+ # rubocop:disable StatsD/MetricValueKeywordArgument
47
+ def test__deprecated__statsd_measure_with_explicit_value_as_keyword_argument
48
+ metric = capture_statsd_call { StatsD.measure('values.foobar', value: 42) }
49
+ assert_equal 'values.foobar', metric.name
50
+ assert_equal 42, metric.value
51
+ assert_equal :ms, metric.type
52
+ end
53
+
54
+ # rubocop:disable StatsD/MeasureAsDistArgument
55
+ def test__deprecated__statsd_measure_with_explicit_value_keyword_and_distribution_override
56
+ metric = capture_statsd_call { StatsD.measure('values.foobar', value: 42, as_dist: true) }
57
+ assert_equal 42, metric.value
58
+ assert_equal :d, metric.type
59
+ end
60
+ # rubocop:enable StatsD/MeasureAsDistArgument
61
+
62
+ def test__deprecated__statsd_increment_with_value_as_keyword_argument
63
+ metric = capture_statsd_call { StatsD.increment('values.foobar', value: 2) }
64
+ assert_equal StatsD.default_sample_rate, metric.sample_rate
65
+ assert_equal 2, metric.value
66
+ end
67
+
68
+ def test__deprecated__statsd_gauge_with_keyword_argument
69
+ metric = capture_statsd_call { StatsD.gauge('values.foobar', value: 13) }
70
+ assert_equal :g, metric.type
71
+ assert_equal 'values.foobar', metric.name
72
+ assert_equal 13, metric.value
73
+ end
74
+ # rubocop:enable StatsD/MetricValueKeywordArgument
75
+
76
+ # rubocop:disable StatsD/MetricReturnValue
77
+ def test__deprecated__statsd_increment_retuns_metric_instance
78
+ metric = StatsD.increment('key')
79
+ assert_kind_of StatsD::Instrument::Metric, metric
80
+ assert_equal 'key', metric.name
81
+ assert_equal :c, metric.type
82
+ assert_equal 1, metric.value
83
+ end
84
+ # rubocop:enable StatsD/MetricReturnValue
85
+
86
+ # rubocop:disable StatsD/PositionalArguments
87
+ def test__deprecated__statsd_increment_with_positional_argument_for_tags
88
+ metric = capture_statsd_call { StatsD.increment('values.foobar', 12, nil, ['test']) }
89
+ assert_equal StatsD.default_sample_rate, metric.sample_rate
90
+ assert_equal ['test'], metric.tags
91
+ assert_equal 12, metric.value
92
+ assert_equal StatsD.default_sample_rate, metric.sample_rate
93
+ end
94
+ # rubocop:enable StatsD/PositionalArguments
95
+
96
+ # rubocop:disable StatsD/MeasureAsDistArgument
97
+ def test__deprecated__statsd_measure_with_explicit_value_and_distribution_override
98
+ metric = capture_statsd_call { StatsD.measure('values.foobar', 42, as_dist: true) }
99
+ assert_equal :d, metric.type
100
+ end
101
+
102
+ def test__deprecated__statsd_measure_use_distribution_override_for_a_block
103
+ metric = capture_statsd_call do
104
+ StatsD.measure('values.foobar', as_dist: true) { 'foo' }
105
+ end
106
+ assert_equal :d, metric.type
107
+ end
108
+
109
+ def test__deprecated__statsd_measure_as_distribution_returns_return_value_of_block_even_if_nil
110
+ return_value = StatsD.measure('values.foobar', as_dist: true) { nil }
111
+ assert_nil return_value
112
+ end
113
+ # rubocop:enable StatsD/MeasureAsDistArgument
114
+
115
+ # rubocop:disable StatsD/MetricPrefixArgument
116
+ def test__deprecated__override_name_prefix
117
+ m = capture_statsd_call { StatsD.increment('counter', prefix: "foobar") }
118
+ assert_equal 'foobar.counter', m.name
119
+
120
+ m = capture_statsd_call { StatsD.increment('counter', prefix: "foobar", no_prefix: true) }
121
+ assert_equal 'counter', m.name
122
+ end
123
+ # rubocop:enable StatsD/MetricPrefixArgument
124
+
125
+ protected
126
+
127
+ def capture_statsd_call(&block)
128
+ metrics = capture_statsd_calls(&block)
129
+ assert_equal 1, metrics.length
130
+ metrics.first
131
+ end
132
+ end