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.
@@ -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