statsd-instrument 2.3.2 → 2.4.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/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
|