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.
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