dogstatsd-ruby 4.7.0 → 5.0.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.
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require 'forwardable'
4
+
5
+ module Datadog
6
+ class Statsd
7
+ module Serialization
8
+ class Serializer
9
+ def initialize(prefix: nil, global_tags: [])
10
+ @stat_serializer = StatSerializer.new(prefix, global_tags: global_tags)
11
+ @service_check_serializer = ServiceCheckSerializer.new(global_tags: global_tags)
12
+ @event_serializer = EventSerializer.new(global_tags: global_tags)
13
+ end
14
+
15
+ # using *args would make new allocations
16
+ def to_stat(name, delta, type, tags: [], sample_rate: 1)
17
+ stat_serializer.format(name, delta, type, tags: tags, sample_rate: sample_rate)
18
+ end
19
+
20
+ # using *args would make new allocations
21
+ def to_service_check(name, status, options = EMPTY_OPTIONS)
22
+ service_check_serializer.format(name, status, options)
23
+ end
24
+
25
+ # using *args would make new allocations
26
+ def to_event(title, text, options = EMPTY_OPTIONS)
27
+ event_serializer.format(title, text, options)
28
+ end
29
+
30
+ def global_tags
31
+ stat_serializer.global_tags
32
+ end
33
+
34
+ protected
35
+ attr_reader :stat_serializer
36
+ attr_reader :service_check_serializer
37
+ attr_reader :event_serializer
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class ServiceCheckSerializer
7
+ SERVICE_CHECK_BASIC_OPTIONS = {
8
+ timestamp: 'd:',
9
+ hostname: 'h:',
10
+ }.freeze
11
+
12
+ def initialize(global_tags: [])
13
+ @tag_serializer = TagSerializer.new(global_tags)
14
+ end
15
+
16
+ def format(name, status, options = EMPTY_OPTIONS)
17
+ String.new.tap do |service_check|
18
+ # line basics
19
+ service_check << "_sc"
20
+ service_check << "|"
21
+ service_check << name.to_s
22
+ service_check << "|"
23
+ service_check << status.to_s
24
+
25
+ # we are serializing the generic service check options
26
+ # before serializing specialized options that need edge-cases
27
+ SERVICE_CHECK_BASIC_OPTIONS.each do |option_key, shortcut|
28
+ if value = options[option_key]
29
+ service_check << '|'
30
+ service_check << shortcut
31
+ service_check << value.to_s.delete('|')
32
+ end
33
+ end
34
+
35
+ if message = options[:message]
36
+ service_check << '|m:'
37
+ service_check << escape_message(message)
38
+ end
39
+
40
+ # also returns the global tags from serializer
41
+ if tags = tag_serializer.format(options[:tags])
42
+ service_check << '|#'
43
+ service_check << tags
44
+ end
45
+ end
46
+ end
47
+
48
+ protected
49
+ attr_reader :tag_serializer
50
+
51
+ def escape_message(message)
52
+ message.delete('|').tap do |m|
53
+ m.gsub!("\n", '\n')
54
+ m.gsub!('m:', 'm\:')
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class StatSerializer
7
+ def initialize(prefix, global_tags: [])
8
+ @prefix = prefix
9
+ @prefix_str = prefix.to_s
10
+ @tag_serializer = TagSerializer.new(global_tags)
11
+ end
12
+
13
+ def format(name, delta, type, tags: [], sample_rate: 1)
14
+ name = formated_name(name)
15
+
16
+ if sample_rate != 1
17
+ if tags_list = tag_serializer.format(tags)
18
+ "#{@prefix_str}#{name}:#{delta}|#{type}|@#{sample_rate}|##{tags_list}"
19
+ else
20
+ "#{@prefix_str}#{name}:#{delta}|#{type}|@#{sample_rate}"
21
+ end
22
+ else
23
+ if tags_list = tag_serializer.format(tags)
24
+ "#{@prefix_str}#{name}:#{delta}|#{type}|##{tags_list}"
25
+ else
26
+ "#{@prefix_str}#{name}:#{delta}|#{type}"
27
+ end
28
+ end
29
+ end
30
+
31
+ def global_tags
32
+ tag_serializer.global_tags
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :prefix
38
+ attr_reader :tag_serializer
39
+
40
+ def formated_name(name)
41
+ if name.is_a?(String)
42
+ # DEV: gsub is faster than dup.gsub!
43
+ formated = name.gsub('::', '.')
44
+ else
45
+ formated = name.to_s
46
+ formated.gsub!('::', '.')
47
+ end
48
+
49
+ formated.tr!(':|@', '_')
50
+ formated
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class TagSerializer
7
+ def initialize(global_tags = [], env = ENV)
8
+ # Convert to hash
9
+ global_tags = to_tags_hash(global_tags)
10
+
11
+ # Merge with default tags
12
+ global_tags = default_tags(env).merge(global_tags)
13
+
14
+ # Convert to tag list and set
15
+ @global_tags = to_tags_list(global_tags)
16
+ @global_tags_formatted = @global_tags.join(',') if @global_tags.any?
17
+ end
18
+
19
+ def format(message_tags)
20
+ if !message_tags || message_tags.empty?
21
+ return @global_tags_formatted
22
+ end
23
+
24
+ tags = if @global_tags_formatted
25
+ [@global_tags_formatted, to_tags_list(message_tags)]
26
+ else
27
+ to_tags_list(message_tags)
28
+ end
29
+
30
+ tags.join(',')
31
+ end
32
+
33
+ attr_reader :global_tags
34
+
35
+ private
36
+
37
+ def to_tags_hash(tags)
38
+ case tags
39
+ when Hash
40
+ tags.dup
41
+ when Array
42
+ Hash[
43
+ tags.map do |string|
44
+ tokens = string.split(':')
45
+ tokens << nil if tokens.length == 1
46
+ tokens.length == 2 ? tokens : nil
47
+ end.compact
48
+ ]
49
+ else
50
+ {}
51
+ end
52
+ end
53
+
54
+ def to_tags_list(tags)
55
+ case tags
56
+ when Hash
57
+ tags.map do |name, value|
58
+ if value
59
+ escape_tag_content("#{name}:#{value}")
60
+ else
61
+ escape_tag_content(name)
62
+ end
63
+ end
64
+ when Array
65
+ tags.map { |tag| escape_tag_content(tag) }
66
+ else
67
+ []
68
+ end
69
+ end
70
+
71
+ def escape_tag_content(tag)
72
+ tag.to_s.delete('|,')
73
+ end
74
+
75
+ def dd_tags(env = ENV)
76
+ return {} unless dd_tags = env['DD_TAGS']
77
+
78
+ to_tags_hash(dd_tags.split(','))
79
+ end
80
+
81
+ def default_tags(env = ENV)
82
+ dd_tags(env).tap do |tags|
83
+ tags['dd.internal.entity_id'] = env['DD_ENTITY_ID'] if env.key?('DD_ENTITY_ID')
84
+ tags['env'] = env['DD_ENV'] if env.key?('DD_ENV')
85
+ tags['service'] = env['DD_SERVICE'] if env.key?('DD_SERVICE')
86
+ tags['version'] = env['DD_VERSION'] if env.key?('DD_VERSION')
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -4,29 +4,34 @@ require 'time'
4
4
  module Datadog
5
5
  class Statsd
6
6
  class Telemetry
7
- attr_accessor :metrics
8
- attr_accessor :events
9
- attr_accessor :service_checks
10
- attr_accessor :bytes_sent
11
- attr_accessor :bytes_dropped
12
- attr_accessor :packets_sent
13
- attr_accessor :packets_dropped
14
- attr_reader :estimate_max_size
7
+ attr_reader :metrics
8
+ attr_reader :events
9
+ attr_reader :service_checks
10
+ attr_reader :bytes_sent
11
+ attr_reader :bytes_dropped
12
+ attr_reader :packets_sent
13
+ attr_reader :packets_dropped
15
14
 
16
- def initialize(disabled, tags, flush_interval)
17
- @disabled = disabled
18
- @tags = tags
15
+ # Rough estimation of maximum telemetry message size without tags
16
+ MAX_TELEMETRY_MESSAGE_SIZE_WT_TAGS = 50 # bytes
17
+
18
+ def initialize(flush_interval, global_tags: [], transport_type: :udp)
19
19
  @flush_interval = flush_interval
20
+ @global_tags = global_tags
21
+ @transport_type = transport_type
20
22
  reset
21
23
 
22
- # estimate_max_size is an estimation or the maximum size of the
23
- # telemetry payload. Since we don't want our packet to go over
24
- # 'max_buffer_bytes', we have to adjust with the size of the telemetry
25
- # (and any tags used). The telemetry payload size will change depending
26
- # on the actual value of metrics: metrics received, packet dropped,
27
- # etc. This is why we add a 63bytes margin: 9 bytes for each of the 7
28
- # telemetry metrics.
29
- @estimate_max_size = @disabled ? 0 : flush().length + 9 * 7
24
+ # TODO: Karim: I don't know why but telemetry tags are serialized
25
+ # before global tags so by refactoring this, I am keeping the same behavior
26
+ @serialized_tags = Serialization::TagSerializer.new(
27
+ client: 'ruby',
28
+ client_version: VERSION,
29
+ client_transport: transport_type,
30
+ ).format(global_tags)
31
+ end
32
+
33
+ def would_fit_in?(max_buffer_payload_size)
34
+ MAX_TELEMETRY_MESSAGE_SIZE_WT_TAGS + serialized_tags.size < max_buffer_payload_size
30
35
  end
31
36
 
32
37
  def reset
@@ -37,28 +42,54 @@ module Datadog
37
42
  @bytes_dropped = 0
38
43
  @packets_sent = 0
39
44
  @packets_dropped = 0
40
- @next_flush_time = Time.now.to_i + @flush_interval
45
+ @next_flush_time = now_in_s + @flush_interval
41
46
  end
42
47
 
43
- def flush?
44
- if @next_flush_time < Time.now.to_i
45
- return true
46
- end
47
- return false
48
+ def sent(metrics: 0, events: 0, service_checks: 0, bytes: 0, packets: 0)
49
+ @metrics += metrics
50
+ @events += events
51
+ @service_checks += service_checks
52
+
53
+ @bytes_sent += bytes
54
+ @packets_sent += packets
55
+ end
56
+
57
+ def dropped(bytes: 0, packets: 0)
58
+ @bytes_dropped += bytes
59
+ @packets_dropped += packets
60
+ end
61
+
62
+ def should_flush?
63
+ @next_flush_time < now_in_s
48
64
  end
49
65
 
50
66
  def flush
51
- return '' if @disabled
67
+ [
68
+ sprintf(pattern, 'metrics', @metrics),
69
+ sprintf(pattern, 'events', @events),
70
+ sprintf(pattern, 'service_checks', @service_checks),
71
+ sprintf(pattern, 'bytes_sent', @bytes_sent),
72
+ sprintf(pattern, 'bytes_dropped', @bytes_dropped),
73
+ sprintf(pattern, 'packets_sent', @packets_sent),
74
+ sprintf(pattern, 'packets_dropped', @packets_dropped),
75
+ ]
76
+ end
52
77
 
53
- # using shorthand syntax to reduce the garbage collection
54
- return %Q(
55
- datadog.dogstatsd.client.metrics:#{@metrics}|#{COUNTER_TYPE}|##{@tags}
56
- datadog.dogstatsd.client.events:#{@events}|#{COUNTER_TYPE}|##{@tags}
57
- datadog.dogstatsd.client.service_checks:#{@service_checks}|#{COUNTER_TYPE}|##{@tags}
58
- datadog.dogstatsd.client.bytes_sent:#{@bytes_sent}|#{COUNTER_TYPE}|##{@tags}
59
- datadog.dogstatsd.client.bytes_dropped:#{@bytes_dropped}|#{COUNTER_TYPE}|##{@tags}
60
- datadog.dogstatsd.client.packets_sent:#{@packets_sent}|#{COUNTER_TYPE}|##{@tags}
61
- datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{@tags})
78
+ private
79
+ attr_reader :serialized_tags
80
+
81
+ def pattern
82
+ @pattern ||= "datadog.dogstatsd.client.%s:%d|#{COUNTER_TYPE}|##{serialized_tags}"
83
+ end
84
+
85
+ if Kernel.const_defined?('Process') && Process.respond_to?(:clock_gettime)
86
+ def now_in_s
87
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
88
+ end
89
+ else
90
+ def now_in_s
91
+ Time.now.to_i
92
+ end
62
93
  end
63
94
  end
64
95
  end
@@ -14,11 +14,11 @@ module Datadog
14
14
  # StatsD port. Defaults to 8125.
15
15
  attr_reader :port
16
16
 
17
- def initialize(host, port, logger, telemetry)
18
- super(telemetry)
17
+ def initialize(host, port, **kwargs)
18
+ super(**kwargs)
19
+
19
20
  @host = host || ENV.fetch('DD_AGENT_HOST', DEFAULT_HOST)
20
- @port = port || ENV.fetch('DD_DOGSTATSD_PORT', DEFAULT_PORT)
21
- @logger = logger
21
+ @port = port || ENV.fetch('DD_DOGSTATSD_PORT', DEFAULT_PORT).to_i
22
22
  end
23
23
 
24
24
  private
@@ -10,10 +10,10 @@ module Datadog
10
10
  # DogStatsd unix socket path
11
11
  attr_reader :socket_path
12
12
 
13
- def initialize(socket_path, logger, telemetry)
14
- super(telemetry)
13
+ def initialize(socket_path, **kwargs)
14
+ super(**kwargs)
15
+
15
16
  @socket_path = socket_path
16
- @logger = logger
17
17
  end
18
18
 
19
19
  private
@@ -28,6 +28,9 @@ module Datadog
28
28
  socket.sendmsg_nonblock(message)
29
29
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENOENT => e
30
30
  @socket = nil
31
+ # TODO: FIXME: This error should be considered as a retryable error in the
32
+ # Connection class. An even better solution would be to make BadSocketError inherit
33
+ # from a specific retryable error class in the Connection class.
31
34
  raise BadSocketError, "#{e.class}: #{e}"
32
35
  end
33
36
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'connection'
4
+
5
+ module Datadog
6
+ class Statsd
7
+ VERSION = '5.0.0'
8
+ end
9
+ end
metadata CHANGED
@@ -1,16 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dogstatsd-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.7.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rein Henrichs
8
- autorequire:
8
+ - Karim Bogtob
9
+ autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2020-02-14 00:00:00.000000000 Z
12
+ date: 2021-04-07 00:00:00.000000000 Z
12
13
  dependencies: []
13
- description: A Ruby DogStastd client
14
+ description: A Ruby DogStatsd client
14
15
  email: code@datadoghq.com
15
16
  executables: []
16
17
  extensions: []
@@ -21,20 +22,29 @@ files:
21
22
  - LICENSE.txt
22
23
  - README.md
23
24
  - lib/datadog/statsd.rb
24
- - lib/datadog/statsd/batch.rb
25
25
  - lib/datadog/statsd/connection.rb
26
+ - lib/datadog/statsd/forwarder.rb
27
+ - lib/datadog/statsd/message_buffer.rb
28
+ - lib/datadog/statsd/sender.rb
29
+ - lib/datadog/statsd/serialization.rb
30
+ - lib/datadog/statsd/serialization/event_serializer.rb
31
+ - lib/datadog/statsd/serialization/serializer.rb
32
+ - lib/datadog/statsd/serialization/service_check_serializer.rb
33
+ - lib/datadog/statsd/serialization/stat_serializer.rb
34
+ - lib/datadog/statsd/serialization/tag_serializer.rb
26
35
  - lib/datadog/statsd/telemetry.rb
27
36
  - lib/datadog/statsd/udp_connection.rb
28
37
  - lib/datadog/statsd/uds_connection.rb
38
+ - lib/datadog/statsd/version.rb
29
39
  homepage: https://github.com/DataDog/dogstatsd-ruby
30
40
  licenses:
31
41
  - MIT
32
42
  metadata:
33
43
  bug_tracker_uri: https://github.com/DataDog/dogstatsd-ruby/issues
34
- changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v4.7.0/CHANGELOG.md
35
- documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/4.7.0
36
- source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v4.7.0
37
- post_install_message:
44
+ changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v5.0.0/CHANGELOG.md
45
+ documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/5.0.0
46
+ source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v5.0.0
47
+ post_install_message:
38
48
  rdoc_options: []
39
49
  require_paths:
40
50
  - lib
@@ -49,9 +59,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
49
59
  - !ruby/object:Gem::Version
50
60
  version: '0'
51
61
  requirements: []
52
- rubyforge_project:
62
+ rubyforge_project:
53
63
  rubygems_version: 2.7.10
54
- signing_key:
64
+ signing_key:
55
65
  specification_version: 4
56
66
  summary: A Ruby DogStatsd client
57
67
  test_files: []