dogstatsd-ruby 4.8.3 → 5.6.1

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.
@@ -5,8 +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/batch'
8
+ require_relative 'statsd/connection_cfg'
9
+ require_relative 'statsd/message_buffer'
9
10
  require_relative 'statsd/serialization'
11
+ require_relative 'statsd/sender'
12
+ require_relative 'statsd/single_thread_sender'
13
+ require_relative 'statsd/forwarder'
14
+ require_relative 'statsd/timer'
10
15
 
11
16
  # = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
12
17
  #
@@ -26,13 +31,23 @@ require_relative 'statsd/serialization'
26
31
  # statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
27
32
  module Datadog
28
33
  class Statsd
34
+ class Error < StandardError
35
+ end
36
+
29
37
  OK = 0
30
38
  WARNING = 1
31
39
  CRITICAL = 2
32
40
  UNKNOWN = 3
33
41
 
34
- DEFAULT_BUFFER_SIZE = 8 * 1_024
42
+ UDP_DEFAULT_BUFFER_SIZE = 1_432
43
+ UDS_DEFAULT_BUFFER_SIZE = 8_192
44
+ DEFAULT_BUFFER_POOL_SIZE = Float::INFINITY
45
+
46
+ UDP_DEFAULT_SENDER_QUEUE_SIZE = 2048
47
+ UDS_DEFAULT_SENDER_QUEUE_SIZE = 512
48
+
35
49
  MAX_EVENT_SIZE = 8 * 1_024
50
+
36
51
  # minimum flush interval for the telemetry in seconds
37
52
  DEFAULT_TELEMETRY_FLUSH_INTERVAL = 10
38
53
 
@@ -51,77 +66,89 @@ module Datadog
51
66
  serializer.global_tags
52
67
  end
53
68
 
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
69
  # Default sample rate
61
70
  attr_reader :sample_rate
62
71
 
63
- # Connection
64
- attr_reader :connection
65
-
66
72
  # @param [String] host your statsd host
67
73
  # @param [Integer] port your statsd port
68
74
  # @option [String] namespace set a namespace to be prepended to every metric name
69
75
  # @option [Array<String>|Hash] tags tags to be added to every metric
70
76
  # @option [Logger] logger for debugging
71
- # @option [Integer] max_buffer_bytes max bytes to buffer when using #batch
77
+ # @option [Integer] buffer_max_payload_size max bytes to buffer
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
80
+ # @option [Numeric] buffer_flush_interval interval in second to flush buffer
72
81
  # @option [String] socket_path unix socket path
73
82
  # @option [Float] default sample rate if not overridden
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
74
85
  def initialize(
75
86
  host = nil,
76
87
  port = nil,
88
+ socket_path: nil,
89
+
77
90
  namespace: nil,
78
91
  tags: nil,
79
- max_buffer_bytes: DEFAULT_BUFFER_SIZE,
80
- socket_path: nil,
81
- logger: nil,
82
92
  sample_rate: nil,
83
- disable_telemetry: false,
93
+
94
+ buffer_max_payload_size: nil,
95
+ buffer_max_pool_size: nil,
96
+ buffer_overflowing_stategy: :drop,
97
+ buffer_flush_interval: nil,
98
+
99
+ sender_queue_size: nil,
100
+
101
+ logger: nil,
102
+
103
+ single_thread: false,
104
+ delay_serialization: false,
105
+
106
+ telemetry_enable: true,
84
107
  telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
85
108
  )
86
109
  unless tags.nil? || tags.is_a?(Array) || tags.is_a?(Hash)
87
- raise ArgumentError, 'tags must be a Array<String> or a Hash'
110
+ raise ArgumentError, 'tags must be an array of string tags or a Hash'
88
111
  end
89
112
 
90
113
  @namespace = namespace
91
114
  @prefix = @namespace ? "#{@namespace}.".freeze : nil
92
-
93
115
  @serializer = Serialization::Serializer.new(prefix: @prefix, global_tags: tags)
116
+ @sample_rate = sample_rate
117
+ @delay_serialization = delay_serialization
94
118
 
95
- transport_type = socket_path.nil? ? :udp : :uds
119
+ @forwarder = Forwarder.new(
120
+ connection_cfg: ConnectionCfg.new(
121
+ host: host,
122
+ port: port,
123
+ socket_path: socket_path,
124
+ ),
96
125
 
97
- @telemetry = Telemetry.new(disable_telemetry, telemetry_flush_interval,
98
126
  global_tags: tags,
99
- transport_type: transport_type
100
- )
127
+ logger: logger,
101
128
 
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
129
+ single_thread: single_thread,
108
130
 
109
- @logger = logger
131
+ buffer_max_payload_size: buffer_max_payload_size,
132
+ buffer_max_pool_size: buffer_max_pool_size,
133
+ buffer_overflowing_stategy: buffer_overflowing_stategy,
134
+ buffer_flush_interval: buffer_flush_interval,
110
135
 
111
- @sample_rate = sample_rate
136
+ sender_queue_size: sender_queue_size,
112
137
 
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))
138
+ telemetry_flush_interval: telemetry_enable ? telemetry_flush_interval : nil,
139
+ serializer: serializer
140
+ )
115
141
  end
116
142
 
117
143
  # yield a new instance to a block and close it when done
118
144
  # for short-term use-cases that don't want to close the socket manually
119
- def self.open(*args)
120
- instance = new(*args)
145
+ # TODO: replace with ... once we are on ruby 2.7
146
+ def self.open(*args, **kwargs)
147
+ instance = new(*args, **kwargs)
121
148
 
122
149
  yield instance
123
150
  ensure
124
- instance.close
151
+ instance.close if instance
125
152
  end
126
153
 
127
154
  # Sends an increment (count = 1) for the given stat to the statsd server.
@@ -129,6 +156,7 @@ module Datadog
129
156
  # @param [String] stat stat name
130
157
  # @param [Hash] opts the options to create the metric with
131
158
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
159
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
132
160
  # @option opts [Array<String>] :tags An array of tags
133
161
  # @option opts [Numeric] :by increment value, default 1
134
162
  # @see #count
@@ -143,6 +171,7 @@ module Datadog
143
171
  # @param [String] stat stat name
144
172
  # @param [Hash] opts the options to create the metric with
145
173
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
174
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
146
175
  # @option opts [Array<String>] :tags An array of tags
147
176
  # @option opts [Numeric] :by decrement value, default 1
148
177
  # @see #count
@@ -158,13 +187,14 @@ module Datadog
158
187
  # @param [Integer] count count
159
188
  # @param [Hash] opts the options to create the metric with
160
189
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
190
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
161
191
  # @option opts [Array<String>] :tags An array of tags
162
192
  def count(stat, count, opts = EMPTY_OPTIONS)
163
193
  opts = { sample_rate: opts } if opts.is_a?(Numeric)
164
194
  send_stats(stat, count, COUNTER_TYPE, opts)
165
195
  end
166
196
 
167
- # Sends an arbitary gauge value for the given stat to the statsd server.
197
+ # Sends an arbitrary gauge value for the given stat to the statsd server.
168
198
  #
169
199
  # This is useful for recording things like available disk space,
170
200
  # memory usage, and the like, which have different semantics than
@@ -174,6 +204,7 @@ module Datadog
174
204
  # @param [Numeric] value gauge value.
175
205
  # @param [Hash] opts the options to create the metric with
176
206
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
207
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
177
208
  # @option opts [Array<String>] :tags An array of tags
178
209
  # @example Report the current user count:
179
210
  # $statsd.gauge('user.count', User.count)
@@ -188,6 +219,7 @@ module Datadog
188
219
  # @param [Numeric] value histogram value.
189
220
  # @param [Hash] opts the options to create the metric with
190
221
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
222
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
191
223
  # @option opts [Array<String>] :tags An array of tags
192
224
  # @example Report the current user count:
193
225
  # $statsd.histogram('user.count', User.count)
@@ -201,6 +233,7 @@ module Datadog
201
233
  # @param [Numeric] value distribution value.
202
234
  # @param [Hash] opts the options to create the metric with
203
235
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
236
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
204
237
  # @option opts [Array<String>] :tags An array of tags
205
238
  # @example Report the current user count:
206
239
  # $statsd.distribution('user.count', User.count)
@@ -208,6 +241,26 @@ module Datadog
208
241
  send_stats(stat, value, DISTRIBUTION_TYPE, opts)
209
242
  end
210
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
+
211
264
  # Sends a timing (in ms) for the given stat to the statsd server. The
212
265
  # sample_rate determines what percentage of the time this report is sent. The
213
266
  # statsd server then uses the sample_rate to correctly track the average
@@ -217,6 +270,7 @@ module Datadog
217
270
  # @param [Integer] ms timing in milliseconds
218
271
  # @param [Hash] opts the options to create the metric with
219
272
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
273
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
220
274
  # @option opts [Array<String>] :tags An array of tags
221
275
  def timing(stat, ms, opts = EMPTY_OPTIONS)
222
276
  opts = { sample_rate: opts } if opts.is_a?(Numeric)
@@ -231,6 +285,7 @@ module Datadog
231
285
  # @param [String] stat stat name
232
286
  # @param [Hash] opts the options to create the metric with
233
287
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
288
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
234
289
  # @option opts [Array<String>] :tags An array of tags
235
290
  # @yield The operation to be timed
236
291
  # @see #timing
@@ -250,6 +305,7 @@ module Datadog
250
305
  # @param [Numeric] value set value.
251
306
  # @param [Hash] opts the options to create the metric with
252
307
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
308
+ # @option opts [Boolean] :pre_sampled If true, the client assumes the caller has already sampled metrics at :sample_rate, and doesn't perform sampling.
253
309
  # @option opts [Array<String>] :tags An array of tags
254
310
  # @example Record a unique visitory by id:
255
311
  # $statsd.set('visitors.uniques', User.id)
@@ -270,9 +326,9 @@ module Datadog
270
326
  # @example Report a critical service check status
271
327
  # $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
272
328
  def service_check(name, status, opts = EMPTY_OPTIONS)
273
- telemetry.sent(service_checks: 1)
329
+ telemetry.sent(service_checks: 1) if telemetry
274
330
 
275
- send_stat(serializer.to_service_check(name, status, opts))
331
+ forwarder.send_message(serializer.to_service_check(name, status, opts))
276
332
  end
277
333
 
278
334
  # This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
@@ -295,13 +351,20 @@ module Datadog
295
351
  # @example Report an awful event:
296
352
  # $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
297
353
  def event(title, text, opts = EMPTY_OPTIONS)
298
- telemetry.sent(events: 1)
354
+ telemetry.sent(events: 1) if telemetry
299
355
 
300
- send_stat(serializer.to_event(title, text, opts))
356
+ forwarder.send_message(serializer.to_event(title, text, opts))
301
357
  end
302
358
 
303
- # Send several metrics in the same UDP Packet
304
- # They will be buffered and flushed when the block finishes
359
+ # Send several metrics in the same packet.
360
+ # They will be buffered and flushed when the block finishes.
361
+ #
362
+ # This method exists for compatibility with v4.x versions, it is not needed
363
+ # anymore since the batching is now automatically done internally.
364
+ # It also means that an automatic flush could occur if the buffer is filled
365
+ # during the execution of the batch block.
366
+ #
367
+ # This method is DEPRECATED and will be removed in future v6.x API.
305
368
  #
306
369
  # @example Send several metrics in one packet:
307
370
  # $statsd.batch do |s|
@@ -309,50 +372,71 @@ module Datadog
309
372
  # s.increment('page.views')
310
373
  # end
311
374
  def batch
312
- @batch.open do
313
- yield self
314
- end
375
+ yield self
376
+ flush(sync: true)
315
377
  end
316
378
 
317
379
  # Close the underlying socket
318
- def close
319
- connection.close
380
+ #
381
+ # @param [Boolean, true] flush Should we flush the metrics before closing
382
+ def close(flush: true)
383
+ flush(sync: true) if flush
384
+ forwarder.close
385
+ end
386
+
387
+ def sync_with_outbound_io
388
+ forwarder.sync_with_outbound_io
389
+ end
390
+
391
+ # Flush the buffer into the connection
392
+ def flush(flush_telemetry: false, sync: false)
393
+ forwarder.flush(flush_telemetry: flush_telemetry, sync: sync)
394
+ end
395
+
396
+ def telemetry
397
+ forwarder.telemetry
398
+ end
399
+
400
+ def host
401
+ forwarder.host
402
+ end
403
+
404
+ def port
405
+ forwarder.port
406
+ end
407
+
408
+ def socket_path
409
+ forwarder.socket_path
410
+ end
411
+
412
+ def transport_type
413
+ forwarder.transport_type
320
414
  end
321
415
 
322
416
  private
323
417
  attr_reader :serializer
324
- attr_reader :telemetry
418
+ attr_reader :forwarder
325
419
 
326
- PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= '2.1.0')
327
420
  EMPTY_OPTIONS = {}.freeze
328
421
 
329
- if PROCESS_TIME_SUPPORTED
330
- def now
331
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
332
- end
333
- else
334
- def now
335
- Time.now.to_f
336
- end
422
+ def now
423
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
337
424
  end
338
425
 
339
426
  def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
340
- telemetry.sent(metrics: 1)
427
+ telemetry.sent(metrics: 1) if telemetry
341
428
 
342
429
  sample_rate = opts[:sample_rate] || @sample_rate || 1
343
430
 
344
- if sample_rate == 1 || rand <= sample_rate
345
- full_stat = serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
346
-
347
- send_stat(full_stat)
348
- end
349
- end
431
+ if sample_rate == 1 || opts[:pre_sampled] || rand <= 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
350
438
 
351
- def send_stat(message)
352
- if @batch.open?
353
- @batch.add(message)
354
- else
355
- @connection.write(message)
439
+ forwarder.send_message(full_stat)
356
440
  end
357
441
  end
358
442
  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.3
4
+ version: 5.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rein Henrichs
8
+ - Karim Bogtob
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2021-02-22 00:00:00.000000000 Z
12
+ date: 2023-09-07 00:00:00.000000000 Z
12
13
  dependencies: []
13
- description: A Ruby DogStastd client
14
+ description: A Ruby DogStatsd client
14
15
  email: code@datadoghq.com
15
16
  executables: []
16
17
  extensions: []
@@ -21,15 +22,20 @@ 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/connection_cfg.rb
27
+ - lib/datadog/statsd/forwarder.rb
28
+ - lib/datadog/statsd/message_buffer.rb
29
+ - lib/datadog/statsd/sender.rb
26
30
  - lib/datadog/statsd/serialization.rb
27
31
  - lib/datadog/statsd/serialization/event_serializer.rb
28
32
  - lib/datadog/statsd/serialization/serializer.rb
29
33
  - lib/datadog/statsd/serialization/service_check_serializer.rb
30
34
  - lib/datadog/statsd/serialization/stat_serializer.rb
31
35
  - lib/datadog/statsd/serialization/tag_serializer.rb
36
+ - lib/datadog/statsd/single_thread_sender.rb
32
37
  - lib/datadog/statsd/telemetry.rb
38
+ - lib/datadog/statsd/timer.rb
33
39
  - lib/datadog/statsd/udp_connection.rb
34
40
  - lib/datadog/statsd/uds_connection.rb
35
41
  - lib/datadog/statsd/version.rb
@@ -38,10 +44,14 @@ licenses:
38
44
  - MIT
39
45
  metadata:
40
46
  bug_tracker_uri: https://github.com/DataDog/dogstatsd-ruby/issues
41
- changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v4.8.3/CHANGELOG.md
42
- documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/4.8.3
43
- source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v4.8.3
44
- post_install_message:
47
+ changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v5.6.1/CHANGELOG.md
48
+ documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/5.6.1
49
+ source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v5.6.1
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
+
45
55
  rdoc_options: []
46
56
  require_paths:
47
57
  - lib
@@ -49,16 +59,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
49
59
  requirements:
50
60
  - - ">="
51
61
  - !ruby/object:Gem::Version
52
- version: 2.0.0
62
+ version: 2.1.0
53
63
  required_rubygems_version: !ruby/object:Gem::Requirement
54
64
  requirements:
55
65
  - - ">="
56
66
  - !ruby/object:Gem::Version
57
67
  version: '0'
58
68
  requirements: []
59
- rubyforge_project:
60
- rubygems_version: 2.7.10
69
+ rubygems_version: 3.2.3
61
70
  signing_key:
62
71
  specification_version: 4
63
72
  summary: A Ruby DogStatsd client
64
73
  test_files: []
74
+ ...
@@ -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