statsd-instrument 2.3.2 → 2.4.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/ci.yml +31 -0
- data/.gitignore +1 -0
- data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +1027 -0
- data/.rubocop.yml +21 -0
- data/CHANGELOG.md +41 -0
- data/CONTRIBUTING.md +26 -6
- data/Gemfile +2 -0
- data/Rakefile +3 -1
- data/lib/statsd/instrument/assertions.rb +24 -18
- data/lib/statsd/instrument/backend.rb +3 -2
- data/lib/statsd/instrument/backends/capture_backend.rb +2 -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 +20 -17
- data/lib/statsd/instrument/environment.rb +2 -0
- data/lib/statsd/instrument/helpers.rb +6 -2
- data/lib/statsd/instrument/matchers.rb +14 -11
- data/lib/statsd/instrument/metric.rb +34 -21
- data/lib/statsd/instrument/metric_expectation.rb +32 -18
- data/lib/statsd/instrument/railtie.rb +2 -1
- data/lib/statsd/instrument/version.rb +3 -1
- data/lib/statsd/instrument.rb +85 -36
- data/lib/statsd-instrument.rb +2 -0
- data/statsd-instrument.gemspec +13 -10
- data/test/assertions_test.rb +15 -4
- 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/environment_test.rb +2 -1
- data/test/helpers_test.rb +2 -1
- data/test/integration_test.rb +27 -7
- data/test/logger_backend_test.rb +10 -8
- data/test/matchers_test.rb +34 -20
- data/test/metric_test.rb +15 -4
- data/test/statsd_instrumentation_test.rb +7 -7
- data/test/statsd_test.rb +100 -10
- data/test/test_helper.rb +2 -0
- data/test/udp_backend_test.rb +5 -28
- metadata +23 -5
- data/.travis.yml +0 -12
data/.rubocop.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
inherit_from:
|
2
|
+
- https://shopify.github.io/ruby-style-guide/rubocop.yml
|
3
|
+
|
4
|
+
AllCops:
|
5
|
+
TargetRubyVersion: 2.3
|
6
|
+
UseCache: true
|
7
|
+
CacheRootDirectory: tmp/rubocop
|
8
|
+
Exclude:
|
9
|
+
- statsd-instrument.gemspec
|
10
|
+
|
11
|
+
Naming/FileName:
|
12
|
+
Enabled: true
|
13
|
+
Exclude:
|
14
|
+
- lib/statsd-instrument.rb
|
15
|
+
|
16
|
+
Style/ClassAndModuleChildren:
|
17
|
+
Enabled: false # TODO: enable later
|
18
|
+
|
19
|
+
|
20
|
+
Style/MethodCallWithArgsParentheses:
|
21
|
+
Enabled: false # TODO: enable later
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,47 @@ please at an entry to the "unreleased changes" section below.
|
|
5
5
|
|
6
6
|
### Unreleased changes
|
7
7
|
|
8
|
+
## Version 2.4.0
|
9
|
+
|
10
|
+
- Add `StatsD.default_tags` to specify tags that should be included in all metrics. (#159)
|
11
|
+
- Improve assertion message when assertying metrics whose tags do not match. (#100)
|
12
|
+
- Enforce the Shopify Ruby style guide. (#164)
|
13
|
+
- Migrate CI to Github actions. (#158)
|
14
|
+
- Make the library frozen string literal-compatible. (#161, #163)
|
15
|
+
- Fix all Ruby warnings. (#162)
|
16
|
+
|
17
|
+
## Version 2.3.5
|
18
|
+
|
19
|
+
- Re-add `StatsD::Instrument.duration`, which was accidentally removed since verison 2.5.3 (#157)
|
20
|
+
|
21
|
+
## Version 2.3.4
|
22
|
+
|
23
|
+
- Improve performance of `Metric#to_s` (#152)
|
24
|
+
- Fix bug in escaping newlines for events with Datadog Backend (#153)
|
25
|
+
|
26
|
+
## Version 2.3.3
|
27
|
+
|
28
|
+
- Capture measure and distribution metrics on exception and early return (#134)
|
29
|
+
|
30
|
+
NOTE: Now that exceptions are measured statistics may behave differently. An exception example:
|
31
|
+
```
|
32
|
+
StatsD.measure('myhttpcall') do
|
33
|
+
my_http_object.invoke
|
34
|
+
end
|
35
|
+
```
|
36
|
+
Version 2.3.2 and below did not track metrics whenever a HTTP Timeout exception was raised.
|
37
|
+
2.3.3 and above will include those metrics which may increase the values included.
|
38
|
+
|
39
|
+
A return example:
|
40
|
+
```
|
41
|
+
StatsD.measure('myexpensivecalculation') do
|
42
|
+
return if expensive_calculation_disabled?
|
43
|
+
expensive_calculation
|
44
|
+
end
|
45
|
+
```
|
46
|
+
If `expensive_calculation_disabled?` is true 50% of the time version 2.3.2 will drop the
|
47
|
+
average metric considerably.
|
48
|
+
|
8
49
|
## Version 2.3.2
|
9
50
|
|
10
51
|
- Add option to override global prefix for metrics (#148)
|
data/CONTRIBUTING.md
CHANGED
@@ -11,27 +11,47 @@ This project is MIT licensed.
|
|
11
11
|
|
12
12
|
Report issues using the [Github issues tracker](https://github.com/Shopify/statsd-instrument/issues/new).
|
13
13
|
|
14
|
-
When reporting issues, please
|
14
|
+
When reporting issues, please include the following information:
|
15
15
|
|
16
16
|
- Your Ruby interpreter version.
|
17
17
|
- The statsd-instrument version. **Note:** only the latest version is supported.
|
18
18
|
- The StatsD backend you are using.
|
19
19
|
|
20
|
-
##
|
20
|
+
## Opening pull requests
|
21
21
|
|
22
22
|
1. Fork the repository, and create a branch.
|
23
23
|
2. Implement the feature or bugfix, and add tests that cover the changed functionality.
|
24
|
-
3. Create a pull request. Make sure that you get
|
24
|
+
3. Create a pull request. Make sure that you get a green CI status on your commit.
|
25
25
|
|
26
26
|
Some notes:
|
27
27
|
|
28
|
-
- Make sure to follow to coding style.
|
28
|
+
- Make sure to follow to coding style. This is enforced by Rubocop
|
29
29
|
- Make sure your changes are properly documented using [yardoc syntax](http://www.rubydoc.info/gems/yard/file/docs/GettingStarted.md).
|
30
30
|
- Add an entry to the "unreleased changes" section of [CHANGELOG.md](./CHANGELOG.md).
|
31
31
|
- **Do not** update `StatsD::Instrument::VERSION`. This will be done during the release prodecure.
|
32
32
|
|
33
|
-
|
34
|
-
|
33
|
+
### On perfomance & benchmarking
|
34
|
+
|
35
|
+
This gem is used in production at Shopify, and is used to instrument some of
|
36
|
+
our hottest code paths. This means that we are very careful about not
|
37
|
+
introducing performance regressions in this library.
|
38
|
+
|
39
|
+
**Important:** Whenever you make changes to the metric emission code path in this library,
|
40
|
+
you **must** include benchmark results to show the impact of your changes.
|
41
|
+
|
42
|
+
The `test/benchmark/` folder contains some example benchmark script that you
|
43
|
+
can use, or can serve as a starting point. Please run your benchmark on your
|
44
|
+
pull request revision, as well as the latest revision on `master`.
|
45
|
+
|
46
|
+
### On backwards compatibility
|
47
|
+
|
48
|
+
Shopify's codebases are heavily instrumented using this library. As a result, we cannot
|
49
|
+
accept changes that are backwards incompatible:
|
50
|
+
|
51
|
+
- Changes that will require us to update our codebases.
|
52
|
+
- Changes that will cause metrics emitted by this library to change in form or shape.
|
53
|
+
|
54
|
+
This means that we may not be able to accept fixes for what you consider a bug.
|
35
55
|
|
36
56
|
## Release procedure
|
37
57
|
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module StatsD::Instrument::Assertions
|
2
4
|
include StatsD::Instrument::Helpers
|
3
5
|
|
4
6
|
def assert_no_statsd_calls(metric_name = nil, &block)
|
5
7
|
metrics = capture_statsd_calls(&block)
|
6
8
|
metrics.select! { |m| m.name == metric_name } if metric_name
|
7
|
-
assert
|
9
|
+
assert(metrics.empty?, "No StatsD calls for metric #{metrics.map(&:name).join(', ')} expected.")
|
8
10
|
end
|
9
11
|
|
10
12
|
def assert_statsd_increment(metric_name, options = {}, &block)
|
@@ -48,32 +50,36 @@ module StatsD::Instrument::Assertions
|
|
48
50
|
expected_metric_times = expected_metric.times
|
49
51
|
expected_metric_times_remaining = expected_metric.times
|
50
52
|
filtered_metrics = metrics.select { |m| m.type == expected_metric.type && m.name == expected_metric.name }
|
51
|
-
|
52
|
-
"No StatsD calls for metric #{expected_metric.name} of type #{expected_metric.type} were made."
|
53
|
+
refute(filtered_metrics.empty?,
|
54
|
+
"No StatsD calls for metric #{expected_metric.name} of type #{expected_metric.type} were made.")
|
53
55
|
|
54
56
|
filtered_metrics.each do |metric|
|
57
|
+
next unless expected_metric.matches(metric)
|
58
|
+
|
55
59
|
assert within_numeric_range?(metric.sample_rate),
|
56
60
|
"Unexpected sample rate type for metric #{metric.name}, must be numeric"
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
|
62
|
+
assert(expected_metric_times_remaining > 0,
|
63
|
+
"Unexpected StatsD call; number of times this metric was expected exceeded: #{expected_metric.inspect}")
|
64
|
+
|
65
|
+
expected_metric_times_remaining -= 1
|
66
|
+
metrics.delete(metric)
|
67
|
+
if expected_metric_times_remaining == 0
|
68
|
+
matched_expected_metrics << expected_metric
|
65
69
|
end
|
66
70
|
end
|
67
71
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
+
msg = +"Metric expected #{expected_metric_times} times but seen " \
|
73
|
+
"#{expected_metric_times - expected_metric_times_remaining} " \
|
74
|
+
"times: #{expected_metric.inspect}."
|
75
|
+
msg << "\nCaptured metrics with the same key: #{filtered_metrics}" if filtered_metrics.any?
|
76
|
+
|
77
|
+
assert(expected_metric_times_remaining == 0, msg)
|
72
78
|
end
|
73
79
|
expected_metrics -= matched_expected_metrics
|
74
80
|
|
75
|
-
assert
|
76
|
-
"Unexpected StatsD calls; the following metric expectations were not satisfied: #{expected_metrics.inspect}"
|
81
|
+
assert(expected_metrics.empty?,
|
82
|
+
"Unexpected StatsD calls; the following metric expectations were not satisfied: #{expected_metrics.inspect}")
|
77
83
|
end
|
78
84
|
|
79
85
|
private
|
@@ -87,6 +93,6 @@ module StatsD::Instrument::Assertions
|
|
87
93
|
end
|
88
94
|
|
89
95
|
def within_numeric_range?(object)
|
90
|
-
object.
|
96
|
+
object.is_a?(Numeric) && (0.0..1.0).cover?(object)
|
91
97
|
end
|
92
98
|
end
|
@@ -1,12 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This abstract class specifies the interface a backend implementation should conform to.
|
2
4
|
# @abstract
|
3
5
|
class StatsD::Instrument::Backend
|
4
|
-
|
5
6
|
# Collects a metric.
|
6
7
|
#
|
7
8
|
# @param metric [StatsD::Instrument::Metric] The metric to collect
|
8
9
|
# @return [void]
|
9
|
-
def collect_metric(
|
10
|
+
def collect_metric(_metric)
|
10
11
|
raise NotImplementedError, "Use a concerete backend implementation"
|
11
12
|
end
|
12
13
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module StatsD::Instrument::Backends
|
3
4
|
# The logger backend simply logs every metric to a logger
|
4
5
|
# @!attribute logger
|
5
6
|
# @return [Logger]
|
6
7
|
class LoggerBackend < StatsD::Instrument::Backend
|
7
|
-
|
8
8
|
attr_accessor :logger
|
9
9
|
|
10
10
|
def initialize(logger)
|
@@ -14,7 +14,7 @@ module StatsD::Instrument::Backends
|
|
14
14
|
# @param metric [StatsD::Instrument::Metric]
|
15
15
|
# @return [void]
|
16
16
|
def collect_metric(metric)
|
17
|
-
logger.info
|
17
|
+
logger.info("[StatsD] #{metric}")
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'monitor'
|
2
4
|
|
3
5
|
module StatsD::Instrument::Backends
|
4
6
|
class UDPBackend < StatsD::Instrument::Backend
|
5
|
-
|
6
7
|
BASE_SUPPORTED_METRIC_TYPES = { c: true, ms: true, g: true, s: true }
|
7
8
|
|
8
9
|
class DogStatsDProtocol
|
@@ -24,11 +25,11 @@ module StatsD::Instrument::Backends
|
|
24
25
|
SUPPORTED_METRIC_TYPES = BASE_SUPPORTED_METRIC_TYPES.merge(h: true, _e: true, _sc: true, d: true)
|
25
26
|
|
26
27
|
def generate_packet(metric)
|
27
|
-
packet = ""
|
28
|
+
packet = +""
|
28
29
|
|
29
30
|
if metric.type == :_e
|
30
|
-
escaped_title = metric.name.
|
31
|
-
escaped_text = metric.value.
|
31
|
+
escaped_title = metric.name.gsub("\n", "\\n")
|
32
|
+
escaped_text = metric.value.gsub("\n", "\\n")
|
32
33
|
|
33
34
|
packet << "_e{#{escaped_title.size},#{escaped_text.size}}:#{escaped_title}|#{escaped_text}"
|
34
35
|
packet << generate_metadata(metric, EVENT_OPTIONS)
|
@@ -41,6 +42,7 @@ module StatsD::Instrument::Backends
|
|
41
42
|
|
42
43
|
packet << "|@#{metric.sample_rate}" if metric.sample_rate < 1
|
43
44
|
packet << "|##{metric.tags.join(',')}" if metric.tags
|
45
|
+
|
44
46
|
packet
|
45
47
|
end
|
46
48
|
|
@@ -57,7 +59,7 @@ module StatsD::Instrument::Backends
|
|
57
59
|
SUPPORTED_METRIC_TYPES = BASE_SUPPORTED_METRIC_TYPES.merge(kv: true)
|
58
60
|
|
59
61
|
def generate_packet(metric)
|
60
|
-
packet = "#{metric.name}:#{metric.value}|#{metric.type}"
|
62
|
+
packet = +"#{metric.name}:#{metric.value}|#{metric.type}"
|
61
63
|
packet << "|@#{metric.sample_rate}" unless metric.sample_rate == 1
|
62
64
|
packet << "\n"
|
63
65
|
packet
|
@@ -68,7 +70,7 @@ module StatsD::Instrument::Backends
|
|
68
70
|
SUPPORTED_METRIC_TYPES = BASE_SUPPORTED_METRIC_TYPES
|
69
71
|
|
70
72
|
def generate_packet(metric)
|
71
|
-
packet = "#{metric.name}:#{metric.value}|#{metric.type}"
|
73
|
+
packet = +"#{metric.name}:#{metric.value}|#{metric.type}"
|
72
74
|
packet << "|@#{metric.sample_rate}" if metric.sample_rate < 1
|
73
75
|
packet
|
74
76
|
end
|
@@ -88,19 +90,20 @@ module StatsD::Instrument::Backends
|
|
88
90
|
|
89
91
|
def implementation=(value)
|
90
92
|
@packet_factory = case value
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
93
|
+
when :datadog
|
94
|
+
DogStatsDProtocol.new
|
95
|
+
when :statsite
|
96
|
+
StatsiteStatsDProtocol.new
|
97
|
+
else
|
98
|
+
StatsDProtocol.new
|
99
|
+
end
|
98
100
|
@implementation = value
|
99
101
|
end
|
100
102
|
|
101
103
|
def collect_metric(metric)
|
102
104
|
unless @packet_factory.class::SUPPORTED_METRIC_TYPES[metric.type]
|
103
|
-
StatsD.logger.warn("[StatsD] Metric type #{metric.type.inspect} not supported
|
105
|
+
StatsD.logger.warn("[StatsD] Metric type #{metric.type.inspect} is not supported " \
|
106
|
+
"on #{implementation} implementation.")
|
104
107
|
return false
|
105
108
|
end
|
106
109
|
|
@@ -139,13 +142,13 @@ module StatsD::Instrument::Backends
|
|
139
142
|
synchronize do
|
140
143
|
socket.send(command, 0) > 0
|
141
144
|
end
|
142
|
-
rescue ThreadError
|
145
|
+
rescue ThreadError
|
143
146
|
# In cases where a TERM or KILL signal has been sent, and we send stats as
|
144
147
|
# part of a signal handler, locks cannot be acquired, so we do our best
|
145
148
|
# to try and send the command without a lock.
|
146
149
|
socket.send(command, 0) > 0
|
147
|
-
rescue SocketError, IOError, SystemCallError
|
148
|
-
StatsD.logger.error
|
150
|
+
rescue SocketError, IOError, SystemCallError => e
|
151
|
+
StatsD.logger.error("[StatsD] #{e.class.name}: #{e.message}")
|
149
152
|
invalidate_socket
|
150
153
|
end
|
151
154
|
|
@@ -1,11 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module StatsD::Instrument::Helpers
|
2
4
|
def capture_statsd_calls(&block)
|
3
5
|
mock_backend = StatsD::Instrument::Backends::CaptureBackend.new
|
4
|
-
old_backend
|
6
|
+
old_backend = StatsD.backend
|
7
|
+
StatsD.backend = mock_backend
|
8
|
+
|
5
9
|
block.call
|
6
10
|
mock_backend.collected_metrics
|
7
11
|
ensure
|
8
|
-
if old_backend.
|
12
|
+
if old_backend.is_a?(StatsD::Instrument::Backends::CaptureBackend)
|
9
13
|
old_backend.collected_metrics.concat(mock_backend.collected_metrics)
|
10
14
|
end
|
11
15
|
|
@@ -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,7 +31,6 @@
|
|
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
|
attr_accessor :type, :name, :value, :sample_rate, :tags, :metadata
|
34
35
|
|
35
36
|
# Initializes a new metric instance.
|
@@ -47,9 +48,18 @@ class StatsD::Instrument::Metric
|
|
47
48
|
# @option options [Array<String>, Hash<String, String>, nil] :tags The tags to apply to this metric.
|
48
49
|
# See {.normalize_tags} for more information.
|
49
50
|
def initialize(options = {})
|
50
|
-
|
51
|
-
|
52
|
-
|
51
|
+
if options[:type]
|
52
|
+
@type = options[:type]
|
53
|
+
else
|
54
|
+
raise ArgumentError, "Metric :type is required."
|
55
|
+
end
|
56
|
+
|
57
|
+
if options[:name]
|
58
|
+
@name = normalize_name(options[:name])
|
59
|
+
else
|
60
|
+
raise ArgumentError, "Metric :name is required."
|
61
|
+
end
|
62
|
+
|
53
63
|
unless options[:no_prefix]
|
54
64
|
@name = if options[:prefix]
|
55
65
|
"#{options[:prefix]}.#{@name}"
|
@@ -58,10 +68,13 @@ class StatsD::Instrument::Metric
|
|
58
68
|
end
|
59
69
|
end
|
60
70
|
|
61
|
-
@value
|
71
|
+
@value = options[:value] || default_value
|
62
72
|
@sample_rate = options[:sample_rate] || StatsD.default_sample_rate
|
63
|
-
@tags
|
64
|
-
|
73
|
+
@tags = StatsD::Instrument::Metric.normalize_tags(options[:tags])
|
74
|
+
if StatsD.default_tags
|
75
|
+
@tags = Array(@tags) + StatsD.default_tags
|
76
|
+
end
|
77
|
+
@metadata = options.reject { |k, _| [:type, :name, :value, :sample_rate, :tags].include?(k) }
|
65
78
|
end
|
66
79
|
|
67
80
|
# The default value for this metric, which will be used if it is not set.
|
@@ -73,36 +86,36 @@ class StatsD::Instrument::Metric
|
|
73
86
|
# @raise ArgumentError if the metric type doesn't have a default value
|
74
87
|
def default_value
|
75
88
|
case type
|
76
|
-
|
77
|
-
|
89
|
+
when :c then 1
|
90
|
+
else raise ArgumentError, "A value is required for metric type #{type.inspect}."
|
78
91
|
end
|
79
92
|
end
|
80
93
|
|
81
94
|
# @private
|
82
95
|
# @return [String]
|
83
96
|
def to_s
|
84
|
-
str = "#{TYPES[type]} #{name}:#{value}"
|
97
|
+
str = +"#{TYPES[type]} #{name}:#{value}"
|
85
98
|
str << " @#{sample_rate}" if sample_rate != 1.0
|
86
|
-
|
99
|
+
tags&.each { |tag| str << " ##{tag}" }
|
87
100
|
str
|
88
101
|
end
|
89
102
|
|
90
103
|
# @private
|
91
104
|
# @return [String]
|
92
105
|
def inspect
|
93
|
-
"#<StatsD::Instrument::Metric #{self
|
106
|
+
"#<StatsD::Instrument::Metric #{self}>"
|
94
107
|
end
|
95
108
|
|
96
109
|
# The metric types that are supported by this library. Note that every StatsD server
|
97
110
|
# implementation only supports a subset of them.
|
98
111
|
TYPES = {
|
99
|
-
c:
|
112
|
+
c: 'increment',
|
100
113
|
ms: 'measure',
|
101
|
-
g:
|
102
|
-
h:
|
103
|
-
d:
|
114
|
+
g: 'gauge',
|
115
|
+
h: 'histogram',
|
116
|
+
d: 'distribution',
|
104
117
|
kv: 'key/value',
|
105
|
-
s:
|
118
|
+
s: 'set',
|
106
119
|
}
|
107
120
|
|
108
121
|
# Strip metric names of special characters used by StatsD line protocol, replace with underscore
|
@@ -110,7 +123,7 @@ class StatsD::Instrument::Metric
|
|
110
123
|
# @param name [String]
|
111
124
|
# @return [String]
|
112
125
|
def normalize_name(name)
|
113
|
-
name.tr(':|@'
|
126
|
+
name.tr(':|@', '_')
|
114
127
|
end
|
115
128
|
|
116
129
|
# Utility function to convert tags to the canonical form.
|
@@ -122,7 +135,7 @@ class StatsD::Instrument::Metric
|
|
122
135
|
# @return [Array<String>, nil] the list of tags in canonical form.
|
123
136
|
def self.normalize_tags(tags)
|
124
137
|
return unless tags
|
125
|
-
tags = tags.map { |k, v| k.to_s + ":"
|
126
|
-
tags.map { |tag| tag.tr('|,'
|
138
|
+
tags = tags.map { |k, v| k.to_s + ":" + v.to_s } if tags.is_a?(Hash)
|
139
|
+
tags.map { |tag| tag.tr('|,', '') }
|
127
140
|
end
|
128
141
|
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
|