statsd-instrument 3.0.0 → 3.0.1
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/workflows/ci.yml +3 -3
- data/.rubocop.yml +3 -13
- data/CHANGELOG.md +6 -0
- data/Gemfile +8 -0
- data/README.md +2 -2
- data/Rakefile +1 -1
- data/bin/rake +29 -0
- data/bin/rubocop +29 -0
- data/lib/statsd/instrument.rb +4 -1
- data/lib/statsd/instrument/assertions.rb +200 -196
- data/lib/statsd/instrument/capture_sink.rb +23 -19
- data/lib/statsd/instrument/client.rb +414 -410
- data/lib/statsd/instrument/datagram.rb +69 -65
- data/lib/statsd/instrument/datagram_builder.rb +81 -77
- data/lib/statsd/instrument/dogstatsd_datagram.rb +76 -72
- data/lib/statsd/instrument/dogstatsd_datagram_builder.rb +68 -64
- data/lib/statsd/instrument/environment.rb +80 -77
- data/lib/statsd/instrument/expectation.rb +96 -92
- data/lib/statsd/instrument/helpers.rb +11 -7
- data/lib/statsd/instrument/log_sink.rb +20 -16
- data/lib/statsd/instrument/matchers.rb +86 -70
- data/lib/statsd/instrument/null_sink.rb +12 -8
- data/lib/statsd/instrument/railtie.rb +11 -7
- data/lib/statsd/instrument/statsd_datagram_builder.rb +12 -8
- data/lib/statsd/instrument/udp_sink.rb +50 -46
- data/lib/statsd/instrument/version.rb +1 -1
- data/statsd-instrument.gemspec +2 -8
- data/test/assertions_test.rb +12 -12
- data/test/capture_sink_test.rb +8 -8
- data/test/client_test.rb +54 -54
- data/test/datagram_builder_test.rb +29 -29
- data/test/datagram_test.rb +1 -1
- data/test/dogstatsd_datagram_builder_test.rb +28 -28
- data/test/environment_test.rb +9 -9
- data/test/helpers/rubocop_helper.rb +9 -6
- data/test/helpers_test.rb +5 -5
- data/test/integration_test.rb +1 -1
- data/test/log_sink_test.rb +2 -2
- data/test/matchers_test.rb +36 -36
- data/test/null_sink_test.rb +2 -2
- data/test/rubocop/metric_return_value_test.rb +3 -3
- data/test/rubocop/positional_arguments_test.rb +10 -10
- data/test/statsd_instrumentation_test.rb +66 -66
- data/test/statsd_test.rb +44 -44
- data/test/test_helper.rb +6 -4
- data/test/udp_sink_test.rb +8 -8
- metadata +7 -103
- data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +0 -1027
@@ -1,75 +1,79 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
class
|
6
|
-
|
3
|
+
module StatsD
|
4
|
+
module Instrument
|
5
|
+
# @note This class is part of the new Client implementation that is intended
|
6
|
+
# to become the new default in the next major release of this library.
|
7
|
+
class DogStatsDDatagramBuilder < StatsD::Instrument::DatagramBuilder
|
8
|
+
unsupported_datagram_types :kv
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
10
|
+
def self.datagram_class
|
11
|
+
StatsD::Instrument::DogStatsDDatagram
|
12
|
+
end
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
def latency_metric_type
|
15
|
+
:d
|
16
|
+
end
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
18
|
+
# Constricts an event datagram.
|
19
|
+
#
|
20
|
+
# @param [String] title Event title.
|
21
|
+
# @param [String] text Event description. Newlines are allowed.
|
22
|
+
# @param [Time] timestamp The of the event. If not provided,
|
23
|
+
# Datadog will interpret it as the current timestamp.
|
24
|
+
# @param [String] hostname A hostname to associate with the event.
|
25
|
+
# @param [String] aggregation_key An aggregation key to group events with the same key.
|
26
|
+
# @param [String] priority Priority of the event. Either "normal" (default) or "low".
|
27
|
+
# @param [String] source_type_name The source type of the event.
|
28
|
+
# @param [String] alert_type Either "error", "warning", "info" (default) or "success".
|
29
|
+
# @param [Array, Hash] tags Tags to associate with the event.
|
30
|
+
# @return [String] The correctly formatted service check datagram
|
31
|
+
#
|
32
|
+
# @see https://docs.datadoghq.com/developers/dogstatsd/datagram_shell/#events
|
33
|
+
def _e(title, text, timestamp: nil, hostname: nil, aggregation_key: nil, priority: nil,
|
34
|
+
source_type_name: nil, alert_type: nil, tags: nil)
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
36
|
+
escaped_title = "#{@prefix}#{title}".gsub("\n", '\n')
|
37
|
+
escaped_text = text.gsub("\n", '\n')
|
38
|
+
tags = normalize_tags(tags) + default_tags
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
40
|
+
datagram = +"_e{#{escaped_title.length},#{escaped_text.length}}:#{escaped_title}|#{escaped_text}"
|
41
|
+
datagram << "|h:#{hostname}" if hostname
|
42
|
+
datagram << "|d:#{timestamp.to_i}" if timestamp
|
43
|
+
datagram << "|k:#{aggregation_key}" if aggregation_key
|
44
|
+
datagram << "|p:#{priority}" if priority
|
45
|
+
datagram << "|s:#{source_type_name}" if source_type_name
|
46
|
+
datagram << "|t:#{alert_type}" if alert_type
|
47
|
+
datagram << "|##{tags.join(',')}" unless tags.empty?
|
48
|
+
datagram
|
49
|
+
end
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
51
|
+
# Constricts a service check datagram.
|
52
|
+
#
|
53
|
+
# @param [String] name Name of the service
|
54
|
+
# @param [Symbol] status Either `:ok`, `:warning`, `:critical` or `:unknown`
|
55
|
+
# @param [Time] timestamp The moment when the service was checked. If not provided,
|
56
|
+
# Datadog will interpret it as the current timestamp.
|
57
|
+
# @param [String] hostname A hostname to associate with the check.
|
58
|
+
# @param [Array, Hash] tags Tags to associate with the check.
|
59
|
+
# @param [String] message A message describing the current state of the service check.
|
60
|
+
# @return [String] The correctly formatted service check datagram
|
61
|
+
#
|
62
|
+
# @see https://docs.datadoghq.com/developers/dogstatsd/datagram_shell/#service-checks
|
63
|
+
def _sc(name, status, timestamp: nil, hostname: nil, tags: nil, message: nil)
|
64
|
+
status_number = status.is_a?(Integer) ? status : SERVICE_CHECK_STATUS_VALUES.fetch(status.to_sym)
|
65
|
+
tags = normalize_tags(tags) + default_tags
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
67
|
+
datagram = +"_sc|#{@prefix}#{normalize_name(name)}|#{status_number}"
|
68
|
+
datagram << "|h:#{hostname}" if hostname
|
69
|
+
datagram << "|d:#{timestamp.to_i}" if timestamp
|
70
|
+
datagram << "|##{tags.join(',')}" unless tags.empty?
|
71
|
+
datagram << "|m:#{normalize_name(message)}" if message
|
72
|
+
datagram
|
73
|
+
end
|
72
74
|
|
73
|
-
|
74
|
-
|
75
|
+
SERVICE_CHECK_STATUS_VALUES = { ok: 0, warning: 1, critical: 2, unknown: 3 }.freeze
|
76
|
+
private_constant :SERVICE_CHECK_STATUS_VALUES
|
77
|
+
end
|
78
|
+
end
|
75
79
|
end
|
@@ -1,94 +1,97 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
module StatsD
|
4
|
+
module Instrument
|
5
|
+
# The environment module is used to detect, and initialize the environment in
|
6
|
+
# which this library is active. It will use different default values based on the environment.
|
7
|
+
class Environment
|
8
|
+
class << self
|
9
|
+
def current
|
10
|
+
@current ||= StatsD::Instrument::Environment.new(ENV)
|
11
|
+
end
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
#
|
17
|
-
# @return [String] The detected environment.
|
18
|
-
def environment
|
19
|
-
current.environment
|
20
|
-
end
|
13
|
+
# @deprecated For backwards compatibility only. Use {StatsD::Instrument::Environment#environment}
|
14
|
+
# through {StatsD::Instrument::Environment.current} instead.
|
15
|
+
def environment
|
16
|
+
current.environment
|
17
|
+
end
|
21
18
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
19
|
+
# Sets default values for sample rate and logger.
|
20
|
+
#
|
21
|
+
# - Default sample rate is set to the value in the STATSD_SAMPLE_RATE environment variable,
|
22
|
+
# or 1.0 otherwise. See {StatsD#default_sample_rate}
|
23
|
+
# - {StatsD#logger} is set to a logger that send output to stderr.
|
24
|
+
#
|
25
|
+
# If you are including this library inside a Rails environment, additional initialization will
|
26
|
+
# be done as part of the {StatsD::Instrument::Railtie}.
|
27
|
+
#
|
28
|
+
# @return [void]
|
29
|
+
def setup
|
30
|
+
StatsD.logger = Logger.new($stderr)
|
31
|
+
end
|
32
|
+
end
|
36
33
|
|
37
|
-
|
34
|
+
attr_reader :env
|
38
35
|
|
39
|
-
|
40
|
-
|
41
|
-
|
36
|
+
def initialize(env)
|
37
|
+
@env = env
|
38
|
+
end
|
42
39
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
40
|
+
# Detects the current environment, either by asking Rails, or by inspecting environment variables.
|
41
|
+
#
|
42
|
+
# - It will prefer the value set in <tt>ENV['STATSD_ENV']</tt>
|
43
|
+
# - Within a Rails application, <tt>Rails.env</tt> is used.
|
44
|
+
# - It will check the following environment variables in order:
|
45
|
+
# - <tt>RAILS_ENV</tt>,
|
46
|
+
# - <tt>RACK_ENV</tt>
|
47
|
+
# - <tt>ENV</tt>.
|
48
|
+
# - If none of these are set, it will return <tt>development</tt>
|
49
|
+
#
|
50
|
+
# @return [String] The detected environment.
|
51
|
+
def environment
|
52
|
+
if env['STATSD_ENV']
|
53
|
+
env['STATSD_ENV']
|
54
|
+
elsif defined?(Rails) && Rails.respond_to?(:env)
|
55
|
+
Rails.env.to_s
|
56
|
+
else
|
57
|
+
env['RAILS_ENV'] || env['RACK_ENV'] || env['ENV'] || 'development'
|
58
|
+
end
|
59
|
+
end
|
59
60
|
|
60
|
-
|
61
|
-
|
62
|
-
|
61
|
+
def statsd_implementation
|
62
|
+
env.fetch('STATSD_IMPLEMENTATION', 'datadog')
|
63
|
+
end
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
65
|
+
def statsd_sample_rate
|
66
|
+
env.fetch('STATSD_SAMPLE_RATE', 1.0).to_f
|
67
|
+
end
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
|
69
|
+
def statsd_prefix
|
70
|
+
env.fetch('STATSD_PREFIX', nil)
|
71
|
+
end
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
73
|
+
def statsd_addr
|
74
|
+
env.fetch('STATSD_ADDR', 'localhost:8125')
|
75
|
+
end
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
77
|
+
def statsd_default_tags
|
78
|
+
env.key?('STATSD_DEFAULT_TAGS') ? env.fetch('STATSD_DEFAULT_TAGS').split(',') : nil
|
79
|
+
end
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
|
81
|
+
def client
|
82
|
+
StatsD::Instrument::Client.from_env(self)
|
83
|
+
end
|
83
84
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
85
|
+
def default_sink_for_environment
|
86
|
+
case environment
|
87
|
+
when 'production', 'staging'
|
88
|
+
StatsD::Instrument::UDPSink.for_addr(statsd_addr)
|
89
|
+
when 'test'
|
90
|
+
StatsD::Instrument::NullSink.new
|
91
|
+
else
|
92
|
+
StatsD::Instrument::LogSink.new(StatsD.logger)
|
93
|
+
end
|
94
|
+
end
|
92
95
|
end
|
93
96
|
end
|
94
97
|
end
|
@@ -1,115 +1,119 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
module StatsD
|
4
|
+
module Instrument
|
5
|
+
# @private
|
6
|
+
class Expectation
|
7
|
+
class << self
|
8
|
+
def increment(name, value = nil, **options)
|
9
|
+
new(type: :c, name: name, value: value, **options)
|
10
|
+
end
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
12
|
+
def measure(name, value = nil, **options)
|
13
|
+
new(type: :ms, name: name, value: value, **options)
|
14
|
+
end
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
def gauge(name, value = nil, **options)
|
17
|
+
new(type: :g, name: name, value: value, **options)
|
18
|
+
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
def set(name, value = nil, **options)
|
21
|
+
new(type: :s, name: name, value: value, **options)
|
22
|
+
end
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
24
|
+
def distribution(name, value = nil, **options)
|
25
|
+
new(type: :d, name: name, value: value, **options)
|
26
|
+
end
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
def histogram(name, value = nil, **options)
|
29
|
+
new(type: :h, name: name, value: value, **options)
|
30
|
+
end
|
31
|
+
end
|
30
32
|
|
31
|
-
|
33
|
+
attr_accessor :times, :type, :name, :value, :sample_rate, :tags
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
+
def initialize(client: StatsD.singleton_client, type:, name:, value: nil,
|
36
|
+
sample_rate: nil, tags: nil, no_prefix: false, times: 1)
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
@type = type
|
39
|
+
@name = client.prefix ? "#{client.prefix}.#{name}" : name unless no_prefix
|
40
|
+
@value = normalized_value_for_type(type, value) if value
|
41
|
+
@sample_rate = sample_rate
|
42
|
+
@tags = normalize_tags(tags)
|
43
|
+
@times = times
|
44
|
+
end
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
46
|
+
def normalized_value_for_type(type, value)
|
47
|
+
case type
|
48
|
+
when :c then Integer(value)
|
49
|
+
when :g, :h, :d, :kv, :ms then Float(value)
|
50
|
+
when :s then String(value)
|
51
|
+
else value
|
52
|
+
end
|
53
|
+
end
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
-
|
55
|
+
def matches(actual_metric)
|
56
|
+
return false if sample_rate && sample_rate != actual_metric.sample_rate
|
57
|
+
return false if value && value != normalized_value_for_type(actual_metric.type, actual_metric.value)
|
56
58
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
if tags
|
60
|
+
expected_tags = Set.new(tags)
|
61
|
+
actual_tags = Set.new(actual_metric.tags)
|
62
|
+
return expected_tags.subset?(actual_tags)
|
63
|
+
end
|
64
|
+
true
|
65
|
+
end
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
67
|
+
def to_s
|
68
|
+
str = +"#{name}:#{value || '<anything>'}|#{type}"
|
69
|
+
str << "|@#{sample_rate}" if sample_rate
|
70
|
+
str << "|#" << tags.join(',') if tags
|
71
|
+
str << " (expected #{times} times)" if times > 1
|
72
|
+
str
|
73
|
+
end
|
72
74
|
|
73
|
-
|
74
|
-
|
75
|
-
|
75
|
+
def inspect
|
76
|
+
"#<StatsD::Instrument::Expectation:\"#{self}\">"
|
77
|
+
end
|
76
78
|
|
77
|
-
|
79
|
+
private
|
78
80
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
81
|
+
# Needed for normalize_tags
|
82
|
+
unless Regexp.method_defined?(:match?) # for ruby 2.3
|
83
|
+
module RubyBackports
|
84
|
+
refine Regexp do
|
85
|
+
def match?(str)
|
86
|
+
(self =~ str) != nil
|
87
|
+
end
|
88
|
+
end
|
85
89
|
end
|
90
|
+
|
91
|
+
using(RubyBackports)
|
86
92
|
end
|
87
|
-
end
|
88
93
|
|
89
|
-
|
90
|
-
|
94
|
+
# @private
|
95
|
+
#
|
96
|
+
# Utility function to convert tags to the canonical form.
|
97
|
+
#
|
98
|
+
# - Tags specified as key value pairs will be converted into an array
|
99
|
+
# - Tags are normalized to remove unsupported characters
|
100
|
+
#
|
101
|
+
# @param tags [Array<String>, Hash<String, String>, nil] Tags specified in any form.
|
102
|
+
# @return [Array<String>, nil] the list of tags in canonical form.
|
103
|
+
#
|
104
|
+
# @todo We should delegate this to thje datagram builder of the current client,
|
105
|
+
# to ensure that this logic matches the logic of the active datagram builder.
|
106
|
+
def normalize_tags(tags)
|
107
|
+
return [] unless tags
|
108
|
+
tags = tags.map { |k, v| "#{k}:#{v}" } if tags.is_a?(Hash)
|
109
|
+
|
110
|
+
# Fast path when no string replacement is needed
|
111
|
+
return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
|
112
|
+
tags.map { |tag| tag.tr('|,', '') }
|
113
|
+
end
|
114
|
+
end
|
91
115
|
|
92
|
-
|
93
|
-
|
94
|
-
# Utility function to convert tags to the canonical form.
|
95
|
-
#
|
96
|
-
# - Tags specified as key value pairs will be converted into an array
|
97
|
-
# - Tags are normalized to remove unsupported characters
|
98
|
-
#
|
99
|
-
# @param tags [Array<String>, Hash<String, String>, nil] Tags specified in any form.
|
100
|
-
# @return [Array<String>, nil] the list of tags in canonical form.
|
101
|
-
#
|
102
|
-
# @todo We should delegate this to thje datagram builder of the current client,
|
103
|
-
# to ensure that this logic matches the logic of the active datagram builder.
|
104
|
-
def normalize_tags(tags)
|
105
|
-
return [] unless tags
|
106
|
-
tags = tags.map { |k, v| "#{k}:#{v}" } if tags.is_a?(Hash)
|
107
|
-
|
108
|
-
# Fast path when no string replacement is needed
|
109
|
-
return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
|
110
|
-
tags.map { |tag| tag.tr('|,', '') }
|
116
|
+
# For backwards compatibility
|
117
|
+
MetricExpectation = Expectation
|
111
118
|
end
|
112
119
|
end
|
113
|
-
|
114
|
-
# For backwards compatibility
|
115
|
-
StatsD::Instrument::MetricExpectation = StatsD::Instrument::Expectation
|