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.
- 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
@@ -1,25 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'logger'
|
2
4
|
|
3
5
|
# The environment module is used to detect, and initialize the environment in
|
4
6
|
# which this library is active. It will use different default values based on the environment.
|
5
|
-
|
6
|
-
|
7
|
+
class StatsD::Instrument::Environment
|
8
|
+
class << self
|
9
|
+
def from_env
|
10
|
+
@from_env ||= StatsD::Instrument::Environment.new(ENV)
|
11
|
+
end
|
7
12
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
# Detects the current environment, either by asking Rails, or by inspecting environment variables.
|
14
|
+
#
|
15
|
+
# - Within a Rails application, <tt>Rails.env</tt> is used.
|
16
|
+
# - It will check the following environment variables in order: <tt>RAILS_ENV</tt>, <tt>RACK_ENV</tt>, <tt>ENV</tt>.
|
17
|
+
# - If none of these are set, it will return <tt>development</tt>
|
18
|
+
#
|
19
|
+
# @return [String] The detected environment.
|
20
|
+
def environment
|
21
|
+
from_env.environment
|
22
|
+
end
|
23
|
+
|
24
|
+
# Instantiates a default backend for the current environment.
|
25
|
+
#
|
26
|
+
# @return [StatsD::Instrument::Backend]
|
27
|
+
# @see #environment
|
28
|
+
def default_backend
|
29
|
+
case environment
|
30
|
+
when 'production', 'staging'
|
31
|
+
StatsD::Instrument::Backends::UDPBackend.new(from_env.statsd_addr, from_env.statsd_implementation)
|
32
|
+
when 'test'
|
33
|
+
StatsD::Instrument::Backends::NullBackend.new
|
34
|
+
else
|
35
|
+
StatsD::Instrument::Backends::LoggerBackend.new(StatsD.logger)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sets default values for sample rate and logger.
|
40
|
+
#
|
41
|
+
# - Default sample rate is set to the value in the STATSD_SAMPLE_RATE environment variable,
|
42
|
+
# or 1.0 otherwise. See {StatsD#default_sample_rate}
|
43
|
+
# - {StatsD#logger} is set to a logger that send output to stderr.
|
44
|
+
#
|
45
|
+
# If you are including this library inside a Rails environment, additional initialization will
|
46
|
+
# be done as part of the {StatsD::Instrument::Railtie}.
|
47
|
+
#
|
48
|
+
# @return [void]
|
49
|
+
def setup
|
50
|
+
StatsD.prefix = from_env.statsd_prefix
|
51
|
+
StatsD.default_tags = from_env.statsd_default_tags
|
52
|
+
StatsD.default_sample_rate = from_env.statsd_sample_rate
|
53
|
+
StatsD.logger = Logger.new($stderr)
|
20
54
|
end
|
21
55
|
end
|
22
56
|
|
57
|
+
attr_reader :env
|
58
|
+
|
59
|
+
def initialize(env)
|
60
|
+
@env = env
|
61
|
+
end
|
62
|
+
|
23
63
|
# Detects the current environment, either by asking Rails, or by inspecting environment variables.
|
24
64
|
#
|
25
65
|
# - Within a Rails application, <tt>Rails.env</tt> is used.
|
@@ -28,26 +68,65 @@ module StatsD::Instrument::Environment
|
|
28
68
|
#
|
29
69
|
# @return [String] The detected environment.
|
30
70
|
def environment
|
31
|
-
if
|
71
|
+
if env['STATSD_ENV']
|
72
|
+
env['STATSD_ENV']
|
73
|
+
elsif defined?(Rails) && Rails.respond_to?(:env)
|
32
74
|
Rails.env.to_s
|
33
75
|
else
|
34
|
-
|
76
|
+
env['RAILS_ENV'] || env['RACK_ENV'] || env['ENV'] || 'development'
|
35
77
|
end
|
36
78
|
end
|
37
79
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
80
|
+
def statsd_implementation
|
81
|
+
env.fetch('STATSD_IMPLEMENTATION', 'statsd')
|
82
|
+
end
|
83
|
+
|
84
|
+
def statsd_sample_rate
|
85
|
+
env.fetch('STATSD_SAMPLE_RATE', 1.0).to_f
|
86
|
+
end
|
87
|
+
|
88
|
+
def statsd_prefix
|
89
|
+
env.fetch('STATSD_PREFIX', nil)
|
90
|
+
end
|
91
|
+
|
92
|
+
def statsd_addr
|
93
|
+
env.fetch('STATSD_ADDR', 'localhost:8125')
|
94
|
+
end
|
95
|
+
|
96
|
+
def statsd_default_tags
|
97
|
+
env.key?('STATSD_DEFAULT_TAGS') ? env.fetch('STATSD_DEFAULT_TAGS').split(',') : nil
|
98
|
+
end
|
99
|
+
|
100
|
+
def default_client
|
101
|
+
@default_client ||= StatsD::Instrument::Client.new(
|
102
|
+
sink: default_sink_for_environment,
|
103
|
+
datagram_builder_class: default_datagram_builder_class_for_implementation,
|
104
|
+
default_sample_rate: statsd_sample_rate,
|
105
|
+
prefix: statsd_prefix,
|
106
|
+
default_tags: statsd_default_tags,
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
def default_datagram_builder_class_for_implementation
|
111
|
+
case statsd_implementation
|
112
|
+
when 'statsd'
|
113
|
+
StatsD::Instrument::StatsDDatagramBuilder
|
114
|
+
when 'datadog', 'dogstatsd'
|
115
|
+
StatsD::Instrument::DogStatsDDatagramBuilder
|
116
|
+
else
|
117
|
+
raise NotImplementedError, "No implementation for #{statsd_implementation}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def default_sink_for_environment
|
122
|
+
case environment
|
123
|
+
when 'production', 'staging'
|
124
|
+
StatsD::Instrument::UDPSink.for_addr(statsd_addr)
|
125
|
+
when 'test'
|
126
|
+
StatsD::Instrument::NullSink.new
|
127
|
+
else
|
128
|
+
StatsD::Instrument::LogSink.new(StatsD.logger)
|
129
|
+
end
|
51
130
|
end
|
52
131
|
end
|
53
132
|
|
@@ -1,14 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module StatsD::Instrument::Helpers
|
2
|
-
def
|
3
|
-
|
4
|
-
|
5
|
-
block.call
|
6
|
-
mock_backend.collected_metrics
|
7
|
-
ensure
|
8
|
-
if old_backend.kind_of?(StatsD::Instrument::Backends::CaptureBackend)
|
9
|
-
old_backend.collected_metrics.concat(mock_backend.collected_metrics)
|
4
|
+
def with_capture_backend(backend, &block)
|
5
|
+
if StatsD.backend.is_a?(StatsD::Instrument::Backends::CaptureBackend)
|
6
|
+
backend.parent = StatsD.backend
|
10
7
|
end
|
11
8
|
|
9
|
+
old_backend = StatsD.backend
|
10
|
+
StatsD.backend = backend
|
11
|
+
|
12
|
+
block.call
|
13
|
+
ensure
|
12
14
|
StatsD.backend = old_backend
|
13
15
|
end
|
16
|
+
|
17
|
+
def capture_statsd_calls(&block)
|
18
|
+
capture_backend = StatsD::Instrument::Backends::CaptureBackend.new
|
19
|
+
with_capture_backend(capture_backend, &block)
|
20
|
+
capture_backend.collected_metrics
|
21
|
+
end
|
14
22
|
end
|
@@ -0,0 +1,24 @@
|
|
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::LogSink
|
6
|
+
attr_reader :logger, :severity
|
7
|
+
|
8
|
+
def initialize(logger, severity: Logger::DEBUG)
|
9
|
+
@logger = logger
|
10
|
+
@severity = severity
|
11
|
+
end
|
12
|
+
|
13
|
+
def sample?(_sample_rate)
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
def <<(datagram)
|
18
|
+
# Some implementations require a newline at the end of datagrams.
|
19
|
+
# When logging, we make sure those newlines are removed using chomp.
|
20
|
+
|
21
|
+
logger.add(severity, "[StatsD] #{datagram.chomp}")
|
22
|
+
self
|
23
|
+
end
|
24
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rspec/expectations'
|
2
4
|
require 'rspec/core/version'
|
3
5
|
|
@@ -9,7 +11,7 @@ module StatsD::Instrument::Matchers
|
|
9
11
|
histogram: :h,
|
10
12
|
distribution: :d,
|
11
13
|
set: :s,
|
12
|
-
key_value: :kv
|
14
|
+
key_value: :kv,
|
13
15
|
}
|
14
16
|
|
15
17
|
class Matcher
|
@@ -23,13 +25,10 @@ module StatsD::Instrument::Matchers
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def matches?(block)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
false
|
32
|
-
end
|
28
|
+
expect_statsd_call(@metric_type, @metric_name, @options, &block)
|
29
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e
|
30
|
+
@message = e.message
|
31
|
+
false
|
33
32
|
end
|
34
33
|
|
35
34
|
def failure_message
|
@@ -50,8 +49,12 @@ module StatsD::Instrument::Matchers
|
|
50
49
|
metrics = capture_statsd_calls(&block)
|
51
50
|
metrics = metrics.select { |m| m.type == metric_type && m.name == metric_name }
|
52
51
|
|
53
|
-
|
54
|
-
|
52
|
+
if metrics.empty?
|
53
|
+
raise RSpec::Expectations::ExpectationNotMetError, "No StatsD calls for metric #{metric_name} were made."
|
54
|
+
elsif options[:times] && options[:times] != metrics.length
|
55
|
+
raise RSpec::Expectations::ExpectationNotMetError, "The numbers of StatsD calls for metric #{metric_name} " \
|
56
|
+
"was unexpected. Expected #{options[:times].inspect}, got #{metrics.length}"
|
57
|
+
end
|
55
58
|
|
56
59
|
[:sample_rate, :value, :tags].each do |expectation|
|
57
60
|
next unless options[expectation]
|
@@ -63,7 +66,7 @@ module StatsD::Instrument::Matchers
|
|
63
66
|
|
64
67
|
found = options[:times] ? num_matches == options[:times] : num_matches > 0
|
65
68
|
|
66
|
-
|
69
|
+
unless found
|
67
70
|
message = metric_information(metric_name, options, metrics, expectation)
|
68
71
|
raise RSpec::Expectations::ExpectationNotMetError, message
|
69
72
|
end
|
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# The Metric class represents a metric sample to be send by a backend.
|
2
4
|
#
|
3
5
|
# @!attribute type
|
4
6
|
# @return [Symbol] The metric type. Must be one of {StatsD::Instrument::Metric::TYPES}
|
5
7
|
# @!attribute name
|
6
8
|
# @return [String] The name of the metric. {StatsD#prefix} will automatically be applied
|
7
|
-
# to the metric in the constructor, unless the <tt>:no_prefix</tt> option is set or is
|
9
|
+
# to the metric in the constructor, unless the <tt>:no_prefix</tt> option is set or is
|
8
10
|
# overridden by the <tt>:prefix</tt> option. Note that <tt>:no_prefix</tt> has greater
|
9
11
|
# precedence than <tt>:prefix</tt>.
|
10
12
|
# @!attribute value
|
@@ -29,39 +31,24 @@
|
|
29
31
|
# @see StatsD::Instrument::Backend A StatsD::Instrument::Backend is used to collect metrics.
|
30
32
|
#
|
31
33
|
class StatsD::Instrument::Metric
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
#
|
39
|
-
# @option options [Symbol] :type The type of the metric.
|
40
|
-
# @option options [String] :name The name of the metric without prefix.
|
41
|
-
# @option options [String] :prefix Override the default StatsD prefix.
|
42
|
-
# @option options [Boolean] :no_prefix Set to <tt>true</tt> if you don't want to apply a prefix.
|
43
|
-
# @option options [Numeric, String, nil] :value The value to collect for the metric. If set to
|
44
|
-
# <tt>nil>/tt>, {#default_value} will be used.
|
45
|
-
# @option options [Numeric, nil] :sample_rate The sample rate to use. If not set, it will use
|
46
|
-
# {StatsD#default_sample_rate}.
|
47
|
-
# @option options [Array<String>, Hash<String, String>, nil] :tags The tags to apply to this metric.
|
48
|
-
# See {.normalize_tags} for more information.
|
49
|
-
def initialize(options = {})
|
50
|
-
@type = options[:type] or raise ArgumentError, "Metric :type is required."
|
51
|
-
@name = options[:name] or raise ArgumentError, "Metric :name is required."
|
52
|
-
@name = normalize_name(@name)
|
53
|
-
unless options[:no_prefix]
|
54
|
-
@name = if options[:prefix]
|
55
|
-
"#{options[:prefix]}.#{@name}"
|
56
|
-
else
|
57
|
-
StatsD.prefix ? "#{StatsD.prefix}.#{@name}" : @name
|
34
|
+
unless Regexp.method_defined?(:match?) # for ruby 2.3
|
35
|
+
module RubyBackports
|
36
|
+
refine Regexp do
|
37
|
+
def match?(str)
|
38
|
+
(self =~ str) != nil
|
39
|
+
end
|
58
40
|
end
|
59
41
|
end
|
60
42
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
43
|
+
using RubyBackports
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.new(
|
47
|
+
type:, name:, value: default_value(type), sample_rate: StatsD.default_sample_rate, tags: nil, metadata: nil
|
48
|
+
)
|
49
|
+
# pass keyword arguments as positional arguments for performance reasons,
|
50
|
+
# since MRI's C implementation of new turns keyword arguments into a hash
|
51
|
+
super(type, name, value, sample_rate, tags, metadata)
|
65
52
|
end
|
66
53
|
|
67
54
|
# The default value for this metric, which will be used if it is not set.
|
@@ -69,40 +56,73 @@ class StatsD::Instrument::Metric
|
|
69
56
|
# A default value is only defined for counter metrics (<tt>1</tt>). For all other
|
70
57
|
# metric types, this emthod will raise an <tt>ArgumentError</tt>.
|
71
58
|
#
|
59
|
+
#
|
60
|
+
# A default value is only defined for counter metrics (<tt>1</tt>). For all other
|
61
|
+
# metric types, this emthod will raise an <tt>ArgumentError</tt>.
|
62
|
+
#
|
72
63
|
# @return [Numeric, String] The default value for this metric.
|
73
64
|
# @raise ArgumentError if the metric type doesn't have a default value
|
74
|
-
def default_value
|
65
|
+
def self.default_value(type)
|
75
66
|
case type
|
76
|
-
|
77
|
-
|
67
|
+
when :c then 1
|
68
|
+
else raise ArgumentError, "A value is required for metric type #{type.inspect}."
|
78
69
|
end
|
79
70
|
end
|
80
71
|
|
72
|
+
attr_accessor :type, :name, :value, :sample_rate, :tags, :metadata
|
73
|
+
|
74
|
+
# Initializes a new metric instance.
|
75
|
+
# Normally, you don't want to call this method directly, but use one of the metric collection
|
76
|
+
# methods on the {StatsD} module.
|
77
|
+
#
|
78
|
+
# @param type [Symbol] The type of the metric.
|
79
|
+
# @option name [String] :name The name of the metric without prefix.
|
80
|
+
# @option value [Numeric, String, nil] The value to collect for the metric.
|
81
|
+
# @option sample_rate [Numeric, nil] The sample rate to use. If not set, it will use
|
82
|
+
# {StatsD#default_sample_rate}.
|
83
|
+
# @option tags [Array<String>, Hash<String, String>, nil] :tags The tags to apply to this metric.
|
84
|
+
# See {.normalize_tags} for more information.
|
85
|
+
def initialize(type, name, value, sample_rate, tags, metadata) # rubocop:disable Metrics/ParameterLists
|
86
|
+
raise ArgumentError, "Metric :type is required." unless type
|
87
|
+
raise ArgumentError, "Metric :name is required." unless name
|
88
|
+
raise ArgumentError, "Metric :value is required." unless value
|
89
|
+
|
90
|
+
@type = type
|
91
|
+
@name = normalize_name(name)
|
92
|
+
@value = value
|
93
|
+
@sample_rate = sample_rate
|
94
|
+
@tags = StatsD::Instrument::Metric.normalize_tags(tags)
|
95
|
+
if StatsD.default_tags
|
96
|
+
@tags = Array(@tags) + StatsD.default_tags
|
97
|
+
end
|
98
|
+
@metadata = metadata
|
99
|
+
end
|
100
|
+
|
81
101
|
# @private
|
82
102
|
# @return [String]
|
83
103
|
def to_s
|
84
|
-
str = "#{TYPES[type]} #{name}:#{value}"
|
104
|
+
str = +"#{TYPES[type]} #{name}:#{value}"
|
85
105
|
str << " @#{sample_rate}" if sample_rate != 1.0
|
86
|
-
|
106
|
+
tags&.each { |tag| str << " ##{tag}" }
|
87
107
|
str
|
88
108
|
end
|
89
109
|
|
90
110
|
# @private
|
91
111
|
# @return [String]
|
92
112
|
def inspect
|
93
|
-
"#<StatsD::Instrument::Metric #{self
|
113
|
+
"#<StatsD::Instrument::Metric #{self}>"
|
94
114
|
end
|
95
115
|
|
96
116
|
# The metric types that are supported by this library. Note that every StatsD server
|
97
117
|
# implementation only supports a subset of them.
|
98
118
|
TYPES = {
|
99
|
-
c:
|
119
|
+
c: 'increment',
|
100
120
|
ms: 'measure',
|
101
|
-
g:
|
102
|
-
h:
|
103
|
-
d:
|
121
|
+
g: 'gauge',
|
122
|
+
h: 'histogram',
|
123
|
+
d: 'distribution',
|
104
124
|
kv: 'key/value',
|
105
|
-
s:
|
125
|
+
s: 'set',
|
106
126
|
}
|
107
127
|
|
108
128
|
# Strip metric names of special characters used by StatsD line protocol, replace with underscore
|
@@ -110,7 +130,10 @@ class StatsD::Instrument::Metric
|
|
110
130
|
# @param name [String]
|
111
131
|
# @return [String]
|
112
132
|
def normalize_name(name)
|
113
|
-
|
133
|
+
# fast path when no normalization is needed to avoid copying the string
|
134
|
+
return name unless /[:|@]/.match?(name)
|
135
|
+
|
136
|
+
name.tr(':|@', '_')
|
114
137
|
end
|
115
138
|
|
116
139
|
# Utility function to convert tags to the canonical form.
|
@@ -122,7 +145,11 @@ class StatsD::Instrument::Metric
|
|
122
145
|
# @return [Array<String>, nil] the list of tags in canonical form.
|
123
146
|
def self.normalize_tags(tags)
|
124
147
|
return unless tags
|
125
|
-
tags = tags.map { |k, v| k.to_s + ":"
|
126
|
-
|
148
|
+
tags = tags.map { |k, v| k.to_s + ":" + v.to_s } if tags.is_a?(Hash)
|
149
|
+
|
150
|
+
# fast path when no string replacement is needed
|
151
|
+
return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
|
152
|
+
|
153
|
+
tags.map { |tag| tag.tr('|,', '') }
|
127
154
|
end
|
128
155
|
end
|
@@ -1,15 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# @private
|
2
4
|
class StatsD::Instrument::MetricExpectation
|
3
|
-
|
4
5
|
attr_accessor :times, :type, :name, :value, :sample_rate, :tags
|
5
6
|
attr_reader :ignore_tags
|
6
7
|
|
7
8
|
def initialize(options = {})
|
8
|
-
|
9
|
-
|
9
|
+
if options[:type]
|
10
|
+
@type = options[:type]
|
11
|
+
else
|
12
|
+
raise ArgumentError, "Metric :type is required."
|
13
|
+
end
|
14
|
+
|
15
|
+
if options[:name]
|
16
|
+
@name = options[:name]
|
17
|
+
else
|
18
|
+
raise ArgumentError, "Metric :name is required."
|
19
|
+
end
|
20
|
+
|
21
|
+
if options[:times]
|
22
|
+
@times = options[:times]
|
23
|
+
else
|
24
|
+
raise ArgumentError, "Metric :times is required."
|
25
|
+
end
|
26
|
+
|
10
27
|
@name = StatsD.prefix ? "#{StatsD.prefix}.#{@name}" : @name unless options[:no_prefix]
|
11
28
|
@tags = StatsD::Instrument::Metric.normalize_tags(options[:tags])
|
12
|
-
@times = options[:times] or raise ArgumentError, "Metric :times is required."
|
13
29
|
@sample_rate = options[:sample_rate]
|
14
30
|
@value = options[:value]
|
15
31
|
@ignore_tags = StatsD::Instrument::Metric.normalize_tags(options[:ignore_tags])
|
@@ -29,7 +45,7 @@ class StatsD::Instrument::MetricExpectation
|
|
29
45
|
actual_tags -= ignored_tags
|
30
46
|
|
31
47
|
if ignore_tags.is_a?(Array)
|
32
|
-
actual_tags.delete_if{ |key| ignore_tags.include?(key.split(":").first) }
|
48
|
+
actual_tags.delete_if { |key| ignore_tags.include?(key.split(":").first) }
|
33
49
|
end
|
34
50
|
end
|
35
51
|
|
@@ -39,30 +55,28 @@ class StatsD::Instrument::MetricExpectation
|
|
39
55
|
end
|
40
56
|
|
41
57
|
def default_value
|
42
|
-
|
43
|
-
when :c; 1
|
44
|
-
end
|
58
|
+
1 if type == :c
|
45
59
|
end
|
46
60
|
|
47
61
|
TYPES = {
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
62
|
+
c: 'increment',
|
63
|
+
ms: 'measure',
|
64
|
+
g: 'gauge',
|
65
|
+
h: 'histogram',
|
66
|
+
d: 'distribution',
|
67
|
+
kv: 'key/value',
|
68
|
+
s: 'set',
|
55
69
|
}
|
56
70
|
|
57
71
|
def to_s
|
58
|
-
str = "#{TYPES[type]} #{name}:#{value}"
|
72
|
+
str = +"#{TYPES[type]} #{name}:#{value}"
|
59
73
|
str << " @#{sample_rate}" if sample_rate != 1.0
|
60
|
-
str << " " << tags.map { |t| "##{t}"}.join(' ') if tags
|
74
|
+
str << " " << tags.map { |t| "##{t}" }.join(' ') if tags
|
61
75
|
str << " times:#{times}" if times > 1
|
62
76
|
str
|
63
77
|
end
|
64
78
|
|
65
79
|
def inspect
|
66
|
-
"#<StatsD::Instrument::MetricExpectation #{self
|
80
|
+
"#<StatsD::Instrument::MetricExpectation #{self}>"
|
67
81
|
end
|
68
82
|
end
|
@@ -0,0 +1,13 @@
|
|
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::NullSink
|
6
|
+
def sample?(_sample_rate)
|
7
|
+
false
|
8
|
+
end
|
9
|
+
|
10
|
+
def <<(_datagram)
|
11
|
+
self # noop
|
12
|
+
end
|
13
|
+
end
|
@@ -1,9 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This Railtie runs some initializers that will set the logger to <tt>Rails#logger</tt>,
|
2
4
|
# and will initialize the {StatsD#backend} based on the Rails environment.
|
3
5
|
#
|
4
6
|
# @see StatsD::Instrument::Environment
|
5
7
|
class StatsD::Instrument::Railtie < Rails::Railtie
|
6
|
-
|
7
8
|
initializer 'statsd-instrument.use_rails_logger' do
|
8
9
|
::StatsD.logger = Rails.logger
|
9
10
|
end
|
@@ -0,0 +1,39 @@
|
|
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 `as_dist: true` keyword argument on `StatsD.measure`
|
9
|
+
# and `statsd_measure`. This argument is deprecated. Instead, you can use `StatsD.distribution`
|
10
|
+
# (or `statsd_distribution`) directly.
|
11
|
+
#
|
12
|
+
# To run this cop on your codebase:
|
13
|
+
#
|
14
|
+
# rubocop --require `bundle show statsd-instrument`/lib/statsd/instrument/rubocop.rb \
|
15
|
+
# --only StatsD/MeasureAsDistArgument
|
16
|
+
#
|
17
|
+
# This cop will not autocorrect offenses.
|
18
|
+
class MeasureAsDistArgument < Cop
|
19
|
+
include RuboCop::Cop::StatsD
|
20
|
+
|
21
|
+
MSG = <<~MSG
|
22
|
+
Do not use StatsD.measure(..., as_dist: true). This is deprecated.
|
23
|
+
|
24
|
+
Use StatsD.distribution (or statsd_distribution) instead.
|
25
|
+
MSG
|
26
|
+
|
27
|
+
def on_send(node)
|
28
|
+
if metric_method?(node) && node.method_name == :measure
|
29
|
+
add_offense(node) if has_keyword_argument?(node, :as_dist)
|
30
|
+
end
|
31
|
+
|
32
|
+
if metaprogramming_method?(node) && node.method_name == :statsd_measure
|
33
|
+
add_offense(node) if has_keyword_argument?(node, :as_dist)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,42 @@
|
|
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 metaprogramming macros for positional
|
9
|
+
# argument usage, which is deprecated. These macros include `statd_count_if`,
|
10
|
+
# `statsd_measure`, etc.
|
11
|
+
#
|
12
|
+
# Use the following Rubocop invocation to check your project's codebase:
|
13
|
+
#
|
14
|
+
# rubocop --only StatsD/MetaprogrammingPositionalArguments
|
15
|
+
# -r `bundle show statsd-instrument`/lib/statsd/instrument/rubocop.rb
|
16
|
+
#
|
17
|
+
#
|
18
|
+
# This cop will not autocorrect the offenses it finds, but generally the fixes are easy to fix
|
19
|
+
class MetaprogrammingPositionalArguments < Cop
|
20
|
+
include RuboCop::Cop::StatsD
|
21
|
+
|
22
|
+
MSG = 'Use keyword arguments for StatsD metaprogramming macros'
|
23
|
+
|
24
|
+
def on_send(node)
|
25
|
+
if metaprogramming_method?(node)
|
26
|
+
arguments = node.arguments.dup
|
27
|
+
arguments.shift # method
|
28
|
+
arguments.shift # metric
|
29
|
+
arguments.pop if arguments.last&.type == :block_pass
|
30
|
+
case arguments.length
|
31
|
+
when 0
|
32
|
+
when 1
|
33
|
+
add_offense(node) if arguments.first.type != :hash
|
34
|
+
else
|
35
|
+
add_offense(node)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|