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.
@@ -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, :tags => 'tag1:true'
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
- DEFAULT_HOST = '127.0.0.1'
23
- DEFAULT_PORT = 8125
24
-
25
- # Create a dictionary to assign a key to every parameter's name, except for tags (treated differently)
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
- # StatsD host. Defaults to 127.0.0.1.
61
- attr_reader :host
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
- # StatsD port. Defaults to 8125.
64
- attr_reader :port
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
- # DogStatsd unix socket path. Not used by default.
67
- attr_reader :socket_path
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
- attr_reader :tags
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
- # Return the current version of the library.
84
- # deprecated, but cannot be removed since uses might use it to check the version against older releases
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 opts [String] :namespace set a namespace to be prepended to every metric name
92
- # @option opts [Array<String>] :tags tags to be added to every metric
93
- def initialize(host = DEFAULT_HOST, port = DEFAULT_PORT, opts = {}, max_buffer_size=50)
94
- self.host, self.port = host, port
95
- @socket_path = opts[:socket_path]
96
- @prefix = nil
97
- @socket = connect_to_socket if @socket_path.nil?
98
- self.namespace = opts[:namespace]
99
- self.tags = opts[:tags]
100
- @buffer = Array.new
101
- self.max_buffer_size = max_buffer_size
102
- @batch_nesting_depth = 0
103
- end
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.nil? ? nil : "#{namespace}.".freeze
108
- end
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
- def host=(host) #:nodoc:
111
- @host = host || DEFAULT_HOST
112
- end
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
- def port=(port) #:nodoc:
115
- @port = port || DEFAULT_PORT
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
- def tags=(tags) #:nodoc:
119
- raise ArgumentError, 'tags must be a Array<String>' unless tags.nil? or tags.is_a? Array
120
- @tags = (tags || []).compact.map! {|tag| escape_tag_content(tag)}
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 = {:sample_rate => opts} if opts.is_a? Numeric
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 stat, incr_value, opts
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 = {:sample_rate => opts} if opts.is_a? Numeric
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 stat, decr_value, opts
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 = {:sample_rate => opts} if opts.is_a? Numeric
160
- send_stats stat, count, COUNTER_TYPE, opts
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 = {:sample_rate => opts} if opts.is_a? Numeric
178
- send_stats stat, value, GAUGE_TYPE, opts
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 stat, value, HISTOGRAM_TYPE, opts
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 stat, value, DISTRIBUTION_TYPE, opts
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 = {:sample_rate => opts} if opts.is_a? Numeric
222
- send_stats stat, ms, TIMING_TYPE, opts
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 = {:sample_rate => opts} if opts.is_a? Numeric
240
- start = (PROCESS_TIME_SUPPORTED ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : Time.now.to_f)
241
- return yield
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
- finished = (PROCESS_TIME_SUPPORTED ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : Time.now.to_f)
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 = {:sample_rate => opts} if opts.is_a? Numeric
257
- send_stats stat, value, SET_TYPE, opts
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 event. Default is now when none
266
- # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
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
- service_check_string = format_service_check(name, status, opts)
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
- if key == :tags
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 \n
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
- event_string = format_event(title, text, opts)
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
- send_to_socket event_string
321
+ forwarder.send_message(serializer.to_event(title, text, opts))
321
322
  end
322
323
 
323
- # Send several metrics in the same UDP Packet
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
- ensure
335
- @batch_nesting_depth -= 1
336
- flush_buffer if @batch_nesting_depth == 0
341
+ flush(sync: true)
337
342
  end
338
343
 
339
- def format_event(title, text, opts={})
340
- escaped_title = escape_event_content(title)
341
- escaped_text = escape_event_content(text)
342
- event_string_data = "_e{#{escaped_title.length},#{escaped_text.length}}:#{escaped_title}|#{escaped_text}"
343
-
344
- # We construct the string to be sent by adding '|key:value' parts to it when needed
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
- # Close the underlying socket
363
- def close()
364
- @socket.close
352
+ def sync_with_outbound_io
353
+ forwarder.sync_with_outbound_io
365
354
  end
366
355
 
367
- private
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
- NEW_LINE = "\n".freeze
370
- ESC_NEW_LINE = "\\n".freeze
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 escape_event_content(msg)
389
- msg.gsub NEW_LINE, ESC_NEW_LINE
365
+ def host
366
+ forwarder.host
390
367
  end
391
368
 
392
- def escape_tag_content(tag)
393
- tag = remove_pipes(tag.to_s)
394
- tag.delete! COMMA
395
- tag
369
+ def port
370
+ forwarder.port
396
371
  end
397
372
 
398
- def remove_pipes(msg)
399
- msg.delete PIPE
373
+ def socket_path
374
+ forwarder.socket_path
400
375
  end
401
376
 
402
- def escape_service_check_message(msg)
403
- escape_event_content(msg).gsub('m:'.freeze, 'm\:'.freeze)
377
+ def transport_type
378
+ forwarder.transport_type
404
379
  end
405
380
 
406
- def send_stats(stat, delta, type, opts={})
407
- sample_rate = opts[:sample_rate] || 1
408
- if sample_rate == 1 or rand < sample_rate
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
- if tags_string = tags_as_string(opts)
430
- full_stat << PIPE
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
- send_stat(full_stat)
388
+ if PROCESS_TIME_SUPPORTED
389
+ def now
390
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
436
391
  end
437
- end
438
-
439
- def send_stat(message)
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 flush_buffer
449
- return @buffer if @buffer.empty?
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
- def connect_to_socket
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
- def sock
466
- @socket ||= connect_to_socket
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
- def send_to_socket(message)
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