dogstatsd-ruby 4.7.0 → 4.8.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: 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: