dogstatsd-ruby 4.7.0 → 5.0.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/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
|