dogstatsd-ruby 4.6.0 → 4.7.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: 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: