dogstatsd-ruby 4.7.0 → 5.0.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: 57d7660dad73ad8c0c5f37f6d6130474d255beaa467aca03f532e552d0b608e0
4
- data.tar.gz: 30c8686d3045e4cfb2ca18c57390f78dbcccba1ff941deb170d89497953d5e84
3
+ metadata.gz: f9f9d5b8de35189b467aae9471e7dcbd8a2913bfa94a0ebea428c4088ed1bf6e
4
+ data.tar.gz: 5a8ec414ba8e7b97dc5ff2d720b183ae12ce3e8f32dd022889276e02fa852ef2
5
5
  SHA512:
6
- metadata.gz: 99214bb827186d3b01568cc59064f7d2a8d043a4a739c7f90a2b013feeeead11ed363937c37d2c29846d101ed886c318a6369c2d5a57292b0c058db81c708813
7
- data.tar.gz: c82e6c5e9668804688fd308e091398e05ada88dac8ea78a678ee2da671eaf297ffd95997efaf376506632f65e88b4096a6a9b2590667f772f19c6b020e238827
6
+ metadata.gz: 0a89bc622a5fcb9c5f2376aa0573a3af843b3ce4952505b7d2076ef6f1ee58f6a8d2ef9b2498f919327bae37f05d43970c975a14a654019bef21cf26153d0080
7
+ data.tar.gz: 7efd1aaf9d2b4a6795422ad012da1226c43ed3c85aca1242d8c7defee5ef1d1e5decc34d0a5ce767f824cd8f3ad84182ea0b8c56c2fb0e5f0d1bb28b79cc52a8
data/README.md CHANGED
@@ -71,9 +71,22 @@ After the client is created, you can start sending events to your Datadog Event
71
71
 
72
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.
73
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
+
74
87
  ## Credits
75
88
 
76
- dogstatsd-ruby is forked from Rien Henrichs [original Statsd
89
+ dogstatsd-ruby is forked from Rein Henrichs [original Statsd
77
90
  client](https://github.com/reinh/statsd).
78
91
 
79
92
  Copyright (c) 2011 Rein Henrichs. See LICENSE.txt for
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
  require 'socket'
3
3
 
4
+ require_relative 'statsd/version'
4
5
  require_relative 'statsd/telemetry'
5
6
  require_relative 'statsd/udp_connection'
6
7
  require_relative 'statsd/uds_connection'
7
- require_relative 'statsd/batch'
8
+ require_relative 'statsd/message_buffer'
9
+ require_relative 'statsd/serialization'
10
+ require_relative 'statsd/sender'
11
+ require_relative 'statsd/forwarder'
8
12
 
9
13
  # = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
10
14
  #
@@ -24,31 +28,17 @@ require_relative 'statsd/batch'
24
28
  # statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
25
29
  module Datadog
26
30
  class Statsd
27
- # Create a dictionary to assign a key to every parameter's name, except for tags (treated differently)
28
- # Goal: Simple and fast to add some other parameters
29
- OPTS_KEYS = {
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
37
-
38
- # Service check options
39
- SC_OPT_KEYS = {
40
- timestamp: 'd:',
41
- hostname: 'h:',
42
- tags: '#',
43
- message: 'm:',
44
- }.freeze
31
+ class Error < StandardError
32
+ end
45
33
 
46
34
  OK = 0
47
35
  WARNING = 1
48
36
  CRITICAL = 2
49
37
  UNKNOWN = 3
50
38
 
51
- DEFAULT_BUFFER_SIZE = 8 * 1_024
39
+ UDP_DEFAULT_BUFFER_SIZE = 1_432
40
+ UDS_DEFAULT_BUFFER_SIZE = 8_192
41
+ DEFAULT_BUFFER_POOL_SIZE = Float::INFINITY
52
42
  MAX_EVENT_SIZE = 8 * 1_024
53
43
  # minimum flush interval for the telemetry in seconds
54
44
  DEFAULT_TELEMETRY_FLUSH_INTERVAL = 10
@@ -59,80 +49,68 @@ module Datadog
59
49
  DISTRIBUTION_TYPE = 'd'
60
50
  TIMING_TYPE = 'ms'
61
51
  SET_TYPE = 's'
62
- VERSION = '4.7.0'
63
52
 
64
53
  # A namespace to prepend to all statsd calls. Defaults to no namespace.
65
54
  attr_reader :namespace
66
55
 
67
56
  # Global tags to be added to every statsd call. Defaults to no tags.
68
- attr_reader :tags
69
-
70
- # Buffer containing the statsd message before they are sent in batch
71
- attr_reader :buffer
72
-
73
- # Maximum buffer size in bytes before it is flushed
74
- attr_reader :max_buffer_bytes
57
+ def tags
58
+ serializer.global_tags
59
+ end
75
60
 
76
61
  # Default sample rate
77
62
  attr_reader :sample_rate
78
63
 
79
- # Connection
80
- attr_reader :connection
81
-
82
64
  # @param [String] host your statsd host
83
65
  # @param [Integer] port your statsd port
84
66
  # @option [String] namespace set a namespace to be prepended to every metric name
85
67
  # @option [Array<String>|Hash] tags tags to be added to every metric
86
68
  # @option [Logger] logger for debugging
87
- # @option [Integer] max_buffer_bytes max bytes to buffer when using #batch
69
+ # @option [Integer] buffer_max_payload_size max bytes to buffer
70
+ # @option [Integer] buffer_max_pool_size max messages to buffer
88
71
  # @option [String] socket_path unix socket path
89
72
  # @option [Float] default sample rate if not overridden
90
73
  def initialize(
91
74
  host = nil,
92
75
  port = nil,
76
+ socket_path: nil,
77
+
93
78
  namespace: nil,
94
79
  tags: nil,
95
- max_buffer_bytes: DEFAULT_BUFFER_SIZE,
96
- socket_path: nil,
97
- logger: nil,
98
80
  sample_rate: nil,
99
- disable_telemetry: false,
100
- telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
101
- )
102
- unless tags.nil? || tags.is_a?(Array) || tags.is_a?(Hash)
103
- raise ArgumentError, 'tags must be a Array<String> or a Hash'
104
- end
105
81
 
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
82
+ buffer_max_payload_size: nil,
83
+ buffer_max_pool_size: nil,
84
+ buffer_overflowing_stategy: :drop,
110
85
 
111
- # append the entity id to tags if DD_ENTITY_ID env var is not 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
116
-
117
- # init telemetry
118
- transport_type = socket_path.nil? ? 'udp': 'uds'
119
- telemetry_tags = (["client:ruby", "client_version:#{VERSION}", "client_transport:#{transport_type}"] + @tags).join(COMMA).freeze
120
- @telemetry = Telemetry.new(disable_telemetry, telemetry_tags, telemetry_flush_interval)
86
+ logger: nil,
121
87
 
122
- if socket_path.nil?
123
- @connection = UDPConnection.new(host, port, logger, @telemetry)
124
- else
125
- @connection = UDSConnection.new(socket_path, logger, @telemetry)
88
+ telemetry_enable: true,
89
+ telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
90
+ )
91
+ unless tags.nil? || tags.is_a?(Array) || tags.is_a?(Hash)
92
+ raise ArgumentError, 'tags must be an array of string tags or a Hash'
126
93
  end
127
- @logger = logger
128
94
 
129
95
  @namespace = namespace
130
96
  @prefix = @namespace ? "#{@namespace}.".freeze : nil
131
-
97
+ @serializer = Serialization::Serializer.new(prefix: @prefix, global_tags: tags)
132
98
  @sample_rate = sample_rate
133
99
 
134
- # we reduce max_buffer_bytes by a the rough estimate of the telemetry payload
135
- @batch = Batch.new(@connection, (max_buffer_bytes - @telemetry.estimate_max_size))
100
+ @forwarder = Forwarder.new(
101
+ host: host,
102
+ port: port,
103
+ socket_path: socket_path,
104
+
105
+ global_tags: tags,
106
+ logger: logger,
107
+
108
+ buffer_max_payload_size: buffer_max_payload_size,
109
+ buffer_max_pool_size: buffer_max_pool_size,
110
+ buffer_overflowing_stategy: buffer_overflowing_stategy,
111
+
112
+ telemetry_flush_interval: telemetry_enable ? telemetry_flush_interval : nil,
113
+ )
136
114
  end
137
115
 
138
116
  # yield a new instance to a block and close it when done
@@ -259,20 +237,10 @@ module Datadog
259
237
  # $statsd.time('account.activate') { @account.activate! }
260
238
  def time(stat, opts = EMPTY_OPTIONS)
261
239
  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
240
+ start = now
267
241
  yield
268
242
  ensure
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
-
275
- timing(stat, ((finished - start) * 1000).round, opts)
243
+ timing(stat, ((now - start) * 1000).round, opts)
276
244
  end
277
245
 
278
246
  # Sends a value to be tracked as a set to the statsd server.
@@ -301,8 +269,9 @@ module Datadog
301
269
  # @example Report a critical service check status
302
270
  # $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
303
271
  def service_check(name, status, opts = EMPTY_OPTIONS)
304
- @telemetry.service_checks += 1
305
- send_stat(format_service_check(name, status, opts))
272
+ telemetry.sent(service_checks: 1) if telemetry
273
+
274
+ forwarder.send_message(serializer.to_service_check(name, status, opts))
306
275
  end
307
276
 
308
277
  # This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
@@ -320,178 +289,77 @@ module Datadog
320
289
  # @option opts [String, nil] :priority ('normal') Can be "normal" or "low"
321
290
  # @option opts [String, nil] :source_type_name (nil) Assign a source type to the event
322
291
  # @option opts [String, nil] :alert_type ('info') Can be "error", "warning", "info" or "success".
292
+ # @option opts [Boolean, false] :truncate_if_too_long (false) Truncate the event if it is too long
323
293
  # @option opts [Array<String>] :tags tags to be added to every metric
324
294
  # @example Report an awful event:
325
295
  # $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
326
296
  def event(title, text, opts = EMPTY_OPTIONS)
327
- @telemetry.events += 1
328
- send_stat(format_event(title, text, opts))
329
- end
297
+ telemetry.sent(events: 1) if telemetry
330
298
 
331
- # Send several metrics in the same UDP Packet
332
- # They will be buffered and flushed when the block finishes
333
- #
334
- # @example Send several metrics in one packet:
335
- # $statsd.batch do |s|
336
- # s.gauge('users.online',156)
337
- # s.increment('page.views')
338
- # end
339
- def batch
340
- @batch.open do
341
- yield self
342
- end
299
+ forwarder.send_message(serializer.to_event(title, text, opts))
343
300
  end
344
301
 
345
302
  # Close the underlying socket
346
303
  def close
347
- @connection.close
304
+ forwarder.close
348
305
  end
349
306
 
350
- private
351
-
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')
360
- EMPTY_OPTIONS = {}.freeze
361
-
362
- private_constant :NEW_LINE, :ESC_NEW_LINE, :COMMA, :PIPE, :DOT,
363
- :DOUBLE_COLON, :UNDERSCORE, :EMPTY_OPTIONS
364
-
365
- def format_service_check(name, status, opts = EMPTY_OPTIONS)
366
- sc_string = "_sc|#{name}|#{status}".dup
367
-
368
- SC_OPT_KEYS.each do |key, shorthand_key|
369
- next unless opts[key]
370
-
371
- if key == :tags
372
- if tags_string = tags_as_string(opts)
373
- sc_string << "|##{tags_string}"
374
- end
375
- elsif key == :message
376
- message = remove_pipes(opts[:message])
377
- escaped_message = escape_service_check_message(message)
378
- sc_string << "|m:#{escaped_message}"
379
- else
380
- if key == :timestamp && opts[key].is_a?(Integer)
381
- value = opts[key]
382
- else
383
- value = remove_pipes(opts[key])
384
- end
385
- sc_string << "|#{shorthand_key}#{value}"
386
- end
387
- end
388
- sc_string
307
+ def sync_with_outbound_io
308
+ forwarder.sync_with_outbound_io
389
309
  end
390
310
 
391
- def format_event(title, text, opts = EMPTY_OPTIONS)
392
- escaped_title = escape_event_content(title)
393
- escaped_text = escape_event_content(text)
394
- event_string_data = "_e{#{escaped_title.bytesize},#{escaped_text.bytesize}}:#{escaped_title}|#{escaped_text}".dup
395
-
396
- # We construct the string to be sent by adding '|key:value' parts to it when needed
397
- # All pipes ('|') in the metadata are removed. Title and Text can keep theirs
398
- OPTS_KEYS.each do |key, shorthand_key|
399
- if key != :tags && opts[key]
400
- # :date_happened is the only key where the value is an Integer
401
- # To not break backwards compatibility, we still accept a String
402
- if key == :date_happened && opts[key].is_a?(Integer)
403
- value = opts[key]
404
- # All other keys only have String values
405
- else
406
- value = remove_pipes(opts[key])
407
- end
408
- event_string_data << "|#{shorthand_key}:#{value}"
409
- end
410
- end
411
-
412
- # Tags are joined and added as last part to the string to be sent
413
- if tags_string = tags_as_string(opts)
414
- event_string_data << "|##{tags_string}"
415
- end
416
-
417
- if event_string_data.bytesize > MAX_EVENT_SIZE
418
- raise "Event #{title} payload is too big (more that 8KB), event discarded"
419
- end
420
- event_string_data
311
+ # Flush the buffer into the connection
312
+ def flush(flush_telemetry: false, sync: false)
313
+ forwarder.flush(flush_telemetry: flush_telemetry, sync: sync)
421
314
  end
422
315
 
423
- def tags_as_string(opts)
424
- if tag_arr = opts[:tags]
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
429
- tag_arr = tags + tag_arr # @tags are normalized when set, so not need to normalize them again
430
- else
431
- tag_arr = tags
432
- end
433
- tag_arr.join(COMMA) unless tag_arr.empty?
316
+ def telemetry
317
+ forwarder.telemetry
434
318
  end
435
319
 
436
- def tag_hash_to_array(tag_hash)
437
- tag_hash.to_a.map do |pair|
438
- pair.compact.join(':')
439
- end
320
+ def host
321
+ forwarder.host
440
322
  end
441
323
 
442
- def escape_event_content(message)
443
- message.gsub(NEW_LINE, ESC_NEW_LINE)
324
+ def port
325
+ forwarder.port
444
326
  end
445
327
 
446
- def escape_tag_content(tag)
447
- tag = remove_pipes(tag.to_s)
448
- tag.delete!(COMMA)
449
- tag
328
+ def socket_path
329
+ forwarder.socket_path
450
330
  end
451
331
 
452
- def remove_pipes(message)
453
- message.delete(PIPE)
332
+ def transport_type
333
+ forwarder.transport_type
454
334
  end
455
335
 
456
- def escape_service_check_message(message)
457
- escape_event_content(message).gsub('m:', 'm\:')
336
+ private
337
+ attr_reader :serializer
338
+ attr_reader :forwarder
339
+
340
+ PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= '2.1.0')
341
+ EMPTY_OPTIONS = {}.freeze
342
+
343
+ if PROCESS_TIME_SUPPORTED
344
+ def now
345
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
346
+ end
347
+ else
348
+ def now
349
+ Time.now.to_f
350
+ end
458
351
  end
459
352
 
460
353
  def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
461
- @telemetry.metrics += 1
354
+ telemetry.sent(metrics: 1) if telemetry
355
+
462
356
  sample_rate = opts[:sample_rate] || @sample_rate || 1
357
+
463
358
  if sample_rate == 1 || rand <= sample_rate
464
- full_stat = ''.dup
465
- full_stat << @prefix if @prefix
466
-
467
- stat = stat.is_a?(String) ? stat.dup : stat.to_s
468
- # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
469
- stat.gsub!(DOUBLE_COLON, DOT)
470
- stat.tr!(':|@', UNDERSCORE)
471
- full_stat << stat
472
-
473
- full_stat << ':'
474
- full_stat << delta.to_s
475
- full_stat << PIPE
476
- full_stat << type
477
-
478
- unless sample_rate == 1
479
- full_stat << PIPE
480
- full_stat << '@'
481
- full_stat << sample_rate.to_s
482
- end
483
-
484
- if tags_string = tags_as_string(opts)
485
- full_stat << PIPE
486
- full_stat << '#'
487
- full_stat << tags_string
488
- end
489
- send_stat(full_stat)
490
- end
491
- end
359
+ full_stat = serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
492
360
 
493
- def send_stat(message)
494
- @batch.open? ? @batch.add(message) : @connection.write(message)
361
+ forwarder.send_message(full_stat)
362
+ end
495
363
  end
496
364
  end
497
365
  end