statsd-instrument 3.10.0 → 3.11.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4768c067e6be6c254db4557916f0cfecb4b86cb318bc8c220acfaf67e9930029
4
- data.tar.gz: 756ea941dc116e8c4ed95f1d6af1060ca1d524271b13cb5a418772fc7952519b
3
+ metadata.gz: 1f2b45a3e809ef0e5ca26b7faf52512632705f62120b3b73987a10282e19ed76
4
+ data.tar.gz: 7d7574e77adea4dc54fcf0355b83e1392623bcf2d4ecccd60a2cc101f62c84bb
5
5
  SHA512:
6
- metadata.gz: aab58b7242348378b11869da05bcf548c434b63a5f9155a904532b186e5a420e44891c30b1e551d0627359da05fad9c50414876d6eb57baba5883b7990f1d127
7
- data.tar.gz: aece18a4af6ef4a14fbb437a91b3dcbd84b7a876a815210d3fb1e207b4851bc8803531589f1b985563da5a31e93e2724d1cc18d2d063742d7dc91ccbeb5abf60
6
+ metadata.gz: bb416ef206605fb8204acec2b0501e4722a2bd51d81fbc1083c4a4a96203a2838dd329355908ee83b64b7a3943f8f8af862cbf7d8433363d4452e68f14292f95
7
+ data.tar.gz: bf0597922ce2dbe8837ef29b0de1f7c4fa1ca0e9623dc4d15628661b49059577d31499c46164a14e98744c5e765d68b407a15f14153c212ec245f3d90ed691e7
data/CHANGELOG.md CHANGED
@@ -6,6 +6,11 @@ section below.
6
6
 
7
7
  ## Unreleased changes
8
8
 
9
+ ## Version 3.11.0
10
+
11
+ - [#418](https://github.com/Shopify/statsd-instrument/pull/418) - Prevent misuse of `CompiledMetric` definitions by requiring a subclass when defining one.
12
+ - [#417](https://github.com/Shopify/statsd-instrument/pull/417) - Add a `metric_name` method to `CompiledMetric`.
13
+
9
14
  ## Version 3.10.0
10
15
 
11
16
  - [#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.
@@ -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 ArgumentError, "Every CompiledMetric subclass needs to call `define` before accessing its sample_rate." unless defined?(@sample_rate)
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 ArgumentError, "Every CompiledMetric subclass needs to call `define` before first invocation of #{method_name}."
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module StatsD
4
4
  module Instrument
5
- VERSION = "3.10.0"
5
+ VERSION = "3.11.0"
6
6
  end
7
7
  end
@@ -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(ArgumentError) do
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(ArgumentError) do
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)
@@ -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(ArgumentError) do
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(ArgumentError) do
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.10.0
4
+ version: 3.11.0
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.8
138
+ rubygems_version: 4.0.9
139
139
  specification_version: 4
140
140
  summary: A StatsD client for Ruby apps
141
141
  test_files: