dogstatsd-ruby 3.3.0 → 5.3.2
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 +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
|