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
@@ -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