statsd-instrument 2.3.2 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/workflows/benchmark.yml +32 -0
  4. data/.github/workflows/ci.yml +47 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +1027 -0
  7. data/.rubocop.yml +50 -0
  8. data/.yardopts +5 -0
  9. data/CHANGELOG.md +288 -2
  10. data/CONTRIBUTING.md +28 -6
  11. data/Gemfile +5 -0
  12. data/README.md +54 -46
  13. data/Rakefile +4 -2
  14. data/benchmark/README.md +29 -0
  15. data/benchmark/datagram-client +41 -0
  16. data/benchmark/send-metrics-to-dev-null-log +47 -0
  17. data/benchmark/send-metrics-to-local-udp-receiver +57 -0
  18. data/lib/statsd/instrument/assertions.rb +179 -30
  19. data/lib/statsd/instrument/backend.rb +3 -2
  20. data/lib/statsd/instrument/backends/capture_backend.rb +4 -1
  21. data/lib/statsd/instrument/backends/logger_backend.rb +3 -3
  22. data/lib/statsd/instrument/backends/null_backend.rb +2 -0
  23. data/lib/statsd/instrument/backends/udp_backend.rb +39 -45
  24. data/lib/statsd/instrument/capture_sink.rb +27 -0
  25. data/lib/statsd/instrument/client.rb +313 -0
  26. data/lib/statsd/instrument/datagram.rb +75 -0
  27. data/lib/statsd/instrument/datagram_builder.rb +101 -0
  28. data/lib/statsd/instrument/dogstatsd_datagram_builder.rb +71 -0
  29. data/lib/statsd/instrument/environment.rb +108 -29
  30. data/lib/statsd/instrument/helpers.rb +16 -8
  31. data/lib/statsd/instrument/log_sink.rb +24 -0
  32. data/lib/statsd/instrument/matchers.rb +14 -11
  33. data/lib/statsd/instrument/metric.rb +72 -45
  34. data/lib/statsd/instrument/metric_expectation.rb +32 -18
  35. data/lib/statsd/instrument/null_sink.rb +13 -0
  36. data/lib/statsd/instrument/railtie.rb +2 -1
  37. data/lib/statsd/instrument/rubocop/measure_as_dist_argument.rb +39 -0
  38. data/lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb +42 -0
  39. data/lib/statsd/instrument/rubocop/metric_prefix_argument.rb +37 -0
  40. data/lib/statsd/instrument/rubocop/metric_return_value.rb +32 -0
  41. data/lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb +36 -0
  42. data/lib/statsd/instrument/rubocop/positional_arguments.rb +99 -0
  43. data/lib/statsd/instrument/rubocop/splat_arguments.rb +31 -0
  44. data/lib/statsd/instrument/rubocop.rb +64 -0
  45. data/lib/statsd/instrument/statsd_datagram_builder.rb +14 -0
  46. data/lib/statsd/instrument/strict.rb +235 -0
  47. data/lib/statsd/instrument/udp_sink.rb +62 -0
  48. data/lib/statsd/instrument/version.rb +3 -1
  49. data/lib/statsd/instrument.rb +340 -163
  50. data/lib/statsd-instrument.rb +2 -0
  51. data/statsd-instrument.gemspec +13 -10
  52. data/test/assertions_test.rb +167 -156
  53. data/test/benchmark/clock_gettime.rb +27 -0
  54. data/test/benchmark/default_tags.rb +47 -0
  55. data/test/benchmark/metrics.rb +9 -8
  56. data/test/benchmark/tags.rb +5 -3
  57. data/test/capture_backend_test.rb +4 -2
  58. data/test/capture_sink_test.rb +44 -0
  59. data/test/client_test.rb +164 -0
  60. data/test/compatibility/dogstatsd_datagram_compatibility_test.rb +162 -0
  61. data/test/datagram_builder_test.rb +120 -0
  62. data/test/deprecations_test.rb +132 -0
  63. data/test/dogstatsd_datagram_builder_test.rb +32 -0
  64. data/test/environment_test.rb +75 -8
  65. data/test/helpers/rubocop_helper.rb +47 -0
  66. data/test/helpers_test.rb +2 -1
  67. data/test/integration_test.rb +31 -7
  68. data/test/log_sink_test.rb +37 -0
  69. data/test/logger_backend_test.rb +10 -8
  70. data/test/matchers_test.rb +42 -28
  71. data/test/metric_test.rb +18 -22
  72. data/test/null_sink_test.rb +13 -0
  73. data/test/rubocop/measure_as_dist_argument_test.rb +44 -0
  74. data/test/rubocop/metaprogramming_positional_arguments_test.rb +58 -0
  75. data/test/rubocop/metric_prefix_argument_test.rb +38 -0
  76. data/test/rubocop/metric_return_value_test.rb +78 -0
  77. data/test/rubocop/metric_value_keyword_argument_test.rb +39 -0
  78. data/test/rubocop/positional_arguments_test.rb +110 -0
  79. data/test/rubocop/splat_arguments_test.rb +27 -0
  80. data/test/statsd_datagram_builder_test.rb +22 -0
  81. data/test/statsd_instrumentation_test.rb +109 -100
  82. data/test/statsd_test.rb +113 -79
  83. data/test/test_helper.rb +12 -1
  84. data/test/udp_backend_test.rb +38 -36
  85. data/test/udp_sink_test.rb +85 -0
  86. metadata +85 -5
  87. data/.travis.yml +0 -12
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'socket'
2
4
  require 'logger'
3
5
 
@@ -27,10 +29,13 @@ require 'logger'
27
29
  # @!attribute logger
28
30
  # The logger to use in case of any errors. The logger is also used as default logger
29
31
  # for the LoggerBackend (although this can be overwritten).
30
- #
31
32
  # @see StatsD::Instrument::Backends::LoggerBackend
32
33
  # @return [Logger]
33
34
  #
35
+ # @!attribute default_tags
36
+ # The tags to apply to all metrics.
37
+ # @return [Array<String>, Hash<String, String>, nil] The default tags, or <tt>nil</tt> when no default tags is used
38
+ #
34
39
  # @see StatsD::Instrument <tt>StatsD::Instrument</tt> contains module to instrument
35
40
  # existing methods with StatsD metrics.
36
41
  module StatsD
@@ -56,20 +61,26 @@ module StatsD
56
61
  metric_name.respond_to?(:call) ? metric_name.call(callee, args).gsub('::', '.') : metric_name.gsub('::', '.')
57
62
  end
58
63
 
59
- if Process.respond_to?(:clock_gettime)
60
- # @private
61
- def self.duration
62
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
63
- yield
64
- Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
65
- end
66
- else
67
- # @private
68
- def self.duration
69
- start = Time.now
70
- yield
71
- Time.now - start
72
- end
64
+ # Even though this method is considered private, and is no longer used internally,
65
+ # applications in the wild rely on it. As a result, we cannot remove this method
66
+ # until the next major version.
67
+ #
68
+ # @deprecated Use Process.clock_gettime(Process::CLOCK_MONOTONIC) instead.
69
+ def self.current_timestamp
70
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
71
+ end
72
+
73
+ # Even though this method is considered private, and is no longer used internally,
74
+ # applications in the wild rely on it. As a result, we cannot remove this method
75
+ # until the next major version.
76
+ #
77
+ # @deprecated You can implement similar functionality yourself using
78
+ # `Process.clock_gettime(Process::CLOCK_MONOTONIC)`. Think about what will
79
+ # happen if an exception happens during the block execution though.
80
+ def self.duration
81
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
82
+ yield
83
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
73
84
  end
74
85
 
75
86
  # Adds execution duration instrumentation to a method as a timing.
@@ -79,10 +90,18 @@ module StatsD
79
90
  # callable to dynamically generate a metric name
80
91
  # @param metric_options (see StatsD#measure)
81
92
  # @return [void]
82
- def statsd_measure(method, name, *metric_options)
93
+ def statsd_measure(method, name, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil, as_dist: false,
94
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg, prefix: nil, no_prefix: false)
95
+
83
96
  add_to_method(method, name, :measure) do
84
97
  define_method(method) do |*args, &block|
85
- StatsD.measure(StatsD::Instrument.generate_metric_name(name, self, *args), *metric_options) { super(*args, &block) }
98
+ key = StatsD::Instrument.generate_metric_name(name, self, *args)
99
+ prefix ||= StatsD.prefix
100
+ StatsD.measure( # rubocop:disable StatsD/MeasureAsDistArgument, StatsD/MetricPrefixArgument
101
+ key, sample_rate: sample_rate, tags: tags, prefix: prefix, no_prefix: no_prefix, as_dist: as_dist
102
+ ) do
103
+ super(*args, &block)
104
+ end
86
105
  end
87
106
  end
88
107
  end
@@ -95,10 +114,18 @@ module StatsD
95
114
  # @param metric_options (see StatsD#measure)
96
115
  # @return [void]
97
116
  # @note Supported by the datadog implementation only (in beta)
98
- def statsd_distribution(method, name, *metric_options)
117
+ def statsd_distribution(method, name, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
118
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg, prefix: nil, no_prefix: false)
119
+
99
120
  add_to_method(method, name, :distribution) do
100
121
  define_method(method) do |*args, &block|
101
- StatsD.distribution(StatsD::Instrument.generate_metric_name(name, self, *args), *metric_options) { super(*args, &block) }
122
+ key = StatsD::Instrument.generate_metric_name(name, self, *args)
123
+ prefix ||= StatsD.prefix
124
+ StatsD.distribution( # rubocop:disable StatsD/MetricPrefixArgument
125
+ key, sample_rate: sample_rate, tags: tags, prefix: prefix, no_prefix: no_prefix
126
+ ) do
127
+ super(*args, &block)
128
+ end
102
129
  end
103
130
  end
104
131
  end
@@ -118,7 +145,9 @@ module StatsD
118
145
  # @yieldreturn [Boolean] Return true iff the return value is consisered a success, false otherwise.
119
146
  # @return [void]
120
147
  # @see #statsd_count_if
121
- def statsd_count_success(method, name, *metric_options)
148
+ def statsd_count_success(method, name, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
149
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg, prefix: nil, no_prefix: false)
150
+
122
151
  add_to_method(method, name, :count_success) do
123
152
  define_method(method) do |*args, &block|
124
153
  begin
@@ -127,30 +156,40 @@ module StatsD
127
156
  truthiness = false
128
157
  raise
129
158
  else
130
- truthiness = (yield(result) rescue false) if block_given?
159
+ if block_given?
160
+ begin
161
+ truthiness = yield(result)
162
+ rescue
163
+ truthiness = false
164
+ end
165
+ end
131
166
  result
132
167
  ensure
133
168
  suffix = truthiness == false ? 'failure' : 'success'
134
- StatsD.increment("#{StatsD::Instrument.generate_metric_name(name, self, *args)}.#{suffix}", 1, *metric_options)
169
+ key = "#{StatsD::Instrument.generate_metric_name(name, self, *args)}.#{suffix}"
170
+ prefix ||= StatsD.prefix
171
+ StatsD.increment(key, prefix: prefix, # rubocop:disable StatsD/MetricPrefixArgument
172
+ sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
135
173
  end
136
174
  end
137
175
  end
138
176
  end
139
177
 
140
- # Adds success and failure counter instrumentation to a method.
178
+ # Adds success counter instrumentation to a method.
141
179
  #
142
180
  # A method call will be considered successful if it does not raise an exception, and the result is true-y.
143
- # Only for successful calls, the metric will be icnremented
181
+ # Only for successful calls, the metric will be incremented.
144
182
  #
145
183
  # @param method (see #statsd_measure)
146
184
  # @param name (see #statsd_measure)
147
- # @param metric_options (see #statsd_measure)
148
185
  # @yield (see #statsd_count_success)
149
186
  # @yieldparam result (see #statsd_count_success)
150
187
  # @yieldreturn (see #statsd_count_success)
151
188
  # @return [void]
152
189
  # @see #statsd_count_success
153
- def statsd_count_if(method, name, *metric_options)
190
+ def statsd_count_if(method, name, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
191
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg, prefix: nil, no_prefix: false)
192
+
154
193
  add_to_method(method, name, :count_if) do
155
194
  define_method(method) do |*args, &block|
156
195
  begin
@@ -159,10 +198,21 @@ module StatsD
159
198
  truthiness = false
160
199
  raise
161
200
  else
162
- truthiness = (yield(result) rescue false) if block_given?
201
+ if block_given?
202
+ begin
203
+ truthiness = yield(result)
204
+ rescue
205
+ truthiness = false
206
+ end
207
+ end
163
208
  result
164
209
  ensure
165
- StatsD.increment(StatsD::Instrument.generate_metric_name(name, self, *args), *metric_options) if truthiness
210
+ if truthiness
211
+ key = StatsD::Instrument.generate_metric_name(name, self, *args)
212
+ prefix ||= StatsD.prefix
213
+ StatsD.increment(key, prefix: prefix, # rubocop:disable StatsD/MetricPrefixArgument
214
+ sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
215
+ end
166
216
  end
167
217
  end
168
218
  end
@@ -177,10 +227,15 @@ module StatsD
177
227
  # @param name (see #statsd_measure)
178
228
  # @param metric_options (see #statsd_measure)
179
229
  # @return [void]
180
- def statsd_count(method, name, *metric_options)
230
+ def statsd_count(method, name, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
231
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg, prefix: nil, no_prefix: false)
232
+
181
233
  add_to_method(method, name, :count) do
182
234
  define_method(method) do |*args, &block|
183
- StatsD.increment(StatsD::Instrument.generate_metric_name(name, self, *args), 1, *metric_options)
235
+ key = StatsD::Instrument.generate_metric_name(name, self, *args)
236
+ prefix ||= StatsD.prefix
237
+ StatsD.increment(key, prefix: prefix, # rubocop:disable StatsD/MetricPrefixArgument
238
+ sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
184
239
  super(*args, &block)
185
240
  end
186
241
  end
@@ -248,8 +303,13 @@ module StatsD
248
303
  def add_to_method(method, name, action, &block)
249
304
  instrumentation_module = statsd_instrumentation_for(method, name, action)
250
305
 
251
- raise ArgumentError, "already instrumented #{method} for #{self.name}" if instrumentation_module.method_defined?(method)
252
- raise ArgumentError, "could not find method #{method} for #{self.name}" unless method_defined?(method) || private_method_defined?(method)
306
+ if instrumentation_module.method_defined?(method)
307
+ raise ArgumentError, "Already instrumented #{method} for #{self.name}"
308
+ end
309
+
310
+ unless method_defined?(method) || private_method_defined?(method)
311
+ raise ArgumentError, "could not find method #{method} for #{self.name}"
312
+ end
253
313
 
254
314
  method_scope = method_visibility(method)
255
315
 
@@ -263,10 +323,9 @@ module StatsD
263
323
  end
264
324
 
265
325
  def method_visibility(method)
266
- case
267
- when private_method_defined?(method)
326
+ if private_method_defined?(method)
268
327
  :private
269
- when protected_method_defined?(method)
328
+ elsif protected_method_defined?(method)
270
329
  :protected
271
330
  else
272
331
  :public
@@ -275,49 +334,75 @@ module StatsD
275
334
  end
276
335
 
277
336
  attr_accessor :logger, :default_sample_rate, :prefix
278
- attr_writer :backend
337
+ attr_writer :backend, :client
338
+ attr_reader :default_tags
339
+
340
+ def default_tags=(tags)
341
+ @default_tags = StatsD::Instrument::Metric.normalize_tags(tags)
342
+ end
279
343
 
280
344
  def backend
281
345
  @backend ||= StatsD::Instrument::Environment.default_backend
282
346
  end
283
347
 
284
- # Emits a duration metric.
348
+ def client
349
+ @client ||= begin
350
+ require 'statsd/instrument/client'
351
+ StatsD::Instrument::Environment.from_env.default_client
352
+ end
353
+ end
354
+
355
+ # @!method measure(name, value = nil, sample_rate: nil, tags: nil, &block)
356
+ #
357
+ # Emits a timing metric
358
+ #
359
+ # @param [String] key The name of the metric.
360
+ # @param sample_rate (see #increment)
361
+ # @param tags (see #increment)
285
362
  #
286
- # @overload measure(key, value, metric_options = {})
287
- # Emits a measure metric, by providing a duration in milliseconds.
288
- # @param key [String] The name of the metric.
289
- # @param value [Float] The measured duration in milliseconds
290
- # @param metric_options [Hash] Options for the metric
291
- # the key :as_dist will submit the value as a distribution instead of a timing
292
- # (only supported by DataDog's implementation)
293
- # @return [StatsD::Instrument::Metric] The metric that was sent to the backend.
363
+ # @example Providing a value directly
364
+ # start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
365
+ # do_something
366
+ # stop = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
367
+ # http_response = StatsD.measure('HTTP.call.duration', stop - start)
294
368
  #
295
- # @overload measure(key, metric_options = {}, &block)
296
- # Emits a measure metric, after measuring the execution duration of the
369
+ # @example Providing a block to measure the duration of its execution
370
+ # http_response = StatsD.measure('HTTP.call.duration') do
371
+ # Net::HTTP.get(url)
372
+ # end
373
+ #
374
+ # @overload measure(key, value, sample_rate: nil, tags: nil)
375
+ # Emits a timing metric, by providing a duration in milliseconds.
376
+ #
377
+ # @param [Float] value The measured duration in milliseconds
378
+ # @return [void]
379
+ #
380
+ # @overload measure(key, sample_rate: nil, tags: nil, &block)
381
+ # Emits a timing metric, after measuring the execution duration of the
297
382
  # block passed to this method.
298
- # @param key [String] The name of the metric.
299
- # @param metric_options [Hash] Options for the metric
300
- # the key :as_dist sets the metric type to a 'distribution' instead of a 'timing'
301
- # (only supported by DataDog's implementation)
302
- # @yield The method will yield the block that was passed to this method to measure its duration.
303
- # @return The value that was returns by the block passed to this method.
304
- #
305
- # @example
306
- # http_response = StatsD.measure('HTTP.call.duration') do
307
- # HTTP.get(url)
308
- # end
309
- def measure(key, value = nil, *metric_options, &block)
310
- value, metric_options = parse_options(value, metric_options)
311
- type = (!metric_options.empty? && metric_options.first[:as_dist] ? :d : :ms)
312
-
313
- result = nil
314
- value = 1000 * StatsD::Instrument.duration { result = block.call } if block_given?
315
- metric = collect_metric(type, key, value, metric_options)
316
- result = metric unless block_given?
317
- result
383
+ #
384
+ # @yield `StatsD.measure` will yield the block and measure the duration. After the block
385
+ # returns, the duration in millisecond will be emitted as metric.
386
+ # @return The value that was returned by the block passed through.
387
+ def measure(
388
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
389
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
390
+ prefix: StatsD.prefix, no_prefix: false, as_dist: false,
391
+ &block
392
+ )
393
+ # TODO: in the next version, hardcode this to :ms when the as_dist argument is dropped.
394
+ type = as_dist ? :d : :ms
395
+ prefix = nil if no_prefix
396
+ if block_given?
397
+ measure_latency(type, key, sample_rate: sample_rate, tags: tags, prefix: prefix, &block)
398
+ else
399
+ collect_metric(type, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
400
+ end
318
401
  end
319
402
 
403
+ # @!method increment(name, value = 1, sample_rate: nil, tags: nil)
320
404
  # Emits a counter metric.
405
+ #
321
406
  # @param key [String] The name of the metric.
322
407
  # @param value [Integer] The value to increment the counter by.
323
408
  #
@@ -326,135 +411,226 @@ module StatsD
326
411
  # The sample rate is part of the packet that is being sent to the server, and the server
327
412
  # should know how to handle it.
328
413
  #
329
- # @param metric_options [Hash] (default: {}) Metric options
330
- # @return (see #collect_metric)
331
- def increment(key, value = 1, *metric_options)
332
- collect_metric(:c, key, value, metric_options)
414
+ # @param sample_rate [Float] (default: `StatsD.default_sample_rate`) The rate at which to sample
415
+ # this metric call. This value should be between 0 and 1. This value can be used to reduce
416
+ # the amount of network I/O (and CPU cycles) used for very frequent metrics.
417
+ #
418
+ # - A value of `0.1` means that only 1 out of 10 calls will be emitted; the other 9 will
419
+ # be short-circuited.
420
+ # - When set to `1`, every metric will be emitted.
421
+ # - If this parameter is not set, the default sample rate for this client will be used.
422
+ # @param tags [Array<String>, Hash<Symbol, String>] The tags to associate with this measurement.
423
+ # They can be provided as an array of strings, or a hash of key/value pairs.
424
+ # _Note:_ Tags are not supported by all implementations.
425
+ # @return [void]
426
+ def increment(
427
+ key, value_arg = 1, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
428
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
429
+ prefix: StatsD.prefix, no_prefix: false
430
+ )
431
+ prefix = nil if no_prefix
432
+ collect_metric(:c, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
333
433
  end
334
434
 
435
+ # @!method gauge(name, value, sample_rate: nil, tags: nil)
436
+ #
335
437
  # Emits a gauge metric.
336
- # @param key [String] The name of the metric.
438
+ #
439
+ # @param key The name of the metric.
337
440
  # @param value [Numeric] The current value to record.
338
- # @param metric_options [Hash] (default: {}) Metric options
339
- # @return (see #collect_metric)
340
- def gauge(key, value, *metric_options)
341
- collect_metric(:g, key, value, metric_options)
441
+ # @param sample_rate (see #increment)
442
+ # @param tags (see #increment)
443
+ # @return [void]
444
+ def gauge(
445
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
446
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
447
+ prefix: StatsD.prefix, no_prefix: false
448
+ )
449
+ prefix = nil if no_prefix
450
+ collect_metric(:g, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
342
451
  end
343
452
 
344
- # Emits a histogram metric.
453
+ # @!method set(name, value, sample_rate: nil, tags: nil)
454
+ #
455
+ # Emits a set metric, which counts the number of distinct values that have occurred.
456
+ #
457
+ # @example Couning the number of unique visitors
458
+ # StatsD.set('visitors.unique', Current.user.id)
459
+ #
345
460
  # @param key [String] The name of the metric.
346
461
  # @param value [Numeric] The value to record.
347
- # @param metric_options [Hash] (default: {}) Metric options
462
+ # @param sample_rate (see #increment)
463
+ # @param tags (see #increment)
464
+ # @return [void]
465
+ def set(
466
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
467
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
468
+ prefix: StatsD.prefix, no_prefix: false
469
+ )
470
+ prefix = nil if no_prefix
471
+ collect_metric(:s, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
472
+ end
473
+
474
+ # @!method histogram(name, value, sample_rate: nil, tags: nil)
475
+ #
476
+ # Emits a histogram metric.
477
+ #
478
+ # @param key The name of the metric.
479
+ # @param value [Numeric] The value to record.
480
+ # @param sample_rate (see #increment)
481
+ # @param tags (see #increment)
348
482
  # @return (see #collect_metric)
349
483
  # @note Supported by the datadog implementation only.
350
- def histogram(key, value, *metric_options)
351
- collect_metric(:h, key, value, metric_options)
484
+ def histogram(
485
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
486
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
487
+ prefix: StatsD.prefix, no_prefix: false
488
+ )
489
+ prefix = nil if no_prefix
490
+ collect_metric(:h, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
352
491
  end
353
492
 
493
+ # @!method distribution(name, value = nil, sample_rate: nil, tags: nil, &block)
494
+ #
354
495
  # Emits a distribution metric.
355
- # @param key [String] The name of the metric.
356
- # @param value [Numeric] The value to record.
357
- # @param metric_options [Hash] (default: {}) Metric options
358
- # @return (see #collect_metric)
359
- # @note Supported by the datadog implementation only (in beta)
496
+ #
497
+ # @param [String] key The name of the metric.
498
+ # @param sample_rate (see #increment)
499
+ # @param tags (see #increment)
500
+ #
501
+ # @note Supported by the datadog implementation only.
502
+ # @example
503
+ # http_response = StatsD.distribution('HTTP.call.duration') do
504
+ # Net::HTTP.get(url)
505
+ # end
506
+ #
507
+ # @overload distribution(name, value, sample_rate: nil, tags: nil)
508
+ #
509
+ # Emits a distribution metric, given a provided value to record.
510
+ #
511
+ # @param [Numeric] value The value to record.
512
+ # @return [void]
360
513
  #
361
514
  # @overload distribution(key, metric_options = {}, &block)
362
- # Emits a distribution metric, after measuring the execution duration of the
363
- # block passed to this method.
364
- # @param key [String] The name of the metric.
365
- # @param metric_options [Hash] Options for the metric
366
- # @yield The method will yield the block that was passed to this method to measure its duration.
367
- # @return The value that was returns by the block passed to this method.
368
- # @note Supported by the datadog implementation only.
369
- #
370
- # @example
371
- # http_response = StatsD.distribution('HTTP.call.duration') do
372
- # HTTP.get(url)
373
- # end
374
- def distribution(key, value=nil, *metric_options, &block)
375
- value, metric_options = parse_options(value, metric_options)
376
- result = nil
377
- value = 1000 * StatsD::Instrument.duration { result = block.call } if block_given?
378
- metric = collect_metric(:d, key, value, metric_options)
379
- result = metric unless block_given?
380
- result
515
+ #
516
+ # Emits a distribution metric for the duration of the provided block, in milliseconds.
517
+ #
518
+ # @yield `StatsD.distribution` will yield the block and measure the duration. After
519
+ # the block returns, the duration in millisecond will be emitted as metric.
520
+ # @return The value that was returned by the block passed through.
521
+ def distribution(
522
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
523
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
524
+ prefix: StatsD.prefix, no_prefix: false,
525
+ &block
526
+ )
527
+ prefix = nil if no_prefix
528
+ if block_given?
529
+ measure_latency(:d, key, sample_rate: sample_rate, tags: tags, prefix: prefix, &block)
530
+ else
531
+ collect_metric(:d, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
532
+ end
381
533
  end
382
534
 
535
+ # @!method key_value(name, value)
536
+ #
383
537
  # Emits a key/value metric.
538
+ #
384
539
  # @param key [String] The name of the metric.
385
540
  # @param value [Numeric] The value to record.
386
- # @param metric_options [Hash] (default: {}) Metric options
387
- # @return (see #collect_metric)
541
+ # @return [void]
542
+ #
388
543
  # @note Supported by the statsite implementation only.
389
- def key_value(key, value, *metric_options)
390
- collect_metric(:kv, key, value, metric_options)
391
- end
392
-
393
- # Emits a set metric.
394
- # @param key [String] The name of the metric.
395
- # @param value [Numeric] The value to record.
396
- # @param metric_options [Hash] (default: {}) Metric options
397
- # @return (see #collect_metric)
398
- # @note Supported by the datadog implementation only.
399
- def set(key, value, *metric_options)
400
- collect_metric(:s, key, value, metric_options)
544
+ def key_value(
545
+ key, value_arg = nil, deprecated_sample_rate_arg = nil,
546
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, no_prefix: false
547
+ )
548
+ prefix = nil if no_prefix
549
+ collect_metric(:kv, key, value, sample_rate: sample_rate, prefix: prefix)
401
550
  end
402
551
 
403
- # Emits an event metric.
404
- # @param title [String] Title of the event.
405
- # @param text [String] Body of the event.
406
- # @param metric_options [Hash] (default: {}) Metric options
407
- # @return (see #collect_metric)
408
- # @note Supported by the datadog implementation only.
409
- def event(title, text, *metric_options)
410
- collect_metric(:_e, title, text, metric_options)
552
+ # @!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
553
+ #
554
+ # Emits an event.
555
+ #
556
+ # @param title [String] Title of the event. A configured prefix may be applied to this title.
557
+ # @param text [String] Body of the event. Can contain newlines.
558
+ # @param [String] hostname The hostname to associate with the event.
559
+ # @param [Time] timestamp The moment the status of the service was checkes. Defaults to now.
560
+ # @param [String] aggregation_key A key to aggregate similar events into groups.
561
+ # @param [String] priority The event's priority, either `"low"` or `"normal"` (default).
562
+ # @param [String] source_type_name The source type.
563
+ # @param [String] alert_type The type of alert. Either `"info"` (default), `"warning"`, `"error"`, or `"success"`.
564
+ # @param tags (see #increment)
565
+ # @return [void]
566
+ #
567
+ # @note Supported by the Datadog implementation only.
568
+ def event(
569
+ title, text,
570
+ deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
571
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
572
+ prefix: StatsD.prefix, no_prefix: false,
573
+ hostname: nil, date_happened: nil, timestamp: date_happened,
574
+ aggregation_key: nil, priority: nil, source_type_name: nil, alert_type: nil,
575
+ **_ignored
576
+ )
577
+ prefix = nil if no_prefix
578
+ collect_metric(:_e, title, text, sample_rate: sample_rate, tags: tags, prefix: prefix, metadata: {
579
+ hostname: hostname, timestamp: timestamp, aggregation_key: aggregation_key,
580
+ priority: priority, source_type_name: source_type_name, alert_type: alert_type
581
+ })
411
582
  end
412
583
 
413
- # Emits a service check metric.
414
- # @param title [String] Title of the event.
415
- # @param text [String] Body of the event.
416
- # @param metric_options [Hash] (default: {}) Metric options
417
- # @return (see #collect_metric)
418
- # @note Supported by the datadog implementation only.
419
- def service_check(name, status, *metric_options)
420
- collect_metric(:_sc, name, status, metric_options)
584
+ # @!method service_check(name, status, tags: nil, hostname: nil, timestamp: nil, message: nil)
585
+ #
586
+ # Emits a service check.
587
+ #
588
+ # @param [String] name Name of the service. A configured prefix may be applied to this title.
589
+ # @param [Symbol] status Current status of the service. Either `:ok`, `:warning`, `:critical`, or `:unknown`.
590
+ # @param [String] hostname The hostname to associate with the event.
591
+ # @param [Time] timestamp The moment the status of the service was checkes. Defaults to now.
592
+ # @param [String] message A message that describes the current status.
593
+ # @param tags (see #increment)
594
+ # @return [void]
595
+ #
596
+ # @note Supported by the Datadog implementation only.
597
+ def service_check(
598
+ name, status,
599
+ deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
600
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
601
+ prefix: StatsD.prefix, no_prefix: false,
602
+ hostname: nil, timestamp: nil, message: nil, **_ignored
603
+ )
604
+ prefix = nil if no_prefix
605
+ collect_metric(:_sc, name, status, sample_rate: sample_rate, prefix: prefix, tags: tags, metadata: {
606
+ hostname: hostname, timestamp: timestamp, message: message
607
+ })
421
608
  end
422
609
 
423
610
  private
424
611
 
425
- # Converts old-style ordered arguments in an argument hash for backwards compatibility.
426
- # @param args [Array] The list of non-required arguments.
427
- # @return [Hash] The hash of optional arguments.
428
- def hash_argument(args)
429
- return {} if args.length == 0
430
- return args.first if args.length == 1 && args.first.is_a?(Hash)
431
-
432
- order = [:sample_rate, :tags]
433
- hash = {}
434
- args.each_with_index do |value, index|
435
- hash[order[index]] = value
612
+ def measure_latency(type, key, sample_rate:, tags:, prefix:)
613
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
614
+ begin
615
+ yield
616
+ ensure
617
+ # Ensure catches both a raised exception and a return in the invoked block
618
+ value = 1000.0 * (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
619
+ collect_metric(type, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
436
620
  end
437
-
438
- return hash
439
- end
440
-
441
- def parse_options(value, metric_options)
442
- if value.is_a?(Hash) && metric_options.empty?
443
- metric_options = [value]
444
- value = value.fetch(:value, nil)
445
- end
446
- [value, metric_options]
447
621
  end
448
622
 
449
623
  # Instantiates a metric, and sends it to the backend for further processing.
450
624
  # @param options (see StatsD::Instrument::Metric#initialize)
451
- # @return [StatsD::Instrument::Metric] The metric that was sent to the backend.
452
- def collect_metric(type, name, value, metric_options)
453
- value, metric_options = parse_options(value, metric_options)
454
-
455
- options = hash_argument(metric_options).merge(type: type, name: name, value: value)
456
- backend.collect_metric(metric = StatsD::Instrument::Metric.new(options))
457
- metric
625
+ # @return [void]
626
+ def collect_metric(type, name, value, sample_rate:, tags: nil, prefix:, metadata: nil)
627
+ sample_rate ||= default_sample_rate
628
+ name = "#{prefix}.#{name}" if prefix
629
+
630
+ metric = StatsD::Instrument::Metric.new(type: type, name: name, value: value,
631
+ sample_rate: sample_rate, tags: tags, metadata: metadata)
632
+ backend.collect_metric(metric)
633
+ metric # TODO: return `nil` in the next major version
458
634
  end
459
635
  end
460
636
 
@@ -466,4 +642,5 @@ require 'statsd/instrument/helpers'
466
642
  require 'statsd/instrument/assertions'
467
643
  require 'statsd/instrument/metric_expectation'
468
644
  require 'statsd/instrument/matchers' if defined?(::RSpec)
469
- require 'statsd/instrument/railtie' if defined?(Rails)
645
+ require 'statsd/instrument/railtie' if defined?(::Rails::Railtie)
646
+ require 'statsd/instrument/strict' if ENV['STATSD_STRICT_MODE']