statsd-instrument 3.9.4 → 3.9.6
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 +4 -4
- data/CHANGELOG.md +10 -0
- data/lib/statsd/instrument/aggregator.rb +19 -9
- data/lib/statsd/instrument/client.rb +35 -20
- data/lib/statsd/instrument/version.rb +1 -1
- data/test/aggregator_test.rb +26 -3
- data/test/client_test.rb +7 -2
- data/test/integration_test.rb +26 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62a2601a6b425e15a8f3b616fcb182d48548179d95bc5b9bbdef74f5cdabcc1d
|
4
|
+
data.tar.gz: 64a4fd724bcee0e6fe8cac4001439c6a9e0b4b9e5234cd5b654b3df4045171cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d74d19d5e29895763d423dd0daf67bedfeeb3e05ebff891f87eda90d4482648f5cd8bb3b163c000f2fe70473ece32f9f4febf36530bcfd16ab2d9d699cf1d49d
|
7
|
+
data.tar.gz: 18d287d1d24792d9e63d7d2f2db3bee74fd929d7e4fd5ea1dccb50447a7278de7cfe8f088184064298809e70ef82b3d719dc27c5d6dc07edbc01b5747dff7246
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,16 @@ section below.
|
|
6
6
|
|
7
7
|
## Unreleased changes
|
8
8
|
|
9
|
+
## Version 3.9.6
|
10
|
+
|
11
|
+
- [#388](https://github.com/Shopify/statsd-instrument/pull/388) - Properly fixing the bug when using aggregation and sending sampled
|
12
|
+
histograms, now the client will respect the sampling rate when sending the metrics and pass it down to the aggregator.
|
13
|
+
|
14
|
+
## Version 3.9.5
|
15
|
+
|
16
|
+
- [#387](https://github.com/Shopify/statsd-instrument/pull/387) - Fixing bug when using aggregation and sending sampled
|
17
|
+
histogram metrics, they will not be scaled properly because of missing sampling rate in the final sent sample.
|
18
|
+
|
9
19
|
## Version 3.9.4
|
10
20
|
|
11
21
|
- [#384](https://github.com/Shopify/statsd-instrument/pull/384) - Aggregation: fixing bug when sending metrics synchronously
|
@@ -3,13 +3,14 @@
|
|
3
3
|
module StatsD
|
4
4
|
module Instrument
|
5
5
|
class AggregationKey
|
6
|
-
attr_reader :name, :tags, :no_prefix, :type, :hash
|
6
|
+
attr_reader :name, :tags, :no_prefix, :type, :hash, :sample_rate
|
7
7
|
|
8
|
-
def initialize(name, tags, no_prefix, type)
|
8
|
+
def initialize(name, tags, no_prefix, type, sample_rate: 1.0)
|
9
9
|
@name = name
|
10
10
|
@tags = tags
|
11
11
|
@no_prefix = no_prefix
|
12
12
|
@type = type
|
13
|
+
@sample_rate = sample_rate
|
13
14
|
@hash = [@name, @tags, @no_prefix, @type].hash
|
14
15
|
end
|
15
16
|
|
@@ -56,7 +57,7 @@ module StatsD
|
|
56
57
|
key.name,
|
57
58
|
key.type.to_s,
|
58
59
|
agg_value,
|
59
|
-
|
60
|
+
key.sample_rate,
|
60
61
|
key.tags,
|
61
62
|
)
|
62
63
|
when GAUGE
|
@@ -134,16 +135,16 @@ module StatsD
|
|
134
135
|
end
|
135
136
|
end
|
136
137
|
|
137
|
-
def aggregate_timing(name, value, tags: [], no_prefix: false, type: DISTRIBUTION)
|
138
|
+
def aggregate_timing(name, value, tags: [], no_prefix: false, type: DISTRIBUTION, sample_rate: CONST_SAMPLE_RATE)
|
138
139
|
unless thread_healthcheck
|
139
140
|
@sink << datagram_builder(no_prefix: no_prefix).timing_value_packed(
|
140
|
-
name, type.to_s, [value],
|
141
|
+
name, type.to_s, [value], sample_rate, tags
|
141
142
|
)
|
142
143
|
return
|
143
144
|
end
|
144
145
|
|
145
146
|
tags = tags_sorted(tags)
|
146
|
-
key = packet_key(name, tags, no_prefix, type)
|
147
|
+
key = packet_key(name, tags, no_prefix, type, sample_rate: sample_rate)
|
147
148
|
|
148
149
|
@mutex.synchronize do
|
149
150
|
values = @aggregation_state[key] ||= []
|
@@ -176,6 +177,9 @@ module StatsD
|
|
176
177
|
|
177
178
|
EMPTY_ARRAY = [].freeze
|
178
179
|
|
180
|
+
# Flushes the aggregated metrics to the sink.
|
181
|
+
# Iterates over the aggregation state and sends each metric to the sink.
|
182
|
+
# If you change this function, you need to update the logic in the finalizer as well.
|
179
183
|
def do_flush
|
180
184
|
@aggregation_state.each do |key, value|
|
181
185
|
case key.type
|
@@ -191,7 +195,7 @@ module StatsD
|
|
191
195
|
key.name,
|
192
196
|
key.type.to_s,
|
193
197
|
value,
|
194
|
-
|
198
|
+
key.sample_rate,
|
195
199
|
key.tags,
|
196
200
|
)
|
197
201
|
when GAUGE
|
@@ -219,8 +223,14 @@ module StatsD
|
|
219
223
|
datagram_builder(no_prefix: false).normalize_tags(tags)
|
220
224
|
end
|
221
225
|
|
222
|
-
def packet_key(name, tags = "".b, no_prefix = false, type = COUNT)
|
223
|
-
AggregationKey.new(
|
226
|
+
def packet_key(name, tags = "".b, no_prefix = false, type = COUNT, sample_rate: CONST_SAMPLE_RATE)
|
227
|
+
AggregationKey.new(
|
228
|
+
DatagramBuilder.normalize_string(name),
|
229
|
+
tags,
|
230
|
+
no_prefix,
|
231
|
+
type,
|
232
|
+
sample_rate: sample_rate,
|
233
|
+
).freeze
|
224
234
|
end
|
225
235
|
|
226
236
|
def datagram_builder(no_prefix:)
|
@@ -318,25 +318,27 @@ module StatsD
|
|
318
318
|
# @param tags (see #increment)
|
319
319
|
# @return [void]
|
320
320
|
def distribution(name, value = nil, sample_rate: nil, tags: nil, no_prefix: false, &block)
|
321
|
+
if block_given?
|
322
|
+
return latency(name, sample_rate: sample_rate, tags: tags, metric_type: :d, no_prefix: no_prefix, &block)
|
323
|
+
end
|
324
|
+
|
325
|
+
# For all timing metrics, we have to use the sampling logic.
|
326
|
+
# Not doing so would impact performance and CPU usage.
|
327
|
+
# See Datadog's documentation for more details: https://github.com/DataDog/datadog-go/blob/20af2dbfabbbe6bd0347780cd57ed931f903f223/statsd/aggregator.go#L281-L283
|
321
328
|
sample_rate ||= @default_sample_rate
|
322
329
|
if sample_rate && !sample?(sample_rate)
|
323
|
-
# For all timing metrics, we have to use the sampling logic.
|
324
|
-
# Not doing so would impact performance and CPU usage.
|
325
|
-
# See Datadog's documentation for more details: https://github.com/DataDog/datadog-go/blob/20af2dbfabbbe6bd0347780cd57ed931f903f223/statsd/aggregator.go#L281-L283
|
326
|
-
|
327
|
-
if block_given?
|
328
|
-
return yield
|
329
|
-
end
|
330
|
-
|
331
330
|
return StatsD::Instrument::VOID
|
332
331
|
end
|
333
332
|
|
334
|
-
if block_given?
|
335
|
-
return latency(name, sample_rate: sample_rate, tags: tags, metric_type: :d, no_prefix: no_prefix, &block)
|
336
|
-
end
|
337
|
-
|
338
333
|
if @enable_aggregation
|
339
|
-
@aggregator.aggregate_timing(
|
334
|
+
@aggregator.aggregate_timing(
|
335
|
+
name,
|
336
|
+
value,
|
337
|
+
tags: tags,
|
338
|
+
no_prefix: no_prefix,
|
339
|
+
type: :d,
|
340
|
+
sample_rate: sample_rate,
|
341
|
+
)
|
340
342
|
return StatsD::Instrument::VOID
|
341
343
|
end
|
342
344
|
|
@@ -392,13 +394,26 @@ module StatsD
|
|
392
394
|
ensure
|
393
395
|
stop = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
394
396
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
397
|
+
# For all timing metrics, we have to use the sampling logic.
|
398
|
+
# Not doing so would impact performance and CPU usage.
|
399
|
+
# See Datadog's documentation for more details:
|
400
|
+
# https://github.com/DataDog/datadog-go/blob/20af2dbfabbbe6bd0347780cd57ed931f903f223/statsd/aggregator.go#L281-L283
|
401
|
+
sample_rate ||= @default_sample_rate
|
402
|
+
if sample_rate.nil? || sample?(sample_rate)
|
403
|
+
|
404
|
+
metric_type ||= datagram_builder(no_prefix: no_prefix).latency_metric_type
|
405
|
+
latency_in_ms = stop - start
|
406
|
+
|
407
|
+
if @enable_aggregation
|
408
|
+
@aggregator.aggregate_timing(
|
409
|
+
name,
|
410
|
+
latency_in_ms,
|
411
|
+
tags: tags,
|
412
|
+
no_prefix: no_prefix,
|
413
|
+
type: metric_type,
|
414
|
+
sample_rate: sample_rate,
|
415
|
+
)
|
416
|
+
else
|
402
417
|
emit(datagram_builder(no_prefix: no_prefix).send(metric_type, name, latency_in_ms, sample_rate, tags))
|
403
418
|
end
|
404
419
|
end
|
data/test/aggregator_test.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "test_helper"
|
4
|
-
require "ostruct"
|
5
4
|
|
6
5
|
class AggregatorTest < Minitest::Test
|
7
6
|
class CaptureLogger
|
@@ -58,6 +57,24 @@ class AggregatorTest < Minitest::Test
|
|
58
57
|
assert_equal([1.0, 100.0], datagram.value)
|
59
58
|
end
|
60
59
|
|
60
|
+
def test_timing_sampling_scaling
|
61
|
+
@subject.aggregate_timing("timing.sampled", 60.0, sample_rate: 0.01)
|
62
|
+
@subject.aggregate_timing("timing.sampled", 80.0, sample_rate: 0.01)
|
63
|
+
@subject.aggregate_timing("timing.unsampled", 60.0, sample_rate: 1.0)
|
64
|
+
|
65
|
+
@subject.flush
|
66
|
+
|
67
|
+
assert_equal(2, @sink.datagrams.size)
|
68
|
+
|
69
|
+
sampled_datagram = @sink.datagrams.find { |d| d.name == "timing.sampled" }
|
70
|
+
assert_equal([60.0, 80.0], sampled_datagram.value)
|
71
|
+
assert_equal(0.01, sampled_datagram.sample_rate)
|
72
|
+
assert_equal("timing.sampled:60.0:80.0|d|@0.01", sampled_datagram.source)
|
73
|
+
|
74
|
+
unsampled_datagram = @sink.datagrams.find { |d| d.name == "timing.unsampled" }
|
75
|
+
assert_equal(60.0, unsampled_datagram.value)
|
76
|
+
end
|
77
|
+
|
61
78
|
def test_mixed_type_timings
|
62
79
|
@subject.aggregate_timing("foo_ms", 1, tags: { foo: "bar" }, type: :ms)
|
63
80
|
@subject.aggregate_timing("foo_ms", 100, tags: { foo: "bar" }, type: :ms)
|
@@ -191,7 +208,7 @@ class AggregatorTest < Minitest::Test
|
|
191
208
|
|
192
209
|
# Additional metrics should also go through synchronously
|
193
210
|
@subject.increment("foo", 1, tags: { foo: "bar" })
|
194
|
-
@subject.aggregate_timing("bar", 200, tags: { foo: "bar" })
|
211
|
+
@subject.aggregate_timing("bar", 200, tags: { foo: "bar" }, sample_rate: 0.5)
|
195
212
|
|
196
213
|
# Verify new metrics were also sent immediately
|
197
214
|
assert_equal(5, @sink.datagrams.size)
|
@@ -203,6 +220,7 @@ class AggregatorTest < Minitest::Test
|
|
203
220
|
timing_datagram = @sink.datagrams.select { |d| d.name == "bar" }.last
|
204
221
|
assert_equal([200.0], [timing_datagram.value])
|
205
222
|
assert_equal(["foo:bar"], timing_datagram.tags)
|
223
|
+
assert_equal(0.5, timing_datagram.sample_rate)
|
206
224
|
|
207
225
|
# undo the stubbing
|
208
226
|
@subject.unstub(:thread_healthcheck)
|
@@ -304,6 +322,7 @@ class AggregatorTest < Minitest::Test
|
|
304
322
|
@subject.increment("foo", 1, tags: { foo: "bar" })
|
305
323
|
@subject.aggregate_timing("bar", 100, tags: { foo: "bar" })
|
306
324
|
@subject.gauge("baz", 100, tags: { foo: "bar" })
|
325
|
+
@subject.aggregate_timing("sampled_timing", 100, tags: { foo: "bar" }, sample_rate: 0.01)
|
307
326
|
|
308
327
|
# Manually trigger the finalizer
|
309
328
|
finalizer = StatsD::Instrument::Aggregator.finalize(
|
@@ -316,7 +335,7 @@ class AggregatorTest < Minitest::Test
|
|
316
335
|
finalizer.call
|
317
336
|
|
318
337
|
# Verify that all pending metrics are sent
|
319
|
-
assert_equal(
|
338
|
+
assert_equal(4, @sink.datagrams.size)
|
320
339
|
|
321
340
|
counter_datagram = @sink.datagrams.find { |d| d.name == "foo" }
|
322
341
|
assert_equal(1, counter_datagram.value)
|
@@ -329,5 +348,9 @@ class AggregatorTest < Minitest::Test
|
|
329
348
|
gauge_datagram = @sink.datagrams.find { |d| d.name == "baz" }
|
330
349
|
assert_equal(100, gauge_datagram.value)
|
331
350
|
assert_equal(["foo:bar"], gauge_datagram.tags)
|
351
|
+
|
352
|
+
sampled_timing_datagram = @sink.datagrams.find { |d| d.name == "sampled_timing" }
|
353
|
+
assert_equal(100.0, sampled_timing_datagram.value)
|
354
|
+
assert_equal(0.01, sampled_timing_datagram.sample_rate)
|
332
355
|
end
|
333
356
|
end
|
data/test/client_test.rb
CHANGED
@@ -90,7 +90,7 @@ class ClientTest < Minitest::Test
|
|
90
90
|
client.measure("block_duration_example") { 1 + 1 }
|
91
91
|
client.force_flush
|
92
92
|
|
93
|
-
datagram = client.sink.datagrams.
|
93
|
+
datagram = client.sink.datagrams.find { |d| d.name == "bar.foo" }
|
94
94
|
assert_equal("bar.foo", datagram.name)
|
95
95
|
assert_equal(2, datagram.value)
|
96
96
|
|
@@ -249,12 +249,17 @@ class ClientTest < Minitest::Test
|
|
249
249
|
mock_sink = mock("sink")
|
250
250
|
mock_sink.stubs(:sample?).returns(false, true, false, false, true)
|
251
251
|
# Since we are aggregating, we only expect a single datagram.
|
252
|
-
mock_sink.expects(:<<).with("metric:60:60|d").once
|
252
|
+
mock_sink.expects(:<<).with("metric:60:60|d|@0.5").once
|
253
253
|
mock_sink.expects(:flush).once
|
254
254
|
|
255
255
|
client = StatsD::Instrument::Client.new(sink: mock_sink, default_sample_rate: 0.5, enable_aggregation: true)
|
256
256
|
5.times { client.distribution("metric", 60) }
|
257
257
|
client.force_flush
|
258
|
+
|
259
|
+
# undo mock
|
260
|
+
mock_sink.unstub(:sample?)
|
261
|
+
mock_sink.unstub(:<<)
|
262
|
+
mock_sink.unstub(:flush)
|
258
263
|
end
|
259
264
|
|
260
265
|
def test_clone_with_prefix_option
|
data/test/integration_test.rb
CHANGED
@@ -77,4 +77,30 @@ class IntegrationTest < Minitest::Test
|
|
77
77
|
assert_equal("counter:20|c", @server.recvfrom(100).first)
|
78
78
|
assert_operator(Time.now - before_flush, :<, 0.3, "Flush and ingest should have happened within 0.4s")
|
79
79
|
end
|
80
|
+
|
81
|
+
def test_live_local_udp_socket_with_aggregation_sampled_scenario
|
82
|
+
client = StatsD::Instrument::Environment.new(
|
83
|
+
"STATSD_ADDR" => "#{@server.addr[2]}:#{@server.addr[1]}",
|
84
|
+
"STATSD_IMPLEMENTATION" => "dogstatsd",
|
85
|
+
"STATSD_ENV" => "production",
|
86
|
+
"STATSD_ENABLE_AGGREGATION" => "true",
|
87
|
+
"STATSD_AGGREGATION_INTERVAL" => "0.1",
|
88
|
+
).client
|
89
|
+
|
90
|
+
100.times do
|
91
|
+
client.increment("counter", 2)
|
92
|
+
client.distribution("test_distribution", 3, sample_rate: 0.1)
|
93
|
+
end
|
94
|
+
|
95
|
+
sleep(0.2)
|
96
|
+
|
97
|
+
packets = []
|
98
|
+
while IO.select([@server], nil, nil, 0.1)
|
99
|
+
packets << @server.recvfrom(300).first
|
100
|
+
end
|
101
|
+
packets = packets.map { |packet| packet.split("\n") }.flatten
|
102
|
+
|
103
|
+
assert_match(/counter:\d+|c/, packets.find { |packet| packet.start_with?("counter:") })
|
104
|
+
assert_match(/test_distribution:\d+:3|d/, packets.find { |packet| packet.start_with?("test_distribution:") })
|
105
|
+
end
|
80
106
|
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.
|
4
|
+
version: 3.9.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jesse Storimer
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-10-
|
13
|
+
date: 2024-10-31 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.
|