dogstatsd-ruby 5.3.1 → 5.4.0

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: 539ec3d4e02bd8784df4d032b0f8e96ebb2a582b510ff5e27c6e7cfff4515926
4
- data.tar.gz: d8ecf4c6724e491aaea6d738cc255891b55119987b1ca5665087d506552135ee
3
+ metadata.gz: d780ad7a840c021ee3cb31a9e8b512ef7ccff4b82b14be9e300206d3a07cc61e
4
+ data.tar.gz: 2da798fe3a84f313c5c84655136597c562e3dc1bfaa24f8ba6441ef722c7cc45
5
5
  SHA512:
6
- metadata.gz: d615efd205f7858c55b9864e1d001d607dcb39a47a26c87f5e27402b25834cf750fb3904d10d667c63a437587d38ba315d7a3c609b7709bc28a8bc8138d30eef
7
- data.tar.gz: 254bf33695141f521d55d5f5b9c4d6b3abf2408b6b231263949094cbc2c9f531bf3ff2aea92ae0f11d505bbb8d25a789a2c0da76d9a065b713f9dc9b5b9d836d
6
+ metadata.gz: 8f92d7dab8099d571c291e26497020e53087ccdbeecfd84d955778f484112f9b94fb0f4ec7d043b9efabd5d249333168fa44e4fe6e54ba4c9541447b92983ceb
7
+ data.tar.gz: 8dadc086c84a821d4a1b1824a25ea4861bdf0b6a36cfcf8c50a8d06e19d6853c35974dbefb03fedc9ccd7a3f57ff13a19d206c5eef152c1aa5ffbc1f789da1bc
data/README.md CHANGED
@@ -180,6 +180,15 @@ There are three different kinds of messages:
180
180
 
181
181
  There is also an implicit message which closes the queue which will cause the sender thread to finish processing and exit.
182
182
 
183
+
184
+ ```ruby
185
+ statsd = Datadog::Statsd.new('localhost', 8125)
186
+ ```
187
+
188
+ The message queue's maximum size (in messages) is given by the `sender_queue_size` argument, and has appropriate defaults for UDP (2048) and UDS (512).
189
+
190
+ The `buffer_flush_interval`, if enabled, is implemented with an additional thread which manages the timing of those flushes. This additional thread is used even if `single_thread: true`.
191
+
183
192
  ### Usual workflow
184
193
 
185
194
  You push metrics to the statsd client which writes them quickly to the sender message queue. The sender thread receives those message, buffers them and flushes them to the connection when the buffer limit is reached.
@@ -204,7 +213,13 @@ When using the `single_thread: true` mode, instances of `Datadog::Statsd` are st
204
213
 
205
214
  ## Versioning
206
215
 
207
- This Ruby gem is using [Semantic Versioning](https://guides.rubygems.org/patterns/#semantic-versioning) but please note that supported Ruby versions can change in a minor release of this library. As much as possible, we will add a "future deprecation" message in the minor release preceding the one dropping the support.
216
+ This Ruby gem is using [Semantic Versioning](https://guides.rubygems.org/patterns/#semantic-versioning) but please note that supported Ruby versions can change in a minor release of this library.
217
+ As much as possible, we will add a "future deprecation" message in the minor release preceding the one dropping the support.
218
+
219
+ ## Ruby Versions
220
+
221
+ This gem supports and is tested on Ruby minor versions 2.1 through 3.1.
222
+ Support for Ruby 2.0 was dropped in version 5.4.0.
208
223
 
209
224
  ## Credits
210
225
 
@@ -38,7 +38,7 @@ module Datadog
38
38
  end
39
39
  end
40
40
 
41
- telemetry.dropped(packets: 1, bytes: payload.length) if telemetry
41
+ telemetry.dropped_writer(packets: 1, bytes: payload.length) if telemetry
42
42
  logger.error { "Statsd: #{boom.class} #{boom}" } if logger
43
43
  nil
44
44
  end
@@ -0,0 +1,76 @@
1
+ module Datadog
2
+ class Statsd
3
+ class ConnectionCfg
4
+ attr_reader :host
5
+ attr_reader :port
6
+ attr_reader :socket_path
7
+ attr_reader :transport_type
8
+
9
+ def initialize(host: nil, port: nil, socket_path: nil)
10
+ initialize_with_constructor_args(host: host, port: port, socket_path: socket_path) ||
11
+ initialize_with_env_vars ||
12
+ initialize_with_defaults
13
+ end
14
+
15
+ def make_connection(**params)
16
+ case @transport_type
17
+ when :udp
18
+ UDPConnection.new(@host, @port, **params)
19
+ when :uds
20
+ UDSConnection.new(@socket_path, **params)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ DEFAULT_HOST = '127.0.0.1'
27
+ DEFAULT_PORT = 8125
28
+
29
+ def initialize_with_constructor_args(host: nil, port: nil, socket_path: nil)
30
+ try_initialize_with(host: host, port: port, socket_path: socket_path,
31
+ not_both_error_message:
32
+ "Both UDP: (host/port #{host}:#{port}) and UDS (socket_path #{socket_path}) " +
33
+ "constructor arguments were given. Use only one or the other.",
34
+ )
35
+ end
36
+
37
+ def initialize_with_env_vars()
38
+ try_initialize_with(
39
+ host: ENV['DD_AGENT_HOST'],
40
+ port: ENV['DD_DOGSTATSD_PORT'] && ENV['DD_DOGSTATSD_PORT'].to_i,
41
+ socket_path: ENV['DD_DOGSTATSD_SOCKET'],
42
+ not_both_error_message:
43
+ "Both UDP (DD_AGENT_HOST/DD_DOGSTATSD_PORT #{ENV['DD_AGENT_HOST']}:#{ENV['DD_DOGSTATSD_PORT']}) " +
44
+ "and UDS (DD_DOGSTATSD_SOCKET #{ENV['DD_DOGSTATSD_SOCKET']}) environment variables are set. " +
45
+ "Set only one or the other.",
46
+ )
47
+ end
48
+
49
+ def initialize_with_defaults()
50
+ try_initialize_with(host: DEFAULT_HOST, port: DEFAULT_PORT)
51
+ end
52
+
53
+ def try_initialize_with(host: nil, port: nil, socket_path: nil, not_both_error_message: "")
54
+ if (host || port) && socket_path
55
+ raise ArgumentError, not_both_error_message
56
+ end
57
+
58
+ if host || port
59
+ @host = host || DEFAULT_HOST
60
+ @port = port || DEFAULT_PORT
61
+ @socket_path = nil
62
+ @transport_type = :udp
63
+ return true
64
+ elsif socket_path
65
+ @host = nil
66
+ @port = nil
67
+ @socket_path = socket_path
68
+ @transport_type = :uds
69
+ return true
70
+ end
71
+
72
+ return false
73
+ end
74
+ end
75
+ end
76
+ end
@@ -7,13 +7,14 @@ module Datadog
7
7
  attr_reader :transport_type
8
8
 
9
9
  def initialize(
10
- host: nil,
11
- port: nil,
12
- socket_path: nil,
10
+ connection_cfg: nil,
13
11
 
14
12
  buffer_max_payload_size: nil,
15
13
  buffer_max_pool_size: nil,
16
14
  buffer_overflowing_stategy: :drop,
15
+ buffer_flush_interval: nil,
16
+
17
+ sender_queue_size: nil,
17
18
 
18
19
  telemetry_flush_interval: nil,
19
20
  global_tags: [],
@@ -22,24 +23,20 @@ module Datadog
22
23
 
23
24
  logger: nil
24
25
  )
25
- @transport_type = socket_path.nil? ? :udp : :uds
26
+ @transport_type = connection_cfg.transport_type
26
27
 
27
28
  if telemetry_flush_interval
28
29
  @telemetry = Telemetry.new(telemetry_flush_interval,
29
30
  global_tags: global_tags,
30
- transport_type: transport_type
31
+ transport_type: @transport_type
31
32
  )
32
33
  end
33
34
 
34
- @connection = case transport_type
35
- when :udp
36
- UDPConnection.new(host, port, logger: logger, telemetry: telemetry)
37
- when :uds
38
- UDSConnection.new(socket_path, logger: logger, telemetry: telemetry)
39
- end
35
+ @connection = connection_cfg.make_connection(logger: logger, telemetry: telemetry)
40
36
 
41
37
  # Initialize buffer
42
- buffer_max_payload_size ||= (transport_type == :udp ? UDP_DEFAULT_BUFFER_SIZE : UDS_DEFAULT_BUFFER_SIZE)
38
+ buffer_max_payload_size ||= (@transport_type == :udp ?
39
+ UDP_DEFAULT_BUFFER_SIZE : UDS_DEFAULT_BUFFER_SIZE)
43
40
 
44
41
  if buffer_max_payload_size <= 0
45
42
  raise ArgumentError, 'buffer_max_payload_size cannot be <= 0'
@@ -54,7 +51,21 @@ module Datadog
54
51
  max_pool_size: buffer_max_pool_size || DEFAULT_BUFFER_POOL_SIZE,
55
52
  overflowing_stategy: buffer_overflowing_stategy,
56
53
  )
57
- @sender = (single_thread ? SingleThreadSender : Sender).new(buffer, logger: logger)
54
+
55
+ sender_queue_size ||= (@transport_type == :udp ?
56
+ UDP_DEFAULT_SENDER_QUEUE_SIZE : UDS_DEFAULT_SENDER_QUEUE_SIZE)
57
+
58
+ @sender = single_thread ?
59
+ SingleThreadSender.new(
60
+ buffer,
61
+ logger: logger,
62
+ flush_interval: buffer_flush_interval) :
63
+ Sender.new(
64
+ buffer,
65
+ logger: logger,
66
+ flush_interval: buffer_flush_interval,
67
+ telemetry: @telemetry,
68
+ queue_size: sender_queue_size)
58
69
  @sender.start
59
70
  end
60
71
 
@@ -12,10 +12,17 @@ module Datadog
12
12
  class Sender
13
13
  CLOSEABLE_QUEUES = Queue.instance_methods.include?(:close)
14
14
 
15
- def initialize(message_buffer, logger: nil)
15
+ def initialize(message_buffer, telemetry: nil, queue_size: UDP_DEFAULT_BUFFER_SIZE, logger: nil, flush_interval: nil, queue_class: Queue, thread_class: Thread)
16
16
  @message_buffer = message_buffer
17
+ @telemetry = telemetry
18
+ @queue_size = queue_size
17
19
  @logger = logger
18
20
  @mx = Mutex.new
21
+ @queue_class = queue_class
22
+ @thread_class = thread_class
23
+ if flush_interval
24
+ @flush_timer = Datadog::Statsd::Timer.new(flush_interval) { flush(sync: true) }
25
+ end
19
26
  end
20
27
 
21
28
  def flush(sync: false)
@@ -42,7 +49,7 @@ module Datadog
42
49
  return unless message_queue
43
50
 
44
51
  # Initialize and get the thread's sync queue
45
- queue = (Thread.current[:statsd_sync_queue] ||= Queue.new)
52
+ queue = (@thread_class.current[:statsd_sync_queue] ||= @queue_class.new)
46
53
  # tell sender-thread to notify us in the current
47
54
  # thread's queue
48
55
  message_queue.push(queue)
@@ -68,19 +75,26 @@ module Datadog
68
75
  @message_queue = nil
69
76
  message_buffer.reset
70
77
  start
78
+ @flush_timer.start if @flush_timer && @flush_timer.stop?
71
79
  }
72
80
  end
73
81
 
74
- message_queue << message
82
+ if message_queue.length <= @queue_size
83
+ message_queue << message
84
+ else
85
+ @telemetry.dropped_queue(packets: 1, bytes: message.bytesize) if @telemetry
86
+ end
75
87
  end
76
88
 
77
89
  def start
78
90
  raise ArgumentError, 'Sender already started' if message_queue
79
91
 
80
92
  # initialize a new message queue for the background thread
81
- @message_queue = Queue.new
93
+ @message_queue = @queue_class.new
82
94
  # start background thread
83
- @sender_thread = Thread.new(&method(:send_loop))
95
+ @sender_thread = @thread_class.new(&method(:send_loop))
96
+ @sender_thread.name = "Statsd Sender" unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
97
+ @flush_timer.start if @flush_timer
84
98
  end
85
99
 
86
100
  if CLOSEABLE_QUEUES
@@ -91,6 +105,7 @@ module Datadog
91
105
  message_queue = @message_queue
92
106
  message_queue.close if message_queue
93
107
 
108
+ @flush_timer.stop if @flush_timer
94
109
  sender_thread = @sender_thread
95
110
  sender_thread.join if sender_thread && join_worker
96
111
  end
@@ -102,6 +117,7 @@ module Datadog
102
117
  message_queue = @message_queue
103
118
  message_queue << :close if message_queue
104
119
 
120
+ @flush_timer.stop if @flush_timer
105
121
  sender_thread = @sender_thread
106
122
  sender_thread.join if sender_thread && join_worker
107
123
  end
@@ -123,7 +139,7 @@ module Datadog
123
139
  case message
124
140
  when :flush
125
141
  message_buffer.flush
126
- when Queue
142
+ when @queue_class
127
143
  message.push(:go_on)
128
144
  else
129
145
  message_buffer.add(message)
@@ -145,7 +161,7 @@ module Datadog
145
161
  break
146
162
  when :flush
147
163
  message_buffer.flush
148
- when Queue
164
+ when @queue_class
149
165
  message.push(:go_on)
150
166
  else
151
167
  message_buffer.add(message)
@@ -7,10 +7,13 @@ module Datadog
7
7
  # It is using current Process.PID to check it is the result of a recent fork
8
8
  # and it is reseting the MessageBuffer if that's the case.
9
9
  class SingleThreadSender
10
- def initialize(message_buffer, logger: nil)
10
+ def initialize(message_buffer, logger: nil, flush_interval: nil)
11
11
  @message_buffer = message_buffer
12
12
  @logger = logger
13
13
  @mx = Mutex.new
14
+ if flush_interval
15
+ @flush_timer = Datadog::Statsd::Timer.new(flush_interval) { flush }
16
+ end
14
17
  # store the pid for which this sender has been created
15
18
  update_fork_pid
16
19
  end
@@ -21,6 +24,7 @@ module Datadog
21
24
  # not send, they belong to the parent process, let's clear the buffer.
22
25
  if forked?
23
26
  @message_buffer.reset
27
+ @flush_timer.start if @flush_timer && @flush_timer.stop?
24
28
  update_fork_pid
25
29
  end
26
30
  @message_buffer.add(message)
@@ -33,12 +37,12 @@ module Datadog
33
37
  }
34
38
  end
35
39
 
36
- # Compatibility with `Sender`
37
40
  def start()
41
+ @flush_timer.start if @flush_timer
38
42
  end
39
43
 
40
- # Compatibility with `Sender`
41
44
  def stop()
45
+ @flush_timer.stop if @flush_timer
42
46
  end
43
47
 
44
48
  # Compatibility with `Sender`
@@ -9,8 +9,12 @@ module Datadog
9
9
  attr_reader :service_checks
10
10
  attr_reader :bytes_sent
11
11
  attr_reader :bytes_dropped
12
+ attr_reader :bytes_dropped_queue
13
+ attr_reader :bytes_dropped_writer
12
14
  attr_reader :packets_sent
13
15
  attr_reader :packets_dropped
16
+ attr_reader :packets_dropped_queue
17
+ attr_reader :packets_dropped_writer
14
18
 
15
19
  # Rough estimation of maximum telemetry message size without tags
16
20
  MAX_TELEMETRY_MESSAGE_SIZE_WT_TAGS = 50 # bytes
@@ -40,8 +44,12 @@ module Datadog
40
44
  @service_checks = 0
41
45
  @bytes_sent = 0
42
46
  @bytes_dropped = 0
47
+ @bytes_dropped_queue = 0
48
+ @bytes_dropped_writer = 0
43
49
  @packets_sent = 0
44
50
  @packets_dropped = 0
51
+ @packets_dropped_queue = 0
52
+ @packets_dropped_writer = 0
45
53
  @next_flush_time = now_in_s + @flush_interval
46
54
  end
47
55
 
@@ -54,9 +62,18 @@ module Datadog
54
62
  @packets_sent += packets
55
63
  end
56
64
 
57
- def dropped(bytes: 0, packets: 0)
65
+ def dropped_queue(bytes: 0, packets: 0)
58
66
  @bytes_dropped += bytes
67
+ @bytes_dropped_queue += bytes
59
68
  @packets_dropped += packets
69
+ @packets_dropped_queue += packets
70
+ end
71
+
72
+ def dropped_writer(bytes: 0, packets: 0)
73
+ @bytes_dropped += bytes
74
+ @bytes_dropped_writer += bytes
75
+ @packets_dropped += packets
76
+ @packets_dropped_writer += packets
60
77
  end
61
78
 
62
79
  def should_flush?
@@ -70,8 +87,12 @@ module Datadog
70
87
  sprintf(pattern, 'service_checks', @service_checks),
71
88
  sprintf(pattern, 'bytes_sent', @bytes_sent),
72
89
  sprintf(pattern, 'bytes_dropped', @bytes_dropped),
90
+ sprintf(pattern, 'bytes_dropped_queue', @bytes_dropped_queue),
91
+ sprintf(pattern, 'bytes_dropped_writer', @bytes_dropped_writer),
73
92
  sprintf(pattern, 'packets_sent', @packets_sent),
74
93
  sprintf(pattern, 'packets_dropped', @packets_dropped),
94
+ sprintf(pattern, 'packets_dropped_queue', @packets_dropped_queue),
95
+ sprintf(pattern, 'packets_dropped_writer', @packets_dropped_writer),
75
96
  ]
76
97
  end
77
98
 
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ class Timer
6
+ def initialize(interval, &callback)
7
+ @mx = Mutex.new
8
+ @cv = ConditionVariable.new
9
+ @interval = interval
10
+ @callback = callback
11
+ @stop = true
12
+ end
13
+
14
+ def start
15
+ return unless stop?
16
+
17
+ @stop = false
18
+ @thread = Thread.new do
19
+ last_execution_time = current_time
20
+ @mx.synchronize do
21
+ until @stop
22
+ timeout = @interval - (current_time - last_execution_time)
23
+ @cv.wait(@mx, timeout > 0 ? timeout : 0)
24
+ last_execution_time = current_time
25
+ @callback.call
26
+ end
27
+ end
28
+ end
29
+ @thread.name = 'Statsd Timer' unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
30
+ end
31
+
32
+ def stop
33
+ return if @thread.nil?
34
+
35
+ @stop = true
36
+ @mx.synchronize do
37
+ @cv.signal
38
+ end
39
+ @thread.join
40
+ @thread = nil
41
+ end
42
+
43
+ def stop?
44
+ @thread.nil? || @thread.stop?
45
+ end
46
+
47
+ private
48
+
49
+ if Process.const_defined?(:CLOCK_MONOTONIC)
50
+ def current_time
51
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
52
+ end
53
+ else
54
+ def current_time
55
+ Time.now
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -5,20 +5,17 @@ require_relative 'connection'
5
5
  module Datadog
6
6
  class Statsd
7
7
  class UDPConnection < Connection
8
- DEFAULT_HOST = '127.0.0.1'
9
- DEFAULT_PORT = 8125
10
-
11
- # StatsD host. Defaults to 127.0.0.1.
8
+ # StatsD host.
12
9
  attr_reader :host
13
10
 
14
- # StatsD port. Defaults to 8125.
11
+ # StatsD port.
15
12
  attr_reader :port
16
13
 
17
14
  def initialize(host, port, **kwargs)
18
15
  super(**kwargs)
19
16
 
20
- @host = host || ENV.fetch('DD_AGENT_HOST', DEFAULT_HOST)
21
- @port = port || ENV.fetch('DD_DOGSTATSD_PORT', DEFAULT_PORT).to_i
17
+ @host = host
18
+ @port = port
22
19
  @socket = nil
23
20
  end
24
21
 
@@ -4,6 +4,6 @@ require_relative 'connection'
4
4
 
5
5
  module Datadog
6
6
  class Statsd
7
- VERSION = '5.3.1'
7
+ VERSION = '5.4.0'
8
8
  end
9
9
  end
@@ -5,14 +5,13 @@ 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/connection_cfg'
8
9
  require_relative 'statsd/message_buffer'
9
10
  require_relative 'statsd/serialization'
10
11
  require_relative 'statsd/sender'
11
12
  require_relative 'statsd/single_thread_sender'
12
13
  require_relative 'statsd/forwarder'
13
-
14
- $deprecation_message_mutex = Mutex.new
15
- $deprecation_message_done = false
14
+ require_relative 'statsd/timer'
16
15
 
17
16
  # = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
18
17
  #
@@ -43,7 +42,12 @@ module Datadog
43
42
  UDP_DEFAULT_BUFFER_SIZE = 1_432
44
43
  UDS_DEFAULT_BUFFER_SIZE = 8_192
45
44
  DEFAULT_BUFFER_POOL_SIZE = Float::INFINITY
45
+
46
+ UDP_DEFAULT_SENDER_QUEUE_SIZE = 2048
47
+ UDS_DEFAULT_SENDER_QUEUE_SIZE = 512
48
+
46
49
  MAX_EVENT_SIZE = 8 * 1_024
50
+
47
51
  # minimum flush interval for the telemetry in seconds
48
52
  DEFAULT_TELEMETRY_FLUSH_INTERVAL = 10
49
53
 
@@ -72,6 +76,8 @@ module Datadog
72
76
  # @option [Logger] logger for debugging
73
77
  # @option [Integer] buffer_max_payload_size max bytes to buffer
74
78
  # @option [Integer] buffer_max_pool_size max messages to buffer
79
+ # @option [Integer] sender_queue_size size of the sender queue in number of buffers (multi-thread only)
80
+ # @option [Numeric] buffer_flush_interval interval in second to flush buffer
75
81
  # @option [String] socket_path unix socket path
76
82
  # @option [Float] default sample rate if not overridden
77
83
  # @option [Boolean] single_thread flushes the metrics on the main thread instead of in a companion thread
@@ -87,6 +93,9 @@ module Datadog
87
93
  buffer_max_payload_size: nil,
88
94
  buffer_max_pool_size: nil,
89
95
  buffer_overflowing_stategy: :drop,
96
+ buffer_flush_interval: nil,
97
+
98
+ sender_queue_size: nil,
90
99
 
91
100
  logger: nil,
92
101
 
@@ -104,23 +113,12 @@ module Datadog
104
113
  @serializer = Serialization::Serializer.new(prefix: @prefix, global_tags: tags)
105
114
  @sample_rate = sample_rate
106
115
 
107
- # deprecation message for ruby < 2.1.0 users as we will drop support for ruby 2.0
108
- # in dogstatsd-ruby 5.4.0
109
- # TODO(remy): remove this message and the two global vars used in dogstatd-ruby 5.4.0
110
- if RUBY_VERSION < '2.1.0' && $deprecation_message_mutex.try_lock && !$deprecation_message_done
111
- if logger != nil
112
- logger.warn { "deprecation: dogstatsd-ruby will drop support of Ruby < 2.1.0 in a next minor release" }
113
- else
114
- puts("warning: deprecation: dogstatsd-ruby will drop support of Ruby < 2.1.0 in a next minor release")
115
- end
116
- $deprecation_message_done = true
117
- $deprecation_message_mutex.unlock
118
- end
119
-
120
116
  @forwarder = Forwarder.new(
121
- host: host,
122
- port: port,
123
- socket_path: socket_path,
117
+ connection_cfg: ConnectionCfg.new(
118
+ host: host,
119
+ port: port,
120
+ socket_path: socket_path,
121
+ ),
124
122
 
125
123
  global_tags: tags,
126
124
  logger: logger,
@@ -130,6 +128,9 @@ module Datadog
130
128
  buffer_max_payload_size: buffer_max_payload_size,
131
129
  buffer_max_pool_size: buffer_max_pool_size,
132
130
  buffer_overflowing_stategy: buffer_overflowing_stategy,
131
+ buffer_flush_interval: buffer_flush_interval,
132
+
133
+ sender_queue_size: sender_queue_size,
133
134
 
134
135
  telemetry_flush_interval: telemetry_enable ? telemetry_flush_interval : nil,
135
136
  )
@@ -137,12 +138,13 @@ module Datadog
137
138
 
138
139
  # yield a new instance to a block and close it when done
139
140
  # for short-term use-cases that don't want to close the socket manually
140
- def self.open(*args)
141
- instance = new(*args)
141
+ # TODO: replace with ... once we are on ruby 2.7
142
+ def self.open(*args, **kwargs)
143
+ instance = new(*args, **kwargs)
142
144
 
143
145
  yield instance
144
146
  ensure
145
- instance.close
147
+ instance.close if instance
146
148
  end
147
149
 
148
150
  # Sends an increment (count = 1) for the given stat to the statsd server.
@@ -150,6 +152,7 @@ module Datadog
150
152
  # @param [String] stat stat name
151
153
  # @param [Hash] opts the options to create the metric with
152
154
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
155
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
153
156
  # @option opts [Array<String>] :tags An array of tags
154
157
  # @option opts [Numeric] :by increment value, default 1
155
158
  # @see #count
@@ -164,6 +167,7 @@ module Datadog
164
167
  # @param [String] stat stat name
165
168
  # @param [Hash] opts the options to create the metric with
166
169
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
170
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
167
171
  # @option opts [Array<String>] :tags An array of tags
168
172
  # @option opts [Numeric] :by decrement value, default 1
169
173
  # @see #count
@@ -179,13 +183,14 @@ module Datadog
179
183
  # @param [Integer] count count
180
184
  # @param [Hash] opts the options to create the metric with
181
185
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
186
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
182
187
  # @option opts [Array<String>] :tags An array of tags
183
188
  def count(stat, count, opts = EMPTY_OPTIONS)
184
189
  opts = { sample_rate: opts } if opts.is_a?(Numeric)
185
190
  send_stats(stat, count, COUNTER_TYPE, opts)
186
191
  end
187
192
 
188
- # Sends an arbitary gauge value for the given stat to the statsd server.
193
+ # Sends an arbitrary gauge value for the given stat to the statsd server.
189
194
  #
190
195
  # This is useful for recording things like available disk space,
191
196
  # memory usage, and the like, which have different semantics than
@@ -195,6 +200,7 @@ module Datadog
195
200
  # @param [Numeric] value gauge value.
196
201
  # @param [Hash] opts the options to create the metric with
197
202
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
203
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
198
204
  # @option opts [Array<String>] :tags An array of tags
199
205
  # @example Report the current user count:
200
206
  # $statsd.gauge('user.count', User.count)
@@ -209,6 +215,7 @@ module Datadog
209
215
  # @param [Numeric] value histogram value.
210
216
  # @param [Hash] opts the options to create the metric with
211
217
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
218
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
212
219
  # @option opts [Array<String>] :tags An array of tags
213
220
  # @example Report the current user count:
214
221
  # $statsd.histogram('user.count', User.count)
@@ -222,6 +229,7 @@ module Datadog
222
229
  # @param [Numeric] value distribution value.
223
230
  # @param [Hash] opts the options to create the metric with
224
231
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
232
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
225
233
  # @option opts [Array<String>] :tags An array of tags
226
234
  # @example Report the current user count:
227
235
  # $statsd.distribution('user.count', User.count)
@@ -238,6 +246,7 @@ module Datadog
238
246
  # @param [Integer] ms timing in milliseconds
239
247
  # @param [Hash] opts the options to create the metric with
240
248
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
249
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
241
250
  # @option opts [Array<String>] :tags An array of tags
242
251
  def timing(stat, ms, opts = EMPTY_OPTIONS)
243
252
  opts = { sample_rate: opts } if opts.is_a?(Numeric)
@@ -252,6 +261,7 @@ module Datadog
252
261
  # @param [String] stat stat name
253
262
  # @param [Hash] opts the options to create the metric with
254
263
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
264
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
255
265
  # @option opts [Array<String>] :tags An array of tags
256
266
  # @yield The operation to be timed
257
267
  # @see #timing
@@ -271,6 +281,7 @@ module Datadog
271
281
  # @param [Numeric] value set value.
272
282
  # @param [Hash] opts the options to create the metric with
273
283
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
284
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
274
285
  # @option opts [Array<String>] :tags An array of tags
275
286
  # @example Record a unique visitory by id:
276
287
  # $statsd.set('visitors.uniques', User.id)
@@ -382,17 +393,10 @@ module Datadog
382
393
  attr_reader :serializer
383
394
  attr_reader :forwarder
384
395
 
385
- PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= '2.1.0')
386
396
  EMPTY_OPTIONS = {}.freeze
387
397
 
388
- if PROCESS_TIME_SUPPORTED
389
- def now
390
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
391
- end
392
- else
393
- def now
394
- Time.now.to_f
395
- end
398
+ def now
399
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
396
400
  end
397
401
 
398
402
  def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
@@ -400,7 +404,7 @@ module Datadog
400
404
 
401
405
  sample_rate = opts[:sample_rate] || @sample_rate || 1
402
406
 
403
- if sample_rate == 1 || rand <= sample_rate
407
+ if sample_rate == 1 || opts[:pre_sampled] || rand <= sample_rate
404
408
  full_stat = serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
405
409
 
406
410
  forwarder.send_message(full_stat)
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dogstatsd-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.3.1
4
+ version: 5.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rein Henrichs
8
8
  - Karim Bogtob
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-10-21 00:00:00.000000000 Z
12
+ date: 2022-03-01 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: A Ruby DogStatsd client
15
15
  email: code@datadoghq.com
@@ -23,6 +23,7 @@ files:
23
23
  - README.md
24
24
  - lib/datadog/statsd.rb
25
25
  - lib/datadog/statsd/connection.rb
26
+ - lib/datadog/statsd/connection_cfg.rb
26
27
  - lib/datadog/statsd/forwarder.rb
27
28
  - lib/datadog/statsd/message_buffer.rb
28
29
  - lib/datadog/statsd/sender.rb
@@ -34,7 +35,7 @@ files:
34
35
  - lib/datadog/statsd/serialization/tag_serializer.rb
35
36
  - lib/datadog/statsd/single_thread_sender.rb
36
37
  - lib/datadog/statsd/telemetry.rb
37
- - lib/datadog/statsd/threaded_sender.rb
38
+ - lib/datadog/statsd/timer.rb
38
39
  - lib/datadog/statsd/udp_connection.rb
39
40
  - lib/datadog/statsd/uds_connection.rb
40
41
  - lib/datadog/statsd/version.rb
@@ -43,10 +44,14 @@ licenses:
43
44
  - MIT
44
45
  metadata:
45
46
  bug_tracker_uri: https://github.com/DataDog/dogstatsd-ruby/issues
46
- changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v5.3.1/CHANGELOG.md
47
- documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/5.3.1
48
- source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v5.3.1
49
- post_install_message:
47
+ changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v5.4.0/CHANGELOG.md
48
+ documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/5.4.0
49
+ source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v5.4.0
50
+ post_install_message: |2+
51
+
52
+ If you are upgrading from v4.x of the dogstatsd-ruby library, note the major change to the threading model:
53
+ https://github.com/DataDog/dogstatsd-ruby#migrating-from-v4x-to-v5x
54
+
50
55
  rdoc_options: []
51
56
  require_paths:
52
57
  - lib
@@ -54,16 +59,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
54
59
  requirements:
55
60
  - - ">="
56
61
  - !ruby/object:Gem::Version
57
- version: 2.0.0
62
+ version: 2.1.0
58
63
  required_rubygems_version: !ruby/object:Gem::Requirement
59
64
  requirements:
60
65
  - - ">="
61
66
  - !ruby/object:Gem::Version
62
67
  version: '0'
63
68
  requirements: []
64
- rubyforge_project:
65
- rubygems_version: 2.7.10
66
- signing_key:
69
+ rubygems_version: 3.2.22
70
+ signing_key:
67
71
  specification_version: 4
68
72
  summary: A Ruby DogStatsd client
69
73
  test_files: []
@@ -1,132 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Datadog
4
- class FlushQueue < Queue
5
- end
6
- class CloseQueue < Queue
7
- end
8
- class Statsd
9
- # Sender is using a background thread to flush and pack messages
10
- # in a `MessageBuffer`.
11
- # The communication with this thread is done using a `Queue`.
12
- # If the thread is dead, it is starting a new one to avoid having a blocked
13
- # Sender with no background thread to communicate with (most of the time,
14
- # having a dead background thread means that a fork just happened and that we
15
- # are running in the child process).
16
- class Sender
17
- CLOSEABLE_QUEUES = Queue.instance_methods.include?(:close)
18
-
19
- def initialize(message_buffer, logger: nil)
20
- @message_buffer = message_buffer
21
- @logger = logger
22
-
23
- # communication and synchronization with the background thread
24
- # @mux is also used to not having multiple threads fighting for
25
- # closing the Sender or creating a new background thread
26
- @channel = Queue.new
27
- @mux = Mutex.new
28
-
29
- @is_closed = false
30
-
31
- # start background thread immediately
32
- @sender_thread = Thread.new(&method(:send_loop))
33
- end
34
-
35
- def flush(sync: false)
36
- @mux.synchronize {
37
- # we don't want to send a flush action to the bg thread if:
38
- # - there is no bg thread running
39
- # - the sender has been closed
40
- return if !sender_thread.alive? || @is_closed
41
-
42
- if sync
43
- # blocking flush
44
- blocking_queue = FlushQueue.new
45
- channel << blocking_queue
46
- blocking_queue.pop # wait for the bg thread to finish its work
47
- blocking_queue.close if CLOSEABLE_QUEUES
48
- else
49
- # asynchronous flush
50
- channel << :flush
51
- end
52
- }
53
- end
54
-
55
- def add(message)
56
- return if @is_closed # don't send a message to the bg thread if the sender has been closed
57
-
58
- # the bg thread is not running anymore, this is happening if the main process has forked and
59
- # we are running in the child, we will spawn a bg thread and reset buffers (containing parents' messages)
60
- if !sender_thread.alive?
61
- @mux.synchronize {
62
- return if @is_closed
63
- # test if a call from another thread has already re-created
64
- # the background thread before this one acquired the lock
65
- break if sender_thread.alive?
66
-
67
- # re-create the channel of communication since we will spawn a new bg thread
68
- channel.close if CLOSEABLE_QUEUES
69
- @channel = Queue.new
70
- message_buffer.reset # don't use messages appended by another fork
71
- @sender_thread = Thread.new(&method(:send_loop))
72
- }
73
- end
74
-
75
- channel << message
76
- end
77
-
78
- # Compatibility with `Sender`
79
- def start()
80
- end
81
-
82
- def stop()
83
- return if @is_closed
84
- # use this lock to both: not having another thread stopping this instance nor
85
- # having a #add call creating a new thread
86
- @mux.synchronize {
87
- @is_closed = true
88
- if sender_thread.alive? # no reasons to stop the bg thread is none is running already
89
- blocking_queue = CloseQueue.new
90
- channel << blocking_queue
91
- blocking_queue.pop # wait for the bg thread to finish its work
92
- blocking_queue.close if CLOSEABLE_QUEUES
93
- sender_thread.join(3) # wait for completion, timeout after 3 seconds
94
- # TODO(remy): should I close `channel` here?
95
- end
96
- }
97
- end
98
-
99
- private
100
-
101
- attr_reader :message_buffer
102
- attr_reader :channel
103
- attr_reader :mux
104
- attr_reader :sender_thread
105
-
106
- def send_loop
107
- until (message = channel.pop).nil? && (CLOSEABLE_QUEUES && channel.closed?)
108
- # skip if message is nil, e.g. when the channel is empty and closed
109
- next unless message
110
-
111
- case message
112
- # if a FlushQueue is received, the background thread has to flush the message
113
- # buffer and to send an :unblock to let the caller know that it has finished
114
- when FlushQueue
115
- message_buffer.flush
116
- message << :unblock
117
- # if a :flush is received, the background thread has to flush asynchronously
118
- when :flush
119
- message_buffer.flush
120
- # if a CloseQueue is received, the background thread has to do a last flush
121
- # and to send an :unblock to let the caller know that it has finished
122
- when CloseQueue
123
- message << :unblock
124
- return
125
- else
126
- message_buffer.add(message)
127
- end
128
- end
129
- end
130
- end
131
- end
132
- end