dogstatsd-ruby 4.8.0 → 5.0.1
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 +82 -55
- data/lib/datadog/statsd/connection.rb +5 -8
- data/lib/datadog/statsd/forwarder.rb +120 -0
- data/lib/datadog/statsd/message_buffer.rb +88 -0
- data/lib/datadog/statsd/sender.rb +116 -0
- data/lib/datadog/statsd/serialization/event_serializer.rb +9 -6
- data/lib/datadog/statsd/serialization/service_check_serializer.rb +4 -5
- data/lib/datadog/statsd/serialization/stat_serializer.rb +22 -29
- data/lib/datadog/statsd/serialization/tag_serializer.rb +20 -15
- data/lib/datadog/statsd/telemetry.rb +21 -23
- data/lib/datadog/statsd/udp_connection.rb +3 -3
- data/lib/datadog/statsd/uds_connection.rb +3 -3
- data/lib/datadog/statsd/version.rb +1 -1
- metadata +14 -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: c77f82b5b9a858517a937a5b2db1a1f890a80c94ca63e60f12e256ab28f7192d
|
4
|
+
data.tar.gz: e95ee401174c084edb117068f73d1c72f08e602ea9b51363e57e53c176072472
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 88d54866c8693d2dab18a1370d6ba5951a42ec4ed62615c6194548dd5faa109731840187e2a7ddaf591f08d07064c8e173df74728f182fe5cb5593c896503e19
|
7
|
+
data.tar.gz: d8021d0b6b21efe7ab14841665b01819c397d4419b247615d88b7685b332b1c8bb9a13d05fc22d21baf5d709d5452f9a9a3d932438addfbf1238e8ab5656a8e5
|
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
@@ -5,8 +5,10 @@ require_relative 'statsd/version'
|
|
5
5
|
require_relative 'statsd/telemetry'
|
6
6
|
require_relative 'statsd/udp_connection'
|
7
7
|
require_relative 'statsd/uds_connection'
|
8
|
-
require_relative 'statsd/
|
8
|
+
require_relative 'statsd/message_buffer'
|
9
9
|
require_relative 'statsd/serialization'
|
10
|
+
require_relative 'statsd/sender'
|
11
|
+
require_relative 'statsd/forwarder'
|
10
12
|
|
11
13
|
# = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
|
12
14
|
#
|
@@ -26,12 +28,17 @@ require_relative 'statsd/serialization'
|
|
26
28
|
# statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
|
27
29
|
module Datadog
|
28
30
|
class Statsd
|
31
|
+
class Error < StandardError
|
32
|
+
end
|
33
|
+
|
29
34
|
OK = 0
|
30
35
|
WARNING = 1
|
31
36
|
CRITICAL = 2
|
32
37
|
UNKNOWN = 3
|
33
38
|
|
34
|
-
|
39
|
+
UDP_DEFAULT_BUFFER_SIZE = 1_432
|
40
|
+
UDS_DEFAULT_BUFFER_SIZE = 8_192
|
41
|
+
DEFAULT_BUFFER_POOL_SIZE = Float::INFINITY
|
35
42
|
MAX_EVENT_SIZE = 8 * 1_024
|
36
43
|
# minimum flush interval for the telemetry in seconds
|
37
44
|
DEFAULT_TELEMETRY_FLUSH_INTERVAL = 10
|
@@ -51,67 +58,59 @@ module Datadog
|
|
51
58
|
serializer.global_tags
|
52
59
|
end
|
53
60
|
|
54
|
-
# Buffer containing the statsd message before they are sent in batch
|
55
|
-
attr_reader :buffer
|
56
|
-
|
57
|
-
# Maximum buffer size in bytes before it is flushed
|
58
|
-
attr_reader :max_buffer_bytes
|
59
|
-
|
60
61
|
# Default sample rate
|
61
62
|
attr_reader :sample_rate
|
62
63
|
|
63
|
-
# Connection
|
64
|
-
attr_reader :connection
|
65
|
-
|
66
64
|
# @param [String] host your statsd host
|
67
65
|
# @param [Integer] port your statsd port
|
68
66
|
# @option [String] namespace set a namespace to be prepended to every metric name
|
69
67
|
# @option [Array<String>|Hash] tags tags to be added to every metric
|
70
68
|
# @option [Logger] logger for debugging
|
71
|
-
# @option [Integer]
|
69
|
+
# @option [Integer] buffer_max_payload_size max bytes to buffer
|
70
|
+
# @option [Integer] buffer_max_pool_size max messages to buffer
|
72
71
|
# @option [String] socket_path unix socket path
|
73
72
|
# @option [Float] default sample rate if not overridden
|
74
73
|
def initialize(
|
75
74
|
host = nil,
|
76
75
|
port = nil,
|
76
|
+
socket_path: nil,
|
77
|
+
|
77
78
|
namespace: nil,
|
78
79
|
tags: nil,
|
79
|
-
max_buffer_bytes: DEFAULT_BUFFER_SIZE,
|
80
|
-
socket_path: nil,
|
81
|
-
logger: nil,
|
82
80
|
sample_rate: nil,
|
83
|
-
|
81
|
+
|
82
|
+
buffer_max_payload_size: nil,
|
83
|
+
buffer_max_pool_size: nil,
|
84
|
+
buffer_overflowing_stategy: :drop,
|
85
|
+
|
86
|
+
logger: nil,
|
87
|
+
|
88
|
+
telemetry_enable: true,
|
84
89
|
telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
|
85
90
|
)
|
86
91
|
unless tags.nil? || tags.is_a?(Array) || tags.is_a?(Hash)
|
87
|
-
raise ArgumentError, 'tags must be
|
92
|
+
raise ArgumentError, 'tags must be an array of string tags or a Hash'
|
88
93
|
end
|
89
94
|
|
90
95
|
@namespace = namespace
|
91
96
|
@prefix = @namespace ? "#{@namespace}.".freeze : nil
|
92
|
-
|
93
97
|
@serializer = Serialization::Serializer.new(prefix: @prefix, global_tags: tags)
|
98
|
+
@sample_rate = sample_rate
|
94
99
|
|
95
|
-
|
100
|
+
@forwarder = Forwarder.new(
|
101
|
+
host: host,
|
102
|
+
port: port,
|
103
|
+
socket_path: socket_path,
|
96
104
|
|
97
|
-
@telemetry = Telemetry.new(disable_telemetry, telemetry_flush_interval,
|
98
105
|
global_tags: tags,
|
99
|
-
|
100
|
-
)
|
101
|
-
|
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
|
106
|
+
logger: logger,
|
108
107
|
|
109
|
-
|
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,
|
110
111
|
|
111
|
-
|
112
|
-
|
113
|
-
# we reduce max_buffer_bytes by a the rough estimate of the telemetry payload
|
114
|
-
@batch = Batch.new(connection, (max_buffer_bytes - telemetry.estimate_max_size))
|
112
|
+
telemetry_flush_interval: telemetry_enable ? telemetry_flush_interval : nil,
|
113
|
+
)
|
115
114
|
end
|
116
115
|
|
117
116
|
# yield a new instance to a block and close it when done
|
@@ -270,9 +269,9 @@ module Datadog
|
|
270
269
|
# @example Report a critical service check status
|
271
270
|
# $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
|
272
271
|
def service_check(name, status, opts = EMPTY_OPTIONS)
|
273
|
-
telemetry.sent(service_checks: 1)
|
272
|
+
telemetry.sent(service_checks: 1) if telemetry
|
274
273
|
|
275
|
-
|
274
|
+
forwarder.send_message(serializer.to_service_check(name, status, opts))
|
276
275
|
end
|
277
276
|
|
278
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.
|
@@ -290,17 +289,25 @@ module Datadog
|
|
290
289
|
# @option opts [String, nil] :priority ('normal') Can be "normal" or "low"
|
291
290
|
# @option opts [String, nil] :source_type_name (nil) Assign a source type to the event
|
292
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
|
293
293
|
# @option opts [Array<String>] :tags tags to be added to every metric
|
294
294
|
# @example Report an awful event:
|
295
295
|
# $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
|
296
296
|
def event(title, text, opts = EMPTY_OPTIONS)
|
297
|
-
telemetry.sent(events: 1)
|
297
|
+
telemetry.sent(events: 1) if telemetry
|
298
298
|
|
299
|
-
|
299
|
+
forwarder.send_message(serializer.to_event(title, text, opts))
|
300
300
|
end
|
301
301
|
|
302
|
-
# Send several metrics in the same
|
303
|
-
# They will be buffered and flushed when the block finishes
|
302
|
+
# Send several metrics in the same packet.
|
303
|
+
# They will be buffered and flushed when the block finishes.
|
304
|
+
#
|
305
|
+
# This method exists for compatibility with v4.x versions, it is not needed
|
306
|
+
# anymore since the batching is now automatically done internally.
|
307
|
+
# It also means that an automatic flush could occur if the buffer is filled
|
308
|
+
# during the execution of the batch block.
|
309
|
+
#
|
310
|
+
# This method is DEPRECATED and will be removed in future v6.x API.
|
304
311
|
#
|
305
312
|
# @example Send several metrics in one packet:
|
306
313
|
# $statsd.batch do |s|
|
@@ -308,19 +315,47 @@ module Datadog
|
|
308
315
|
# s.increment('page.views')
|
309
316
|
# end
|
310
317
|
def batch
|
311
|
-
|
312
|
-
|
313
|
-
end
|
318
|
+
yield self
|
319
|
+
flush(sync: true)
|
314
320
|
end
|
315
321
|
|
316
322
|
# Close the underlying socket
|
317
323
|
def close
|
318
|
-
|
324
|
+
forwarder.close
|
325
|
+
end
|
326
|
+
|
327
|
+
def sync_with_outbound_io
|
328
|
+
forwarder.sync_with_outbound_io
|
329
|
+
end
|
330
|
+
|
331
|
+
# Flush the buffer into the connection
|
332
|
+
def flush(flush_telemetry: false, sync: false)
|
333
|
+
forwarder.flush(flush_telemetry: flush_telemetry, sync: sync)
|
334
|
+
end
|
335
|
+
|
336
|
+
def telemetry
|
337
|
+
forwarder.telemetry
|
338
|
+
end
|
339
|
+
|
340
|
+
def host
|
341
|
+
forwarder.host
|
342
|
+
end
|
343
|
+
|
344
|
+
def port
|
345
|
+
forwarder.port
|
346
|
+
end
|
347
|
+
|
348
|
+
def socket_path
|
349
|
+
forwarder.socket_path
|
350
|
+
end
|
351
|
+
|
352
|
+
def transport_type
|
353
|
+
forwarder.transport_type
|
319
354
|
end
|
320
355
|
|
321
356
|
private
|
322
357
|
attr_reader :serializer
|
323
|
-
attr_reader :
|
358
|
+
attr_reader :forwarder
|
324
359
|
|
325
360
|
PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= '2.1.0')
|
326
361
|
EMPTY_OPTIONS = {}.freeze
|
@@ -336,22 +371,14 @@ module Datadog
|
|
336
371
|
end
|
337
372
|
|
338
373
|
def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
|
339
|
-
telemetry.sent(metrics: 1)
|
374
|
+
telemetry.sent(metrics: 1) if telemetry
|
340
375
|
|
341
376
|
sample_rate = opts[:sample_rate] || @sample_rate || 1
|
342
377
|
|
343
378
|
if sample_rate == 1 || rand <= sample_rate
|
344
379
|
full_stat = serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
|
345
380
|
|
346
|
-
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
def send_stat(message)
|
351
|
-
if @batch.open?
|
352
|
-
@batch.add(message)
|
353
|
-
else
|
354
|
-
@connection.write(message)
|
381
|
+
forwarder.send_message(full_stat)
|
355
382
|
end
|
356
383
|
end
|
357
384
|
end
|
@@ -3,8 +3,9 @@
|
|
3
3
|
module Datadog
|
4
4
|
class Statsd
|
5
5
|
class Connection
|
6
|
-
def initialize(telemetry)
|
6
|
+
def initialize(telemetry: nil, logger: nil)
|
7
7
|
@telemetry = telemetry
|
8
|
+
@logger = logger
|
8
9
|
end
|
9
10
|
|
10
11
|
# Close the underlying socket
|
@@ -20,15 +21,11 @@ module Datadog
|
|
20
21
|
def write(payload)
|
21
22
|
logger.debug { "Statsd: #{payload}" } if logger
|
22
23
|
|
23
|
-
flush_telemetry = telemetry.flush?
|
24
|
-
|
25
|
-
payload += telemetry.flush if flush_telemetry
|
26
|
-
|
27
24
|
send_message(payload)
|
28
25
|
|
29
|
-
telemetry.
|
26
|
+
telemetry.sent(packets: 1, bytes: payload.length) if telemetry
|
30
27
|
|
31
|
-
|
28
|
+
true
|
32
29
|
rescue StandardError => boom
|
33
30
|
# Try once to reconnect if the socket has been closed
|
34
31
|
retries ||= 1
|
@@ -45,7 +42,7 @@ module Datadog
|
|
45
42
|
end
|
46
43
|
end
|
47
44
|
|
48
|
-
telemetry.dropped(packets: 1, bytes: payload.length)
|
45
|
+
telemetry.dropped(packets: 1, bytes: payload.length) if telemetry
|
49
46
|
logger.error { "Statsd: #{boom.class} #{boom}" } if logger
|
50
47
|
nil
|
51
48
|
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
class Statsd
|
5
|
+
class Forwarder
|
6
|
+
attr_reader :telemetry
|
7
|
+
attr_reader :transport_type
|
8
|
+
|
9
|
+
def initialize(
|
10
|
+
host: nil,
|
11
|
+
port: nil,
|
12
|
+
socket_path: nil,
|
13
|
+
|
14
|
+
buffer_max_payload_size: nil,
|
15
|
+
buffer_max_pool_size: nil,
|
16
|
+
buffer_overflowing_stategy: :drop,
|
17
|
+
|
18
|
+
telemetry_flush_interval: nil,
|
19
|
+
global_tags: [],
|
20
|
+
|
21
|
+
logger: nil
|
22
|
+
)
|
23
|
+
@transport_type = socket_path.nil? ? :udp : :uds
|
24
|
+
|
25
|
+
if telemetry_flush_interval
|
26
|
+
@telemetry = Telemetry.new(telemetry_flush_interval,
|
27
|
+
global_tags: global_tags,
|
28
|
+
transport_type: transport_type
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
@connection = case transport_type
|
33
|
+
when :udp
|
34
|
+
UDPConnection.new(host, port, logger: logger, telemetry: telemetry)
|
35
|
+
when :uds
|
36
|
+
UDSConnection.new(socket_path, logger: logger, telemetry: telemetry)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Initialize buffer
|
40
|
+
buffer_max_payload_size ||= (transport_type == :udp ? UDP_DEFAULT_BUFFER_SIZE : UDS_DEFAULT_BUFFER_SIZE)
|
41
|
+
|
42
|
+
if buffer_max_payload_size <= 0
|
43
|
+
raise ArgumentError, 'buffer_max_payload_size cannot be <= 0'
|
44
|
+
end
|
45
|
+
|
46
|
+
unless telemetry.nil? || telemetry.would_fit_in?(buffer_max_payload_size)
|
47
|
+
raise ArgumentError, "buffer_max_payload_size is not high enough to use telemetry (tags=(#{global_tags.inspect}))"
|
48
|
+
end
|
49
|
+
|
50
|
+
@buffer = MessageBuffer.new(@connection,
|
51
|
+
max_payload_size: buffer_max_payload_size,
|
52
|
+
max_pool_size: buffer_max_pool_size || DEFAULT_BUFFER_POOL_SIZE,
|
53
|
+
overflowing_stategy: buffer_overflowing_stategy,
|
54
|
+
)
|
55
|
+
|
56
|
+
@sender = Sender.new(buffer)
|
57
|
+
@sender.start
|
58
|
+
end
|
59
|
+
|
60
|
+
def send_message(message)
|
61
|
+
sender.add(message)
|
62
|
+
|
63
|
+
tick_telemetry
|
64
|
+
end
|
65
|
+
|
66
|
+
def sync_with_outbound_io
|
67
|
+
sender.rendez_vous
|
68
|
+
end
|
69
|
+
|
70
|
+
def flush(flush_telemetry: false, sync: false)
|
71
|
+
do_flush_telemetry if telemetry && flush_telemetry
|
72
|
+
|
73
|
+
sender.flush(sync: sync)
|
74
|
+
end
|
75
|
+
|
76
|
+
def host
|
77
|
+
return nil unless transport_type == :udp
|
78
|
+
|
79
|
+
connection.host
|
80
|
+
end
|
81
|
+
|
82
|
+
def port
|
83
|
+
return nil unless transport_type == :udp
|
84
|
+
|
85
|
+
connection.port
|
86
|
+
end
|
87
|
+
|
88
|
+
def socket_path
|
89
|
+
return nil unless transport_type == :uds
|
90
|
+
|
91
|
+
connection.socket_path
|
92
|
+
end
|
93
|
+
|
94
|
+
def close
|
95
|
+
sender.stop
|
96
|
+
connection.close
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
attr_reader :buffer
|
101
|
+
attr_reader :sender
|
102
|
+
attr_reader :connection
|
103
|
+
|
104
|
+
def do_flush_telemetry
|
105
|
+
telemetry_snapshot = telemetry.flush
|
106
|
+
telemetry.reset
|
107
|
+
|
108
|
+
telemetry_snapshot.each do |message|
|
109
|
+
sender.add(message)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def tick_telemetry
|
114
|
+
return nil unless telemetry
|
115
|
+
|
116
|
+
do_flush_telemetry if telemetry.should_flush?
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
class Statsd
|
5
|
+
class MessageBuffer
|
6
|
+
PAYLOAD_SIZE_TOLERANCE = 0.05
|
7
|
+
|
8
|
+
def initialize(connection,
|
9
|
+
max_payload_size: nil,
|
10
|
+
max_pool_size: DEFAULT_BUFFER_POOL_SIZE,
|
11
|
+
overflowing_stategy: :drop
|
12
|
+
)
|
13
|
+
raise ArgumentError, 'max_payload_size keyword argument must be provided' unless max_payload_size
|
14
|
+
raise ArgumentError, 'max_pool_size keyword argument must be provided' unless max_pool_size
|
15
|
+
|
16
|
+
@connection = connection
|
17
|
+
@max_payload_size = max_payload_size
|
18
|
+
@max_pool_size = max_pool_size
|
19
|
+
@overflowing_stategy = overflowing_stategy
|
20
|
+
|
21
|
+
@buffer = String.new
|
22
|
+
@message_count = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(message)
|
26
|
+
message_size = message.bytesize
|
27
|
+
|
28
|
+
return nil unless message_size > 0 # to avoid adding empty messages to the buffer
|
29
|
+
return nil unless ensure_sendable!(message_size)
|
30
|
+
|
31
|
+
flush if should_flush?(message_size)
|
32
|
+
|
33
|
+
buffer << "\n" unless buffer.empty?
|
34
|
+
buffer << message
|
35
|
+
|
36
|
+
@message_count += 1
|
37
|
+
|
38
|
+
# flush when we're pretty sure that we won't be able
|
39
|
+
# to add another message to the buffer
|
40
|
+
flush if preemptive_flush?
|
41
|
+
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def flush
|
46
|
+
return if buffer.empty?
|
47
|
+
|
48
|
+
connection.write(buffer)
|
49
|
+
|
50
|
+
buffer.clear
|
51
|
+
@message_count = 0
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
attr :max_payload_size
|
56
|
+
attr :max_pool_size
|
57
|
+
|
58
|
+
attr :overflowing_stategy
|
59
|
+
|
60
|
+
attr :connection
|
61
|
+
attr :buffer
|
62
|
+
|
63
|
+
def should_flush?(message_size)
|
64
|
+
return true if buffer.bytesize + 1 + message_size >= max_payload_size
|
65
|
+
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
def preemptive_flush?
|
70
|
+
@message_count == max_pool_size || buffer.bytesize > bytesize_threshold
|
71
|
+
end
|
72
|
+
|
73
|
+
def ensure_sendable!(message_size)
|
74
|
+
return true if message_size <= max_payload_size
|
75
|
+
|
76
|
+
if overflowing_stategy == :raise
|
77
|
+
raise Error, 'Message too big for payload limit'
|
78
|
+
end
|
79
|
+
|
80
|
+
false
|
81
|
+
end
|
82
|
+
|
83
|
+
def bytesize_threshold
|
84
|
+
@bytesize_threshold ||= (max_payload_size - PAYLOAD_SIZE_TOLERANCE * max_payload_size).to_i
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
class Statsd
|
5
|
+
class Sender
|
6
|
+
CLOSEABLE_QUEUES = Queue.instance_methods.include?(:close)
|
7
|
+
|
8
|
+
def initialize(message_buffer)
|
9
|
+
@message_buffer = message_buffer
|
10
|
+
end
|
11
|
+
|
12
|
+
def flush(sync: false)
|
13
|
+
raise ArgumentError, 'Start sender first' unless message_queue
|
14
|
+
|
15
|
+
message_queue.push(:flush)
|
16
|
+
|
17
|
+
rendez_vous if sync
|
18
|
+
end
|
19
|
+
|
20
|
+
def rendez_vous
|
21
|
+
# Initialize and get the thread's sync queue
|
22
|
+
queue = (Thread.current[:statsd_sync_queue] ||= Queue.new)
|
23
|
+
# tell sender-thread to notify us in the current
|
24
|
+
# thread's queue
|
25
|
+
message_queue.push(queue)
|
26
|
+
# wait for the sender thread to send a message
|
27
|
+
# once the flush is done
|
28
|
+
queue.pop
|
29
|
+
end
|
30
|
+
|
31
|
+
def add(message)
|
32
|
+
raise ArgumentError, 'Start sender first' unless message_queue
|
33
|
+
|
34
|
+
message_queue << message
|
35
|
+
end
|
36
|
+
|
37
|
+
def start
|
38
|
+
raise ArgumentError, 'Sender already started' if message_queue
|
39
|
+
|
40
|
+
# initialize message queue for background thread
|
41
|
+
@message_queue = Queue.new
|
42
|
+
# start background thread
|
43
|
+
@sender_thread = Thread.new(&method(:send_loop))
|
44
|
+
end
|
45
|
+
|
46
|
+
if CLOSEABLE_QUEUES
|
47
|
+
def stop(join_worker: true)
|
48
|
+
message_queue = @message_queue
|
49
|
+
message_queue.close if message_queue
|
50
|
+
|
51
|
+
sender_thread = @sender_thread
|
52
|
+
sender_thread.join if sender_thread && join_worker
|
53
|
+
end
|
54
|
+
else
|
55
|
+
def stop(join_worker: true)
|
56
|
+
message_queue = @message_queue
|
57
|
+
message_queue << :close if message_queue
|
58
|
+
|
59
|
+
sender_thread = @sender_thread
|
60
|
+
sender_thread.join if sender_thread && join_worker
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
attr_reader :message_buffer
|
67
|
+
|
68
|
+
attr_reader :message_queue
|
69
|
+
attr_reader :sender_thread
|
70
|
+
|
71
|
+
if CLOSEABLE_QUEUES
|
72
|
+
def send_loop
|
73
|
+
until (message = message_queue.pop).nil? && message_queue.closed?
|
74
|
+
# skip if message is nil, e.g. when message_queue
|
75
|
+
# is empty and closed
|
76
|
+
next unless message
|
77
|
+
|
78
|
+
case message
|
79
|
+
when :flush
|
80
|
+
message_buffer.flush
|
81
|
+
when Queue
|
82
|
+
message.push(:go_on)
|
83
|
+
else
|
84
|
+
message_buffer.add(message)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
@message_queue = nil
|
89
|
+
@sender_thread = nil
|
90
|
+
end
|
91
|
+
else
|
92
|
+
def send_loop
|
93
|
+
loop do
|
94
|
+
message = message_queue.pop
|
95
|
+
|
96
|
+
next unless message
|
97
|
+
|
98
|
+
case message
|
99
|
+
when :close
|
100
|
+
break
|
101
|
+
when :flush
|
102
|
+
message_buffer.flush
|
103
|
+
when Queue
|
104
|
+
message.push(:go_on)
|
105
|
+
else
|
106
|
+
message_buffer.add(message)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
@message_queue = nil
|
111
|
+
@sender_thread = nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -41,15 +41,18 @@ module Datadog
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
44
|
+
# also returns the global tags from serializer
|
45
|
+
if tags = tag_serializer.format(options[:tags])
|
46
|
+
event << '|#'
|
47
|
+
event << tags
|
49
48
|
end
|
50
49
|
|
51
50
|
if event.bytesize > MAX_EVENT_SIZE
|
52
|
-
|
51
|
+
if options[:truncate_if_too_long]
|
52
|
+
event.slice!(MAX_EVENT_SIZE..event.length)
|
53
|
+
else
|
54
|
+
raise "Event #{title} payload is too big (more that 8KB), event discarded"
|
55
|
+
end
|
53
56
|
end
|
54
57
|
end
|
55
58
|
end
|
@@ -37,11 +37,10 @@ module Datadog
|
|
37
37
|
service_check << escape_message(message)
|
38
38
|
end
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
40
|
+
# also returns the global tags from serializer
|
41
|
+
if tags = tag_serializer.format(options[:tags])
|
42
|
+
service_check << '|#'
|
43
|
+
service_check << tags
|
45
44
|
end
|
46
45
|
end
|
47
46
|
end
|
@@ -6,34 +6,24 @@ module Datadog
|
|
6
6
|
class StatSerializer
|
7
7
|
def initialize(prefix, global_tags: [])
|
8
8
|
@prefix = prefix
|
9
|
+
@prefix_str = prefix.to_s
|
9
10
|
@tag_serializer = TagSerializer.new(global_tags)
|
10
11
|
end
|
11
12
|
|
12
13
|
def format(name, delta, type, tags: [], sample_rate: 1)
|
13
|
-
|
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
|
14
|
+
name = formated_name(name)
|
31
15
|
|
32
|
-
|
16
|
+
if sample_rate != 1
|
17
|
+
if tags_list = tag_serializer.format(tags)
|
18
|
+
"#{@prefix_str}#{name}:#{delta}|#{type}|@#{sample_rate}|##{tags_list}"
|
19
|
+
else
|
20
|
+
"#{@prefix_str}#{name}:#{delta}|#{type}|@#{sample_rate}"
|
21
|
+
end
|
22
|
+
else
|
33
23
|
if tags_list = tag_serializer.format(tags)
|
34
|
-
|
35
|
-
|
36
|
-
|
24
|
+
"#{@prefix_str}#{name}:#{delta}|#{type}|##{tags_list}"
|
25
|
+
else
|
26
|
+
"#{@prefix_str}#{name}:#{delta}|#{type}"
|
37
27
|
end
|
38
28
|
end
|
39
29
|
end
|
@@ -43,18 +33,21 @@ module Datadog
|
|
43
33
|
end
|
44
34
|
|
45
35
|
private
|
36
|
+
|
46
37
|
attr_reader :prefix
|
47
38
|
attr_reader :tag_serializer
|
48
39
|
|
49
40
|
def formated_name(name)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
f.tr!(':|@', '_')
|
41
|
+
if name.is_a?(String)
|
42
|
+
# DEV: gsub is faster than dup.gsub!
|
43
|
+
formated = name.gsub('::', '.')
|
44
|
+
else
|
45
|
+
formated = name.to_s
|
46
|
+
formated.gsub!('::', '.')
|
57
47
|
end
|
48
|
+
|
49
|
+
formated.tr!(':|@', '_')
|
50
|
+
formated
|
58
51
|
end
|
59
52
|
end
|
60
53
|
end
|
@@ -13,18 +13,25 @@ module Datadog
|
|
13
13
|
|
14
14
|
# Convert to tag list and set
|
15
15
|
@global_tags = to_tags_list(global_tags)
|
16
|
+
if @global_tags.any?
|
17
|
+
@global_tags_formatted = @global_tags.join(',')
|
18
|
+
else
|
19
|
+
@global_tags_formatted = nil
|
20
|
+
end
|
16
21
|
end
|
17
22
|
|
18
23
|
def format(message_tags)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
global_tags + to_tags_list(message_tags)
|
23
|
-
else
|
24
|
-
global_tags
|
25
|
-
end
|
24
|
+
if !message_tags || message_tags.empty?
|
25
|
+
return @global_tags_formatted
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
+
tags = if @global_tags_formatted
|
29
|
+
[@global_tags_formatted, to_tags_list(message_tags)]
|
30
|
+
else
|
31
|
+
to_tags_list(message_tags)
|
32
|
+
end
|
33
|
+
|
34
|
+
tags.join(',')
|
28
35
|
end
|
29
36
|
|
30
37
|
attr_reader :global_tags
|
@@ -51,19 +58,17 @@ module Datadog
|
|
51
58
|
def to_tags_list(tags)
|
52
59
|
case tags
|
53
60
|
when Hash
|
54
|
-
tags.
|
55
|
-
if
|
56
|
-
|
61
|
+
tags.map do |name, value|
|
62
|
+
if value
|
63
|
+
escape_tag_content("#{name}:#{value}")
|
57
64
|
else
|
58
|
-
|
65
|
+
escape_tag_content(name)
|
59
66
|
end
|
60
67
|
end
|
61
68
|
when Array
|
62
|
-
tags.
|
69
|
+
tags.map { |tag| escape_tag_content(tag) }
|
63
70
|
else
|
64
71
|
[]
|
65
|
-
end.map! do |tag|
|
66
|
-
escape_tag_content(tag)
|
67
72
|
end
|
68
73
|
end
|
69
74
|
|
@@ -11,10 +11,11 @@ module Datadog
|
|
11
11
|
attr_reader :bytes_dropped
|
12
12
|
attr_reader :packets_sent
|
13
13
|
attr_reader :packets_dropped
|
14
|
-
attr_reader :estimate_max_size
|
15
14
|
|
16
|
-
|
17
|
-
|
15
|
+
# Rough estimation of maximum telemetry message size without tags
|
16
|
+
MAX_TELEMETRY_MESSAGE_SIZE_WT_TAGS = 50 # bytes
|
17
|
+
|
18
|
+
def initialize(flush_interval, global_tags: [], transport_type: :udp)
|
18
19
|
@flush_interval = flush_interval
|
19
20
|
@global_tags = global_tags
|
20
21
|
@transport_type = transport_type
|
@@ -27,15 +28,10 @@ module Datadog
|
|
27
28
|
client_version: VERSION,
|
28
29
|
client_transport: transport_type,
|
29
30
|
).format(global_tags)
|
31
|
+
end
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
# 'max_buffer_bytes', we have to adjust with the size of the telemetry
|
34
|
-
# (and any tags used). The telemetry payload size will change depending
|
35
|
-
# on the actual value of metrics: metrics received, packet dropped,
|
36
|
-
# etc. This is why we add a 63bytes margin: 9 bytes for each of the 7
|
37
|
-
# telemetry metrics.
|
38
|
-
@estimate_max_size = disabled ? 0 : flush.length + 9 * 7
|
33
|
+
def would_fit_in?(max_buffer_payload_size)
|
34
|
+
MAX_TELEMETRY_MESSAGE_SIZE_WT_TAGS + serialized_tags.size < max_buffer_payload_size
|
39
35
|
end
|
40
36
|
|
41
37
|
def reset
|
@@ -63,27 +59,29 @@ module Datadog
|
|
63
59
|
@packets_dropped += packets
|
64
60
|
end
|
65
61
|
|
66
|
-
def
|
62
|
+
def should_flush?
|
67
63
|
@next_flush_time < now_in_s
|
68
64
|
end
|
69
65
|
|
70
66
|
def flush
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
datadog.dogstatsd.client.packets_sent:#{@packets_sent}|#{COUNTER_TYPE}|##{serialized_tags}
|
81
|
-
datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{serialized_tags})
|
67
|
+
[
|
68
|
+
sprintf(pattern, 'metrics', @metrics),
|
69
|
+
sprintf(pattern, 'events', @events),
|
70
|
+
sprintf(pattern, 'service_checks', @service_checks),
|
71
|
+
sprintf(pattern, 'bytes_sent', @bytes_sent),
|
72
|
+
sprintf(pattern, 'bytes_dropped', @bytes_dropped),
|
73
|
+
sprintf(pattern, 'packets_sent', @packets_sent),
|
74
|
+
sprintf(pattern, 'packets_dropped', @packets_dropped),
|
75
|
+
]
|
82
76
|
end
|
83
77
|
|
84
78
|
private
|
85
79
|
attr_reader :serialized_tags
|
86
80
|
|
81
|
+
def pattern
|
82
|
+
@pattern ||= "datadog.dogstatsd.client.%s:%d|#{COUNTER_TYPE}|##{serialized_tags}"
|
83
|
+
end
|
84
|
+
|
87
85
|
if Kernel.const_defined?('Process') && Process.respond_to?(:clock_gettime)
|
88
86
|
def now_in_s
|
89
87
|
Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
|
@@ -14,11 +14,11 @@ module Datadog
|
|
14
14
|
# StatsD port. Defaults to 8125.
|
15
15
|
attr_reader :port
|
16
16
|
|
17
|
-
def initialize(host, port,
|
18
|
-
super(
|
17
|
+
def initialize(host, port, **kwargs)
|
18
|
+
super(**kwargs)
|
19
|
+
|
19
20
|
@host = host || ENV.fetch('DD_AGENT_HOST', DEFAULT_HOST)
|
20
21
|
@port = port || ENV.fetch('DD_DOGSTATSD_PORT', DEFAULT_PORT).to_i
|
21
|
-
@logger = logger
|
22
22
|
end
|
23
23
|
|
24
24
|
private
|
@@ -10,10 +10,10 @@ module Datadog
|
|
10
10
|
# DogStatsd unix socket path
|
11
11
|
attr_reader :socket_path
|
12
12
|
|
13
|
-
def initialize(socket_path,
|
14
|
-
super(
|
13
|
+
def initialize(socket_path, **kwargs)
|
14
|
+
super(**kwargs)
|
15
|
+
|
15
16
|
@socket_path = socket_path
|
16
|
-
@logger = logger
|
17
17
|
end
|
18
18
|
|
19
19
|
private
|
metadata
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dogstatsd-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rein Henrichs
|
8
|
-
|
8
|
+
- Karim Bogtob
|
9
|
+
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2021-04-09 00:00:00.000000000 Z
|
12
13
|
dependencies: []
|
13
|
-
description: A Ruby
|
14
|
+
description: A Ruby DogStatsd client
|
14
15
|
email: code@datadoghq.com
|
15
16
|
executables: []
|
16
17
|
extensions: []
|
@@ -21,8 +22,10 @@ files:
|
|
21
22
|
- LICENSE.txt
|
22
23
|
- README.md
|
23
24
|
- lib/datadog/statsd.rb
|
24
|
-
- lib/datadog/statsd/batch.rb
|
25
25
|
- lib/datadog/statsd/connection.rb
|
26
|
+
- lib/datadog/statsd/forwarder.rb
|
27
|
+
- lib/datadog/statsd/message_buffer.rb
|
28
|
+
- lib/datadog/statsd/sender.rb
|
26
29
|
- lib/datadog/statsd/serialization.rb
|
27
30
|
- lib/datadog/statsd/serialization/event_serializer.rb
|
28
31
|
- lib/datadog/statsd/serialization/serializer.rb
|
@@ -38,10 +41,10 @@ licenses:
|
|
38
41
|
- MIT
|
39
42
|
metadata:
|
40
43
|
bug_tracker_uri: https://github.com/DataDog/dogstatsd-ruby/issues
|
41
|
-
changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/
|
42
|
-
documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/
|
43
|
-
source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/
|
44
|
-
post_install_message:
|
44
|
+
changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v5.0.1/CHANGELOG.md
|
45
|
+
documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/5.0.1
|
46
|
+
source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v5.0.1
|
47
|
+
post_install_message:
|
45
48
|
rdoc_options: []
|
46
49
|
require_paths:
|
47
50
|
- lib
|
@@ -56,9 +59,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
59
|
- !ruby/object:Gem::Version
|
57
60
|
version: '0'
|
58
61
|
requirements: []
|
59
|
-
rubyforge_project:
|
62
|
+
rubyforge_project:
|
60
63
|
rubygems_version: 2.7.10
|
61
|
-
signing_key:
|
64
|
+
signing_key:
|
62
65
|
specification_version: 4
|
63
66
|
summary: A Ruby DogStatsd client
|
64
67
|
test_files: []
|
data/lib/datadog/statsd/batch.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Datadog
|
4
|
-
class Statsd
|
5
|
-
class Batch
|
6
|
-
def initialize(connection, max_buffer_bytes)
|
7
|
-
@connection = connection
|
8
|
-
@max_buffer_bytes = max_buffer_bytes
|
9
|
-
@depth = 0
|
10
|
-
reset
|
11
|
-
end
|
12
|
-
|
13
|
-
def open
|
14
|
-
@depth += 1
|
15
|
-
|
16
|
-
yield
|
17
|
-
ensure
|
18
|
-
@depth -= 1
|
19
|
-
flush if !open?
|
20
|
-
end
|
21
|
-
|
22
|
-
def open?
|
23
|
-
@depth > 0
|
24
|
-
end
|
25
|
-
|
26
|
-
def add(message)
|
27
|
-
message_bytes = message.bytesize
|
28
|
-
|
29
|
-
unless @buffer_bytes == 0
|
30
|
-
if @buffer_bytes + 1 + message_bytes >= @max_buffer_bytes
|
31
|
-
flush
|
32
|
-
else
|
33
|
-
@buffer << "\n"
|
34
|
-
@buffer_bytes += 1
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
@buffer << message
|
39
|
-
@buffer_bytes += message_bytes
|
40
|
-
end
|
41
|
-
|
42
|
-
def flush
|
43
|
-
return if @buffer_bytes == 0
|
44
|
-
@connection.write(@buffer)
|
45
|
-
reset
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def reset
|
51
|
-
@buffer = String.new
|
52
|
-
@buffer_bytes = 0
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|