dogstatsd-ruby 4.6.0 → 4.8.3

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: dc2f6d365efc8cf25456fb9dbd5ad526abe0ec61f2b63e9b79346c767aa3a3b0
4
+ data.tar.gz: 84a029741390b63cf0540de60a872c6b0820bf6ad4c1619cea120d71bc38c3bc
5
5
  SHA512:
6
- metadata.gz: f43fb9314373873b5d436824a3cae0a5dc0ef4fcc2a458b8a98b98dbd8b12cb28d3b32293436451829a53da8cb70f17c6104aa64403cfb6000822e59a74ca29a
7
- data.tar.gz: a796456e38da22047d952b7a1bebaf1f923a0847c29a9e7548020c8b658144e729133e7e90a16453d0f48a815353734523870e2e391371d8699f1d6574bd8379
6
+ metadata.gz: 769645a11d455fceb1b0c4d7d718005d869c62546cd1c255f0a45b9da51a300500dfe2c1a4e601248f1e8ef6ce1e40c15ebaca55d6e55092babef1c153ccdcf0
7
+ data.tar.gz: 1429d390a2c52758f957dd4766cf30db5039986527982f3188fcc44d7c058431d9f85180b1af7e5dae8f82db31325115b0ea4efb1fced82d7585924b0702fd0c
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
 
@@ -66,6 +71,19 @@ After the client is created, you can start sending events to your Datadog Event
66
71
 
67
72
  After the client is created, you can start sending Service Checks to Datadog. See the dedicated [Service Check Submission: DogStatsD documentation](https://docs.datadoghq.com/developers/service_checks/dogstatsd_service_checks_submission/?tab=ruby) to see how to submit a Service Check to Datadog.
68
73
 
74
+ ### Maximum packets size in high-throughput scenarios
75
+
76
+ In order to have the most efficient use of this library in high-throughput scenarios,
77
+ default values for the maximum packets size have already been set for both UDS (8192 bytes)
78
+ and UDP (1432 bytes) in order to have the best usage of the underlying network.
79
+ However, if you perfectly know your network and you know that a different value for the maximum packets
80
+ size should be used, you can set it with the parameter `buffer_max_payload_size`. Example:
81
+
82
+ ```ruby
83
+ # Create a DogStatsD client instance.
84
+ statsd = Datadog::Statsd.new('localhost', 8125, buffer_max_payload_size: 4096)
85
+ ```
86
+
69
87
  ## Credits
70
88
 
71
89
  dogstatsd-ruby is forked from Rien Henrichs [original Statsd
@@ -1,6 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
  require 'socket'
3
3
 
4
+ require_relative 'statsd/version'
5
+ require_relative 'statsd/telemetry'
6
+ require_relative 'statsd/udp_connection'
7
+ require_relative 'statsd/uds_connection'
8
+ require_relative 'statsd/batch'
9
+ require_relative 'statsd/serialization'
10
+
4
11
  # = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
5
12
  #
6
13
  # @example Set up a global Statsd client for a server on localhost:8125
@@ -19,252 +26,30 @@ require 'socket'
19
26
  # statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
20
27
  module Datadog
21
28
  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
- # Create a dictionary to assign a key to every parameter's name, except for tags (treated differently)
229
- # Goal: Simple and fast to add some other parameters
230
- 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
- }
238
-
239
- # Service check options
240
- SC_OPT_KEYS = {
241
- :timestamp => 'd:'.freeze,
242
- :hostname => 'h:'.freeze,
243
- :tags => '#'.freeze,
244
- :message => 'm:'.freeze,
245
- }
246
-
247
- OK = 0
248
- WARNING = 1
249
- CRITICAL = 2
250
- UNKNOWN = 3
29
+ OK = 0
30
+ WARNING = 1
31
+ CRITICAL = 2
32
+ UNKNOWN = 3
251
33
 
252
34
  DEFAULT_BUFFER_SIZE = 8 * 1_024
253
35
  MAX_EVENT_SIZE = 8 * 1_024
36
+ # minimum flush interval for the telemetry in seconds
37
+ DEFAULT_TELEMETRY_FLUSH_INTERVAL = 10
254
38
 
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
39
+ COUNTER_TYPE = 'c'
40
+ GAUGE_TYPE = 'g'
41
+ HISTOGRAM_TYPE = 'h'
42
+ DISTRIBUTION_TYPE = 'd'
43
+ TIMING_TYPE = 'ms'
44
+ SET_TYPE = 's'
262
45
 
263
46
  # A namespace to prepend to all statsd calls. Defaults to no namespace.
264
47
  attr_reader :namespace
265
48
 
266
49
  # Global tags to be added to every statsd call. Defaults to no tags.
267
- attr_reader :tags
50
+ def tags
51
+ serializer.global_tags
52
+ end
268
53
 
269
54
  # Buffer containing the statsd message before they are sent in batch
270
55
  attr_reader :buffer
@@ -295,43 +80,45 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
295
80
  socket_path: nil,
296
81
  logger: nil,
297
82
  sample_rate: nil,
298
- disable_telemetry: false
83
+ disable_telemetry: false,
84
+ telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
299
85
  )
300
- unless tags.nil? or tags.is_a? Array or tags.is_a? Hash
86
+ unless tags.nil? || tags.is_a?(Array) || tags.is_a?(Hash)
301
87
  raise ArgumentError, 'tags must be a Array<String> or a Hash'
302
88
  end
303
89
 
304
- tags = tag_hash_to_array(tags) if tags.is_a? Hash
305
- @tags = (tags || []).compact.map! {|tag| escape_tag_content(tag)}
90
+ @namespace = namespace
91
+ @prefix = @namespace ? "#{@namespace}.".freeze : nil
306
92
 
307
- # 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?
93
+ @serializer = Serialization::Serializer.new(prefix: @prefix, global_tags: tags)
309
94
 
310
- # init telemetry
311
- transport_type = socket_path.nil? ? "udp": "uds"
312
- telemetry_tags = (["client:ruby", "client_version:#{VERSION}", "client_transport:#{transport_type}"] + @tags).join(COMMA).freeze
313
- @telemetry = Telemetry.new(disable_telemetry, telemetry_tags)
95
+ transport_type = socket_path.nil? ? :udp : :uds
314
96
 
315
- if socket_path.nil?
316
- @connection = UDPConnection.new(host, port, logger, @telemetry)
317
- else
318
- @connection = UDSConnection.new(socket_path, logger, @telemetry)
319
- end
320
- @logger = logger
97
+ @telemetry = Telemetry.new(disable_telemetry, telemetry_flush_interval,
98
+ global_tags: tags,
99
+ transport_type: transport_type
100
+ )
321
101
 
322
- @namespace = namespace
323
- @prefix = @namespace ? "#{@namespace}.".freeze : nil
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
108
+
109
+ @logger = logger
324
110
 
325
111
  @sample_rate = sample_rate
326
112
 
327
113
  # we reduce max_buffer_bytes by a the rough estimate of the telemetry payload
328
- @batch = Batch.new(@connection, (max_buffer_bytes - @telemetry.estimate_max_size))
114
+ @batch = Batch.new(connection, (max_buffer_bytes - telemetry.estimate_max_size))
329
115
  end
330
116
 
331
117
  # yield a new instance to a block and close it when done
332
118
  # for short-term use-cases that don't want to close the socket manually
333
119
  def self.open(*args)
334
120
  instance = new(*args)
121
+
335
122
  yield instance
336
123
  ensure
337
124
  instance.close
@@ -345,10 +132,10 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
345
132
  # @option opts [Array<String>] :tags An array of tags
346
133
  # @option opts [Numeric] :by increment value, default 1
347
134
  # @see #count
348
- def increment(stat, opts=EMPTY_OPTIONS)
349
- opts = {:sample_rate => opts} if opts.is_a? Numeric
135
+ def increment(stat, opts = EMPTY_OPTIONS)
136
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
350
137
  incr_value = opts.fetch(:by, 1)
351
- count stat, incr_value, opts
138
+ count(stat, incr_value, opts)
352
139
  end
353
140
 
354
141
  # Sends a decrement (count = -1) for the given stat to the statsd server.
@@ -359,10 +146,10 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
359
146
  # @option opts [Array<String>] :tags An array of tags
360
147
  # @option opts [Numeric] :by decrement value, default 1
361
148
  # @see #count
362
- def decrement(stat, opts=EMPTY_OPTIONS)
363
- opts = {:sample_rate => opts} if opts.is_a? Numeric
149
+ def decrement(stat, opts = EMPTY_OPTIONS)
150
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
364
151
  decr_value = - opts.fetch(:by, 1)
365
- count stat, decr_value, opts
152
+ count(stat, decr_value, opts)
366
153
  end
367
154
 
368
155
  # Sends an arbitrary count for the given stat to the statsd server.
@@ -372,9 +159,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
372
159
  # @param [Hash] opts the options to create the metric with
373
160
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
374
161
  # @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
162
+ def count(stat, count, opts = EMPTY_OPTIONS)
163
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
164
+ send_stats(stat, count, COUNTER_TYPE, opts)
378
165
  end
379
166
 
380
167
  # Sends an arbitary gauge value for the given stat to the statsd server.
@@ -390,9 +177,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
390
177
  # @option opts [Array<String>] :tags An array of tags
391
178
  # @example Report the current user count:
392
179
  # $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
180
+ def gauge(stat, value, opts = EMPTY_OPTIONS)
181
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
182
+ send_stats(stat, value, GAUGE_TYPE, opts)
396
183
  end
397
184
 
398
185
  # Sends a value to be tracked as a histogram to the statsd server.
@@ -404,8 +191,8 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
404
191
  # @option opts [Array<String>] :tags An array of tags
405
192
  # @example Report the current user count:
406
193
  # $statsd.histogram('user.count', User.count)
407
- def histogram(stat, value, opts=EMPTY_OPTIONS)
408
- send_stats stat, value, HISTOGRAM_TYPE, opts
194
+ def histogram(stat, value, opts = EMPTY_OPTIONS)
195
+ send_stats(stat, value, HISTOGRAM_TYPE, opts)
409
196
  end
410
197
 
411
198
  # Sends a value to be tracked as a distribution to the statsd server.
@@ -417,8 +204,8 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
417
204
  # @option opts [Array<String>] :tags An array of tags
418
205
  # @example Report the current user count:
419
206
  # $statsd.distribution('user.count', User.count)
420
- def distribution(stat, value, opts=EMPTY_OPTIONS)
421
- send_stats stat, value, DISTRIBUTION_TYPE, opts
207
+ def distribution(stat, value, opts = EMPTY_OPTIONS)
208
+ send_stats(stat, value, DISTRIBUTION_TYPE, opts)
422
209
  end
423
210
 
424
211
  # Sends a timing (in ms) for the given stat to the statsd server. The
@@ -431,9 +218,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
431
218
  # @param [Hash] opts the options to create the metric with
432
219
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
433
220
  # @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
221
+ def timing(stat, ms, opts = EMPTY_OPTIONS)
222
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
223
+ send_stats(stat, ms, TIMING_TYPE, opts)
437
224
  end
438
225
 
439
226
  # Reports execution time of the provided block using {#timing}.
@@ -449,13 +236,12 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
449
236
  # @see #timing
450
237
  # @example Report the time (in ms) taken to activate an account
451
238
  # $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
239
+ def time(stat, opts = EMPTY_OPTIONS)
240
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
241
+ start = now
242
+ yield
456
243
  ensure
457
- finished = (PROCESS_TIME_SUPPORTED ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : Time.now.to_f)
458
- timing(stat, ((finished - start) * 1000).round, opts)
244
+ timing(stat, ((now - start) * 1000).round, opts)
459
245
  end
460
246
 
461
247
  # Sends a value to be tracked as a set to the statsd server.
@@ -467,9 +253,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
467
253
  # @option opts [Array<String>] :tags An array of tags
468
254
  # @example Record a unique visitory by id:
469
255
  # $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
256
+ def set(stat, value, opts = EMPTY_OPTIONS)
257
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
258
+ send_stats(stat, value, SET_TYPE, opts)
473
259
  end
474
260
 
475
261
  # This method allows you to send custom service check statuses.
@@ -483,9 +269,10 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
483
269
  # @option opts [String, nil] :message (nil) A message to associate with this service check status
484
270
  # @example Report a critical service check status
485
271
  # $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
486
- def service_check(name, status, opts=EMPTY_OPTIONS)
487
- @telemetry.service_checks += 1
488
- send_stat format_service_check(name, status, opts)
272
+ def service_check(name, status, opts = EMPTY_OPTIONS)
273
+ telemetry.sent(service_checks: 1)
274
+
275
+ send_stat(serializer.to_service_check(name, status, opts))
489
276
  end
490
277
 
491
278
  # This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
@@ -503,12 +290,14 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
503
290
  # @option opts [String, nil] :priority ('normal') Can be "normal" or "low"
504
291
  # @option opts [String, nil] :source_type_name (nil) Assign a source type to the event
505
292
  # @option opts [String, nil] :alert_type ('info') Can be "error", "warning", "info" or "success".
293
+ # @option opts [Boolean, false] :truncate_if_too_long (false) Truncate the event if it is too long
506
294
  # @option opts [Array<String>] :tags tags to be added to every metric
507
295
  # @example Report an awful event:
508
296
  # $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)
510
- @telemetry.events += 1
511
- send_stat format_event(title, text, opts)
297
+ def event(title, text, opts = EMPTY_OPTIONS)
298
+ telemetry.sent(events: 1)
299
+
300
+ send_stat(serializer.to_event(title, text, opts))
512
301
  end
513
302
 
514
303
  # Send several metrics in the same UDP Packet
@@ -520,153 +309,51 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
520
309
  # s.increment('page.views')
521
310
  # end
522
311
  def batch
523
- @batch.open { yield self }
312
+ @batch.open do
313
+ yield self
314
+ end
524
315
  end
525
316
 
526
317
  # Close the underlying socket
527
318
  def close
528
- @connection.close
319
+ connection.close
529
320
  end
530
321
 
531
322
  private
323
+ attr_reader :serializer
324
+ attr_reader :telemetry
532
325
 
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")
326
+ PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= '2.1.0')
541
327
  EMPTY_OPTIONS = {}.freeze
542
328
 
543
- private_constant :NEW_LINE, :ESC_NEW_LINE, :COMMA, :PIPE, :DOT,
544
- :DOUBLE_COLON, :UNDERSCORE, :EMPTY_OPTIONS
545
-
546
- def format_service_check(name, status, opts=EMPTY_OPTIONS)
547
- sc_string = "_sc|#{name}|#{status}".dup
548
-
549
- SC_OPT_KEYS.each do |key, shorthand_key|
550
- next unless opts[key]
551
-
552
- if key == :tags
553
- if tags_string = tags_as_string(opts)
554
- sc_string << "|##{tags_string}"
555
- end
556
- elsif key == :message
557
- message = remove_pipes(opts[:message])
558
- escaped_message = escape_service_check_message(message)
559
- sc_string << "|m:#{escaped_message}"
560
- else
561
- if key == :timestamp && opts[key].is_a?(Integer)
562
- value = opts[key]
563
- else
564
- value = remove_pipes(opts[key])
565
- end
566
- sc_string << "|#{shorthand_key}#{value}"
567
- end
568
- end
569
- sc_string
570
- end
571
-
572
- def format_event(title, text, opts=EMPTY_OPTIONS)
573
- escaped_title = escape_event_content(title)
574
- escaped_text = escape_event_content(text)
575
- event_string_data = "_e{#{escaped_title.bytesize},#{escaped_text.bytesize}}:#{escaped_title}|#{escaped_text}".dup
576
-
577
- # We construct the string to be sent by adding '|key:value' parts to it when needed
578
- # All pipes ('|') in the metadata are removed. Title and Text can keep theirs
579
- OPTS_KEYS.each do |key, shorthand_key|
580
- if key != :tags && opts[key]
581
- # :date_happened is the only key where the value is an Integer
582
- # To not break backwards compatibility, we still accept a String
583
- if key == :date_happened && opts[key].is_a?(Integer)
584
- value = opts[key]
585
- # All other keys only have String values
586
- else
587
- value = remove_pipes(opts[key])
588
- end
589
- event_string_data << "|#{shorthand_key}:#{value}"
590
- end
329
+ if PROCESS_TIME_SUPPORTED
330
+ def now
331
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
591
332
  end
592
-
593
- # Tags are joined and added as last part to the string to be sent
594
- if tags_string = tags_as_string(opts)
595
- event_string_data << "|##{tags_string}"
596
- end
597
-
598
- raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string_data.bytesize > MAX_EVENT_SIZE
599
- event_string_data
600
- end
601
-
602
- def tags_as_string(opts)
603
- 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) }
606
- tag_arr = tags + tag_arr # @tags are normalized when set, so not need to normalize them again
607
- else
608
- tag_arr = tags
333
+ else
334
+ def now
335
+ Time.now.to_f
609
336
  end
610
- tag_arr.join(COMMA) unless tag_arr.empty?
611
337
  end
612
338
 
613
- def tag_hash_to_array(tag_hash)
614
- tag_hash.to_a.map {|pair| pair.compact.join(":")}
615
- end
616
-
617
- def escape_event_content(msg)
618
- msg.gsub NEW_LINE, ESC_NEW_LINE
619
- end
339
+ def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
340
+ telemetry.sent(metrics: 1)
620
341
 
621
- def escape_tag_content(tag)
622
- tag = remove_pipes(tag.to_s)
623
- tag.delete! COMMA
624
- tag
625
- end
626
-
627
- def remove_pipes(msg)
628
- msg.delete PIPE
629
- end
342
+ sample_rate = opts[:sample_rate] || @sample_rate || 1
630
343
 
631
- def escape_service_check_message(msg)
632
- escape_event_content(msg).gsub('m:'.freeze, 'm\:'.freeze)
633
- end
344
+ if sample_rate == 1 || rand <= sample_rate
345
+ full_stat = serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
634
346
 
635
- def send_stats(stat, delta, type, opts=EMPTY_OPTIONS)
636
- @telemetry.metrics += 1
637
- sample_rate = opts[:sample_rate] || @sample_rate || 1
638
- if sample_rate == 1 or rand <= sample_rate
639
- full_stat = ''.dup
640
- full_stat << @prefix if @prefix
641
-
642
- stat = stat.is_a?(String) ? stat.dup : stat.to_s
643
- # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
644
- stat.gsub!(DOUBLE_COLON, DOT)
645
- stat.tr!(':|@'.freeze, UNDERSCORE)
646
- full_stat << stat
647
-
648
- full_stat << ':'.freeze
649
- full_stat << delta.to_s
650
- full_stat << PIPE
651
- full_stat << type
652
-
653
- unless sample_rate == 1
654
- full_stat << PIPE
655
- full_stat << '@'.freeze
656
- full_stat << sample_rate.to_s
657
- end
658
-
659
- if tags_string = tags_as_string(opts)
660
- full_stat << PIPE
661
- full_stat << '#'.freeze
662
- full_stat << tags_string
663
- end
664
347
  send_stat(full_stat)
665
348
  end
666
349
  end
667
350
 
668
351
  def send_stat(message)
669
- @batch.open? ? @batch.add(message) : @connection.write(message)
352
+ if @batch.open?
353
+ @batch.add(message)
354
+ else
355
+ @connection.write(message)
356
+ end
670
357
  end
671
358
  end
672
359
  end