dogstatsd-ruby 5.4.0 → 5.6.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: 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
+ ...