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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/workflows/ci.yml +31 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +1027 -0
  6. data/.rubocop.yml +21 -0
  7. data/CHANGELOG.md +41 -0
  8. data/CONTRIBUTING.md +26 -6
  9. data/Gemfile +2 -0
  10. data/Rakefile +3 -1
  11. data/lib/statsd/instrument/assertions.rb +24 -18
  12. data/lib/statsd/instrument/backend.rb +3 -2
  13. data/lib/statsd/instrument/backends/capture_backend.rb +2 -1
  14. data/lib/statsd/instrument/backends/logger_backend.rb +3 -3
  15. data/lib/statsd/instrument/backends/null_backend.rb +2 -0
  16. data/lib/statsd/instrument/backends/udp_backend.rb +20 -17
  17. data/lib/statsd/instrument/environment.rb +2 -0
  18. data/lib/statsd/instrument/helpers.rb +6 -2
  19. data/lib/statsd/instrument/matchers.rb +14 -11
  20. data/lib/statsd/instrument/metric.rb +34 -21
  21. data/lib/statsd/instrument/metric_expectation.rb +32 -18
  22. data/lib/statsd/instrument/railtie.rb +2 -1
  23. data/lib/statsd/instrument/version.rb +3 -1
  24. data/lib/statsd/instrument.rb +85 -36
  25. data/lib/statsd-instrument.rb +2 -0
  26. data/statsd-instrument.gemspec +13 -10
  27. data/test/assertions_test.rb +15 -4
  28. data/test/benchmark/default_tags.rb +47 -0
  29. data/test/benchmark/metrics.rb +9 -8
  30. data/test/benchmark/tags.rb +5 -3
  31. data/test/capture_backend_test.rb +4 -2
  32. data/test/environment_test.rb +2 -1
  33. data/test/helpers_test.rb +2 -1
  34. data/test/integration_test.rb +27 -7
  35. data/test/logger_backend_test.rb +10 -8
  36. data/test/matchers_test.rb +34 -20
  37. data/test/metric_test.rb +15 -4
  38. data/test/statsd_instrumentation_test.rb +7 -7
  39. data/test/statsd_test.rb +100 -10
  40. data/test/test_helper.rb +2 -0
  41. data/test/udp_backend_test.rb +5 -28
  42. metadata +23 -5
  43. 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 incldue the following information:
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
- ## Pull request
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 Travis CI passes.
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
- > **Important:** if you change anything in the hot code path (sending a StatsD metric), please
34
- > include benchmarks to show the performance impact of your changes.
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
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
  gemspec
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rake/testtask'
3
5
 
@@ -7,4 +9,4 @@ Rake::TestTask.new('test') do |t|
7
9
  t.test_files = FileList['test/*.rb']
8
10
  end
9
11
 
10
- task :default => :test
12
+ task default: :test
@@ -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 metrics.empty?, "No StatsD calls for metric #{metrics.map(&:name).join(', ')} expected."
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
- assert filtered_metrics.length > 0,
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
- if expected_metric.matches(metric)
58
- assert expected_metric_times_remaining > 0,
59
- "Unexpected StatsD call; number of times this metric was expected exceeded: #{expected_metric.inspect}"
60
- expected_metric_times_remaining -= 1
61
- metrics.delete(metric)
62
- if expected_metric_times_remaining == 0
63
- matched_expected_metrics << expected_metric
64
- end
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
- assert expected_metric_times_remaining == 0,
69
- "Metric expected #{expected_metric_times} times but seen"\
70
- " #{expected_metric_times-expected_metric_times_remaining}"\
71
- " times: #{expected_metric.inspect}"
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 expected_metrics.empty?,
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.kind_of?(Numeric) && (0.0..1.0).cover?(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(metric)
10
+ def collect_metric(_metric)
10
11
  raise NotImplementedError, "Use a concerete backend implementation"
11
12
  end
12
13
  end
@@ -1,5 +1,6 @@
1
- module StatsD::Instrument::Backends
1
+ # frozen_string_literal: true
2
2
 
3
+ module StatsD::Instrument::Backends
3
4
  # The capture backend is used to capture the metrics that are collected, so you can
4
5
  # run assertions on them.
5
6
  #
@@ -1,10 +1,10 @@
1
- module StatsD::Instrument::Backends
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 "[StatsD] #{metric}"
17
+ logger.info("[StatsD] #{metric}")
18
18
  end
19
19
  end
20
20
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module StatsD::Instrument::Backends
2
4
  # The null backend does nothing when receiving a metric, effectively disabling the gem completely.
3
5
  class NullBackend < StatsD::Instrument::Backend
@@ -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.tr('\n', '\\n')
31
- escaped_text = metric.value.tr('\n', '\\n')
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
- when :datadog
92
- DogStatsDProtocol.new
93
- when :statsite
94
- StatsiteStatsDProtocol.new
95
- else
96
- StatsDProtocol.new
97
- end
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 on #{implementation} implementation.")
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 => e
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, Errno::ECONNREFUSED => e
148
- StatsD.logger.error "[StatsD] #{e.class.name}: #{e.message}"
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,3 +1,5 @@
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
@@ -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, StatsD.backend = StatsD.backend, mock_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.kind_of?(StatsD::Instrument::Backends::CaptureBackend)
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
- begin
27
- expect_statsd_call(@metric_type, @metric_name, @options, &block)
28
- rescue RSpec::Expectations::ExpectationNotMetError => e
29
- @message = e.message
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
- raise RSpec::Expectations::ExpectationNotMetError, "No StatsD calls for metric #{metric_name} were made." if metrics.empty?
54
- raise RSpec::Expectations::ExpectationNotMetError, "The numbers of StatsD calls for metric #{metric_name} was unexpected. Expected #{options[:times].inspect}, got #{metrics.length}" if options[:times] && options[:times] != metrics.length
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
- if !found
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
- @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)
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 = options[:value] || default_value
71
+ @value = options[:value] || default_value
62
72
  @sample_rate = options[:sample_rate] || StatsD.default_sample_rate
63
- @tags = StatsD::Instrument::Metric.normalize_tags(options[:tags])
64
- @metadata = options.reject { |k, _| [:type, :name, :value, :sample_rate, :tags].include?(k) }
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
- when :c; 1
77
- else raise ArgumentError, "A value is required for metric type #{type.inspect}."
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
- str << " " << tags.map { |t| "##{t}"}.join(' ') if tags
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.to_s}>"
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: 'increment',
112
+ c: 'increment',
100
113
  ms: 'measure',
101
- g: 'gauge',
102
- h: 'histogram',
103
- d: 'distribution',
114
+ g: 'gauge',
115
+ h: 'histogram',
116
+ d: 'distribution',
104
117
  kv: 'key/value',
105
- s: 'set',
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(':|@'.freeze, '_')
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 + ":".freeze + v.to_s } if tags.is_a?(Hash)
126
- tags.map { |tag| tag.tr('|,'.freeze, ''.freeze) }
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
- @type = options[:type] or raise ArgumentError, "Metric :type is required."
9
- @name = options[:name] or raise ArgumentError, "Metric :name is required."
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
- case type
43
- when :c; 1
44
- end
58
+ 1 if type == :c
45
59
  end
46
60
 
47
61
  TYPES = {
48
- c: 'increment',
49
- ms: 'measure',
50
- g: 'gauge',
51
- h: 'histogram',
52
- d: 'distribution',
53
- kv: 'key/value',
54
- s: 'set',
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.to_s}>"
80
+ "#<StatsD::Instrument::MetricExpectation #{self}>"
67
81
  end
68
82
  end