dogstatsd-ruby 4.8.0 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7485524dc133a21013d2ab5144511a4ba38740226bdeaf1d3131be99859f33ec
4
- data.tar.gz: 6358ca98c85788c904e48ebf3d0d3751372b3ba5baaaf194199fef1f5badbae0
3
+ metadata.gz: c77f82b5b9a858517a937a5b2db1a1f890a80c94ca63e60f12e256ab28f7192d
4
+ data.tar.gz: e95ee401174c084edb117068f73d1c72f08e602ea9b51363e57e53c176072472
5
5
  SHA512:
6
- metadata.gz: edf3b324873893d4acfff6a8775ee7d3ca337e416f283f29137abbaed447f8db220d8d09cc00131a0a53b0d4f38555f48d669658812ebe739903dc68a74ab8c3
7
- data.tar.gz: 9b5bd953d65e25ad74733175febb8a4a3b4dfee5e2353d1f539684443ddface8f65c9ebde4f88dfd5bd4063b892c2c76350754a112481bfeacb2f454a199dac9
6
+ metadata.gz: 88d54866c8693d2dab18a1370d6ba5951a42ec4ed62615c6194548dd5faa109731840187e2a7ddaf591f08d07064c8e173df74728f182fe5cb5593c896503e19
7
+ data.tar.gz: d8021d0b6b21efe7ab14841665b01819c397d4419b247615d88b7685b332b1c8bb9a13d05fc22d21baf5d709d5452f9a9a3d932438addfbf1238e8ab5656a8e5
data/README.md CHANGED
@@ -71,9 +71,22 @@ After the client is created, you can start sending events to your Datadog Event
71
71
 
72
72
  After the client is created, you can start sending Service Checks to Datadog. See the dedicated [Service Check Submission: DogStatsD documentation](https://docs.datadoghq.com/developers/service_checks/dogstatsd_service_checks_submission/?tab=ruby) to see how to submit a Service Check to Datadog.
73
73
 
74
+ ### Maximum packets size in high-throughput scenarios
75
+
76
+ In order to have the most efficient use of this library in high-throughput scenarios,
77
+ default values for the maximum packets size have already been set for both UDS (8192 bytes)
78
+ and UDP (1432 bytes) in order to have the best usage of the underlying network.
79
+ However, if you perfectly know your network and you know that a different value for the maximum packets
80
+ size should be used, you can set it with the parameter `buffer_max_payload_size`. Example:
81
+
82
+ ```ruby
83
+ # Create a DogStatsD client instance.
84
+ statsd = Datadog::Statsd.new('localhost', 8125, buffer_max_payload_size: 4096)
85
+ ```
86
+
74
87
  ## Credits
75
88
 
76
- dogstatsd-ruby is forked from Rien Henrichs [original Statsd
89
+ dogstatsd-ruby is forked from Rein Henrichs [original Statsd
77
90
  client](https://github.com/reinh/statsd).
78
91
 
79
92
  Copyright (c) 2011 Rein Henrichs. See LICENSE.txt for
@@ -5,8 +5,10 @@ require_relative 'statsd/version'
5
5
  require_relative 'statsd/telemetry'
6
6
  require_relative 'statsd/udp_connection'
7
7
  require_relative 'statsd/uds_connection'
8
- require_relative 'statsd/batch'
8
+ require_relative 'statsd/message_buffer'
9
9
  require_relative 'statsd/serialization'
10
+ require_relative 'statsd/sender'
11
+ require_relative 'statsd/forwarder'
10
12
 
11
13
  # = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
12
14
  #
@@ -26,12 +28,17 @@ require_relative 'statsd/serialization'
26
28
  # statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
27
29
  module Datadog
28
30
  class Statsd
31
+ class Error < StandardError
32
+ end
33
+
29
34
  OK = 0
30
35
  WARNING = 1
31
36
  CRITICAL = 2
32
37
  UNKNOWN = 3
33
38
 
34
- DEFAULT_BUFFER_SIZE = 8 * 1_024
39
+ UDP_DEFAULT_BUFFER_SIZE = 1_432
40
+ UDS_DEFAULT_BUFFER_SIZE = 8_192
41
+ DEFAULT_BUFFER_POOL_SIZE = Float::INFINITY
35
42
  MAX_EVENT_SIZE = 8 * 1_024
36
43
  # minimum flush interval for the telemetry in seconds
37
44
  DEFAULT_TELEMETRY_FLUSH_INTERVAL = 10
@@ -51,67 +58,59 @@ module Datadog
51
58
  serializer.global_tags
52
59
  end
53
60
 
54
- # Buffer containing the statsd message before they are sent in batch
55
- attr_reader :buffer
56
-
57
- # Maximum buffer size in bytes before it is flushed
58
- attr_reader :max_buffer_bytes
59
-
60
61
  # Default sample rate
61
62
  attr_reader :sample_rate
62
63
 
63
- # Connection
64
- attr_reader :connection
65
-
66
64
  # @param [String] host your statsd host
67
65
  # @param [Integer] port your statsd port
68
66
  # @option [String] namespace set a namespace to be prepended to every metric name
69
67
  # @option [Array<String>|Hash] tags tags to be added to every metric
70
68
  # @option [Logger] logger for debugging
71
- # @option [Integer] max_buffer_bytes max bytes to buffer when using #batch
69
+ # @option [Integer] buffer_max_payload_size max bytes to buffer
70
+ # @option [Integer] buffer_max_pool_size max messages to buffer
72
71
  # @option [String] socket_path unix socket path
73
72
  # @option [Float] default sample rate if not overridden
74
73
  def initialize(
75
74
  host = nil,
76
75
  port = nil,
76
+ socket_path: nil,
77
+
77
78
  namespace: nil,
78
79
  tags: nil,
79
- max_buffer_bytes: DEFAULT_BUFFER_SIZE,
80
- socket_path: nil,
81
- logger: nil,
82
80
  sample_rate: nil,
83
- disable_telemetry: false,
81
+
82
+ buffer_max_payload_size: nil,
83
+ buffer_max_pool_size: nil,
84
+ buffer_overflowing_stategy: :drop,
85
+
86
+ logger: nil,
87
+
88
+ telemetry_enable: true,
84
89
  telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
85
90
  )
86
91
  unless tags.nil? || tags.is_a?(Array) || tags.is_a?(Hash)
87
- raise ArgumentError, 'tags must be a Array<String> or a Hash'
92
+ raise ArgumentError, 'tags must be an array of string tags or a Hash'
88
93
  end
89
94
 
90
95
  @namespace = namespace
91
96
  @prefix = @namespace ? "#{@namespace}.".freeze : nil
92
-
93
97
  @serializer = Serialization::Serializer.new(prefix: @prefix, global_tags: tags)
98
+ @sample_rate = sample_rate
94
99
 
95
- transport_type = socket_path.nil? ? :udp : :uds
100
+ @forwarder = Forwarder.new(
101
+ host: host,
102
+ port: port,
103
+ socket_path: socket_path,
96
104
 
97
- @telemetry = Telemetry.new(disable_telemetry, telemetry_flush_interval,
98
105
  global_tags: tags,
99
- transport_type: transport_type
100
- )
101
-
102
- @connection = case transport_type
103
- when :udp
104
- UDPConnection.new(host, port, logger, telemetry)
105
- when :uds
106
- UDSConnection.new(socket_path, logger, telemetry)
107
- end
106
+ logger: logger,
108
107
 
109
- @logger = logger
108
+ buffer_max_payload_size: buffer_max_payload_size,
109
+ buffer_max_pool_size: buffer_max_pool_size,
110
+ buffer_overflowing_stategy: buffer_overflowing_stategy,
110
111
 
111
- @sample_rate = sample_rate
112
-
113
- # we reduce max_buffer_bytes by a the rough estimate of the telemetry payload
114
- @batch = Batch.new(connection, (max_buffer_bytes - telemetry.estimate_max_size))
112
+ telemetry_flush_interval: telemetry_enable ? telemetry_flush_interval : nil,
113
+ )
115
114
  end
116
115
 
117
116
  # yield a new instance to a block and close it when done
@@ -270,9 +269,9 @@ module Datadog
270
269
  # @example Report a critical service check status
271
270
  # $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
272
271
  def service_check(name, status, opts = EMPTY_OPTIONS)
273
- telemetry.sent(service_checks: 1)
272
+ telemetry.sent(service_checks: 1) if telemetry
274
273
 
275
- send_stat(serializer.to_service_check(name, status, opts))
274
+ forwarder.send_message(serializer.to_service_check(name, status, opts))
276
275
  end
277
276
 
278
277
  # This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
@@ -290,17 +289,25 @@ module Datadog
290
289
  # @option opts [String, nil] :priority ('normal') Can be "normal" or "low"
291
290
  # @option opts [String, nil] :source_type_name (nil) Assign a source type to the event
292
291
  # @option opts [String, nil] :alert_type ('info') Can be "error", "warning", "info" or "success".
292
+ # @option opts [Boolean, false] :truncate_if_too_long (false) Truncate the event if it is too long
293
293
  # @option opts [Array<String>] :tags tags to be added to every metric
294
294
  # @example Report an awful event:
295
295
  # $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
296
296
  def event(title, text, opts = EMPTY_OPTIONS)
297
- telemetry.sent(events: 1)
297
+ telemetry.sent(events: 1) if telemetry
298
298
 
299
- send_stat(serializer.to_event(title, text, opts))
299
+ forwarder.send_message(serializer.to_event(title, text, opts))
300
300
  end
301
301
 
302
- # Send several metrics in the same UDP Packet
303
- # They will be buffered and flushed when the block finishes
302
+ # Send several metrics in the same packet.
303
+ # They will be buffered and flushed when the block finishes.
304
+ #
305
+ # This method exists for compatibility with v4.x versions, it is not needed
306
+ # anymore since the batching is now automatically done internally.
307
+ # It also means that an automatic flush could occur if the buffer is filled
308
+ # during the execution of the batch block.
309
+ #
310
+ # This method is DEPRECATED and will be removed in future v6.x API.
304
311
  #
305
312
  # @example Send several metrics in one packet:
306
313
  # $statsd.batch do |s|
@@ -308,19 +315,47 @@ module Datadog
308
315
  # s.increment('page.views')
309
316
  # end
310
317
  def batch
311
- @batch.open do
312
- yield self
313
- end
318
+ yield self
319
+ flush(sync: true)
314
320
  end
315
321
 
316
322
  # Close the underlying socket
317
323
  def close
318
- connection.close
324
+ forwarder.close
325
+ end
326
+
327
+ def sync_with_outbound_io
328
+ forwarder.sync_with_outbound_io
329
+ end
330
+
331
+ # Flush the buffer into the connection
332
+ def flush(flush_telemetry: false, sync: false)
333
+ forwarder.flush(flush_telemetry: flush_telemetry, sync: sync)
334
+ end
335
+
336
+ def telemetry
337
+ forwarder.telemetry
338
+ end
339
+
340
+ def host
341
+ forwarder.host
342
+ end
343
+
344
+ def port
345
+ forwarder.port
346
+ end
347
+
348
+ def socket_path
349
+ forwarder.socket_path
350
+ end
351
+
352
+ def transport_type
353
+ forwarder.transport_type
319
354
  end
320
355
 
321
356
  private
322
357
  attr_reader :serializer
323
- attr_reader :telemetry
358
+ attr_reader :forwarder
324
359
 
325
360
  PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= '2.1.0')
326
361
  EMPTY_OPTIONS = {}.freeze
@@ -336,22 +371,14 @@ module Datadog
336
371
  end
337
372
 
338
373
  def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
339
- telemetry.sent(metrics: 1)
374
+ telemetry.sent(metrics: 1) if telemetry
340
375
 
341
376
  sample_rate = opts[:sample_rate] || @sample_rate || 1
342
377
 
343
378
  if sample_rate == 1 || rand <= sample_rate
344
379
  full_stat = serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
345
380
 
346
- send_stat(full_stat)
347
- end
348
- end
349
-
350
- def send_stat(message)
351
- if @batch.open?
352
- @batch.add(message)
353
- else
354
- @connection.write(message)
381
+ forwarder.send_message(full_stat)
355
382
  end
356
383
  end
357
384
  end
@@ -3,8 +3,9 @@
3
3
  module Datadog
4
4
  class Statsd
5
5
  class Connection
6
- def initialize(telemetry)
6
+ def initialize(telemetry: nil, logger: nil)
7
7
  @telemetry = telemetry
8
+ @logger = logger
8
9
  end
9
10
 
10
11
  # Close the underlying socket
@@ -20,15 +21,11 @@ module Datadog
20
21
  def write(payload)
21
22
  logger.debug { "Statsd: #{payload}" } if logger
22
23
 
23
- flush_telemetry = telemetry.flush?
24
-
25
- payload += telemetry.flush if flush_telemetry
26
-
27
24
  send_message(payload)
28
25
 
29
- telemetry.reset if flush_telemetry
26
+ telemetry.sent(packets: 1, bytes: payload.length) if telemetry
30
27
 
31
- telemetry.sent(packets: 1, bytes: payload.length)
28
+ true
32
29
  rescue StandardError => boom
33
30
  # Try once to reconnect if the socket has been closed
34
31
  retries ||= 1
@@ -45,7 +42,7 @@ module Datadog
45
42
  end
46
43
  end
47
44
 
48
- telemetry.dropped(packets: 1, bytes: payload.length)
45
+ telemetry.dropped(packets: 1, bytes: payload.length) if telemetry
49
46
  logger.error { "Statsd: #{boom.class} #{boom}" } if logger
50
47
  nil
51
48
  end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ class Forwarder
6
+ attr_reader :telemetry
7
+ attr_reader :transport_type
8
+
9
+ def initialize(
10
+ host: nil,
11
+ port: nil,
12
+ socket_path: nil,
13
+
14
+ buffer_max_payload_size: nil,
15
+ buffer_max_pool_size: nil,
16
+ buffer_overflowing_stategy: :drop,
17
+
18
+ telemetry_flush_interval: nil,
19
+ global_tags: [],
20
+
21
+ logger: nil
22
+ )
23
+ @transport_type = socket_path.nil? ? :udp : :uds
24
+
25
+ if telemetry_flush_interval
26
+ @telemetry = Telemetry.new(telemetry_flush_interval,
27
+ global_tags: global_tags,
28
+ transport_type: transport_type
29
+ )
30
+ end
31
+
32
+ @connection = case transport_type
33
+ when :udp
34
+ UDPConnection.new(host, port, logger: logger, telemetry: telemetry)
35
+ when :uds
36
+ UDSConnection.new(socket_path, logger: logger, telemetry: telemetry)
37
+ end
38
+
39
+ # Initialize buffer
40
+ buffer_max_payload_size ||= (transport_type == :udp ? UDP_DEFAULT_BUFFER_SIZE : UDS_DEFAULT_BUFFER_SIZE)
41
+
42
+ if buffer_max_payload_size <= 0
43
+ raise ArgumentError, 'buffer_max_payload_size cannot be <= 0'
44
+ end
45
+
46
+ unless telemetry.nil? || telemetry.would_fit_in?(buffer_max_payload_size)
47
+ raise ArgumentError, "buffer_max_payload_size is not high enough to use telemetry (tags=(#{global_tags.inspect}))"
48
+ end
49
+
50
+ @buffer = MessageBuffer.new(@connection,
51
+ max_payload_size: buffer_max_payload_size,
52
+ max_pool_size: buffer_max_pool_size || DEFAULT_BUFFER_POOL_SIZE,
53
+ overflowing_stategy: buffer_overflowing_stategy,
54
+ )
55
+
56
+ @sender = Sender.new(buffer)
57
+ @sender.start
58
+ end
59
+
60
+ def send_message(message)
61
+ sender.add(message)
62
+
63
+ tick_telemetry
64
+ end
65
+
66
+ def sync_with_outbound_io
67
+ sender.rendez_vous
68
+ end
69
+
70
+ def flush(flush_telemetry: false, sync: false)
71
+ do_flush_telemetry if telemetry && flush_telemetry
72
+
73
+ sender.flush(sync: sync)
74
+ end
75
+
76
+ def host
77
+ return nil unless transport_type == :udp
78
+
79
+ connection.host
80
+ end
81
+
82
+ def port
83
+ return nil unless transport_type == :udp
84
+
85
+ connection.port
86
+ end
87
+
88
+ def socket_path
89
+ return nil unless transport_type == :uds
90
+
91
+ connection.socket_path
92
+ end
93
+
94
+ def close
95
+ sender.stop
96
+ connection.close
97
+ end
98
+
99
+ private
100
+ attr_reader :buffer
101
+ attr_reader :sender
102
+ attr_reader :connection
103
+
104
+ def do_flush_telemetry
105
+ telemetry_snapshot = telemetry.flush
106
+ telemetry.reset
107
+
108
+ telemetry_snapshot.each do |message|
109
+ sender.add(message)
110
+ end
111
+ end
112
+
113
+ def tick_telemetry
114
+ return nil unless telemetry
115
+
116
+ do_flush_telemetry if telemetry.should_flush?
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ class MessageBuffer
6
+ PAYLOAD_SIZE_TOLERANCE = 0.05
7
+
8
+ def initialize(connection,
9
+ max_payload_size: nil,
10
+ max_pool_size: DEFAULT_BUFFER_POOL_SIZE,
11
+ overflowing_stategy: :drop
12
+ )
13
+ raise ArgumentError, 'max_payload_size keyword argument must be provided' unless max_payload_size
14
+ raise ArgumentError, 'max_pool_size keyword argument must be provided' unless max_pool_size
15
+
16
+ @connection = connection
17
+ @max_payload_size = max_payload_size
18
+ @max_pool_size = max_pool_size
19
+ @overflowing_stategy = overflowing_stategy
20
+
21
+ @buffer = String.new
22
+ @message_count = 0
23
+ end
24
+
25
+ def add(message)
26
+ message_size = message.bytesize
27
+
28
+ return nil unless message_size > 0 # to avoid adding empty messages to the buffer
29
+ return nil unless ensure_sendable!(message_size)
30
+
31
+ flush if should_flush?(message_size)
32
+
33
+ buffer << "\n" unless buffer.empty?
34
+ buffer << message
35
+
36
+ @message_count += 1
37
+
38
+ # flush when we're pretty sure that we won't be able
39
+ # to add another message to the buffer
40
+ flush if preemptive_flush?
41
+
42
+ true
43
+ end
44
+
45
+ def flush
46
+ return if buffer.empty?
47
+
48
+ connection.write(buffer)
49
+
50
+ buffer.clear
51
+ @message_count = 0
52
+ end
53
+
54
+ private
55
+ attr :max_payload_size
56
+ attr :max_pool_size
57
+
58
+ attr :overflowing_stategy
59
+
60
+ attr :connection
61
+ attr :buffer
62
+
63
+ def should_flush?(message_size)
64
+ return true if buffer.bytesize + 1 + message_size >= max_payload_size
65
+
66
+ false
67
+ end
68
+
69
+ def preemptive_flush?
70
+ @message_count == max_pool_size || buffer.bytesize > bytesize_threshold
71
+ end
72
+
73
+ def ensure_sendable!(message_size)
74
+ return true if message_size <= max_payload_size
75
+
76
+ if overflowing_stategy == :raise
77
+ raise Error, 'Message too big for payload limit'
78
+ end
79
+
80
+ false
81
+ end
82
+
83
+ def bytesize_threshold
84
+ @bytesize_threshold ||= (max_payload_size - PAYLOAD_SIZE_TOLERANCE * max_payload_size).to_i
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ class Sender
6
+ CLOSEABLE_QUEUES = Queue.instance_methods.include?(:close)
7
+
8
+ def initialize(message_buffer)
9
+ @message_buffer = message_buffer
10
+ end
11
+
12
+ def flush(sync: false)
13
+ raise ArgumentError, 'Start sender first' unless message_queue
14
+
15
+ message_queue.push(:flush)
16
+
17
+ rendez_vous if sync
18
+ end
19
+
20
+ def rendez_vous
21
+ # Initialize and get the thread's sync queue
22
+ queue = (Thread.current[:statsd_sync_queue] ||= Queue.new)
23
+ # tell sender-thread to notify us in the current
24
+ # thread's queue
25
+ message_queue.push(queue)
26
+ # wait for the sender thread to send a message
27
+ # once the flush is done
28
+ queue.pop
29
+ end
30
+
31
+ def add(message)
32
+ raise ArgumentError, 'Start sender first' unless message_queue
33
+
34
+ message_queue << message
35
+ end
36
+
37
+ def start
38
+ raise ArgumentError, 'Sender already started' if message_queue
39
+
40
+ # initialize message queue for background thread
41
+ @message_queue = Queue.new
42
+ # start background thread
43
+ @sender_thread = Thread.new(&method(:send_loop))
44
+ end
45
+
46
+ if CLOSEABLE_QUEUES
47
+ def stop(join_worker: true)
48
+ message_queue = @message_queue
49
+ message_queue.close if message_queue
50
+
51
+ sender_thread = @sender_thread
52
+ sender_thread.join if sender_thread && join_worker
53
+ end
54
+ else
55
+ def stop(join_worker: true)
56
+ message_queue = @message_queue
57
+ message_queue << :close if message_queue
58
+
59
+ sender_thread = @sender_thread
60
+ sender_thread.join if sender_thread && join_worker
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ attr_reader :message_buffer
67
+
68
+ attr_reader :message_queue
69
+ attr_reader :sender_thread
70
+
71
+ if CLOSEABLE_QUEUES
72
+ def send_loop
73
+ until (message = message_queue.pop).nil? && message_queue.closed?
74
+ # skip if message is nil, e.g. when message_queue
75
+ # is empty and closed
76
+ next unless message
77
+
78
+ case message
79
+ when :flush
80
+ message_buffer.flush
81
+ when Queue
82
+ message.push(:go_on)
83
+ else
84
+ message_buffer.add(message)
85
+ end
86
+ end
87
+
88
+ @message_queue = nil
89
+ @sender_thread = nil
90
+ end
91
+ else
92
+ def send_loop
93
+ loop do
94
+ message = message_queue.pop
95
+
96
+ next unless message
97
+
98
+ case message
99
+ when :close
100
+ break
101
+ when :flush
102
+ message_buffer.flush
103
+ when Queue
104
+ message.push(:go_on)
105
+ else
106
+ message_buffer.add(message)
107
+ end
108
+ end
109
+
110
+ @message_queue = nil
111
+ @sender_thread = nil
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -41,15 +41,18 @@ module Datadog
41
41
  end
42
42
  end
43
43
 
44
- if raw_tags = options[:tags]
45
- if tags = tag_serializer.format(raw_tags)
46
- event << '|#'
47
- event << tags
48
- end
44
+ # also returns the global tags from serializer
45
+ if tags = tag_serializer.format(options[:tags])
46
+ event << '|#'
47
+ event << tags
49
48
  end
50
49
 
51
50
  if event.bytesize > MAX_EVENT_SIZE
52
- raise "Event #{title} payload is too big (more that 8KB), event discarded"
51
+ if options[:truncate_if_too_long]
52
+ event.slice!(MAX_EVENT_SIZE..event.length)
53
+ else
54
+ raise "Event #{title} payload is too big (more that 8KB), event discarded"
55
+ end
53
56
  end
54
57
  end
55
58
  end
@@ -37,11 +37,10 @@ module Datadog
37
37
  service_check << escape_message(message)
38
38
  end
39
39
 
40
- if raw_tags = options[:tags]
41
- if tags = tag_serializer.format(raw_tags)
42
- service_check << '|#'
43
- service_check << tags
44
- end
40
+ # also returns the global tags from serializer
41
+ if tags = tag_serializer.format(options[:tags])
42
+ service_check << '|#'
43
+ service_check << tags
45
44
  end
46
45
  end
47
46
  end
@@ -6,34 +6,24 @@ module Datadog
6
6
  class StatSerializer
7
7
  def initialize(prefix, global_tags: [])
8
8
  @prefix = prefix
9
+ @prefix_str = prefix.to_s
9
10
  @tag_serializer = TagSerializer.new(global_tags)
10
11
  end
11
12
 
12
13
  def format(name, delta, type, tags: [], sample_rate: 1)
13
- String.new.tap do |stat|
14
- stat << prefix if prefix
15
-
16
- # stat value
17
- stat << formated_name(name)
18
- stat << ':'
19
- stat << delta.to_s
20
-
21
- # stat type
22
- stat << '|'
23
- stat << type
24
-
25
- # sample_rate
26
- if sample_rate != 1
27
- stat << '|'
28
- stat << '@'
29
- stat << sample_rate.to_s
30
- end
14
+ name = formated_name(name)
31
15
 
32
- # tags
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
33
23
  if tags_list = tag_serializer.format(tags)
34
- stat << '|'
35
- stat << '#'
36
- stat << tags_list
24
+ "#{@prefix_str}#{name}:#{delta}|#{type}|##{tags_list}"
25
+ else
26
+ "#{@prefix_str}#{name}:#{delta}|#{type}"
37
27
  end
38
28
  end
39
29
  end
@@ -43,18 +33,21 @@ module Datadog
43
33
  end
44
34
 
45
35
  private
36
+
46
37
  attr_reader :prefix
47
38
  attr_reader :tag_serializer
48
39
 
49
40
  def formated_name(name)
50
- formated = name.is_a?(String) ? name.dup : name.to_s
51
-
52
- formated.tap do |f|
53
- # replace Ruby module scoping with '.'
54
- f.gsub!('::', '.')
55
- # replace reserved chars (: | @) with underscores.
56
- f.tr!(':|@', '_')
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!('::', '.')
57
47
  end
48
+
49
+ formated.tr!(':|@', '_')
50
+ formated
58
51
  end
59
52
  end
60
53
  end
@@ -13,18 +13,25 @@ module Datadog
13
13
 
14
14
  # Convert to tag list and set
15
15
  @global_tags = to_tags_list(global_tags)
16
+ if @global_tags.any?
17
+ @global_tags_formatted = @global_tags.join(',')
18
+ else
19
+ @global_tags_formatted = nil
20
+ end
16
21
  end
17
22
 
18
23
  def format(message_tags)
19
- # fast return global tags if there's no message_tags
20
- # to avoid more allocations
21
- tag_list = if message_tags && message_tags.any?
22
- global_tags + to_tags_list(message_tags)
23
- else
24
- global_tags
25
- end
24
+ if !message_tags || message_tags.empty?
25
+ return @global_tags_formatted
26
+ end
26
27
 
27
- tag_list.join(',') if tag_list.any?
28
+ tags = if @global_tags_formatted
29
+ [@global_tags_formatted, to_tags_list(message_tags)]
30
+ else
31
+ to_tags_list(message_tags)
32
+ end
33
+
34
+ tags.join(',')
28
35
  end
29
36
 
30
37
  attr_reader :global_tags
@@ -51,19 +58,17 @@ module Datadog
51
58
  def to_tags_list(tags)
52
59
  case tags
53
60
  when Hash
54
- tags.each_with_object([]) do |tag_pair, formatted_tags|
55
- if tag_pair.last.nil?
56
- formatted_tags << "#{tag_pair.first}"
61
+ tags.map do |name, value|
62
+ if value
63
+ escape_tag_content("#{name}:#{value}")
57
64
  else
58
- formatted_tags << "#{tag_pair.first}:#{tag_pair.last}"
65
+ escape_tag_content(name)
59
66
  end
60
67
  end
61
68
  when Array
62
- tags.dup
69
+ tags.map { |tag| escape_tag_content(tag) }
63
70
  else
64
71
  []
65
- end.map! do |tag|
66
- escape_tag_content(tag)
67
72
  end
68
73
  end
69
74
 
@@ -11,10 +11,11 @@ module Datadog
11
11
  attr_reader :bytes_dropped
12
12
  attr_reader :packets_sent
13
13
  attr_reader :packets_dropped
14
- attr_reader :estimate_max_size
15
14
 
16
- def initialize(disabled, flush_interval, global_tags: [], transport_type: :udp)
17
- @disabled = disabled
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)
18
19
  @flush_interval = flush_interval
19
20
  @global_tags = global_tags
20
21
  @transport_type = transport_type
@@ -27,15 +28,10 @@ module Datadog
27
28
  client_version: VERSION,
28
29
  client_transport: transport_type,
29
30
  ).format(global_tags)
31
+ end
30
32
 
31
- # estimate_max_size is an estimation or the maximum size of the
32
- # telemetry payload. Since we don't want our packet to go over
33
- # 'max_buffer_bytes', we have to adjust with the size of the telemetry
34
- # (and any tags used). The telemetry payload size will change depending
35
- # on the actual value of metrics: metrics received, packet dropped,
36
- # etc. This is why we add a 63bytes margin: 9 bytes for each of the 7
37
- # telemetry metrics.
38
- @estimate_max_size = disabled ? 0 : flush.length + 9 * 7
33
+ def would_fit_in?(max_buffer_payload_size)
34
+ MAX_TELEMETRY_MESSAGE_SIZE_WT_TAGS + serialized_tags.size < max_buffer_payload_size
39
35
  end
40
36
 
41
37
  def reset
@@ -63,27 +59,29 @@ module Datadog
63
59
  @packets_dropped += packets
64
60
  end
65
61
 
66
- def flush?
62
+ def should_flush?
67
63
  @next_flush_time < now_in_s
68
64
  end
69
65
 
70
66
  def flush
71
- return '' if @disabled
72
-
73
- # using shorthand syntax to reduce the garbage collection
74
- %Q(
75
- datadog.dogstatsd.client.metrics:#{@metrics}|#{COUNTER_TYPE}|##{serialized_tags}
76
- datadog.dogstatsd.client.events:#{@events}|#{COUNTER_TYPE}|##{serialized_tags}
77
- datadog.dogstatsd.client.service_checks:#{@service_checks}|#{COUNTER_TYPE}|##{serialized_tags}
78
- datadog.dogstatsd.client.bytes_sent:#{@bytes_sent}|#{COUNTER_TYPE}|##{serialized_tags}
79
- datadog.dogstatsd.client.bytes_dropped:#{@bytes_dropped}|#{COUNTER_TYPE}|##{serialized_tags}
80
- datadog.dogstatsd.client.packets_sent:#{@packets_sent}|#{COUNTER_TYPE}|##{serialized_tags}
81
- datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{serialized_tags})
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
+ ]
82
76
  end
83
77
 
84
78
  private
85
79
  attr_reader :serialized_tags
86
80
 
81
+ def pattern
82
+ @pattern ||= "datadog.dogstatsd.client.%s:%d|#{COUNTER_TYPE}|##{serialized_tags}"
83
+ end
84
+
87
85
  if Kernel.const_defined?('Process') && Process.respond_to?(:clock_gettime)
88
86
  def now_in_s
89
87
  Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
@@ -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
21
  @port = port || ENV.fetch('DD_DOGSTATSD_PORT', DEFAULT_PORT).to_i
21
- @logger = logger
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
@@ -4,6 +4,6 @@ require_relative 'connection'
4
4
 
5
5
  module Datadog
6
6
  class Statsd
7
- VERSION = '4.8.0'
7
+ VERSION = '5.0.1'
8
8
  end
9
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.8.0
4
+ version: 5.0.1
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-04-21 00:00:00.000000000 Z
12
+ date: 2021-04-09 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,8 +22,10 @@ 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
26
29
  - lib/datadog/statsd/serialization.rb
27
30
  - lib/datadog/statsd/serialization/event_serializer.rb
28
31
  - lib/datadog/statsd/serialization/serializer.rb
@@ -38,10 +41,10 @@ licenses:
38
41
  - MIT
39
42
  metadata:
40
43
  bug_tracker_uri: https://github.com/DataDog/dogstatsd-ruby/issues
41
- changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v4.8.0/CHANGELOG.md
42
- documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/4.8.0
43
- source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v4.8.0
44
- post_install_message:
44
+ changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v5.0.1/CHANGELOG.md
45
+ documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/5.0.1
46
+ source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v5.0.1
47
+ post_install_message:
45
48
  rdoc_options: []
46
49
  require_paths:
47
50
  - lib
@@ -56,9 +59,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
59
  - !ruby/object:Gem::Version
57
60
  version: '0'
58
61
  requirements: []
59
- rubyforge_project:
62
+ rubyforge_project:
60
63
  rubygems_version: 2.7.10
61
- signing_key:
64
+ signing_key:
62
65
  specification_version: 4
63
66
  summary: A Ruby DogStatsd client
64
67
  test_files: []
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Datadog
4
- class Statsd
5
- class Batch
6
- def initialize(connection, max_buffer_bytes)
7
- @connection = connection
8
- @max_buffer_bytes = max_buffer_bytes
9
- @depth = 0
10
- reset
11
- end
12
-
13
- def open
14
- @depth += 1
15
-
16
- yield
17
- ensure
18
- @depth -= 1
19
- flush if !open?
20
- end
21
-
22
- def open?
23
- @depth > 0
24
- end
25
-
26
- def add(message)
27
- message_bytes = message.bytesize
28
-
29
- unless @buffer_bytes == 0
30
- if @buffer_bytes + 1 + message_bytes >= @max_buffer_bytes
31
- flush
32
- else
33
- @buffer << "\n"
34
- @buffer_bytes += 1
35
- end
36
- end
37
-
38
- @buffer << message
39
- @buffer_bytes += message_bytes
40
- end
41
-
42
- def flush
43
- return if @buffer_bytes == 0
44
- @connection.write(@buffer)
45
- reset
46
- end
47
-
48
- private
49
-
50
- def reset
51
- @buffer = String.new
52
- @buffer_bytes = 0
53
- end
54
- end
55
- end
56
- end