dogstatsd-ruby 4.6.0 → 4.8.3
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 +19 -1
- data/lib/datadog/statsd.rb +102 -415
- data/lib/datadog/statsd/batch.rb +56 -0
- data/lib/datadog/statsd/connection.rb +62 -0
- data/lib/datadog/statsd/serialization.rb +15 -0
- data/lib/datadog/statsd/serialization/event_serializer.rb +71 -0
- data/lib/datadog/statsd/serialization/serializer.rb +41 -0
- data/lib/datadog/statsd/serialization/service_check_serializer.rb +60 -0
- data/lib/datadog/statsd/serialization/stat_serializer.rb +55 -0
- data/lib/datadog/statsd/serialization/tag_serializer.rb +92 -0
- data/lib/datadog/statsd/telemetry.rb +98 -0
- data/lib/datadog/statsd/udp_connection.rb +37 -0
- data/lib/datadog/statsd/uds_connection.rb +38 -0
- data/lib/datadog/statsd/version.rb +9 -0
- metadata +21 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc2f6d365efc8cf25456fb9dbd5ad526abe0ec61f2b63e9b79346c767aa3a3b0
|
4
|
+
data.tar.gz: 84a029741390b63cf0540de60a872c6b0820bf6ad4c1619cea120d71bc38c3bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 769645a11d455fceb1b0c4d7d718005d869c62546cd1c255f0a45b9da51a300500dfe2c1a4e601248f1e8ef6ce1e40c15ebaca55d6e55092babef1c153ccdcf0
|
7
|
+
data.tar.gz: 1429d390a2c52758f957dd4766cf30db5039986527982f3188fcc44d7c058431d9f85180b1af7e5dae8f82db31325115b0ea4efb1fced82d7585924b0702fd0c
|
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
|
|
@@ -66,6 +71,19 @@ After the client is created, you can start sending events to your Datadog Event
|
|
66
71
|
|
67
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.
|
68
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
|
+
|
69
87
|
## Credits
|
70
88
|
|
71
89
|
dogstatsd-ruby is forked from Rien Henrichs [original Statsd
|
data/lib/datadog/statsd.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'socket'
|
3
3
|
|
4
|
+
require_relative 'statsd/version'
|
5
|
+
require_relative 'statsd/telemetry'
|
6
|
+
require_relative 'statsd/udp_connection'
|
7
|
+
require_relative 'statsd/uds_connection'
|
8
|
+
require_relative 'statsd/batch'
|
9
|
+
require_relative 'statsd/serialization'
|
10
|
+
|
4
11
|
# = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
|
5
12
|
#
|
6
13
|
# @example Set up a global Statsd client for a server on localhost:8125
|
@@ -19,252 +26,30 @@ require 'socket'
|
|
19
26
|
# statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
|
20
27
|
module Datadog
|
21
28
|
class Statsd
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
# Create a dictionary to assign a key to every parameter's name, except for tags (treated differently)
|
229
|
-
# Goal: Simple and fast to add some other parameters
|
230
|
-
OPTS_KEYS = {
|
231
|
-
:date_happened => :d,
|
232
|
-
:hostname => :h,
|
233
|
-
:aggregation_key => :k,
|
234
|
-
:priority => :p,
|
235
|
-
:source_type_name => :s,
|
236
|
-
:alert_type => :t,
|
237
|
-
}
|
238
|
-
|
239
|
-
# Service check options
|
240
|
-
SC_OPT_KEYS = {
|
241
|
-
:timestamp => 'd:'.freeze,
|
242
|
-
:hostname => 'h:'.freeze,
|
243
|
-
:tags => '#'.freeze,
|
244
|
-
:message => 'm:'.freeze,
|
245
|
-
}
|
246
|
-
|
247
|
-
OK = 0
|
248
|
-
WARNING = 1
|
249
|
-
CRITICAL = 2
|
250
|
-
UNKNOWN = 3
|
29
|
+
OK = 0
|
30
|
+
WARNING = 1
|
31
|
+
CRITICAL = 2
|
32
|
+
UNKNOWN = 3
|
251
33
|
|
252
34
|
DEFAULT_BUFFER_SIZE = 8 * 1_024
|
253
35
|
MAX_EVENT_SIZE = 8 * 1_024
|
36
|
+
# minimum flush interval for the telemetry in seconds
|
37
|
+
DEFAULT_TELEMETRY_FLUSH_INTERVAL = 10
|
254
38
|
|
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 = "4.6.0".freeze
|
39
|
+
COUNTER_TYPE = 'c'
|
40
|
+
GAUGE_TYPE = 'g'
|
41
|
+
HISTOGRAM_TYPE = 'h'
|
42
|
+
DISTRIBUTION_TYPE = 'd'
|
43
|
+
TIMING_TYPE = 'ms'
|
44
|
+
SET_TYPE = 's'
|
262
45
|
|
263
46
|
# A namespace to prepend to all statsd calls. Defaults to no namespace.
|
264
47
|
attr_reader :namespace
|
265
48
|
|
266
49
|
# Global tags to be added to every statsd call. Defaults to no tags.
|
267
|
-
|
50
|
+
def tags
|
51
|
+
serializer.global_tags
|
52
|
+
end
|
268
53
|
|
269
54
|
# Buffer containing the statsd message before they are sent in batch
|
270
55
|
attr_reader :buffer
|
@@ -295,43 +80,45 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
295
80
|
socket_path: nil,
|
296
81
|
logger: nil,
|
297
82
|
sample_rate: nil,
|
298
|
-
disable_telemetry: false
|
83
|
+
disable_telemetry: false,
|
84
|
+
telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
|
299
85
|
)
|
300
|
-
unless tags.nil?
|
86
|
+
unless tags.nil? || tags.is_a?(Array) || tags.is_a?(Hash)
|
301
87
|
raise ArgumentError, 'tags must be a Array<String> or a Hash'
|
302
88
|
end
|
303
89
|
|
304
|
-
|
305
|
-
@
|
90
|
+
@namespace = namespace
|
91
|
+
@prefix = @namespace ? "#{@namespace}.".freeze : nil
|
306
92
|
|
307
|
-
|
308
|
-
@tags << 'dd.internal.entity_id:' + escape_tag_content(ENV.fetch('DD_ENTITY_ID', nil)) unless ENV.fetch('DD_ENTITY_ID', nil).nil?
|
93
|
+
@serializer = Serialization::Serializer.new(prefix: @prefix, global_tags: tags)
|
309
94
|
|
310
|
-
|
311
|
-
transport_type = socket_path.nil? ? "udp": "uds"
|
312
|
-
telemetry_tags = (["client:ruby", "client_version:#{VERSION}", "client_transport:#{transport_type}"] + @tags).join(COMMA).freeze
|
313
|
-
@telemetry = Telemetry.new(disable_telemetry, telemetry_tags)
|
95
|
+
transport_type = socket_path.nil? ? :udp : :uds
|
314
96
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
end
|
320
|
-
@logger = logger
|
97
|
+
@telemetry = Telemetry.new(disable_telemetry, telemetry_flush_interval,
|
98
|
+
global_tags: tags,
|
99
|
+
transport_type: transport_type
|
100
|
+
)
|
321
101
|
|
322
|
-
@
|
323
|
-
|
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
|
108
|
+
|
109
|
+
@logger = logger
|
324
110
|
|
325
111
|
@sample_rate = sample_rate
|
326
112
|
|
327
113
|
# we reduce max_buffer_bytes by a the rough estimate of the telemetry payload
|
328
|
-
@batch = Batch.new(
|
114
|
+
@batch = Batch.new(connection, (max_buffer_bytes - telemetry.estimate_max_size))
|
329
115
|
end
|
330
116
|
|
331
117
|
# yield a new instance to a block and close it when done
|
332
118
|
# for short-term use-cases that don't want to close the socket manually
|
333
119
|
def self.open(*args)
|
334
120
|
instance = new(*args)
|
121
|
+
|
335
122
|
yield instance
|
336
123
|
ensure
|
337
124
|
instance.close
|
@@ -345,10 +132,10 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
345
132
|
# @option opts [Array<String>] :tags An array of tags
|
346
133
|
# @option opts [Numeric] :by increment value, default 1
|
347
134
|
# @see #count
|
348
|
-
def increment(stat, opts=EMPTY_OPTIONS)
|
349
|
-
opts = {:
|
135
|
+
def increment(stat, opts = EMPTY_OPTIONS)
|
136
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
350
137
|
incr_value = opts.fetch(:by, 1)
|
351
|
-
count
|
138
|
+
count(stat, incr_value, opts)
|
352
139
|
end
|
353
140
|
|
354
141
|
# Sends a decrement (count = -1) for the given stat to the statsd server.
|
@@ -359,10 +146,10 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
359
146
|
# @option opts [Array<String>] :tags An array of tags
|
360
147
|
# @option opts [Numeric] :by decrement value, default 1
|
361
148
|
# @see #count
|
362
|
-
def decrement(stat, opts=EMPTY_OPTIONS)
|
363
|
-
opts = {:
|
149
|
+
def decrement(stat, opts = EMPTY_OPTIONS)
|
150
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
364
151
|
decr_value = - opts.fetch(:by, 1)
|
365
|
-
count
|
152
|
+
count(stat, decr_value, opts)
|
366
153
|
end
|
367
154
|
|
368
155
|
# Sends an arbitrary count for the given stat to the statsd server.
|
@@ -372,9 +159,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
372
159
|
# @param [Hash] opts the options to create the metric with
|
373
160
|
# @option opts [Numeric] :sample_rate sample rate, 1 for always
|
374
161
|
# @option opts [Array<String>] :tags An array of tags
|
375
|
-
def count(stat, count, opts=EMPTY_OPTIONS)
|
376
|
-
opts = {:
|
377
|
-
send_stats
|
162
|
+
def count(stat, count, opts = EMPTY_OPTIONS)
|
163
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
164
|
+
send_stats(stat, count, COUNTER_TYPE, opts)
|
378
165
|
end
|
379
166
|
|
380
167
|
# Sends an arbitary gauge value for the given stat to the statsd server.
|
@@ -390,9 +177,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
390
177
|
# @option opts [Array<String>] :tags An array of tags
|
391
178
|
# @example Report the current user count:
|
392
179
|
# $statsd.gauge('user.count', User.count)
|
393
|
-
def gauge(stat, value, opts=EMPTY_OPTIONS)
|
394
|
-
opts = {:
|
395
|
-
send_stats
|
180
|
+
def gauge(stat, value, opts = EMPTY_OPTIONS)
|
181
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
182
|
+
send_stats(stat, value, GAUGE_TYPE, opts)
|
396
183
|
end
|
397
184
|
|
398
185
|
# Sends a value to be tracked as a histogram to the statsd server.
|
@@ -404,8 +191,8 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
404
191
|
# @option opts [Array<String>] :tags An array of tags
|
405
192
|
# @example Report the current user count:
|
406
193
|
# $statsd.histogram('user.count', User.count)
|
407
|
-
def histogram(stat, value, opts=EMPTY_OPTIONS)
|
408
|
-
send_stats
|
194
|
+
def histogram(stat, value, opts = EMPTY_OPTIONS)
|
195
|
+
send_stats(stat, value, HISTOGRAM_TYPE, opts)
|
409
196
|
end
|
410
197
|
|
411
198
|
# Sends a value to be tracked as a distribution to the statsd server.
|
@@ -417,8 +204,8 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
417
204
|
# @option opts [Array<String>] :tags An array of tags
|
418
205
|
# @example Report the current user count:
|
419
206
|
# $statsd.distribution('user.count', User.count)
|
420
|
-
def distribution(stat, value, opts=EMPTY_OPTIONS)
|
421
|
-
send_stats
|
207
|
+
def distribution(stat, value, opts = EMPTY_OPTIONS)
|
208
|
+
send_stats(stat, value, DISTRIBUTION_TYPE, opts)
|
422
209
|
end
|
423
210
|
|
424
211
|
# Sends a timing (in ms) for the given stat to the statsd server. The
|
@@ -431,9 +218,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
431
218
|
# @param [Hash] opts the options to create the metric with
|
432
219
|
# @option opts [Numeric] :sample_rate sample rate, 1 for always
|
433
220
|
# @option opts [Array<String>] :tags An array of tags
|
434
|
-
def timing(stat, ms, opts=EMPTY_OPTIONS)
|
435
|
-
opts = {:
|
436
|
-
send_stats
|
221
|
+
def timing(stat, ms, opts = EMPTY_OPTIONS)
|
222
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
223
|
+
send_stats(stat, ms, TIMING_TYPE, opts)
|
437
224
|
end
|
438
225
|
|
439
226
|
# Reports execution time of the provided block using {#timing}.
|
@@ -449,13 +236,12 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
449
236
|
# @see #timing
|
450
237
|
# @example Report the time (in ms) taken to activate an account
|
451
238
|
# $statsd.time('account.activate') { @account.activate! }
|
452
|
-
def time(stat, opts=EMPTY_OPTIONS)
|
453
|
-
opts = {:
|
454
|
-
start =
|
455
|
-
|
239
|
+
def time(stat, opts = EMPTY_OPTIONS)
|
240
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
241
|
+
start = now
|
242
|
+
yield
|
456
243
|
ensure
|
457
|
-
|
458
|
-
timing(stat, ((finished - start) * 1000).round, opts)
|
244
|
+
timing(stat, ((now - start) * 1000).round, opts)
|
459
245
|
end
|
460
246
|
|
461
247
|
# Sends a value to be tracked as a set to the statsd server.
|
@@ -467,9 +253,9 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
467
253
|
# @option opts [Array<String>] :tags An array of tags
|
468
254
|
# @example Record a unique visitory by id:
|
469
255
|
# $statsd.set('visitors.uniques', User.id)
|
470
|
-
def set(stat, value, opts=EMPTY_OPTIONS)
|
471
|
-
opts = {:
|
472
|
-
send_stats
|
256
|
+
def set(stat, value, opts = EMPTY_OPTIONS)
|
257
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
258
|
+
send_stats(stat, value, SET_TYPE, opts)
|
473
259
|
end
|
474
260
|
|
475
261
|
# This method allows you to send custom service check statuses.
|
@@ -483,9 +269,10 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
483
269
|
# @option opts [String, nil] :message (nil) A message to associate with this service check status
|
484
270
|
# @example Report a critical service check status
|
485
271
|
# $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
|
486
|
-
def service_check(name, status, opts=EMPTY_OPTIONS)
|
487
|
-
|
488
|
-
|
272
|
+
def service_check(name, status, opts = EMPTY_OPTIONS)
|
273
|
+
telemetry.sent(service_checks: 1)
|
274
|
+
|
275
|
+
send_stat(serializer.to_service_check(name, status, opts))
|
489
276
|
end
|
490
277
|
|
491
278
|
# This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
|
@@ -503,12 +290,14 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
503
290
|
# @option opts [String, nil] :priority ('normal') Can be "normal" or "low"
|
504
291
|
# @option opts [String, nil] :source_type_name (nil) Assign a source type to the event
|
505
292
|
# @option opts [String, nil] :alert_type ('info') Can be "error", "warning", "info" or "success".
|
293
|
+
# @option opts [Boolean, false] :truncate_if_too_long (false) Truncate the event if it is too long
|
506
294
|
# @option opts [Array<String>] :tags tags to be added to every metric
|
507
295
|
# @example Report an awful event:
|
508
296
|
# $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)
|
510
|
-
|
511
|
-
|
297
|
+
def event(title, text, opts = EMPTY_OPTIONS)
|
298
|
+
telemetry.sent(events: 1)
|
299
|
+
|
300
|
+
send_stat(serializer.to_event(title, text, opts))
|
512
301
|
end
|
513
302
|
|
514
303
|
# Send several metrics in the same UDP Packet
|
@@ -520,153 +309,51 @@ datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{
|
|
520
309
|
# s.increment('page.views')
|
521
310
|
# end
|
522
311
|
def batch
|
523
|
-
@batch.open
|
312
|
+
@batch.open do
|
313
|
+
yield self
|
314
|
+
end
|
524
315
|
end
|
525
316
|
|
526
317
|
# Close the underlying socket
|
527
318
|
def close
|
528
|
-
|
319
|
+
connection.close
|
529
320
|
end
|
530
321
|
|
531
322
|
private
|
323
|
+
attr_reader :serializer
|
324
|
+
attr_reader :telemetry
|
532
325
|
|
533
|
-
|
534
|
-
ESC_NEW_LINE = "\\n".freeze
|
535
|
-
COMMA = ",".freeze
|
536
|
-
PIPE = "|".freeze
|
537
|
-
DOT = ".".freeze
|
538
|
-
DOUBLE_COLON = "::".freeze
|
539
|
-
UNDERSCORE = "_".freeze
|
540
|
-
PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= "2.1.0")
|
326
|
+
PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= '2.1.0')
|
541
327
|
EMPTY_OPTIONS = {}.freeze
|
542
328
|
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
def format_service_check(name, status, opts=EMPTY_OPTIONS)
|
547
|
-
sc_string = "_sc|#{name}|#{status}".dup
|
548
|
-
|
549
|
-
SC_OPT_KEYS.each do |key, shorthand_key|
|
550
|
-
next unless opts[key]
|
551
|
-
|
552
|
-
if key == :tags
|
553
|
-
if tags_string = tags_as_string(opts)
|
554
|
-
sc_string << "|##{tags_string}"
|
555
|
-
end
|
556
|
-
elsif key == :message
|
557
|
-
message = remove_pipes(opts[:message])
|
558
|
-
escaped_message = escape_service_check_message(message)
|
559
|
-
sc_string << "|m:#{escaped_message}"
|
560
|
-
else
|
561
|
-
if key == :timestamp && opts[key].is_a?(Integer)
|
562
|
-
value = opts[key]
|
563
|
-
else
|
564
|
-
value = remove_pipes(opts[key])
|
565
|
-
end
|
566
|
-
sc_string << "|#{shorthand_key}#{value}"
|
567
|
-
end
|
568
|
-
end
|
569
|
-
sc_string
|
570
|
-
end
|
571
|
-
|
572
|
-
def format_event(title, text, opts=EMPTY_OPTIONS)
|
573
|
-
escaped_title = escape_event_content(title)
|
574
|
-
escaped_text = escape_event_content(text)
|
575
|
-
event_string_data = "_e{#{escaped_title.bytesize},#{escaped_text.bytesize}}:#{escaped_title}|#{escaped_text}".dup
|
576
|
-
|
577
|
-
# We construct the string to be sent by adding '|key:value' parts to it when needed
|
578
|
-
# All pipes ('|') in the metadata are removed. Title and Text can keep theirs
|
579
|
-
OPTS_KEYS.each do |key, shorthand_key|
|
580
|
-
if key != :tags && opts[key]
|
581
|
-
# :date_happened is the only key where the value is an Integer
|
582
|
-
# To not break backwards compatibility, we still accept a String
|
583
|
-
if key == :date_happened && opts[key].is_a?(Integer)
|
584
|
-
value = opts[key]
|
585
|
-
# All other keys only have String values
|
586
|
-
else
|
587
|
-
value = remove_pipes(opts[key])
|
588
|
-
end
|
589
|
-
event_string_data << "|#{shorthand_key}:#{value}"
|
590
|
-
end
|
329
|
+
if PROCESS_TIME_SUPPORTED
|
330
|
+
def now
|
331
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
591
332
|
end
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
event_string_data << "|##{tags_string}"
|
596
|
-
end
|
597
|
-
|
598
|
-
raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string_data.bytesize > MAX_EVENT_SIZE
|
599
|
-
event_string_data
|
600
|
-
end
|
601
|
-
|
602
|
-
def tags_as_string(opts)
|
603
|
-
if tag_arr = opts[:tags]
|
604
|
-
tag_arr = tag_hash_to_array(tag_arr) if tag_arr.is_a? Hash
|
605
|
-
tag_arr = tag_arr.map { |tag| escape_tag_content(tag) }
|
606
|
-
tag_arr = tags + tag_arr # @tags are normalized when set, so not need to normalize them again
|
607
|
-
else
|
608
|
-
tag_arr = tags
|
333
|
+
else
|
334
|
+
def now
|
335
|
+
Time.now.to_f
|
609
336
|
end
|
610
|
-
tag_arr.join(COMMA) unless tag_arr.empty?
|
611
337
|
end
|
612
338
|
|
613
|
-
def
|
614
|
-
|
615
|
-
end
|
616
|
-
|
617
|
-
def escape_event_content(msg)
|
618
|
-
msg.gsub NEW_LINE, ESC_NEW_LINE
|
619
|
-
end
|
339
|
+
def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
|
340
|
+
telemetry.sent(metrics: 1)
|
620
341
|
|
621
|
-
|
622
|
-
tag = remove_pipes(tag.to_s)
|
623
|
-
tag.delete! COMMA
|
624
|
-
tag
|
625
|
-
end
|
626
|
-
|
627
|
-
def remove_pipes(msg)
|
628
|
-
msg.delete PIPE
|
629
|
-
end
|
342
|
+
sample_rate = opts[:sample_rate] || @sample_rate || 1
|
630
343
|
|
631
|
-
|
632
|
-
|
633
|
-
end
|
344
|
+
if sample_rate == 1 || rand <= sample_rate
|
345
|
+
full_stat = serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
|
634
346
|
|
635
|
-
def send_stats(stat, delta, type, opts=EMPTY_OPTIONS)
|
636
|
-
@telemetry.metrics += 1
|
637
|
-
sample_rate = opts[:sample_rate] || @sample_rate || 1
|
638
|
-
if sample_rate == 1 or rand <= sample_rate
|
639
|
-
full_stat = ''.dup
|
640
|
-
full_stat << @prefix if @prefix
|
641
|
-
|
642
|
-
stat = stat.is_a?(String) ? stat.dup : stat.to_s
|
643
|
-
# Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
|
644
|
-
stat.gsub!(DOUBLE_COLON, DOT)
|
645
|
-
stat.tr!(':|@'.freeze, UNDERSCORE)
|
646
|
-
full_stat << stat
|
647
|
-
|
648
|
-
full_stat << ':'.freeze
|
649
|
-
full_stat << delta.to_s
|
650
|
-
full_stat << PIPE
|
651
|
-
full_stat << type
|
652
|
-
|
653
|
-
unless sample_rate == 1
|
654
|
-
full_stat << PIPE
|
655
|
-
full_stat << '@'.freeze
|
656
|
-
full_stat << sample_rate.to_s
|
657
|
-
end
|
658
|
-
|
659
|
-
if tags_string = tags_as_string(opts)
|
660
|
-
full_stat << PIPE
|
661
|
-
full_stat << '#'.freeze
|
662
|
-
full_stat << tags_string
|
663
|
-
end
|
664
347
|
send_stat(full_stat)
|
665
348
|
end
|
666
349
|
end
|
667
350
|
|
668
351
|
def send_stat(message)
|
669
|
-
@batch.open?
|
352
|
+
if @batch.open?
|
353
|
+
@batch.add(message)
|
354
|
+
else
|
355
|
+
@connection.write(message)
|
356
|
+
end
|
670
357
|
end
|
671
358
|
end
|
672
359
|
end
|