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
@@ -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
|