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 +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:
|