statsd-instrument 3.1.0 → 3.2.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: 3a481f6cd0897b93e3e992fc5315b496c033a98a8cccd02948146cb638e0a6b2
4
- data.tar.gz: ab454f2fecc89aa662ac2f16632fe2fc3c4e4b2b102d3c4058642e5706bf64c1
3
+ metadata.gz: f42d327300430dd5926c971c7d27d860acd99800cc8eeb4552892f755037553f
4
+ data.tar.gz: 88c22b7ef591313d0e159a24f3875ddc7a63acf0c6d10551cac6bf069a6c0ad3
5
5
  SHA512:
6
- metadata.gz: f3fae39281925135491d66805ff8dada34b0ea798d3403fdf0e6d3b9c1e2d3eda4a78295d7eea046ba7a0db55fb5d357d72e853558f12fdf81c71eff3713c816
7
- data.tar.gz: 852e0838027594b8451d4fb617ca9aa5f614b570c50180bd513fb2986419adf3e9ea4f59ce86e63fbe799ccc3a07720954c87778c9a9dca63cd62fb339906d43
6
+ metadata.gz: 8a0d04e0acf3ddb8ae85bb3d5b4a560dbb713c6c8d26654cca6d52b5b3d70a8570b424b489e33d5bcc301f419db8e1de448daaee863fb0624ff52aa10c67c9a4
7
+ data.tar.gz: f56d3bd3ecd117d9a359de5445f3b00750d11151a40da26f6e902cd1c5800c8688ae1bab05f2457eb519751c642f69278425ba4541a6fe6caa41dd766dd3a21c
@@ -5,18 +5,16 @@ on: push
5
5
  jobs:
6
6
  test:
7
7
  name: Send metric over UDP
8
- runs-on: ubuntu-18.04
8
+ runs-on: ubuntu-latest
9
9
 
10
10
  steps:
11
11
  - uses: actions/checkout@v1
12
12
 
13
- - name: Setup Ruby
14
- uses: actions/setup-ruby@v1
13
+ - name: Set up Ruby
14
+ uses: ruby/setup-ruby@v1
15
15
  with:
16
16
  ruby-version: 2.6
17
-
18
- - name: Install dependencies
19
- run: gem install bundler && bundle install --jobs 4 --retry 3
17
+ bundler-cache: true
20
18
 
21
19
  - name: Run benchmark on branch
22
20
  run: benchmark/send-metrics-to-local-udp-receiver
@@ -5,18 +5,16 @@ on: push
5
5
  jobs:
6
6
  test:
7
7
  name: Rubocop
8
- runs-on: ubuntu-18.04
8
+ runs-on: ubuntu-latest
9
9
 
10
10
  steps:
11
11
  - uses: actions/checkout@v1
12
12
 
13
- - name: Setup Ruby
14
- uses: actions/setup-ruby@v1
13
+ - name: Set up Ruby
14
+ uses: ruby/setup-ruby@v1
15
15
  with:
16
16
  ruby-version: 2.6
17
-
18
- - name: Install dependencies
19
- run: gem install bundler && bundle install --jobs 4 --retry 3
17
+ bundler-cache: true
20
18
 
21
19
  - name: Run Rubocop
22
20
  run: bin/rubocop
@@ -4,12 +4,12 @@ on: push
4
4
 
5
5
  jobs:
6
6
  test:
7
- name: Ruby ${{ matrix.ruby }} on ubuntu-18.04
8
- runs-on: ubuntu-18.04
7
+ name: Ruby ${{ matrix.ruby }} on ubuntu-latest
8
+ runs-on: ubuntu-latest
9
9
  strategy:
10
10
  fail-fast: false
11
11
  matrix:
12
- ruby: ['2.6', '2.7', '3.0']
12
+ ruby: ['2.6', '2.7', '3.0', '3.1']
13
13
 
14
14
  # Windows on macOS builds started failing, so they are disabled for noew
15
15
  # platform: [windows-2019, macOS-10.14, ubuntu-18.04]
@@ -19,13 +19,11 @@ jobs:
19
19
  steps:
20
20
  - uses: actions/checkout@v1
21
21
 
22
- - name: Setup Ruby
23
- uses: actions/setup-ruby@v1
22
+ - name: Set up Ruby
23
+ uses: ruby/setup-ruby@v1
24
24
  with:
25
25
  ruby-version: ${{ matrix.ruby }}
26
-
27
- - name: Install dependencies
28
- run: gem install bundler && bundle install --jobs 4 --retry 3
26
+ bundler-cache: true
29
27
 
30
28
  - name: Run test suite
31
- run: rake test
29
+ run: bundle exec rake test
data/.rubocop.yml CHANGED
@@ -5,8 +5,9 @@ require:
5
5
  - ./lib/statsd/instrument/rubocop.rb
6
6
 
7
7
  AllCops:
8
- TargetRubyVersion: 2.6
8
+ TargetRubyVersion: 2.7
9
9
  UseCache: true
10
+ SuggestExtensions: false
10
11
  CacheRootDirectory: tmp/rubocop
11
12
  Exclude:
12
13
  - statsd-instrument.gemspec
data/CHANGELOG.md CHANGED
@@ -8,6 +8,18 @@ section below.
8
8
 
9
9
  _Nothing yet_
10
10
 
11
+ ## Version 3.2.0
12
+
13
+ - Add `tag_error_class` option to `statsd_count_success` which tags the class of a thrown error
14
+
15
+ ## Version 3.1.2
16
+
17
+ - Fix bug when passing custom client to expectation.
18
+
19
+ ## Version 3.1.1
20
+
21
+ - Improved flushing of buffered datagrams on process exit when using UDP batching.
22
+
11
23
  ## Version 3.1.0
12
24
 
13
25
  - Introduced UDP batching using a dispatcher thread, and made it the
data/Gemfile CHANGED
@@ -8,6 +8,6 @@ gem "minitest"
8
8
  gem "rspec"
9
9
  gem "mocha"
10
10
  gem "yard"
11
- gem "rubocop", ">= 1.0"
11
+ gem "rubocop", [">= 1.0", "< 1.30"] # TODO: Our cops are broken by rubocop 1.30, we need to figure out why
12
12
  gem "rubocop-shopify", require: false
13
13
  gem "benchmark-ips"
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # StatsD client for Ruby apps
2
2
 
3
- This is a ruby client for statsd (http://github.com/etsy/statsd). It provides
3
+ This is a ruby client for statsd (https://github.com/statsd/statsd). It provides
4
4
  a lightweight way to track and measure metrics in your application.
5
5
 
6
6
  We call out to statsd by sending data over a UDP socket. UDP sockets are fast,
@@ -10,8 +10,8 @@ because it means your code doesn't get bogged down trying to log statistics.
10
10
  We send data to statsd several times per request and haven't noticed a
11
11
  performance hit.
12
12
 
13
- For more information about StatsD, see the [README of the Etsy
14
- project](http://github.com/etsy/statsd).
13
+ For more information about StatsD, see the [README of the StatsD
14
+ project](https://github.com/statsd/statsd).
15
15
 
16
16
  ## Configuration
17
17
 
@@ -27,7 +27,7 @@ The following environment variables are supported:
27
27
  explicitly, this will be determined based on other environment variables,
28
28
  like `RAILS_ENV` or `ENV`. The library will behave differently:
29
29
 
30
- - In the **production** and **staging** environment, thre library will
30
+ - In the **production** and **staging** environment, the library will
31
31
  actually send UDP packets.
32
32
  - In the **test** environment, it will swallow all calls, but allows you to
33
33
  capture them for testing purposes. See below for notes on writing tests.
@@ -6,8 +6,8 @@ require "tmpdir"
6
6
  require "benchmark/ips"
7
7
 
8
8
  revision = %x(git rev-parse HEAD).rstrip
9
- master_revision = %x(git rev-parse origin/master).rstrip
10
- branch = if revision == master_revision
9
+ base_revision = %x(git rev-parse origin/master).rstrip
10
+ branch = if revision == base_revision
11
11
  "master"
12
12
  else
13
13
  %x(git rev-parse --abbrev-ref HEAD).rstrip
@@ -8,8 +8,8 @@ require "socket"
8
8
  require "statsd-instrument"
9
9
 
10
10
  revision = %x(git rev-parse HEAD).rstrip
11
- master_revision = %x(git rev-parse origin/master).rstrip
12
- branch = if revision == master_revision
11
+ base_revision = %x(git rev-parse origin/master).rstrip
12
+ branch = if revision == base_revision
13
13
  "master"
14
14
  else
15
15
  %x(git rev-parse --abbrev-ref HEAD).rstrip
@@ -57,6 +57,7 @@ module StatsD
57
57
  def assert_no_statsd_calls(*metric_names, datagrams: nil, client: nil, &block)
58
58
  if datagrams.nil?
59
59
  raise LocalJumpError, "assert_no_statsd_calls requires a block" unless block_given?
60
+
60
61
  datagrams = capture_statsd_datagrams_with_exception_handling(client: client, &block)
61
62
  end
62
63
 
@@ -74,7 +75,7 @@ module StatsD
74
75
  # @raise [Minitest::Assertion] If an exception occurs, or if the metric did
75
76
  # not occur as specified during the execution the block.
76
77
  def assert_statsd_increment(metric_name, value = nil, datagrams: nil, client: nil, **options, &block)
77
- expectation = StatsD::Instrument::Expectation.increment(metric_name, value, **options)
78
+ expectation = StatsD::Instrument::Expectation.increment(metric_name, value, client: client, **options)
78
79
  assert_statsd_expectation(expectation, datagrams: datagrams, client: client, &block)
79
80
  end
80
81
 
@@ -86,7 +87,7 @@ module StatsD
86
87
  # @return [void]
87
88
  # @raise (see #assert_statsd_increment)
88
89
  def assert_statsd_measure(metric_name, value = nil, datagrams: nil, client: nil, **options, &block)
89
- expectation = StatsD::Instrument::Expectation.measure(metric_name, value, **options)
90
+ expectation = StatsD::Instrument::Expectation.measure(metric_name, value, client: client, **options)
90
91
  assert_statsd_expectation(expectation, datagrams: datagrams, client: client, &block)
91
92
  end
92
93
 
@@ -98,7 +99,7 @@ module StatsD
98
99
  # @return [void]
99
100
  # @raise (see #assert_statsd_increment)
100
101
  def assert_statsd_gauge(metric_name, value = nil, datagrams: nil, client: nil, **options, &block)
101
- expectation = StatsD::Instrument::Expectation.gauge(metric_name, value, **options)
102
+ expectation = StatsD::Instrument::Expectation.gauge(metric_name, value, client: client, **options)
102
103
  assert_statsd_expectation(expectation, datagrams: datagrams, client: client, &block)
103
104
  end
104
105
 
@@ -110,7 +111,7 @@ module StatsD
110
111
  # @return [void]
111
112
  # @raise (see #assert_statsd_increment)
112
113
  def assert_statsd_histogram(metric_name, value = nil, datagrams: nil, client: nil, **options, &block)
113
- expectation = StatsD::Instrument::Expectation.histogram(metric_name, value, **options)
114
+ expectation = StatsD::Instrument::Expectation.histogram(metric_name, value, client: client, **options)
114
115
  assert_statsd_expectation(expectation, datagrams: datagrams, client: client, &block)
115
116
  end
116
117
 
@@ -122,7 +123,7 @@ module StatsD
122
123
  # @return [void]
123
124
  # @raise (see #assert_statsd_increment)
124
125
  def assert_statsd_distribution(metric_name, value = nil, datagrams: nil, client: nil, **options, &block)
125
- expectation = StatsD::Instrument::Expectation.distribution(metric_name, value, **options)
126
+ expectation = StatsD::Instrument::Expectation.distribution(metric_name, value, client: client, **options)
126
127
  assert_statsd_expectation(expectation, datagrams: datagrams, client: client, &block)
127
128
  end
128
129
 
@@ -134,7 +135,7 @@ module StatsD
134
135
  # @return [void]
135
136
  # @raise (see #assert_statsd_increment)
136
137
  def assert_statsd_set(metric_name, value = nil, datagrams: nil, client: nil, **options, &block)
137
- expectation = StatsD::Instrument::Expectation.set(metric_name, value, **options)
138
+ expectation = StatsD::Instrument::Expectation.set(metric_name, value, client: client, **options)
138
139
  assert_statsd_expectation(expectation, datagrams: datagrams, client: client, &block)
139
140
  end
140
141
 
@@ -152,6 +153,7 @@ module StatsD
152
153
  def assert_statsd_expectations(expectations, datagrams: nil, client: nil, &block)
153
154
  if datagrams.nil?
154
155
  raise LocalJumpError, "assert_statsd_expectations requires a block" unless block_given?
156
+
155
157
  datagrams = capture_statsd_datagrams_with_exception_handling(client: client, &block)
156
158
  end
157
159
 
@@ -29,7 +29,7 @@ module StatsD
29
29
  end
30
30
 
31
31
  def sample?(sample_rate)
32
- sample_rate == 1 || rand < sample_rate
32
+ sample_rate == 1.0 || rand < sample_rate
33
33
  end
34
34
 
35
35
  def <<(datagram)
@@ -71,8 +71,13 @@ module StatsD
71
71
  self
72
72
  end
73
73
 
74
- def shutdown
74
+ def shutdown(wait = @flush_interval * 2)
75
75
  @interrupted = true
76
+ if @dispatcher_thread&.alive?
77
+ @dispatcher_thread.join(wait)
78
+ else
79
+ flush
80
+ end
76
81
  end
77
82
 
78
83
  private
@@ -200,6 +200,7 @@ module StatsD
200
200
  def increment(name, value = 1, sample_rate: nil, tags: nil, no_prefix: false)
201
201
  sample_rate ||= @default_sample_rate
202
202
  return StatsD::Instrument::VOID unless sample?(sample_rate)
203
+
203
204
  emit(datagram_builder(no_prefix: no_prefix).c(name, value, sample_rate, tags))
204
205
  end
205
206
 
@@ -217,6 +218,7 @@ module StatsD
217
218
 
218
219
  sample_rate ||= @default_sample_rate
219
220
  return StatsD::Instrument::VOID unless sample?(sample_rate)
221
+
220
222
  emit(datagram_builder(no_prefix: no_prefix).ms(name, value, sample_rate, tags))
221
223
  end
222
224
 
@@ -236,6 +238,7 @@ module StatsD
236
238
  def gauge(name, value, sample_rate: nil, tags: nil, no_prefix: false)
237
239
  sample_rate ||= @default_sample_rate
238
240
  return StatsD::Instrument::VOID unless sample?(sample_rate)
241
+
239
242
  emit(datagram_builder(no_prefix: no_prefix).g(name, value, sample_rate, tags))
240
243
  end
241
244
 
@@ -249,6 +252,7 @@ module StatsD
249
252
  def set(name, value, sample_rate: nil, tags: nil, no_prefix: false)
250
253
  sample_rate ||= @default_sample_rate
251
254
  return StatsD::Instrument::VOID unless sample?(sample_rate)
255
+
252
256
  emit(datagram_builder(no_prefix: no_prefix).s(name, value, sample_rate, tags))
253
257
  end
254
258
 
@@ -271,6 +275,7 @@ module StatsD
271
275
 
272
276
  sample_rate ||= @default_sample_rate
273
277
  return StatsD::Instrument::VOID unless sample?(sample_rate)
278
+
274
279
  emit(datagram_builder(no_prefix: no_prefix).d(name, value, sample_rate, tags))
275
280
  end
276
281
 
@@ -288,6 +293,7 @@ module StatsD
288
293
  def histogram(name, value, sample_rate: nil, tags: nil, no_prefix: false)
289
294
  sample_rate ||= @default_sample_rate
290
295
  return StatsD::Instrument::VOID unless sample?(sample_rate)
296
+
291
297
  emit(datagram_builder(no_prefix: no_prefix).h(name, value, sample_rate, tags))
292
298
  end
293
299
 
@@ -5,18 +5,6 @@ module StatsD
5
5
  # @note This class is part of the new Client implementation that is intended
6
6
  # to become the new default in the next major release of this library.
7
7
  class DatagramBuilder
8
- unless Regexp.method_defined?(:match?) # for ruby 2.3
9
- module RubyBackports
10
- refine Regexp do
11
- def match?(str)
12
- match(str) != nil
13
- end
14
- end
15
- end
16
-
17
- using(RubyBackports)
18
- end
19
-
20
8
  def self.unsupported_datagram_types(*types)
21
9
  types.each do |type|
22
10
  define_method(type) do |_, _, _, _|
@@ -79,10 +67,12 @@ module StatsD
79
67
  # @return [Array<String>, nil] the list of tags in canonical form.
80
68
  def normalize_tags(tags)
81
69
  return [] unless tags
70
+
82
71
  tags = tags.map { |k, v| "#{k}:#{v}" } if tags.is_a?(Hash)
83
72
 
84
73
  # Fast path when no string replacement is needed
85
74
  return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
75
+
86
76
  tags.map { |tag| tag.tr("|,", "") }
87
77
  end
88
78
 
@@ -90,6 +80,7 @@ module StatsD
90
80
  def normalize_name(name)
91
81
  # Fast path when no normalization is needed to avoid copying the string
92
82
  return name unless /[:|@]/.match?(name)
83
+
93
84
  name.tr(":|@", "_")
94
85
  end
95
86
 
@@ -32,9 +32,10 @@ module StatsD
32
32
 
33
33
  attr_accessor :times, :type, :name, :value, :sample_rate, :tags
34
34
 
35
- def initialize(client: StatsD.singleton_client, type:, name:, value: nil,
35
+ def initialize(client: nil, type:, name:, value: nil,
36
36
  sample_rate: nil, tags: nil, no_prefix: false, times: 1)
37
37
 
38
+ client ||= StatsD.singleton_client
38
39
  @type = type
39
40
  @name = no_prefix || !client.prefix ? name : "#{client.prefix}.#{name}"
40
41
  @value = normalized_value_for_type(type, value) if value
@@ -78,19 +79,6 @@ module StatsD
78
79
 
79
80
  private
80
81
 
81
- # Needed for normalize_tags
82
- unless Regexp.method_defined?(:match?) # for ruby 2.3
83
- module RubyBackports
84
- refine Regexp do
85
- def match?(str)
86
- (self =~ str) != nil
87
- end
88
- end
89
- end
90
-
91
- using(RubyBackports)
92
- end
93
-
94
82
  # @private
95
83
  #
96
84
  # Utility function to convert tags to the canonical form.
@@ -105,10 +93,12 @@ module StatsD
105
93
  # to ensure that this logic matches the logic of the active datagram builder.
106
94
  def normalize_tags(tags)
107
95
  return [] unless tags
96
+
108
97
  tags = tags.map { |k, v| "#{k}:#{v}" } if tags.is_a?(Hash)
109
98
 
110
99
  # Fast path when no string replacement is needed
111
100
  return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
101
+
112
102
  tags.map { |tag| tag.tr("|,", "") }
113
103
  end
114
104
  end
@@ -10,6 +10,22 @@ module StatsD
10
10
 
11
11
  # For backwards compatibility
12
12
  alias_method :capture_statsd_calls, :capture_statsd_datagrams
13
+
14
+ def self.add_tag(tags, key, value)
15
+ tags = tags.dup || {}
16
+
17
+ if tags.is_a?(String)
18
+ tags = tags.empty? ? "#{key}:#{value}" : "#{tags},#{key}:#{value}"
19
+ elsif tags.is_a?(Array)
20
+ tags << "#{key}:#{value}"
21
+ elsif tags.is_a?(Hash)
22
+ tags[key] = value
23
+ else
24
+ raise ArgumentError, "add_tag only supports string, array or hash, #{tags.class} provided"
25
+ end
26
+
27
+ tags
28
+ end
13
29
  end
14
30
  end
15
31
  end
@@ -49,7 +49,7 @@ module StatsD
49
49
  raise RSpec::Expectations::ExpectationNotMetError, "No StatsD calls for metric #{metric_name} were made."
50
50
  elsif options[:times] && options[:times] != metrics.length
51
51
  raise RSpec::Expectations::ExpectationNotMetError, "The numbers of StatsD calls for metric " \
52
- "#{metric_name} was unexpected. Expected #{options[:times].inspect}, got #{metrics.length}"
52
+ "#{metric_name} was unexpected. Expected #{options[:times].inspect}, got #{metrics.length}"
53
53
  end
54
54
 
55
55
  [:sample_rate, :value, :tags].each do |expectation|
@@ -19,7 +19,7 @@ module RuboCop
19
19
 
20
20
  MSG = "Do not use the return value of StatsD metric methods"
21
21
 
22
- INVALID_PARENTS = %i{lvasgn array pair send return yield}
22
+ INVALID_PARENTS = [:lvasgn, :array, :pair, :send, :return, :yield]
23
23
 
24
24
  def on_send(node)
25
25
  if metric_method?(node) && node.arguments.last&.type != :block_pass
@@ -41,7 +41,7 @@ module RuboCop
41
41
  end
42
42
 
43
43
  def autocorrect(node)
44
- -> (corrector) do
44
+ ->(corrector) do
45
45
  positional_arguments = if node.arguments.last.type == :block_pass
46
46
  node.arguments[2...node.arguments.length - 1]
47
47
  else
@@ -3,34 +3,13 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module StatsD
6
- METRIC_METHODS = %i{
7
- increment
8
- gauge
9
- measure
10
- set
11
- histogram
12
- distribution
13
- key_value
14
- }
6
+ METRIC_METHODS = [:increment, :gauge, :measure, :set, :histogram, :distribution, :key_value]
15
7
 
16
- METAPROGRAMMING_METHODS = %i{
17
- statsd_measure
18
- statsd_distribution
19
- statsd_count_success
20
- statsd_count_if
21
- statsd_count
22
- }
8
+ METAPROGRAMMING_METHODS = [:statsd_measure, :statsd_distribution, :statsd_count_success, :statsd_count_if,
9
+ :statsd_count,]
23
10
 
24
- SINGLETON_CONFIGURATION_METHODS = %i{
25
- backend
26
- backend=
27
- prefix
28
- prefix=
29
- default_tags
30
- default_tags=
31
- default_sample_rate
32
- default_sample_rate=
33
- }
11
+ SINGLETON_CONFIGURATION_METHODS = [:backend, :"backend=", :prefix, :"prefix=", :default_tags, :"default_tags=",
12
+ :default_sample_rate, :"default_sample_rate=",]
34
13
 
35
14
  private
36
15
 
@@ -60,6 +39,7 @@ module RuboCop
60
39
 
61
40
  def keyword_arguments(node)
62
41
  return nil if node.arguments.empty?
42
+
63
43
  last_argument = if node.arguments.last&.type == :block_pass
64
44
  node.arguments[node.arguments.length - 2]
65
45
  else
@@ -11,6 +11,7 @@ module StatsD
11
11
 
12
12
  def normalize_tags(tags)
13
13
  raise NotImplementedError, "#{self.class.name} does not support tags" if tags
14
+
14
15
  super
15
16
  end
16
17
  end
@@ -27,6 +27,7 @@ module StatsD
27
27
  def increment(key, value = 1, sample_rate: nil, tags: nil, no_prefix: false)
28
28
  raise ArgumentError, "StatsD.increment does not accept a block" if block_given?
29
29
  raise ArgumentError, "The value argument should be an integer, got #{value.inspect}" unless value.is_a?(Integer)
30
+
30
31
  check_tags_and_sample_rate(sample_rate, tags)
31
32
 
32
33
  super
@@ -35,6 +36,7 @@ module StatsD
35
36
  def gauge(key, value, sample_rate: nil, tags: nil, no_prefix: false)
36
37
  raise ArgumentError, "StatsD.increment does not accept a block" if block_given?
37
38
  raise ArgumentError, "The value argument should be an integer, got #{value.inspect}" unless value.is_a?(Numeric)
39
+
38
40
  check_tags_and_sample_rate(sample_rate, tags)
39
41
 
40
42
  super
@@ -43,6 +45,7 @@ module StatsD
43
45
  def histogram(key, value, sample_rate: nil, tags: nil, no_prefix: false)
44
46
  raise ArgumentError, "StatsD.increment does not accept a block" if block_given?
45
47
  raise ArgumentError, "The value argument should be an integer, got #{value.inspect}" unless value.is_a?(Numeric)
48
+
46
49
  check_tags_and_sample_rate(sample_rate, tags)
47
50
 
48
51
  super
@@ -50,6 +53,7 @@ module StatsD
50
53
 
51
54
  def set(key, value, sample_rate: nil, tags: nil, no_prefix: false)
52
55
  raise ArgumentError, "StatsD.set does not accept a block" if block_given?
56
+
53
57
  check_tags_and_sample_rate(sample_rate, tags)
54
58
 
55
59
  super
@@ -20,19 +20,17 @@ module StatsD
20
20
  end
21
21
 
22
22
  def sample?(sample_rate)
23
- sample_rate == 1 || rand < sample_rate
23
+ sample_rate == 1.0 || rand < sample_rate
24
24
  end
25
25
 
26
26
  def <<(datagram)
27
27
  with_socket { |socket| socket.send(datagram, 0) }
28
28
  self
29
-
30
29
  rescue ThreadError
31
30
  # In cases where a TERM or KILL signal has been sent, and we send stats as
32
31
  # part of a signal handler, locks cannot be acquired, so we do our best
33
32
  # to try and send the datagram without a lock.
34
33
  socket.send(datagram, 0) > 0
35
-
36
34
  rescue SocketError, IOError, SystemCallError => error
37
35
  StatsD.logger.debug do
38
36
  "[StatsD::Instrument::UDPSink] Resetting connection because of #{error.class}: #{error.message}"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module StatsD
4
4
  module Instrument
5
- VERSION = "3.1.0"
5
+ VERSION = "3.2.0"
6
6
  end
7
7
  end
@@ -104,24 +104,26 @@ module StatsD
104
104
  # @param method (see #statsd_measure)
105
105
  # @param name (see #statsd_measure)
106
106
  # @param metric_options (see #statsd_measure)
107
+ # @param tag_error_class add a <tt>error_class</tt> tag with the error class when an error is thrown
107
108
  # @yield You can pass a block to this method if you want to define yourself what is a successful call
108
109
  # based on the return value of the method.
109
110
  # @yieldparam result The return value of the instrumented method.
110
111
  # @yieldreturn [Boolean] Return true iff the return value is considered a success, false otherwise.
111
112
  # @return [void]
112
113
  # @see #statsd_count_if
113
- def statsd_count_success(method, name, sample_rate: nil, tags: nil, no_prefix: false, client: nil)
114
+ def statsd_count_success(method, name, sample_rate: nil,
115
+ tags: nil, no_prefix: false, client: nil, tag_error_class: false)
114
116
  add_to_method(method, name, :count_success) do
115
117
  define_method(method) do |*args, &block|
116
118
  truthiness = result = super(*args, &block)
117
- rescue
119
+ rescue => error
118
120
  truthiness = false
119
121
  raise
120
122
  else
121
123
  if block_given?
122
124
  begin
123
125
  truthiness = yield(result)
124
- rescue
126
+ rescue => error
125
127
  truthiness = false
126
128
  end
127
129
  end
@@ -130,6 +132,9 @@ module StatsD
130
132
  client ||= StatsD.singleton_client
131
133
  suffix = truthiness == false ? "failure" : "success"
132
134
  key = StatsD::Instrument.generate_metric_name(name, self, *args)
135
+
136
+ tags = Helpers.add_tag(tags, :error_class, error.class.name) if tag_error_class && error
137
+
133
138
  client.increment("#{key}.#{suffix}", sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
134
139
  end
135
140
  end
@@ -440,4 +440,17 @@ class AssertionsTest < Minitest::Test
440
440
  StatsD.increment("incr", no_prefix: true)
441
441
  end
442
442
  end
443
+
444
+ def test_client_propagation_to_expectations
445
+ foo_1_metric = StatsD::Instrument::Expectation.increment("foo")
446
+ @test_case.assert_statsd_expectations([foo_1_metric]) do
447
+ StatsD.increment("foo")
448
+ end
449
+
450
+ client = StatsD::Instrument::Client.new(prefix: "prefix")
451
+ foo_2_metric = StatsD::Instrument::Expectation.increment("foo", client: client)
452
+ @test_case.assert_statsd_expectations([foo_2_metric]) do
453
+ StatsD.increment("prefix.foo")
454
+ end
455
+ end
443
456
  end
@@ -16,7 +16,7 @@ class DatagramBuilderTest < Minitest::Test
16
16
 
17
17
  def test_normalize_unsupported_tag_names
18
18
  assert_equal(["ign#ored"], @datagram_builder.send(:normalize_tags, ["ign#o|re,d"]))
19
- # Note: how this is interpreted by the backend is undefined.
19
+ # NOTE: how this is interpreted by the backend is undefined.
20
20
  # We rely on the user to not do stuff like this if they don't want to be surprised.
21
21
  # We do not want to take the performance hit of normalizing this.
22
22
  assert_equal(["lol::class:omg::lol"], @datagram_builder.send(:normalize_tags, "lol::class" => "omg::lol"))
data/test/helpers_test.rb CHANGED
@@ -35,8 +35,47 @@ class HelpersTest < Minitest::Test
35
35
  StatsD.gauge("gauge", 15)
36
36
 
37
37
  assert_equal(2, metrics.length)
38
-
39
38
  ensure
40
39
  StatsD.singleton_client = @old_client
41
40
  end
41
+
42
+ def test_add_tag_works_for_nil
43
+ assert_equal({ key: 123 }, StatsD::Instrument::Helpers.add_tag(nil, :key, 123))
44
+ end
45
+
46
+ def test_add_tag_works_for_hashes
47
+ assert_equal({ key: 123 }, StatsD::Instrument::Helpers.add_tag({}, :key, 123))
48
+
49
+ existing = { existing: 123 }
50
+ assert_equal({ existing: 123, new: 456 }, StatsD::Instrument::Helpers.add_tag(existing, :new, 456))
51
+
52
+ # ensure we do not modify the existing tags
53
+ assert_equal({ existing: 123 }, existing)
54
+ end
55
+
56
+ def test_add_tag_works_for_arrays
57
+ assert_equal(["key:123"], StatsD::Instrument::Helpers.add_tag([], :key, 123))
58
+
59
+ existing = ["existing:123"]
60
+ assert_equal(["existing:123", "new:456"], StatsD::Instrument::Helpers.add_tag(existing, :new, 456))
61
+
62
+ # ensure we do not modify the existing tags
63
+ assert_equal(["existing:123"], existing)
64
+ end
65
+
66
+ def test_add_tag_works_for_strings
67
+ assert_equal("key:123", StatsD::Instrument::Helpers.add_tag("", :key, 123))
68
+
69
+ existing = "existing:123"
70
+ assert_equal("existing:123,new:456", StatsD::Instrument::Helpers.add_tag(existing, :new, 456))
71
+
72
+ # ensure we do not modify the existing tags
73
+ assert_equal("existing:123", existing)
74
+ end
75
+
76
+ def test_add_tags_raises_for_other
77
+ assert_raises(ArgumentError, "add_tag only supports string, array or hash, Integer provided") do
78
+ StatsD::Instrument::Helpers.add_tag(1, :key, 123)
79
+ end
80
+ end
42
81
  end
@@ -159,6 +159,38 @@ class StatsDInstrumentationTest < Minitest::Test
159
159
  ActiveMerchant::UniqueGateway.statsd_remove_count_success(:ssl_post, "ActiveMerchant.Gateway")
160
160
  end
161
161
 
162
+ def test_statsd_count_success_tag_error_class
163
+ ActiveMerchant::Base.statsd_count_success(:ssl_post, "ActiveMerchant.Base", tag_error_class: true)
164
+
165
+ assert_statsd_increment("ActiveMerchant.Base.success", tags: nil) do
166
+ ActiveMerchant::Base.new.ssl_post(true)
167
+ end
168
+
169
+ assert_statsd_increment("ActiveMerchant.Base.failure", tags: ["error_class:RuntimeError"]) do
170
+ assert_raises(RuntimeError, "Not OK") do
171
+ ActiveMerchant::Base.new.ssl_post(false)
172
+ end
173
+ end
174
+ ensure
175
+ ActiveMerchant::Base.statsd_remove_count_success(:ssl_post, "ActiveMerchant.Base")
176
+ end
177
+
178
+ def test_statsd_count_success_tag_error_class_is_opt_in
179
+ ActiveMerchant::Base.statsd_count_success(:ssl_post, "ActiveMerchant.Base")
180
+
181
+ assert_statsd_increment("ActiveMerchant.Base.success", tags: nil) do
182
+ ActiveMerchant::Base.new.ssl_post(true)
183
+ end
184
+
185
+ assert_statsd_increment("ActiveMerchant.Base.failure", tags: nil) do
186
+ assert_raises(RuntimeError, "Not OK") do
187
+ ActiveMerchant::Base.new.ssl_post(false)
188
+ end
189
+ end
190
+ ensure
191
+ ActiveMerchant::Base.statsd_remove_count_success(:ssl_post, "ActiveMerchant.Base")
192
+ end
193
+
162
194
  def test_statsd_count
163
195
  ActiveMerchant::Gateway.statsd_count(:ssl_post, "ActiveMerchant.Gateway.ssl_post")
164
196
 
@@ -345,7 +377,7 @@ class StatsDInstrumentationTest < Minitest::Test
345
377
  client = StatsD::Instrument::Client.new(prefix: "prefix")
346
378
 
347
379
  ActiveMerchant::Gateway.statsd_count(:ssl_post, "ActiveMerchant.Gateway.ssl_post", client: client)
348
- assert_statsd_increment("prefix.ActiveMerchant.Gateway.ssl_post", client: client) do
380
+ assert_statsd_increment("ActiveMerchant.Gateway.ssl_post", client: client) do
349
381
  ActiveMerchant::Gateway.new.purchase(true)
350
382
  end
351
383
  ensure
@@ -69,6 +69,32 @@ module UDPSinkTests
69
69
  pass("Fork is not implemented on #{RUBY_PLATFORM}")
70
70
  end
71
71
 
72
+ def test_sends_datagram_before_exit
73
+ udp_sink = build_sink(@host, @port)
74
+ fork do
75
+ udp_sink << "exiting:1|c"
76
+ Process.exit(0)
77
+ end
78
+
79
+ @receiver.wait_readable(1)
80
+ assert_equal("exiting:1|c", @receiver.recvfrom_nonblock(100).first)
81
+ rescue NotImplementedError
82
+ pass("Fork is not implemented on #{RUBY_PLATFORM}")
83
+ end
84
+
85
+ def test_sends_datagram_when_termed
86
+ udp_sink = build_sink(@host, @port)
87
+ fork do
88
+ udp_sink << "exiting:1|c"
89
+ Process.kill("TERM", Process.pid)
90
+ end
91
+
92
+ @receiver.wait_readable(1)
93
+ assert_equal("exiting:1|c", @receiver.recvfrom_nonblock(100).first)
94
+ rescue NotImplementedError
95
+ pass("Fork is not implemented on #{RUBY_PLATFORM}")
96
+ end
97
+
72
98
  private
73
99
 
74
100
  def build_sink(host = @host, port = @port)
@@ -110,7 +136,7 @@ module UDPSinkTests
110
136
 
111
137
  assert_equal(
112
138
  "[#{@sink_class}] Resetting connection because of " \
113
- "Errno::EDESTADDRREQ: Destination address required\n",
139
+ "Errno::EDESTADDRREQ: Destination address required\n",
114
140
  logs.string,
115
141
  )
116
142
  ensure
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.1.0
4
+ version: 3.2.0
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: 2021-04-09 00:00:00.000000000 Z
13
+ date: 2022-06-22 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: concurrent-ruby
@@ -87,7 +87,6 @@ files:
87
87
  - statsd-instrument.gemspec
88
88
  - test/assertions_test.rb
89
89
  - test/benchmark/clock_gettime.rb
90
- - test/benchmark/default_tags.rb
91
90
  - test/benchmark/metrics.rb
92
91
  - test/benchmark/tags.rb
93
92
  - test/capture_sink_test.rb
@@ -135,14 +134,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
134
  - !ruby/object:Gem::Version
136
135
  version: '0'
137
136
  requirements: []
138
- rubygems_version: 3.0.3
137
+ rubygems_version: 3.3.3
139
138
  signing_key:
140
139
  specification_version: 4
141
140
  summary: A StatsD client for Ruby apps
142
141
  test_files:
143
142
  - test/assertions_test.rb
144
143
  - test/benchmark/clock_gettime.rb
145
- - test/benchmark/default_tags.rb
146
144
  - test/benchmark/metrics.rb
147
145
  - test/benchmark/tags.rb
148
146
  - test/capture_sink_test.rb
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "statsd-instrument"
4
- require "benchmark/ips"
5
-
6
- StatsD.logger = Logger.new(File::NULL)
7
-
8
- class Suite
9
- def warming(*args)
10
- StatsD.default_tags = if args[0] == "with default tags"
11
- { first_tag: "first_value", second_tag: "second_value" }
12
- end
13
- puts "warming with default tags: #{StatsD.default_tags}"
14
- end
15
-
16
- def running(*args)
17
- StatsD.default_tags = if args[0] == "with default tags"
18
- { first_tag: "first_value", second_tag: "second_value" }
19
- end
20
- puts "running with default tags: #{StatsD.default_tags}"
21
- end
22
-
23
- def warmup_stats(*)
24
- end
25
-
26
- def add_report(*)
27
- end
28
- end
29
-
30
- suite = Suite.new
31
-
32
- Benchmark.ips do |bench|
33
- bench.config(suite: suite)
34
- bench.report("without default tags") do
35
- StatsD.increment("GoogleBase.insert", tags: {
36
- first_tag: "first_value",
37
- second_tag: "second_value",
38
- third_tag: "third_value",
39
- })
40
- end
41
-
42
- bench.report("with default tags") do
43
- StatsD.increment("GoogleBase.insert", tags: { third_tag: "third_value" })
44
- end
45
-
46
- bench.compare!
47
- end