Skip to content

Commit

Permalink
Add support for parsing service check and event datagrams.
Browse files Browse the repository at this point in the history
  • Loading branch information
wvanbergen committed Oct 23, 2019
1 parent 2be2dec commit 3ba96e5
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 6 deletions.
1 change: 1 addition & 0 deletions 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'
Expand Down
3 changes: 1 addition & 2 deletions 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.
Expand Down Expand Up @@ -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))
Expand Down
88 changes: 88 additions & 0 deletions 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
(?<type>_sc)\|(?<name>[^\|]+)\|(?<value>\d+)
(?:\|h:(?<hostname>[^\|]+))?
(?:\|d:(?<timestamp>\d+))?
(?:\|\#(?<tags>(?:[^\|,]+(?:,[^\|,]+)*)))?
(?:\|m:(?<message>[^\|]+))?
\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
(?<type>_e)\{\d+\,\d+\}:(?<name>[^\|]+)\|(?<value>[^\|]+)
(?:\|h:(?<hostname>[^\|]+))?
(?:\|d:(?<timestamp>\d+))?
(?:\|k:(?<aggregation_key>[^\|]+))?
(?:\|p:(?<priority>[^\|]+))?
(?:\|s:(?<source_type_name>[^\|]+))?
(?:\|t:(?<alert_type>[^\|]+))?
(?:\|\#(?<tags>(?:[^\|,]+(?:,[^\|,]+)*)))?
\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
4 changes: 4 additions & 0 deletions lib/statsd/instrument/dogstatsd_datagram_builder.rb
Expand Up @@ -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
Expand Down
45 changes: 41 additions & 4 deletions test/dogstatsd_datagram_builder_test.rb
Expand Up @@ -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

0 comments on commit 3ba96e5

Please sign in to comment.