statsd-instrument 3.7.0 → 3.9.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.
@@ -26,4 +26,55 @@ class IntegrationTest < Minitest::Test
26
26
  StatsD.increment("counter")
27
27
  assert_equal("counter:1|c", @server.recvfrom(100).first)
28
28
  end
29
+
30
+ def test_live_local_udp_socket_with_aggregation_flush
31
+ client = StatsD::Instrument::Environment.new(
32
+ "STATSD_ADDR" => "#{@server.addr[2]}:#{@server.addr[1]}",
33
+ "STATSD_IMPLEMENTATION" => "dogstatsd",
34
+ "STATSD_ENV" => "production",
35
+ "STATSD_ENABLE_AGGREGATION" => "true",
36
+ "STATSD_AGGREGATION_INTERVAL" => "5.0",
37
+ ).client
38
+
39
+ 10.times do |i|
40
+ client.increment("counter", 2)
41
+ client.distribution("test_distribution", 3 * i)
42
+ client.gauge("test_gauge", 3 * i)
43
+ end
44
+
45
+ client.force_flush
46
+
47
+ packets = []
48
+ while IO.select([@server], nil, nil, 0.1)
49
+ packets << @server.recvfrom(200).first
50
+ end
51
+ packets = packets.map { |packet| packet.split("\n") }.flatten
52
+
53
+ assert_equal("counter:20|c", packets.find { |packet| packet.start_with?("counter:") })
54
+ assert_equal(
55
+ "test_distribution:0:3:6:9:12:15:18:21:24:27|d",
56
+ packets.find { |packet| packet.start_with?("test_distribution:") },
57
+ )
58
+ assert_equal("test_gauge:27|g", packets.find { |packet| packet.start_with?("test_gauge:") })
59
+ end
60
+
61
+ def test_live_local_udp_socket_with_aggregation_periodic_flush
62
+ client = StatsD::Instrument::Environment.new(
63
+ "STATSD_ADDR" => "#{@server.addr[2]}:#{@server.addr[1]}",
64
+ "STATSD_IMPLEMENTATION" => "dogstatsd",
65
+ "STATSD_ENV" => "production",
66
+ "STATSD_ENABLE_AGGREGATION" => "true",
67
+ "STATSD_AGGREGATION_INTERVAL" => "0.1",
68
+ ).client
69
+
70
+ 10.times do
71
+ client.increment("counter", 2)
72
+ end
73
+
74
+ before_flush = Time.now
75
+ sleep(0.2)
76
+
77
+ assert_equal("counter:20|c", @server.recvfrom(100).first)
78
+ assert_operator(Time.now - before_flush, :<, 0.3, "Flush and ingest should have happened within 0.4s")
79
+ end
29
80
  end
data/test/test_helper.rb CHANGED
@@ -6,8 +6,13 @@ end
6
6
 
7
7
  ENV["ENV"] = "test"
8
8
 
9
+ unless ENV.key?("CI")
10
+ require "minitest/pride"
11
+ end
9
12
  require "minitest/autorun"
10
- require "minitest/pride"
13
+ unless ENV.key?("CI")
14
+ require "minitest/pride"
15
+ end
11
16
  require "mocha/minitest"
12
17
  require "statsd-instrument"
13
18
 
@@ -107,7 +107,8 @@ module UDPSinkTests
107
107
  private
108
108
 
109
109
  def build_sink(host = @host, port = @port)
110
- @sink_class.new(host, port)
110
+ connection = StatsD::Instrument::UdpConnection.new(host, port)
111
+ StatsD::Instrument::Sink.new(connection)
111
112
  end
112
113
 
113
114
  def read_datagrams(count, timeout: ENV["CI"] ? 5 : 1)
@@ -132,7 +133,7 @@ class UDPSinkTest < Minitest::Test
132
133
  @receiver.bind("localhost", 0)
133
134
  @host = @receiver.addr[2]
134
135
  @port = @receiver.addr[1]
135
- @sink_class = StatsD::Instrument::UDPSink
136
+ @sink_class = StatsD::Instrument::Sink
136
137
  end
137
138
 
138
139
  def teardown
@@ -167,7 +168,7 @@ class UDPSinkTest < Minitest::Test
167
168
  ensure
168
169
  StatsD.logger = previous_logger
169
170
  # Make sure our fake socket is closed so that it doesn't interfere with other tests
170
- udp_sink&.send(:invalidate_socket)
171
+ udp_sink&.send(:invalidate_connection)
171
172
  end
172
173
  end
173
174
  end
@@ -180,7 +181,7 @@ class BatchedUDPSinkTest < Minitest::Test
180
181
  @receiver.bind("localhost", 0)
181
182
  @host = @receiver.addr[2]
182
183
  @port = @receiver.addr[1]
183
- @sink_class = StatsD::Instrument::BatchedUDPSink
184
+ @sink_class = StatsD::Instrument::BatchedSink
184
185
  @sinks = []
185
186
  end
186
187
 
@@ -202,10 +203,31 @@ class BatchedUDPSinkTest < Minitest::Test
202
203
  assert(buffer.empty?)
203
204
  end
204
205
 
206
+ def test_statistics
207
+ datagrams = StatsD.singleton_client.capture do
208
+ buffer_size = 2
209
+ sink = build_sink(@host, @port, buffer_capacity: buffer_size, statistics_interval: 0.1)
210
+ 2.times { |i| sink << "foo:#{i}|c" }
211
+ sink.flush(blocking: false)
212
+ sink.instance_variable_get(:@dispatcher).instance_variable_get(:@statistics).maybe_flush!(force: true)
213
+ end
214
+
215
+ assert(datagrams.any? { |d| d.name.start_with?("statsd_instrument.batched_udp_sink.avg_batch_length") })
216
+ assert(datagrams.any? { |d| d.name.start_with?("statsd_instrument.batched_udp_sink.avg_batched_packet_size") })
217
+ assert(datagrams.any? { |d| d.name.start_with?("statsd_instrument.batched_udp_sink.avg_buffer_length") })
218
+ assert(datagrams.any? { |d| d.name.start_with?("statsd_instrument.batched_udp_sink.batched_sends") })
219
+ assert(datagrams.any? { |d| d.name.start_with?("statsd_instrument.batched_udp_sink.synchronous_sends") })
220
+ end
221
+
205
222
  private
206
223
 
207
- def build_sink(host = @host, port = @port, buffer_capacity: 50)
208
- sink = @sink_class.new(host, port, buffer_capacity: buffer_capacity)
224
+ def build_sink(host = @host, port = @port, buffer_capacity: 50, statistics_interval: 0)
225
+ sink = StatsD::Instrument::Sink.for_addr("#{host}:#{port}")
226
+ sink = @sink_class.new(
227
+ sink,
228
+ buffer_capacity: buffer_capacity,
229
+ statistics_interval: statistics_interval,
230
+ )
209
231
  @sinks << sink
210
232
  sink
211
233
  end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ module UdsTestHelper
6
+ MAX_READ_BYTES = 64 * 1024
7
+ private_constant :MAX_READ_BYTES
8
+
9
+ private
10
+
11
+ def create_socket_file
12
+ tmpdir = Dir.mktmpdir
13
+ socket_path = File.join(tmpdir, "sockets", "statsd.sock")
14
+ FileUtils.mkdir_p(File.dirname(socket_path))
15
+
16
+ socket_path
17
+ end
18
+
19
+ def create_receiver(socket_path)
20
+ FileUtils.rm_f(socket_path)
21
+ receiver = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
22
+ receiver.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
23
+ receiver.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF, (2 * MAX_READ_BYTES).to_i)
24
+ receiver.bind(Socket.pack_sockaddr_un(socket_path))
25
+
26
+ receiver
27
+ end
28
+
29
+ def build_sink(socket_path)
30
+ connection = StatsD::Instrument::UdsConnection.new(socket_path)
31
+ @sink_class.new(connection)
32
+ end
33
+
34
+ def sink
35
+ @sink ||= build_sink(@socket_path)
36
+ end
37
+
38
+ def skip_on_jruby(message = "JRuby does not support UNIX domain sockets")
39
+ skip(message) if RUBY_PLATFORM == "java"
40
+ end
41
+
42
+ def read_datagrams(count, timeout: ENV["CI"] ? 5 : 1)
43
+ datagrams = []
44
+ count.times do
45
+ if @receiver.wait_readable(timeout)
46
+
47
+ datagrams += @receiver.recvfrom(MAX_READ_BYTES).first.lines(chomp: true)
48
+ break if datagrams.size >= count
49
+ else
50
+ break
51
+ end
52
+ end
53
+
54
+ datagrams
55
+ end
56
+ end
57
+
58
+ class UdsSinkTest < Minitest::Test
59
+ include UdsTestHelper
60
+
61
+ def setup
62
+ @sink_class = StatsD::Instrument::Sink
63
+ @socket_path = create_socket_file
64
+ skip_on_jruby
65
+
66
+ @receiver = create_receiver(@socket_path)
67
+ end
68
+
69
+ def teardown
70
+ return if RUBY_PLATFORM == "java"
71
+
72
+ @receiver.close
73
+ FileUtils.rm_f(@socket_path)
74
+ end
75
+
76
+ def test_send_metric_with_tags
77
+ metric = "test.metric"
78
+ value = 42
79
+ tags = { region: "us-west", environment: "production" }
80
+ sink << "#{metric}:#{value}|c|##{"region:#{tags[:region]},environment:#{tags[:environment]}"}"
81
+ # Assert that the metric with tags was sent successfully
82
+
83
+ datagrams = read_datagrams(1)
84
+ assert_equal("test.metric:42|c|#region:us-west,environment:production".b, datagrams.first)
85
+ end
86
+
87
+ def test_send_metric_with_sample_rate
88
+ metric = "test.metric"
89
+ value = 42
90
+ sample_rate = 0.5
91
+ sink << "#{metric}:#{value}|c|@#{sample_rate}"
92
+ datagrams = read_datagrams(1)
93
+ assert_equal("test.metric:42|c|@0.5".b, datagrams.first)
94
+ end
95
+
96
+ def test_flush_with_empty_batch
97
+ sink.flush
98
+ datagrams = read_datagrams(1, timeout: 0.1)
99
+ assert_empty(datagrams)
100
+ end
101
+ end
102
+
103
+ class BatchedUdsSinkTest < Minitest::Test
104
+ include UdsTestHelper
105
+
106
+ def setup
107
+ @socket_path = create_socket_file
108
+ @sink_class = StatsD::Instrument::BatchedSink
109
+ @sinks = []
110
+
111
+ skip_on_jruby
112
+
113
+ @receiver = create_receiver(@socket_path)
114
+ end
115
+
116
+ def teardown
117
+ return if RUBY_PLATFORM == "java"
118
+
119
+ @receiver.close
120
+ FileUtils.remove_entry(@socket_path)
121
+ @sinks.each(&:shutdown)
122
+ end
123
+
124
+ def test_send_metric_with_tags
125
+ metric = "test.metric"
126
+ value = 42
127
+ tags = { region: "us-west", environment: "production" }
128
+ sink << "#{metric}:#{value}|c|##{"region:#{tags[:region]},environment:#{tags[:environment]}"}"
129
+ datagrams = read_datagrams(1)
130
+ assert_equal("test.metric:42|c|#region:us-west,environment:production".b, datagrams.first)
131
+ end
132
+
133
+ def test_send_metric_with_sample_rate
134
+ metric = "test.metric"
135
+ value = 42
136
+ sample_rate = 0.5
137
+ sink << "#{metric}:#{value}|c|@#{sample_rate}"
138
+ datagrams = read_datagrams(1)
139
+ assert_equal("test.metric:42|c|@0.5".b, datagrams.first)
140
+ end
141
+
142
+ def test_flush_with_empty_batch
143
+ sink.flush(blocking: false)
144
+ datagrams = read_datagrams(1, timeout: 0.1)
145
+ assert_empty(datagrams)
146
+ end
147
+
148
+ def test_flush
149
+ buffer_size = 50
150
+ sink = build_sink(@socket_path, buffer_capacity: buffer_size)
151
+ dispatcher = sink.instance_variable_get(:@dispatcher)
152
+ buffer = dispatcher.instance_variable_get(:@buffer)
153
+ (buffer_size * 2).times { |i| sink << "foo:#{i}|c" }
154
+ assert(!buffer.empty?)
155
+ sink.flush(blocking: false)
156
+ assert(buffer.empty?)
157
+ end
158
+
159
+ def test_statistics
160
+ datagrams = StatsD.singleton_client.capture do
161
+ buffer_size = 2
162
+ sink = build_sink(@socket_path, buffer_capacity: buffer_size, statistics_interval: 0.1)
163
+ 2.times { |i| sink << "foo:#{i}|c" }
164
+ sink.flush(blocking: false)
165
+ sink.instance_variable_get(:@dispatcher).instance_variable_get(:@statistics).maybe_flush!(force: true)
166
+ end
167
+
168
+ assert(datagrams.any? { |d| d.name.start_with?("statsd_instrument.batched_uds_sink.avg_batch_length") })
169
+ assert(datagrams.any? { |d| d.name.start_with?("statsd_instrument.batched_uds_sink.avg_batched_packet_size") })
170
+ assert(datagrams.any? { |d| d.name.start_with?("statsd_instrument.batched_uds_sink.avg_buffer_length") })
171
+ assert(datagrams.any? { |d| d.name.start_with?("statsd_instrument.batched_uds_sink.batched_sends") })
172
+ assert(datagrams.any? { |d| d.name.start_with?("statsd_instrument.batched_uds_sink.synchronous_sends") })
173
+ end
174
+
175
+ private
176
+
177
+ def build_sink(socket_path, buffer_capacity: 50, statistics_interval: 0)
178
+ sink = StatsD::Instrument::Sink.for_addr(socket_path)
179
+ sink = @sink_class.new(
180
+ sink,
181
+ buffer_capacity: buffer_capacity,
182
+ statistics_interval: statistics_interval,
183
+ )
184
+ @sinks << sink
185
+ sink
186
+ end
187
+ end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statsd-instrument
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.7.0
4
+ version: 3.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jesse Storimer
8
8
  - Tobias Lutke
9
9
  - Willem van Bergen
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-03-05 00:00:00.000000000 Z
13
+ date: 2024-08-19 00:00:00.000000000 Z
14
14
  dependencies: []
15
15
  description: A StatsD client for Ruby apps. Provides metaprogramming methods to inject
16
16
  StatsD instrumentation into your code.
@@ -21,12 +21,14 @@ extensions: []
21
21
  extra_rdoc_files: []
22
22
  files:
23
23
  - ".github/CODEOWNERS"
24
+ - ".github/pull_request_template.md"
24
25
  - ".github/workflows/benchmark.yml"
25
26
  - ".github/workflows/cla.yml"
26
27
  - ".github/workflows/lint.yml"
27
28
  - ".github/workflows/tests.yml"
28
29
  - ".gitignore"
29
30
  - ".rubocop.yml"
31
+ - ".ruby-version"
30
32
  - ".yardopts"
31
33
  - CHANGELOG.md
32
34
  - CONTRIBUTING.md
@@ -42,8 +44,9 @@ files:
42
44
  - bin/rubocop
43
45
  - lib/statsd-instrument.rb
44
46
  - lib/statsd/instrument.rb
47
+ - lib/statsd/instrument/aggregator.rb
45
48
  - lib/statsd/instrument/assertions.rb
46
- - lib/statsd/instrument/batched_udp_sink.rb
49
+ - lib/statsd/instrument/batched_sink.rb
47
50
  - lib/statsd/instrument/capture_sink.rb
48
51
  - lib/statsd/instrument/client.rb
49
52
  - lib/statsd/instrument/datagram.rb
@@ -66,12 +69,15 @@ files:
66
69
  - lib/statsd/instrument/rubocop/positional_arguments.rb
67
70
  - lib/statsd/instrument/rubocop/singleton_configuration.rb
68
71
  - lib/statsd/instrument/rubocop/splat_arguments.rb
72
+ - lib/statsd/instrument/sink.rb
69
73
  - lib/statsd/instrument/statsd_datagram_builder.rb
70
74
  - lib/statsd/instrument/strict.rb
71
- - lib/statsd/instrument/udp_sink.rb
75
+ - lib/statsd/instrument/udp_connection.rb
76
+ - lib/statsd/instrument/uds_connection.rb
72
77
  - lib/statsd/instrument/version.rb
73
78
  - shipit.rubygems.yml
74
79
  - statsd-instrument.gemspec
80
+ - test/aggregator_test.rb
75
81
  - test/assertions_test.rb
76
82
  - test/benchmark/clock_gettime.rb
77
83
  - test/benchmark/metrics.rb
@@ -81,6 +87,7 @@ files:
81
87
  - test/client_test.rb
82
88
  - test/datagram_builder_test.rb
83
89
  - test/datagram_test.rb
90
+ - test/dispatcher_stats_test.rb
84
91
  - test/dogstatsd_datagram_builder_test.rb
85
92
  - test/environment_test.rb
86
93
  - test/helpers/rubocop_helper.rb
@@ -102,12 +109,13 @@ files:
102
109
  - test/statsd_test.rb
103
110
  - test/test_helper.rb
104
111
  - test/udp_sink_test.rb
112
+ - test/uds_sink_test.rb
105
113
  homepage: https://github.com/Shopify/statsd-instrument
106
114
  licenses:
107
115
  - MIT
108
116
  metadata:
109
117
  allowed_push_host: https://rubygems.org
110
- post_install_message:
118
+ post_install_message:
111
119
  rdoc_options: []
112
120
  require_paths:
113
121
  - lib
@@ -115,18 +123,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
115
123
  requirements:
116
124
  - - ">="
117
125
  - !ruby/object:Gem::Version
118
- version: '0'
126
+ version: 2.6.0
119
127
  required_rubygems_version: !ruby/object:Gem::Requirement
120
128
  requirements:
121
129
  - - ">="
122
130
  - !ruby/object:Gem::Version
123
131
  version: '0'
124
132
  requirements: []
125
- rubygems_version: 3.5.6
126
- signing_key:
133
+ rubygems_version: 3.5.17
134
+ signing_key:
127
135
  specification_version: 4
128
136
  summary: A StatsD client for Ruby apps
129
137
  test_files:
138
+ - test/aggregator_test.rb
130
139
  - test/assertions_test.rb
131
140
  - test/benchmark/clock_gettime.rb
132
141
  - test/benchmark/metrics.rb
@@ -136,6 +145,7 @@ test_files:
136
145
  - test/client_test.rb
137
146
  - test/datagram_builder_test.rb
138
147
  - test/datagram_test.rb
148
+ - test/dispatcher_stats_test.rb
139
149
  - test/dogstatsd_datagram_builder_test.rb
140
150
  - test/environment_test.rb
141
151
  - test/helpers/rubocop_helper.rb
@@ -157,3 +167,4 @@ test_files:
157
167
  - test/statsd_test.rb
158
168
  - test/test_helper.rb
159
169
  - test/udp_sink_test.rb
170
+ - test/uds_sink_test.rb