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 +4 -4
- data/.github/workflows/benchmark.yml +4 -6
- data/.github/workflows/lint.yml +4 -6
- data/.github/workflows/tests.yml +7 -9
- data/.rubocop.yml +2 -1
- data/CHANGELOG.md +12 -0
- data/Gemfile +1 -1
- data/README.md +4 -4
- data/benchmark/send-metrics-to-dev-null-log +2 -2
- data/benchmark/send-metrics-to-local-udp-receiver +2 -2
- data/lib/statsd/instrument/assertions.rb +8 -6
- data/lib/statsd/instrument/batched_udp_sink.rb +7 -2
- data/lib/statsd/instrument/client.rb +6 -0
- data/lib/statsd/instrument/datagram_builder.rb +3 -12
- data/lib/statsd/instrument/expectation.rb +4 -14
- data/lib/statsd/instrument/helpers.rb +16 -0
- data/lib/statsd/instrument/matchers.rb +1 -1
- data/lib/statsd/instrument/rubocop/metric_return_value.rb +1 -1
- data/lib/statsd/instrument/rubocop/positional_arguments.rb +1 -1
- data/lib/statsd/instrument/rubocop.rb +6 -26
- data/lib/statsd/instrument/statsd_datagram_builder.rb +1 -0
- data/lib/statsd/instrument/strict.rb +4 -0
- data/lib/statsd/instrument/udp_sink.rb +1 -3
- data/lib/statsd/instrument/version.rb +1 -1
- data/lib/statsd/instrument.rb +8 -3
- data/test/assertions_test.rb +13 -0
- data/test/datagram_builder_test.rb +1 -1
- data/test/helpers_test.rb +40 -1
- data/test/statsd_instrumentation_test.rb +33 -1
- data/test/udp_sink_test.rb +27 -1
- metadata +3 -5
- data/test/benchmark/default_tags.rb +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f42d327300430dd5926c971c7d27d860acd99800cc8eeb4552892f755037553f
|
4
|
+
data.tar.gz: 88c22b7ef591313d0e159a24f3875ddc7a63acf0c6d10551cac6bf069a6c0ad3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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-
|
8
|
+
runs-on: ubuntu-latest
|
9
9
|
|
10
10
|
steps:
|
11
11
|
- uses: actions/checkout@v1
|
12
12
|
|
13
|
-
- name:
|
14
|
-
uses:
|
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
|
data/.github/workflows/lint.yml
CHANGED
@@ -5,18 +5,16 @@ on: push
|
|
5
5
|
jobs:
|
6
6
|
test:
|
7
7
|
name: Rubocop
|
8
|
-
runs-on: ubuntu-
|
8
|
+
runs-on: ubuntu-latest
|
9
9
|
|
10
10
|
steps:
|
11
11
|
- uses: actions/checkout@v1
|
12
12
|
|
13
|
-
- name:
|
14
|
-
uses:
|
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
|
data/.github/workflows/tests.yml
CHANGED
@@ -4,12 +4,12 @@ on: push
|
|
4
4
|
|
5
5
|
jobs:
|
6
6
|
test:
|
7
|
-
name: Ruby ${{ matrix.ruby }} on ubuntu-
|
8
|
-
runs-on: ubuntu-
|
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:
|
23
|
-
uses:
|
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
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
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 (
|
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
|
14
|
-
project](
|
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,
|
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
|
-
|
10
|
-
branch = if 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
|
-
|
12
|
-
branch = if 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:
|
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
|
-
|
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 =
|
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
|
@@ -3,34 +3,13 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module StatsD
|
6
|
-
METRIC_METHODS =
|
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 =
|
17
|
-
|
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 =
|
25
|
-
|
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
|
@@ -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}"
|
data/lib/statsd/instrument.rb
CHANGED
@@ -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,
|
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
|
data/test/assertions_test.rb
CHANGED
@@ -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
|
-
#
|
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("
|
380
|
+
assert_statsd_increment("ActiveMerchant.Gateway.ssl_post", client: client) do
|
349
381
|
ActiveMerchant::Gateway.new.purchase(true)
|
350
382
|
end
|
351
383
|
ensure
|
data/test/udp_sink_test.rb
CHANGED
@@ -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
|
-
|
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.
|
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:
|
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.
|
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
|