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