statsd-instrument 2.3.2 → 2.6.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/CODEOWNERS +1 -0
- data/.github/workflows/benchmark.yml +32 -0
- data/.github/workflows/ci.yml +47 -0
- data/.gitignore +1 -0
- data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +1027 -0
- data/.rubocop.yml +50 -0
- data/.yardopts +5 -0
- data/CHANGELOG.md +288 -2
- data/CONTRIBUTING.md +28 -6
- data/Gemfile +5 -0
- data/README.md +54 -46
- data/Rakefile +4 -2
- data/benchmark/README.md +29 -0
- data/benchmark/datagram-client +41 -0
- data/benchmark/send-metrics-to-dev-null-log +47 -0
- data/benchmark/send-metrics-to-local-udp-receiver +57 -0
- data/lib/statsd/instrument/assertions.rb +179 -30
- data/lib/statsd/instrument/backend.rb +3 -2
- data/lib/statsd/instrument/backends/capture_backend.rb +4 -1
- data/lib/statsd/instrument/backends/logger_backend.rb +3 -3
- data/lib/statsd/instrument/backends/null_backend.rb +2 -0
- data/lib/statsd/instrument/backends/udp_backend.rb +39 -45
- data/lib/statsd/instrument/capture_sink.rb +27 -0
- data/lib/statsd/instrument/client.rb +313 -0
- data/lib/statsd/instrument/datagram.rb +75 -0
- data/lib/statsd/instrument/datagram_builder.rb +101 -0
- data/lib/statsd/instrument/dogstatsd_datagram_builder.rb +71 -0
- data/lib/statsd/instrument/environment.rb +108 -29
- data/lib/statsd/instrument/helpers.rb +16 -8
- data/lib/statsd/instrument/log_sink.rb +24 -0
- data/lib/statsd/instrument/matchers.rb +14 -11
- data/lib/statsd/instrument/metric.rb +72 -45
- data/lib/statsd/instrument/metric_expectation.rb +32 -18
- data/lib/statsd/instrument/null_sink.rb +13 -0
- data/lib/statsd/instrument/railtie.rb +2 -1
- data/lib/statsd/instrument/rubocop/measure_as_dist_argument.rb +39 -0
- data/lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb +42 -0
- data/lib/statsd/instrument/rubocop/metric_prefix_argument.rb +37 -0
- data/lib/statsd/instrument/rubocop/metric_return_value.rb +32 -0
- data/lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb +36 -0
- data/lib/statsd/instrument/rubocop/positional_arguments.rb +99 -0
- data/lib/statsd/instrument/rubocop/splat_arguments.rb +31 -0
- data/lib/statsd/instrument/rubocop.rb +64 -0
- data/lib/statsd/instrument/statsd_datagram_builder.rb +14 -0
- data/lib/statsd/instrument/strict.rb +235 -0
- data/lib/statsd/instrument/udp_sink.rb +62 -0
- data/lib/statsd/instrument/version.rb +3 -1
- data/lib/statsd/instrument.rb +340 -163
- data/lib/statsd-instrument.rb +2 -0
- data/statsd-instrument.gemspec +13 -10
- data/test/assertions_test.rb +167 -156
- data/test/benchmark/clock_gettime.rb +27 -0
- data/test/benchmark/default_tags.rb +47 -0
- data/test/benchmark/metrics.rb +9 -8
- data/test/benchmark/tags.rb +5 -3
- data/test/capture_backend_test.rb +4 -2
- data/test/capture_sink_test.rb +44 -0
- data/test/client_test.rb +164 -0
- data/test/compatibility/dogstatsd_datagram_compatibility_test.rb +162 -0
- data/test/datagram_builder_test.rb +120 -0
- data/test/deprecations_test.rb +132 -0
- data/test/dogstatsd_datagram_builder_test.rb +32 -0
- data/test/environment_test.rb +75 -8
- data/test/helpers/rubocop_helper.rb +47 -0
- data/test/helpers_test.rb +2 -1
- data/test/integration_test.rb +31 -7
- data/test/log_sink_test.rb +37 -0
- data/test/logger_backend_test.rb +10 -8
- data/test/matchers_test.rb +42 -28
- data/test/metric_test.rb +18 -22
- data/test/null_sink_test.rb +13 -0
- data/test/rubocop/measure_as_dist_argument_test.rb +44 -0
- data/test/rubocop/metaprogramming_positional_arguments_test.rb +58 -0
- data/test/rubocop/metric_prefix_argument_test.rb +38 -0
- data/test/rubocop/metric_return_value_test.rb +78 -0
- data/test/rubocop/metric_value_keyword_argument_test.rb +39 -0
- data/test/rubocop/positional_arguments_test.rb +110 -0
- data/test/rubocop/splat_arguments_test.rb +27 -0
- data/test/statsd_datagram_builder_test.rb +22 -0
- data/test/statsd_instrumentation_test.rb +109 -100
- data/test/statsd_test.rb +113 -79
- data/test/test_helper.rb +12 -1
- data/test/udp_backend_test.rb +38 -36
- data/test/udp_sink_test.rb +85 -0
- metadata +85 -5
- data/.travis.yml +0 -12
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'benchmark/ips'
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
revision = %x(git rev-parse HEAD).rstrip
|
9
|
+
master_revision = %x(git rev-parse origin/master).rstrip
|
10
|
+
branch = if revision == master_revision
|
11
|
+
'master'
|
12
|
+
else
|
13
|
+
%x(git rev-parse --abbrev-ref HEAD).rstrip
|
14
|
+
end
|
15
|
+
|
16
|
+
intermediate_results_filename = "#{Dir.tmpdir}/statsd-instrument-benchmarks/#{File.basename($PROGRAM_NAME)}"
|
17
|
+
FileUtils.mkdir_p(File.dirname(intermediate_results_filename))
|
18
|
+
|
19
|
+
ENV['ENV'] = "development"
|
20
|
+
require 'statsd-instrument'
|
21
|
+
StatsD.logger = Logger.new(File::NULL)
|
22
|
+
|
23
|
+
report = Benchmark.ips do |bench|
|
24
|
+
bench.report("StatsD metrics to /dev/null log (branch: #{branch}, sha: #{revision[0, 7]})") do
|
25
|
+
StatsD.increment('StatsD.increment', 10, sample_rate: 15)
|
26
|
+
StatsD.measure('StatsD.measure') { 1 + 1 }
|
27
|
+
StatsD.gauge('StatsD.gauge', 12.0, tags: ["foo:bar", "quc"])
|
28
|
+
StatsD.set('StatsD.set', 'value', tags: { foo: 'bar', baz: 'quc' })
|
29
|
+
StatsD.event('StasD.event', "12345")
|
30
|
+
StatsD.service_check("StatsD.service_check", "ok")
|
31
|
+
end
|
32
|
+
|
33
|
+
# Store the results in between runs
|
34
|
+
bench.save!(intermediate_results_filename)
|
35
|
+
bench.compare!
|
36
|
+
end
|
37
|
+
|
38
|
+
if report.entries.length == 1
|
39
|
+
puts
|
40
|
+
puts "To compare the performance of this revision against another revision (e.g. master),"
|
41
|
+
puts "check out a different branch and run this benchmark script again."
|
42
|
+
elsif ENV['KEEP_RESULTS']
|
43
|
+
puts
|
44
|
+
puts "The intermediate results have been stored in #{intermediate_results_filename}"
|
45
|
+
else
|
46
|
+
File.unlink(intermediate_results_filename)
|
47
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'benchmark/ips'
|
6
|
+
require 'socket'
|
7
|
+
|
8
|
+
revision = %x(git rev-parse HEAD).rstrip
|
9
|
+
master_revision = %x(git rev-parse origin/master).rstrip
|
10
|
+
branch = if revision == master_revision
|
11
|
+
'master'
|
12
|
+
else
|
13
|
+
%x(git rev-parse --abbrev-ref HEAD).rstrip
|
14
|
+
end
|
15
|
+
|
16
|
+
intermediate_results_filename = "#{Dir.tmpdir}/statsd-instrument-benchmarks/#{File.basename($PROGRAM_NAME)}"
|
17
|
+
FileUtils.mkdir_p(File.dirname(intermediate_results_filename))
|
18
|
+
|
19
|
+
# Set up an UDP listener to which we can send StatsD packets
|
20
|
+
receiver = UDPSocket.new
|
21
|
+
receiver.bind('localhost', 0)
|
22
|
+
|
23
|
+
ENV['ENV'] = "production"
|
24
|
+
ENV['STATSD_ADDR'] = "#{receiver.addr[2]}:#{receiver.addr[1]}"
|
25
|
+
ENV['STATSD_IMPLEMENTATION'] ||= 'datadog'
|
26
|
+
|
27
|
+
require 'statsd-instrument'
|
28
|
+
|
29
|
+
report = Benchmark.ips do |bench|
|
30
|
+
bench.report("StatsD metrics to local UDP receiver (branch: #{branch}, sha: #{revision[0, 7]})") do
|
31
|
+
StatsD.increment('StatsD.increment', 10)
|
32
|
+
StatsD.measure('StatsD.measure') { 1 + 1 }
|
33
|
+
StatsD.gauge('StatsD.gauge', 12.0, tags: ["foo:bar", "quc"])
|
34
|
+
StatsD.set('StatsD.set', 'value', tags: { foo: 'bar', baz: 'quc' })
|
35
|
+
if StatsD.backend.implementation == :datadog
|
36
|
+
StatsD.event('StasD.event', "12345")
|
37
|
+
StatsD.service_check("StatsD.service_check", "ok")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Store the results in between runs
|
42
|
+
bench.save!(intermediate_results_filename)
|
43
|
+
bench.compare!
|
44
|
+
end
|
45
|
+
|
46
|
+
receiver.close
|
47
|
+
|
48
|
+
if report.entries.length == 1
|
49
|
+
puts
|
50
|
+
puts "To compare the performance of this revision against another revision (e.g. master),"
|
51
|
+
puts "check out a different branch and run this benchmark script again."
|
52
|
+
elsif ENV['KEEP_RESULTS']
|
53
|
+
puts
|
54
|
+
puts "The intermediate results have been stored in #{intermediate_results_filename}"
|
55
|
+
else
|
56
|
+
File.unlink(intermediate_results_filename)
|
57
|
+
end
|
@@ -1,79 +1,228 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This module defines several assertion methods that can be used to verify that
|
4
|
+
# your application is emitting the right StatsD metrics.
|
5
|
+
#
|
6
|
+
# Every metric type has its own assertion method, like {#assert_statsd_increment}
|
7
|
+
# to assert `StatsD.increment` calls. You can also assert other properties of the
|
8
|
+
# metric that was emitted, lioke the sample rate or presence of tags.
|
9
|
+
# To check for the absence of metrics, use {#assert_no_statsd_calls}.
|
10
|
+
#
|
11
|
+
# @example Check for metric properties:
|
12
|
+
# assert_statsd_measure('foo', sample_rate: 0.1, tags: ["bar"]) do
|
13
|
+
# StatsD.measure('foo', sample_rate: 0.5, tags: ['bar','baz']) do
|
14
|
+
# some_code_to_measure
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @example Check for multiple occurrences:
|
19
|
+
# assert_statsd_increment('foo', times: 2) do
|
20
|
+
# StatsD.increment('foo')
|
21
|
+
# StatsD.increment('foo')
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @example Absence of metrics
|
25
|
+
# assert_no_statsd_calls do
|
26
|
+
# foo
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @example Handling exceptions
|
30
|
+
# assert_statsd_increment('foo.error') do
|
31
|
+
# # If we expect exceptions to occur, we have to handle them inside
|
32
|
+
# # the block we pass to assert_statsd_increment.
|
33
|
+
# assert_raises(RuntimeError) do
|
34
|
+
# begin
|
35
|
+
# attempt_foo
|
36
|
+
# rescue
|
37
|
+
# StatsD.increment('foo.error')
|
38
|
+
# raise 'foo failed'
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
# end
|
1
42
|
module StatsD::Instrument::Assertions
|
2
43
|
include StatsD::Instrument::Helpers
|
3
44
|
|
45
|
+
# Asserts no metric occurred during the execution of the provided block.
|
46
|
+
#
|
47
|
+
# @param [String] metric_name (default: nil) The metric name that is not allowed
|
48
|
+
# to happen inside the block. If this is set to `nil`, the assertion will fail
|
49
|
+
# if any metric occurs.
|
50
|
+
# @yield A block in which the specified metric should not occur. This block
|
51
|
+
# should not raise any exceptions.
|
52
|
+
# @return [void]
|
53
|
+
# @raise [Minitest::Assertion] If an exception occurs, or if any metric (with the
|
54
|
+
# provided name, or any), occurred during the execution of the provided block.
|
4
55
|
def assert_no_statsd_calls(metric_name = nil, &block)
|
5
56
|
metrics = capture_statsd_calls(&block)
|
6
57
|
metrics.select! { |m| m.name == metric_name } if metric_name
|
7
|
-
assert
|
58
|
+
assert(metrics.empty?, "No StatsD calls for metric #{metrics.map(&:name).join(', ')} expected.")
|
59
|
+
rescue => exception
|
60
|
+
flunk(<<~MESSAGE)
|
61
|
+
An exception occurred in the block provided to the StatsD assertion.
|
62
|
+
|
63
|
+
#{exception.class.name}: #{exception.message}
|
64
|
+
\t#{exception.backtrace.join("\n\t")}
|
65
|
+
|
66
|
+
If this exception is expected, make sure to handle it using `assert_raises`
|
67
|
+
inside the block provided to the StatsD assertion.
|
68
|
+
MESSAGE
|
8
69
|
end
|
9
70
|
|
71
|
+
# Asserts that a given counter metric occurred inside the provided block.
|
72
|
+
#
|
73
|
+
# @param [String] metric_name The name of the metric that should occur.
|
74
|
+
# @param [Hash] options (see StatsD::Instrument::MetricExpectation.new)
|
75
|
+
# @yield A block in which the specified metric should occur. This block
|
76
|
+
# should not raise any exceptions.
|
77
|
+
# @return [void]
|
78
|
+
# @raise [Minitest::Assertion] If an exception occurs, or if the metric did
|
79
|
+
# not occur as specified during the execution the block.
|
10
80
|
def assert_statsd_increment(metric_name, options = {}, &block)
|
11
81
|
assert_statsd_call(:c, metric_name, options, &block)
|
12
82
|
end
|
13
83
|
|
84
|
+
# Asserts that a given timing metric occurred inside the provided block.
|
85
|
+
#
|
86
|
+
# @param metric_name (see #assert_statsd_increment)
|
87
|
+
# @param options (see #assert_statsd_increment)
|
88
|
+
# @yield (see #assert_statsd_increment)
|
89
|
+
# @return [void]
|
90
|
+
# @raise (see #assert_statsd_increment)
|
14
91
|
def assert_statsd_measure(metric_name, options = {}, &block)
|
15
92
|
assert_statsd_call(:ms, metric_name, options, &block)
|
16
93
|
end
|
17
94
|
|
95
|
+
# Asserts that a given gauge metric occurred inside the provided block.
|
96
|
+
#
|
97
|
+
# @param metric_name (see #assert_statsd_increment)
|
98
|
+
# @param options (see #assert_statsd_increment)
|
99
|
+
# @yield (see #assert_statsd_increment)
|
100
|
+
# @return [void]
|
101
|
+
# @raise (see #assert_statsd_increment)
|
18
102
|
def assert_statsd_gauge(metric_name, options = {}, &block)
|
19
103
|
assert_statsd_call(:g, metric_name, options, &block)
|
20
104
|
end
|
21
105
|
|
106
|
+
# Asserts that a given histogram metric occurred inside the provided block.
|
107
|
+
#
|
108
|
+
# @param metric_name (see #assert_statsd_increment)
|
109
|
+
# @param options (see #assert_statsd_increment)
|
110
|
+
# @yield (see #assert_statsd_increment)
|
111
|
+
# @return [void]
|
112
|
+
# @raise (see #assert_statsd_increment)
|
22
113
|
def assert_statsd_histogram(metric_name, options = {}, &block)
|
23
114
|
assert_statsd_call(:h, metric_name, options, &block)
|
24
115
|
end
|
25
116
|
|
117
|
+
# Asserts that a given distribution metric occurred inside the provided block.
|
118
|
+
#
|
119
|
+
# @param metric_name (see #assert_statsd_increment)
|
120
|
+
# @param options (see #assert_statsd_increment)
|
121
|
+
# @yield (see #assert_statsd_increment)
|
122
|
+
# @return [void]
|
123
|
+
# @raise (see #assert_statsd_increment)
|
26
124
|
def assert_statsd_distribution(metric_name, options = {}, &block)
|
27
125
|
assert_statsd_call(:d, metric_name, options, &block)
|
28
126
|
end
|
29
127
|
|
128
|
+
# Asserts that a given set metric occurred inside the provided block.
|
129
|
+
#
|
130
|
+
# @param metric_name (see #assert_statsd_increment)
|
131
|
+
# @param options (see #assert_statsd_increment)
|
132
|
+
# @yield (see #assert_statsd_increment)
|
133
|
+
# @return [void]
|
134
|
+
# @raise (see #assert_statsd_increment)
|
30
135
|
def assert_statsd_set(metric_name, options = {}, &block)
|
31
136
|
assert_statsd_call(:s, metric_name, options, &block)
|
32
137
|
end
|
33
138
|
|
139
|
+
# Asserts that a given key/value metric occurred inside the provided block.
|
140
|
+
#
|
141
|
+
# @param metric_name (see #assert_statsd_increment)
|
142
|
+
# @param options (see #assert_statsd_increment)
|
143
|
+
# @yield (see #assert_statsd_increment)
|
144
|
+
# @return [void]
|
145
|
+
# @raise (see #assert_statsd_increment)
|
34
146
|
def assert_statsd_key_value(metric_name, options = {}, &block)
|
35
147
|
assert_statsd_call(:kv, metric_name, options, &block)
|
36
148
|
end
|
37
149
|
|
150
|
+
# Asserts that the set of provided metric expectations came true.
|
151
|
+
#
|
152
|
+
# Generally, it's recommended to use more specific assertion methods, like
|
153
|
+
# {#assert_statsd_increment} and others.
|
154
|
+
#
|
38
155
|
# @private
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
156
|
+
# @param [Array<StatsD::Instrument::MetricExpectation>] expected_metrics The set of
|
157
|
+
# metric expectations to verify.
|
158
|
+
# @yield (see #assert_statsd_increment)
|
159
|
+
# @return [void]
|
160
|
+
# @raise (see #assert_statsd_increment)
|
161
|
+
def assert_statsd_calls(expected_metrics)
|
162
|
+
raise ArgumentError, "block must be given" unless block_given?
|
163
|
+
|
164
|
+
capture_backend = StatsD::Instrument::Backends::CaptureBackend.new
|
165
|
+
with_capture_backend(capture_backend) do
|
166
|
+
begin
|
167
|
+
yield
|
168
|
+
rescue => exception
|
169
|
+
flunk(<<~MESSAGE)
|
170
|
+
An exception occurred in the block provided to the StatsD assertion.
|
171
|
+
|
172
|
+
#{exception.class.name}: #{exception.message}
|
173
|
+
\t#{exception.backtrace.join("\n\t")}
|
174
|
+
|
175
|
+
If this exception is expected, make sure to handle it using `assert_raises`
|
176
|
+
inside the block provided to the StatsD assertion.
|
177
|
+
MESSAGE
|
178
|
+
end
|
179
|
+
|
180
|
+
metrics = capture_backend.collected_metrics
|
181
|
+
matched_expected_metrics = []
|
182
|
+
expected_metrics.each do |expected_metric|
|
183
|
+
expected_metric_times = expected_metric.times
|
184
|
+
expected_metric_times_remaining = expected_metric.times
|
185
|
+
filtered_metrics = metrics.select { |m| m.type == expected_metric.type && m.name == expected_metric.name }
|
186
|
+
|
187
|
+
if filtered_metrics.empty?
|
188
|
+
flunk("No StatsD calls for metric #{expected_metric.name} of type #{expected_metric.type} were made.")
|
189
|
+
end
|
190
|
+
|
191
|
+
filtered_metrics.each do |metric|
|
192
|
+
next unless expected_metric.matches(metric)
|
193
|
+
|
194
|
+
assert(within_numeric_range?(metric.sample_rate),
|
195
|
+
"Unexpected sample rate type for metric #{metric.name}, must be numeric")
|
196
|
+
|
197
|
+
if expected_metric_times_remaining == 0
|
198
|
+
flunk("Unexpected StatsD call; number of times this metric " \
|
199
|
+
"was expected exceeded: #{expected_metric.inspect}")
|
200
|
+
end
|
43
201
|
|
44
|
-
metrics = capture_statsd_calls(&block)
|
45
|
-
matched_expected_metrics = []
|
46
|
-
|
47
|
-
expected_metrics.each do |expected_metric|
|
48
|
-
expected_metric_times = expected_metric.times
|
49
|
-
expected_metric_times_remaining = expected_metric.times
|
50
|
-
filtered_metrics = metrics.select { |m| m.type == expected_metric.type && m.name == expected_metric.name }
|
51
|
-
assert filtered_metrics.length > 0,
|
52
|
-
"No StatsD calls for metric #{expected_metric.name} of type #{expected_metric.type} were made."
|
53
|
-
|
54
|
-
filtered_metrics.each do |metric|
|
55
|
-
assert within_numeric_range?(metric.sample_rate),
|
56
|
-
"Unexpected sample rate type for metric #{metric.name}, must be numeric"
|
57
|
-
if expected_metric.matches(metric)
|
58
|
-
assert expected_metric_times_remaining > 0,
|
59
|
-
"Unexpected StatsD call; number of times this metric was expected exceeded: #{expected_metric.inspect}"
|
60
202
|
expected_metric_times_remaining -= 1
|
61
203
|
metrics.delete(metric)
|
62
204
|
if expected_metric_times_remaining == 0
|
63
205
|
matched_expected_metrics << expected_metric
|
64
206
|
end
|
65
207
|
end
|
208
|
+
|
209
|
+
next if expected_metric_times_remaining == 0
|
210
|
+
|
211
|
+
msg = +"Metric expected #{expected_metric_times} times but seen " \
|
212
|
+
"#{expected_metric_times - expected_metric_times_remaining} " \
|
213
|
+
"times: #{expected_metric.inspect}."
|
214
|
+
msg << "\nCaptured metrics with the same key: #{filtered_metrics}" if filtered_metrics.any?
|
215
|
+
flunk(msg)
|
66
216
|
end
|
217
|
+
expected_metrics -= matched_expected_metrics
|
67
218
|
|
68
|
-
|
69
|
-
|
70
|
-
" #{
|
71
|
-
|
72
|
-
end
|
73
|
-
expected_metrics -= matched_expected_metrics
|
219
|
+
unless expected_metrics.empty?
|
220
|
+
flunk("Unexpected StatsD calls; the following metric expectations " \
|
221
|
+
"were not satisfied: #{expected_metrics.inspect}")
|
222
|
+
end
|
74
223
|
|
75
|
-
|
76
|
-
|
224
|
+
pass
|
225
|
+
end
|
77
226
|
end
|
78
227
|
|
79
228
|
private
|
@@ -87,6 +236,6 @@ module StatsD::Instrument::Assertions
|
|
87
236
|
end
|
88
237
|
|
89
238
|
def within_numeric_range?(object)
|
90
|
-
object.
|
239
|
+
object.is_a?(Numeric) && (0.0..1.0).cover?(object)
|
91
240
|
end
|
92
241
|
end
|
@@ -1,12 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This abstract class specifies the interface a backend implementation should conform to.
|
2
4
|
# @abstract
|
3
5
|
class StatsD::Instrument::Backend
|
4
|
-
|
5
6
|
# Collects a metric.
|
6
7
|
#
|
7
8
|
# @param metric [StatsD::Instrument::Metric] The metric to collect
|
8
9
|
# @return [void]
|
9
|
-
def collect_metric(
|
10
|
+
def collect_metric(_metric)
|
10
11
|
raise NotImplementedError, "Use a concerete backend implementation"
|
11
12
|
end
|
12
13
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module StatsD::Instrument::Backends
|
3
4
|
# The capture backend is used to capture the metrics that are collected, so you can
|
4
5
|
# run assertions on them.
|
5
6
|
#
|
@@ -8,6 +9,7 @@ module StatsD::Instrument::Backends
|
|
8
9
|
# @see StatsD::Instrument::Assertions
|
9
10
|
class CaptureBackend < StatsD::Instrument::Backend
|
10
11
|
attr_reader :collected_metrics
|
12
|
+
attr_accessor :parent
|
11
13
|
|
12
14
|
def initialize
|
13
15
|
reset
|
@@ -17,6 +19,7 @@ module StatsD::Instrument::Backends
|
|
17
19
|
# @param metric [StatsD::Instrument::Metric] The metric to collect.
|
18
20
|
# @return [void]
|
19
21
|
def collect_metric(metric)
|
22
|
+
parent&.collect_metric(metric)
|
20
23
|
@collected_metrics << metric
|
21
24
|
end
|
22
25
|
|
@@ -1,10 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module StatsD::Instrument::Backends
|
3
4
|
# The logger backend simply logs every metric to a logger
|
4
5
|
# @!attribute logger
|
5
6
|
# @return [Logger]
|
6
7
|
class LoggerBackend < StatsD::Instrument::Backend
|
7
|
-
|
8
8
|
attr_accessor :logger
|
9
9
|
|
10
10
|
def initialize(logger)
|
@@ -14,7 +14,7 @@ module StatsD::Instrument::Backends
|
|
14
14
|
# @param metric [StatsD::Instrument::Metric]
|
15
15
|
# @return [void]
|
16
16
|
def collect_metric(metric)
|
17
|
-
logger.info
|
17
|
+
logger.info("[StatsD] #{metric}")
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -1,63 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'monitor'
|
2
4
|
|
3
5
|
module StatsD::Instrument::Backends
|
4
6
|
class UDPBackend < StatsD::Instrument::Backend
|
5
|
-
|
6
7
|
BASE_SUPPORTED_METRIC_TYPES = { c: true, ms: true, g: true, s: true }
|
7
8
|
|
8
9
|
class DogStatsDProtocol
|
9
|
-
EVENT_OPTIONS = {
|
10
|
-
date_happened: 'd',
|
11
|
-
hostname: 'h',
|
12
|
-
aggregation_key: 'k',
|
13
|
-
priority: 'p',
|
14
|
-
source_type_name: 's',
|
15
|
-
alert_type: 't',
|
16
|
-
}
|
17
|
-
|
18
|
-
SERVICE_CHECK_OPTIONS = {
|
19
|
-
timestamp: 'd',
|
20
|
-
hostname: 'h',
|
21
|
-
message: 'm',
|
22
|
-
}
|
23
|
-
|
24
10
|
SUPPORTED_METRIC_TYPES = BASE_SUPPORTED_METRIC_TYPES.merge(h: true, _e: true, _sc: true, d: true)
|
25
11
|
|
12
|
+
SERVICE_CHECK_STATUSES = { ok: 0, warning: 1, critical: 2, unknown: 3 }
|
13
|
+
|
26
14
|
def generate_packet(metric)
|
27
|
-
packet = ""
|
15
|
+
packet = +""
|
28
16
|
|
29
17
|
if metric.type == :_e
|
30
|
-
escaped_title = metric.name.
|
31
|
-
escaped_text = metric.value.
|
18
|
+
escaped_title = metric.name.gsub("\n", "\\n")
|
19
|
+
escaped_text = metric.value.gsub("\n", "\\n")
|
32
20
|
|
33
21
|
packet << "_e{#{escaped_title.size},#{escaped_text.size}}:#{escaped_title}|#{escaped_text}"
|
34
|
-
packet <<
|
22
|
+
packet << "|h:#{metric.metadata[:hostname]}" if metric.metadata[:hostname]
|
23
|
+
packet << "|d:#{metric.metadata[:timestamp].to_i}" if metric.metadata[:timestamp]
|
24
|
+
packet << "|k:#{metric.metadata[:aggregation_key]}" if metric.metadata[:aggregation_key]
|
25
|
+
packet << "|p:#{metric.metadata[:priority]}" if metric.metadata[:priority]
|
26
|
+
packet << "|s:#{metric.metadata[:source_type_name]}" if metric.metadata[:source_type_name]
|
27
|
+
packet << "|t:#{metric.metadata[:alert_type]}" if metric.metadata[:alert_type]
|
28
|
+
packet << "|##{metric.tags.join(',')}" if metric.tags
|
29
|
+
|
35
30
|
elsif metric.type == :_sc
|
36
|
-
|
37
|
-
|
31
|
+
status = metric.value.is_a?(Integer) ? metric.value : SERVICE_CHECK_STATUSES.fetch(metric.value.to_sym)
|
32
|
+
|
33
|
+
packet << "_sc|#{metric.name}|#{status}"
|
34
|
+
packet << "|h:#{metric.metadata[:hostname]}" if metric.metadata[:hostname]
|
35
|
+
packet << "|d:#{metric.metadata[:timestamp].to_i}" if metric.metadata[:timestamp]
|
36
|
+
packet << "|##{metric.tags.join(',')}" if metric.tags
|
37
|
+
packet << "|m:#{metric.metadata[:message]}" if metric.metadata[:message]
|
38
|
+
|
38
39
|
else
|
39
40
|
packet << "#{metric.name}:#{metric.value}|#{metric.type}"
|
41
|
+
packet << "|@#{metric.sample_rate}" if metric.sample_rate < 1
|
42
|
+
packet << "|##{metric.tags.join(',')}" if metric.tags
|
40
43
|
end
|
41
44
|
|
42
|
-
packet << "|@#{metric.sample_rate}" if metric.sample_rate < 1
|
43
|
-
packet << "|##{metric.tags.join(',')}" if metric.tags
|
44
45
|
packet
|
45
46
|
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def generate_metadata(metric, options)
|
50
|
-
(metric.metadata.keys & options.keys).map do |key|
|
51
|
-
"|#{options[key]}:#{metric.metadata[key]}"
|
52
|
-
end.join
|
53
|
-
end
|
54
47
|
end
|
55
48
|
|
56
49
|
class StatsiteStatsDProtocol
|
57
50
|
SUPPORTED_METRIC_TYPES = BASE_SUPPORTED_METRIC_TYPES.merge(kv: true)
|
58
51
|
|
59
52
|
def generate_packet(metric)
|
60
|
-
packet = "#{metric.name}:#{metric.value}|#{metric.type}"
|
53
|
+
packet = +"#{metric.name}:#{metric.value}|#{metric.type}"
|
61
54
|
packet << "|@#{metric.sample_rate}" unless metric.sample_rate == 1
|
62
55
|
packet << "\n"
|
63
56
|
packet
|
@@ -68,7 +61,7 @@ module StatsD::Instrument::Backends
|
|
68
61
|
SUPPORTED_METRIC_TYPES = BASE_SUPPORTED_METRIC_TYPES
|
69
62
|
|
70
63
|
def generate_packet(metric)
|
71
|
-
packet = "#{metric.name}:#{metric.value}|#{metric.type}"
|
64
|
+
packet = +"#{metric.name}:#{metric.value}|#{metric.type}"
|
72
65
|
packet << "|@#{metric.sample_rate}" if metric.sample_rate < 1
|
73
66
|
packet
|
74
67
|
end
|
@@ -88,19 +81,20 @@ module StatsD::Instrument::Backends
|
|
88
81
|
|
89
82
|
def implementation=(value)
|
90
83
|
@packet_factory = case value
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
84
|
+
when :datadog
|
85
|
+
DogStatsDProtocol.new
|
86
|
+
when :statsite
|
87
|
+
StatsiteStatsDProtocol.new
|
88
|
+
else
|
89
|
+
StatsDProtocol.new
|
90
|
+
end
|
98
91
|
@implementation = value
|
99
92
|
end
|
100
93
|
|
101
94
|
def collect_metric(metric)
|
102
95
|
unless @packet_factory.class::SUPPORTED_METRIC_TYPES[metric.type]
|
103
|
-
StatsD.logger.warn("[StatsD] Metric type #{metric.type.inspect} not supported
|
96
|
+
StatsD.logger.warn("[StatsD] Metric type #{metric.type.inspect} is not supported " \
|
97
|
+
"on #{implementation} implementation.")
|
104
98
|
return false
|
105
99
|
end
|
106
100
|
|
@@ -139,13 +133,13 @@ module StatsD::Instrument::Backends
|
|
139
133
|
synchronize do
|
140
134
|
socket.send(command, 0) > 0
|
141
135
|
end
|
142
|
-
rescue ThreadError
|
136
|
+
rescue ThreadError
|
143
137
|
# In cases where a TERM or KILL signal has been sent, and we send stats as
|
144
138
|
# part of a signal handler, locks cannot be acquired, so we do our best
|
145
139
|
# to try and send the command without a lock.
|
146
140
|
socket.send(command, 0) > 0
|
147
|
-
rescue SocketError, IOError, SystemCallError
|
148
|
-
StatsD.logger.error
|
141
|
+
rescue SocketError, IOError, SystemCallError => e
|
142
|
+
StatsD.logger.error("[StatsD] #{e.class.name}: #{e.message}")
|
149
143
|
invalidate_socket
|
150
144
|
end
|
151
145
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @note This class is part of the new Client implementation that is intended
|
4
|
+
# to become the new default in the next major release of this library.
|
5
|
+
class StatsD::Instrument::CaptureSink
|
6
|
+
attr_reader :parent, :datagrams, :datagram_class
|
7
|
+
|
8
|
+
def initialize(parent:, datagram_class: StatsD::Instrument::Datagram)
|
9
|
+
@parent = parent
|
10
|
+
@datagram_class = datagram_class
|
11
|
+
@datagrams = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def sample?(_sample_rate)
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def <<(datagram)
|
19
|
+
@datagrams << datagram_class.new(datagram)
|
20
|
+
parent << datagram
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def clear
|
25
|
+
@datagrams.clear
|
26
|
+
end
|
27
|
+
end
|