statsd-instrument 3.10.0 → 3.11.1
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 +9 -0
- data/lib/statsd/instrument/client.rb +19 -0
- data/lib/statsd/instrument/compiled_metric.rb +37 -5
- data/lib/statsd/instrument/version.rb +1 -1
- data/test/compiled_metric/counter_test.rb +1 -1
- data/test/compiled_metric/distribution_test.rb +42 -6
- data/test/compiled_metric/gauge_test.rb +1 -1
- data/test/compiled_metric_test.rb +70 -1
- 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: 3da8fd9b1f80c7cf776c41d335ab14858dd625299bbec536c7f8994dfe93baf6
|
|
4
|
+
data.tar.gz: b1ca1c4049a5f3956ea510074027bb577d26ab8a2033cf96d4af5fcc39fc90c9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f8774b020de05ec35687a82aca947a19b095ea34867fdcd73518748c9ccddfe28e41cdc41c2dfe1b9c2e4f657b4416218d67fe3b0e259a8bf10a810580f8ea60
|
|
7
|
+
data.tar.gz: 420ea1eb853426b83d2d5f546dde091d402aaff31ef243b912df3869dd02882e8ef24bde4b124f860beccb0e9528dcfbcd7ab6b25b36b3d2fc3e02798fa209e1
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,15 @@ section below.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased changes
|
|
8
8
|
|
|
9
|
+
## Version 3.11.1
|
|
10
|
+
|
|
11
|
+
- Fix `CompiledMetric::Distribution` applying `sample_rate` twice.
|
|
12
|
+
|
|
13
|
+
## Version 3.11.0
|
|
14
|
+
|
|
15
|
+
- [#418](https://github.com/Shopify/statsd-instrument/pull/418) - Prevent misuse of `CompiledMetric` definitions by requiring a subclass when defining one.
|
|
16
|
+
- [#417](https://github.com/Shopify/statsd-instrument/pull/417) - Add a `metric_name` method to `CompiledMetric`.
|
|
17
|
+
|
|
9
18
|
## Version 3.10.0
|
|
10
19
|
|
|
11
20
|
- [#416](https://github.com/Shopify/statsd-instrument/pull/416) - Fix missing `metric_prefix` in Aggregator finalizer, causing metrics to lose their prefix when flushed during GC.
|
|
@@ -285,6 +285,25 @@ module StatsD
|
|
|
285
285
|
StatsD::Instrument::VOID
|
|
286
286
|
end
|
|
287
287
|
|
|
288
|
+
# Emits a pre-sampled compiled distribution metric.
|
|
289
|
+
# This method is used when the generated compiled metric method already made
|
|
290
|
+
# the sampling decision before measuring block latency.
|
|
291
|
+
#
|
|
292
|
+
# @param precompiled_datagram [StatsD::Instrument::CompiledMetric::PrecompiledDatagram]
|
|
293
|
+
# The precompiled metric datagram
|
|
294
|
+
# @param value [Numeric] The metric value
|
|
295
|
+
# @return [void]
|
|
296
|
+
# @api private
|
|
297
|
+
def emit_presampled_precompiled_distribution_metric(precompiled_datagram, value)
|
|
298
|
+
if @enable_aggregation
|
|
299
|
+
@aggregator.aggregate_precompiled_timing_metric(precompiled_datagram, value)
|
|
300
|
+
return StatsD::Instrument::VOID
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
emit(precompiled_datagram.to_datagram(value))
|
|
304
|
+
StatsD::Instrument::VOID
|
|
305
|
+
end
|
|
306
|
+
|
|
288
307
|
# Emits a precompiled metric for high-performance use cases.
|
|
289
308
|
# This method is used by {StatsD::Instrument::CompiledMetric::Gauge} to emit metrics
|
|
290
309
|
# with minimal allocations.
|
|
@@ -18,6 +18,12 @@ module StatsD
|
|
|
18
18
|
# # Later, emit with minimal allocations:
|
|
19
19
|
# CheckoutMetric.increment(shop_id: 123, user_id: 456, value: 1)
|
|
20
20
|
class CompiledMetric
|
|
21
|
+
# Raised when a CompiledMetric subclass is defined incorrectly.
|
|
22
|
+
# This includes calling `define` on a base type class directly,
|
|
23
|
+
# calling `define` more than once, or using a metric before `define`
|
|
24
|
+
# has been called.
|
|
25
|
+
class DefinitionError < StandardError; end
|
|
26
|
+
|
|
21
27
|
# Default maximum number of unique tag combinations to cache before clearing
|
|
22
28
|
# the cache to prevent unbounded memory growth
|
|
23
29
|
DEFAULT_MAX_TAG_COMBINATION_CACHE_SIZE = 5000
|
|
@@ -33,6 +39,19 @@ module StatsD
|
|
|
33
39
|
# @param max_cache_size [Integer] Maximum tag combinations this metric supports, and will be retained in-memory. Cardinality beyond this number will fall back to the slow path and should be avoided.
|
|
34
40
|
# @return [Class] A new CompiledMetric subclass configured for this metric
|
|
35
41
|
def define(name:, static_tags: {}, tags: {}, no_prefix: false, sample_rate: nil, max_cache_size: DEFAULT_MAX_TAG_COMBINATION_CACHE_SIZE)
|
|
42
|
+
if equal?(CompiledMetric) || superclass.equal?(CompiledMetric)
|
|
43
|
+
raise DefinitionError,
|
|
44
|
+
"`define` must be called on a subclass, not on #{self.name} directly. " \
|
|
45
|
+
"Use `Class.new(#{self.name}) { define(...) }` or " \
|
|
46
|
+
"`class MyMetric < #{self.name}; define(...); end` instead."
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if defined?(@datagram_blueprint)
|
|
50
|
+
raise DefinitionError,
|
|
51
|
+
"`define` has already been called on #{self.name}. " \
|
|
52
|
+
"Each CompiledMetric subclass can only be defined once."
|
|
53
|
+
end
|
|
54
|
+
|
|
36
55
|
client = StatsD.singleton_client
|
|
37
56
|
|
|
38
57
|
# Build the datagram blueprint using the builder
|
|
@@ -51,7 +70,7 @@ module StatsD
|
|
|
51
70
|
# Create a new class for this specific metric
|
|
52
71
|
# Using classes instead of instances for better YJIT optimization
|
|
53
72
|
metric_class = tap do
|
|
54
|
-
@name = DatagramBlueprintBuilder.normalize_name(name)
|
|
73
|
+
@name = DatagramBlueprintBuilder.normalize_name(name).freeze
|
|
55
74
|
@datagram_blueprint = datagram_blueprint
|
|
56
75
|
@tag_combination_cache = {}
|
|
57
76
|
@max_cache_size = max_cache_size
|
|
@@ -101,10 +120,15 @@ module StatsD
|
|
|
101
120
|
@singleton_client.sink.sample?(sample_rate)
|
|
102
121
|
end
|
|
103
122
|
|
|
123
|
+
# @return [String, nil] The normalized metric name for this compiled metric class (`nil` if not yet defined).
|
|
124
|
+
def metric_name
|
|
125
|
+
@name
|
|
126
|
+
end
|
|
127
|
+
|
|
104
128
|
# @return [Float] The defined sample rate for a metric class.
|
|
105
129
|
# Will raise when `define` has not yet been called on the class.
|
|
106
130
|
def sample_rate
|
|
107
|
-
raise
|
|
131
|
+
raise DefinitionError, "Every CompiledMetric subclass needs to call `define` before accessing its sample_rate." unless defined?(@sample_rate)
|
|
108
132
|
|
|
109
133
|
@sample_rate
|
|
110
134
|
end
|
|
@@ -115,7 +139,7 @@ module StatsD
|
|
|
115
139
|
# Once `define` was called during the class creation, it will override the
|
|
116
140
|
# method implementation to emit the actual metric datagrams.
|
|
117
141
|
def require_define_to_be_called
|
|
118
|
-
raise
|
|
142
|
+
raise DefinitionError, "Every CompiledMetric subclass needs to call `define` before first invocation of #{method_name}."
|
|
119
143
|
end
|
|
120
144
|
|
|
121
145
|
def generate_block_handler
|
|
@@ -153,6 +177,10 @@ module StatsD
|
|
|
153
177
|
default_val = default_value
|
|
154
178
|
default_val_assignment = default_val.nil? ? "" : " = #{default_val.inspect}"
|
|
155
179
|
allow_block = allow_measuring_latency
|
|
180
|
+
# Block-enabled compiled metrics run through generate_block_handler first,
|
|
181
|
+
# which applies sample_rate before emitting. Use the presampled client helper
|
|
182
|
+
# here so distributions don't make a second sampling decision.
|
|
183
|
+
client_emit_method = allow_block ? "emit_presampled_precompiled_#{method}_metric" : "emit_precompiled_#{method}_metric"
|
|
156
184
|
|
|
157
185
|
method_code = <<~RUBY
|
|
158
186
|
def self.#{method}(__value__#{default_val_assignment}, #{tag_names.map { |name| "#{name}:" }.join(", ")})
|
|
@@ -194,7 +222,7 @@ module StatsD
|
|
|
194
222
|
|
|
195
223
|
__datagram__ ||= PrecompiledDatagram.new([#{tag_names.join(", ")}], @datagram_blueprint, @sample_rate)
|
|
196
224
|
|
|
197
|
-
@singleton_client
|
|
225
|
+
@singleton_client.#{client_emit_method}(__datagram__, __value__)
|
|
198
226
|
__return_value__
|
|
199
227
|
end
|
|
200
228
|
RUBY
|
|
@@ -209,12 +237,16 @@ module StatsD
|
|
|
209
237
|
method = method_name
|
|
210
238
|
default_val = default_value
|
|
211
239
|
allow_block = allow_measuring_latency
|
|
240
|
+
# Block-enabled compiled metrics run through generate_block_handler first,
|
|
241
|
+
# which applies sample_rate before emitting. Use the presampled client helper
|
|
242
|
+
# here so distributions don't make a second sampling decision.
|
|
243
|
+
client_emit_method = allow_block ? "emit_presampled_precompiled_#{method}_metric" : "emit_precompiled_#{method}_metric"
|
|
212
244
|
|
|
213
245
|
instance_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
|
214
246
|
def self.#{method}(__value__ = #{default_val.inspect})
|
|
215
247
|
__return_value__ = StatsD::Instrument::VOID
|
|
216
248
|
#{generate_block_handler if allow_block}
|
|
217
|
-
@singleton_client
|
|
249
|
+
@singleton_client.#{client_emit_method}(@static_datagram, __value__)
|
|
218
250
|
__return_value__
|
|
219
251
|
end
|
|
220
252
|
RUBY
|
|
@@ -24,7 +24,7 @@ class CompiledMetricCounterTest < Minitest::Test
|
|
|
24
24
|
def test_counter_without_define
|
|
25
25
|
metric = Class.new(StatsD::Instrument::CompiledMetric::Counter)
|
|
26
26
|
|
|
27
|
-
error = assert_raises(
|
|
27
|
+
error = assert_raises(StatsD::Instrument::CompiledMetric::DefinitionError) do
|
|
28
28
|
metric.increment(5)
|
|
29
29
|
end
|
|
30
30
|
assert_equal("Every CompiledMetric subclass needs to call `define` before first invocation of increment.", error.message)
|
|
@@ -24,7 +24,7 @@ class CompiledMetricDistributionTest < Minitest::Test
|
|
|
24
24
|
def test_distribution_without_define
|
|
25
25
|
metric = Class.new(StatsD::Instrument::CompiledMetric::Distribution)
|
|
26
26
|
|
|
27
|
-
error = assert_raises(
|
|
27
|
+
error = assert_raises(StatsD::Instrument::CompiledMetric::DefinitionError) do
|
|
28
28
|
metric.distribution(5)
|
|
29
29
|
end
|
|
30
30
|
assert_equal("Every CompiledMetric subclass needs to call `define` before first invocation of distribution.", error.message)
|
|
@@ -230,6 +230,43 @@ class CompiledMetricDistributionTest < Minitest::Test
|
|
|
230
230
|
assert_equal(["env:production", "region:us-east", "service:web"], datagram.tags.sort)
|
|
231
231
|
end
|
|
232
232
|
|
|
233
|
+
def test_sampled_distribution_value_uses_one_sampling_decision
|
|
234
|
+
sample_rate = 0.5
|
|
235
|
+
@sink.expects(:sample?).with(sample_rate).once.returns(true)
|
|
236
|
+
|
|
237
|
+
metric = Class.new(StatsD::Instrument::CompiledMetric::Distribution) do
|
|
238
|
+
define(
|
|
239
|
+
name: "foo.bar",
|
|
240
|
+
tags: { shop_id: Integer },
|
|
241
|
+
sample_rate: sample_rate,
|
|
242
|
+
)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
metric.distribution(5, shop_id: 123)
|
|
246
|
+
|
|
247
|
+
assert_equal(1, @sink.datagrams.size)
|
|
248
|
+
assert_equal("test.foo.bar:5|d|@0.5|#shop_id:123", @sink.datagrams.first.source)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def test_sampled_distribution_block_uses_one_sampling_decision
|
|
252
|
+
sample_rate = 0.5
|
|
253
|
+
@sink.expects(:sample?).with(sample_rate).once.returns(true)
|
|
254
|
+
Process.stubs(:clock_gettime).with(Process::CLOCK_MONOTONIC, :float_millisecond).returns(100.0, 125.0)
|
|
255
|
+
|
|
256
|
+
metric = Class.new(StatsD::Instrument::CompiledMetric::Distribution) do
|
|
257
|
+
define(
|
|
258
|
+
name: "foo.bar",
|
|
259
|
+
sample_rate: sample_rate,
|
|
260
|
+
)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
returned_value = metric.distribution { :result }
|
|
264
|
+
|
|
265
|
+
assert_equal(:result, returned_value)
|
|
266
|
+
assert_equal(1, @sink.datagrams.size)
|
|
267
|
+
assert_equal("test.foo.bar:25.0|d|@0.5", @sink.datagrams.first.source)
|
|
268
|
+
end
|
|
269
|
+
|
|
233
270
|
def test_latency_as_value_when_block_provided
|
|
234
271
|
metric = Class.new(StatsD::Instrument::CompiledMetric::Distribution) do
|
|
235
272
|
define(
|
|
@@ -415,18 +452,17 @@ class CompiledMetricDistributionWithAggregationTest < Minitest::Test
|
|
|
415
452
|
end
|
|
416
453
|
|
|
417
454
|
def test_sample_rate_with_aggregation
|
|
418
|
-
|
|
419
|
-
|
|
455
|
+
sample_rate = 0.5
|
|
456
|
+
@sink.expects(:sample?).with(sample_rate).times(5).returns(false, true, false, false, true)
|
|
457
|
+
|
|
420
458
|
metric = Class.new(StatsD::Instrument::CompiledMetric::Distribution) do
|
|
421
459
|
define(
|
|
422
460
|
name: "foo.bar",
|
|
423
461
|
static_tags: { service: "web" },
|
|
424
|
-
sample_rate:
|
|
462
|
+
sample_rate: sample_rate,
|
|
425
463
|
)
|
|
426
464
|
end
|
|
427
465
|
|
|
428
|
-
metric.stubs(:sample?).returns(false, true, false, false, true)
|
|
429
|
-
|
|
430
466
|
metric.distribution(1)
|
|
431
467
|
metric.distribution(2)
|
|
432
468
|
metric.distribution(3)
|
|
@@ -24,7 +24,7 @@ class CompiledMetricGaugeTest < Minitest::Test
|
|
|
24
24
|
def test_gauge_without_define
|
|
25
25
|
metric = Class.new(StatsD::Instrument::CompiledMetric::Gauge)
|
|
26
26
|
|
|
27
|
-
error = assert_raises(
|
|
27
|
+
error = assert_raises(StatsD::Instrument::CompiledMetric::DefinitionError) do
|
|
28
28
|
metric.gauge(5)
|
|
29
29
|
end
|
|
30
30
|
assert_equal("Every CompiledMetric subclass needs to call `define` before first invocation of gauge.", error.message)
|
|
@@ -439,9 +439,78 @@ class CompiledMetricDefinitionTest < Minitest::Test
|
|
|
439
439
|
def test_sample_rate_without_define
|
|
440
440
|
metric = Class.new(StatsD::Instrument::CompiledMetric::Counter)
|
|
441
441
|
|
|
442
|
-
error = assert_raises(
|
|
442
|
+
error = assert_raises(StatsD::Instrument::CompiledMetric::DefinitionError) do
|
|
443
443
|
metric.sample_rate
|
|
444
444
|
end
|
|
445
445
|
assert_equal("Every CompiledMetric subclass needs to call `define` before accessing its sample_rate.", error.message)
|
|
446
446
|
end
|
|
447
|
+
|
|
448
|
+
def test_metric_name
|
|
449
|
+
metric = Class.new(StatsD::Instrument::CompiledMetric::Counter) do
|
|
450
|
+
define(
|
|
451
|
+
name: "foo.bar",
|
|
452
|
+
)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
assert_equal("foo.bar", metric.metric_name)
|
|
456
|
+
assert_predicate(metric.metric_name, :frozen?)
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def test_metric_name_is_normalized
|
|
460
|
+
metric = Class.new(StatsD::Instrument::CompiledMetric::Counter) do
|
|
461
|
+
define(
|
|
462
|
+
name: "foo:bar|baz@qux",
|
|
463
|
+
)
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
assert_equal("foo_bar_baz_qux", metric.metric_name)
|
|
467
|
+
assert_predicate(metric.metric_name, :frozen?)
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def test_metric_name_without_define
|
|
471
|
+
metric = Class.new(StatsD::Instrument::CompiledMetric::Counter)
|
|
472
|
+
|
|
473
|
+
assert_nil(metric.metric_name)
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def test_define_directly_on_counter_raises
|
|
477
|
+
error = assert_raises(StatsD::Instrument::CompiledMetric::DefinitionError) do
|
|
478
|
+
StatsD::Instrument::CompiledMetric::Counter.define(name: "bad_metric")
|
|
479
|
+
end
|
|
480
|
+
assert_includes(error.message, "`define` must be called on a subclass")
|
|
481
|
+
assert_includes(error.message, "Counter")
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
def test_define_directly_on_gauge_raises
|
|
485
|
+
error = assert_raises(StatsD::Instrument::CompiledMetric::DefinitionError) do
|
|
486
|
+
StatsD::Instrument::CompiledMetric::Gauge.define(name: "bad_metric")
|
|
487
|
+
end
|
|
488
|
+
assert_includes(error.message, "`define` must be called on a subclass")
|
|
489
|
+
assert_includes(error.message, "Gauge")
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def test_define_directly_on_distribution_raises
|
|
493
|
+
error = assert_raises(StatsD::Instrument::CompiledMetric::DefinitionError) do
|
|
494
|
+
StatsD::Instrument::CompiledMetric::Distribution.define(name: "bad_metric")
|
|
495
|
+
end
|
|
496
|
+
assert_includes(error.message, "`define` must be called on a subclass")
|
|
497
|
+
assert_includes(error.message, "Distribution")
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def test_define_directly_on_compiled_metric_raises
|
|
501
|
+
error = assert_raises(StatsD::Instrument::CompiledMetric::DefinitionError) do
|
|
502
|
+
StatsD::Instrument::CompiledMetric.define(name: "bad_metric")
|
|
503
|
+
end
|
|
504
|
+
assert_includes(error.message, "`define` must be called on a subclass")
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def test_double_define_raises
|
|
508
|
+
error = assert_raises(StatsD::Instrument::CompiledMetric::DefinitionError) do
|
|
509
|
+
Class.new(StatsD::Instrument::CompiledMetric::Counter) do
|
|
510
|
+
define(name: "first_metric")
|
|
511
|
+
define(name: "second_metric")
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
assert_includes(error.message, "`define` has already been called")
|
|
515
|
+
end
|
|
447
516
|
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.
|
|
4
|
+
version: 3.11.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jesse Storimer
|
|
@@ -135,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
135
135
|
- !ruby/object:Gem::Version
|
|
136
136
|
version: '0'
|
|
137
137
|
requirements: []
|
|
138
|
-
rubygems_version: 4.0.
|
|
138
|
+
rubygems_version: 4.0.14
|
|
139
139
|
specification_version: 4
|
|
140
140
|
summary: A StatsD client for Ruby apps
|
|
141
141
|
test_files:
|