dogstatsd-ruby 4.6.0 → 4.7.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: 7d521571f811d6206670773db4ac3808c02513d8761445ecf917f831578e2614
4
- data.tar.gz: 97d68494f8520a777569dc8f7b3c04070152aa0924edabc67ffa0ae1f16cb615
3
+ metadata.gz: 57d7660dad73ad8c0c5f37f6d6130474d255beaa467aca03f532e552d0b608e0
4
+ data.tar.gz: 30c8686d3045e4cfb2ca18c57390f78dbcccba1ff941deb170d89497953d5e84
5
5
  SHA512:
6
- metadata.gz: f43fb9314373873b5d436824a3cae0a5dc0ef4fcc2a458b8a98b98dbd8b12cb28d3b32293436451829a53da8cb70f17c6104aa64403cfb6000822e59a74ca29a
7
- data.tar.gz: a796456e38da22047d952b7a1bebaf1f923a0847c29a9e7548020c8b658144e729133e7e90a16453d0f48a815353734523870e2e391371d8699f1d6574bd8379
6
+ metadata.gz: 99214bb827186d3b01568cc59064f7d2a8d043a4a739c7f90a2b013feeeead11ed363937c37d2c29846d101ed886c318a6369c2d5a57292b0c058db81c708813
7
+ data.tar.gz: c82e6c5e9668804688fd308e091398e05ada88dac8ea78a678ee2da671eaf297ffd95997efaf376506632f65e88b4096a6a9b2590667f772f19c6b020e238827
data/README.md CHANGED
@@ -25,8 +25,13 @@ require 'datadog/statsd'
25
25
  # Create a DogStatsD client instance.
26
26
  statsd = Datadog::Statsd.new('localhost', 8125)
27
27
  ```
28
+ Or if you want to connect over Unix Domain Socket:
29
+ ```ruby
30
+ # Connection over Unix Domain Socket
31
+ statsd = Datadog::Statsd.new(socket_path: '/path/to/socket/file')
32
+ ```
28
33
 
29
- Find a list of all the available options for your DogStatsD Client in the [DogStatsD-ruby rubydoc](https://www.rubydoc.info/github/DataDog/dogstatsd-ruby/master/Datadog/Statsd) or in the [Datadog public DogStatsD documentation](https://docs.datadoghq.com/developers/dogstatsd/?tab=go#client-instantiation-parameters).
34
+ Find a list of all the available options for your DogStatsD Client in the [DogStatsD-ruby rubydoc](https://www.rubydoc.info/github/DataDog/dogstatsd-ruby/master/Datadog/Statsd) or in the [Datadog public DogStatsD documentation](https://docs.datadoghq.com/developers/dogstatsd/?tab=ruby#client-instantiation-parameters).
30
35
 
31
36
  ### Origin detection over UDP
32
37
 
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
  require 'socket'
3
3
 
4
+ require_relative 'statsd/telemetry'
5
+ require_relative 'statsd/udp_connection'
6
+ require_relative 'statsd/uds_connection'
7
+ require_relative 'statsd/batch'
8
+
4
9
  # = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
5
10
  #
6
11
  # @example Set up a global Statsd client for a server on localhost:8125
@@ -19,246 +24,42 @@ require 'socket'
19
24
  # statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
20
25
  module Datadog
21
26
  class Statsd
22
-
23
- class Telemetry
24
- attr_accessor :metrics
25
- attr_accessor :events
26
- attr_accessor :service_checks
27
- attr_accessor :bytes_sent
28
- attr_accessor :bytes_dropped
29
- attr_accessor :packets_sent
30
- attr_accessor :packets_dropped
31
- attr_reader :estimate_max_size
32
-
33
- def initialize(disabled, tags)
34
- @disabled = disabled
35
- @tags = tags
36
- reset
37
-
38
- # estimate_max_size is an estimation or the maximum size of the
39
- # telemetry payload. Since we don't want our packet to go over
40
- # 'max_buffer_bytes', we have to adjust with the size of the telemetry
41
- # (and any tags used). The telemetry payload size will change depending
42
- # on the actual value of metrics: metrics received, packet dropped,
43
- # etc. This is why we add a 63bytes margin: 9 bytes for each of the 7
44
- # telemetry metrics.
45
- @estimate_max_size = @disabled ? 0 : flush().length + 9 * 7
46
- end
47
-
48
- def reset
49
- @metrics = 0
50
- @events = 0
51
- @service_checks = 0
52
- @bytes_sent = 0
53
- @bytes_dropped = 0
54
- @packets_sent = 0
55
- @packets_dropped = 0
56
- end
57
-
58
- def flush
59
- return '' if @disabled
60
-
61
- # using shorthand syntax to reduce the garbage collection
62
- return %Q(
63
- datadog.dogstatsd.client.metrics:#{@metrics}|#{COUNTER_TYPE}|##{@tags}
64
- datadog.dogstatsd.client.events:#{@events}|#{COUNTER_TYPE}|##{@tags}
65
- datadog.dogstatsd.client.service_checks:#{@service_checks}|#{COUNTER_TYPE}|##{@tags}
66
- datadog.dogstatsd.client.bytes_sent:#{@bytes_sent}|#{COUNTER_TYPE}|##{@tags}
67
- datadog.dogstatsd.client.bytes_dropped:#{@bytes_dropped}|#{COUNTER_TYPE}|##{@tags}
68
- datadog.dogstatsd.client.packets_sent:#{@packets_sent}|#{COUNTER_TYPE}|##{@tags}
69
- datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{@tags})
70
- end
71
- end
72
-
73
- class Connection
74
- DEFAULT_HOST = '127.0.0.1'
75
- DEFAULT_PORT = 8125
76
-
77
- # StatsD host. Defaults to 127.0.0.1.
78
- attr_reader :host
79
-
80
- # StatsD port. Defaults to 8125.
81
- attr_reader :port
82
-
83
- # DogStatsd unix socket path. Not used by default.
84
- attr_reader :socket_path
85
-
86
- def initialize(telemetry)
87
- @telemetry = telemetry
88
- end
89
-
90
- # Close the underlying socket
91
- def close
92
- @socket && @socket.close
93
- end
94
-
95
- def write(message)
96
- @logger.debug { "Statsd: #{message}" } if @logger
97
- payload = message + @telemetry.flush()
98
- send_message(payload)
99
-
100
- @telemetry.reset
101
- @telemetry.bytes_sent += payload.length
102
- @telemetry.packets_sent += 1
103
- rescue StandardError => boom
104
- # Try once to reconnect if the socket has been closed
105
- retries ||= 1
106
- if retries <= 1 &&
107
- (boom.is_a?(Errno::ENOTCONN) or
108
- boom.is_a?(Errno::ECONNREFUSED) or
109
- boom.is_a?(IOError) && boom.message =~ /closed stream/i)
110
- retries += 1
111
- begin
112
- @socket = connect
113
- retry
114
- rescue StandardError => e
115
- boom = e
116
- end
117
- end
118
-
119
- @telemetry.bytes_dropped += payload.length
120
- @telemetry.packets_dropped += 1
121
- @logger.error { "Statsd: #{boom.class} #{boom}" } if @logger
122
- nil
123
- end
124
-
125
- private
126
-
127
- def socket
128
- @socket ||= connect
129
- end
130
- end
131
-
132
- class UDPConnection < Connection
133
- def initialize(host, port, logger, telemetry)
134
- super(telemetry)
135
- @host = host || ENV.fetch('DD_AGENT_HOST', nil) || DEFAULT_HOST
136
- @port = port || ENV.fetch('DD_DOGSTATSD_PORT', nil) || DEFAULT_PORT
137
- @logger = logger
138
- end
139
-
140
- private
141
-
142
- def connect
143
- socket = UDPSocket.new
144
- socket.connect(@host, @port)
145
- socket
146
- end
147
-
148
- def send_message(message)
149
- socket.send(message, 0)
150
- end
151
- end
152
-
153
- class UDSConnection < Connection
154
- class BadSocketError < StandardError; end
155
-
156
- def initialize(socket_path, logger, telemetry)
157
- super(telemetry)
158
- @socket_path = socket_path
159
- @logger = logger
160
- end
161
-
162
- private
163
-
164
- def connect
165
- socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
166
- socket.connect(Socket.pack_sockaddr_un(@socket_path))
167
- socket
168
- end
169
-
170
- def send_message(message)
171
- socket.sendmsg_nonblock(message)
172
- rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENOENT => e
173
- @socket = nil
174
- raise BadSocketError, "#{e.class}: #{e}"
175
- end
176
- end
177
-
178
- class Batch
179
- def initialize(connection, max_buffer_bytes)
180
- @connection = connection
181
- @max_buffer_bytes = max_buffer_bytes
182
- @depth = 0
183
- reset
184
- end
185
-
186
- def open
187
- @depth += 1
188
- yield
189
- ensure
190
- @depth -= 1
191
- flush if !open?
192
- end
193
-
194
- def open?
195
- @depth > 0
196
- end
197
-
198
- def add(message)
199
- message_bytes = message.bytesize
200
-
201
- unless @buffer_bytes == 0
202
- if @buffer_bytes + 1 + message_bytes >= @max_buffer_bytes
203
- flush
204
- else
205
- @buffer << NEW_LINE
206
- @buffer_bytes += 1
207
- end
208
- end
209
-
210
- @buffer << message
211
- @buffer_bytes += message_bytes
212
- end
213
-
214
- def flush
215
- return if @buffer_bytes == 0
216
- @connection.write @buffer
217
- reset
218
- end
219
-
220
- private
221
-
222
- def reset
223
- @buffer = String.new
224
- @buffer_bytes = 0
225
- end
226
- end
227
-
228
27
  # Create a dictionary to assign a key to every parameter's name, except for tags (treated differently)
229
28
  # Goal: Simple and fast to add some other parameters
230
29
  OPTS_KEYS = {
231
- :date_happened => :d,
232
- :hostname => :h,
233
- :aggregation_key => :k,
234
- :priority => :p,
235
- :source_type_name => :s,
236
- :alert_type => :t,
237
- }
30
+ date_happened: :d,
31
+ hostname: :h,
32
+ aggregation_key: :k,
33
+ priority: :p,
34
+ source_type_name: :s,
35
+ alert_type: :t,
36
+ }.freeze
238
37
 
239
38
  # Service check options
240
39
  SC_OPT_KEYS = {
241
- :timestamp => 'd:'.freeze,
242
- :hostname => 'h:'.freeze,
243
- :tags => '#'.freeze,
244
- :message => 'm:'.freeze,
245
- }
40
+ timestamp: 'd:',
41
+ hostname: 'h:',
42
+ tags: '#',
43
+ message: 'm:',
44
+ }.freeze
246
45
 
247
- OK = 0
248
- WARNING = 1
249
- CRITICAL = 2
250
- UNKNOWN = 3
46
+ OK = 0
47
+ WARNING = 1
48
+ CRITICAL = 2
49
+ UNKNOWN = 3
251
50
 
252
51
  DEFAULT_BUFFER_SIZE = 8 * 1_024
253
52
  MAX_EVENT_SIZE = 8 * 1_024
53
+ # minimum flush interval for the telemetry in seconds
54
+ DEFAULT_TELEMETRY_FLUSH_INTERVAL = 10
254
55
 
255
- COUNTER_TYPE = 'c'.freeze
256
- GAUGE_TYPE = 'g'.freeze
257
- HISTOGRAM_TYPE = 'h'.freeze
258
- DISTRIBUTION_TYPE = 'd'.freeze
259
- TIMING_TYPE = 'ms'.freeze
260
- SET_TYPE = 's'.freeze
261
- VERSION = "4.6.0".freeze
56
+ COUNTER_TYPE = 'c'
57
+ GAUGE_TYPE = 'g'
58
+ HISTOGRAM_TYPE = 'h'
59
+ DISTRIBUTION_TYPE = 'd'
60
+ TIMING_TYPE = 'ms'
61
+ SET_TYPE = 's'
62
+ VERSION = '4.7.0'
262
63
 
263
64
  # A namespace to prepend to all statsd calls. Defaults to no namespace.
264
65
  attr_reader :namespace
@@ -295,22 +96,28 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
295
96
  socket_path: nil,
296
97
  logger: nil,
297
98
  sample_rate: nil,
298
- disable_telemetry: false
99
+ disable_telemetry: false,
100
+ telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
299
101
  )
300
- unless tags.nil? or tags.is_a? Array or tags.is_a? Hash
102
+ unless tags.nil? || tags.is_a?(Array) || tags.is_a?(Hash)
301
103
  raise ArgumentError, 'tags must be a Array<String> or a Hash'
302
104
  end
303
105
 
304
- tags = tag_hash_to_array(tags) if tags.is_a? Hash
305
- @tags = (tags || []).compact.map! {|tag| escape_tag_content(tag)}
106
+ tags = tag_hash_to_array(tags) if tags.is_a?(Hash)
107
+ @tags = (tags || []).compact.map! do |tag|
108
+ escape_tag_content(tag)
109
+ end
306
110
 
307
111
  # append the entity id to tags if DD_ENTITY_ID env var is not nil
308
- @tags << 'dd.internal.entity_id:' + escape_tag_content(ENV.fetch('DD_ENTITY_ID', nil)) unless ENV.fetch('DD_ENTITY_ID', nil).nil?
112
+ unless ENV.fetch('DD_ENTITY_ID', nil).nil?
113
+ dd_entity = escape_tag_content(ENV.fetch('DD_ENTITY_ID', nil))
114
+ @tags << 'dd.internal.entity_id:' + dd_entity
115
+ end
309
116
 
310
117
  # init telemetry
311
- transport_type = socket_path.nil? ? "udp": "uds"
118
+ transport_type = socket_path.nil? ? 'udp': 'uds'
312
119
  telemetry_tags = (["client:ruby", "client_version:#{VERSION}", "client_transport:#{transport_type}"] + @tags).join(COMMA).freeze
313
- @telemetry = Telemetry.new(disable_telemetry, telemetry_tags)
120
+ @telemetry = Telemetry.new(disable_telemetry, telemetry_tags, telemetry_flush_interval)
314
121
 
315
122
  if socket_path.nil?
316
123
  @connection = UDPConnection.new(host, port, logger, @telemetry)
@@ -332,6 +139,7 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
332
139
  # for short-term use-cases that don't want to close the socket manually
333
140
  def self.open(*args)
334
141
  instance = new(*args)
142
+
335
143
  yield instance
336
144
  ensure
337
145
  instance.close
@@ -345,10 +153,10 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
345
153
  # @option opts [Array<String>] :tags An array of tags
346
154
  # @option opts [Numeric] :by increment value, default 1
347
155
  # @see #count
348
- def increment(stat, opts=EMPTY_OPTIONS)
349
- opts = {:sample_rate => opts} if opts.is_a? Numeric
156
+ def increment(stat, opts = EMPTY_OPTIONS)
157
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
350
158
  incr_value = opts.fetch(:by, 1)
351
- count stat, incr_value, opts
159
+ count(stat, incr_value, opts)
352
160
  end
353
161
 
354
162
  # Sends a decrement (count = -1) for the given stat to the statsd server.
@@ -359,10 +167,10 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
359
167
  # @option opts [Array<String>] :tags An array of tags
360
168
  # @option opts [Numeric] :by decrement value, default 1
361
169
  # @see #count
362
- def decrement(stat, opts=EMPTY_OPTIONS)
363
- opts = {:sample_rate => opts} if opts.is_a? Numeric
170
+ def decrement(stat, opts = EMPTY_OPTIONS)
171
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
364
172
  decr_value = - opts.fetch(:by, 1)
365
- count stat, decr_value, opts
173
+ count(stat, decr_value, opts)
366
174
  end
367
175
 
368
176
  # Sends an arbitrary count for the given stat to the statsd server.
@@ -372,9 +180,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
372
180
  # @param [Hash] opts the options to create the metric with
373
181
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
374
182
  # @option opts [Array<String>] :tags An array of tags
375
- def count(stat, count, opts=EMPTY_OPTIONS)
376
- opts = {:sample_rate => opts} if opts.is_a? Numeric
377
- send_stats stat, count, COUNTER_TYPE, opts
183
+ def count(stat, count, opts = EMPTY_OPTIONS)
184
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
185
+ send_stats(stat, count, COUNTER_TYPE, opts)
378
186
  end
379
187
 
380
188
  # Sends an arbitary gauge value for the given stat to the statsd server.
@@ -390,9 +198,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
390
198
  # @option opts [Array<String>] :tags An array of tags
391
199
  # @example Report the current user count:
392
200
  # $statsd.gauge('user.count', User.count)
393
- def gauge(stat, value, opts=EMPTY_OPTIONS)
394
- opts = {:sample_rate => opts} if opts.is_a? Numeric
395
- send_stats stat, value, GAUGE_TYPE, opts
201
+ def gauge(stat, value, opts = EMPTY_OPTIONS)
202
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
203
+ send_stats(stat, value, GAUGE_TYPE, opts)
396
204
  end
397
205
 
398
206
  # Sends a value to be tracked as a histogram to the statsd server.
@@ -404,8 +212,8 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
404
212
  # @option opts [Array<String>] :tags An array of tags
405
213
  # @example Report the current user count:
406
214
  # $statsd.histogram('user.count', User.count)
407
- def histogram(stat, value, opts=EMPTY_OPTIONS)
408
- send_stats stat, value, HISTOGRAM_TYPE, opts
215
+ def histogram(stat, value, opts = EMPTY_OPTIONS)
216
+ send_stats(stat, value, HISTOGRAM_TYPE, opts)
409
217
  end
410
218
 
411
219
  # Sends a value to be tracked as a distribution to the statsd server.
@@ -417,8 +225,8 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
417
225
  # @option opts [Array<String>] :tags An array of tags
418
226
  # @example Report the current user count:
419
227
  # $statsd.distribution('user.count', User.count)
420
- def distribution(stat, value, opts=EMPTY_OPTIONS)
421
- send_stats stat, value, DISTRIBUTION_TYPE, opts
228
+ def distribution(stat, value, opts = EMPTY_OPTIONS)
229
+ send_stats(stat, value, DISTRIBUTION_TYPE, opts)
422
230
  end
423
231
 
424
232
  # Sends a timing (in ms) for the given stat to the statsd server. The
@@ -431,9 +239,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
431
239
  # @param [Hash] opts the options to create the metric with
432
240
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
433
241
  # @option opts [Array<String>] :tags An array of tags
434
- def timing(stat, ms, opts=EMPTY_OPTIONS)
435
- opts = {:sample_rate => opts} if opts.is_a? Numeric
436
- send_stats stat, ms, TIMING_TYPE, opts
242
+ def timing(stat, ms, opts = EMPTY_OPTIONS)
243
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
244
+ send_stats(stat, ms, TIMING_TYPE, opts)
437
245
  end
438
246
 
439
247
  # Reports execution time of the provided block using {#timing}.
@@ -449,12 +257,21 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
449
257
  # @see #timing
450
258
  # @example Report the time (in ms) taken to activate an account
451
259
  # $statsd.time('account.activate') { @account.activate! }
452
- def time(stat, opts=EMPTY_OPTIONS)
453
- opts = {:sample_rate => opts} if opts.is_a? Numeric
454
- start = (PROCESS_TIME_SUPPORTED ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : Time.now.to_f)
455
- return yield
260
+ def time(stat, opts = EMPTY_OPTIONS)
261
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
262
+ start = if PROCESS_TIME_SUPPORTED
263
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) # uncovered
264
+ else
265
+ Time.now.to_f # uncovered
266
+ end
267
+ yield
456
268
  ensure
457
- finished = (PROCESS_TIME_SUPPORTED ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : Time.now.to_f)
269
+ finished = if PROCESS_TIME_SUPPORTED
270
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) # uncovered
271
+ else
272
+ Time.now.to_f # uncovered
273
+ end
274
+
458
275
  timing(stat, ((finished - start) * 1000).round, opts)
459
276
  end
460
277
 
@@ -467,9 +284,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
467
284
  # @option opts [Array<String>] :tags An array of tags
468
285
  # @example Record a unique visitory by id:
469
286
  # $statsd.set('visitors.uniques', User.id)
470
- def set(stat, value, opts=EMPTY_OPTIONS)
471
- opts = {:sample_rate => opts} if opts.is_a? Numeric
472
- send_stats stat, value, SET_TYPE, opts
287
+ def set(stat, value, opts = EMPTY_OPTIONS)
288
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
289
+ send_stats(stat, value, SET_TYPE, opts)
473
290
  end
474
291
 
475
292
  # This method allows you to send custom service check statuses.
@@ -483,9 +300,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
483
300
  # @option opts [String, nil] :message (nil) A message to associate with this service check status
484
301
  # @example Report a critical service check status
485
302
  # $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
486
- def service_check(name, status, opts=EMPTY_OPTIONS)
303
+ def service_check(name, status, opts = EMPTY_OPTIONS)
487
304
  @telemetry.service_checks += 1
488
- send_stat format_service_check(name, status, opts)
305
+ send_stat(format_service_check(name, status, opts))
489
306
  end
490
307
 
491
308
  # This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
@@ -506,9 +323,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
506
323
  # @option opts [Array<String>] :tags tags to be added to every metric
507
324
  # @example Report an awful event:
508
325
  # $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
509
- def event(title, text, opts=EMPTY_OPTIONS)
326
+ def event(title, text, opts = EMPTY_OPTIONS)
510
327
  @telemetry.events += 1
511
- send_stat format_event(title, text, opts)
328
+ send_stat(format_event(title, text, opts))
512
329
  end
513
330
 
514
331
  # Send several metrics in the same UDP Packet
@@ -520,7 +337,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
520
337
  # s.increment('page.views')
521
338
  # end
522
339
  def batch
523
- @batch.open { yield self }
340
+ @batch.open do
341
+ yield self
342
+ end
524
343
  end
525
344
 
526
345
  # Close the underlying socket
@@ -530,20 +349,20 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
530
349
 
531
350
  private
532
351
 
533
- NEW_LINE = "\n".freeze
534
- ESC_NEW_LINE = "\\n".freeze
535
- COMMA = ",".freeze
536
- PIPE = "|".freeze
537
- DOT = ".".freeze
538
- DOUBLE_COLON = "::".freeze
539
- UNDERSCORE = "_".freeze
540
- PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= "2.1.0")
352
+ NEW_LINE = "\n"
353
+ ESC_NEW_LINE = '\n'
354
+ COMMA = ','
355
+ PIPE = '|'
356
+ DOT = '.'
357
+ DOUBLE_COLON = '::'
358
+ UNDERSCORE = '_'
359
+ PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= '2.1.0')
541
360
  EMPTY_OPTIONS = {}.freeze
542
361
 
543
362
  private_constant :NEW_LINE, :ESC_NEW_LINE, :COMMA, :PIPE, :DOT,
544
363
  :DOUBLE_COLON, :UNDERSCORE, :EMPTY_OPTIONS
545
364
 
546
- def format_service_check(name, status, opts=EMPTY_OPTIONS)
365
+ def format_service_check(name, status, opts = EMPTY_OPTIONS)
547
366
  sc_string = "_sc|#{name}|#{status}".dup
548
367
 
549
368
  SC_OPT_KEYS.each do |key, shorthand_key|
@@ -569,7 +388,7 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
569
388
  sc_string
570
389
  end
571
390
 
572
- def format_event(title, text, opts=EMPTY_OPTIONS)
391
+ def format_event(title, text, opts = EMPTY_OPTIONS)
573
392
  escaped_title = escape_event_content(title)
574
393
  escaped_text = escape_event_content(text)
575
394
  event_string_data = "_e{#{escaped_title.bytesize},#{escaped_text.bytesize}}:#{escaped_title}|#{escaped_text}".dup
@@ -595,14 +414,18 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
595
414
  event_string_data << "|##{tags_string}"
596
415
  end
597
416
 
598
- raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string_data.bytesize > MAX_EVENT_SIZE
417
+ if event_string_data.bytesize > MAX_EVENT_SIZE
418
+ raise "Event #{title} payload is too big (more that 8KB), event discarded"
419
+ end
599
420
  event_string_data
600
421
  end
601
422
 
602
423
  def tags_as_string(opts)
603
424
  if tag_arr = opts[:tags]
604
- tag_arr = tag_hash_to_array(tag_arr) if tag_arr.is_a? Hash
605
- tag_arr = tag_arr.map { |tag| escape_tag_content(tag) }
425
+ tag_arr = tag_hash_to_array(tag_arr) if tag_arr.is_a?(Hash)
426
+ tag_arr = tag_arr.map do |tag|
427
+ escape_tag_content(tag)
428
+ end
606
429
  tag_arr = tags + tag_arr # @tags are normalized when set, so not need to normalize them again
607
430
  else
608
431
  tag_arr = tags
@@ -611,54 +434,56 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
611
434
  end
612
435
 
613
436
  def tag_hash_to_array(tag_hash)
614
- tag_hash.to_a.map {|pair| pair.compact.join(":")}
437
+ tag_hash.to_a.map do |pair|
438
+ pair.compact.join(':')
439
+ end
615
440
  end
616
441
 
617
- def escape_event_content(msg)
618
- msg.gsub NEW_LINE, ESC_NEW_LINE
442
+ def escape_event_content(message)
443
+ message.gsub(NEW_LINE, ESC_NEW_LINE)
619
444
  end
620
445
 
621
446
  def escape_tag_content(tag)
622
447
  tag = remove_pipes(tag.to_s)
623
- tag.delete! COMMA
448
+ tag.delete!(COMMA)
624
449
  tag
625
450
  end
626
451
 
627
- def remove_pipes(msg)
628
- msg.delete PIPE
452
+ def remove_pipes(message)
453
+ message.delete(PIPE)
629
454
  end
630
455
 
631
- def escape_service_check_message(msg)
632
- escape_event_content(msg).gsub('m:'.freeze, 'm\:'.freeze)
456
+ def escape_service_check_message(message)
457
+ escape_event_content(message).gsub('m:', 'm\:')
633
458
  end
634
459
 
635
- def send_stats(stat, delta, type, opts=EMPTY_OPTIONS)
460
+ def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
636
461
  @telemetry.metrics += 1
637
462
  sample_rate = opts[:sample_rate] || @sample_rate || 1
638
- if sample_rate == 1 or rand <= sample_rate
463
+ if sample_rate == 1 || rand <= sample_rate
639
464
  full_stat = ''.dup
640
465
  full_stat << @prefix if @prefix
641
466
 
642
467
  stat = stat.is_a?(String) ? stat.dup : stat.to_s
643
468
  # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
644
469
  stat.gsub!(DOUBLE_COLON, DOT)
645
- stat.tr!(':|@'.freeze, UNDERSCORE)
470
+ stat.tr!(':|@', UNDERSCORE)
646
471
  full_stat << stat
647
472
 
648
- full_stat << ':'.freeze
473
+ full_stat << ':'
649
474
  full_stat << delta.to_s
650
475
  full_stat << PIPE
651
476
  full_stat << type
652
477
 
653
478
  unless sample_rate == 1
654
479
  full_stat << PIPE
655
- full_stat << '@'.freeze
480
+ full_stat << '@'
656
481
  full_stat << sample_rate.to_s
657
482
  end
658
483
 
659
484
  if tags_string = tags_as_string(opts)
660
485
  full_stat << PIPE
661
- full_stat << '#'.freeze
486
+ full_stat << '#'
662
487
  full_stat << tags_string
663
488
  end
664
489
  send_stat(full_stat)
@@ -0,0 +1,56 @@
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 << NEW_LINE
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
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ class Connection
6
+ def initialize(telemetry)
7
+ @telemetry = telemetry
8
+ end
9
+
10
+ # Close the underlying socket
11
+ def close
12
+ @socket && @socket.close
13
+ end
14
+
15
+ def write(payload)
16
+ logger.debug { "Statsd: #{payload}" } if logger
17
+ flush_telemetry = @telemetry.flush?
18
+ if flush_telemetry
19
+ payload += @telemetry.flush()
20
+ end
21
+
22
+ send_message(payload)
23
+
24
+ if flush_telemetry
25
+ @telemetry.reset
26
+ end
27
+
28
+ telemetry.bytes_sent += payload.length
29
+ telemetry.packets_sent += 1
30
+ rescue StandardError => boom
31
+ # Try once to reconnect if the socket has been closed
32
+ retries ||= 1
33
+ if retries <= 1 &&
34
+ (boom.is_a?(Errno::ENOTCONN) or
35
+ boom.is_a?(Errno::ECONNREFUSED) or
36
+ boom.is_a?(IOError) && boom.message =~ /closed stream/i)
37
+ retries += 1
38
+ begin
39
+ @socket = connect
40
+ retry
41
+ rescue StandardError => e
42
+ boom = e
43
+ end
44
+ end
45
+
46
+ telemetry.bytes_dropped += payload.length
47
+ telemetry.packets_dropped += 1
48
+ logger.error { "Statsd: #{boom.class} #{boom}" } if logger
49
+ nil
50
+ end
51
+
52
+ private
53
+
54
+ attr_reader :telemetry
55
+ attr_reader :logger
56
+
57
+ def socket
58
+ @socket ||= connect
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+ require 'time'
3
+
4
+ module Datadog
5
+ class Statsd
6
+ class Telemetry
7
+ attr_accessor :metrics
8
+ attr_accessor :events
9
+ attr_accessor :service_checks
10
+ attr_accessor :bytes_sent
11
+ attr_accessor :bytes_dropped
12
+ attr_accessor :packets_sent
13
+ attr_accessor :packets_dropped
14
+ attr_reader :estimate_max_size
15
+
16
+ def initialize(disabled, tags, flush_interval)
17
+ @disabled = disabled
18
+ @tags = tags
19
+ @flush_interval = flush_interval
20
+ reset
21
+
22
+ # estimate_max_size is an estimation or the maximum size of the
23
+ # telemetry payload. Since we don't want our packet to go over
24
+ # 'max_buffer_bytes', we have to adjust with the size of the telemetry
25
+ # (and any tags used). The telemetry payload size will change depending
26
+ # on the actual value of metrics: metrics received, packet dropped,
27
+ # etc. This is why we add a 63bytes margin: 9 bytes for each of the 7
28
+ # telemetry metrics.
29
+ @estimate_max_size = @disabled ? 0 : flush().length + 9 * 7
30
+ end
31
+
32
+ def reset
33
+ @metrics = 0
34
+ @events = 0
35
+ @service_checks = 0
36
+ @bytes_sent = 0
37
+ @bytes_dropped = 0
38
+ @packets_sent = 0
39
+ @packets_dropped = 0
40
+ @next_flush_time = Time.now.to_i + @flush_interval
41
+ end
42
+
43
+ def flush?
44
+ if @next_flush_time < Time.now.to_i
45
+ return true
46
+ end
47
+ return false
48
+ end
49
+
50
+ def flush
51
+ return '' if @disabled
52
+
53
+ # using shorthand syntax to reduce the garbage collection
54
+ return %Q(
55
+ datadog.dogstatsd.client.metrics:#{@metrics}|#{COUNTER_TYPE}|##{@tags}
56
+ datadog.dogstatsd.client.events:#{@events}|#{COUNTER_TYPE}|##{@tags}
57
+ datadog.dogstatsd.client.service_checks:#{@service_checks}|#{COUNTER_TYPE}|##{@tags}
58
+ datadog.dogstatsd.client.bytes_sent:#{@bytes_sent}|#{COUNTER_TYPE}|##{@tags}
59
+ datadog.dogstatsd.client.bytes_dropped:#{@bytes_dropped}|#{COUNTER_TYPE}|##{@tags}
60
+ datadog.dogstatsd.client.packets_sent:#{@packets_sent}|#{COUNTER_TYPE}|##{@tags}
61
+ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{@tags})
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'connection'
4
+
5
+ module Datadog
6
+ class Statsd
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.
12
+ attr_reader :host
13
+
14
+ # StatsD port. Defaults to 8125.
15
+ attr_reader :port
16
+
17
+ def initialize(host, port, logger, telemetry)
18
+ super(telemetry)
19
+ @host = host || ENV.fetch('DD_AGENT_HOST', DEFAULT_HOST)
20
+ @port = port || ENV.fetch('DD_DOGSTATSD_PORT', DEFAULT_PORT)
21
+ @logger = logger
22
+ end
23
+
24
+ private
25
+
26
+ def connect
27
+ UDPSocket.new.tap do |socket|
28
+ socket.connect(host, port)
29
+ end
30
+ end
31
+
32
+ def send_message(message)
33
+ socket.send(message, 0)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'connection'
4
+
5
+ module Datadog
6
+ class Statsd
7
+ class UDSConnection < Connection
8
+ class BadSocketError < StandardError; end
9
+
10
+ # DogStatsd unix socket path
11
+ attr_reader :socket_path
12
+
13
+ def initialize(socket_path, logger, telemetry)
14
+ super(telemetry)
15
+ @socket_path = socket_path
16
+ @logger = logger
17
+ end
18
+
19
+ private
20
+
21
+ def connect
22
+ socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
23
+ socket.connect(Socket.pack_sockaddr_un(@socket_path))
24
+ socket
25
+ end
26
+
27
+ def send_message(message)
28
+ socket.sendmsg_nonblock(message)
29
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENOENT => e
30
+ @socket = nil
31
+ raise BadSocketError, "#{e.class}: #{e}"
32
+ end
33
+ end
34
+ end
35
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dogstatsd-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.6.0
4
+ version: 4.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rein Henrichs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-20 00:00:00.000000000 Z
11
+ date: 2020-02-14 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A Ruby DogStastd client
14
14
  email: code@datadoghq.com
@@ -21,14 +21,19 @@ files:
21
21
  - LICENSE.txt
22
22
  - README.md
23
23
  - lib/datadog/statsd.rb
24
+ - lib/datadog/statsd/batch.rb
25
+ - lib/datadog/statsd/connection.rb
26
+ - lib/datadog/statsd/telemetry.rb
27
+ - lib/datadog/statsd/udp_connection.rb
28
+ - lib/datadog/statsd/uds_connection.rb
24
29
  homepage: https://github.com/DataDog/dogstatsd-ruby
25
30
  licenses:
26
31
  - MIT
27
32
  metadata:
28
33
  bug_tracker_uri: https://github.com/DataDog/dogstatsd-ruby/issues
29
- changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v4.6.0/CHANGELOG.md
30
- documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/4.6.0
31
- source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v4.6.0
34
+ changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v4.7.0/CHANGELOG.md
35
+ documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/4.7.0
36
+ source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v4.7.0
32
37
  post_install_message:
33
38
  rdoc_options: []
34
39
  require_paths: