dogstatsd-ruby 4.6.0 → 4.7.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 +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:
|