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,37 @@
|
|
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 specifying the `prefix` keyword argument on `StatsD` metric
|
9
|
+
# methods and `statsd_*` metaprogramming methods. To run this cop on your codebase:
|
10
|
+
#
|
11
|
+
# rubocop --require `bundle show statsd-instrument`/lib/statsd/instrument/rubocop.rb \
|
12
|
+
# --only StatsD/MetricPrefixArgument
|
13
|
+
#
|
14
|
+
# This cop will not autocorrect offenses.
|
15
|
+
class MetricPrefixArgument < Cop
|
16
|
+
include RuboCop::Cop::StatsD
|
17
|
+
|
18
|
+
MSG = <<~MSG
|
19
|
+
Do not use StatsD.metric(..., prefix: "foo"). The prefix argument is deprecated.
|
20
|
+
|
21
|
+
You can simply include the prefix in the metric name instead.
|
22
|
+
If you want to override the global prefix, you can set `no_prefix: true`.
|
23
|
+
MSG
|
24
|
+
|
25
|
+
def on_send(node)
|
26
|
+
if metric_method?(node)
|
27
|
+
add_offense(node) if has_keyword_argument?(node, :prefix)
|
28
|
+
end
|
29
|
+
|
30
|
+
if metaprogramming_method?(node)
|
31
|
+
add_offense(node) if has_keyword_argument?(node, :prefix)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,32 @@
|
|
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 using the return value of StatsD metric calls, which is deprecated.
|
9
|
+
# To check your codebase, use the following Rubocop invocation:
|
10
|
+
#
|
11
|
+
# rubocop --require `bundle show statsd-instrument`/lib/statsd/instrument/rubocop.rb \
|
12
|
+
# --only StatsD/MetricReturnValue
|
13
|
+
#
|
14
|
+
# This cop cannot autocorrect offenses. In production code, StatsD should be used in a fire-and-forget
|
15
|
+
# fashion. This means that you shouldn't rely on the return value. If you really need to access the
|
16
|
+
# emitted metrics, you can look into `capture_statsd_calls`
|
17
|
+
class MetricReturnValue < Cop
|
18
|
+
include RuboCop::Cop::StatsD
|
19
|
+
|
20
|
+
MSG = 'Do not use the return value of StatsD metric methods'
|
21
|
+
|
22
|
+
INVALID_PARENTS = %i{lvasgn array pair send return yield}
|
23
|
+
|
24
|
+
def on_send(node)
|
25
|
+
if metric_method?(node) && node.arguments.last&.type != :block_pass
|
26
|
+
add_offense(node.parent) if INVALID_PARENTS.include?(node.parent&.type)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
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 providing the value for a metric using a keyword argument, which is
|
9
|
+
# deprecated. Use the following Rubocop invocation to check your project's codebase:
|
10
|
+
#
|
11
|
+
# rubocop --require \
|
12
|
+
# `bundle show statsd-instrument`/lib/statsd/instrument/rubocop.rb \
|
13
|
+
# --only StatsD/MetricValueKeywordArgument
|
14
|
+
#
|
15
|
+
# This cop will not autocorrect offenses. Most of the time, these are easy to fix by providing the
|
16
|
+
# value as the second argument, rather than a keyword argument.
|
17
|
+
#
|
18
|
+
# `StatsD.increment('foo', value: 3)` => `StatsD.increment('foo', 3)`
|
19
|
+
class MetricValueKeywordArgument < Cop
|
20
|
+
include RuboCop::Cop::StatsD
|
21
|
+
|
22
|
+
MSG = <<~MSG
|
23
|
+
Do not use the StatsD.metric('name', value: <value>, ...). The `value` keyword argument is deprecated.
|
24
|
+
|
25
|
+
Use a positional argument instead: StatsD.metric('name', <value>, ...).
|
26
|
+
MSG
|
27
|
+
|
28
|
+
def on_send(node)
|
29
|
+
if metric_method?(node) && has_keyword_argument?(node, :value)
|
30
|
+
add_offense(node)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,99 @@
|
|
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 using the StatsD metric methods (e.g. `StatsD.instrument`)
|
9
|
+
# for positional argument usage, which is deprecated.
|
10
|
+
#
|
11
|
+
# Use the following Rubocop invocation to check your project's codebase:
|
12
|
+
#
|
13
|
+
# rubocop --require `bundle show statsd-instrument`/lib/statsd/instrument/rubocop.rb \
|
14
|
+
# --only StatsD/PositionalArguments
|
15
|
+
#
|
16
|
+
# This cop can autocorrect some offenses it finds, but not all of them.
|
17
|
+
class PositionalArguments < Cop
|
18
|
+
include RuboCop::Cop::StatsD
|
19
|
+
|
20
|
+
MSG = 'Use keyword arguments for StatsD calls'
|
21
|
+
|
22
|
+
POSITIONAL_ARGUMENT_TYPES = Set[:int, :float, :nil]
|
23
|
+
UNKNOWN_ARGUMENT_TYPES = Set[:send, :const, :lvar, :splat]
|
24
|
+
REFUSED_ARGUMENT_TYPES = POSITIONAL_ARGUMENT_TYPES | UNKNOWN_ARGUMENT_TYPES
|
25
|
+
|
26
|
+
KEYWORD_ARGUMENT_TYPES = Set[:hash]
|
27
|
+
BLOCK_ARGUMENT_TYPES = Set[:block_pass]
|
28
|
+
ACCEPTED_ARGUMENT_TYPES = KEYWORD_ARGUMENT_TYPES | BLOCK_ARGUMENT_TYPES
|
29
|
+
|
30
|
+
def on_send(node)
|
31
|
+
if metric_method?(node) && node.arguments.length >= 3
|
32
|
+
case node.arguments[2].type
|
33
|
+
when *REFUSED_ARGUMENT_TYPES
|
34
|
+
add_offense(node)
|
35
|
+
when *ACCEPTED_ARGUMENT_TYPES
|
36
|
+
nil
|
37
|
+
else
|
38
|
+
$stderr.puts "[StatsD/PositionalArguments] Unhandled argument type: #{node.arguments[2].type.inspect}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def autocorrect(node)
|
44
|
+
-> (corrector) do
|
45
|
+
positial_arguments = if node.arguments.last.type == :block_pass
|
46
|
+
node.arguments[2...node.arguments.length - 1]
|
47
|
+
else
|
48
|
+
node.arguments[2...node.arguments.length]
|
49
|
+
end
|
50
|
+
|
51
|
+
case positial_arguments[0].type
|
52
|
+
when *UNKNOWN_ARGUMENT_TYPES
|
53
|
+
# We don't know whether the method returns a hash, in which case it would be interpreted
|
54
|
+
# as keyword arguments. In this case, the fix would be to add a keywordf splat:
|
55
|
+
#
|
56
|
+
# `StatsD.instrument('foo', 1, method_call)`
|
57
|
+
# => `StatsD.instrument('foo', 1, **method_call)`
|
58
|
+
#
|
59
|
+
# However, it's also possible this method returns a sample rate, in which case the fix
|
60
|
+
# above will not do the right thing.
|
61
|
+
#
|
62
|
+
# `StatsD.instrument('foo', 1, SAMPLE_RATE_CONSTANT)`
|
63
|
+
# => `StatsD.instrument('foo', 1, sample_rate: SAMPLE_RATE_CONSTANT)`
|
64
|
+
#
|
65
|
+
# Because of this, we will not auto-correct and let the user fix the issue manually.
|
66
|
+
return
|
67
|
+
|
68
|
+
when *POSITIONAL_ARGUMENT_TYPES
|
69
|
+
value_argument = node.arguments[1]
|
70
|
+
from = value_argument.source_range.end_pos
|
71
|
+
to = positial_arguments.last.source_range.end_pos
|
72
|
+
range = Parser::Source::Range.new(node.source_range.source_buffer, from, to)
|
73
|
+
corrector.remove(range)
|
74
|
+
|
75
|
+
keyword_arguments = []
|
76
|
+
sample_rate = positial_arguments[0]
|
77
|
+
if sample_rate && sample_rate.type != :nil
|
78
|
+
keyword_arguments << "sample_rate: #{sample_rate.source}"
|
79
|
+
end
|
80
|
+
|
81
|
+
tags = positial_arguments[1]
|
82
|
+
if tags && tags.type != :nil
|
83
|
+
keyword_arguments << if tags.type == :hash && tags.source[0] != '{'
|
84
|
+
"tags: { #{tags.source} }"
|
85
|
+
else
|
86
|
+
"tags: #{tags.source}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
unless keyword_arguments.empty?
|
91
|
+
corrector.insert_after(value_argument.source_range, ", #{keyword_arguments.join(', ')}")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,31 @@
|
|
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 using splat arguments (*args) in StatsD metric calls. To run
|
9
|
+
# this rule on your codebase, invoke Rubocop this way:
|
10
|
+
#
|
11
|
+
# rubocop --require \
|
12
|
+
# `bundle show statsd-instrument`/lib/statsd/instrument/rubocop.rb \
|
13
|
+
# --only StatsD/SplatArguments
|
14
|
+
#
|
15
|
+
# This cop will not autocorrect offenses.
|
16
|
+
class SplatArguments < Cop
|
17
|
+
include RuboCop::Cop::StatsD
|
18
|
+
|
19
|
+
MSG = 'Do not use splat arguments in StatsD metric calls'
|
20
|
+
|
21
|
+
def on_send(node)
|
22
|
+
if metric_method?(node)
|
23
|
+
if node.arguments.any? { |arg| arg.type == :splat }
|
24
|
+
add_offense(node)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module StatsD
|
6
|
+
METRIC_METHODS = %i{
|
7
|
+
increment
|
8
|
+
gauge
|
9
|
+
measure
|
10
|
+
set
|
11
|
+
histogram
|
12
|
+
distribution
|
13
|
+
key_value
|
14
|
+
}
|
15
|
+
|
16
|
+
METAPROGRAMMING_METHODS = %i{
|
17
|
+
statsd_measure
|
18
|
+
statsd_distribution
|
19
|
+
statsd_count_success
|
20
|
+
statsd_count_if
|
21
|
+
statsd_count
|
22
|
+
}
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def metaprogramming_method?(node)
|
27
|
+
METAPROGRAMMING_METHODS.include?(node.method_name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def metric_method?(node)
|
31
|
+
node.receiver&.type == :const &&
|
32
|
+
node.receiver&.const_name == "StatsD" &&
|
33
|
+
METRIC_METHODS.include?(node.method_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_keyword_argument?(node, sym)
|
37
|
+
if (kwargs = keyword_arguments(node))
|
38
|
+
kwargs.child_nodes.detect do |pair|
|
39
|
+
pair.child_nodes[0]&.type == :sym && pair.child_nodes[0].value == sym
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def keyword_arguments(node)
|
45
|
+
return nil if node.arguments.empty?
|
46
|
+
last_argument = if node.arguments.last&.type == :block_pass
|
47
|
+
node.arguments[node.arguments.length - 2]
|
48
|
+
else
|
49
|
+
node.arguments[node.arguments.length - 1]
|
50
|
+
end
|
51
|
+
|
52
|
+
last_argument&.type == :hash ? last_argument : nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
require_relative 'rubocop/metaprogramming_positional_arguments'
|
59
|
+
require_relative 'rubocop/metric_return_value'
|
60
|
+
require_relative 'rubocop/metric_value_keyword_argument'
|
61
|
+
require_relative 'rubocop/positional_arguments'
|
62
|
+
require_relative 'rubocop/splat_arguments'
|
63
|
+
require_relative 'rubocop/measure_as_dist_argument'
|
64
|
+
require_relative 'rubocop/metric_prefix_argument'
|
@@ -0,0 +1,14 @@
|
|
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::StatsDDatagramBuilder < StatsD::Instrument::DatagramBuilder
|
6
|
+
unsupported_datagram_types :h, :d, :kv
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def normalize_tags(tags)
|
11
|
+
raise NotImplementedError, "#{self.class.name} does not support tags" if tags
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'statsd-instrument' unless Object.const_defined?(:StatsD)
|
4
|
+
|
5
|
+
module StatsD
|
6
|
+
module Instrument
|
7
|
+
UNSPECIFIED = Object.new.freeze
|
8
|
+
private_constant :UNSPECIFIED
|
9
|
+
|
10
|
+
# The Strict monkeypatch can be loaded to see if you're using the StatsD library in
|
11
|
+
# a deprecated way.
|
12
|
+
#
|
13
|
+
# - The metric methods are not retuning a Metric instance.
|
14
|
+
# - Only accept keyword arguments for tags and sample_rate, rather than position arguments.
|
15
|
+
# - Only accept a position argument for value, rather than a keyword argument.
|
16
|
+
# - The provided arguments have the right type.
|
17
|
+
#
|
18
|
+
# You can enable thois monkeypatch by changing your Gemfile as follows:
|
19
|
+
#
|
20
|
+
# gem 'statsd-instrument', require: 'statsd/instrument/strict'
|
21
|
+
#
|
22
|
+
# By doing this as part of your QA/CI, you can find where you are still using deprecated patterns,
|
23
|
+
# and fix them before the deprecated behavior is removed in the next major version.
|
24
|
+
#
|
25
|
+
# This monkeypatch is not meant to be used in production.
|
26
|
+
module Strict
|
27
|
+
def increment(key, value = 1, sample_rate: nil, tags: nil, no_prefix: false)
|
28
|
+
raise ArgumentError, "StatsD.increment does not accept a block" if block_given?
|
29
|
+
raise ArgumentError, "The value argument should be an integer, got #{value.inspect}" unless value.is_a?(Integer)
|
30
|
+
check_tags_and_sample_rate(sample_rate, tags)
|
31
|
+
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
def gauge(key, value, sample_rate: nil, tags: nil, no_prefix: false)
|
36
|
+
raise ArgumentError, "StatsD.increment does not accept a block" if block_given?
|
37
|
+
raise ArgumentError, "The value argument should be an integer, got #{value.inspect}" unless value.is_a?(Numeric)
|
38
|
+
check_tags_and_sample_rate(sample_rate, tags)
|
39
|
+
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
def histogram(key, value, sample_rate: nil, tags: nil, no_prefix: false)
|
44
|
+
raise ArgumentError, "StatsD.increment does not accept a block" if block_given?
|
45
|
+
raise ArgumentError, "The value argument should be an integer, got #{value.inspect}" unless value.is_a?(Numeric)
|
46
|
+
check_tags_and_sample_rate(sample_rate, tags)
|
47
|
+
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
def set(key, value, sample_rate: nil, tags: nil, no_prefix: false)
|
52
|
+
raise ArgumentError, "StatsD.set does not accept a block" if block_given?
|
53
|
+
check_tags_and_sample_rate(sample_rate, tags)
|
54
|
+
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
def service_check(name, status, tags: nil, no_prefix: false,
|
59
|
+
hostname: nil, timestamp: nil, message: nil)
|
60
|
+
|
61
|
+
super
|
62
|
+
end
|
63
|
+
|
64
|
+
def event(title, text, tags: nil, no_prefix: false,
|
65
|
+
hostname: nil, timestamp: nil, aggregation_key: nil, priority: nil, source_type_name: nil, alert_type: nil)
|
66
|
+
|
67
|
+
super
|
68
|
+
end
|
69
|
+
|
70
|
+
def measure(key, value = UNSPECIFIED, sample_rate: nil, tags: nil, no_prefix: false, &block)
|
71
|
+
check_block_or_numeric_value(value, &block)
|
72
|
+
check_tags_and_sample_rate(sample_rate, tags)
|
73
|
+
|
74
|
+
super
|
75
|
+
end
|
76
|
+
|
77
|
+
def distribution(key, value = UNSPECIFIED, sample_rate: nil, tags: nil, no_prefix: false, &block)
|
78
|
+
check_block_or_numeric_value(value, &block)
|
79
|
+
check_tags_and_sample_rate(sample_rate, tags)
|
80
|
+
|
81
|
+
super
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def check_block_or_numeric_value(value)
|
87
|
+
if block_given?
|
88
|
+
raise ArgumentError, "The value argument should not be set when providing a block" unless value == UNSPECIFIED
|
89
|
+
else
|
90
|
+
raise ArgumentError, "The value argument should be a number, got #{value.inspect}" unless value.is_a?(Numeric)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def check_tags_and_sample_rate(sample_rate, tags)
|
95
|
+
unless sample_rate.nil? || sample_rate.is_a?(Numeric)
|
96
|
+
raise ArgumentError, "The sample_rate argument should be a number, got #{sample_rate}"
|
97
|
+
end
|
98
|
+
unless tags.nil? || tags.is_a?(Hash) || tags.is_a?(Array)
|
99
|
+
raise ArgumentError, "The tags argument should be a hash or an array, got #{tags.inspect}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def collect_metric(type, name, value, sample_rate:, tags: nil, prefix:, metadata: nil)
|
104
|
+
super
|
105
|
+
nil # We explicitly discard the return value, so people cannot depend on it.
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
module StrictMetaprogramming
|
110
|
+
def statsd_measure(method, name, sample_rate: nil, tags: nil, no_prefix: false)
|
111
|
+
check_method_and_metric_name(method, name)
|
112
|
+
|
113
|
+
# Unfortunately, we have to inline the new method implementation ebcause we have to fix the
|
114
|
+
# Stats.measure call to not use the `as_dist` and `prefix` arguments.
|
115
|
+
add_to_method(method, name, :measure) do
|
116
|
+
define_method(method) do |*args, &block|
|
117
|
+
key = StatsD::Instrument.generate_metric_name(name, self, *args)
|
118
|
+
StatsD.measure(key, sample_rate: sample_rate, tags: tags, no_prefix: no_prefix) do
|
119
|
+
super(*args, &block)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def statsd_distribution(method, name, sample_rate: nil, tags: nil, no_prefix: false)
|
126
|
+
check_method_and_metric_name(method, name)
|
127
|
+
|
128
|
+
# Unfortunately, we have to inline the new method implementation ebcause we have to fix the
|
129
|
+
# Stats.distribution call to not use the `prefix` argument.
|
130
|
+
|
131
|
+
add_to_method(method, name, :distribution) do
|
132
|
+
define_method(method) do |*args, &block|
|
133
|
+
key = StatsD::Instrument.generate_metric_name(name, self, *args)
|
134
|
+
StatsD.distribution(key, sample_rate: sample_rate, tags: tags, no_prefix: no_prefix) do
|
135
|
+
super(*args, &block)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def statsd_count_success(method, name, sample_rate: nil, tags: nil, no_prefix: false)
|
142
|
+
check_method_and_metric_name(method, name)
|
143
|
+
|
144
|
+
# Unfortunately, we have to inline the new method implementation ebcause we have to fix the
|
145
|
+
# Stats.increment call to not use the `prefix` argument.
|
146
|
+
|
147
|
+
add_to_method(method, name, :count_success) do
|
148
|
+
define_method(method) do |*args, &block|
|
149
|
+
begin
|
150
|
+
truthiness = result = super(*args, &block)
|
151
|
+
rescue
|
152
|
+
truthiness = false
|
153
|
+
raise
|
154
|
+
else
|
155
|
+
if block_given?
|
156
|
+
begin
|
157
|
+
truthiness = yield(result)
|
158
|
+
rescue
|
159
|
+
truthiness = false
|
160
|
+
end
|
161
|
+
end
|
162
|
+
result
|
163
|
+
ensure
|
164
|
+
suffix = truthiness == false ? 'failure' : 'success'
|
165
|
+
key = "#{StatsD::Instrument.generate_metric_name(name, self, *args)}.#{suffix}"
|
166
|
+
StatsD.increment(key, sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def statsd_count_if(method, name, sample_rate: nil, tags: nil, no_prefix: false)
|
173
|
+
check_method_and_metric_name(method, name)
|
174
|
+
|
175
|
+
# Unfortunately, we have to inline the new method implementation ebcause we have to fix the
|
176
|
+
# Stats.increment call to not use the `prefix` argument.
|
177
|
+
|
178
|
+
add_to_method(method, name, :count_if) do
|
179
|
+
define_method(method) do |*args, &block|
|
180
|
+
begin
|
181
|
+
truthiness = result = super(*args, &block)
|
182
|
+
rescue
|
183
|
+
truthiness = false
|
184
|
+
raise
|
185
|
+
else
|
186
|
+
if block_given?
|
187
|
+
begin
|
188
|
+
truthiness = yield(result)
|
189
|
+
rescue
|
190
|
+
truthiness = false
|
191
|
+
end
|
192
|
+
end
|
193
|
+
result
|
194
|
+
ensure
|
195
|
+
if truthiness
|
196
|
+
key = StatsD::Instrument.generate_metric_name(name, self, *args)
|
197
|
+
StatsD.increment(key, sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def statsd_count(method, name, sample_rate: nil, tags: nil, no_prefix: false)
|
205
|
+
check_method_and_metric_name(method, name)
|
206
|
+
|
207
|
+
# Unfortunately, we have to inline the new method implementation ebcause we have to fix the
|
208
|
+
# Stats.increment call to not use the `prefix` argument.
|
209
|
+
|
210
|
+
add_to_method(method, name, :count) do
|
211
|
+
define_method(method) do |*args, &block|
|
212
|
+
key = StatsD::Instrument.generate_metric_name(name, self, *args)
|
213
|
+
StatsD.increment(key, sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
|
214
|
+
super(*args, &block)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
|
221
|
+
def check_method_and_metric_name(method, metric_name)
|
222
|
+
unless method.is_a?(Symbol)
|
223
|
+
raise ArgumentError, "The method name should be provided as symbol, got #{method.inspect}"
|
224
|
+
end
|
225
|
+
|
226
|
+
unless metric_name.is_a?(String) || metric_name.is_a?(Proc)
|
227
|
+
raise ArgumentError, "The metric name should be a proc or string, got #{metric_name.inspect}"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
StatsD.singleton_class.prepend(StatsD::Instrument::Strict)
|
235
|
+
StatsD::Instrument.prepend(StatsD::Instrument::StrictMetaprogramming)
|
@@ -0,0 +1,62 @@
|
|
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::UDPSink
|
6
|
+
def self.for_addr(addr)
|
7
|
+
host, port_as_string = addr.split(':', 2)
|
8
|
+
new(host, Integer(port_as_string))
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :host, :port
|
12
|
+
|
13
|
+
def initialize(host, port)
|
14
|
+
@host = host
|
15
|
+
@port = port
|
16
|
+
@mutex = Mutex.new
|
17
|
+
@socket = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def sample?(sample_rate)
|
21
|
+
sample_rate == 1 || rand < sample_rate
|
22
|
+
end
|
23
|
+
|
24
|
+
def <<(datagram)
|
25
|
+
with_socket { |socket| socket.send(datagram, 0) > 0 }
|
26
|
+
self
|
27
|
+
|
28
|
+
rescue ThreadError
|
29
|
+
# In cases where a TERM or KILL signal has been sent, and we send stats as
|
30
|
+
# part of a signal handler, locks cannot be acquired, so we do our best
|
31
|
+
# to try and send the datagram without a lock.
|
32
|
+
socket.send(datagram, 0) > 0
|
33
|
+
|
34
|
+
rescue SocketError, IOError, SystemCallError
|
35
|
+
# TODO: log?
|
36
|
+
invalidate_socket
|
37
|
+
end
|
38
|
+
|
39
|
+
def addr
|
40
|
+
"#{host}:#{port}"
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def with_socket
|
46
|
+
@mutex.synchronize { yield(socket) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def socket
|
50
|
+
if @socket.nil?
|
51
|
+
@socket = UDPSocket.new
|
52
|
+
@socket.connect(@host, @port)
|
53
|
+
end
|
54
|
+
@socket
|
55
|
+
end
|
56
|
+
|
57
|
+
def invalidate_socket
|
58
|
+
@mutex.synchronize do
|
59
|
+
@socket = nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|