statsd-instrument 3.9.4 → 3.9.6
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|