From 3ba96e555b4e3088fb63bbd3897d0c42e069082f Mon Sep 17 00:00:00 2001 From: Willem van Bergen Date: Wed, 23 Oct 2019 05:27:31 -0400 Subject: [PATCH] Add support for parsing service check and event datagrams. --- lib/statsd/instrument/client.rb | 1 + lib/statsd/instrument/datagram.rb | 3 +- lib/statsd/instrument/dogstatsd_datagram.rb | 88 +++++++++++++++++++ .../instrument/dogstatsd_datagram_builder.rb | 4 + test/dogstatsd_datagram_builder_test.rb | 45 +++++++++- 5 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 lib/statsd/instrument/dogstatsd_datagram.rb diff --git a/lib/statsd/instrument/client.rb b/lib/statsd/instrument/client.rb index 6ab17a37..b547a861 100644 --- a/lib/statsd/instrument/client.rb +++ b/lib/statsd/instrument/client.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'statsd/instrument/datagram' +require 'statsd/instrument/dogstatsd_datagram' require 'statsd/instrument/datagram_builder' require 'statsd/instrument/statsd_datagram_builder' require 'statsd/instrument/dogstatsd_datagram_builder' diff --git a/lib/statsd/instrument/datagram.rb b/lib/statsd/instrument/datagram.rb index 88de80f7..cbfcce2e 100644 --- a/lib/statsd/instrument/datagram.rb +++ b/lib/statsd/instrument/datagram.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# The Datagram class parses and inspects a StatsD datagrans +# The Datagram class parses and inspects a StatsD datagrams # # @note This class is part of the new Client implementation that is intended # to become the new default in the next major release of this library. @@ -72,7 +72,6 @@ def eql?(other) \n? # In some implementations, the datagram may include a trailing newline. \z }x - private_constant :PARSER def parsed_datagram @parsed ||= if (match_info = PARSER.match(@source)) diff --git a/lib/statsd/instrument/dogstatsd_datagram.rb b/lib/statsd/instrument/dogstatsd_datagram.rb new file mode 100644 index 00000000..172cd2ec --- /dev/null +++ b/lib/statsd/instrument/dogstatsd_datagram.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +# The Datagram class parses and inspects a StatsD datagrams +# +# @note This class is part of the new Client implementation that is intended +# to become the new default in the next major release of this library. +class StatsD::Instrument::DogStatsDDatagram < StatsD::Instrument::Datagram + def name + @name ||= case type + when :_e then parsed_datagram[:name].gsub('\n', "\n") + else super + end + end + + def value + @value ||= case type + when :_sc then Integer(parsed_datagram[:value]) + when :_e then parsed_datagram[:value].gsub('\n', "\n") + else super + end + end + + def hostname + parsed_datagram[:hostname] + end + + def timestamp + Time.at(Integer(parsed_datagram[:timestamp])).utc + end + + def aggregation_key + parsed_datagram[:aggregation_key] + end + + def source_type_name + parsed_datagram[:source_type_name] + end + + def priority + parsed_datagram[:priority] + end + + def alert_type + parsed_datagram[:alert_type] + end + + def message + parsed_datagram[:message] + end + + protected + + def parsed_datagram + @parsed ||= if (match_info = PARSER.match(@source)) + match_info + else + raise ArgumentError, "Invalid DogStatsD datagram: #{@source}" + end + end + + SERVICE_CHECK_PARSER = %r{ + \A + (?_sc)\|(?[^\|]+)\|(?\d+) + (?:\|h:(?[^\|]+))? + (?:\|d:(?\d+))? + (?:\|\#(?(?:[^\|,]+(?:,[^\|,]+)*)))? + (?:\|m:(?[^\|]+))? + \n? # In some implementations, the datagram may include a trailing newline. + \z + }x + + # |k:my-key|p:low|s:source|t:success| + EVENT_PARSER = %r{ + \A + (?_e)\{\d+\,\d+\}:(?[^\|]+)\|(?[^\|]+) + (?:\|h:(?[^\|]+))? + (?:\|d:(?\d+))? + (?:\|k:(?[^\|]+))? + (?:\|p:(?[^\|]+))? + (?:\|s:(?[^\|]+))? + (?:\|t:(?[^\|]+))? + (?:\|\#(?(?:[^\|,]+(?:,[^\|,]+)*)))? + \n? # In some implementations, the datagram may include a trailing newline. + \z + }x + + PARSER = Regexp.union(StatsD::Instrument::Datagram::PARSER, SERVICE_CHECK_PARSER, EVENT_PARSER) +end diff --git a/lib/statsd/instrument/dogstatsd_datagram_builder.rb b/lib/statsd/instrument/dogstatsd_datagram_builder.rb index a7cbf49c..c09abda3 100644 --- a/lib/statsd/instrument/dogstatsd_datagram_builder.rb +++ b/lib/statsd/instrument/dogstatsd_datagram_builder.rb @@ -5,6 +5,10 @@ class StatsD::Instrument::DogStatsDDatagramBuilder < StatsD::Instrument::DatagramBuilder unsupported_datagram_types :kv + def self.datagram_class + StatsD::Instrument::DogStatsDDatagram + end + def latency_metric_type :d end diff --git a/test/dogstatsd_datagram_builder_test.rb b/test/dogstatsd_datagram_builder_test.rb index e05b157e..23f3314d 100644 --- a/test/dogstatsd_datagram_builder_test.rb +++ b/test/dogstatsd_datagram_builder_test.rb @@ -11,20 +11,57 @@ def test_raises_on_unsupported_metrics assert_raises(NotImplementedError) { @datagram_builder.kv('foo', 10, nil, nil) } end - def test_service_check - assert_equal '_sc|service|0', @datagram_builder._sc('service', :ok) + def test_simple_service_check + datagram = @datagram_builder._sc('service', :ok) + assert_equal '_sc|service|0', datagram + parsed_datagram = StatsD::Instrument::DogStatsDDatagramBuilder.datagram_class.new(datagram) + assert_equal :_sc, parsed_datagram.type + assert_equal 'service', parsed_datagram.name + assert_equal 0, parsed_datagram.value + end + + def test_complex_service_check datagram = @datagram_builder._sc('service', :warning, timestamp: Time.parse('2019-09-30T04:22:12Z'), hostname: 'localhost', tags: { foo: 'bar|baz' }, message: 'blah') assert_equal "_sc|service|1|h:localhost|d:1569817332|#foo:barbaz|m:blah", datagram + + parsed_datagram = StatsD::Instrument::DogStatsDDatagramBuilder.datagram_class.new(datagram) + assert_equal :_sc, parsed_datagram.type + assert_equal 'service', parsed_datagram.name + assert_equal 1, parsed_datagram.value + assert_equal 'localhost', parsed_datagram.hostname + assert_equal Time.parse('2019-09-30T04:22:12Z'), parsed_datagram.timestamp + assert_equal ["foo:barbaz"], parsed_datagram.tags + assert_equal 'blah', parsed_datagram.message end - def test_event - assert_equal '_e{5,5}:hello|world', @datagram_builder._e('hello', "world") + def test_simple_event + datagram = @datagram_builder._e('hello', "world") + assert_equal '_e{5,5}:hello|world', datagram + parsed_datagram = StatsD::Instrument::DogStatsDDatagramBuilder.datagram_class.new(datagram) + assert_equal :_e, parsed_datagram.type + assert_equal 'hello', parsed_datagram.name + assert_equal 'world', parsed_datagram.value + end + + def test_complex_event datagram = @datagram_builder._e("testing", "with\nnewline", timestamp: Time.parse('2019-09-30T04:22:12Z'), hostname: 'localhost', aggregation_key: 'my-key', priority: 'low', source_type_name: 'source', alert_type: 'success', tags: { foo: 'bar|baz' }) assert_equal '_e{7,13}:testing|with\\nnewline|h:localhost|d:1569817332|k:my-key|' \ 'p:low|s:source|t:success|#foo:barbaz', datagram + + parsed_datagram = StatsD::Instrument::DogStatsDDatagramBuilder.datagram_class.new(datagram) + assert_equal :_e, parsed_datagram.type + assert_equal 'testing', parsed_datagram.name + assert_equal "with\nnewline", parsed_datagram.value + assert_equal 'localhost', parsed_datagram.hostname + assert_equal Time.parse('2019-09-30T04:22:12Z'), parsed_datagram.timestamp + assert_equal ["foo:barbaz"], parsed_datagram.tags + assert_equal "my-key", parsed_datagram.aggregation_key + assert_equal "low", parsed_datagram.priority + assert_equal "source", parsed_datagram.source_type_name + assert_equal "success", parsed_datagram.alert_type end end