statsd-instrument 3.1.0 → 3.2.0

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