statsd-instrument 3.0.2 → 3.1.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/lint.yml +22 -0
  3. data/.github/workflows/{ci.yml → tests.yml} +3 -21
  4. data/.rubocop.yml +1 -1
  5. data/CHANGELOG.md +6 -0
  6. data/Gemfile +8 -10
  7. data/README.md +3 -0
  8. data/Rakefile +6 -6
  9. data/benchmark/send-metrics-to-dev-null-log +12 -12
  10. data/benchmark/send-metrics-to-local-udp-receiver +16 -16
  11. data/lib/statsd-instrument.rb +1 -1
  12. data/lib/statsd/instrument.rb +56 -59
  13. data/lib/statsd/instrument/assertions.rb +1 -1
  14. data/lib/statsd/instrument/batched_udp_sink.rb +154 -0
  15. data/lib/statsd/instrument/client.rb +3 -3
  16. data/lib/statsd/instrument/datagram.rb +1 -1
  17. data/lib/statsd/instrument/datagram_builder.rb +10 -10
  18. data/lib/statsd/instrument/dogstatsd_datagram_builder.rb +2 -2
  19. data/lib/statsd/instrument/environment.rb +19 -11
  20. data/lib/statsd/instrument/expectation.rb +3 -3
  21. data/lib/statsd/instrument/matchers.rb +8 -4
  22. data/lib/statsd/instrument/railtie.rb +1 -1
  23. data/lib/statsd/instrument/rubocop.rb +8 -8
  24. data/lib/statsd/instrument/rubocop/measure_as_dist_argument.rb +1 -1
  25. data/lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb +2 -2
  26. data/lib/statsd/instrument/rubocop/metric_prefix_argument.rb +1 -1
  27. data/lib/statsd/instrument/rubocop/metric_return_value.rb +2 -2
  28. data/lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb +1 -1
  29. data/lib/statsd/instrument/rubocop/positional_arguments.rb +4 -4
  30. data/lib/statsd/instrument/rubocop/singleton_configuration.rb +1 -1
  31. data/lib/statsd/instrument/rubocop/splat_arguments.rb +2 -2
  32. data/lib/statsd/instrument/strict.rb +1 -1
  33. data/lib/statsd/instrument/udp_sink.rb +10 -12
  34. data/lib/statsd/instrument/version.rb +1 -1
  35. data/statsd-instrument.gemspec +2 -0
  36. data/test/assertions_test.rb +167 -169
  37. data/test/benchmark/clock_gettime.rb +1 -1
  38. data/test/benchmark/default_tags.rb +9 -9
  39. data/test/benchmark/metrics.rb +8 -8
  40. data/test/benchmark/tags.rb +4 -4
  41. data/test/capture_sink_test.rb +11 -11
  42. data/test/client_test.rb +64 -64
  43. data/test/datagram_builder_test.rb +40 -40
  44. data/test/datagram_test.rb +5 -5
  45. data/test/dogstatsd_datagram_builder_test.rb +22 -22
  46. data/test/environment_test.rb +26 -17
  47. data/test/helpers/rubocop_helper.rb +2 -2
  48. data/test/helpers_test.rb +12 -12
  49. data/test/integration_test.rb +6 -6
  50. data/test/log_sink_test.rb +2 -2
  51. data/test/matchers_test.rb +46 -46
  52. data/test/null_sink_test.rb +2 -2
  53. data/test/rubocop/measure_as_dist_argument_test.rb +2 -2
  54. data/test/rubocop/metaprogramming_positional_arguments_test.rb +2 -2
  55. data/test/rubocop/metric_prefix_argument_test.rb +2 -2
  56. data/test/rubocop/metric_return_value_test.rb +3 -3
  57. data/test/rubocop/metric_value_keyword_argument_test.rb +2 -2
  58. data/test/rubocop/positional_arguments_test.rb +2 -2
  59. data/test/rubocop/singleton_configuration_test.rb +8 -8
  60. data/test/rubocop/splat_arguments_test.rb +2 -2
  61. data/test/statsd_datagram_builder_test.rb +6 -6
  62. data/test/statsd_instrumentation_test.rb +104 -104
  63. data/test/statsd_test.rb +35 -35
  64. data/test/test_helper.rb +13 -6
  65. data/test/udp_sink_test.rb +117 -45
  66. metadata +20 -4
@@ -53,9 +53,9 @@ module StatsD
53
53
  # supported.
54
54
  def datagram_builder_class_for_implementation(implementation)
55
55
  case implementation.to_s
56
- when 'statsd'
56
+ when "statsd"
57
57
  StatsD::Instrument::StatsDDatagramBuilder
58
- when 'datadog', 'dogstatsd'
58
+ when "datadog", "dogstatsd"
59
59
  StatsD::Instrument::DogStatsDDatagramBuilder
60
60
  else
61
61
  raise NotImplementedError, "Implementation named #{implementation} could not be found"
@@ -148,7 +148,7 @@ module StatsD
148
148
  prefix: nil,
149
149
  default_sample_rate: 1.0,
150
150
  default_tags: nil,
151
- implementation: 'datadog',
151
+ implementation: "datadog",
152
152
  sink: StatsD::Instrument::NullSink.new,
153
153
  datagram_builder_class: self.class.datagram_builder_class_for_implementation(implementation)
154
154
  )
@@ -40,7 +40,7 @@ module StatsD
40
40
  end
41
41
 
42
42
  def tags
43
- @tags ||= parsed_datagram[:tags] ? parsed_datagram[:tags].split(',') : nil
43
+ @tags ||= parsed_datagram[:tags] ? parsed_datagram[:tags].split(",") : nil
44
44
  end
45
45
 
46
46
  def inspect
@@ -35,31 +35,31 @@ module StatsD
35
35
  end
36
36
 
37
37
  def c(name, value, sample_rate, tags)
38
- generate_generic_datagram(name, value, 'c', sample_rate, tags)
38
+ generate_generic_datagram(name, value, "c", sample_rate, tags)
39
39
  end
40
40
 
41
41
  def g(name, value, sample_rate, tags)
42
- generate_generic_datagram(name, value, 'g', sample_rate, tags)
42
+ generate_generic_datagram(name, value, "g", sample_rate, tags)
43
43
  end
44
44
 
45
45
  def ms(name, value, sample_rate, tags)
46
- generate_generic_datagram(name, value, 'ms', sample_rate, tags)
46
+ generate_generic_datagram(name, value, "ms", sample_rate, tags)
47
47
  end
48
48
 
49
49
  def s(name, value, sample_rate, tags)
50
- generate_generic_datagram(name, value, 's', sample_rate, tags)
50
+ generate_generic_datagram(name, value, "s", sample_rate, tags)
51
51
  end
52
52
 
53
53
  def h(name, value, sample_rate, tags)
54
- generate_generic_datagram(name, value, 'h', sample_rate, tags)
54
+ generate_generic_datagram(name, value, "h", sample_rate, tags)
55
55
  end
56
56
 
57
57
  def d(name, value, sample_rate, tags)
58
- generate_generic_datagram(name, value, 'd', sample_rate, tags)
58
+ generate_generic_datagram(name, value, "d", sample_rate, tags)
59
59
  end
60
60
 
61
61
  def kv(name, value, sample_rate, tags)
62
- generate_generic_datagram(name, value, 'kv', sample_rate, tags)
62
+ generate_generic_datagram(name, value, "kv", sample_rate, tags)
63
63
  end
64
64
 
65
65
  def latency_metric_type
@@ -83,21 +83,21 @@ module StatsD
83
83
 
84
84
  # Fast path when no string replacement is needed
85
85
  return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
86
- tags.map { |tag| tag.tr('|,', '') }
86
+ tags.map { |tag| tag.tr("|,", "") }
87
87
  end
88
88
 
89
89
  # Utility function to remove invalid characters from a StatsD metric name
90
90
  def normalize_name(name)
91
91
  # Fast path when no normalization is needed to avoid copying the string
92
92
  return name unless /[:|@]/.match?(name)
93
- name.tr(':|@', '_')
93
+ name.tr(":|@", "_")
94
94
  end
95
95
 
96
96
  def generate_generic_datagram(name, value, type, sample_rate, tags)
97
97
  tags = normalize_tags(tags) + default_tags
98
98
  datagram = +"#{@prefix}#{normalize_name(name)}:#{value}|#{type}"
99
99
  datagram << "|@#{sample_rate}" if sample_rate && sample_rate < 1
100
- datagram << "|##{tags.join(',')}" unless tags.empty?
100
+ datagram << "|##{tags.join(",")}" unless tags.empty?
101
101
  datagram
102
102
  end
103
103
  end
@@ -44,7 +44,7 @@ module StatsD
44
44
  datagram << "|p:#{priority}" if priority
45
45
  datagram << "|s:#{source_type_name}" if source_type_name
46
46
  datagram << "|t:#{alert_type}" if alert_type
47
- datagram << "|##{tags.join(',')}" unless tags.empty?
47
+ datagram << "|##{tags.join(",")}" unless tags.empty?
48
48
  datagram
49
49
  end
50
50
 
@@ -67,7 +67,7 @@ module StatsD
67
67
  datagram = +"_sc|#{@prefix}#{normalize_name(name)}|#{status_number}"
68
68
  datagram << "|h:#{hostname}" if hostname
69
69
  datagram << "|d:#{timestamp.to_i}" if timestamp
70
- datagram << "|##{tags.join(',')}" unless tags.empty?
70
+ datagram << "|##{tags.join(",")}" unless tags.empty?
71
71
  datagram << "|m:#{normalize_name(message)}" if message
72
72
  datagram
73
73
  end
@@ -49,33 +49,37 @@ module StatsD
49
49
  #
50
50
  # @return [String] The detected environment.
51
51
  def environment
52
- if env['STATSD_ENV']
53
- env['STATSD_ENV']
52
+ if env["STATSD_ENV"]
53
+ env["STATSD_ENV"]
54
54
  elsif defined?(Rails) && Rails.respond_to?(:env)
55
55
  Rails.env.to_s
56
56
  else
57
- env['RAILS_ENV'] || env['RACK_ENV'] || env['ENV'] || 'development'
57
+ env["RAILS_ENV"] || env["RACK_ENV"] || env["ENV"] || "development"
58
58
  end
59
59
  end
60
60
 
61
61
  def statsd_implementation
62
- env.fetch('STATSD_IMPLEMENTATION', 'datadog')
62
+ env.fetch("STATSD_IMPLEMENTATION", "datadog")
63
63
  end
64
64
 
65
65
  def statsd_sample_rate
66
- env.fetch('STATSD_SAMPLE_RATE', 1.0).to_f
66
+ env.fetch("STATSD_SAMPLE_RATE", 1.0).to_f
67
67
  end
68
68
 
69
69
  def statsd_prefix
70
- env.fetch('STATSD_PREFIX', nil)
70
+ env.fetch("STATSD_PREFIX", nil)
71
71
  end
72
72
 
73
73
  def statsd_addr
74
- env.fetch('STATSD_ADDR', 'localhost:8125')
74
+ env.fetch("STATSD_ADDR", "localhost:8125")
75
75
  end
76
76
 
77
77
  def statsd_default_tags
78
- env.key?('STATSD_DEFAULT_TAGS') ? env.fetch('STATSD_DEFAULT_TAGS').split(',') : nil
78
+ env.key?("STATSD_DEFAULT_TAGS") ? env.fetch("STATSD_DEFAULT_TAGS").split(",") : nil
79
+ end
80
+
81
+ def statsd_flush_interval
82
+ Float(env.fetch("STATSD_FLUSH_INTERVAL", 1.0))
79
83
  end
80
84
 
81
85
  def client
@@ -84,9 +88,13 @@ module StatsD
84
88
 
85
89
  def default_sink_for_environment
86
90
  case environment
87
- when 'production', 'staging'
88
- StatsD::Instrument::UDPSink.for_addr(statsd_addr)
89
- when 'test'
91
+ when "production", "staging"
92
+ if statsd_flush_interval > 0.0
93
+ StatsD::Instrument::BatchedUDPSink.for_addr(statsd_addr, flush_interval: statsd_flush_interval)
94
+ else
95
+ StatsD::Instrument::UDPSink.for_addr(statsd_addr)
96
+ end
97
+ when "test"
90
98
  StatsD::Instrument::NullSink.new
91
99
  else
92
100
  StatsD::Instrument::LogSink.new(StatsD.logger)
@@ -65,9 +65,9 @@ module StatsD
65
65
  end
66
66
 
67
67
  def to_s
68
- str = +"#{name}:#{value || '<anything>'}|#{type}"
68
+ str = +"#{name}:#{value || "<anything>"}|#{type}"
69
69
  str << "|@#{sample_rate}" if sample_rate
70
- str << "|#" << tags.join(',') if tags
70
+ str << "|#" << tags.join(",") if tags
71
71
  str << " (expected #{times} times)" if times > 1
72
72
  str
73
73
  end
@@ -109,7 +109,7 @@ module StatsD
109
109
 
110
110
  # Fast path when no string replacement is needed
111
111
  return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
112
- tags.map { |tag| tag.tr('|,', '') }
112
+ tags.map { |tag| tag.tr("|,", "") }
113
113
  end
114
114
  end
115
115
 
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rspec/expectations'
4
- require 'rspec/core/version'
3
+ require "rspec/expectations"
4
+ require "rspec/core/version"
5
5
 
6
6
  module StatsD
7
7
  module Instrument
8
8
  module Matchers
9
9
  class Matcher
10
- include(RSpec::Matchers::Composable) if RSpec::Core::Version::STRING.start_with?('3')
10
+ include(RSpec::Matchers::Composable) if RSpec::Core::Version::STRING.start_with?("3")
11
11
  include StatsD::Instrument::Helpers
12
12
 
13
13
  def initialize(metric_type, metric_name, options = {})
@@ -35,6 +35,10 @@ module StatsD
35
35
  true
36
36
  end
37
37
 
38
+ def description
39
+ "trigger a statsd call for metric #{@metric_name}"
40
+ end
41
+
38
42
  private
39
43
 
40
44
  def expect_statsd_call(metric_type, metric_name, options, &block)
@@ -74,7 +78,7 @@ module StatsD
74
78
  message += options[:times] ? "exactly #{options[:times]} times" : "at least once"
75
79
  message += " with: #{options[expectation]}"
76
80
 
77
- message += "\n captured metric values: #{metrics.map(&expectation).join(', ')}"
81
+ message += "\n captured metric values: #{metrics.map(&expectation).join(", ")}"
78
82
 
79
83
  message
80
84
  end
@@ -7,7 +7,7 @@ module StatsD
7
7
  #
8
8
  # @see StatsD::Instrument::Environment
9
9
  class Railtie < Rails::Railtie
10
- initializer 'statsd-instrument.use_rails_logger' do
10
+ initializer "statsd-instrument.use_rails_logger" do
11
11
  ::StatsD.logger = Rails.logger
12
12
  end
13
13
  end
@@ -72,11 +72,11 @@ module RuboCop
72
72
  end
73
73
  end
74
74
 
75
- require_relative 'rubocop/metaprogramming_positional_arguments'
76
- require_relative 'rubocop/metric_return_value'
77
- require_relative 'rubocop/metric_value_keyword_argument'
78
- require_relative 'rubocop/positional_arguments'
79
- require_relative 'rubocop/splat_arguments'
80
- require_relative 'rubocop/measure_as_dist_argument'
81
- require_relative 'rubocop/metric_prefix_argument'
82
- require_relative 'rubocop/singleton_configuration'
75
+ require_relative "rubocop/metaprogramming_positional_arguments"
76
+ require_relative "rubocop/metric_return_value"
77
+ require_relative "rubocop/metric_value_keyword_argument"
78
+ require_relative "rubocop/positional_arguments"
79
+ require_relative "rubocop/splat_arguments"
80
+ require_relative "rubocop/measure_as_dist_argument"
81
+ require_relative "rubocop/metric_prefix_argument"
82
+ require_relative "rubocop/singleton_configuration"
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require_relative '../rubocop' unless defined?(RuboCop::Cop::StatsD)
3
+ require_relative "../rubocop" unless defined?(RuboCop::Cop::StatsD)
4
4
 
5
5
  module RuboCop
6
6
  module Cop
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require_relative '../rubocop' unless defined?(RuboCop::Cop::StatsD)
3
+ require_relative "../rubocop" unless defined?(RuboCop::Cop::StatsD)
4
4
 
5
5
  module RuboCop
6
6
  module Cop
@@ -19,7 +19,7 @@ module RuboCop
19
19
  class MetaprogrammingPositionalArguments < Cop
20
20
  include RuboCop::Cop::StatsD
21
21
 
22
- MSG = 'Use keyword arguments for StatsD metaprogramming macros'
22
+ MSG = "Use keyword arguments for StatsD metaprogramming macros"
23
23
 
24
24
  def on_send(node)
25
25
  if metaprogramming_method?(node)
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require_relative '../rubocop' unless defined?(RuboCop::Cop::StatsD)
3
+ require_relative "../rubocop" unless defined?(RuboCop::Cop::StatsD)
4
4
 
5
5
  module RuboCop
6
6
  module Cop
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require_relative '../rubocop' unless defined?(RuboCop::Cop::StatsD)
3
+ require_relative "../rubocop" unless defined?(RuboCop::Cop::StatsD)
4
4
 
5
5
  module RuboCop
6
6
  module Cop
@@ -17,7 +17,7 @@ module RuboCop
17
17
  class MetricReturnValue < Cop
18
18
  include RuboCop::Cop::StatsD
19
19
 
20
- MSG = 'Do not use the return value of StatsD metric methods'
20
+ MSG = "Do not use the return value of StatsD metric methods"
21
21
 
22
22
  INVALID_PARENTS = %i{lvasgn array pair send return yield}
23
23
 
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require_relative '../rubocop' unless defined?(RuboCop::Cop::StatsD)
3
+ require_relative "../rubocop" unless defined?(RuboCop::Cop::StatsD)
4
4
 
5
5
  module RuboCop
6
6
  module Cop
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require_relative '../rubocop' unless defined?(RuboCop::Cop::StatsD)
3
+ require_relative "../rubocop" unless defined?(RuboCop::Cop::StatsD)
4
4
 
5
5
  module RuboCop
6
6
  module Cop
@@ -17,7 +17,7 @@ module RuboCop
17
17
  class PositionalArguments < Cop
18
18
  include RuboCop::Cop::StatsD
19
19
 
20
- MSG = 'Use keyword arguments for StatsD calls'
20
+ MSG = "Use keyword arguments for StatsD calls"
21
21
 
22
22
  POSITIONAL_ARGUMENT_TYPES = Set[:int, :float, :nil]
23
23
  UNKNOWN_ARGUMENT_TYPES = Set[:send, :const, :lvar, :splat]
@@ -80,7 +80,7 @@ module RuboCop
80
80
 
81
81
  tags = positional_arguments[1]
82
82
  if tags && tags.type != :nil
83
- keyword_arguments << if tags.type == :hash && tags.source[0] != '{'
83
+ keyword_arguments << if tags.type == :hash && tags.source[0] != "{"
84
84
  "tags: { #{tags.source} }"
85
85
  else
86
86
  "tags: #{tags.source}"
@@ -88,7 +88,7 @@ module RuboCop
88
88
  end
89
89
 
90
90
  unless keyword_arguments.empty?
91
- corrector.insert_after(value_argument.source_range, ", #{keyword_arguments.join(', ')}")
91
+ corrector.insert_after(value_argument.source_range, ", #{keyword_arguments.join(", ")}")
92
92
  end
93
93
  end
94
94
  end
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require_relative '../rubocop' unless defined?(RuboCop::Cop::StatsD)
3
+ require_relative "../rubocop" unless defined?(RuboCop::Cop::StatsD)
4
4
 
5
5
  module RuboCop
6
6
  module Cop
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require_relative '../rubocop' unless defined?(RuboCop::Cop::StatsD)
3
+ require_relative "../rubocop" unless defined?(RuboCop::Cop::StatsD)
4
4
 
5
5
  module RuboCop
6
6
  module Cop
@@ -16,7 +16,7 @@ module RuboCop
16
16
  class SplatArguments < Cop
17
17
  include RuboCop::Cop::StatsD
18
18
 
19
- MSG = 'Do not use splat arguments in StatsD metric calls'
19
+ MSG = "Do not use splat arguments in StatsD metric calls"
20
20
 
21
21
  def on_send(node)
22
22
  if metric_method?(node)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'statsd-instrument' unless Object.const_defined?(:StatsD)
3
+ require "statsd-instrument" unless Object.const_defined?(:StatsD)
4
4
 
5
5
  module StatsD
6
6
  module Instrument
@@ -6,7 +6,7 @@ module StatsD
6
6
  # to become the new default in the next major release of this library.
7
7
  class UDPSink
8
8
  def self.for_addr(addr)
9
- host, port_as_string = addr.split(':', 2)
9
+ host, port_as_string = addr.split(":", 2)
10
10
  new(host, Integer(port_as_string))
11
11
  end
12
12
 
@@ -24,7 +24,7 @@ module StatsD
24
24
  end
25
25
 
26
26
  def <<(datagram)
27
- with_socket { |socket| socket.send(datagram, 0) > 0 }
27
+ with_socket { |socket| socket.send(datagram, 0) }
28
28
  self
29
29
 
30
30
  rescue ThreadError
@@ -33,15 +33,13 @@ module StatsD
33
33
  # to try and send the datagram without a lock.
34
34
  socket.send(datagram, 0) > 0
35
35
 
36
- rescue SocketError, IOError, SystemCallError
37
- # TODO: log?
36
+ rescue SocketError, IOError, SystemCallError => error
37
+ StatsD.logger.debug do
38
+ "[StatsD::Instrument::UDPSink] Resetting connection because of #{error.class}: #{error.message}"
39
+ end
38
40
  invalidate_socket
39
41
  end
40
42
 
41
- def addr
42
- "#{host}:#{port}"
43
- end
44
-
45
43
  private
46
44
 
47
45
  def with_socket
@@ -49,11 +47,11 @@ module StatsD
49
47
  end
50
48
 
51
49
  def socket
52
- if @socket.nil?
53
- @socket = UDPSocket.new
54
- @socket.connect(@host, @port)
50
+ @socket ||= begin
51
+ socket = UDPSocket.new
52
+ socket.connect(@host, @port)
53
+ socket
55
54
  end
56
- @socket
57
55
  end
58
56
 
59
57
  def invalidate_socket