statsd-instrument 3.0.0.pre1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/lint.yml +22 -0
  3. data/.github/workflows/tests.yml +31 -0
  4. data/.rubocop.yml +3 -13
  5. data/CHANGELOG.md +50 -0
  6. data/Gemfile +8 -2
  7. data/README.md +6 -3
  8. data/Rakefile +7 -7
  9. data/benchmark/send-metrics-to-dev-null-log +12 -11
  10. data/benchmark/send-metrics-to-local-udp-receiver +16 -15
  11. data/bin/rake +29 -0
  12. data/bin/rubocop +29 -0
  13. data/lib/statsd-instrument.rb +1 -1
  14. data/lib/statsd/instrument.rb +112 -145
  15. data/lib/statsd/instrument/assertions.rb +200 -208
  16. data/lib/statsd/instrument/batched_udp_sink.rb +154 -0
  17. data/lib/statsd/instrument/capture_sink.rb +23 -19
  18. data/lib/statsd/instrument/client.rb +410 -306
  19. data/lib/statsd/instrument/datagram.rb +69 -65
  20. data/lib/statsd/instrument/datagram_builder.rb +81 -77
  21. data/lib/statsd/instrument/dogstatsd_datagram.rb +76 -72
  22. data/lib/statsd/instrument/dogstatsd_datagram_builder.rb +68 -64
  23. data/lib/statsd/instrument/environment.rb +88 -77
  24. data/lib/statsd/instrument/expectation.rb +96 -96
  25. data/lib/statsd/instrument/helpers.rb +11 -7
  26. data/lib/statsd/instrument/log_sink.rb +20 -16
  27. data/lib/statsd/instrument/matchers.rb +93 -74
  28. data/lib/statsd/instrument/null_sink.rb +12 -8
  29. data/lib/statsd/instrument/railtie.rb +11 -7
  30. data/lib/statsd/instrument/rubocop.rb +8 -8
  31. data/lib/statsd/instrument/rubocop/measure_as_dist_argument.rb +1 -1
  32. data/lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb +2 -2
  33. data/lib/statsd/instrument/rubocop/metric_prefix_argument.rb +1 -1
  34. data/lib/statsd/instrument/rubocop/metric_return_value.rb +2 -2
  35. data/lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb +1 -1
  36. data/lib/statsd/instrument/rubocop/positional_arguments.rb +4 -4
  37. data/lib/statsd/instrument/rubocop/singleton_configuration.rb +1 -1
  38. data/lib/statsd/instrument/rubocop/splat_arguments.rb +2 -2
  39. data/lib/statsd/instrument/statsd_datagram_builder.rb +12 -8
  40. data/lib/statsd/instrument/strict.rb +1 -6
  41. data/lib/statsd/instrument/udp_sink.rb +49 -47
  42. data/lib/statsd/instrument/version.rb +1 -1
  43. data/statsd-instrument.gemspec +4 -8
  44. data/test/assertions_test.rb +205 -161
  45. data/test/benchmark/clock_gettime.rb +1 -1
  46. data/test/benchmark/default_tags.rb +9 -9
  47. data/test/benchmark/metrics.rb +8 -8
  48. data/test/benchmark/tags.rb +4 -4
  49. data/test/capture_sink_test.rb +14 -14
  50. data/test/client_test.rb +96 -96
  51. data/test/datagram_builder_test.rb +55 -55
  52. data/test/datagram_test.rb +5 -5
  53. data/test/dogstatsd_datagram_builder_test.rb +37 -37
  54. data/test/environment_test.rb +30 -21
  55. data/test/helpers/rubocop_helper.rb +12 -9
  56. data/test/helpers_test.rb +15 -15
  57. data/test/integration_test.rb +7 -7
  58. data/test/log_sink_test.rb +4 -4
  59. data/test/matchers_test.rb +54 -54
  60. data/test/null_sink_test.rb +4 -4
  61. data/test/rubocop/measure_as_dist_argument_test.rb +2 -2
  62. data/test/rubocop/metaprogramming_positional_arguments_test.rb +2 -2
  63. data/test/rubocop/metric_prefix_argument_test.rb +2 -2
  64. data/test/rubocop/metric_return_value_test.rb +6 -6
  65. data/test/rubocop/metric_value_keyword_argument_test.rb +3 -3
  66. data/test/rubocop/positional_arguments_test.rb +12 -12
  67. data/test/rubocop/singleton_configuration_test.rb +8 -8
  68. data/test/rubocop/splat_arguments_test.rb +2 -2
  69. data/test/statsd_datagram_builder_test.rb +6 -6
  70. data/test/statsd_instrumentation_test.rb +122 -122
  71. data/test/statsd_test.rb +69 -67
  72. data/test/test_helper.rb +19 -10
  73. data/test/udp_sink_test.rb +122 -50
  74. metadata +12 -92
  75. data/.github/workflows/ci.yml +0 -56
  76. data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +0 -1027
@@ -1,83 +1,87 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # The Datagram class parses and inspects a StatsD datagrams
4
- #
5
- # @note This class is part of the new Client implementation that is intended
6
- # to become the new default in the next major release of this library.
7
- class StatsD::Instrument::Datagram
8
- attr_reader :source
3
+ module StatsD
4
+ module Instrument
5
+ # The Datagram class parses and inspects a StatsD datagrams
6
+ #
7
+ # @note This class is part of the new Client implementation that is intended
8
+ # to become the new default in the next major release of this library.
9
+ class Datagram
10
+ attr_reader :source
9
11
 
10
- def initialize(source)
11
- @source = source
12
- end
12
+ def initialize(source)
13
+ @source = source
14
+ end
13
15
 
14
- # @return [Float] The sample rate at which this datagram was emitted, between 0 and 1.
15
- def sample_rate
16
- parsed_datagram[:sample_rate] ? Float(parsed_datagram[:sample_rate]) : 1.0
17
- end
16
+ # @return [Float] The sample rate at which this datagram was emitted, between 0 and 1.
17
+ def sample_rate
18
+ parsed_datagram[:sample_rate] ? Float(parsed_datagram[:sample_rate]) : 1.0
19
+ end
18
20
 
19
- def type
20
- @type ||= parsed_datagram[:type].to_sym
21
- end
21
+ def type
22
+ @type ||= parsed_datagram[:type].to_sym
23
+ end
22
24
 
23
- def name
24
- parsed_datagram[:name]
25
- end
25
+ def name
26
+ parsed_datagram[:name]
27
+ end
26
28
 
27
- def value
28
- @value ||= case type
29
- when :c
30
- Integer(parsed_datagram[:value])
31
- when :g, :h, :d, :kv, :ms
32
- Float(parsed_datagram[:value])
33
- when :s
34
- String(parsed_datagram[:value])
35
- else
36
- parsed_datagram[:value]
37
- end
38
- end
29
+ def value
30
+ @value ||= case type
31
+ when :c
32
+ Integer(parsed_datagram[:value])
33
+ when :g, :h, :d, :kv, :ms
34
+ Float(parsed_datagram[:value])
35
+ when :s
36
+ String(parsed_datagram[:value])
37
+ else
38
+ parsed_datagram[:value]
39
+ end
40
+ end
39
41
 
40
- def tags
41
- @tags ||= parsed_datagram[:tags] ? parsed_datagram[:tags].split(',') : nil
42
- end
42
+ def tags
43
+ @tags ||= parsed_datagram[:tags] ? parsed_datagram[:tags].split(",") : nil
44
+ end
43
45
 
44
- def inspect
45
- "#<#{self.class.name}:\"#{@source}\">"
46
- end
46
+ def inspect
47
+ "#<#{self.class.name}:\"#{@source}\">"
48
+ end
47
49
 
48
- def hash
49
- source.hash
50
- end
50
+ def hash
51
+ source.hash
52
+ end
51
53
 
52
- def eql?(other)
53
- case other
54
- when StatsD::Instrument::Datagram
55
- source == other.source
56
- when String
57
- source == other
58
- else
59
- false
60
- end
61
- end
54
+ def eql?(other)
55
+ case other
56
+ when StatsD::Instrument::Datagram
57
+ source == other.source
58
+ when String
59
+ source == other
60
+ else
61
+ false
62
+ end
63
+ end
62
64
 
63
- alias_method :==, :eql?
65
+ alias_method :==, :eql?
64
66
 
65
- private
67
+ private
66
68
 
67
- PARSER = %r{
68
- \A
69
- (?<name>[^\:\|\@]+)\:(?<value>[^\:\|\@]+)\|(?<type>c|ms|g|s|h|d)
70
- (?:\|\@(?<sample_rate>\d*(?:\.\d*)?))?
71
- (?:\|\#(?<tags>(?:[^\|,]+(?:,[^\|,]+)*)))?
72
- \n? # In some implementations, the datagram may include a trailing newline.
73
- \z
74
- }x
69
+ PARSER = %r{
70
+ \A
71
+ (?<name>[^\:\|\@]+)\:(?<value>[^\:\|\@]+)\|(?<type>c|ms|g|s|h|d)
72
+ (?:\|\@(?<sample_rate>\d*(?:\.\d*)?))?
73
+ (?:\|\#(?<tags>(?:[^\|,]+(?:,[^\|,]+)*)))?
74
+ \n? # In some implementations, the datagram may include a trailing newline.
75
+ \z
76
+ }x
75
77
 
76
- def parsed_datagram
77
- @parsed ||= if (match_info = PARSER.match(@source))
78
- match_info
79
- else
80
- raise ArgumentError, "Invalid StatsD datagram: #{@source}"
78
+ def parsed_datagram
79
+ @parsed ||= if (match_info = PARSER.match(@source))
80
+ match_info
81
+ else
82
+ raise ArgumentError, "Invalid StatsD datagram: #{@source}"
83
+ end
84
+ end
81
85
  end
82
86
  end
83
87
  end
@@ -1,101 +1,105 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # @note This class is part of the new Client implementation that is intended
4
- # to become the new default in the next major release of this library.
5
- class StatsD::Instrument::DatagramBuilder
6
- unless Regexp.method_defined?(:match?) # for ruby 2.3
7
- module RubyBackports
8
- refine Regexp do
9
- def match?(str)
10
- match(str) != nil
3
+ module StatsD
4
+ module Instrument
5
+ # @note This class is part of the new Client implementation that is intended
6
+ # to become the new default in the next major release of this library.
7
+ class DatagramBuilder
8
+ unless Regexp.method_defined?(:match?) # for ruby 2.3
9
+ module RubyBackports
10
+ refine Regexp do
11
+ def match?(str)
12
+ match(str) != nil
13
+ end
14
+ end
11
15
  end
12
- end
13
- end
14
16
 
15
- using RubyBackports
16
- end
17
+ using(RubyBackports)
18
+ end
17
19
 
18
- def self.unsupported_datagram_types(*types)
19
- types.each do |type|
20
- define_method(type) do |_, _, _, _|
21
- raise NotImplementedError, "Type #{type} metrics are not supported by #{self.class.name}."
20
+ def self.unsupported_datagram_types(*types)
21
+ types.each do |type|
22
+ define_method(type) do |_, _, _, _|
23
+ raise NotImplementedError, "Type #{type} metrics are not supported by #{self.class.name}."
24
+ end
25
+ end
22
26
  end
23
- end
24
- end
25
27
 
26
- def self.datagram_class
27
- StatsD::Instrument::Datagram
28
- end
28
+ def self.datagram_class
29
+ StatsD::Instrument::Datagram
30
+ end
29
31
 
30
- def initialize(prefix: nil, default_tags: nil)
31
- @prefix = prefix.nil? ? "" : "#{normalize_name(prefix)}."
32
- @default_tags = normalize_tags(default_tags)
33
- end
32
+ def initialize(prefix: nil, default_tags: nil)
33
+ @prefix = prefix.nil? ? "" : "#{normalize_name(prefix)}."
34
+ @default_tags = normalize_tags(default_tags)
35
+ end
34
36
 
35
- def c(name, value, sample_rate, tags)
36
- generate_generic_datagram(name, value, 'c', sample_rate, tags)
37
- end
37
+ def c(name, value, sample_rate, tags)
38
+ generate_generic_datagram(name, value, "c", sample_rate, tags)
39
+ end
38
40
 
39
- def g(name, value, sample_rate, tags)
40
- generate_generic_datagram(name, value, 'g', sample_rate, tags)
41
- end
41
+ def g(name, value, sample_rate, tags)
42
+ generate_generic_datagram(name, value, "g", sample_rate, tags)
43
+ end
42
44
 
43
- def ms(name, value, sample_rate, tags)
44
- generate_generic_datagram(name, value, 'ms', sample_rate, tags)
45
- end
45
+ def ms(name, value, sample_rate, tags)
46
+ generate_generic_datagram(name, value, "ms", sample_rate, tags)
47
+ end
46
48
 
47
- def s(name, value, sample_rate, tags)
48
- generate_generic_datagram(name, value, 's', sample_rate, tags)
49
- end
49
+ def s(name, value, sample_rate, tags)
50
+ generate_generic_datagram(name, value, "s", sample_rate, tags)
51
+ end
50
52
 
51
- def h(name, value, sample_rate, tags)
52
- generate_generic_datagram(name, value, 'h', sample_rate, tags)
53
- end
53
+ def h(name, value, sample_rate, tags)
54
+ generate_generic_datagram(name, value, "h", sample_rate, tags)
55
+ end
54
56
 
55
- def d(name, value, sample_rate, tags)
56
- generate_generic_datagram(name, value, 'd', sample_rate, tags)
57
- end
57
+ def d(name, value, sample_rate, tags)
58
+ generate_generic_datagram(name, value, "d", sample_rate, tags)
59
+ end
58
60
 
59
- def kv(name, value, sample_rate, tags)
60
- generate_generic_datagram(name, value, 'kv', sample_rate, tags)
61
- end
61
+ def kv(name, value, sample_rate, tags)
62
+ generate_generic_datagram(name, value, "kv", sample_rate, tags)
63
+ end
62
64
 
63
- def latency_metric_type
64
- :ms
65
- end
65
+ def latency_metric_type
66
+ :ms
67
+ end
66
68
 
67
- protected
69
+ protected
68
70
 
69
- attr_reader :prefix, :default_tags
71
+ attr_reader :prefix, :default_tags
70
72
 
71
- # Utility function to convert tags to the canonical form.
72
- #
73
- # - Tags specified as key value pairs will be converted into an array
74
- # - Tags are normalized to remove unsupported characters
75
- #
76
- # @param tags [Array<String>, Hash<String, String>, nil] Tags specified in any form.
77
- # @return [Array<String>, nil] the list of tags in canonical form.
78
- def normalize_tags(tags)
79
- return [] unless tags
80
- tags = tags.map { |k, v| "#{k}:#{v}" } if tags.is_a?(Hash)
73
+ # Utility function to convert tags to the canonical form.
74
+ #
75
+ # - Tags specified as key value pairs will be converted into an array
76
+ # - Tags are normalized to remove unsupported characters
77
+ #
78
+ # @param tags [Array<String>, Hash<String, String>, nil] Tags specified in any form.
79
+ # @return [Array<String>, nil] the list of tags in canonical form.
80
+ def normalize_tags(tags)
81
+ return [] unless tags
82
+ tags = tags.map { |k, v| "#{k}:#{v}" } if tags.is_a?(Hash)
81
83
 
82
- # Fast path when no string replacement is needed
83
- return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
84
- tags.map { |tag| tag.tr('|,', '') }
85
- end
84
+ # Fast path when no string replacement is needed
85
+ return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
86
+ tags.map { |tag| tag.tr("|,", "") }
87
+ end
86
88
 
87
- # Utility function to remove invalid characters from a StatsD metric name
88
- def normalize_name(name)
89
- # Fast path when no normalization is needed to avoid copying the string
90
- return name unless /[:|@]/.match?(name)
91
- name.tr(':|@', '_')
92
- end
89
+ # Utility function to remove invalid characters from a StatsD metric name
90
+ def normalize_name(name)
91
+ # Fast path when no normalization is needed to avoid copying the string
92
+ return name unless /[:|@]/.match?(name)
93
+ name.tr(":|@", "_")
94
+ end
93
95
 
94
- def generate_generic_datagram(name, value, type, sample_rate, tags)
95
- tags = normalize_tags(tags) + default_tags
96
- datagram = +"#{@prefix}#{normalize_name(name)}:#{value}|#{type}"
97
- datagram << "|@#{sample_rate}" if sample_rate && sample_rate < 1
98
- datagram << "|##{tags.join(',')}" unless tags.empty?
99
- datagram
96
+ def generate_generic_datagram(name, value, type, sample_rate, tags)
97
+ tags = normalize_tags(tags) + default_tags
98
+ datagram = +"#{@prefix}#{normalize_name(name)}:#{value}|#{type}"
99
+ datagram << "|@#{sample_rate}" if sample_rate && sample_rate < 1
100
+ datagram << "|##{tags.join(",")}" unless tags.empty?
101
+ datagram
102
+ end
103
+ end
100
104
  end
101
105
  end
@@ -1,88 +1,92 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # The Datagram class parses and inspects a StatsD datagrams
4
- #
5
- # @note This class is part of the new Client implementation that is intended
6
- # to become the new default in the next major release of this library.
7
- class StatsD::Instrument::DogStatsDDatagram < StatsD::Instrument::Datagram
8
- def name
9
- @name ||= case type
10
- when :_e then parsed_datagram[:name].gsub('\n', "\n")
11
- else super
12
- end
13
- end
3
+ module StatsD
4
+ module Instrument
5
+ # The Datagram class parses and inspects a StatsD datagrams
6
+ #
7
+ # @note This class is part of the new Client implementation that is intended
8
+ # to become the new default in the next major release of this library.
9
+ class DogStatsDDatagram < StatsD::Instrument::Datagram
10
+ def name
11
+ @name ||= case type
12
+ when :_e then parsed_datagram[:name].gsub('\n', "\n")
13
+ else super
14
+ end
15
+ end
14
16
 
15
- def value
16
- @value ||= case type
17
- when :_sc then Integer(parsed_datagram[:value])
18
- when :_e then parsed_datagram[:value].gsub('\n', "\n")
19
- else super
20
- end
21
- end
17
+ def value
18
+ @value ||= case type
19
+ when :_sc then Integer(parsed_datagram[:value])
20
+ when :_e then parsed_datagram[:value].gsub('\n', "\n")
21
+ else super
22
+ end
23
+ end
22
24
 
23
- def hostname
24
- parsed_datagram[:hostname]
25
- end
25
+ def hostname
26
+ parsed_datagram[:hostname]
27
+ end
26
28
 
27
- def timestamp
28
- Time.at(Integer(parsed_datagram[:timestamp])).utc
29
- end
29
+ def timestamp
30
+ Time.at(Integer(parsed_datagram[:timestamp])).utc
31
+ end
30
32
 
31
- def aggregation_key
32
- parsed_datagram[:aggregation_key]
33
- end
33
+ def aggregation_key
34
+ parsed_datagram[:aggregation_key]
35
+ end
34
36
 
35
- def source_type_name
36
- parsed_datagram[:source_type_name]
37
- end
37
+ def source_type_name
38
+ parsed_datagram[:source_type_name]
39
+ end
38
40
 
39
- def priority
40
- parsed_datagram[:priority]
41
- end
41
+ def priority
42
+ parsed_datagram[:priority]
43
+ end
42
44
 
43
- def alert_type
44
- parsed_datagram[:alert_type]
45
- end
45
+ def alert_type
46
+ parsed_datagram[:alert_type]
47
+ end
46
48
 
47
- def message
48
- parsed_datagram[:message]
49
- end
49
+ def message
50
+ parsed_datagram[:message]
51
+ end
50
52
 
51
- protected
53
+ protected
52
54
 
53
- def parsed_datagram
54
- @parsed ||= if (match_info = PARSER.match(@source))
55
- match_info
56
- else
57
- raise ArgumentError, "Invalid DogStatsD datagram: #{@source}"
58
- end
59
- end
55
+ def parsed_datagram
56
+ @parsed ||= if (match_info = PARSER.match(@source))
57
+ match_info
58
+ else
59
+ raise ArgumentError, "Invalid DogStatsD datagram: #{@source}"
60
+ end
61
+ end
60
62
 
61
- SERVICE_CHECK_PARSER = %r{
62
- \A
63
- (?<type>_sc)\|(?<name>[^\|]+)\|(?<value>\d+)
64
- (?:\|h:(?<hostname>[^\|]+))?
65
- (?:\|d:(?<timestamp>\d+))?
66
- (?:\|\#(?<tags>(?:[^\|,]+(?:,[^\|,]+)*)))?
67
- (?:\|m:(?<message>[^\|]+))?
68
- \n? # In some implementations, the datagram may include a trailing newline.
69
- \z
70
- }x
63
+ SERVICE_CHECK_PARSER = %r{
64
+ \A
65
+ (?<type>_sc)\|(?<name>[^\|]+)\|(?<value>\d+)
66
+ (?:\|h:(?<hostname>[^\|]+))?
67
+ (?:\|d:(?<timestamp>\d+))?
68
+ (?:\|\#(?<tags>(?:[^\|,]+(?:,[^\|,]+)*)))?
69
+ (?:\|m:(?<message>[^\|]+))?
70
+ \n? # In some implementations, the datagram may include a trailing newline.
71
+ \z
72
+ }x
71
73
 
72
- # |k:my-key|p:low|s:source|t:success|
73
- EVENT_PARSER = %r{
74
- \A
75
- (?<type>_e)\{\d+\,\d+\}:(?<name>[^\|]+)\|(?<value>[^\|]+)
76
- (?:\|h:(?<hostname>[^\|]+))?
77
- (?:\|d:(?<timestamp>\d+))?
78
- (?:\|k:(?<aggregation_key>[^\|]+))?
79
- (?:\|p:(?<priority>[^\|]+))?
80
- (?:\|s:(?<source_type_name>[^\|]+))?
81
- (?:\|t:(?<alert_type>[^\|]+))?
82
- (?:\|\#(?<tags>(?:[^\|,]+(?:,[^\|,]+)*)))?
83
- \n? # In some implementations, the datagram may include a trailing newline.
84
- \z
85
- }x
74
+ # |k:my-key|p:low|s:source|t:success|
75
+ EVENT_PARSER = %r{
76
+ \A
77
+ (?<type>_e)\{\d+\,\d+\}:(?<name>[^\|]+)\|(?<value>[^\|]+)
78
+ (?:\|h:(?<hostname>[^\|]+))?
79
+ (?:\|d:(?<timestamp>\d+))?
80
+ (?:\|k:(?<aggregation_key>[^\|]+))?
81
+ (?:\|p:(?<priority>[^\|]+))?
82
+ (?:\|s:(?<source_type_name>[^\|]+))?
83
+ (?:\|t:(?<alert_type>[^\|]+))?
84
+ (?:\|\#(?<tags>(?:[^\|,]+(?:,[^\|,]+)*)))?
85
+ \n? # In some implementations, the datagram may include a trailing newline.
86
+ \z
87
+ }x
86
88
 
87
- PARSER = Regexp.union(StatsD::Instrument::Datagram::PARSER, SERVICE_CHECK_PARSER, EVENT_PARSER)
89
+ PARSER = Regexp.union(StatsD::Instrument::Datagram::PARSER, SERVICE_CHECK_PARSER, EVENT_PARSER)
90
+ end
91
+ end
88
92
  end