dogstatsd-ruby 5.4.0 → 5.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d780ad7a840c021ee3cb31a9e8b512ef7ccff4b82b14be9e300206d3a07cc61e
4
- data.tar.gz: 2da798fe3a84f313c5c84655136597c562e3dc1bfaa24f8ba6441ef722c7cc45
3
+ metadata.gz: 20363c18876c0641ab23a8cee8d28c22de3e4210420e398d639d6a7b384da168
4
+ data.tar.gz: 851d63e75192dd81451f7a94abba7efe5c81e9c4afdc2dce8102a76e44a10d9a
5
5
  SHA512:
6
- metadata.gz: 8f92d7dab8099d571c291e26497020e53087ccdbeecfd84d955778f484112f9b94fb0f4ec7d043b9efabd5d249333168fa44e4fe6e54ba4c9541447b92983ceb
7
- data.tar.gz: 8dadc086c84a821d4a1b1824a25ea4861bdf0b6a36cfcf8c50a8d06e19d6853c35974dbefb03fedc9ccd7a3f57ff13a19d206c5eef152c1aa5ffbc1f789da1bc
6
+ metadata.gz: 72f565700a7ddf32ca56a686617e840c53ad167770be812a66ad6af5d1641c96f89796fe147c1565c38c7d83be14fc638e0f8ce76a00e62e980f43a510826d18
7
+ data.tar.gz: 659bbd4b5b669a11bf62f723387d0263d76a243bda5c6b5de101700b20ad2991d6fd6535832857acdd15b0f809faff80cb1e9f459e1ddacb3262a6be27c4785f
data/README.md CHANGED
@@ -81,8 +81,6 @@ Version v5.x of `dogstatsd-ruby` is using a sender thread for flushing. This pro
81
81
 
82
82
  If you are using [Sidekiq](https://github.com/mperham/sidekiq), please make sure to close the client instances that are instantiated. [See this example on using DogStatsD-ruby v5.x with Sidekiq](https://github.com/DataDog/dogstatsd-ruby/blob/master/examples/sidekiq_example.rb).
83
83
 
84
- If you are using [Puma](https://github.com/puma/puma) or [Unicorn](https://yhbt.net/unicorn.git), please make sure to create the instance of DogStatsD in the workers, not in the main process before it forks to create its workers. See [this comment for more details](https://github.com/DataDog/dogstatsd-ruby/issues/179#issuecomment-845570345).
85
-
86
84
  Applications that run into issues but can't apply these recommendations should use the `single_thread` mode which disables the use of the sender thread.
87
85
  Here is how to instantiate a client in this mode:
88
86
 
@@ -185,7 +183,7 @@ There is also an implicit message which closes the queue which will cause the se
185
183
  statsd = Datadog::Statsd.new('localhost', 8125)
186
184
  ```
187
185
 
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).
186
+ The message queue's maximum size (in messages) is given by the `sender_queue_size` argument, and has appropriate defaults for UDP (2048), UDS (512) and `single_thread: true` (1).
189
187
 
190
188
  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
189
 
@@ -211,6 +209,16 @@ By default, instances of `Datadog::Statsd` are thread-safe and we recommend that
211
209
 
212
210
  When using the `single_thread: true` mode, instances of `Datadog::Statsd` are still thread-safe, but you may run into contention on heavily-threaded applications, so we don’t recommend (for performance reasons) reusing these instances.
213
211
 
212
+ ### Delaying serialization
213
+
214
+ By default, message serialization happens synchronously whenever stat methods such as `#increment` gets called, blocking the caller. If the blocking is impacting your program's performance, you may want to consider the `delay_serialization: true` mode.
215
+
216
+ The `delay_serialization: true` mode delays the serialization of metrics to avoid the wait when submitting metrics. Serialization will still have to happen at some point, but it might be postponed until a more convenient time, such as after an HTTP request has completed.
217
+
218
+ In `single_thread: true` mode, you'll probably want to set `sender_queue_size:` from it's default of `1` to some greater value, so that it can benefit from `delay_serialization: true`. Messages will then be queued unserialized in the sender queue and processed normally whenever `sender_queue_size` is reached or `#flush` is called. You might set `sender_queue_size: Float::INFINITY` to allow for an unbounded queue that will only be processed on explicit `#flush`.
219
+
220
+ In `single_thread: false` mode, `delay_serialization: true`, will cause serialization to happen inside the sender thread.
221
+
214
222
  ## Versioning
215
223
 
216
224
  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.
@@ -9,7 +9,7 @@ module Datadog
9
9
  end
10
10
 
11
11
  def reset_telemetry
12
- telemetry.reset
12
+ telemetry.reset if telemetry
13
13
  end
14
14
 
15
15
  # not thread safe: `Sender` instances that use this are required to properly synchronize or sequence calls to this method
@@ -23,12 +23,25 @@ module Datadog
23
23
 
24
24
  private
25
25
 
26
+ ERROR_MESSAGE = "Valid environment variables combination for connection configuration:\n" +
27
+ " - DD_DOGSTATSD_URL for UDP or UDS connection.\n" +
28
+ " Example for UDP: DD_DOGSTATSD_URL='udp://localhost:8125'\n" +
29
+ " Example for UDS: DD_DOGSTATSD_URL='unix:///path/to/unix.sock'\n" +
30
+ " or\n" +
31
+ " - DD_AGENT_HOST and DD_DOGSTATSD_PORT for an UDP connection. E.g. DD_AGENT_HOST='localhost' DD_DOGSTATSD_PORT=8125\n" +
32
+ " or\n" +
33
+ " - DD_DOGSTATSD_SOCKET for an UDS connection: E.g. DD_DOGSTATSD_SOCKET='/path/to/unix.sock'\n" +
34
+ " Note that DD_DOGSTATSD_URL has priority on other environment variables."
35
+
26
36
  DEFAULT_HOST = '127.0.0.1'
27
37
  DEFAULT_PORT = 8125
28
38
 
39
+ UDP_PREFIX = 'udp://'
40
+ UDS_PREFIX = 'unix://'
41
+
29
42
  def initialize_with_constructor_args(host: nil, port: nil, socket_path: nil)
30
43
  try_initialize_with(host: host, port: port, socket_path: socket_path,
31
- not_both_error_message:
44
+ error_message:
32
45
  "Both UDP: (host/port #{host}:#{port}) and UDS (socket_path #{socket_path}) " +
33
46
  "constructor arguments were given. Use only one or the other.",
34
47
  )
@@ -36,13 +49,11 @@ module Datadog
36
49
 
37
50
  def initialize_with_env_vars()
38
51
  try_initialize_with(
52
+ dogstatsd_url: ENV['DD_DOGSTATSD_URL'],
39
53
  host: ENV['DD_AGENT_HOST'],
40
54
  port: ENV['DD_DOGSTATSD_PORT'] && ENV['DD_DOGSTATSD_PORT'].to_i,
41
55
  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.",
56
+ error_message: ERROR_MESSAGE,
46
57
  )
47
58
  end
48
59
 
@@ -50,9 +61,13 @@ module Datadog
50
61
  try_initialize_with(host: DEFAULT_HOST, port: DEFAULT_PORT)
51
62
  end
52
63
 
53
- def try_initialize_with(host: nil, port: nil, socket_path: nil, not_both_error_message: "")
64
+ def try_initialize_with(dogstatsd_url: nil, host: nil, port: nil, socket_path: nil, error_message: ERROR_MESSAGE)
54
65
  if (host || port) && socket_path
55
- raise ArgumentError, not_both_error_message
66
+ raise ArgumentError, error_message
67
+ end
68
+
69
+ if dogstatsd_url
70
+ host, port, socket_path = parse_dogstatsd_url(str: dogstatsd_url.to_s)
56
71
  end
57
72
 
58
73
  if host || port
@@ -71,6 +86,40 @@ module Datadog
71
86
 
72
87
  return false
73
88
  end
89
+
90
+ def parse_dogstatsd_url(str:)
91
+ # udp socket connection
92
+
93
+ if str.start_with?(UDP_PREFIX)
94
+ dogstatsd_url = str[UDP_PREFIX.size..str.size]
95
+ host = nil
96
+ port = nil
97
+
98
+ if dogstatsd_url.include?(":")
99
+ parts = dogstatsd_url.split(":")
100
+ if parts.size > 2
101
+ raise ArgumentError, "Error: DD_DOGSTATSD_URL wrong format for an UDP connection. E.g. 'udp://localhost:8125'"
102
+ end
103
+
104
+ host = parts[0]
105
+ port = parts[1].to_i
106
+ else
107
+ host = dogstatsd_url
108
+ end
109
+
110
+ return host, port, nil
111
+ end
112
+
113
+ # unix socket connection
114
+
115
+ if str.start_with?(UDS_PREFIX)
116
+ return nil, nil, str[UDS_PREFIX.size..str.size]
117
+ end
118
+
119
+ # malformed value
120
+
121
+ raise ArgumentError, "Error: DD_DOGSTATSD_URL has been provided but is not starting with 'udp://' nor 'unix://'"
122
+ end
74
123
  end
75
124
  end
76
125
  end
@@ -21,15 +21,19 @@ module Datadog
21
21
 
22
22
  single_thread: false,
23
23
 
24
- logger: nil
24
+ logger: nil,
25
+
26
+ serializer:
25
27
  )
26
28
  @transport_type = connection_cfg.transport_type
27
29
 
28
- if telemetry_flush_interval
29
- @telemetry = Telemetry.new(telemetry_flush_interval,
30
+ @telemetry = if telemetry_flush_interval
31
+ Telemetry.new(telemetry_flush_interval,
30
32
  global_tags: global_tags,
31
33
  transport_type: @transport_type
32
34
  )
35
+ else
36
+ nil
33
37
  end
34
38
 
35
39
  @connection = connection_cfg.make_connection(logger: logger, telemetry: telemetry)
@@ -50,8 +54,10 @@ module Datadog
50
54
  max_payload_size: buffer_max_payload_size,
51
55
  max_pool_size: buffer_max_pool_size || DEFAULT_BUFFER_POOL_SIZE,
52
56
  overflowing_stategy: buffer_overflowing_stategy,
57
+ serializer: serializer
53
58
  )
54
59
 
60
+ sender_queue_size ||= 1 if single_thread
55
61
  sender_queue_size ||= (@transport_type == :udp ?
56
62
  UDP_DEFAULT_SENDER_QUEUE_SIZE : UDS_DEFAULT_SENDER_QUEUE_SIZE)
57
63
 
@@ -59,7 +65,8 @@ module Datadog
59
65
  SingleThreadSender.new(
60
66
  buffer,
61
67
  logger: logger,
62
- flush_interval: buffer_flush_interval) :
68
+ flush_interval: buffer_flush_interval,
69
+ queue_size: sender_queue_size) :
63
70
  Sender.new(
64
71
  buffer,
65
72
  logger: logger,
@@ -8,7 +8,8 @@ module Datadog
8
8
  def initialize(connection,
9
9
  max_payload_size: nil,
10
10
  max_pool_size: DEFAULT_BUFFER_POOL_SIZE,
11
- overflowing_stategy: :drop
11
+ overflowing_stategy: :drop,
12
+ serializer:
12
13
  )
13
14
  raise ArgumentError, 'max_payload_size keyword argument must be provided' unless max_payload_size
14
15
  raise ArgumentError, 'max_pool_size keyword argument must be provided' unless max_pool_size
@@ -17,12 +18,19 @@ module Datadog
17
18
  @max_payload_size = max_payload_size
18
19
  @max_pool_size = max_pool_size
19
20
  @overflowing_stategy = overflowing_stategy
21
+ @serializer = serializer
20
22
 
21
23
  @buffer = String.new
22
24
  clear_buffer
23
25
  end
24
26
 
25
27
  def add(message)
28
+ # Serializes the message if it hasn't been already. Part of the
29
+ # delay_serialization feature.
30
+ if message.is_a?(Array)
31
+ message = @serializer.to_stat(*message[0], **message[1])
32
+ end
33
+
26
34
  message_size = message.bytesize
27
35
 
28
36
  return nil unless message_size > 0 # to avoid adding empty messages to the buffer
@@ -20,8 +20,10 @@ module Datadog
20
20
  @mx = Mutex.new
21
21
  @queue_class = queue_class
22
22
  @thread_class = thread_class
23
- if flush_interval
24
- @flush_timer = Datadog::Statsd::Timer.new(flush_interval) { flush(sync: true) }
23
+ @flush_timer = if flush_interval
24
+ Datadog::Statsd::Timer.new(flush_interval) { flush(sync: true) }
25
+ else
26
+ nil
25
27
  end
26
28
  end
27
29
 
@@ -82,7 +84,10 @@ module Datadog
82
84
  if message_queue.length <= @queue_size
83
85
  message_queue << message
84
86
  else
85
- @telemetry.dropped_queue(packets: 1, bytes: message.bytesize) if @telemetry
87
+ if @telemetry
88
+ bytesize = message.respond_to?(:bytesize) ? message.bytesize : 0
89
+ @telemetry.dropped_queue(packets: 1, bytes: bytesize)
90
+ end
86
91
  end
87
92
  end
88
93
 
@@ -102,10 +107,11 @@ module Datadog
102
107
  # to close the sender nor trying to continue to `#add` more message
103
108
  # into the sender.
104
109
  def stop(join_worker: true)
110
+ @flush_timer.stop if @flush_timer
111
+
105
112
  message_queue = @message_queue
106
113
  message_queue.close if message_queue
107
114
 
108
- @flush_timer.stop if @flush_timer
109
115
  sender_thread = @sender_thread
110
116
  sender_thread.join if sender_thread && join_worker
111
117
  end
@@ -114,10 +120,11 @@ module Datadog
114
120
  # to close the sender nor trying to continue to `#add` more message
115
121
  # into the sender.
116
122
  def stop(join_worker: true)
123
+ @flush_timer.stop if @flush_timer
124
+
117
125
  message_queue = @message_queue
118
126
  message_queue << :close if message_queue
119
127
 
120
- @flush_timer.stop if @flush_timer
121
128
  sender_thread = @sender_thread
122
129
  sender_thread.join if sender_thread && join_worker
123
130
  end
@@ -7,12 +7,16 @@ 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, flush_interval: nil)
10
+ def initialize(message_buffer, logger: nil, flush_interval: nil, queue_size: 1)
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 }
14
+ @message_queue_size = queue_size
15
+ @message_queue = []
16
+ @flush_timer = if flush_interval
17
+ Datadog::Statsd::Timer.new(flush_interval) { flush }
18
+ else
19
+ nil
16
20
  end
17
21
  # store the pid for which this sender has been created
18
22
  update_fork_pid
@@ -24,15 +28,21 @@ module Datadog
24
28
  # not send, they belong to the parent process, let's clear the buffer.
25
29
  if forked?
26
30
  @message_buffer.reset
31
+ @message_queue.clear
27
32
  @flush_timer.start if @flush_timer && @flush_timer.stop?
28
33
  update_fork_pid
29
34
  end
30
- @message_buffer.add(message)
35
+
36
+ @message_queue << message
37
+ if @message_queue.size >= @message_queue_size
38
+ drain_message_queue
39
+ end
31
40
  }
32
41
  end
33
42
 
34
43
  def flush(*)
35
44
  @mx.synchronize {
45
+ drain_message_queue
36
46
  @message_buffer.flush()
37
47
  }
38
48
  end
@@ -51,6 +61,12 @@ module Datadog
51
61
 
52
62
  private
53
63
 
64
+ def drain_message_queue
65
+ while msg = @message_queue.shift
66
+ @message_buffer.add(msg)
67
+ end
68
+ end
69
+
54
70
  # below are "fork management" methods to be able to clean the MessageBuffer
55
71
  # if it detects that it is running in a unknown PID.
56
72
 
@@ -9,6 +9,7 @@ module Datadog
9
9
  @interval = interval
10
10
  @callback = callback
11
11
  @stop = true
12
+ @thread = nil
12
13
  end
13
14
 
14
15
  def start
@@ -4,6 +4,6 @@ require_relative 'connection'
4
4
 
5
5
  module Datadog
6
6
  class Statsd
7
- VERSION = '5.4.0'
7
+ VERSION = '5.6.0'
8
8
  end
9
9
  end
@@ -76,11 +76,12 @@ module Datadog
76
76
  # @option [Logger] logger for debugging
77
77
  # @option [Integer] buffer_max_payload_size max bytes to buffer
78
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)
79
+ # @option [Integer] sender_queue_size size of the sender queue in number of buffers
80
80
  # @option [Numeric] buffer_flush_interval interval in second to flush buffer
81
81
  # @option [String] socket_path unix socket path
82
82
  # @option [Float] default sample rate if not overridden
83
83
  # @option [Boolean] single_thread flushes the metrics on the main thread instead of in a companion thread
84
+ # @option [Boolean] delay_serialization delays stat serialization
84
85
  def initialize(
85
86
  host = nil,
86
87
  port = nil,
@@ -100,6 +101,7 @@ module Datadog
100
101
  logger: nil,
101
102
 
102
103
  single_thread: false,
104
+ delay_serialization: false,
103
105
 
104
106
  telemetry_enable: true,
105
107
  telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
@@ -112,6 +114,7 @@ module Datadog
112
114
  @prefix = @namespace ? "#{@namespace}.".freeze : nil
113
115
  @serializer = Serialization::Serializer.new(prefix: @prefix, global_tags: tags)
114
116
  @sample_rate = sample_rate
117
+ @delay_serialization = delay_serialization
115
118
 
116
119
  @forwarder = Forwarder.new(
117
120
  connection_cfg: ConnectionCfg.new(
@@ -133,6 +136,7 @@ module Datadog
133
136
  sender_queue_size: sender_queue_size,
134
137
 
135
138
  telemetry_flush_interval: telemetry_enable ? telemetry_flush_interval : nil,
139
+ serializer: serializer
136
140
  )
137
141
  end
138
142
 
@@ -237,6 +241,26 @@ module Datadog
237
241
  send_stats(stat, value, DISTRIBUTION_TYPE, opts)
238
242
  end
239
243
 
244
+ # Reports execution time of the provided block as a distribution.
245
+ #
246
+ # If the block fails, the stat is still reported, then the error
247
+ # is reraised
248
+ #
249
+ # @param [String] stat stat name.
250
+ # @param [Numeric] value distribution value.
251
+ # @param [Hash] opts the options to create the metric with
252
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
253
+ # @option opts [Array<String>] :tags An array of tags
254
+ # @example Report the time (in ms) taken to activate an account
255
+ # $statsd.distribution_time('account.activate') { @account.activate! }
256
+ def distribution_time(stat, opts = EMPTY_OPTIONS)
257
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
258
+ start = now
259
+ yield
260
+ ensure
261
+ distribution(stat, ((now - start) * 1000).round, opts)
262
+ end
263
+
240
264
  # Sends a timing (in ms) for the given stat to the statsd server. The
241
265
  # sample_rate determines what percentage of the time this report is sent. The
242
266
  # statsd server then uses the sample_rate to correctly track the average
@@ -405,7 +429,12 @@ module Datadog
405
429
  sample_rate = opts[:sample_rate] || @sample_rate || 1
406
430
 
407
431
  if sample_rate == 1 || opts[:pre_sampled] || rand <= sample_rate
408
- full_stat = serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
432
+ full_stat =
433
+ if @delay_serialization
434
+ [[stat, delta, type], {tags: opts[:tags], sample_rate: sample_rate}]
435
+ else
436
+ serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
437
+ end
409
438
 
410
439
  forwarder.send_message(full_stat)
411
440
  end
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.4.0
4
+ version: 5.6.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: 2022-03-01 00:00:00.000000000 Z
12
+ date: 2023-07-10 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: A Ruby DogStatsd client
15
15
  email: code@datadoghq.com
@@ -44,9 +44,9 @@ licenses:
44
44
  - MIT
45
45
  metadata:
46
46
  bug_tracker_uri: https://github.com/DataDog/dogstatsd-ruby/issues
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
47
+ changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v5.6.0/CHANGELOG.md
48
+ documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/5.6.0
49
+ source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v5.6.0
50
50
  post_install_message: |2+
51
51
 
52
52
  If you are upgrading from v4.x of the dogstatsd-ruby library, note the major change to the threading model:
@@ -66,8 +66,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0'
68
68
  requirements: []
69
- rubygems_version: 3.2.22
70
- signing_key:
69
+ rubygems_version: 3.2.3
70
+ signing_key:
71
71
  specification_version: 4
72
72
  summary: A Ruby DogStatsd client
73
73
  test_files: []
74
+ ...