dogstatsd-ruby 3.3.0 → 5.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +174 -53
- data/lib/datadog/statsd/connection.rb +60 -0
- data/lib/datadog/statsd/forwarder.rb +120 -0
- data/lib/datadog/statsd/message_buffer.rb +97 -0
- data/lib/datadog/statsd/sender.rb +161 -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 +96 -0
- data/lib/datadog/statsd/serialization.rb +15 -0
- data/lib/datadog/statsd/single_thread_sender.rb +62 -0
- data/lib/datadog/statsd/telemetry.rb +96 -0
- data/lib/datadog/statsd/udp_connection.rb +49 -0
- data/lib/datadog/statsd/uds_connection.rb +49 -0
- data/lib/datadog/statsd/version.rb +9 -0
- data/lib/datadog/statsd.rb +198 -286
- metadata +32 -9
data/lib/datadog/statsd.rb
CHANGED
@@ -1,5 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'socket'
|
2
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/message_buffer'
|
9
|
+
require_relative 'statsd/serialization'
|
10
|
+
require_relative 'statsd/sender'
|
11
|
+
require_relative 'statsd/single_thread_sender'
|
12
|
+
require_relative 'statsd/forwarder'
|
13
|
+
|
14
|
+
$deprecation_message_mutex = Mutex.new
|
15
|
+
$deprecation_message_done = false
|
16
|
+
|
3
17
|
# = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
|
4
18
|
#
|
5
19
|
# @example Set up a global Statsd client for a server on localhost:8125
|
@@ -15,109 +29,120 @@ require 'socket'
|
|
15
29
|
# statsd = Datadog::Statsd.new 'localhost', 8125, :namespace => 'account'
|
16
30
|
# statsd.increment 'activate'
|
17
31
|
# @example Create a statsd client with global tags
|
18
|
-
# statsd = Datadog::Statsd.new 'localhost', 8125, :
|
32
|
+
# statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
|
19
33
|
module Datadog
|
20
34
|
class Statsd
|
35
|
+
class Error < StandardError
|
36
|
+
end
|
21
37
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# Goal: Simple and fast to add some other parameters
|
27
|
-
OPTS_KEYS = {
|
28
|
-
:date_happened => :d,
|
29
|
-
:hostname => :h,
|
30
|
-
:aggregation_key => :k,
|
31
|
-
:priority => :p,
|
32
|
-
:source_type_name => :s,
|
33
|
-
:alert_type => :t,
|
34
|
-
}
|
35
|
-
|
36
|
-
# Service check options
|
37
|
-
SC_OPT_KEYS = {
|
38
|
-
:timestamp => 'd:'.freeze,
|
39
|
-
:hostname => 'h:'.freeze,
|
40
|
-
:tags => '#'.freeze,
|
41
|
-
:message => 'm:'.freeze,
|
42
|
-
}
|
43
|
-
|
44
|
-
OK = 0
|
45
|
-
WARNING = 1
|
46
|
-
CRITICAL = 2
|
47
|
-
UNKNOWN = 3
|
48
|
-
|
49
|
-
COUNTER_TYPE = 'c'.freeze
|
50
|
-
GAUGE_TYPE = 'g'.freeze
|
51
|
-
HISTOGRAM_TYPE = 'h'.freeze
|
52
|
-
DISTRIBUTION_TYPE = 'd'.freeze
|
53
|
-
TIMING_TYPE = 'ms'.freeze
|
54
|
-
SET_TYPE = 's'.freeze
|
55
|
-
VERSION = "3.3.0".freeze
|
56
|
-
|
57
|
-
# A namespace to prepend to all statsd calls. Defaults to no namespace.
|
58
|
-
attr_reader :namespace
|
38
|
+
OK = 0
|
39
|
+
WARNING = 1
|
40
|
+
CRITICAL = 2
|
41
|
+
UNKNOWN = 3
|
59
42
|
|
60
|
-
|
61
|
-
|
43
|
+
UDP_DEFAULT_BUFFER_SIZE = 1_432
|
44
|
+
UDS_DEFAULT_BUFFER_SIZE = 8_192
|
45
|
+
DEFAULT_BUFFER_POOL_SIZE = Float::INFINITY
|
46
|
+
MAX_EVENT_SIZE = 8 * 1_024
|
47
|
+
# minimum flush interval for the telemetry in seconds
|
48
|
+
DEFAULT_TELEMETRY_FLUSH_INTERVAL = 10
|
62
49
|
|
63
|
-
|
64
|
-
|
50
|
+
COUNTER_TYPE = 'c'
|
51
|
+
GAUGE_TYPE = 'g'
|
52
|
+
HISTOGRAM_TYPE = 'h'
|
53
|
+
DISTRIBUTION_TYPE = 'd'
|
54
|
+
TIMING_TYPE = 'ms'
|
55
|
+
SET_TYPE = 's'
|
65
56
|
|
66
|
-
#
|
67
|
-
attr_reader :
|
57
|
+
# A namespace to prepend to all statsd calls. Defaults to no namespace.
|
58
|
+
attr_reader :namespace
|
68
59
|
|
69
60
|
# Global tags to be added to every statsd call. Defaults to no tags.
|
70
|
-
|
71
|
-
|
72
|
-
# Buffer containing the statsd message before they are sent in batch
|
73
|
-
attr_reader :buffer
|
74
|
-
|
75
|
-
# Maximum number of metrics in the buffer before it is flushed
|
76
|
-
attr_accessor :max_buffer_size
|
77
|
-
|
78
|
-
class << self
|
79
|
-
# Set to a standard logger instance to enable debug logging.
|
80
|
-
attr_accessor :logger
|
61
|
+
def tags
|
62
|
+
serializer.global_tags
|
81
63
|
end
|
82
64
|
|
83
|
-
#
|
84
|
-
|
85
|
-
def self.VERSION
|
86
|
-
VERSION
|
87
|
-
end
|
65
|
+
# Default sample rate
|
66
|
+
attr_reader :sample_rate
|
88
67
|
|
89
68
|
# @param [String] host your statsd host
|
90
69
|
# @param [Integer] port your statsd port
|
91
|
-
# @option
|
92
|
-
# @option
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
70
|
+
# @option [String] namespace set a namespace to be prepended to every metric name
|
71
|
+
# @option [Array<String>|Hash] tags tags to be added to every metric
|
72
|
+
# @option [Logger] logger for debugging
|
73
|
+
# @option [Integer] buffer_max_payload_size max bytes to buffer
|
74
|
+
# @option [Integer] buffer_max_pool_size max messages to buffer
|
75
|
+
# @option [String] socket_path unix socket path
|
76
|
+
# @option [Float] default sample rate if not overridden
|
77
|
+
# @option [Boolean] single_thread flushes the metrics on the main thread instead of in a companion thread
|
78
|
+
def initialize(
|
79
|
+
host = nil,
|
80
|
+
port = nil,
|
81
|
+
socket_path: nil,
|
82
|
+
|
83
|
+
namespace: nil,
|
84
|
+
tags: nil,
|
85
|
+
sample_rate: nil,
|
86
|
+
|
87
|
+
buffer_max_payload_size: nil,
|
88
|
+
buffer_max_pool_size: nil,
|
89
|
+
buffer_overflowing_stategy: :drop,
|
90
|
+
|
91
|
+
logger: nil,
|
92
|
+
|
93
|
+
single_thread: false,
|
94
|
+
|
95
|
+
telemetry_enable: true,
|
96
|
+
telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
|
97
|
+
)
|
98
|
+
unless tags.nil? || tags.is_a?(Array) || tags.is_a?(Hash)
|
99
|
+
raise ArgumentError, 'tags must be an array of string tags or a Hash'
|
100
|
+
end
|
104
101
|
|
105
|
-
def namespace=(namespace) #:nodoc:
|
106
102
|
@namespace = namespace
|
107
|
-
@prefix = namespace
|
108
|
-
|
103
|
+
@prefix = @namespace ? "#{@namespace}.".freeze : nil
|
104
|
+
@serializer = Serialization::Serializer.new(prefix: @prefix, global_tags: tags)
|
105
|
+
@sample_rate = sample_rate
|
106
|
+
|
107
|
+
# deprecation message for ruby < 2.1.0 users as we will drop support for ruby 2.0
|
108
|
+
# in dogstatsd-ruby 5.4.0
|
109
|
+
# TODO(remy): remove this message and the two global vars used in dogstatd-ruby 5.4.0
|
110
|
+
if RUBY_VERSION < '2.1.0' && $deprecation_message_mutex.try_lock && !$deprecation_message_done
|
111
|
+
if logger != nil
|
112
|
+
logger.warn { "deprecation: dogstatsd-ruby will drop support of Ruby < 2.1.0 in a next minor release" }
|
113
|
+
else
|
114
|
+
puts("warning: deprecation: dogstatsd-ruby will drop support of Ruby < 2.1.0 in a next minor release")
|
115
|
+
end
|
116
|
+
$deprecation_message_done = true
|
117
|
+
$deprecation_message_mutex.unlock
|
118
|
+
end
|
109
119
|
|
110
|
-
|
111
|
-
|
112
|
-
|
120
|
+
@forwarder = Forwarder.new(
|
121
|
+
host: host,
|
122
|
+
port: port,
|
123
|
+
socket_path: socket_path,
|
124
|
+
|
125
|
+
global_tags: tags,
|
126
|
+
logger: logger,
|
127
|
+
|
128
|
+
single_thread: single_thread,
|
113
129
|
|
114
|
-
|
115
|
-
|
130
|
+
buffer_max_payload_size: buffer_max_payload_size,
|
131
|
+
buffer_max_pool_size: buffer_max_pool_size,
|
132
|
+
buffer_overflowing_stategy: buffer_overflowing_stategy,
|
133
|
+
|
134
|
+
telemetry_flush_interval: telemetry_enable ? telemetry_flush_interval : nil,
|
135
|
+
)
|
116
136
|
end
|
117
137
|
|
118
|
-
|
119
|
-
|
120
|
-
|
138
|
+
# yield a new instance to a block and close it when done
|
139
|
+
# for short-term use-cases that don't want to close the socket manually
|
140
|
+
def self.open(*args)
|
141
|
+
instance = new(*args)
|
142
|
+
|
143
|
+
yield instance
|
144
|
+
ensure
|
145
|
+
instance.close
|
121
146
|
end
|
122
147
|
|
123
148
|
# Sends an increment (count = 1) for the given stat to the statsd server.
|
@@ -128,10 +153,10 @@ module Datadog
|
|
128
153
|
# @option opts [Array<String>] :tags An array of tags
|
129
154
|
# @option opts [Numeric] :by increment value, default 1
|
130
155
|
# @see #count
|
131
|
-
def increment(stat, opts=
|
132
|
-
opts = {:
|
156
|
+
def increment(stat, opts = EMPTY_OPTIONS)
|
157
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
133
158
|
incr_value = opts.fetch(:by, 1)
|
134
|
-
count
|
159
|
+
count(stat, incr_value, opts)
|
135
160
|
end
|
136
161
|
|
137
162
|
# Sends a decrement (count = -1) for the given stat to the statsd server.
|
@@ -142,10 +167,10 @@ module Datadog
|
|
142
167
|
# @option opts [Array<String>] :tags An array of tags
|
143
168
|
# @option opts [Numeric] :by decrement value, default 1
|
144
169
|
# @see #count
|
145
|
-
def decrement(stat, opts=
|
146
|
-
opts = {:
|
170
|
+
def decrement(stat, opts = EMPTY_OPTIONS)
|
171
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
147
172
|
decr_value = - opts.fetch(:by, 1)
|
148
|
-
count
|
173
|
+
count(stat, decr_value, opts)
|
149
174
|
end
|
150
175
|
|
151
176
|
# Sends an arbitrary count for the given stat to the statsd server.
|
@@ -155,9 +180,9 @@ module Datadog
|
|
155
180
|
# @param [Hash] opts the options to create the metric with
|
156
181
|
# @option opts [Numeric] :sample_rate sample rate, 1 for always
|
157
182
|
# @option opts [Array<String>] :tags An array of tags
|
158
|
-
def count(stat, count, opts=
|
159
|
-
opts = {:
|
160
|
-
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)
|
161
186
|
end
|
162
187
|
|
163
188
|
# Sends an arbitary gauge value for the given stat to the statsd server.
|
@@ -173,9 +198,9 @@ module Datadog
|
|
173
198
|
# @option opts [Array<String>] :tags An array of tags
|
174
199
|
# @example Report the current user count:
|
175
200
|
# $statsd.gauge('user.count', User.count)
|
176
|
-
def gauge(stat, value, opts=
|
177
|
-
opts = {:
|
178
|
-
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)
|
179
204
|
end
|
180
205
|
|
181
206
|
# Sends a value to be tracked as a histogram to the statsd server.
|
@@ -187,14 +212,11 @@ module Datadog
|
|
187
212
|
# @option opts [Array<String>] :tags An array of tags
|
188
213
|
# @example Report the current user count:
|
189
214
|
# $statsd.histogram('user.count', User.count)
|
190
|
-
def histogram(stat, value, opts=
|
191
|
-
send_stats
|
215
|
+
def histogram(stat, value, opts = EMPTY_OPTIONS)
|
216
|
+
send_stats(stat, value, HISTOGRAM_TYPE, opts)
|
192
217
|
end
|
193
218
|
|
194
219
|
# Sends a value to be tracked as a distribution to the statsd server.
|
195
|
-
# Note: Distributions are a beta feature of Datadog and not generally
|
196
|
-
# available. Distributions must be specifically enabled for your
|
197
|
-
# organization.
|
198
220
|
#
|
199
221
|
# @param [String] stat stat name.
|
200
222
|
# @param [Numeric] value distribution value.
|
@@ -203,8 +225,8 @@ module Datadog
|
|
203
225
|
# @option opts [Array<String>] :tags An array of tags
|
204
226
|
# @example Report the current user count:
|
205
227
|
# $statsd.distribution('user.count', User.count)
|
206
|
-
def distribution(stat, value, opts=
|
207
|
-
send_stats
|
228
|
+
def distribution(stat, value, opts = EMPTY_OPTIONS)
|
229
|
+
send_stats(stat, value, DISTRIBUTION_TYPE, opts)
|
208
230
|
end
|
209
231
|
|
210
232
|
# Sends a timing (in ms) for the given stat to the statsd server. The
|
@@ -217,9 +239,9 @@ module Datadog
|
|
217
239
|
# @param [Hash] opts the options to create the metric with
|
218
240
|
# @option opts [Numeric] :sample_rate sample rate, 1 for always
|
219
241
|
# @option opts [Array<String>] :tags An array of tags
|
220
|
-
def timing(stat, ms, opts=
|
221
|
-
opts = {:
|
222
|
-
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)
|
223
245
|
end
|
224
246
|
|
225
247
|
# Reports execution time of the provided block using {#timing}.
|
@@ -235,14 +257,14 @@ module Datadog
|
|
235
257
|
# @see #timing
|
236
258
|
# @example Report the time (in ms) taken to activate an account
|
237
259
|
# $statsd.time('account.activate') { @account.activate! }
|
238
|
-
def time(stat, opts=
|
239
|
-
opts = {:
|
240
|
-
start =
|
241
|
-
|
260
|
+
def time(stat, opts = EMPTY_OPTIONS)
|
261
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
262
|
+
start = now
|
263
|
+
yield
|
242
264
|
ensure
|
243
|
-
|
244
|
-
timing(stat, ((finished - start) * 1000).round, opts)
|
265
|
+
timing(stat, ((now - start) * 1000).round, opts)
|
245
266
|
end
|
267
|
+
|
246
268
|
# Sends a value to be tracked as a set to the statsd server.
|
247
269
|
#
|
248
270
|
# @param [String] stat stat name.
|
@@ -252,9 +274,9 @@ module Datadog
|
|
252
274
|
# @option opts [Array<String>] :tags An array of tags
|
253
275
|
# @example Record a unique visitory by id:
|
254
276
|
# $statsd.set('visitors.uniques', User.id)
|
255
|
-
def set(stat, value, opts=
|
256
|
-
opts = {:
|
257
|
-
send_stats
|
277
|
+
def set(stat, value, opts = EMPTY_OPTIONS)
|
278
|
+
opts = { sample_rate: opts } if opts.is_a?(Numeric)
|
279
|
+
send_stats(stat, value, SET_TYPE, opts)
|
258
280
|
end
|
259
281
|
|
260
282
|
# This method allows you to send custom service check statuses.
|
@@ -262,37 +284,16 @@ module Datadog
|
|
262
284
|
# @param [String] name Service check name
|
263
285
|
# @param [String] status Service check status.
|
264
286
|
# @param [Hash] opts the additional data about the service check
|
265
|
-
# @option opts [Integer, nil] :timestamp (nil) Assign a timestamp to the
|
266
|
-
# @option opts [String, nil] :hostname (nil) Assign a hostname to the
|
287
|
+
# @option opts [Integer, String, nil] :timestamp (nil) Assign a timestamp to the service check. Default is now when none
|
288
|
+
# @option opts [String, nil] :hostname (nil) Assign a hostname to the service check.
|
267
289
|
# @option opts [Array<String>, nil] :tags (nil) An array of tags
|
268
290
|
# @option opts [String, nil] :message (nil) A message to associate with this service check status
|
269
291
|
# @example Report a critical service check status
|
270
292
|
# $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
|
271
|
-
def service_check(name, status, opts=
|
272
|
-
|
273
|
-
send_to_socket service_check_string
|
274
|
-
end
|
275
|
-
|
276
|
-
def format_service_check(name, status, opts={})
|
277
|
-
sc_string = "_sc|#{name}|#{status}"
|
278
|
-
|
279
|
-
SC_OPT_KEYS.each do |key, shorthand_key|
|
280
|
-
next unless opts[key]
|
293
|
+
def service_check(name, status, opts = EMPTY_OPTIONS)
|
294
|
+
telemetry.sent(service_checks: 1) if telemetry
|
281
295
|
|
282
|
-
|
283
|
-
if tags_string = tags_as_string(opts)
|
284
|
-
sc_string << "|##{tags_string}"
|
285
|
-
end
|
286
|
-
elsif key == :message
|
287
|
-
message = remove_pipes(opts[:message])
|
288
|
-
escaped_message = escape_service_check_message(message)
|
289
|
-
sc_string << "|m:#{escaped_message}"
|
290
|
-
else
|
291
|
-
value = remove_pipes(opts[key])
|
292
|
-
sc_string << "|#{shorthand_key}#{value}"
|
293
|
-
end
|
294
|
-
end
|
295
|
-
return sc_string
|
296
|
+
forwarder.send_message(serializer.to_service_check(name, status, opts))
|
296
297
|
end
|
297
298
|
|
298
299
|
# This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
|
@@ -302,197 +303,108 @@ module Datadog
|
|
302
303
|
# it will be grouped with other events that don't have an event type.
|
303
304
|
#
|
304
305
|
# @param [String] title Event title
|
305
|
-
# @param [String] text Event text. Supports
|
306
|
+
# @param [String] text Event text. Supports newlines (+\n+)
|
306
307
|
# @param [Hash] opts the additional data about the event
|
307
|
-
# @option opts [Integer, nil] :date_happened (nil) Assign a timestamp to the event. Default is now when none
|
308
|
+
# @option opts [Integer, String, nil] :date_happened (nil) Assign a timestamp to the event. Default is now when none
|
308
309
|
# @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
|
309
310
|
# @option opts [String, nil] :aggregation_key (nil) Assign an aggregation key to the event, to group it with some others
|
310
311
|
# @option opts [String, nil] :priority ('normal') Can be "normal" or "low"
|
311
312
|
# @option opts [String, nil] :source_type_name (nil) Assign a source type to the event
|
312
313
|
# @option opts [String, nil] :alert_type ('info') Can be "error", "warning", "info" or "success".
|
314
|
+
# @option opts [Boolean, false] :truncate_if_too_long (false) Truncate the event if it is too long
|
313
315
|
# @option opts [Array<String>] :tags tags to be added to every metric
|
314
316
|
# @example Report an awful event:
|
315
317
|
# $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
|
316
|
-
def event(title, text, opts=
|
317
|
-
|
318
|
-
raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string.length > 8 * 1024
|
318
|
+
def event(title, text, opts = EMPTY_OPTIONS)
|
319
|
+
telemetry.sent(events: 1) if telemetry
|
319
320
|
|
320
|
-
|
321
|
+
forwarder.send_message(serializer.to_event(title, text, opts))
|
321
322
|
end
|
322
323
|
|
323
|
-
# Send several metrics in the same
|
324
|
-
# They will be buffered and flushed when the block finishes
|
324
|
+
# Send several metrics in the same packet.
|
325
|
+
# They will be buffered and flushed when the block finishes.
|
326
|
+
#
|
327
|
+
# This method exists for compatibility with v4.x versions, it is not needed
|
328
|
+
# anymore since the batching is now automatically done internally.
|
329
|
+
# It also means that an automatic flush could occur if the buffer is filled
|
330
|
+
# during the execution of the batch block.
|
331
|
+
#
|
332
|
+
# This method is DEPRECATED and will be removed in future v6.x API.
|
325
333
|
#
|
326
334
|
# @example Send several metrics in one packet:
|
327
335
|
# $statsd.batch do |s|
|
328
336
|
# s.gauge('users.online',156)
|
329
337
|
# s.increment('page.views')
|
330
338
|
# end
|
331
|
-
def batch
|
332
|
-
@batch_nesting_depth += 1
|
339
|
+
def batch
|
333
340
|
yield self
|
334
|
-
|
335
|
-
@batch_nesting_depth -= 1
|
336
|
-
flush_buffer if @batch_nesting_depth == 0
|
341
|
+
flush(sync: true)
|
337
342
|
end
|
338
343
|
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
# All pipes ('|') in the metadata are removed. Title and Text can keep theirs
|
346
|
-
OPTS_KEYS.each do |key, shorthand_key|
|
347
|
-
if key != :tags && opts[key]
|
348
|
-
value = remove_pipes(opts[key])
|
349
|
-
event_string_data << "|#{shorthand_key}:#{value}"
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
# Tags are joined and added as last part to the string to be sent
|
354
|
-
if tags_string = tags_as_string(opts)
|
355
|
-
event_string_data << "|##{tags_string}"
|
356
|
-
end
|
357
|
-
|
358
|
-
raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string_data.length > 8192 # 8 * 1024 = 8192
|
359
|
-
return event_string_data
|
344
|
+
# Close the underlying socket
|
345
|
+
#
|
346
|
+
# @param [Boolean, true] flush Should we flush the metrics before closing
|
347
|
+
def close(flush: true)
|
348
|
+
flush(sync: true) if flush
|
349
|
+
forwarder.close
|
360
350
|
end
|
361
351
|
|
362
|
-
|
363
|
-
|
364
|
-
@socket.close
|
352
|
+
def sync_with_outbound_io
|
353
|
+
forwarder.sync_with_outbound_io
|
365
354
|
end
|
366
355
|
|
367
|
-
|
356
|
+
# Flush the buffer into the connection
|
357
|
+
def flush(flush_telemetry: false, sync: false)
|
358
|
+
forwarder.flush(flush_telemetry: flush_telemetry, sync: sync)
|
359
|
+
end
|
368
360
|
|
369
|
-
|
370
|
-
|
371
|
-
COMMA = ",".freeze
|
372
|
-
PIPE = "|".freeze
|
373
|
-
DOT = ".".freeze
|
374
|
-
DOUBLE_COLON = "::".freeze
|
375
|
-
UNDERSCORE = "_".freeze
|
376
|
-
PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= "2.1.0")
|
377
|
-
|
378
|
-
private_constant :NEW_LINE, :ESC_NEW_LINE, :COMMA, :PIPE, :DOT,
|
379
|
-
:DOUBLE_COLON, :UNDERSCORE
|
380
|
-
|
381
|
-
def tags_as_string(opts)
|
382
|
-
tag_arr = opts[:tags] || []
|
383
|
-
tag_arr = tag_arr.map { |tag| escape_tag_content(tag) }
|
384
|
-
tag_arr = tags + tag_arr # @tags are normalized when set, so not need to normalize them again
|
385
|
-
tag_arr.join(COMMA) unless tag_arr.empty?
|
361
|
+
def telemetry
|
362
|
+
forwarder.telemetry
|
386
363
|
end
|
387
364
|
|
388
|
-
def
|
389
|
-
|
365
|
+
def host
|
366
|
+
forwarder.host
|
390
367
|
end
|
391
368
|
|
392
|
-
def
|
393
|
-
|
394
|
-
tag.delete! COMMA
|
395
|
-
tag
|
369
|
+
def port
|
370
|
+
forwarder.port
|
396
371
|
end
|
397
372
|
|
398
|
-
def
|
399
|
-
|
373
|
+
def socket_path
|
374
|
+
forwarder.socket_path
|
400
375
|
end
|
401
376
|
|
402
|
-
def
|
403
|
-
|
377
|
+
def transport_type
|
378
|
+
forwarder.transport_type
|
404
379
|
end
|
405
380
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
full_stat = ''
|
410
|
-
full_stat << @prefix if @prefix
|
411
|
-
|
412
|
-
stat = stat.is_a?(String) ? stat.dup : stat.to_s
|
413
|
-
# Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
|
414
|
-
stat.gsub!(DOUBLE_COLON, DOT)
|
415
|
-
stat.tr!(':|@'.freeze, UNDERSCORE)
|
416
|
-
full_stat << stat
|
417
|
-
|
418
|
-
full_stat << ':'.freeze
|
419
|
-
full_stat << delta.to_s
|
420
|
-
full_stat << PIPE
|
421
|
-
full_stat << type
|
422
|
-
|
423
|
-
unless sample_rate == 1
|
424
|
-
full_stat << PIPE
|
425
|
-
full_stat << '@'.freeze
|
426
|
-
full_stat << sample_rate.to_s
|
427
|
-
end
|
381
|
+
private
|
382
|
+
attr_reader :serializer
|
383
|
+
attr_reader :forwarder
|
428
384
|
|
429
|
-
|
430
|
-
|
431
|
-
full_stat << '#'.freeze
|
432
|
-
full_stat << tags_string
|
433
|
-
end
|
385
|
+
PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= '2.1.0')
|
386
|
+
EMPTY_OPTIONS = {}.freeze
|
434
387
|
|
435
|
-
|
388
|
+
if PROCESS_TIME_SUPPORTED
|
389
|
+
def now
|
390
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
436
391
|
end
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
if @batch_nesting_depth > 0
|
441
|
-
@buffer << message
|
442
|
-
flush_buffer if @buffer.length >= @max_buffer_size
|
443
|
-
else
|
444
|
-
send_to_socket(message)
|
392
|
+
else
|
393
|
+
def now
|
394
|
+
Time.now.to_f
|
445
395
|
end
|
446
396
|
end
|
447
397
|
|
448
|
-
def
|
449
|
-
|
450
|
-
send_to_socket(@buffer.join(NEW_LINE))
|
451
|
-
@buffer = Array.new
|
452
|
-
end
|
398
|
+
def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
|
399
|
+
telemetry.sent(metrics: 1) if telemetry
|
453
400
|
|
454
|
-
|
455
|
-
if !@socket_path.nil?
|
456
|
-
socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
|
457
|
-
socket.connect(Socket.pack_sockaddr_un(@socket_path))
|
458
|
-
else
|
459
|
-
socket = UDPSocket.new
|
460
|
-
socket.connect(@host, @port)
|
461
|
-
end
|
462
|
-
socket
|
463
|
-
end
|
401
|
+
sample_rate = opts[:sample_rate] || @sample_rate || 1
|
464
402
|
|
465
|
-
|
466
|
-
|
467
|
-
end
|
403
|
+
if sample_rate == 1 || rand <= sample_rate
|
404
|
+
full_stat = serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
|
468
405
|
|
469
|
-
|
470
|
-
self.class.logger.debug { "Statsd: #{message}" } if self.class.logger
|
471
|
-
if @socket_path.nil?
|
472
|
-
sock.send(message, 0)
|
473
|
-
else
|
474
|
-
sock.sendmsg_nonblock(message)
|
475
|
-
end
|
476
|
-
rescue => boom
|
477
|
-
if @socket_path && (boom.is_a?(Errno::ECONNREFUSED) ||
|
478
|
-
boom.is_a?(Errno::ECONNRESET) ||
|
479
|
-
boom.is_a?(Errno::ENOENT))
|
480
|
-
return @socket = nil
|
406
|
+
forwarder.send_message(full_stat)
|
481
407
|
end
|
482
|
-
# Try once to reconnect if the socket has been closed
|
483
|
-
retries ||= 1
|
484
|
-
if retries <= 1 && boom.is_a?(IOError) && boom.message =~ /closed stream/i
|
485
|
-
retries += 1
|
486
|
-
begin
|
487
|
-
@socket = connect_to_socket
|
488
|
-
retry
|
489
|
-
rescue => e
|
490
|
-
boom = e
|
491
|
-
end
|
492
|
-
end
|
493
|
-
|
494
|
-
self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
|
495
|
-
nil
|
496
408
|
end
|
497
409
|
end
|
498
410
|
end
|