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 +4 -4
- data/lib/datadog/statsd.rb +49 -188
- data/lib/datadog/statsd/batch.rb +1 -1
- data/lib/datadog/statsd/connection.rb +14 -14
- data/lib/datadog/statsd/serialization.rb +15 -0
- data/lib/datadog/statsd/serialization/event_serializer.rb +68 -0
- data/lib/datadog/statsd/serialization/serializer.rb +41 -0
- data/lib/datadog/statsd/serialization/service_check_serializer.rb +61 -0
- data/lib/datadog/statsd/serialization/stat_serializer.rb +62 -0
- data/lib/datadog/statsd/serialization/tag_serializer.rb +91 -0
- data/lib/datadog/statsd/telemetry.rb +57 -24
- data/lib/datadog/statsd/udp_connection.rb +1 -1
- data/lib/datadog/statsd/uds_connection.rb +3 -0
- data/lib/datadog/statsd/version.rb +9 -0
- metadata +12 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7485524dc133a21013d2ab5144511a4ba38740226bdeaf1d3131be99859f33ec
|
4
|
+
data.tar.gz: 6358ca98c85788c904e48ebf3d0d3751372b3ba5baaaf194199fef1f5badbae0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: edf3b324873893d4acfff6a8775ee7d3ca337e416f283f29137abbaed447f8db220d8d09cc00131a0a53b0d4f38555f48d669658812ebe739903dc68a74ab8c3
|
7
|
+
data.tar.gz: 9b5bd953d65e25ad74733175febb8a4a3b4dfee5e2353d1f539684443ddface8f65c9ebde4f88dfd5bd4063b892c2c76350754a112481bfeacb2f454a199dac9
|
data/lib/datadog/statsd.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
107
|
-
@
|
108
|
-
escape_tag_content(tag)
|
109
|
-
end
|
90
|
+
@namespace = namespace
|
91
|
+
@prefix = @namespace ? "#{@namespace}.".freeze : nil
|
110
92
|
|
111
|
-
|
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
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
@
|
130
|
-
|
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(
|
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 =
|
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
|
-
|
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
|
-
|
305
|
-
|
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
|
-
|
328
|
-
|
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
|
-
|
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
|
-
|
363
|
-
|
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
|
-
|
418
|
-
|
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
|
-
|
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 =
|
465
|
-
|
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?
|
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
|
data/lib/datadog/statsd/batch.rb
CHANGED
@@ -9,24 +9,26 @@ module Datadog
|
|
9
9
|
|
10
10
|
# Close the underlying socket
|
11
11
|
def close
|
12
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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.
|
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
|
-
|
41
|
+
close
|
40
42
|
retry
|
41
43
|
rescue StandardError => e
|
42
44
|
boom = e
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
46
|
-
telemetry.
|
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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
attr_reader
|
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,
|
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 =
|
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 =
|
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
|
-
|
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
|
-
|
55
|
-
datadog.dogstatsd.client.metrics:#{@metrics}|#{COUNTER_TYPE}|##{
|
56
|
-
datadog.dogstatsd.client.events:#{@events}|#{COUNTER_TYPE}|##{
|
57
|
-
datadog.dogstatsd.client.service_checks:#{@service_checks}|#{COUNTER_TYPE}|##{
|
58
|
-
datadog.dogstatsd.client.bytes_sent:#{@bytes_sent}|#{COUNTER_TYPE}|##{
|
59
|
-
datadog.dogstatsd.client.bytes_dropped:#{@bytes_dropped}|#{COUNTER_TYPE}|##{
|
60
|
-
datadog.dogstatsd.client.packets_sent:#{@packets_sent}|#{COUNTER_TYPE}|##{
|
61
|
-
datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
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
|
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.
|
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-
|
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.
|
35
|
-
documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/4.
|
36
|
-
source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v4.
|
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:
|