statsd-instrument 3.0.2 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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