statsd-instrument 2.6.0 → 2.7.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/CHANGELOG.md +79 -9
- data/benchmark/datagram-client +0 -1
- data/benchmark/send-metrics-to-dev-null-log +0 -1
- data/lib/statsd/instrument.rb +66 -292
- data/lib/statsd/instrument/assertions.rb +83 -93
- data/lib/statsd/instrument/client.rb +2 -2
- data/lib/statsd/instrument/datagram.rb +12 -3
- data/lib/statsd/instrument/environment.rb +9 -3
- data/lib/statsd/instrument/expectation.rb +93 -0
- data/lib/statsd/instrument/helpers.rb +29 -11
- data/lib/statsd/instrument/legacy_client.rb +301 -0
- data/lib/statsd/instrument/metric.rb +8 -8
- data/lib/statsd/instrument/rubocop.rb +18 -0
- data/lib/statsd/instrument/rubocop/singleton_configuration.rb +53 -0
- data/lib/statsd/instrument/version.rb +1 -1
- data/test/assertions_on_legacy_client_test.rb +376 -0
- data/test/assertions_test.rb +105 -39
- data/test/capture_sink_test.rb +0 -2
- data/test/client_test.rb +0 -2
- data/test/compatibility/dogstatsd_datagram_compatibility_test.rb +0 -1
- data/test/datagram_builder_test.rb +1 -3
- data/test/datagram_test.rb +14 -0
- data/test/dogstatsd_datagram_builder_test.rb +0 -2
- data/test/environment_test.rb +1 -1
- data/test/helpers_test.rb +17 -0
- data/test/log_sink_test.rb +0 -2
- data/test/logger_backend_test.rb +2 -2
- data/test/metric_test.rb +2 -2
- data/test/null_sink_test.rb +0 -2
- data/test/rubocop/singleton_configuration_test.rb +43 -0
- data/test/statsd_datagram_builder_test.rb +0 -2
- data/test/statsd_instrumentation_test.rb +4 -6
- data/test/statsd_test.rb +1 -1
- data/test/test_helper.rb +0 -2
- data/test/udp_backend_test.rb +1 -1
- data/test/udp_sink_test.rb +0 -2
- metadata +11 -3
- data/lib/statsd/instrument/metric_expectation.rb +0 -82
@@ -0,0 +1,301 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class StatsD::Instrument::LegacyClient
|
4
|
+
def self.singleton
|
5
|
+
@singleton ||= new
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_accessor :default_sample_rate, :prefix
|
9
|
+
attr_writer :backend
|
10
|
+
attr_reader :default_tags
|
11
|
+
|
12
|
+
def default_tags=(tags)
|
13
|
+
@default_tags = StatsD::Instrument::Metric.normalize_tags(tags)
|
14
|
+
end
|
15
|
+
|
16
|
+
def backend
|
17
|
+
@backend ||= StatsD::Instrument::Environment.default_backend
|
18
|
+
end
|
19
|
+
|
20
|
+
# @!method measure(name, value = nil, sample_rate: nil, tags: nil, &block)
|
21
|
+
#
|
22
|
+
# Emits a timing metric
|
23
|
+
#
|
24
|
+
# @param [String] key The name of the metric.
|
25
|
+
# @param sample_rate (see #increment)
|
26
|
+
# @param tags (see #increment)
|
27
|
+
#
|
28
|
+
# @example Providing a value directly
|
29
|
+
# start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
30
|
+
# do_something
|
31
|
+
# stop = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
32
|
+
# http_response = StatsD.measure('HTTP.call.duration', stop - start)
|
33
|
+
#
|
34
|
+
# @example Providing a block to measure the duration of its execution
|
35
|
+
# http_response = StatsD.measure('HTTP.call.duration') do
|
36
|
+
# Net::HTTP.get(url)
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# @overload measure(key, value, sample_rate: nil, tags: nil)
|
40
|
+
# Emits a timing metric, by providing a duration in milliseconds.
|
41
|
+
#
|
42
|
+
# @param [Float] value The measured duration in milliseconds
|
43
|
+
# @return [void]
|
44
|
+
#
|
45
|
+
# @overload measure(key, sample_rate: nil, tags: nil, &block)
|
46
|
+
# Emits a timing metric, after measuring the execution duration of the
|
47
|
+
# block passed to this method.
|
48
|
+
#
|
49
|
+
# @yield `StatsD.measure` will yield the block and measure the duration. After the block
|
50
|
+
# returns, the duration in millisecond will be emitted as metric.
|
51
|
+
# @return The value that was returned by the block passed through.
|
52
|
+
def measure(
|
53
|
+
key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
|
54
|
+
value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
|
55
|
+
prefix: self.prefix, no_prefix: false, as_dist: false,
|
56
|
+
&block
|
57
|
+
)
|
58
|
+
# TODO: in the next version, hardcode this to :ms when the as_dist argument is dropped.
|
59
|
+
type = as_dist ? :d : :ms
|
60
|
+
prefix = nil if no_prefix
|
61
|
+
if block_given?
|
62
|
+
measure_latency(type, key, sample_rate: sample_rate, tags: tags, prefix: prefix, &block)
|
63
|
+
else
|
64
|
+
collect_metric(type, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @!method increment(name, value = 1, sample_rate: nil, tags: nil)
|
69
|
+
#
|
70
|
+
# Emits a counter metric.
|
71
|
+
#
|
72
|
+
# @param key [String] The name of the metric.
|
73
|
+
# @param value [Integer] The value to increment the counter by.
|
74
|
+
#
|
75
|
+
# You should not compensate for the sample rate using the counter increment. E.g., if
|
76
|
+
# your sample rate is 0.01, you should <b>not</b> use 100 as increment to compensate for it.
|
77
|
+
# The sample rate is part of the packet that is being sent to the server, and the server
|
78
|
+
# should know how to handle it.
|
79
|
+
#
|
80
|
+
# @param sample_rate [Float] (default: `StatsD.default_sample_rate`) The rate at which to sample
|
81
|
+
# this metric call. This value should be between 0 and 1. This value can be used to reduce
|
82
|
+
# the amount of network I/O (and CPU cycles) used for very frequent metrics.
|
83
|
+
#
|
84
|
+
# - A value of `0.1` means that only 1 out of 10 calls will be emitted; the other 9 will
|
85
|
+
# be short-circuited.
|
86
|
+
# - When set to `1`, every metric will be emitted.
|
87
|
+
# - If this parameter is not set, the default sample rate for this client will be used.
|
88
|
+
# @param tags [Array<String>, Hash<Symbol, String>] The tags to associate with this measurement.
|
89
|
+
# They can be provided as an array of strings, or a hash of key/value pairs.
|
90
|
+
# _Note:_ Tags are not supported by all implementations.
|
91
|
+
# @return [void]
|
92
|
+
def increment(
|
93
|
+
key, value_arg = 1, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
|
94
|
+
value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
|
95
|
+
prefix: self.prefix, no_prefix: false
|
96
|
+
)
|
97
|
+
prefix = nil if no_prefix
|
98
|
+
collect_metric(:c, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
|
99
|
+
end
|
100
|
+
|
101
|
+
# @!method gauge(name, value, sample_rate: nil, tags: nil)
|
102
|
+
#
|
103
|
+
# Emits a gauge metric.
|
104
|
+
#
|
105
|
+
# @param key The name of the metric.
|
106
|
+
# @param value [Numeric] The current value to record.
|
107
|
+
# @param sample_rate (see #increment)
|
108
|
+
# @param tags (see #increment)
|
109
|
+
# @return [void]
|
110
|
+
def gauge(
|
111
|
+
key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
|
112
|
+
value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
|
113
|
+
prefix: self.prefix, no_prefix: false
|
114
|
+
)
|
115
|
+
prefix = nil if no_prefix
|
116
|
+
collect_metric(:g, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
|
117
|
+
end
|
118
|
+
|
119
|
+
# @!method set(name, value, sample_rate: nil, tags: nil)
|
120
|
+
#
|
121
|
+
# Emits a set metric, which counts the number of distinct values that have occurred.
|
122
|
+
#
|
123
|
+
# @example Couning the number of unique visitors
|
124
|
+
# StatsD.set('visitors.unique', Current.user.id)
|
125
|
+
#
|
126
|
+
# @param key [String] The name of the metric.
|
127
|
+
# @param value [Numeric] The value to record.
|
128
|
+
# @param sample_rate (see #increment)
|
129
|
+
# @param tags (see #increment)
|
130
|
+
# @return [void]
|
131
|
+
def set(
|
132
|
+
key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
|
133
|
+
value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
|
134
|
+
prefix: self.prefix, no_prefix: false
|
135
|
+
)
|
136
|
+
prefix = nil if no_prefix
|
137
|
+
collect_metric(:s, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
|
138
|
+
end
|
139
|
+
|
140
|
+
# @!method histogram(name, value, sample_rate: nil, tags: nil)
|
141
|
+
#
|
142
|
+
# Emits a histogram metric.
|
143
|
+
#
|
144
|
+
# @param key The name of the metric.
|
145
|
+
# @param value [Numeric] The value to record.
|
146
|
+
# @param sample_rate (see #increment)
|
147
|
+
# @param tags (see #increment)
|
148
|
+
# @return (see #collect_metric)
|
149
|
+
# @note Supported by the datadog implementation only.
|
150
|
+
def histogram(
|
151
|
+
key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
|
152
|
+
value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
|
153
|
+
prefix: self.prefix, no_prefix: false
|
154
|
+
)
|
155
|
+
prefix = nil if no_prefix
|
156
|
+
collect_metric(:h, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
|
157
|
+
end
|
158
|
+
|
159
|
+
# @!method distribution(name, value = nil, sample_rate: nil, tags: nil, &block)
|
160
|
+
#
|
161
|
+
# Emits a distribution metric.
|
162
|
+
#
|
163
|
+
# @param [String] key The name of the metric.
|
164
|
+
# @param sample_rate (see #increment)
|
165
|
+
# @param tags (see #increment)
|
166
|
+
#
|
167
|
+
# @note Supported by the datadog implementation only.
|
168
|
+
# @example
|
169
|
+
# http_response = StatsD.distribution('HTTP.call.duration') do
|
170
|
+
# Net::HTTP.get(url)
|
171
|
+
# end
|
172
|
+
#
|
173
|
+
# @overload distribution(name, value, sample_rate: nil, tags: nil)
|
174
|
+
#
|
175
|
+
# Emits a distribution metric, given a provided value to record.
|
176
|
+
#
|
177
|
+
# @param [Numeric] value The value to record.
|
178
|
+
# @return [void]
|
179
|
+
#
|
180
|
+
# @overload distribution(key, metric_options = {}, &block)
|
181
|
+
#
|
182
|
+
# Emits a distribution metric for the duration of the provided block, in milliseconds.
|
183
|
+
#
|
184
|
+
# @yield `StatsD.distribution` will yield the block and measure the duration. After
|
185
|
+
# the block returns, the duration in millisecond will be emitted as metric.
|
186
|
+
# @return The value that was returned by the block passed through.
|
187
|
+
def distribution(
|
188
|
+
key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
|
189
|
+
value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
|
190
|
+
prefix: self.prefix, no_prefix: false,
|
191
|
+
&block
|
192
|
+
)
|
193
|
+
prefix = nil if no_prefix
|
194
|
+
if block_given?
|
195
|
+
measure_latency(:d, key, sample_rate: sample_rate, tags: tags, prefix: prefix, &block)
|
196
|
+
else
|
197
|
+
collect_metric(:d, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# @!method key_value(name, value)
|
202
|
+
#
|
203
|
+
# Emits a key/value metric.
|
204
|
+
#
|
205
|
+
# @param key [String] The name of the metric.
|
206
|
+
# @param value [Numeric] The value to record.
|
207
|
+
# @return [void]
|
208
|
+
#
|
209
|
+
# @note Supported by the statsite implementation only.
|
210
|
+
def key_value(
|
211
|
+
key, value_arg = nil, deprecated_sample_rate_arg = nil,
|
212
|
+
value: value_arg, sample_rate: deprecated_sample_rate_arg, no_prefix: false
|
213
|
+
)
|
214
|
+
prefix = nil if no_prefix
|
215
|
+
collect_metric(:kv, key, value, sample_rate: sample_rate, prefix: prefix)
|
216
|
+
end
|
217
|
+
|
218
|
+
# @!method event(title, text, tags: nil, hostname: nil, timestamp: nil, aggregation_key: nil, priority: nil, source_type_name: nil, alert_type: nil) # rubocop:disable Metrics/LineLength
|
219
|
+
#
|
220
|
+
# Emits an event.
|
221
|
+
#
|
222
|
+
# @param title [String] Title of the event. A configured prefix may be applied to this title.
|
223
|
+
# @param text [String] Body of the event. Can contain newlines.
|
224
|
+
# @param [String] hostname The hostname to associate with the event.
|
225
|
+
# @param [Time] timestamp The moment the status of the service was checkes. Defaults to now.
|
226
|
+
# @param [String] aggregation_key A key to aggregate similar events into groups.
|
227
|
+
# @param [String] priority The event's priority, either `"low"` or `"normal"` (default).
|
228
|
+
# @param [String] source_type_name The source type.
|
229
|
+
# @param [String] alert_type The type of alert. Either `"info"` (default), `"warning"`, `"error"`, or `"success"`.
|
230
|
+
# @param tags (see #increment)
|
231
|
+
# @return [void]
|
232
|
+
#
|
233
|
+
# @note Supported by the Datadog implementation only.
|
234
|
+
def event(
|
235
|
+
title, text,
|
236
|
+
deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
|
237
|
+
sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
|
238
|
+
prefix: self.prefix, no_prefix: false,
|
239
|
+
hostname: nil, date_happened: nil, timestamp: date_happened,
|
240
|
+
aggregation_key: nil, priority: nil, source_type_name: nil, alert_type: nil,
|
241
|
+
**_ignored
|
242
|
+
)
|
243
|
+
prefix = nil if no_prefix
|
244
|
+
collect_metric(:_e, title, text, sample_rate: sample_rate, tags: tags, prefix: prefix, metadata: {
|
245
|
+
hostname: hostname, timestamp: timestamp, aggregation_key: aggregation_key,
|
246
|
+
priority: priority, source_type_name: source_type_name, alert_type: alert_type
|
247
|
+
})
|
248
|
+
end
|
249
|
+
|
250
|
+
# @!method service_check(name, status, tags: nil, hostname: nil, timestamp: nil, message: nil)
|
251
|
+
#
|
252
|
+
# Emits a service check.
|
253
|
+
#
|
254
|
+
# @param [String] name Name of the service. A configured prefix may be applied to this title.
|
255
|
+
# @param [Symbol] status Current status of the service. Either `:ok`, `:warning`, `:critical`, or `:unknown`.
|
256
|
+
# @param [String] hostname The hostname to associate with the event.
|
257
|
+
# @param [Time] timestamp The moment the status of the service was checkes. Defaults to now.
|
258
|
+
# @param [String] message A message that describes the current status.
|
259
|
+
# @param tags (see #increment)
|
260
|
+
# @return [void]
|
261
|
+
#
|
262
|
+
# @note Supported by the Datadog implementation only.
|
263
|
+
def service_check(
|
264
|
+
name, status,
|
265
|
+
deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
|
266
|
+
sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
|
267
|
+
prefix: self.prefix, no_prefix: false,
|
268
|
+
hostname: nil, timestamp: nil, message: nil, **_ignored
|
269
|
+
)
|
270
|
+
prefix = nil if no_prefix
|
271
|
+
collect_metric(:_sc, name, status, sample_rate: sample_rate, prefix: prefix, tags: tags, metadata: {
|
272
|
+
hostname: hostname, timestamp: timestamp, message: message
|
273
|
+
})
|
274
|
+
end
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
def measure_latency(type, key, sample_rate:, tags:, prefix:)
|
279
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
280
|
+
begin
|
281
|
+
yield
|
282
|
+
ensure
|
283
|
+
# Ensure catches both a raised exception and a return in the invoked block
|
284
|
+
value = 1000.0 * (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
|
285
|
+
collect_metric(type, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# Instantiates a metric, and sends it to the backend for further processing.
|
290
|
+
# @param options (see StatsD::Instrument::Metric#initialize)
|
291
|
+
# @return [void]
|
292
|
+
def collect_metric(type, name, value, sample_rate:, tags: nil, prefix:, metadata: nil)
|
293
|
+
sample_rate ||= default_sample_rate
|
294
|
+
name = "#{prefix}.#{name}" if prefix
|
295
|
+
|
296
|
+
metric = StatsD::Instrument::Metric.new(type: type, name: name, value: value,
|
297
|
+
sample_rate: sample_rate, tags: tags, metadata: metadata)
|
298
|
+
backend.collect_metric(metric)
|
299
|
+
metric # TODO: return `nil` in the next major version
|
300
|
+
end
|
301
|
+
end
|
@@ -43,9 +43,9 @@ class StatsD::Instrument::Metric
|
|
43
43
|
using RubyBackports
|
44
44
|
end
|
45
45
|
|
46
|
-
def self.new(
|
47
|
-
|
48
|
-
|
46
|
+
def self.new(type:, name:, value: default_value(type), tags: nil, metadata: nil,
|
47
|
+
sample_rate: StatsD.legacy_singleton_client.default_sample_rate)
|
48
|
+
|
49
49
|
# pass keyword arguments as positional arguments for performance reasons,
|
50
50
|
# since MRI's C implementation of new turns keyword arguments into a hash
|
51
51
|
super(type, name, value, sample_rate, tags, metadata)
|
@@ -92,8 +92,8 @@ class StatsD::Instrument::Metric
|
|
92
92
|
@value = value
|
93
93
|
@sample_rate = sample_rate
|
94
94
|
@tags = StatsD::Instrument::Metric.normalize_tags(tags)
|
95
|
-
if StatsD.default_tags
|
96
|
-
@tags = Array(@tags) + StatsD.default_tags
|
95
|
+
if StatsD.legacy_singleton_client.default_tags
|
96
|
+
@tags = Array(@tags) + StatsD.legacy_singleton_client.default_tags
|
97
97
|
end
|
98
98
|
@metadata = metadata
|
99
99
|
end
|
@@ -101,9 +101,9 @@ class StatsD::Instrument::Metric
|
|
101
101
|
# @private
|
102
102
|
# @return [String]
|
103
103
|
def to_s
|
104
|
-
str = +"#{
|
105
|
-
str << "
|
106
|
-
|
104
|
+
str = +"#{name}:#{value}|#{type}"
|
105
|
+
str << "|@#{sample_rate}" if sample_rate && sample_rate != 1.0
|
106
|
+
str << "|#" << tags.join(',') if tags && !tags.empty?
|
107
107
|
str
|
108
108
|
end
|
109
109
|
|
@@ -21,6 +21,17 @@ module RuboCop
|
|
21
21
|
statsd_count
|
22
22
|
}
|
23
23
|
|
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
|
+
}
|
34
|
+
|
24
35
|
private
|
25
36
|
|
26
37
|
def metaprogramming_method?(node)
|
@@ -33,6 +44,12 @@ module RuboCop
|
|
33
44
|
METRIC_METHODS.include?(node.method_name)
|
34
45
|
end
|
35
46
|
|
47
|
+
def singleton_configuration_method?(node)
|
48
|
+
node.receiver&.type == :const &&
|
49
|
+
node.receiver&.const_name == "StatsD" &&
|
50
|
+
SINGLETON_CONFIGURATION_METHODS.include?(node.method_name)
|
51
|
+
end
|
52
|
+
|
36
53
|
def has_keyword_argument?(node, sym)
|
37
54
|
if (kwargs = keyword_arguments(node))
|
38
55
|
kwargs.child_nodes.detect do |pair|
|
@@ -62,3 +79,4 @@ require_relative 'rubocop/positional_arguments'
|
|
62
79
|
require_relative 'rubocop/splat_arguments'
|
63
80
|
require_relative 'rubocop/measure_as_dist_argument'
|
64
81
|
require_relative 'rubocop/metric_prefix_argument'
|
82
|
+
require_relative 'rubocop/singleton_configuration'
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require_relative '../rubocop' unless defined?(RuboCop::Cop::StatsD)
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module StatsD
|
8
|
+
# This Rubocop will check for calls to StatsD singleton congfiguration methods
|
9
|
+
# (e.g. `StatsD.prefix`). The library is moving away from having just a single
|
10
|
+
# singleton client, so these methods are deprecated.
|
11
|
+
#
|
12
|
+
# Use the following Rubocop invocation to check your project's codebase:
|
13
|
+
#
|
14
|
+
# rubocop --require `bundle show statsd-instrument`/lib/statsd/instrument/rubocop.rb \
|
15
|
+
# --only StatsD/SingletonConfiguration
|
16
|
+
#
|
17
|
+
# This cop will not autocorrect violations. There are several ways of fixing the violation.
|
18
|
+
#
|
19
|
+
# - The best option is to configure the library using environment variables, like
|
20
|
+
# `STATSD_ADDR`, `STATSD_IMPLEMENTATION`, `STATSD_PREFIX`, and `STATSD_DEFAULT_TAGS`.
|
21
|
+
# Metric methods called on the StatsD singleton (e.g. `StatsD.increment`) will by default
|
22
|
+
# be delegated to a client that is configured using these environment variables.
|
23
|
+
# - Alternatively, you can instantiate your own client using `StatsD::Instrument::Client.new`,
|
24
|
+
# and assign it to `StatsD.singleton_client`. The client constructor accepts many of the
|
25
|
+
# same options.
|
26
|
+
# - If you have to, you can call the old methods on `StatsD.legacy_singleton_client`. Note
|
27
|
+
# that this option will go away in the next major version.
|
28
|
+
class SingletonConfiguration < Cop
|
29
|
+
include RuboCop::Cop::StatsD
|
30
|
+
|
31
|
+
MSG = <<~MESSAGE
|
32
|
+
Singleton methods to configure StatsD are deprecated.
|
33
|
+
|
34
|
+
- The best option is to configure the library using environment variables, like
|
35
|
+
`STATSD_ADDR`, `STATSD_IMPLEMENTATION`, `STATSD_PREFIX`, and `STATSD_DEFAULT_TAGS`.
|
36
|
+
Metric methods called on the StatsD singleton (e.g. `StatsD.increment`) will by default
|
37
|
+
be delegated to a client that is configured using these environment variables.
|
38
|
+
- Alternatively, you can instantiate your own client using `StatsD::Instrument::Client.new`,
|
39
|
+
and assign it to `StatsD.singleton_client`. The client constructor accepts many of the
|
40
|
+
same options.
|
41
|
+
- If you have to, you can call the old methods on `StatsD.legacy_singleton_client`. Note
|
42
|
+
that this option will go away in the next major version.
|
43
|
+
MESSAGE
|
44
|
+
|
45
|
+
def on_send(node)
|
46
|
+
if singleton_configuration_method?(node)
|
47
|
+
add_offense(node)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,376 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class AssertionsOnLegacyClientTest < Minitest::Test
|
6
|
+
def setup
|
7
|
+
@old_client = StatsD.singleton_client
|
8
|
+
StatsD.singleton_client = StatsD.legacy_singleton_client
|
9
|
+
|
10
|
+
test_class = Class.new(Minitest::Test)
|
11
|
+
test_class.send(:include, StatsD::Instrument::Assertions)
|
12
|
+
@test_case = test_class.new('fake')
|
13
|
+
end
|
14
|
+
|
15
|
+
def teardown
|
16
|
+
StatsD.singleton_client = @old_client
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_assert_no_statsd_calls
|
20
|
+
@test_case.assert_no_statsd_calls('counter') do
|
21
|
+
# noop
|
22
|
+
end
|
23
|
+
|
24
|
+
@test_case.assert_no_statsd_calls('counter') do
|
25
|
+
StatsD.increment('other')
|
26
|
+
end
|
27
|
+
|
28
|
+
assertion = assert_raises(Minitest::Assertion) do
|
29
|
+
@test_case.assert_no_statsd_calls('counter') do
|
30
|
+
StatsD.increment('counter')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
assert_equal assertion.message, "No StatsD calls for metric counter expected."
|
34
|
+
|
35
|
+
assertion = assert_raises(Minitest::Assertion) do
|
36
|
+
@test_case.assert_no_statsd_calls do
|
37
|
+
StatsD.increment('other')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
assert_equal assertion.message, "No StatsD calls for metric other expected."
|
41
|
+
|
42
|
+
assertion = assert_raises(Minitest::Assertion) do
|
43
|
+
@test_case.assert_no_statsd_calls do
|
44
|
+
StatsD.increment('other')
|
45
|
+
StatsD.increment('another')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
assert_equal assertion.message, "No StatsD calls for metric other, another expected."
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_assert_statsd_call
|
52
|
+
@test_case.assert_statsd_increment('counter') do
|
53
|
+
StatsD.increment('counter')
|
54
|
+
end
|
55
|
+
|
56
|
+
@test_case.assert_statsd_increment('counter') do
|
57
|
+
StatsD.increment('counter')
|
58
|
+
StatsD.increment('other')
|
59
|
+
end
|
60
|
+
|
61
|
+
assert_raises(Minitest::Assertion) do
|
62
|
+
@test_case.assert_statsd_increment('counter') do
|
63
|
+
StatsD.increment('other')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
assert_raises(Minitest::Assertion) do
|
68
|
+
@test_case.assert_statsd_increment('counter') do
|
69
|
+
StatsD.gauge('counter', 42)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
assert_raises(Minitest::Assertion) do
|
74
|
+
@test_case.assert_statsd_increment('counter') do
|
75
|
+
StatsD.increment('counter')
|
76
|
+
StatsD.increment('counter')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
@test_case.assert_statsd_increment('counter', times: 2) do
|
81
|
+
StatsD.increment('counter')
|
82
|
+
StatsD.increment('counter')
|
83
|
+
end
|
84
|
+
|
85
|
+
@test_case.assert_statsd_increment('counter', times: 2, tags: ['foo:1']) do
|
86
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
87
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
88
|
+
end
|
89
|
+
|
90
|
+
assert_raises(Minitest::Assertion) do
|
91
|
+
@test_case.assert_statsd_increment('counter', times: 2, tags: ['foo:1']) do
|
92
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
93
|
+
StatsD.increment('counter', tags: { foo: 2 })
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
@test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a', 'b']) do
|
98
|
+
StatsD.increment('counter', sample_rate: 0.5, tags: ['a', 'b'])
|
99
|
+
end
|
100
|
+
|
101
|
+
assert_raises(Minitest::Assertion) do
|
102
|
+
@test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a', 'b'], ignore_tags: ['b']) do
|
103
|
+
StatsD.increment('counter', sample_rate: 0.5, tags: ['a'])
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
@test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a'], ignore_tags: ['b']) do
|
108
|
+
StatsD.increment('counter', sample_rate: 0.5, tags: ['a', 'b'])
|
109
|
+
end
|
110
|
+
|
111
|
+
@test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a'], ignore_tags: ['b']) do
|
112
|
+
StatsD.increment('counter', sample_rate: 0.5, tags: ['a'])
|
113
|
+
end
|
114
|
+
|
115
|
+
@test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1 }, ignore_tags: { b: 2 }) do
|
116
|
+
StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2 })
|
117
|
+
end
|
118
|
+
|
119
|
+
@test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1 }, ignore_tags: { b: 2 }) do
|
120
|
+
StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 3 })
|
121
|
+
end
|
122
|
+
|
123
|
+
assert_raises(Minitest::Assertion) do
|
124
|
+
@test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1, b: 3 }, ignore_tags: ['b']) do
|
125
|
+
StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2 })
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
@test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1 }, ignore_tags: ['b']) do
|
130
|
+
StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2 })
|
131
|
+
end
|
132
|
+
|
133
|
+
assert_raises(Minitest::Assertion) do
|
134
|
+
@test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a', 'b']) do
|
135
|
+
StatsD.increment('counter', sample_rate: 0.2, tags: ['c'])
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_assert_statsd_gauge_call_with_numeric_value
|
141
|
+
@test_case.assert_statsd_gauge('gauge', value: 42) do
|
142
|
+
StatsD.gauge('gauge', 42)
|
143
|
+
end
|
144
|
+
|
145
|
+
@test_case.assert_statsd_gauge('gauge', value: '42') do
|
146
|
+
StatsD.gauge('gauge', 42)
|
147
|
+
end
|
148
|
+
|
149
|
+
assert_raises(Minitest::Assertion) do
|
150
|
+
@test_case.assert_statsd_gauge('gauge', value: 42) do
|
151
|
+
StatsD.gauge('gauge', 45)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_assert_statsd_set_call_with_string_value
|
157
|
+
@test_case.assert_statsd_set('set', value: 12345) do
|
158
|
+
StatsD.set('set', '12345')
|
159
|
+
end
|
160
|
+
|
161
|
+
@test_case.assert_statsd_set('set', value: '12345') do
|
162
|
+
StatsD.set('set', '12345')
|
163
|
+
end
|
164
|
+
|
165
|
+
@test_case.assert_statsd_set('set', value: 12345) do
|
166
|
+
StatsD.set('set', 12345)
|
167
|
+
end
|
168
|
+
|
169
|
+
@test_case.assert_statsd_set('set', value: '12345') do
|
170
|
+
StatsD.set('set', 12345)
|
171
|
+
end
|
172
|
+
|
173
|
+
assert_raises(Minitest::Assertion) do
|
174
|
+
@test_case.assert_statsd_set('set', value: '42') do
|
175
|
+
StatsD.set('set', 45)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_tags_will_match_subsets
|
181
|
+
@test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1 }) do
|
182
|
+
StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2 })
|
183
|
+
end
|
184
|
+
|
185
|
+
assert_raises(Minitest::Assertion) do
|
186
|
+
@test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1, b: 3 }) do
|
187
|
+
StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2, c: 4 })
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def test_tags_friendly_error
|
193
|
+
assertion = assert_raises(Minitest::Assertion) do
|
194
|
+
@test_case.assert_statsd_increment('counter', tags: { class: "AnotherJob" }) do
|
195
|
+
StatsD.increment('counter', tags: { class: "MyJob" })
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
assert_includes assertion.message, "Captured metrics with the same key"
|
200
|
+
assert_includes assertion.message, "MyJob"
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_multiple_metrics_are_not_order_dependent
|
204
|
+
foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:1'])
|
205
|
+
foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
|
206
|
+
@test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
|
207
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
208
|
+
StatsD.increment('counter', tags: { foo: 2 })
|
209
|
+
end
|
210
|
+
|
211
|
+
foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:1'])
|
212
|
+
foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
|
213
|
+
@test_case.assert_statsd_calls([foo_2_metric, foo_1_metric]) do
|
214
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
215
|
+
StatsD.increment('counter', tags: { foo: 2 })
|
216
|
+
end
|
217
|
+
|
218
|
+
foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
|
219
|
+
foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
|
220
|
+
@test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
|
221
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
222
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
223
|
+
StatsD.increment('counter', tags: { foo: 2 })
|
224
|
+
end
|
225
|
+
|
226
|
+
foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
|
227
|
+
foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
|
228
|
+
@test_case.assert_statsd_calls([foo_2_metric, foo_1_metric]) do
|
229
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
230
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
231
|
+
StatsD.increment('counter', tags: { foo: 2 })
|
232
|
+
end
|
233
|
+
|
234
|
+
foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
|
235
|
+
foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
|
236
|
+
@test_case.assert_statsd_calls([foo_2_metric, foo_1_metric]) do
|
237
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
238
|
+
StatsD.increment('counter', tags: { foo: 2 })
|
239
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def test_assert_multiple_statsd_calls
|
244
|
+
assert_raises(Minitest::Assertion) do
|
245
|
+
foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
|
246
|
+
foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
|
247
|
+
@test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
|
248
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
249
|
+
StatsD.increment('counter', tags: { foo: 2 })
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
assert_raises(Minitest::Assertion) do
|
254
|
+
foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
|
255
|
+
foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
|
256
|
+
@test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
|
257
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
258
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
259
|
+
StatsD.increment('counter', tags: { foo: 2 })
|
260
|
+
StatsD.increment('counter', tags: { foo: 2 })
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
|
265
|
+
foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
|
266
|
+
@test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
|
267
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
268
|
+
StatsD.increment('counter', tags: { foo: 1 })
|
269
|
+
StatsD.increment('counter', tags: { foo: 2 })
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_assert_statsd_call_with_tags
|
274
|
+
@test_case.assert_statsd_increment('counter', tags: ['a:b', 'c:d']) do
|
275
|
+
StatsD.increment('counter', tags: { a: 'b', c: 'd' })
|
276
|
+
end
|
277
|
+
|
278
|
+
@test_case.assert_statsd_increment('counter', tags: { a: 'b', c: 'd' }) do
|
279
|
+
StatsD.increment('counter', tags: ['a:b', 'c:d'])
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def test_nested_assertions
|
284
|
+
@test_case.assert_statsd_increment('counter1') do
|
285
|
+
@test_case.assert_statsd_increment('counter2') do
|
286
|
+
StatsD.increment('counter1')
|
287
|
+
StatsD.increment('counter2')
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
@test_case.assert_statsd_increment('counter1') do
|
292
|
+
StatsD.increment('counter1')
|
293
|
+
@test_case.assert_statsd_increment('counter2') do
|
294
|
+
StatsD.increment('counter2')
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
assert_raises(Minitest::Assertion) do
|
299
|
+
@test_case.assert_statsd_increment('counter1') do
|
300
|
+
@test_case.assert_statsd_increment('counter2') do
|
301
|
+
StatsD.increment('counter2')
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
assert_raises(Minitest::Assertion) do
|
307
|
+
@test_case.assert_statsd_increment('counter1') do
|
308
|
+
@test_case.assert_statsd_increment('counter2') do
|
309
|
+
StatsD.increment('counter1')
|
310
|
+
end
|
311
|
+
StatsD.increment('counter2')
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def test_assertion_block_with_expected_exceptions
|
317
|
+
@test_case.assert_statsd_increment('expected_happened') do
|
318
|
+
@test_case.assert_raises(RuntimeError) do
|
319
|
+
begin
|
320
|
+
raise "expected"
|
321
|
+
rescue
|
322
|
+
StatsD.increment('expected_happened')
|
323
|
+
raise
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
assertion = assert_raises(Minitest::Assertion) do
|
329
|
+
@test_case.assert_statsd_increment('counter') do
|
330
|
+
@test_case.assert_raises(RuntimeError) do
|
331
|
+
raise "expected"
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
assert_includes assertion.message, "No StatsD calls for metric counter of type c were made"
|
336
|
+
end
|
337
|
+
|
338
|
+
def test_assertion_block_with_unexpected_exceptions
|
339
|
+
assertion = assert_raises(Minitest::Assertion) do
|
340
|
+
@test_case.assert_statsd_increment('counter') do
|
341
|
+
StatsD.increment('counter')
|
342
|
+
raise "unexpected"
|
343
|
+
end
|
344
|
+
end
|
345
|
+
assert_includes assertion.message, "An exception occurred in the block provided to the StatsD assertion"
|
346
|
+
|
347
|
+
assertion = assert_raises(Minitest::Assertion) do
|
348
|
+
@test_case.assert_raises(RuntimeError) do
|
349
|
+
@test_case.assert_statsd_increment('counter') do
|
350
|
+
StatsD.increment('counter')
|
351
|
+
raise "unexpected"
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
assert_includes assertion.message, "An exception occurred in the block provided to the StatsD assertion"
|
356
|
+
|
357
|
+
assertion = assert_raises(Minitest::Assertion) do
|
358
|
+
@test_case.assert_raises(RuntimeError) do
|
359
|
+
@test_case.assert_no_statsd_calls do
|
360
|
+
raise "unexpected"
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
assert_includes assertion.message, "An exception occurred in the block provided to the StatsD assertion"
|
365
|
+
end
|
366
|
+
|
367
|
+
def test_assertion_block_with_other_assertion_failures
|
368
|
+
# If another assertion failure happens inside the block, that failrue should have priority
|
369
|
+
assertion = assert_raises(Minitest::Assertion) do
|
370
|
+
@test_case.assert_statsd_increment('counter') do
|
371
|
+
@test_case.flunk('other assertion failure')
|
372
|
+
end
|
373
|
+
end
|
374
|
+
assert_equal "other assertion failure", assertion.message
|
375
|
+
end
|
376
|
+
end
|