dogstatsd-ruby 4.7.0 → 4.8.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: 57d7660dad73ad8c0c5f37f6d6130474d255beaa467aca03f532e552d0b608e0
4
- data.tar.gz: 30c8686d3045e4cfb2ca18c57390f78dbcccba1ff941deb170d89497953d5e84
3
+ metadata.gz: 7485524dc133a21013d2ab5144511a4ba38740226bdeaf1d3131be99859f33ec
4
+ data.tar.gz: 6358ca98c85788c904e48ebf3d0d3751372b3ba5baaaf194199fef1f5badbae0
5
5
  SHA512:
6
- metadata.gz: 99214bb827186d3b01568cc59064f7d2a8d043a4a739c7f90a2b013feeeead11ed363937c37d2c29846d101ed886c318a6369c2d5a57292b0c058db81c708813
7
- data.tar.gz: c82e6c5e9668804688fd308e091398e05ada88dac8ea78a678ee2da671eaf297ffd95997efaf376506632f65e88b4096a6a9b2590667f772f19c6b020e238827
6
+ metadata.gz: edf3b324873893d4acfff6a8775ee7d3ca337e416f283f29137abbaed447f8db220d8d09cc00131a0a53b0d4f38555f48d669658812ebe739903dc68a74ab8c3
7
+ data.tar.gz: 9b5bd953d65e25ad74733175febb8a4a3b4dfee5e2353d1f539684443ddface8f65c9ebde4f88dfd5bd4063b892c2c76350754a112481bfeacb2f454a199dac9
@@ -1,10 +1,12 @@
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
8
  require_relative 'statsd/batch'
9
+ require_relative 'statsd/serialization'
8
10
 
9
11
  # = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
10
12
  #
@@ -24,25 +26,6 @@ require_relative 'statsd/batch'
24
26
  # statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
25
27
  module Datadog
26
28
  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
45
-
46
29
  OK = 0
47
30
  WARNING = 1
48
31
  CRITICAL = 2
@@ -59,13 +42,14 @@ module Datadog
59
42
  DISTRIBUTION_TYPE = 'd'
60
43
  TIMING_TYPE = 'ms'
61
44
  SET_TYPE = 's'
62
- VERSION = '4.7.0'
63
45
 
64
46
  # A namespace to prepend to all statsd calls. Defaults to no namespace.
65
47
  attr_reader :namespace
66
48
 
67
49
  # Global tags to be added to every statsd call. Defaults to no tags.
68
- attr_reader :tags
50
+ def tags
51
+ serializer.global_tags
52
+ end
69
53
 
70
54
  # Buffer containing the statsd message before they are sent in batch
71
55
  attr_reader :buffer
@@ -103,36 +87,31 @@ module Datadog
103
87
  raise ArgumentError, 'tags must be a Array<String> or a Hash'
104
88
  end
105
89
 
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
90
+ @namespace = namespace
91
+ @prefix = @namespace ? "#{@namespace}.".freeze : nil
110
92
 
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
93
+ @serializer = Serialization::Serializer.new(prefix: @prefix, global_tags: tags)
116
94
 
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)
95
+ transport_type = socket_path.nil? ? :udp : :uds
121
96
 
122
- if socket_path.nil?
123
- @connection = UDPConnection.new(host, port, logger, @telemetry)
124
- else
125
- @connection = UDSConnection.new(socket_path, logger, @telemetry)
126
- end
127
- @logger = logger
97
+ @telemetry = Telemetry.new(disable_telemetry, telemetry_flush_interval,
98
+ global_tags: tags,
99
+ transport_type: transport_type
100
+ )
128
101
 
129
- @namespace = namespace
130
- @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
131
110
 
132
111
  @sample_rate = sample_rate
133
112
 
134
113
  # 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))
114
+ @batch = Batch.new(connection, (max_buffer_bytes - telemetry.estimate_max_size))
136
115
  end
137
116
 
138
117
  # yield a new instance to a block and close it when done
@@ -259,20 +238,10 @@ module Datadog
259
238
  # $statsd.time('account.activate') { @account.activate! }
260
239
  def time(stat, opts = EMPTY_OPTIONS)
261
240
  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
241
+ start = now
267
242
  yield
268
243
  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)
244
+ timing(stat, ((now - start) * 1000).round, opts)
276
245
  end
277
246
 
278
247
  # Sends a value to be tracked as a set to the statsd server.
@@ -301,8 +270,9 @@ module Datadog
301
270
  # @example Report a critical service check status
302
271
  # $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
303
272
  def service_check(name, status, opts = EMPTY_OPTIONS)
304
- @telemetry.service_checks += 1
305
- send_stat(format_service_check(name, status, opts))
273
+ telemetry.sent(service_checks: 1)
274
+
275
+ send_stat(serializer.to_service_check(name, status, opts))
306
276
  end
307
277
 
308
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.
@@ -324,8 +294,9 @@ module Datadog
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))
297
+ telemetry.sent(events: 1)
298
+
299
+ send_stat(serializer.to_event(title, text, opts))
329
300
  end
330
301
 
331
302
  # Send several metrics in the same UDP Packet
@@ -344,154 +315,44 @@ module Datadog
344
315
 
345
316
  # Close the underlying socket
346
317
  def close
347
- @connection.close
318
+ connection.close
348
319
  end
349
320
 
350
321
  private
322
+ attr_reader :serializer
323
+ attr_reader :telemetry
351
324
 
352
- NEW_LINE = "\n"
353
- ESC_NEW_LINE = '\n'
354
- COMMA = ','
355
- PIPE = '|'
356
- DOT = '.'
357
- DOUBLE_COLON = '::'
358
- UNDERSCORE = '_'
359
325
  PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= '2.1.0')
360
326
  EMPTY_OPTIONS = {}.freeze
361
327
 
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
389
- end
390
-
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}"
328
+ if PROCESS_TIME_SUPPORTED
329
+ def now
330
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
415
331
  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
421
- end
422
-
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
332
+ else
333
+ def now
334
+ Time.now.to_f
432
335
  end
433
- tag_arr.join(COMMA) unless tag_arr.empty?
434
- end
435
-
436
- def tag_hash_to_array(tag_hash)
437
- tag_hash.to_a.map do |pair|
438
- pair.compact.join(':')
439
- end
440
- end
441
-
442
- def escape_event_content(message)
443
- message.gsub(NEW_LINE, ESC_NEW_LINE)
444
- end
445
-
446
- def escape_tag_content(tag)
447
- tag = remove_pipes(tag.to_s)
448
- tag.delete!(COMMA)
449
- tag
450
- end
451
-
452
- def remove_pipes(message)
453
- message.delete(PIPE)
454
- end
455
-
456
- def escape_service_check_message(message)
457
- escape_event_content(message).gsub('m:', 'm\:')
458
336
  end
459
337
 
460
338
  def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
461
- @telemetry.metrics += 1
339
+ telemetry.sent(metrics: 1)
340
+
462
341
  sample_rate = opts[:sample_rate] || @sample_rate || 1
342
+
463
343
  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
344
+ full_stat = serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
345
+
489
346
  send_stat(full_stat)
490
347
  end
491
348
  end
492
349
 
493
350
  def send_stat(message)
494
- @batch.open? ? @batch.add(message) : @connection.write(message)
351
+ if @batch.open?
352
+ @batch.add(message)
353
+ else
354
+ @connection.write(message)
355
+ end
495
356
  end
496
357
  end
497
358
  end
@@ -30,7 +30,7 @@ module Datadog
30
30
  if @buffer_bytes + 1 + message_bytes >= @max_buffer_bytes
31
31
  flush
32
32
  else
33
- @buffer << NEW_LINE
33
+ @buffer << "\n"
34
34
  @buffer_bytes += 1
35
35
  end
36
36
  end
@@ -9,24 +9,26 @@ module Datadog
9
9
 
10
10
  # Close the underlying socket
11
11
  def close
12
- @socket && @socket.close
12
+ begin
13
+ @socket && @socket.close if instance_variable_defined?(:@socket)
14
+ rescue StandardError => boom
15
+ logger.error { "Statsd: #{boom.class} #{boom}" } if logger
16
+ end
17
+ @socket = nil
13
18
  end
14
19
 
15
20
  def write(payload)
16
21
  logger.debug { "Statsd: #{payload}" } if logger
17
- flush_telemetry = @telemetry.flush?
18
- if flush_telemetry
19
- payload += @telemetry.flush()
20
- end
22
+
23
+ flush_telemetry = telemetry.flush?
24
+
25
+ payload += telemetry.flush if flush_telemetry
21
26
 
22
27
  send_message(payload)
23
28
 
24
- if flush_telemetry
25
- @telemetry.reset
26
- end
29
+ telemetry.reset if flush_telemetry
27
30
 
28
- telemetry.bytes_sent += payload.length
29
- telemetry.packets_sent += 1
31
+ telemetry.sent(packets: 1, bytes: payload.length)
30
32
  rescue StandardError => boom
31
33
  # Try once to reconnect if the socket has been closed
32
34
  retries ||= 1
@@ -36,21 +38,19 @@ module Datadog
36
38
  boom.is_a?(IOError) && boom.message =~ /closed stream/i)
37
39
  retries += 1
38
40
  begin
39
- @socket = connect
41
+ close
40
42
  retry
41
43
  rescue StandardError => e
42
44
  boom = e
43
45
  end
44
46
  end
45
47
 
46
- telemetry.bytes_dropped += payload.length
47
- telemetry.packets_dropped += 1
48
+ telemetry.dropped(packets: 1, bytes: payload.length)
48
49
  logger.error { "Statsd: #{boom.class} #{boom}" } if logger
49
50
  nil
50
51
  end
51
52
 
52
53
  private
53
-
54
54
  attr_reader :telemetry
55
55
  attr_reader :logger
56
56
 
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ end
7
+ end
8
+ end
9
+
10
+ require_relative 'serialization/tag_serializer'
11
+ require_relative 'serialization/service_check_serializer'
12
+ require_relative 'serialization/event_serializer'
13
+ require_relative 'serialization/stat_serializer'
14
+
15
+ require_relative 'serialization/serializer'
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class EventSerializer
7
+ EVENT_BASIC_OPTIONS = {
8
+ date_happened: 'd:',
9
+ hostname: 'h:',
10
+ aggregation_key: 'k:',
11
+ priority: 'p:',
12
+ source_type_name: 's:',
13
+ alert_type: 't:',
14
+ }.freeze
15
+
16
+ def initialize(global_tags: [])
17
+ @tag_serializer = TagSerializer.new(global_tags)
18
+ end
19
+
20
+ def format(title, text, options = EMPTY_OPTIONS)
21
+ title = escape(title)
22
+ text = escape(text)
23
+
24
+ String.new.tap do |event|
25
+ event << '_e{'
26
+ event << title.bytesize.to_s
27
+ event << ','
28
+ event << text.bytesize.to_s
29
+ event << '}:'
30
+ event << title
31
+ event << '|'
32
+ event << text
33
+
34
+ # we are serializing the generic service check options
35
+ # before serializing specialized options that need edge-cases
36
+ EVENT_BASIC_OPTIONS.each do |option_key, shortcut|
37
+ if value = options[option_key]
38
+ event << '|'
39
+ event << shortcut
40
+ event << value.to_s.delete('|')
41
+ end
42
+ end
43
+
44
+ if raw_tags = options[:tags]
45
+ if tags = tag_serializer.format(raw_tags)
46
+ event << '|#'
47
+ event << tags
48
+ end
49
+ end
50
+
51
+ if event.bytesize > MAX_EVENT_SIZE
52
+ raise "Event #{title} payload is too big (more that 8KB), event discarded"
53
+ end
54
+ end
55
+ end
56
+
57
+ protected
58
+ attr_reader :tag_serializer
59
+
60
+ def escape(text)
61
+ text.delete('|').tap do |t|
62
+ t.gsub!("\n", '\n')
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require 'forwardable'
4
+
5
+ module Datadog
6
+ class Statsd
7
+ module Serialization
8
+ class Serializer
9
+ def initialize(prefix: nil, global_tags: [])
10
+ @stat_serializer = StatSerializer.new(prefix, global_tags: global_tags)
11
+ @service_check_serializer = ServiceCheckSerializer.new(global_tags: global_tags)
12
+ @event_serializer = EventSerializer.new(global_tags: global_tags)
13
+ end
14
+
15
+ # using *args would make new allocations
16
+ def to_stat(name, delta, type, tags: [], sample_rate: 1)
17
+ stat_serializer.format(name, delta, type, tags: tags, sample_rate: sample_rate)
18
+ end
19
+
20
+ # using *args would make new allocations
21
+ def to_service_check(name, status, options = EMPTY_OPTIONS)
22
+ service_check_serializer.format(name, status, options)
23
+ end
24
+
25
+ # using *args would make new allocations
26
+ def to_event(title, text, options = EMPTY_OPTIONS)
27
+ event_serializer.format(title, text, options)
28
+ end
29
+
30
+ def global_tags
31
+ stat_serializer.global_tags
32
+ end
33
+
34
+ protected
35
+ attr_reader :stat_serializer
36
+ attr_reader :service_check_serializer
37
+ attr_reader :event_serializer
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class ServiceCheckSerializer
7
+ SERVICE_CHECK_BASIC_OPTIONS = {
8
+ timestamp: 'd:',
9
+ hostname: 'h:',
10
+ }.freeze
11
+
12
+ def initialize(global_tags: [])
13
+ @tag_serializer = TagSerializer.new(global_tags)
14
+ end
15
+
16
+ def format(name, status, options = EMPTY_OPTIONS)
17
+ String.new.tap do |service_check|
18
+ # line basics
19
+ service_check << "_sc"
20
+ service_check << "|"
21
+ service_check << name.to_s
22
+ service_check << "|"
23
+ service_check << status.to_s
24
+
25
+ # we are serializing the generic service check options
26
+ # before serializing specialized options that need edge-cases
27
+ SERVICE_CHECK_BASIC_OPTIONS.each do |option_key, shortcut|
28
+ if value = options[option_key]
29
+ service_check << '|'
30
+ service_check << shortcut
31
+ service_check << value.to_s.delete('|')
32
+ end
33
+ end
34
+
35
+ if message = options[:message]
36
+ service_check << '|m:'
37
+ service_check << escape_message(message)
38
+ end
39
+
40
+ if raw_tags = options[:tags]
41
+ if tags = tag_serializer.format(raw_tags)
42
+ service_check << '|#'
43
+ service_check << tags
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ protected
50
+ attr_reader :tag_serializer
51
+
52
+ def escape_message(message)
53
+ message.delete('|').tap do |m|
54
+ m.gsub!("\n", '\n')
55
+ m.gsub!('m:', 'm\:')
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class StatSerializer
7
+ def initialize(prefix, global_tags: [])
8
+ @prefix = prefix
9
+ @tag_serializer = TagSerializer.new(global_tags)
10
+ end
11
+
12
+ def format(name, delta, type, tags: [], sample_rate: 1)
13
+ String.new.tap do |stat|
14
+ stat << prefix if prefix
15
+
16
+ # stat value
17
+ stat << formated_name(name)
18
+ stat << ':'
19
+ stat << delta.to_s
20
+
21
+ # stat type
22
+ stat << '|'
23
+ stat << type
24
+
25
+ # sample_rate
26
+ if sample_rate != 1
27
+ stat << '|'
28
+ stat << '@'
29
+ stat << sample_rate.to_s
30
+ end
31
+
32
+ # tags
33
+ if tags_list = tag_serializer.format(tags)
34
+ stat << '|'
35
+ stat << '#'
36
+ stat << tags_list
37
+ end
38
+ end
39
+ end
40
+
41
+ def global_tags
42
+ tag_serializer.global_tags
43
+ end
44
+
45
+ private
46
+ attr_reader :prefix
47
+ attr_reader :tag_serializer
48
+
49
+ def formated_name(name)
50
+ formated = name.is_a?(String) ? name.dup : name.to_s
51
+
52
+ formated.tap do |f|
53
+ # replace Ruby module scoping with '.'
54
+ f.gsub!('::', '.')
55
+ # replace reserved chars (: | @) with underscores.
56
+ f.tr!(':|@', '_')
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class TagSerializer
7
+ def initialize(global_tags = [], env = ENV)
8
+ # Convert to hash
9
+ global_tags = to_tags_hash(global_tags)
10
+
11
+ # Merge with default tags
12
+ global_tags = default_tags(env).merge(global_tags)
13
+
14
+ # Convert to tag list and set
15
+ @global_tags = to_tags_list(global_tags)
16
+ end
17
+
18
+ def format(message_tags)
19
+ # fast return global tags if there's no message_tags
20
+ # to avoid more allocations
21
+ tag_list = if message_tags && message_tags.any?
22
+ global_tags + to_tags_list(message_tags)
23
+ else
24
+ global_tags
25
+ end
26
+
27
+ tag_list.join(',') if tag_list.any?
28
+ end
29
+
30
+ attr_reader :global_tags
31
+
32
+ private
33
+
34
+ def to_tags_hash(tags)
35
+ case tags
36
+ when Hash
37
+ tags.dup
38
+ when Array
39
+ Hash[
40
+ tags.map do |string|
41
+ tokens = string.split(':')
42
+ tokens << nil if tokens.length == 1
43
+ tokens.length == 2 ? tokens : nil
44
+ end.compact
45
+ ]
46
+ else
47
+ {}
48
+ end
49
+ end
50
+
51
+ def to_tags_list(tags)
52
+ case tags
53
+ when Hash
54
+ tags.each_with_object([]) do |tag_pair, formatted_tags|
55
+ if tag_pair.last.nil?
56
+ formatted_tags << "#{tag_pair.first}"
57
+ else
58
+ formatted_tags << "#{tag_pair.first}:#{tag_pair.last}"
59
+ end
60
+ end
61
+ when Array
62
+ tags.dup
63
+ else
64
+ []
65
+ end.map! do |tag|
66
+ escape_tag_content(tag)
67
+ end
68
+ end
69
+
70
+ def escape_tag_content(tag)
71
+ tag.to_s.delete('|,')
72
+ end
73
+
74
+ def dd_tags(env = ENV)
75
+ return {} unless dd_tags = env['DD_TAGS']
76
+
77
+ to_tags_hash(dd_tags.split(','))
78
+ end
79
+
80
+ def default_tags(env = ENV)
81
+ dd_tags(env).tap do |tags|
82
+ tags['dd.internal.entity_id'] = env['DD_ENTITY_ID'] if env.key?('DD_ENTITY_ID')
83
+ tags['env'] = env['DD_ENV'] if env.key?('DD_ENV')
84
+ tags['service'] = env['DD_SERVICE'] if env.key?('DD_SERVICE')
85
+ tags['version'] = env['DD_VERSION'] if env.key?('DD_VERSION')
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -4,21 +4,30 @@ require 'time'
4
4
  module Datadog
5
5
  class Statsd
6
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
7
+ attr_reader :metrics
8
+ attr_reader :events
9
+ attr_reader :service_checks
10
+ attr_reader :bytes_sent
11
+ attr_reader :bytes_dropped
12
+ attr_reader :packets_sent
13
+ attr_reader :packets_dropped
14
+ attr_reader :estimate_max_size
15
15
 
16
- def initialize(disabled, tags, flush_interval)
16
+ def initialize(disabled, flush_interval, global_tags: [], transport_type: :udp)
17
17
  @disabled = disabled
18
- @tags = tags
19
18
  @flush_interval = flush_interval
19
+ @global_tags = global_tags
20
+ @transport_type = transport_type
20
21
  reset
21
22
 
23
+ # TODO: Karim: I don't know why but telemetry tags are serialized
24
+ # before global tags so by refactoring this, I am keeping the same behavior
25
+ @serialized_tags = Serialization::TagSerializer.new(
26
+ client: 'ruby',
27
+ client_version: VERSION,
28
+ client_transport: transport_type,
29
+ ).format(global_tags)
30
+
22
31
  # estimate_max_size is an estimation or the maximum size of the
23
32
  # telemetry payload. Since we don't want our packet to go over
24
33
  # 'max_buffer_bytes', we have to adjust with the size of the telemetry
@@ -26,7 +35,7 @@ module Datadog
26
35
  # on the actual value of metrics: metrics received, packet dropped,
27
36
  # etc. This is why we add a 63bytes margin: 9 bytes for each of the 7
28
37
  # telemetry metrics.
29
- @estimate_max_size = @disabled ? 0 : flush().length + 9 * 7
38
+ @estimate_max_size = disabled ? 0 : flush.length + 9 * 7
30
39
  end
31
40
 
32
41
  def reset
@@ -37,28 +46,52 @@ module Datadog
37
46
  @bytes_dropped = 0
38
47
  @packets_sent = 0
39
48
  @packets_dropped = 0
40
- @next_flush_time = Time.now.to_i + @flush_interval
49
+ @next_flush_time = now_in_s + @flush_interval
50
+ end
51
+
52
+ def sent(metrics: 0, events: 0, service_checks: 0, bytes: 0, packets: 0)
53
+ @metrics += metrics
54
+ @events += events
55
+ @service_checks += service_checks
56
+
57
+ @bytes_sent += bytes
58
+ @packets_sent += packets
59
+ end
60
+
61
+ def dropped(bytes: 0, packets: 0)
62
+ @bytes_dropped += bytes
63
+ @packets_dropped += packets
41
64
  end
42
65
 
43
66
  def flush?
44
- if @next_flush_time < Time.now.to_i
45
- return true
46
- end
47
- return false
67
+ @next_flush_time < now_in_s
48
68
  end
49
69
 
50
70
  def flush
51
71
  return '' if @disabled
52
72
 
53
73
  # 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})
74
+ %Q(
75
+ datadog.dogstatsd.client.metrics:#{@metrics}|#{COUNTER_TYPE}|##{serialized_tags}
76
+ datadog.dogstatsd.client.events:#{@events}|#{COUNTER_TYPE}|##{serialized_tags}
77
+ datadog.dogstatsd.client.service_checks:#{@service_checks}|#{COUNTER_TYPE}|##{serialized_tags}
78
+ datadog.dogstatsd.client.bytes_sent:#{@bytes_sent}|#{COUNTER_TYPE}|##{serialized_tags}
79
+ datadog.dogstatsd.client.bytes_dropped:#{@bytes_dropped}|#{COUNTER_TYPE}|##{serialized_tags}
80
+ datadog.dogstatsd.client.packets_sent:#{@packets_sent}|#{COUNTER_TYPE}|##{serialized_tags}
81
+ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{serialized_tags})
82
+ end
83
+
84
+ private
85
+ attr_reader :serialized_tags
86
+
87
+ if Kernel.const_defined?('Process') && Process.respond_to?(:clock_gettime)
88
+ def now_in_s
89
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
90
+ end
91
+ else
92
+ def now_in_s
93
+ Time.now.to_i
94
+ end
62
95
  end
63
96
  end
64
97
  end
@@ -17,7 +17,7 @@ module Datadog
17
17
  def initialize(host, port, logger, telemetry)
18
18
  super(telemetry)
19
19
  @host = host || ENV.fetch('DD_AGENT_HOST', DEFAULT_HOST)
20
- @port = port || ENV.fetch('DD_DOGSTATSD_PORT', DEFAULT_PORT)
20
+ @port = port || ENV.fetch('DD_DOGSTATSD_PORT', DEFAULT_PORT).to_i
21
21
  @logger = logger
22
22
  end
23
23
 
@@ -28,6 +28,9 @@ module Datadog
28
28
  socket.sendmsg_nonblock(message)
29
29
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENOENT => e
30
30
  @socket = nil
31
+ # TODO: FIXME: This error should be considered as a retryable error in the
32
+ # Connection class. An even better solution would be to make BadSocketError inherit
33
+ # from a specific retryable error class in the Connection class.
31
34
  raise BadSocketError, "#{e.class}: #{e}"
32
35
  end
33
36
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'connection'
4
+
5
+ module Datadog
6
+ class Statsd
7
+ VERSION = '4.8.0'
8
+ end
9
+ 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.7.0
4
+ version: 4.8.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-02-14 00:00:00.000000000 Z
11
+ date: 2020-04-21 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A Ruby DogStastd client
14
14
  email: code@datadoghq.com
@@ -23,17 +23,24 @@ files:
23
23
  - lib/datadog/statsd.rb
24
24
  - lib/datadog/statsd/batch.rb
25
25
  - lib/datadog/statsd/connection.rb
26
+ - lib/datadog/statsd/serialization.rb
27
+ - lib/datadog/statsd/serialization/event_serializer.rb
28
+ - lib/datadog/statsd/serialization/serializer.rb
29
+ - lib/datadog/statsd/serialization/service_check_serializer.rb
30
+ - lib/datadog/statsd/serialization/stat_serializer.rb
31
+ - lib/datadog/statsd/serialization/tag_serializer.rb
26
32
  - lib/datadog/statsd/telemetry.rb
27
33
  - lib/datadog/statsd/udp_connection.rb
28
34
  - lib/datadog/statsd/uds_connection.rb
35
+ - lib/datadog/statsd/version.rb
29
36
  homepage: https://github.com/DataDog/dogstatsd-ruby
30
37
  licenses:
31
38
  - MIT
32
39
  metadata:
33
40
  bug_tracker_uri: https://github.com/DataDog/dogstatsd-ruby/issues
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
41
+ changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v4.8.0/CHANGELOG.md
42
+ documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/4.8.0
43
+ source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v4.8.0
37
44
  post_install_message:
38
45
  rdoc_options: []
39
46
  require_paths: