dogstatsd-ruby 4.0.0 → 4.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d2b1b9e7ec2a48c5305f6acda103d0b7a09787801dd899a38fb665f5389e4f3
4
- data.tar.gz: 3d974fffb4ace3bc248e69dcdaabb4c556e6783a25d65af98cb65bdccafc52ee
3
+ metadata.gz: dc2f6d365efc8cf25456fb9dbd5ad526abe0ec61f2b63e9b79346c767aa3a3b0
4
+ data.tar.gz: 84a029741390b63cf0540de60a872c6b0820bf6ad4c1619cea120d71bc38c3bc
5
5
  SHA512:
6
- metadata.gz: 16c82cb62bfd324d5e1dc1c6293fa121b92bd0c88d00474210c5da67f2cb07ae350cc0b717b15739c7a6f09e9021323ba348cec70ec2d0ac66a362f15f81007b
7
- data.tar.gz: 6e82cef56d746ecf7b10890d397e313c143611b18b4ae65db67eb2f252cb27acacb45de03f6447b7b4ebb1666209806d6b048fb2857c5c8790f1f6729f4ae806
6
+ metadata.gz: 769645a11d455fceb1b0c4d7d718005d869c62546cd1c255f0a45b9da51a300500dfe2c1a4e601248f1e8ef6ce1e40c15ebaca55d6e55092babef1c153ccdcf0
7
+ data.tar.gz: 1429d390a2c52758f957dd4766cf30db5039986527982f3188fcc44d7c058431d9f85180b1af7e5dae8f82db31325115b0ea4efb1fced82d7585924b0702fd0c
data/README.md CHANGED
@@ -1,95 +1,90 @@
1
+ # dogstatsd-ruby
1
2
 
2
- dogstatsd-ruby
3
- ==============
3
+ A client for DogStatsD, an extension of the StatsD metric server for Datadog. Full API documentation is available in [DogStatsD-ruby rubydoc](https://www.rubydoc.info/github/DataDog/dogstatsd-ruby/master/Datadog/Statsd).
4
4
 
5
- A client for DogStatsD, an extension of the StatsD metric server for Datadog.
5
+ [![Build Status](https://secure.travis-ci.org/DataDog/dogstatsd-ruby.svg)](http://travis-ci.org/DataDog/dogstatsd-ruby)
6
6
 
7
- [![Build Status](https://secure.travis-ci.org/DataDog/dogstatsd-ruby.png)](http://travis-ci.org/DataDog/dogstatsd-ruby)
7
+ See [CHANGELOG.md](CHANGELOG.md) for changes. To suggest a feature, report a bug, or general discussion, [open an issue](http://github.com/DataDog/dogstatsd-ruby/issues/).
8
8
 
9
- Quick Start Guide
10
- -----------------
9
+ ## Installation
11
10
 
12
11
  First install the library:
13
12
 
14
- gem install dogstatsd-ruby
13
+ ```
14
+ gem install dogstatsd-ruby
15
+ ```
16
+
17
+ ## Configuration
15
18
 
16
- Then start instrumenting your code:
19
+ To instantiate a DogStatsd client:
17
20
 
18
- ``` ruby
19
- # Load the dogstats module.
21
+ ```ruby
22
+ # Import the library
20
23
  require 'datadog/statsd'
21
24
 
22
- # Create a stats instance.
25
+ # Create a DogStatsD client instance.
23
26
  statsd = Datadog::Statsd.new('localhost', 8125)
27
+ ```
28
+ Or if you want to connect over Unix Domain Socket:
29
+ ```ruby
30
+ # Connection over Unix Domain Socket
31
+ statsd = Datadog::Statsd.new(socket_path: '/path/to/socket/file')
32
+ ```
24
33
 
25
- # you could also create a statsd class if you need a drop in replacement
26
- # class Statsd < Datadog::Statsd
27
- # end
28
-
29
- # Increment a counter.
30
- statsd.increment('page.views')
31
-
32
- # Record a gauge 50% of the time.
33
- statsd.gauge('users.online', 123, :sample_rate=>0.5)
34
-
35
- # Sample a histogram
36
- statsd.histogram('file.upload.size', 1234)
37
-
38
- # Time a block of code
39
- statsd.time('page.render') do
40
- render_page('home.html')
41
- end
34
+ Find a list of all the available options for your DogStatsD Client in the [DogStatsD-ruby rubydoc](https://www.rubydoc.info/github/DataDog/dogstatsd-ruby/master/Datadog/Statsd) or in the [Datadog public DogStatsD documentation](https://docs.datadoghq.com/developers/dogstatsd/?tab=ruby#client-instantiation-parameters).
42
35
 
43
- # Send several metrics at the same time
44
- # All metrics will be buffered and sent in one packet when the block completes
45
- statsd.batch do |s|
46
- s.increment('page.views')
47
- s.gauge('users.online', 123)
48
- end
36
+ ### Origin detection over UDP
49
37
 
50
- # Tag a metric.
51
- statsd.histogram('query.time', 10, :tags => ["version:1"])
38
+ Origin detection is a method to detect which pod DogStatsD packets are coming from in order to add the pod's tags to the tag list.
52
39
 
53
- # Auto-close socket after end of block
54
- Datadog::Statsd.open('localhost', 8125) do |s|
55
- s.increment('page.views')
56
- end
40
+ To enable origin detection over UDP, add the following lines to your application manifest
41
+ ```yaml
42
+ env:
43
+ - name: DD_ENTITY_ID
44
+ valueFrom:
45
+ fieldRef:
46
+ fieldPath: metadata.uid
57
47
  ```
48
+ The DogStatsD client attaches an internal tag, `entity_id`. The value of this tag is the content of the `DD_ENTITY_ID` environment variable, which is the pod’s UID.
58
49
 
59
- You can also post events to your stream. You can tag them, set priority and even aggregate them with other events.
50
+ ## Usage
60
51
 
61
- Aggregation in the stream is made on hostname/event_type/source_type/aggregation_key.
52
+ In order to use DogStatsD metrics, events, and Service Checks the Agent must be [running and available](https://docs.datadoghq.com/developers/dogstatsd/?tab=ruby).
62
53
 
63
- ``` ruby
64
- # Post a simple message
65
- statsd.event("There might be a storm tomorrow", "A friend warned me earlier.")
66
-
67
- # Cry for help
68
- statsd.event("SO MUCH SNOW", "Started yesterday and it won't stop !!", :alert_type => "error", :tags => ["urgent", "endoftheworld"])
69
- ```
54
+ ### Metrics
70
55
 
56
+ After the client is created, you can start sending custom metrics to Datadog. See the dedicated [Metric Submission: DogStatsD documentation](https://docs.datadoghq.com/developers/metrics/dogstatsd_metrics_submission/?tab=ruby) to see how to submit all supported metric types to Datadog with working code examples:
71
57
 
58
+ * [Submit a COUNT metric](https://docs.datadoghq.com/developers/metrics/dogstatsd_metrics_submission/?tab=ruby#count).
59
+ * [Submit a GAUGE metric](https://docs.datadoghq.com/developers/metrics/dogstatsd_metrics_submission/?tab=ruby#gauge).
60
+ * [Submit a SET metric](https://docs.datadoghq.com/developers/metrics/dogstatsd_metrics_submission/?tab=ruby#set)
61
+ * [Submit a HISTOGRAM metric](https://docs.datadoghq.com/developers/metrics/dogstatsd_metrics_submission/?tab=ruby#histogram)
62
+ * [Submit a DISTRIBUTION metric](https://docs.datadoghq.com/developers/metrics/dogstatsd_metrics_submission/?tab=ruby#distribution)
72
63
 
73
- Documentation
74
- -------------
64
+ Some options are suppported when submitting metrics, like [applying a Sample Rate to your metrics](https://docs.datadoghq.com/developers/metrics/dogstatsd_metrics_submission/?tab=ruby#metric-submission-options) or [tagging your metrics with your custom tags](https://docs.datadoghq.com/developers/metrics/dogstatsd_metrics_submission/?tab=ruby#metric-tagging). Find all the available functions to report metrics in the [DogStatsD-ruby rubydoc](https://www.rubydoc.info/github/DataDog/dogstatsd-ruby/master/Datadog/Statsd).
75
65
 
76
- Full API documentation is available
77
- [here](http://www.rubydoc.info/github/DataDog/dogstatsd-ruby/master/frames).
66
+ ### Events
78
67
 
68
+ After the client is created, you can start sending events to your Datadog Event Stream. See the dedicated [Event Submission: DogStatsD documentation](https://docs.datadoghq.com/developers/events/dogstatsd/?tab=ruby) to see how to submit an event to Datadog your Event Stream.
79
69
 
80
- Feedback
81
- --------
70
+ ### Service Checks
82
71
 
83
- To suggest a feature, report a bug, or general discussion, head over
84
- [here](http://github.com/DataDog/dogstatsd-ruby/issues/).
72
+ After the client is created, you can start sending Service Checks to Datadog. See the dedicated [Service Check Submission: DogStatsD documentation](https://docs.datadoghq.com/developers/service_checks/dogstatsd_service_checks_submission/?tab=ruby) to see how to submit a Service Check to Datadog.
85
73
 
74
+ ### Maximum packets size in high-throughput scenarios
86
75
 
87
- [Change Log](CHANGELOG.md)
88
- ----------------------------
76
+ In order to have the most efficient use of this library in high-throughput scenarios,
77
+ default values for the maximum packets size have already been set for both UDS (8192 bytes)
78
+ and UDP (1432 bytes) in order to have the best usage of the underlying network.
79
+ However, if you perfectly know your network and you know that a different value for the maximum packets
80
+ size should be used, you can set it with the parameter `buffer_max_payload_size`. Example:
89
81
 
82
+ ```ruby
83
+ # Create a DogStatsD client instance.
84
+ statsd = Datadog::Statsd.new('localhost', 8125, buffer_max_payload_size: 4096)
85
+ ```
90
86
 
91
- Credits
92
- -------
87
+ ## Credits
93
88
 
94
89
  dogstatsd-ruby is forked from Rien Henrichs [original Statsd
95
90
  client](https://github.com/reinh/statsd).
@@ -1,6 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
  require 'socket'
3
3
 
4
+ require_relative 'statsd/version'
5
+ require_relative 'statsd/telemetry'
6
+ require_relative 'statsd/udp_connection'
7
+ require_relative 'statsd/uds_connection'
8
+ require_relative 'statsd/batch'
9
+ require_relative 'statsd/serialization'
10
+
4
11
  # = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
5
12
  #
6
13
  # @example Set up a global Statsd client for a server on localhost:8125
@@ -19,174 +26,30 @@ require 'socket'
19
26
  # statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
20
27
  module Datadog
21
28
  class Statsd
22
-
23
- class Connection
24
- DEFAULT_HOST = '127.0.0.1'
25
- DEFAULT_PORT = 8125
26
-
27
- # StatsD host. Defaults to 127.0.0.1.
28
- attr_reader :host
29
-
30
- # StatsD port. Defaults to 8125.
31
- attr_reader :port
32
-
33
- # DogStatsd unix socket path. Not used by default.
34
- attr_reader :socket_path
35
-
36
- def initialize(host, port, socket_path, logger)
37
- @host = host || DEFAULT_HOST
38
- @port = port || DEFAULT_PORT
39
- @socket_path = socket_path
40
- @logger = logger
41
- end
42
-
43
- def write(message)
44
- @logger.debug { "Statsd: #{message}" } if @logger
45
- if @socket_path.nil?
46
- socket.send(message, 0)
47
- else
48
- socket.sendmsg_nonblock(message)
49
- end
50
- rescue StandardError => boom
51
- # Give up on this socket if it looks like it is bad
52
- bad_socket = !@socket_path.nil? && (
53
- boom.is_a?(Errno::ECONNREFUSED) ||
54
- boom.is_a?(Errno::ECONNRESET) ||
55
- boom.is_a?(Errno::ENOENT)
56
- )
57
- if bad_socket
58
- @socket = nil
59
- return
60
- end
61
-
62
- # Try once to reconnect if the socket has been closed
63
- retries ||= 1
64
- if retries <= 1 && boom.is_a?(IOError) && boom.message =~ /closed stream/i
65
- retries += 1
66
- begin
67
- @socket = connect
68
- retry
69
- rescue StandardError => e
70
- boom = e
71
- end
72
- end
73
-
74
- @logger.error { "Statsd: #{boom.class} #{boom}" } if @logger
75
- nil
76
- end
77
-
78
- # Close the underlying socket
79
- def close
80
- @socket && @socket.close
81
- end
82
-
83
- private
84
-
85
- def socket
86
- @socket ||= connect
87
- end
88
-
89
- def connect
90
- if @socket_path.nil?
91
- socket = UDPSocket.new
92
- socket.connect(@host, @port)
93
- else
94
- socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
95
- socket.connect(Socket.pack_sockaddr_un(@socket_path))
96
- end
97
- socket
98
- end
99
- end
100
-
101
- class Batch
102
- def initialize(connection, max_buffer_bytes)
103
- @connection = connection
104
- @max_buffer_bytes = max_buffer_bytes
105
- @depth = 0
106
- reset
107
- end
108
-
109
- def open
110
- @depth += 1
111
- yield
112
- ensure
113
- @depth -= 1
114
- flush if !open?
115
- end
116
-
117
- def open?
118
- @depth > 0
119
- end
120
-
121
- def add(message)
122
- message_bytes = message.bytesize
123
-
124
- unless @buffer_bytes == 0
125
- if @buffer_bytes + 1 + message_bytes >= @max_buffer_bytes
126
- flush
127
- else
128
- @buffer << NEW_LINE
129
- @buffer_bytes += 1
130
- end
131
- end
132
-
133
- @buffer << message
134
- @buffer_bytes += message_bytes
135
- end
136
-
137
- def flush
138
- return if @buffer_bytes == 0
139
- @connection.write @buffer
140
- reset
141
- end
142
-
143
- private
144
-
145
- def reset
146
- @buffer = String.new
147
- @buffer_bytes = 0
148
- end
149
- end
150
-
151
- # Create a dictionary to assign a key to every parameter's name, except for tags (treated differently)
152
- # Goal: Simple and fast to add some other parameters
153
- OPTS_KEYS = {
154
- :date_happened => :d,
155
- :hostname => :h,
156
- :aggregation_key => :k,
157
- :priority => :p,
158
- :source_type_name => :s,
159
- :alert_type => :t,
160
- }
161
-
162
- # Service check options
163
- SC_OPT_KEYS = {
164
- :timestamp => 'd:'.freeze,
165
- :hostname => 'h:'.freeze,
166
- :tags => '#'.freeze,
167
- :message => 'm:'.freeze,
168
- }
169
-
170
- OK = 0
171
- WARNING = 1
172
- CRITICAL = 2
173
- UNKNOWN = 3
174
-
175
- MAX_EVENT_SIZE = 8 * 1024
176
-
177
- COUNTER_TYPE = 'c'.freeze
178
- GAUGE_TYPE = 'g'.freeze
179
- HISTOGRAM_TYPE = 'h'.freeze
180
- DISTRIBUTION_TYPE = 'd'.freeze
181
- TIMING_TYPE = 'ms'.freeze
182
- SET_TYPE = 's'.freeze
183
- VERSION = "4.0.0".freeze
29
+ OK = 0
30
+ WARNING = 1
31
+ CRITICAL = 2
32
+ UNKNOWN = 3
33
+
34
+ DEFAULT_BUFFER_SIZE = 8 * 1_024
35
+ MAX_EVENT_SIZE = 8 * 1_024
36
+ # minimum flush interval for the telemetry in seconds
37
+ DEFAULT_TELEMETRY_FLUSH_INTERVAL = 10
38
+
39
+ COUNTER_TYPE = 'c'
40
+ GAUGE_TYPE = 'g'
41
+ HISTOGRAM_TYPE = 'h'
42
+ DISTRIBUTION_TYPE = 'd'
43
+ TIMING_TYPE = 'ms'
44
+ SET_TYPE = 's'
184
45
 
185
46
  # A namespace to prepend to all statsd calls. Defaults to no namespace.
186
47
  attr_reader :namespace
187
48
 
188
49
  # Global tags to be added to every statsd call. Defaults to no tags.
189
- attr_reader :tags
50
+ def tags
51
+ serializer.global_tags
52
+ end
190
53
 
191
54
  # Buffer containing the statsd message before they are sent in batch
192
55
  attr_reader :buffer
@@ -194,41 +57,68 @@ module Datadog
194
57
  # Maximum buffer size in bytes before it is flushed
195
58
  attr_reader :max_buffer_bytes
196
59
 
60
+ # Default sample rate
61
+ attr_reader :sample_rate
62
+
197
63
  # Connection
198
64
  attr_reader :connection
199
65
 
200
66
  # @param [String] host your statsd host
201
67
  # @param [Integer] port your statsd port
202
68
  # @option [String] namespace set a namespace to be prepended to every metric name
203
- # @option [Array<String>] tags tags to be added to every metric
204
- # @option [Loger] logger for debugging
69
+ # @option [Array<String>|Hash] tags tags to be added to every metric
70
+ # @option [Logger] logger for debugging
205
71
  # @option [Integer] max_buffer_bytes max bytes to buffer when using #batch
206
72
  # @option [String] socket_path unix socket path
73
+ # @option [Float] default sample rate if not overridden
207
74
  def initialize(
208
75
  host = nil,
209
76
  port = nil,
210
77
  namespace: nil,
211
78
  tags: nil,
212
- max_buffer_bytes: 8192,
79
+ max_buffer_bytes: DEFAULT_BUFFER_SIZE,
213
80
  socket_path: nil,
214
- logger: nil
81
+ logger: nil,
82
+ sample_rate: nil,
83
+ disable_telemetry: false,
84
+ telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
215
85
  )
216
- @connection = Connection.new(host, port, socket_path, logger)
217
- @logger = logger
86
+ unless tags.nil? || tags.is_a?(Array) || tags.is_a?(Hash)
87
+ raise ArgumentError, 'tags must be a Array<String> or a Hash'
88
+ end
218
89
 
219
90
  @namespace = namespace
220
91
  @prefix = @namespace ? "#{@namespace}.".freeze : nil
221
92
 
222
- raise ArgumentError, 'tags must be a Array<String>' unless tags.nil? or tags.is_a? Array
223
- @tags = (tags || []).compact.map! {|tag| escape_tag_content(tag)}
93
+ @serializer = Serialization::Serializer.new(prefix: @prefix, global_tags: tags)
94
+
95
+ transport_type = socket_path.nil? ? :udp : :uds
96
+
97
+ @telemetry = Telemetry.new(disable_telemetry, telemetry_flush_interval,
98
+ global_tags: tags,
99
+ transport_type: transport_type
100
+ )
224
101
 
225
- @batch = Batch.new @connection, max_buffer_bytes
102
+ @connection = case transport_type
103
+ when :udp
104
+ UDPConnection.new(host, port, logger, telemetry)
105
+ when :uds
106
+ UDSConnection.new(socket_path, logger, telemetry)
107
+ end
108
+
109
+ @logger = logger
110
+
111
+ @sample_rate = sample_rate
112
+
113
+ # we reduce max_buffer_bytes by a the rough estimate of the telemetry payload
114
+ @batch = Batch.new(connection, (max_buffer_bytes - telemetry.estimate_max_size))
226
115
  end
227
116
 
228
117
  # yield a new instance to a block and close it when done
229
118
  # for short-term use-cases that don't want to close the socket manually
230
119
  def self.open(*args)
231
120
  instance = new(*args)
121
+
232
122
  yield instance
233
123
  ensure
234
124
  instance.close
@@ -242,10 +132,10 @@ module Datadog
242
132
  # @option opts [Array<String>] :tags An array of tags
243
133
  # @option opts [Numeric] :by increment value, default 1
244
134
  # @see #count
245
- def increment(stat, opts=EMPTY_OPTIONS)
246
- opts = {:sample_rate => opts} if opts.is_a? Numeric
135
+ def increment(stat, opts = EMPTY_OPTIONS)
136
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
247
137
  incr_value = opts.fetch(:by, 1)
248
- count stat, incr_value, opts
138
+ count(stat, incr_value, opts)
249
139
  end
250
140
 
251
141
  # Sends a decrement (count = -1) for the given stat to the statsd server.
@@ -256,10 +146,10 @@ module Datadog
256
146
  # @option opts [Array<String>] :tags An array of tags
257
147
  # @option opts [Numeric] :by decrement value, default 1
258
148
  # @see #count
259
- def decrement(stat, opts=EMPTY_OPTIONS)
260
- opts = {:sample_rate => opts} if opts.is_a? Numeric
149
+ def decrement(stat, opts = EMPTY_OPTIONS)
150
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
261
151
  decr_value = - opts.fetch(:by, 1)
262
- count stat, decr_value, opts
152
+ count(stat, decr_value, opts)
263
153
  end
264
154
 
265
155
  # Sends an arbitrary count for the given stat to the statsd server.
@@ -269,9 +159,9 @@ module Datadog
269
159
  # @param [Hash] opts the options to create the metric with
270
160
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
271
161
  # @option opts [Array<String>] :tags An array of tags
272
- def count(stat, count, opts=EMPTY_OPTIONS)
273
- opts = {:sample_rate => opts} if opts.is_a? Numeric
274
- send_stats stat, count, COUNTER_TYPE, opts
162
+ def count(stat, count, opts = EMPTY_OPTIONS)
163
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
164
+ send_stats(stat, count, COUNTER_TYPE, opts)
275
165
  end
276
166
 
277
167
  # Sends an arbitary gauge value for the given stat to the statsd server.
@@ -287,9 +177,9 @@ module Datadog
287
177
  # @option opts [Array<String>] :tags An array of tags
288
178
  # @example Report the current user count:
289
179
  # $statsd.gauge('user.count', User.count)
290
- def gauge(stat, value, opts=EMPTY_OPTIONS)
291
- opts = {:sample_rate => opts} if opts.is_a? Numeric
292
- send_stats stat, value, GAUGE_TYPE, opts
180
+ def gauge(stat, value, opts = EMPTY_OPTIONS)
181
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
182
+ send_stats(stat, value, GAUGE_TYPE, opts)
293
183
  end
294
184
 
295
185
  # Sends a value to be tracked as a histogram to the statsd server.
@@ -301,14 +191,11 @@ module Datadog
301
191
  # @option opts [Array<String>] :tags An array of tags
302
192
  # @example Report the current user count:
303
193
  # $statsd.histogram('user.count', User.count)
304
- def histogram(stat, value, opts=EMPTY_OPTIONS)
305
- send_stats stat, value, HISTOGRAM_TYPE, opts
194
+ def histogram(stat, value, opts = EMPTY_OPTIONS)
195
+ send_stats(stat, value, HISTOGRAM_TYPE, opts)
306
196
  end
307
197
 
308
198
  # Sends a value to be tracked as a distribution to the statsd server.
309
- # Note: Distributions are a beta feature of Datadog and not generally
310
- # available. Distributions must be specifically enabled for your
311
- # organization.
312
199
  #
313
200
  # @param [String] stat stat name.
314
201
  # @param [Numeric] value distribution value.
@@ -317,8 +204,8 @@ module Datadog
317
204
  # @option opts [Array<String>] :tags An array of tags
318
205
  # @example Report the current user count:
319
206
  # $statsd.distribution('user.count', User.count)
320
- def distribution(stat, value, opts=EMPTY_OPTIONS)
321
- send_stats stat, value, DISTRIBUTION_TYPE, opts
207
+ def distribution(stat, value, opts = EMPTY_OPTIONS)
208
+ send_stats(stat, value, DISTRIBUTION_TYPE, opts)
322
209
  end
323
210
 
324
211
  # Sends a timing (in ms) for the given stat to the statsd server. The
@@ -331,9 +218,9 @@ module Datadog
331
218
  # @param [Hash] opts the options to create the metric with
332
219
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
333
220
  # @option opts [Array<String>] :tags An array of tags
334
- def timing(stat, ms, opts=EMPTY_OPTIONS)
335
- opts = {:sample_rate => opts} if opts.is_a? Numeric
336
- send_stats stat, ms, TIMING_TYPE, opts
221
+ def timing(stat, ms, opts = EMPTY_OPTIONS)
222
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
223
+ send_stats(stat, ms, TIMING_TYPE, opts)
337
224
  end
338
225
 
339
226
  # Reports execution time of the provided block using {#timing}.
@@ -349,13 +236,12 @@ module Datadog
349
236
  # @see #timing
350
237
  # @example Report the time (in ms) taken to activate an account
351
238
  # $statsd.time('account.activate') { @account.activate! }
352
- def time(stat, opts=EMPTY_OPTIONS)
353
- opts = {:sample_rate => opts} if opts.is_a? Numeric
354
- start = (PROCESS_TIME_SUPPORTED ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : Time.now.to_f)
355
- return yield
239
+ def time(stat, opts = EMPTY_OPTIONS)
240
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
241
+ start = now
242
+ yield
356
243
  ensure
357
- finished = (PROCESS_TIME_SUPPORTED ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : Time.now.to_f)
358
- timing(stat, ((finished - start) * 1000).round, opts)
244
+ timing(stat, ((now - start) * 1000).round, opts)
359
245
  end
360
246
 
361
247
  # Sends a value to be tracked as a set to the statsd server.
@@ -367,9 +253,9 @@ module Datadog
367
253
  # @option opts [Array<String>] :tags An array of tags
368
254
  # @example Record a unique visitory by id:
369
255
  # $statsd.set('visitors.uniques', User.id)
370
- def set(stat, value, opts=EMPTY_OPTIONS)
371
- opts = {:sample_rate => opts} if opts.is_a? Numeric
372
- send_stats stat, value, SET_TYPE, opts
256
+ def set(stat, value, opts = EMPTY_OPTIONS)
257
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
258
+ send_stats(stat, value, SET_TYPE, opts)
373
259
  end
374
260
 
375
261
  # This method allows you to send custom service check statuses.
@@ -377,14 +263,16 @@ module Datadog
377
263
  # @param [String] name Service check name
378
264
  # @param [String] status Service check status.
379
265
  # @param [Hash] opts the additional data about the service check
380
- # @option opts [Integer, nil] :timestamp (nil) Assign a timestamp to the event. Default is now when none
381
- # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
266
+ # @option opts [Integer, String, nil] :timestamp (nil) Assign a timestamp to the service check. Default is now when none
267
+ # @option opts [String, nil] :hostname (nil) Assign a hostname to the service check.
382
268
  # @option opts [Array<String>, nil] :tags (nil) An array of tags
383
269
  # @option opts [String, nil] :message (nil) A message to associate with this service check status
384
270
  # @example Report a critical service check status
385
271
  # $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
386
- def service_check(name, status, opts=EMPTY_OPTIONS)
387
- send_stat format_service_check(name, status, opts)
272
+ def service_check(name, status, opts = EMPTY_OPTIONS)
273
+ telemetry.sent(service_checks: 1)
274
+
275
+ send_stat(serializer.to_service_check(name, status, opts))
388
276
  end
389
277
 
390
278
  # This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
@@ -394,19 +282,22 @@ module Datadog
394
282
  # it will be grouped with other events that don't have an event type.
395
283
  #
396
284
  # @param [String] title Event title
397
- # @param [String] text Event text. Supports \n
285
+ # @param [String] text Event text. Supports newlines (+\n+)
398
286
  # @param [Hash] opts the additional data about the event
399
- # @option opts [Integer, nil] :date_happened (nil) Assign a timestamp to the event. Default is now when none
287
+ # @option opts [Integer, String, nil] :date_happened (nil) Assign a timestamp to the event. Default is now when none
400
288
  # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
401
289
  # @option opts [String, nil] :aggregation_key (nil) Assign an aggregation key to the event, to group it with some others
402
290
  # @option opts [String, nil] :priority ('normal') Can be "normal" or "low"
403
291
  # @option opts [String, nil] :source_type_name (nil) Assign a source type to the event
404
292
  # @option opts [String, nil] :alert_type ('info') Can be "error", "warning", "info" or "success".
293
+ # @option opts [Boolean, false] :truncate_if_too_long (false) Truncate the event if it is too long
405
294
  # @option opts [Array<String>] :tags tags to be added to every metric
406
295
  # @example Report an awful event:
407
296
  # $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
408
- def event(title, text, opts=EMPTY_OPTIONS)
409
- send_stat format_event(title, text, opts)
297
+ def event(title, text, opts = EMPTY_OPTIONS)
298
+ telemetry.sent(events: 1)
299
+
300
+ send_stat(serializer.to_event(title, text, opts))
410
301
  end
411
302
 
412
303
  # Send several metrics in the same UDP Packet
@@ -418,130 +309,40 @@ module Datadog
418
309
  # s.increment('page.views')
419
310
  # end
420
311
  def batch
421
- @batch.open { yield self }
312
+ @batch.open do
313
+ yield self
314
+ end
422
315
  end
423
316
 
424
317
  # Close the underlying socket
425
318
  def close
426
- @connection.close
319
+ connection.close
427
320
  end
428
321
 
429
322
  private
323
+ attr_reader :serializer
324
+ attr_reader :telemetry
430
325
 
431
- NEW_LINE = "\n".freeze
432
- ESC_NEW_LINE = "\\n".freeze
433
- COMMA = ",".freeze
434
- PIPE = "|".freeze
435
- DOT = ".".freeze
436
- DOUBLE_COLON = "::".freeze
437
- UNDERSCORE = "_".freeze
438
- PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= "2.1.0")
326
+ PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= '2.1.0')
439
327
  EMPTY_OPTIONS = {}.freeze
440
328
 
441
- private_constant :NEW_LINE, :ESC_NEW_LINE, :COMMA, :PIPE, :DOT,
442
- :DOUBLE_COLON, :UNDERSCORE, :EMPTY_OPTIONS
443
-
444
- def format_service_check(name, status, opts=EMPTY_OPTIONS)
445
- sc_string = "_sc|#{name}|#{status}".dup
446
-
447
- SC_OPT_KEYS.each do |key, shorthand_key|
448
- next unless opts[key]
449
-
450
- if key == :tags
451
- if tags_string = tags_as_string(opts)
452
- sc_string << "|##{tags_string}"
453
- end
454
- elsif key == :message
455
- message = remove_pipes(opts[:message])
456
- escaped_message = escape_service_check_message(message)
457
- sc_string << "|m:#{escaped_message}"
458
- else
459
- value = remove_pipes(opts[key])
460
- sc_string << "|#{shorthand_key}#{value}"
461
- end
329
+ if PROCESS_TIME_SUPPORTED
330
+ def now
331
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
462
332
  end
463
- sc_string
464
- end
465
-
466
- def format_event(title, text, opts=EMPTY_OPTIONS)
467
- escaped_title = escape_event_content(title)
468
- escaped_text = escape_event_content(text)
469
- event_string_data = "_e{#{escaped_title.length},#{escaped_text.length}}:#{escaped_title}|#{escaped_text}".dup
470
-
471
- # We construct the string to be sent by adding '|key:value' parts to it when needed
472
- # All pipes ('|') in the metadata are removed. Title and Text can keep theirs
473
- OPTS_KEYS.each do |key, shorthand_key|
474
- if key != :tags && opts[key]
475
- value = remove_pipes(opts[key])
476
- event_string_data << "|#{shorthand_key}:#{value}"
477
- end
478
- end
479
-
480
- # Tags are joined and added as last part to the string to be sent
481
- if tags_string = tags_as_string(opts)
482
- event_string_data << "|##{tags_string}"
333
+ else
334
+ def now
335
+ Time.now.to_f
483
336
  end
484
-
485
- raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string_data.length > MAX_EVENT_SIZE
486
- event_string_data
487
337
  end
488
338
 
489
- def tags_as_string(opts)
490
- if tag_arr = opts[:tags]
491
- tag_arr = tag_arr.map { |tag| escape_tag_content(tag) }
492
- tag_arr = tags + tag_arr # @tags are normalized when set, so not need to normalize them again
493
- else
494
- tag_arr = tags
495
- end
496
- tag_arr.join(COMMA) unless tag_arr.empty?
497
- end
339
+ def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
340
+ telemetry.sent(metrics: 1)
498
341
 
499
- def escape_event_content(msg)
500
- msg.gsub NEW_LINE, ESC_NEW_LINE
501
- end
502
-
503
- def escape_tag_content(tag)
504
- tag = remove_pipes(tag.to_s)
505
- tag.delete! COMMA
506
- tag
507
- end
508
-
509
- def remove_pipes(msg)
510
- msg.delete PIPE
511
- end
512
-
513
- def escape_service_check_message(msg)
514
- escape_event_content(msg).gsub('m:'.freeze, 'm\:'.freeze)
515
- end
342
+ sample_rate = opts[:sample_rate] || @sample_rate || 1
516
343
 
517
- def send_stats(stat, delta, type, opts=EMPTY_OPTIONS)
518
- sample_rate = opts[:sample_rate] || 1
519
- if sample_rate == 1 or rand < sample_rate
520
- full_stat = ''.dup
521
- full_stat << @prefix if @prefix
522
-
523
- stat = stat.is_a?(String) ? stat.dup : stat.to_s
524
- # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
525
- stat.gsub!(DOUBLE_COLON, DOT)
526
- stat.tr!(':|@'.freeze, UNDERSCORE)
527
- full_stat << stat
528
-
529
- full_stat << ':'.freeze
530
- full_stat << delta.to_s
531
- full_stat << PIPE
532
- full_stat << type
533
-
534
- unless sample_rate == 1
535
- full_stat << PIPE
536
- full_stat << '@'.freeze
537
- full_stat << sample_rate.to_s
538
- end
539
-
540
- if tags_string = tags_as_string(opts)
541
- full_stat << PIPE
542
- full_stat << '#'.freeze
543
- full_stat << tags_string
544
- end
344
+ if sample_rate == 1 || rand <= sample_rate
345
+ full_stat = serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
545
346
 
546
347
  send_stat(full_stat)
547
348
  end
@@ -549,7 +350,7 @@ module Datadog
549
350
 
550
351
  def send_stat(message)
551
352
  if @batch.open?
552
- @batch.add message
353
+ @batch.add(message)
553
354
  else
554
355
  @connection.write(message)
555
356
  end