dogstatsd-ruby 4.6.0 → 4.7.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 +6 -1
- data/lib/datadog/statsd.rb +120 -295
- data/lib/datadog/statsd/batch.rb +56 -0
- data/lib/datadog/statsd/connection.rb +62 -0
- data/lib/datadog/statsd/telemetry.rb +65 -0
- data/lib/datadog/statsd/udp_connection.rb +37 -0
- data/lib/datadog/statsd/uds_connection.rb +35 -0
- metadata +10 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57d7660dad73ad8c0c5f37f6d6130474d255beaa467aca03f532e552d0b608e0
|
4
|
+
data.tar.gz: 30c8686d3045e4cfb2ca18c57390f78dbcccba1ff941deb170d89497953d5e84
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99214bb827186d3b01568cc59064f7d2a8d043a4a739c7f90a2b013feeeead11ed363937c37d2c29846d101ed886c318a6369c2d5a57292b0c058db81c708813
|
7
|
+
data.tar.gz: c82e6c5e9668804688fd308e091398e05ada88dac8ea78a678ee2da671eaf297ffd95997efaf376506632f65e88b4096a6a9b2590667f772f19c6b020e238827
|
data/README.md
CHANGED
@@ -25,8 +25,13 @@ require 'datadog/statsd'
|
|
25
25
|
# Create a DogStatsD client instance.
|
26
26
|
statsd = Datadog::Statsd.new('localhost', 8125)
|
27
27
|
```
|
28
|
+
Or if you want to connect over Unix Domain Socket:
|
29
|
+
```ruby
|
30
|
+
# Connection over Unix Domain Socket
|
31
|
+
statsd = Datadog::Statsd.new(socket_path: '/path/to/socket/file')
|
32
|
+
```
|
28
33
|
|
29
|
-
Find a list of all the available options for your DogStatsD Client in the [DogStatsD-ruby rubydoc](https://www.rubydoc.info/github/DataDog/dogstatsd-ruby/master/Datadog/Statsd) or in the [Datadog public DogStatsD documentation](https://docs.datadoghq.com/developers/dogstatsd/?tab=
|
34
|
+
Find a list of all the available options for your DogStatsD Client in the [DogStatsD-ruby rubydoc](https://www.rubydoc.info/github/DataDog/dogstatsd-ruby/master/Datadog/Statsd) or in the [Datadog public DogStatsD documentation](https://docs.datadoghq.com/developers/dogstatsd/?tab=ruby#client-instantiation-parameters).
|
30
35
|
|
31
36
|
### Origin detection over UDP
|
32
37
|
|
data/lib/datadog/statsd.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'socket'
|
3
3
|
|
4
|
+
require_relative 'statsd/telemetry'
|
5
|
+
require_relative 'statsd/udp_connection'
|
6
|
+
require_relative 'statsd/uds_connection'
|
7
|
+
require_relative 'statsd/batch'
|
8
|
+
|
4
9
|
# = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
|
5
10
|
#
|
6
11
|
# @example Set up a global Statsd client for a server on localhost:8125
|
@@ -19,246 +24,42 @@ require 'socket'
|
|
19
24
|
# statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
|
20
25
|
module Datadog
|
21
26
|
class Statsd
|
22
|
-
|
23
|
-
class Telemetry
|
24
|
-
attr_accessor :metrics
|
25
|
-
attr_accessor :events
|
26
|
-
attr_accessor :service_checks
|
27
|
-
attr_accessor :bytes_sent
|
28
|
-
attr_accessor :bytes_dropped
|
29
|
-
attr_accessor :packets_sent
|
30
|
-
attr_accessor :packets_dropped
|
31
|
-
attr_reader :estimate_max_size
|
32
|
-
|
33
|
-
def initialize(disabled, tags)
|
34
|
-
@disabled = disabled
|
35
|
-
@tags = tags
|
36
|
-
reset
|
37
|
-
|
38
|
-
# estimate_max_size is an estimation or the maximum size of the
|
39
|
-
# telemetry payload. Since we don't want our packet to go over
|
40
|
-
# 'max_buffer_bytes', we have to adjust with the size of the telemetry
|
41
|
-
# (and any tags used). The telemetry payload size will change depending
|
42
|
-
# on the actual value of metrics: metrics received, packet dropped,
|
43
|
-
# etc. This is why we add a 63bytes margin: 9 bytes for each of the 7
|
44
|
-
# telemetry metrics.
|
45
|
-
@estimate_max_size = @disabled ? 0 : flush().length + 9 * 7
|
46
|
-
end
|
47
|
-
|
48
|
-
def reset
|
49
|
-
@metrics = 0
|
50
|
-
@events = 0
|
51
|
-
@service_checks = 0
|
52
|
-
@bytes_sent = 0
|
53
|
-
@bytes_dropped = 0
|
54
|
-
@packets_sent = 0
|
55
|
-
@packets_dropped = 0
|
56
|
-
end
|
57
|
-
|
58
|
-
def flush
|
59
|
-
return '' if @disabled
|
60
|
-
|
61
|
-
# using shorthand syntax to reduce the garbage collection
|
62
|
-
return %Q(
|
63
|
-
datadog.dogstatsd.client.metrics:#{@metrics}|#{COUNTER_TYPE}|##{@tags}
|
64
|
-
datadog.dogstatsd.client.events:#{@events}|#{COUNTER_TYPE}|##{@tags}
|
65
|
-
datadog.dogstatsd.client.service_checks:#{@service_checks}|#{COUNTER_TYPE}|##{@tags}
|
66
|
-
datadog.dogstatsd.client.bytes_sent:#{@bytes_sent}|#{COUNTER_TYPE}|##{@tags}
|
67
|
-
datadog.dogstatsd.client.bytes_dropped:#{@bytes_dropped}|#{COUNTER_TYPE}|##{@tags}
|
68
|
-
datadog.dogstatsd.client.packets_sent:#{@packets_sent}|#{COUNTER_TYPE}|##{@tags}
|
69
|
-
datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{@tags})
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
class Connection
|
74
|
-
DEFAULT_HOST = '127.0.0.1'
|
75
|
-
DEFAULT_PORT = 8125
|
76
|
-
|
77
|
-
# StatsD host. Defaults to 127.0.0.1.
|
78
|
-
attr_reader :host
|
79
|
-
|
80
|
-
# StatsD port. Defaults to 8125.
|
81
|
-
attr_reader :port
|
82
|
-
|
83
|
-
# DogStatsd unix socket path. Not used by default.
|
84
|
-
attr_reader :socket_path
|
85
|
-
|
86
|
-
def initialize(telemetry)
|
87
|
-
@telemetry = telemetry
|
88
|
-
end
|
89
|
-
|
90
|
-
# Close the underlying socket
|
91
|
-
def close
|
92
|
-
@socket && @socket.close
|
93
|
-
end
|
94
|
-
|
95
|
-
def write(message)
|
96
|
-
@logger.debug { "Statsd: #{message}" } if @logger
|
97
|
-
payload = message + @telemetry.flush()
|
98
|
-
send_message(payload)
|
99
|
-
|
100
|
-
@telemetry.reset
|
101
|
-
@telemetry.bytes_sent += payload.length
|
102
|
-
@telemetry.packets_sent += 1
|
103
|
-
rescue StandardError => boom
|
104
|
-
# Try once to reconnect if the socket has been closed
|
105
|
-
retries ||= 1
|
106
|
-
if retries <= 1 &&
|
107
|
-
(boom.is_a?(Errno::ENOTCONN) or
|
108
|
-
boom.is_a?(Errno::ECONNREFUSED) or
|
109
|
-
boom.is_a?(IOError) && boom.message =~ /closed stream/i)
|
110
|
-
retries += 1
|
111
|
-
begin
|
112
|
-
@socket = connect
|
113
|
-
retry
|
114
|
-
rescue StandardError => e
|
115
|
-
boom = e
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
@telemetry.bytes_dropped += payload.length
|
120
|
-
@telemetry.packets_dropped += 1
|
121
|
-
@logger.error { "Statsd: #{boom.class} #{boom}" } if @logger
|
122
|
-
nil
|
123
|
-
end
|
124
|
-
|
125
|
-
private
|
126
|
-
|
127
|
-
def socket
|
128
|
-
@socket ||= connect
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
class UDPConnection < Connection
|
133
|
-
def initialize(host, port, logger, telemetry)
|
134
|
-
super(telemetry)
|
135
|
-
@host = host || ENV.fetch('DD_AGENT_HOST', nil) || DEFAULT_HOST
|
136
|
-
@port = port || ENV.fetch('DD_DOGSTATSD_PORT', nil) || DEFAULT_PORT
|
137
|
-
@logger = logger
|
138
|
-
end
|
139
|
-
|
140
|
-
private
|
141
|
-
|
142
|
-
def connect
|
143
|
-
socket = UDPSocket.new
|
144
|
-
socket.connect(@host, @port)
|
145
|
-
socket
|
146
|
-
end
|
147
|
-
|
148
|
-
def send_message(message)
|
149
|
-
socket.send(message, 0)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
class UDSConnection < Connection
|
154
|
-
class BadSocketError < StandardError; end
|
155
|
-
|
156
|
-
def initialize(socket_path, logger, telemetry)
|
157
|
-
super(telemetry)
|
158
|
-
@socket_path = socket_path
|
159
|
-
@logger = logger
|
160
|
-
end
|
161
|
-
|
162
|
-
private
|
163
|
-
|
164
|
-
def connect
|
165
|
-
socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
|
166
|
-
socket.connect(Socket.pack_sockaddr_un(@socket_path))
|
167
|
-
socket
|
168
|
-
end
|
169
|
-
|
170
|
-
def send_message(message)
|
171
|
-
socket.sendmsg_nonblock(message)
|
172
|
-
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENOENT => e
|
173
|
-
@socket = nil
|
174
|
-
raise BadSocketError, "#{e.class}: #{e}"
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
class Batch
|
179
|
-
def initialize(connection, max_buffer_bytes)
|
180
|
-
@connection = connection
|
181
|
-
@max_buffer_bytes = max_buffer_bytes
|
182
|
-
@depth = 0
|
183
|
-
reset
|
184
|
-
end
|
185
|
-
|
186
|
-
def open
|
187
|
-
@depth += 1
|
188
|
-
yield
|
189
|
-
ensure
|
190
|
-
@depth -= 1
|
191
|
-
flush if !open?
|
192
|
-
end
|
193
|
-
|
194
|
-
def open?
|
195
|
-
@depth > 0
|
196
|
-
end
|
197
|
-
|
198
|
-
def add(message)
|
199
|
-
message_bytes = message.bytesize
|
200
|
-
|
201
|
-
unless @buffer_bytes == 0
|
202
|
-
if @buffer_bytes + 1 + message_bytes >= @max_buffer_bytes
|
203
|
-
flush
|
204
|
-
else
|
205
|
-
@buffer << NEW_LINE
|
206
|
-
@buffer_bytes += 1
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
@buffer << message
|
211
|
-
@buffer_bytes += message_bytes
|
212
|
-
end
|
213
|
-
|
214
|
-
def flush
|
215
|
-
return if @buffer_bytes == 0
|
216
|
-
@connection.write @buffer
|
217
|
-
reset
|
218
|
-
end
|
219
|
-
|
220
|
-
private
|
221
|
-
|
222
|
-
def reset
|
223
|
-
@buffer = String.new
|
224
|
-
@buffer_bytes = 0
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
27
|
# Create a dictionary to assign a key to every parameter's name, except for tags (treated differently)
|
229
28
|
# Goal: Simple and fast to add some other parameters
|
230
29
|
OPTS_KEYS = {
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
:
|
236
|
-
|
237
|
-
}
|
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
|
238
37
|
|
239
38
|
# Service check options
|
240
39
|
SC_OPT_KEYS = {
|
241
|
-
:
|
242
|
-
:
|
243
|
-
:
|
244
|
-
:
|
245
|
-
}
|
40
|
+
timestamp: 'd:',
|
41
|
+
hostname: 'h:',
|
42
|
+
tags: '#',
|
43
|
+
message: 'm:',
|
44
|
+
}.freeze
|
246
45
|
|
247
|
-
OK
|
248
|
-
WARNING
|
249
|
-
CRITICAL
|
250
|
-
UNKNOWN
|
46
|
+
OK = 0
|
47
|
+
WARNING = 1
|
48
|
+
CRITICAL = 2
|
49
|
+
UNKNOWN = 3
|
251
50
|
|
252
51
|
DEFAULT_BUFFER_SIZE = 8 * 1_024
|
253
52
|
MAX_EVENT_SIZE = 8 * 1_024
|
53
|
+
# minimum flush interval for the telemetry in seconds
|
54
|
+
DEFAULT_TELEMETRY_FLUSH_INTERVAL = 10
|
254
55
|
|
255
|
-
COUNTER_TYPE = 'c'
|
256
|
-
GAUGE_TYPE = 'g'
|
257
|
-
HISTOGRAM_TYPE = 'h'
|
258
|
-
DISTRIBUTION_TYPE = 'd'
|
259
|
-
TIMING_TYPE = 'ms'
|
260
|
-
SET_TYPE = 's'
|
261
|
-
VERSION =
|
56
|
+
COUNTER_TYPE = 'c'
|
57
|
+
GAUGE_TYPE = 'g'
|
58
|
+
HISTOGRAM_TYPE = 'h'
|
59
|
+
DISTRIBUTION_TYPE = 'd'
|
60
|
+
TIMING_TYPE = 'ms'
|
61
|
+
SET_TYPE = 's'
|
62
|
+
VERSION = '4.7.0'
|
262
63
|
|
263
64
|
# A namespace to prepend to all statsd calls. Defaults to no namespace.
|
264
65
|
attr_reader :namespace
|
@@ -295,22 +96,28 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
295
96
|
socket_path: nil,
|
296
97
|
logger: nil,
|
297
98
|
sample_rate: nil,
|
298
|
-
disable_telemetry: false
|
99
|
+
disable_telemetry: false,
|
100
|
+
telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
|
299
101
|
)
|
300
|
-
unless tags.nil?
|
102
|
+
unless tags.nil? || tags.is_a?(Array) || tags.is_a?(Hash)
|
301
103
|
raise ArgumentError, 'tags must be a Array<String> or a Hash'
|
302
104
|
end
|
303
105
|
|
304
|
-
tags = tag_hash_to_array(tags) if tags.is_a?
|
305
|
-
@tags = (tags || []).compact.map!
|
106
|
+
tags = tag_hash_to_array(tags) if tags.is_a?(Hash)
|
107
|
+
@tags = (tags || []).compact.map! do |tag|
|
108
|
+
escape_tag_content(tag)
|
109
|
+
end
|
306
110
|
|
307
111
|
# append the entity id to tags if DD_ENTITY_ID env var is not nil
|
308
|
-
|
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
|
309
116
|
|
310
117
|
# init telemetry
|
311
|
-
transport_type = socket_path.nil? ?
|
118
|
+
transport_type = socket_path.nil? ? 'udp': 'uds'
|
312
119
|
telemetry_tags = (["client:ruby", "client_version:#{VERSION}", "client_transport:#{transport_type}"] + @tags).join(COMMA).freeze
|
313
|
-
@telemetry = Telemetry.new(disable_telemetry, telemetry_tags)
|
120
|
+
@telemetry = Telemetry.new(disable_telemetry, telemetry_tags, telemetry_flush_interval)
|
314
121
|
|
315
122
|
if socket_path.nil?
|
316
123
|
@connection = UDPConnection.new(host, port, logger, @telemetry)
|
@@ -332,6 +139,7 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
332
139
|
# for short-term use-cases that don't want to close the socket manually
|
333
140
|
def self.open(*args)
|
334
141
|
instance = new(*args)
|
142
|
+
|
335
143
|
yield instance
|
336
144
|
ensure
|
337
145
|
instance.close
|
@@ -345,10 +153,10 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
345
153
|
# @option opts [Array<String>] :tags An array of tags
|
346
154
|
# @option opts [Numeric] :by increment value, default 1
|
347
155
|
# @see #count
|
348
|
-
def increment(stat, opts=EMPTY_OPTIONS)
|
349
|
-
opts = {:
|
156
|
+
def increment(stat, opts = EMPTY_OPTIONS)
|
157
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
350
158
|
incr_value = opts.fetch(:by, 1)
|
351
|
-
count
|
159
|
+
count(stat, incr_value, opts)
|
352
160
|
end
|
353
161
|
|
354
162
|
# Sends a decrement (count = -1) for the given stat to the statsd server.
|
@@ -359,10 +167,10 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
359
167
|
# @option opts [Array<String>] :tags An array of tags
|
360
168
|
# @option opts [Numeric] :by decrement value, default 1
|
361
169
|
# @see #count
|
362
|
-
def decrement(stat, opts=EMPTY_OPTIONS)
|
363
|
-
opts = {:
|
170
|
+
def decrement(stat, opts = EMPTY_OPTIONS)
|
171
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
364
172
|
decr_value = - opts.fetch(:by, 1)
|
365
|
-
count
|
173
|
+
count(stat, decr_value, opts)
|
366
174
|
end
|
367
175
|
|
368
176
|
# Sends an arbitrary count for the given stat to the statsd server.
|
@@ -372,9 +180,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
372
180
|
# @param [Hash] opts the options to create the metric with
|
373
181
|
# @option opts [Numeric] :sample_rate sample rate, 1 for always
|
374
182
|
# @option opts [Array<String>] :tags An array of tags
|
375
|
-
def count(stat, count, opts=EMPTY_OPTIONS)
|
376
|
-
opts = {:
|
377
|
-
send_stats
|
183
|
+
def count(stat, count, opts = EMPTY_OPTIONS)
|
184
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
185
|
+
send_stats(stat, count, COUNTER_TYPE, opts)
|
378
186
|
end
|
379
187
|
|
380
188
|
# Sends an arbitary gauge value for the given stat to the statsd server.
|
@@ -390,9 +198,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
390
198
|
# @option opts [Array<String>] :tags An array of tags
|
391
199
|
# @example Report the current user count:
|
392
200
|
# $statsd.gauge('user.count', User.count)
|
393
|
-
def gauge(stat, value, opts=EMPTY_OPTIONS)
|
394
|
-
opts = {:
|
395
|
-
send_stats
|
201
|
+
def gauge(stat, value, opts = EMPTY_OPTIONS)
|
202
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
203
|
+
send_stats(stat, value, GAUGE_TYPE, opts)
|
396
204
|
end
|
397
205
|
|
398
206
|
# Sends a value to be tracked as a histogram to the statsd server.
|
@@ -404,8 +212,8 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
404
212
|
# @option opts [Array<String>] :tags An array of tags
|
405
213
|
# @example Report the current user count:
|
406
214
|
# $statsd.histogram('user.count', User.count)
|
407
|
-
def histogram(stat, value, opts=EMPTY_OPTIONS)
|
408
|
-
send_stats
|
215
|
+
def histogram(stat, value, opts = EMPTY_OPTIONS)
|
216
|
+
send_stats(stat, value, HISTOGRAM_TYPE, opts)
|
409
217
|
end
|
410
218
|
|
411
219
|
# Sends a value to be tracked as a distribution to the statsd server.
|
@@ -417,8 +225,8 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
417
225
|
# @option opts [Array<String>] :tags An array of tags
|
418
226
|
# @example Report the current user count:
|
419
227
|
# $statsd.distribution('user.count', User.count)
|
420
|
-
def distribution(stat, value, opts=EMPTY_OPTIONS)
|
421
|
-
send_stats
|
228
|
+
def distribution(stat, value, opts = EMPTY_OPTIONS)
|
229
|
+
send_stats(stat, value, DISTRIBUTION_TYPE, opts)
|
422
230
|
end
|
423
231
|
|
424
232
|
# Sends a timing (in ms) for the given stat to the statsd server. The
|
@@ -431,9 +239,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
431
239
|
# @param [Hash] opts the options to create the metric with
|
432
240
|
# @option opts [Numeric] :sample_rate sample rate, 1 for always
|
433
241
|
# @option opts [Array<String>] :tags An array of tags
|
434
|
-
def timing(stat, ms, opts=EMPTY_OPTIONS)
|
435
|
-
opts = {:
|
436
|
-
send_stats
|
242
|
+
def timing(stat, ms, opts = EMPTY_OPTIONS)
|
243
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
244
|
+
send_stats(stat, ms, TIMING_TYPE, opts)
|
437
245
|
end
|
438
246
|
|
439
247
|
# Reports execution time of the provided block using {#timing}.
|
@@ -449,12 +257,21 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
449
257
|
# @see #timing
|
450
258
|
# @example Report the time (in ms) taken to activate an account
|
451
259
|
# $statsd.time('account.activate') { @account.activate! }
|
452
|
-
def time(stat, opts=EMPTY_OPTIONS)
|
453
|
-
opts = {:
|
454
|
-
start =
|
455
|
-
|
260
|
+
def time(stat, opts = EMPTY_OPTIONS)
|
261
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
262
|
+
start = if PROCESS_TIME_SUPPORTED
|
263
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) # uncovered
|
264
|
+
else
|
265
|
+
Time.now.to_f # uncovered
|
266
|
+
end
|
267
|
+
yield
|
456
268
|
ensure
|
457
|
-
finished =
|
269
|
+
finished = if PROCESS_TIME_SUPPORTED
|
270
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) # uncovered
|
271
|
+
else
|
272
|
+
Time.now.to_f # uncovered
|
273
|
+
end
|
274
|
+
|
458
275
|
timing(stat, ((finished - start) * 1000).round, opts)
|
459
276
|
end
|
460
277
|
|
@@ -467,9 +284,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
467
284
|
# @option opts [Array<String>] :tags An array of tags
|
468
285
|
# @example Record a unique visitory by id:
|
469
286
|
# $statsd.set('visitors.uniques', User.id)
|
470
|
-
def set(stat, value, opts=EMPTY_OPTIONS)
|
471
|
-
opts = {:
|
472
|
-
send_stats
|
287
|
+
def set(stat, value, opts = EMPTY_OPTIONS)
|
288
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
289
|
+
send_stats(stat, value, SET_TYPE, opts)
|
473
290
|
end
|
474
291
|
|
475
292
|
# This method allows you to send custom service check statuses.
|
@@ -483,9 +300,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
483
300
|
# @option opts [String, nil] :message (nil) A message to associate with this service check status
|
484
301
|
# @example Report a critical service check status
|
485
302
|
# $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
|
486
|
-
def service_check(name, status, opts=EMPTY_OPTIONS)
|
303
|
+
def service_check(name, status, opts = EMPTY_OPTIONS)
|
487
304
|
@telemetry.service_checks += 1
|
488
|
-
send_stat
|
305
|
+
send_stat(format_service_check(name, status, opts))
|
489
306
|
end
|
490
307
|
|
491
308
|
# This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
|
@@ -506,9 +323,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
506
323
|
# @option opts [Array<String>] :tags tags to be added to every metric
|
507
324
|
# @example Report an awful event:
|
508
325
|
# $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
|
509
|
-
def event(title, text, opts=EMPTY_OPTIONS)
|
326
|
+
def event(title, text, opts = EMPTY_OPTIONS)
|
510
327
|
@telemetry.events += 1
|
511
|
-
send_stat
|
328
|
+
send_stat(format_event(title, text, opts))
|
512
329
|
end
|
513
330
|
|
514
331
|
# Send several metrics in the same UDP Packet
|
@@ -520,7 +337,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
520
337
|
# s.increment('page.views')
|
521
338
|
# end
|
522
339
|
def batch
|
523
|
-
@batch.open
|
340
|
+
@batch.open do
|
341
|
+
yield self
|
342
|
+
end
|
524
343
|
end
|
525
344
|
|
526
345
|
# Close the underlying socket
|
@@ -530,20 +349,20 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
530
349
|
|
531
350
|
private
|
532
351
|
|
533
|
-
NEW_LINE = "\n"
|
534
|
-
ESC_NEW_LINE =
|
535
|
-
COMMA =
|
536
|
-
PIPE =
|
537
|
-
DOT =
|
538
|
-
DOUBLE_COLON =
|
539
|
-
UNDERSCORE =
|
540
|
-
PROCESS_TIME_SUPPORTED = (RUBY_VERSION >=
|
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')
|
541
360
|
EMPTY_OPTIONS = {}.freeze
|
542
361
|
|
543
362
|
private_constant :NEW_LINE, :ESC_NEW_LINE, :COMMA, :PIPE, :DOT,
|
544
363
|
:DOUBLE_COLON, :UNDERSCORE, :EMPTY_OPTIONS
|
545
364
|
|
546
|
-
def format_service_check(name, status, opts=EMPTY_OPTIONS)
|
365
|
+
def format_service_check(name, status, opts = EMPTY_OPTIONS)
|
547
366
|
sc_string = "_sc|#{name}|#{status}".dup
|
548
367
|
|
549
368
|
SC_OPT_KEYS.each do |key, shorthand_key|
|
@@ -569,7 +388,7 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
569
388
|
sc_string
|
570
389
|
end
|
571
390
|
|
572
|
-
def format_event(title, text, opts=EMPTY_OPTIONS)
|
391
|
+
def format_event(title, text, opts = EMPTY_OPTIONS)
|
573
392
|
escaped_title = escape_event_content(title)
|
574
393
|
escaped_text = escape_event_content(text)
|
575
394
|
event_string_data = "_e{#{escaped_title.bytesize},#{escaped_text.bytesize}}:#{escaped_title}|#{escaped_text}".dup
|
@@ -595,14 +414,18 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
595
414
|
event_string_data << "|##{tags_string}"
|
596
415
|
end
|
597
416
|
|
598
|
-
|
417
|
+
if event_string_data.bytesize > MAX_EVENT_SIZE
|
418
|
+
raise "Event #{title} payload is too big (more that 8KB), event discarded"
|
419
|
+
end
|
599
420
|
event_string_data
|
600
421
|
end
|
601
422
|
|
602
423
|
def tags_as_string(opts)
|
603
424
|
if tag_arr = opts[:tags]
|
604
|
-
tag_arr = tag_hash_to_array(tag_arr) if tag_arr.is_a?
|
605
|
-
tag_arr = tag_arr.map
|
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
|
606
429
|
tag_arr = tags + tag_arr # @tags are normalized when set, so not need to normalize them again
|
607
430
|
else
|
608
431
|
tag_arr = tags
|
@@ -611,54 +434,56 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
611
434
|
end
|
612
435
|
|
613
436
|
def tag_hash_to_array(tag_hash)
|
614
|
-
tag_hash.to_a.map
|
437
|
+
tag_hash.to_a.map do |pair|
|
438
|
+
pair.compact.join(':')
|
439
|
+
end
|
615
440
|
end
|
616
441
|
|
617
|
-
def escape_event_content(
|
618
|
-
|
442
|
+
def escape_event_content(message)
|
443
|
+
message.gsub(NEW_LINE, ESC_NEW_LINE)
|
619
444
|
end
|
620
445
|
|
621
446
|
def escape_tag_content(tag)
|
622
447
|
tag = remove_pipes(tag.to_s)
|
623
|
-
tag.delete!
|
448
|
+
tag.delete!(COMMA)
|
624
449
|
tag
|
625
450
|
end
|
626
451
|
|
627
|
-
def remove_pipes(
|
628
|
-
|
452
|
+
def remove_pipes(message)
|
453
|
+
message.delete(PIPE)
|
629
454
|
end
|
630
455
|
|
631
|
-
def escape_service_check_message(
|
632
|
-
escape_event_content(
|
456
|
+
def escape_service_check_message(message)
|
457
|
+
escape_event_content(message).gsub('m:', 'm\:')
|
633
458
|
end
|
634
459
|
|
635
|
-
def send_stats(stat, delta, type, opts=EMPTY_OPTIONS)
|
460
|
+
def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
|
636
461
|
@telemetry.metrics += 1
|
637
462
|
sample_rate = opts[:sample_rate] || @sample_rate || 1
|
638
|
-
if sample_rate == 1
|
463
|
+
if sample_rate == 1 || rand <= sample_rate
|
639
464
|
full_stat = ''.dup
|
640
465
|
full_stat << @prefix if @prefix
|
641
466
|
|
642
467
|
stat = stat.is_a?(String) ? stat.dup : stat.to_s
|
643
468
|
# Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
|
644
469
|
stat.gsub!(DOUBLE_COLON, DOT)
|
645
|
-
stat.tr!(':|@'
|
470
|
+
stat.tr!(':|@', UNDERSCORE)
|
646
471
|
full_stat << stat
|
647
472
|
|
648
|
-
full_stat << ':'
|
473
|
+
full_stat << ':'
|
649
474
|
full_stat << delta.to_s
|
650
475
|
full_stat << PIPE
|
651
476
|
full_stat << type
|
652
477
|
|
653
478
|
unless sample_rate == 1
|
654
479
|
full_stat << PIPE
|
655
|
-
full_stat << '@'
|
480
|
+
full_stat << '@'
|
656
481
|
full_stat << sample_rate.to_s
|
657
482
|
end
|
658
483
|
|
659
484
|
if tags_string = tags_as_string(opts)
|
660
485
|
full_stat << PIPE
|
661
|
-
full_stat << '#'
|
486
|
+
full_stat << '#'
|
662
487
|
full_stat << tags_string
|
663
488
|
end
|
664
489
|
send_stat(full_stat)
|
@@ -0,0 +1,56 @@
|
|
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 << NEW_LINE
|
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
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
class Statsd
|
5
|
+
class Connection
|
6
|
+
def initialize(telemetry)
|
7
|
+
@telemetry = telemetry
|
8
|
+
end
|
9
|
+
|
10
|
+
# Close the underlying socket
|
11
|
+
def close
|
12
|
+
@socket && @socket.close
|
13
|
+
end
|
14
|
+
|
15
|
+
def write(payload)
|
16
|
+
logger.debug { "Statsd: #{payload}" } if logger
|
17
|
+
flush_telemetry = @telemetry.flush?
|
18
|
+
if flush_telemetry
|
19
|
+
payload += @telemetry.flush()
|
20
|
+
end
|
21
|
+
|
22
|
+
send_message(payload)
|
23
|
+
|
24
|
+
if flush_telemetry
|
25
|
+
@telemetry.reset
|
26
|
+
end
|
27
|
+
|
28
|
+
telemetry.bytes_sent += payload.length
|
29
|
+
telemetry.packets_sent += 1
|
30
|
+
rescue StandardError => boom
|
31
|
+
# Try once to reconnect if the socket has been closed
|
32
|
+
retries ||= 1
|
33
|
+
if retries <= 1 &&
|
34
|
+
(boom.is_a?(Errno::ENOTCONN) or
|
35
|
+
boom.is_a?(Errno::ECONNREFUSED) or
|
36
|
+
boom.is_a?(IOError) && boom.message =~ /closed stream/i)
|
37
|
+
retries += 1
|
38
|
+
begin
|
39
|
+
@socket = connect
|
40
|
+
retry
|
41
|
+
rescue StandardError => e
|
42
|
+
boom = e
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
telemetry.bytes_dropped += payload.length
|
47
|
+
telemetry.packets_dropped += 1
|
48
|
+
logger.error { "Statsd: #{boom.class} #{boom}" } if logger
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
attr_reader :telemetry
|
55
|
+
attr_reader :logger
|
56
|
+
|
57
|
+
def socket
|
58
|
+
@socket ||= connect
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Datadog
|
5
|
+
class Statsd
|
6
|
+
class Telemetry
|
7
|
+
attr_accessor :metrics
|
8
|
+
attr_accessor :events
|
9
|
+
attr_accessor :service_checks
|
10
|
+
attr_accessor :bytes_sent
|
11
|
+
attr_accessor :bytes_dropped
|
12
|
+
attr_accessor :packets_sent
|
13
|
+
attr_accessor :packets_dropped
|
14
|
+
attr_reader :estimate_max_size
|
15
|
+
|
16
|
+
def initialize(disabled, tags, flush_interval)
|
17
|
+
@disabled = disabled
|
18
|
+
@tags = tags
|
19
|
+
@flush_interval = flush_interval
|
20
|
+
reset
|
21
|
+
|
22
|
+
# estimate_max_size is an estimation or the maximum size of the
|
23
|
+
# telemetry payload. Since we don't want our packet to go over
|
24
|
+
# 'max_buffer_bytes', we have to adjust with the size of the telemetry
|
25
|
+
# (and any tags used). The telemetry payload size will change depending
|
26
|
+
# on the actual value of metrics: metrics received, packet dropped,
|
27
|
+
# etc. This is why we add a 63bytes margin: 9 bytes for each of the 7
|
28
|
+
# telemetry metrics.
|
29
|
+
@estimate_max_size = @disabled ? 0 : flush().length + 9 * 7
|
30
|
+
end
|
31
|
+
|
32
|
+
def reset
|
33
|
+
@metrics = 0
|
34
|
+
@events = 0
|
35
|
+
@service_checks = 0
|
36
|
+
@bytes_sent = 0
|
37
|
+
@bytes_dropped = 0
|
38
|
+
@packets_sent = 0
|
39
|
+
@packets_dropped = 0
|
40
|
+
@next_flush_time = Time.now.to_i + @flush_interval
|
41
|
+
end
|
42
|
+
|
43
|
+
def flush?
|
44
|
+
if @next_flush_time < Time.now.to_i
|
45
|
+
return true
|
46
|
+
end
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
|
50
|
+
def flush
|
51
|
+
return '' if @disabled
|
52
|
+
|
53
|
+
# using shorthand syntax to reduce the garbage collection
|
54
|
+
return %Q(
|
55
|
+
datadog.dogstatsd.client.metrics:#{@metrics}|#{COUNTER_TYPE}|##{@tags}
|
56
|
+
datadog.dogstatsd.client.events:#{@events}|#{COUNTER_TYPE}|##{@tags}
|
57
|
+
datadog.dogstatsd.client.service_checks:#{@service_checks}|#{COUNTER_TYPE}|##{@tags}
|
58
|
+
datadog.dogstatsd.client.bytes_sent:#{@bytes_sent}|#{COUNTER_TYPE}|##{@tags}
|
59
|
+
datadog.dogstatsd.client.bytes_dropped:#{@bytes_dropped}|#{COUNTER_TYPE}|##{@tags}
|
60
|
+
datadog.dogstatsd.client.packets_sent:#{@packets_sent}|#{COUNTER_TYPE}|##{@tags}
|
61
|
+
datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{@tags})
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'connection'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
class Statsd
|
7
|
+
class UDPConnection < Connection
|
8
|
+
DEFAULT_HOST = '127.0.0.1'
|
9
|
+
DEFAULT_PORT = 8125
|
10
|
+
|
11
|
+
# StatsD host. Defaults to 127.0.0.1.
|
12
|
+
attr_reader :host
|
13
|
+
|
14
|
+
# StatsD port. Defaults to 8125.
|
15
|
+
attr_reader :port
|
16
|
+
|
17
|
+
def initialize(host, port, logger, telemetry)
|
18
|
+
super(telemetry)
|
19
|
+
@host = host || ENV.fetch('DD_AGENT_HOST', DEFAULT_HOST)
|
20
|
+
@port = port || ENV.fetch('DD_DOGSTATSD_PORT', DEFAULT_PORT)
|
21
|
+
@logger = logger
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def connect
|
27
|
+
UDPSocket.new.tap do |socket|
|
28
|
+
socket.connect(host, port)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def send_message(message)
|
33
|
+
socket.send(message, 0)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'connection'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
class Statsd
|
7
|
+
class UDSConnection < Connection
|
8
|
+
class BadSocketError < StandardError; end
|
9
|
+
|
10
|
+
# DogStatsd unix socket path
|
11
|
+
attr_reader :socket_path
|
12
|
+
|
13
|
+
def initialize(socket_path, logger, telemetry)
|
14
|
+
super(telemetry)
|
15
|
+
@socket_path = socket_path
|
16
|
+
@logger = logger
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def connect
|
22
|
+
socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
|
23
|
+
socket.connect(Socket.pack_sockaddr_un(@socket_path))
|
24
|
+
socket
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_message(message)
|
28
|
+
socket.sendmsg_nonblock(message)
|
29
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENOENT => e
|
30
|
+
@socket = nil
|
31
|
+
raise BadSocketError, "#{e.class}: #{e}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
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.7.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-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A Ruby DogStastd client
|
14
14
|
email: code@datadoghq.com
|
@@ -21,14 +21,19 @@ files:
|
|
21
21
|
- LICENSE.txt
|
22
22
|
- README.md
|
23
23
|
- lib/datadog/statsd.rb
|
24
|
+
- lib/datadog/statsd/batch.rb
|
25
|
+
- lib/datadog/statsd/connection.rb
|
26
|
+
- lib/datadog/statsd/telemetry.rb
|
27
|
+
- lib/datadog/statsd/udp_connection.rb
|
28
|
+
- lib/datadog/statsd/uds_connection.rb
|
24
29
|
homepage: https://github.com/DataDog/dogstatsd-ruby
|
25
30
|
licenses:
|
26
31
|
- MIT
|
27
32
|
metadata:
|
28
33
|
bug_tracker_uri: https://github.com/DataDog/dogstatsd-ruby/issues
|
29
|
-
changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v4.
|
30
|
-
documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/4.
|
31
|
-
source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v4.
|
34
|
+
changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v4.7.0/CHANGELOG.md
|
35
|
+
documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/4.7.0
|
36
|
+
source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v4.7.0
|
32
37
|
post_install_message:
|
33
38
|
rdoc_options: []
|
34
39
|
require_paths:
|