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,11 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module StatsD
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module StatsD
|
4
|
+
module Instrument
|
5
|
+
module Helpers
|
6
|
+
def capture_statsd_datagrams(client: nil, &block)
|
7
|
+
client ||= StatsD.singleton_client
|
8
|
+
client.capture(&block)
|
9
|
+
end
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
+
# For backwards compatibility
|
12
|
+
alias_method :capture_statsd_calls, :capture_statsd_datagrams
|
13
|
+
end
|
14
|
+
end
|
11
15
|
end
|
@@ -1,24 +1,28 @@
|
|
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 LogSink
|
8
|
+
attr_reader :logger, :severity
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def initialize(logger, severity: Logger::DEBUG)
|
11
|
+
@logger = logger
|
12
|
+
@severity = severity
|
13
|
+
end
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
def sample?(_sample_rate)
|
16
|
+
true
|
17
|
+
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
19
|
+
def <<(datagram)
|
20
|
+
# Some implementations require a newline at the end of datagrams.
|
21
|
+
# When logging, we make sure those newlines are removed using chomp.
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
+
logger.add(severity, "[StatsD] #{datagram.chomp}")
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
23
27
|
end
|
24
28
|
end
|
@@ -3,97 +3,113 @@
|
|
3
3
|
require 'rspec/expectations'
|
4
4
|
require 'rspec/core/version'
|
5
5
|
|
6
|
-
module StatsD
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
include StatsD::Instrument::Helpers
|
19
|
-
|
20
|
-
def initialize(metric_type, metric_name, options = {})
|
21
|
-
@metric_type = metric_type
|
22
|
-
@metric_name = metric_name
|
23
|
-
@options = options
|
24
|
-
end
|
6
|
+
module StatsD
|
7
|
+
module Instrument
|
8
|
+
module Matchers
|
9
|
+
class Matcher
|
10
|
+
include(RSpec::Matchers::Composable) if RSpec::Core::Version::STRING.start_with?('3')
|
11
|
+
include StatsD::Instrument::Helpers
|
12
|
+
|
13
|
+
def initialize(metric_type, metric_name, options = {})
|
14
|
+
@metric_type = metric_type
|
15
|
+
@metric_name = metric_name
|
16
|
+
@options = options
|
17
|
+
end
|
25
18
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
19
|
+
def matches?(block)
|
20
|
+
expect_statsd_call(@metric_type, @metric_name, @options, &block)
|
21
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e
|
22
|
+
@message = e.message
|
23
|
+
false
|
24
|
+
end
|
32
25
|
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
def failure_message
|
27
|
+
@message
|
28
|
+
end
|
36
29
|
|
37
|
-
|
38
|
-
|
39
|
-
|
30
|
+
def failure_message_when_negated
|
31
|
+
"No StatsD calls for metric #{@metric_name} expected."
|
32
|
+
end
|
40
33
|
|
41
|
-
|
42
|
-
|
43
|
-
|
34
|
+
def supports_block_expectations?
|
35
|
+
true
|
36
|
+
end
|
44
37
|
|
45
|
-
|
38
|
+
private
|
46
39
|
|
47
|
-
|
48
|
-
|
49
|
-
|
40
|
+
def expect_statsd_call(metric_type, metric_name, options, &block)
|
41
|
+
metrics = capture_statsd_calls(&block)
|
42
|
+
metrics = metrics.select { |m| m.type == metric_type && m.name == metric_name }
|
50
43
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
44
|
+
if metrics.empty?
|
45
|
+
raise RSpec::Expectations::ExpectationNotMetError, "No StatsD calls for metric #{metric_name} were made."
|
46
|
+
elsif options[:times] && options[:times] != metrics.length
|
47
|
+
raise RSpec::Expectations::ExpectationNotMetError, "The numbers of StatsD calls for metric " \
|
48
|
+
"#{metric_name} was unexpected. Expected #{options[:times].inspect}, got #{metrics.length}"
|
49
|
+
end
|
50
|
+
|
51
|
+
[:sample_rate, :value, :tags].each do |expectation|
|
52
|
+
next unless options[expectation]
|
53
|
+
|
54
|
+
num_matches = metrics.count do |m|
|
55
|
+
matcher = RSpec::Matchers::BuiltIn::Match.new(options[expectation])
|
56
|
+
matcher.matches?(m.public_send(expectation))
|
57
|
+
end
|
57
58
|
|
58
|
-
|
59
|
-
next unless options[expectation]
|
59
|
+
found = options[:times] ? num_matches == options[:times] : num_matches > 0
|
60
60
|
|
61
|
-
|
62
|
-
|
63
|
-
|
61
|
+
unless found
|
62
|
+
message = metric_information(metric_name, options, metrics, expectation)
|
63
|
+
raise RSpec::Expectations::ExpectationNotMetError, message
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
true
|
64
68
|
end
|
65
69
|
|
66
|
-
|
70
|
+
def metric_information(metric_name, options, metrics, expectation)
|
71
|
+
message = "expected StatsD #{expectation.inspect} for metric '#{metric_name}' to be called"
|
72
|
+
|
73
|
+
message += "\n "
|
74
|
+
message += options[:times] ? "exactly #{options[:times]} times" : "at least once"
|
75
|
+
message += " with: #{options[expectation]}"
|
67
76
|
|
68
|
-
|
69
|
-
|
70
|
-
|
77
|
+
message += "\n captured metric values: #{metrics.map(&expectation).join(', ')}"
|
78
|
+
|
79
|
+
message
|
71
80
|
end
|
72
81
|
end
|
73
82
|
|
74
|
-
|
75
|
-
|
83
|
+
Increment = Class.new(Matcher)
|
84
|
+
Measure = Class.new(Matcher)
|
85
|
+
Gauge = Class.new(Matcher)
|
86
|
+
Set = Class.new(Matcher)
|
87
|
+
Histogram = Class.new(Matcher)
|
88
|
+
Distribution = Class.new(Matcher)
|
76
89
|
|
77
|
-
|
78
|
-
|
90
|
+
def trigger_statsd_increment(metric_name, options = {})
|
91
|
+
Increment.new(:c, metric_name, options)
|
92
|
+
end
|
79
93
|
|
80
|
-
|
81
|
-
|
82
|
-
|
94
|
+
def trigger_statsd_measure(metric_name, options = {})
|
95
|
+
Measure.new(:ms, metric_name, options)
|
96
|
+
end
|
83
97
|
|
84
|
-
|
98
|
+
def trigger_statsd_gauge(metric_name, options = {})
|
99
|
+
Gauge.new(:g, metric_name, options)
|
100
|
+
end
|
85
101
|
|
86
|
-
|
87
|
-
|
88
|
-
|
102
|
+
def trigger_statsd_set(metric_name, options = {})
|
103
|
+
Set.new(:s, metric_name, options)
|
104
|
+
end
|
89
105
|
|
90
|
-
|
91
|
-
|
106
|
+
def trigger_statsd_histogram(metric_name, options = {})
|
107
|
+
Histogram.new(:h, metric_name, options)
|
108
|
+
end
|
92
109
|
|
93
|
-
|
94
|
-
|
110
|
+
def trigger_statsd_distribution(metric_name, options = {})
|
111
|
+
Distribution.new(:d, metric_name, options)
|
112
|
+
end
|
95
113
|
end
|
96
|
-
|
97
|
-
StatsD::Instrument::Matchers.const_set(method_name.capitalize, klass)
|
98
114
|
end
|
99
115
|
end
|
@@ -1,13 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
class
|
6
|
-
|
7
|
-
|
8
|
-
|
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 NullSink
|
8
|
+
def sample?(_sample_rate)
|
9
|
+
true
|
10
|
+
end
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
+
def <<(_datagram)
|
13
|
+
self # noop
|
14
|
+
end
|
15
|
+
end
|
12
16
|
end
|
13
17
|
end
|
@@ -1,11 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
module StatsD
|
4
|
+
module Instrument
|
5
|
+
# This Railtie runs some initializers that will set the logger to <tt>Rails#logger</tt>,
|
6
|
+
# and will initialize the {StatsD#backend} based on the Rails environment.
|
7
|
+
#
|
8
|
+
# @see StatsD::Instrument::Environment
|
9
|
+
class Railtie < Rails::Railtie
|
10
|
+
initializer 'statsd-instrument.use_rails_logger' do
|
11
|
+
::StatsD.logger = Rails.logger
|
12
|
+
end
|
13
|
+
end
|
10
14
|
end
|
11
15
|
end
|
@@ -1,14 +1,18 @@
|
|
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 StatsDDatagramBuilder < StatsD::Instrument::DatagramBuilder
|
8
|
+
unsupported_datagram_types :h, :d, :kv
|
7
9
|
|
8
|
-
|
10
|
+
protected
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
12
|
+
def normalize_tags(tags)
|
13
|
+
raise NotImplementedError, "#{self.class.name} does not support tags" if tags
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
13
17
|
end
|
14
18
|
end
|
@@ -1,62 +1,66 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
class
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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 UDPSink
|
8
|
+
def self.for_addr(addr)
|
9
|
+
host, port_as_string = addr.split(':', 2)
|
10
|
+
new(host, Integer(port_as_string))
|
11
|
+
end
|
10
12
|
|
11
|
-
|
13
|
+
attr_reader :host, :port
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def initialize(host, port)
|
16
|
+
@host = host
|
17
|
+
@port = port
|
18
|
+
@mutex = Mutex.new
|
19
|
+
@socket = nil
|
20
|
+
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
def sample?(sample_rate)
|
23
|
+
sample_rate == 1 || rand < sample_rate
|
24
|
+
end
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
def <<(datagram)
|
27
|
+
with_socket { |socket| socket.send(datagram, 0) > 0 }
|
28
|
+
self
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
rescue ThreadError
|
31
|
+
# In cases where a TERM or KILL signal has been sent, and we send stats as
|
32
|
+
# part of a signal handler, locks cannot be acquired, so we do our best
|
33
|
+
# to try and send the datagram without a lock.
|
34
|
+
socket.send(datagram, 0) > 0
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
rescue SocketError, IOError, SystemCallError
|
37
|
+
# TODO: log?
|
38
|
+
invalidate_socket
|
39
|
+
end
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
41
|
+
def addr
|
42
|
+
"#{host}:#{port}"
|
43
|
+
end
|
42
44
|
|
43
|
-
|
45
|
+
private
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
-
|
47
|
+
def with_socket
|
48
|
+
@mutex.synchronize { yield(socket) }
|
49
|
+
end
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
51
|
+
def socket
|
52
|
+
if @socket.nil?
|
53
|
+
@socket = UDPSocket.new
|
54
|
+
@socket.connect(@host, @port)
|
55
|
+
end
|
56
|
+
@socket
|
57
|
+
end
|
56
58
|
|
57
|
-
|
58
|
-
|
59
|
-
|
59
|
+
def invalidate_socket
|
60
|
+
@mutex.synchronize do
|
61
|
+
@socket = nil
|
62
|
+
end
|
63
|
+
end
|
60
64
|
end
|
61
65
|
end
|
62
66
|
end
|