statsd-instrument 3.9.9 → 3.9.10

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60ed39eafdc0c0c60e8b39cc40a525990f7a51c8bf0621bf657558f29e6767c4
4
- data.tar.gz: 6d27813cbc21f7f5327cdc261d1dfd7c874d9c4f0c1fdfb17aee1ee63944ea9c
3
+ metadata.gz: d6bb36fa3a5254a64999335275c5515579febf9c2d92d35522fee1d883d0f56d
4
+ data.tar.gz: 69c96c4f5abaa8cae674a85e562a41ab3bbcf05cde0ef523c934d86846103658
5
5
  SHA512:
6
- metadata.gz: 6cafa0a80f7c5d7374d8607698b03fc8eb9fe94cfd7b8a4c6accd1bb9ac4c66a83fea40215df8bcc87c4f729e4379956d2614970539375d13c2cd6d65840a5b9
7
- data.tar.gz: 7e254ea3318a5fc3d79bcb648c5cc939dbbb1552f942c67c38f06d698f44659e790489b5a720cca815ca6998a59b317399a3a54350dca61defafc27ed2da15f6
6
+ metadata.gz: 321874ee863b3f66e1eb7abbace4e734af55dc86fbd11aa8e009e398c54414f47d91447cdcbf08fb93bd4f7b1d88d58a16217093e9f8978b400187ed513dbd74
7
+ data.tar.gz: 9f34eed89dee5efc7d9a3d800c1d6824b0396054782694d708dab40180e0c3b423f6a0c2337b995bf6f58d72ae750bed564cf02bb5e13fc08518540e443be400
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.3.3
1
+ 3.4.4
data/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ section below.
6
6
 
7
7
  ## Unreleased changes
8
8
 
9
+ ## Version 3.9.10
10
+
11
+ - [#398](https://github.com/Shopify/statsd-instrument/pull/398) - Fix metrics not being sent from signal trap contexts when aggregation is enabled.
12
+ When the aggregator is enabled and metrics are emitted from within a signal handler (e.g., SIGTERM, SIGINT),
13
+ the thread health check would fail with `ThreadError: can't be called from trap context` due to mutex
14
+ synchronization. The aggregator now gracefully falls back to direct writes when called from a trap context,
15
+ ensuring metrics are not lost during signal handling such as graceful shutdowns.
16
+
9
17
  ## Version 3.9.9
10
18
 
11
19
  - [#392](https://github.com/Shopify/statsd-instrument/pull/392) - Prevent ENOBUFS errors when using UDP, by skipping setting socket buffer size.
@@ -278,6 +278,16 @@ module StatsD
278
278
  end
279
279
  true
280
280
  end
281
+ rescue ThreadError => e
282
+ # If we're in a trap context, we can't use mutex synchronization
283
+ # Fall back to direct writes to avoid losing metrics
284
+ if e.message.include?("can't be called from trap context")
285
+ StatsD.logger.debug { "[#{self.class.name}] In trap context, falling back to direct writes" }
286
+ false
287
+ else
288
+ # Re-raise other ThreadErrors
289
+ raise
290
+ end
281
291
  end
282
292
  end
283
293
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module StatsD
4
4
  module Instrument
5
- VERSION = "3.9.9"
5
+ VERSION = "3.9.10"
6
6
  end
7
7
  end
@@ -362,7 +362,7 @@ module StatsD
362
362
  # @!method distribution(name, value = nil, sample_rate: nil, tags: nil, &block)
363
363
  # (see StatsD::Instrument::Client#distribution)
364
364
  #
365
- # @!method event(title, text, tags: nil, hostname: nil, timestamp: nil, aggregation_key: nil, priority: nil, source_type_name: nil, alert_type: nil) # rubocop:disable Layout/LineLength
365
+ # @!method event(title, text, tags: nil, hostname: nil, timestamp: nil, aggregation_key: nil, priority: nil, source_type_name: nil, alert_type: nil)
366
366
  # (see StatsD::Instrument::Client#event)
367
367
  #
368
368
  # @!method service_check(name, status, tags: nil, hostname: nil, timestamp: nil, message: nil)
@@ -353,4 +353,50 @@ class AggregatorTest < Minitest::Test
353
353
  assert_equal(100.0, sampled_timing_datagram.value)
354
354
  assert_equal(0.01, sampled_timing_datagram.sample_rate)
355
355
  end
356
+
357
+ def test_signal_trap_context_fallback_to_direct_writes
358
+ skip("#{RUBY_ENGINE} not supported for this test. Reason: signal handling") if RUBY_ENGINE != "ruby"
359
+
360
+ signal_received = false
361
+ metrics_sent_in_trap = []
362
+
363
+ old_trap = Signal.trap("USR1") do
364
+ signal_received = true
365
+ # These operations should now fall back to direct writes
366
+ @subject.increment("trap_counter", 1)
367
+ @subject.gauge("trap_gauge", 42)
368
+ @subject.aggregate_timing("trap_timing", 100)
369
+
370
+ metrics_sent_in_trap = @sink.datagrams.map(&:name)
371
+ end
372
+
373
+ @sink.clear
374
+
375
+ Process.kill("USR1", Process.pid)
376
+
377
+ sleep(0.1)
378
+
379
+ assert(signal_received, "Signal should have been received")
380
+
381
+ assert_includes(metrics_sent_in_trap, "trap_counter")
382
+ assert_includes(metrics_sent_in_trap, "trap_gauge")
383
+ assert_includes(metrics_sent_in_trap, "trap_timing")
384
+
385
+ counter_datagram = @sink.datagrams.find { |d| d.name == "trap_counter" }
386
+ assert_equal(1, counter_datagram.value)
387
+
388
+ gauge_datagram = @sink.datagrams.find { |d| d.name == "trap_gauge" }
389
+ assert_equal(42, gauge_datagram.value)
390
+
391
+ timing_datagram = @sink.datagrams.find { |d| d.name == "trap_timing" }
392
+ assert_equal([100.0], [timing_datagram.value].flatten)
393
+
394
+ debug_messages = @logger.messages.select { |m| m[:severity] == :debug }
395
+ assert(
396
+ debug_messages.any? { |m| m[:message].include?("In trap context, falling back to direct writes") },
397
+ "Expected debug message about trap context fallback",
398
+ )
399
+ ensure
400
+ Signal.trap("USR1", old_trap || "DEFAULT")
401
+ end
356
402
  end
@@ -48,21 +48,21 @@ class DispatcherStatsTest < Minitest::Test
48
48
  end
49
49
  assert_equal(batches.length, stats.instance_variable_get(:@batched_sends))
50
50
  assert_equal(
51
- batches.map { |b|
51
+ batches.map do |b|
52
52
  b[:buffer_len]
53
- }.sum / batches.length,
53
+ end.sum / batches.length,
54
54
  stats.instance_variable_get(:@avg_buffer_length),
55
55
  )
56
56
  assert_equal(
57
- batches.map { |b|
57
+ batches.map do |b|
58
58
  b[:packet_size]
59
- }.sum / batches.length,
59
+ end.sum / batches.length,
60
60
  stats.instance_variable_get(:@avg_batched_packet_size),
61
61
  )
62
62
  assert_equal(
63
- batches.map { |b|
63
+ batches.map do |b|
64
64
  b[:batch_len]
65
- }.sum / batches.length,
65
+ end.sum / batches.length,
66
66
  stats.instance_variable_get(:@avg_batch_length),
67
67
  )
68
68
  end
@@ -103,4 +103,56 @@ class IntegrationTest < Minitest::Test
103
103
  assert_match(/counter:\d+|c/, packets.find { |packet| packet.start_with?("counter:") })
104
104
  assert_match(/test_distribution:\d+:3|d/, packets.find { |packet| packet.start_with?("test_distribution:") })
105
105
  end
106
+
107
+ def test_signal_trap_with_aggregation_fallback
108
+ skip("#{RUBY_ENGINE} not supported for this test. Reason: signal handling") if RUBY_ENGINE != "ruby"
109
+
110
+ client = StatsD::Instrument::Environment.new(
111
+ "STATSD_ADDR" => "#{@server.addr[2]}:#{@server.addr[1]}",
112
+ "STATSD_IMPLEMENTATION" => "dogstatsd",
113
+ "STATSD_ENV" => "production",
114
+ "STATSD_ENABLE_AGGREGATION" => "true",
115
+ "STATSD_AGGREGATION_INTERVAL" => "5.0",
116
+ ).client
117
+
118
+ signal_received = false
119
+
120
+ old_trap = Signal.trap("USR1") do
121
+ signal_received = true
122
+ # These should fall back to direct writes
123
+ client.increment("trap_metric", 5)
124
+ client.gauge("trap_gauge", 42)
125
+ client.distribution("trap_distribution", 100)
126
+ end
127
+
128
+ Process.kill("USR1", Process.pid)
129
+
130
+ sleep(0.1)
131
+
132
+ assert(signal_received, "Signal should have been received")
133
+
134
+ packets = []
135
+ while IO.select([@server], nil, nil, 0.1)
136
+ packet = @server.recvfrom(300).first
137
+ packets.concat(packet.split("\n"))
138
+ end
139
+
140
+ # When aggregation is disabled due to trap context, metrics might be batched
141
+ assert(packets.size >= 3, "Expected at least 3 metrics, got #{packets.size}: #{packets.inspect}")
142
+
143
+ assert(
144
+ packets.any? { |p| p == "trap_metric:5|c" },
145
+ "Expected counter metric, got: #{packets.inspect}",
146
+ )
147
+ assert(
148
+ packets.any? { |p| p == "trap_gauge:42|g" },
149
+ "Expected gauge metric, got: #{packets.inspect}",
150
+ )
151
+ assert(
152
+ packets.any? { |p| p == "trap_distribution:100|d" },
153
+ "Expected distribution metric, got: #{packets.inspect}",
154
+ )
155
+ ensure
156
+ Signal.trap("USR1", old_trap || "DEFAULT")
157
+ end
106
158
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statsd-instrument
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.9.9
4
+ version: 3.9.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jesse Storimer
@@ -9,7 +9,7 @@ authors:
9
9
  - Willem van Bergen
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-01-14 00:00:00.000000000 Z
12
+ date: 1980-01-02 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: A StatsD client for Ruby apps. Provides metaprogramming methods to inject
15
15
  StatsD instrumentation into your code.
@@ -130,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
130
  - !ruby/object:Gem::Version
131
131
  version: '0'
132
132
  requirements: []
133
- rubygems_version: 3.6.2
133
+ rubygems_version: 3.6.9
134
134
  specification_version: 4
135
135
  summary: A StatsD client for Ruby apps
136
136
  test_files: