dogstatsd 2.0.0

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.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +89 -0
  4. data/lib/dogstatsd.rb +377 -0
  5. metadata +105 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4541fbd03602c695372479982c0e530ca2bb8d1c
4
+ data.tar.gz: c7ce14813d97c3077695d73bd31ee33de178ed4c
5
+ SHA512:
6
+ metadata.gz: 7142447c162ad59a7737dc90b748ea3fb6eb27c8a03d17155bdcc8673ef20988e2f4aa5a9f736696182f1ee6e0dc383a8ace632b85da144321800cf9ebca4c18
7
+ data.tar.gz: cc2250c86c1688d31016fd0175674316321ee3e26c89cd377b7c69ad22180f90951ad65e3640f52742fea21dc5033d2d9ec496926f5e43d62af3b0cd2ddd861a
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Rein Henrichs
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,89 @@
1
+
2
+ dogstatsd
3
+ =========
4
+
5
+ A client for DogStatsD, an extension of the StatsD metric server for Datadog.
6
+
7
+ [![Build Status](https://secure.travis-ci.org/DataDog/dogstatsd-ruby.png)](http://travis-ci.org/DataDog/dogstatsd-ruby)
8
+
9
+ Quick Start Guide
10
+ -----------------
11
+
12
+ First install the library:
13
+
14
+ gem install dogstatsd
15
+
16
+ Then start instrumenting your code:
17
+
18
+ ``` ruby
19
+ # Load the dogstats module.
20
+ require 'dogstatsd'
21
+
22
+ # Create a stats instance.
23
+ statsd = Dogstatsd.new('localhost', 8125)
24
+
25
+ # Increment a counter.
26
+ statsd.increment('page.views')
27
+
28
+ # Record a gauge 50% of the time.
29
+ statsd.gauge('users.online', 123, :sample_rate=>0.5)
30
+
31
+ # Sample a histogram
32
+ statsd.histogram('file.upload.size', 1234)
33
+
34
+ # Time a block of code
35
+ statsd.time('page.render') do
36
+ render_page('home.html')
37
+ end
38
+
39
+ # Send several metrics at the same time
40
+ # All metrics will be buffered and sent in one packet when the block completes
41
+ statsd.batch do |s|
42
+ s.increment('page.views')
43
+ s.gauge('users.online', 123)
44
+ end
45
+
46
+ # Tag a metric.
47
+ statsd.histogram('query.time', 10, :tags => ["version:1"])
48
+ ```
49
+
50
+ You can also post events to your stream. You can tag them, set priority and even aggregate them with other events.
51
+
52
+ Aggregation in the stream is made on hostname/event_type/source_type/aggregation_key.
53
+
54
+ ``` ruby
55
+ # Post a simple message
56
+ statsd.event("There might be a storm tomorrow", "A friend warned me earlier.")
57
+
58
+ # Cry for help
59
+ statsd.event("SO MUCH SNOW", "Started yesterday and it won't stop !!", :alert_type => "error", :tags => ["urgent", "endoftheworld"])
60
+ ```
61
+
62
+
63
+
64
+ Documentation
65
+ -------------
66
+
67
+ Full API documentation is available
68
+ [here](http://www.rubydoc.info/github/DataDog/dogstatsd-ruby/master/frames).
69
+
70
+
71
+ Feedback
72
+ --------
73
+
74
+ To suggest a feature, report a bug, or general discussion, head over
75
+ [here](http://github.com/DataDog/dogstatsd-ruby/issues/).
76
+
77
+
78
+ [Change Log](CHANGELOG.md)
79
+ ----------------------------
80
+
81
+
82
+ Credits
83
+ -------
84
+
85
+ dogstatsd-ruby is forked from Rien Henrichs [original Statsd
86
+ client](https://github.com/reinh/statsd).
87
+
88
+ Copyright (c) 2011 Rein Henrichs. See LICENSE.txt for
89
+ further details.
@@ -0,0 +1,377 @@
1
+ require 'socket'
2
+
3
+ # = Dogstatsd: A Dogstatsd client (https://www.datadoghq.com)
4
+ #
5
+ # @example Set up a global Dogstatsd client for a server on localhost:8125
6
+ # require 'dogstatsd'
7
+ # $statsd = Dogstatsd.new 'localhost', 8125
8
+ # @example Send some stats
9
+ # $statsd.increment 'page.views'
10
+ # $statsd.timing 'page.load', 320
11
+ # $statsd.gauge 'users.online', 100
12
+ # @example Use {#time} to time the execution of a block
13
+ # $statsd.time('account.activate') { @account.activate! }
14
+ # @example Create a namespaced statsd client and increment 'account.activate'
15
+ # statsd = Dogstatsd.new 'localhost', 8125, :namespace => 'account'
16
+ # statsd.increment 'activate'
17
+ # @example Create a statsd client with global tags
18
+ # statsd = Dogstatsd.new 'localhost', 8125, :tags => 'tag1:true'
19
+ class Dogstatsd
20
+
21
+ VERSION = '2.0.0'
22
+
23
+ DEFAULT_HOST = '127.0.0.1'
24
+ DEFAULT_PORT = 8125
25
+
26
+ # Create a dictionary to assign a key to every parameter's name, except for tags (treated differently)
27
+ # Goal: Simple and fast to add some other parameters
28
+ OPTS_KEYS = [
29
+ ['date_happened', 'd'],
30
+ ['hostname', 'h'],
31
+ ['aggregation_key', 'k'],
32
+ ['priority', 'p'],
33
+ ['source_type_name', 's'],
34
+ ['alert_type', 't']
35
+ ]
36
+
37
+ # Service check options
38
+ SC_OPT_KEYS = [
39
+ ['timestamp', 'd:'],
40
+ ['hostname', 'h:'],
41
+ ['tags', '#'],
42
+ ['message', 'm:']
43
+ ]
44
+ OK = 0
45
+ WARNING = 1
46
+ CRITICAL = 2
47
+ UNKNOWN = 3
48
+
49
+ # A namespace to prepend to all statsd calls. Defaults to no namespace.
50
+ attr_reader :namespace
51
+
52
+ # StatsD host. Defaults to 127.0.0.1.
53
+ attr_reader :host
54
+
55
+ # StatsD port. Defaults to 8125.
56
+ attr_reader :port
57
+
58
+ # Global tags to be added to every statsd call. Defaults to no tags.
59
+ attr_reader :tags
60
+
61
+ # Buffer containing the statsd message before they are sent in batch
62
+ attr_reader :buffer
63
+
64
+ # Maximum number of metrics in the buffer before it is flushed
65
+ attr_accessor :max_buffer_size
66
+
67
+ class << self
68
+ # Set to a standard logger instance to enable debug logging.
69
+ attr_accessor :logger
70
+ end
71
+
72
+ # @param [String] host your statsd host
73
+ # @param [Integer] port your statsd port
74
+ # @option opts [String] :namespace set a namespace to be prepended to every metric name
75
+ # @option opts [Array<String>] :tags tags to be added to every metric
76
+ def initialize(host = DEFAULT_HOST, port = DEFAULT_PORT, opts = {}, max_buffer_size=50)
77
+ self.host, self.port = host, port
78
+ @prefix = nil
79
+ @socket = UDPSocket.new
80
+ self.namespace = opts[:namespace]
81
+ self.tags = opts[:tags]
82
+ @buffer = Array.new
83
+ self.max_buffer_size = max_buffer_size
84
+ alias :send_stat :send_to_socket
85
+ end
86
+
87
+ def namespace=(namespace) #:nodoc:
88
+ @namespace = namespace
89
+ @prefix = namespace.nil? ? nil : "#{namespace}."
90
+ end
91
+
92
+ def host=(host) #:nodoc:
93
+ @host = host || '127.0.0.1'
94
+ end
95
+
96
+ def port=(port) #:nodoc:
97
+ @port = port || 8125
98
+ end
99
+
100
+ def tags=(tags) #:nodoc:
101
+ @tags = tags || []
102
+ end
103
+
104
+ # Sends an increment (count = 1) for the given stat to the statsd server.
105
+ #
106
+ # @param [String] stat stat name
107
+ # @param [Hash] opts the options to create the metric with
108
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
109
+ # @option opts [Array<String>] :tags An array of tags
110
+ # @see #count
111
+ def increment(stat, opts={})
112
+ count stat, 1, opts
113
+ end
114
+
115
+ # Sends a decrement (count = -1) for the given stat to the statsd server.
116
+ #
117
+ # @param [String] stat stat name
118
+ # @param [Hash] opts the options to create the metric with
119
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
120
+ # @option opts [Array<String>] :tags An array of tags
121
+ # @see #count
122
+ def decrement(stat, opts={})
123
+ count stat, -1, opts
124
+ end
125
+
126
+ # Sends an arbitrary count for the given stat to the statsd server.
127
+ #
128
+ # @param [String] stat stat name
129
+ # @param [Integer] count count
130
+ # @param [Hash] opts the options to create the metric with
131
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
132
+ # @option opts [Array<String>] :tags An array of tags
133
+ def count(stat, count, opts={})
134
+ send_stats stat, count, :c, opts
135
+ end
136
+
137
+ # Sends an arbitary gauge value for the given stat to the statsd server.
138
+ #
139
+ # This is useful for recording things like available disk space,
140
+ # memory usage, and the like, which have different semantics than
141
+ # counters.
142
+ #
143
+ # @param [String] stat stat name.
144
+ # @param [Numeric] value gauge value.
145
+ # @param [Hash] opts the options to create the metric with
146
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
147
+ # @option opts [Array<String>] :tags An array of tags
148
+ # @example Report the current user count:
149
+ # $statsd.gauge('user.count', User.count)
150
+ def gauge(stat, value, opts={})
151
+ send_stats stat, value, :g, opts
152
+ end
153
+
154
+ # Sends a value to be tracked as a histogram to the statsd server.
155
+ #
156
+ # @param [String] stat stat name.
157
+ # @param [Numeric] value histogram value.
158
+ # @param [Hash] opts the options to create the metric with
159
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
160
+ # @option opts [Array<String>] :tags An array of tags
161
+ # @example Report the current user count:
162
+ # $statsd.histogram('user.count', User.count)
163
+ def histogram(stat, value, opts={})
164
+ send_stats stat, value, :h, opts
165
+ end
166
+
167
+ # Sends a timing (in ms) for the given stat to the statsd server. The
168
+ # sample_rate determines what percentage of the time this report is sent. The
169
+ # statsd server then uses the sample_rate to correctly track the average
170
+ # timing for the stat.
171
+ #
172
+ # @param [String] stat stat name
173
+ # @param [Integer] ms timing in milliseconds
174
+ # @param [Hash] opts the options to create the metric with
175
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
176
+ # @option opts [Array<String>] :tags An array of tags
177
+ def timing(stat, ms, opts={})
178
+ send_stats stat, ms, :ms, opts
179
+ end
180
+
181
+ # Reports execution time of the provided block using {#timing}.
182
+ #
183
+ # If the block fails, the stat is still reported, then the error
184
+ # is reraised
185
+ #
186
+ # @param [String] stat stat name
187
+ # @param [Hash] opts the options to create the metric with
188
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
189
+ # @option opts [Array<String>] :tags An array of tags
190
+ # @yield The operation to be timed
191
+ # @see #timing
192
+ # @example Report the time (in ms) taken to activate an account
193
+ # $statsd.time('account.activate') { @account.activate! }
194
+ def time(stat, opts={})
195
+ start = Time.now
196
+ result = yield
197
+ time_since(stat, start, opts)
198
+ result
199
+ rescue
200
+ time_since(stat, start, opts)
201
+ raise
202
+ end
203
+ # Sends a value to be tracked as a set to the statsd server.
204
+ #
205
+ # @param [String] stat stat name.
206
+ # @param [Numeric] value set value.
207
+ # @param [Hash] opts the options to create the metric with
208
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
209
+ # @option opts [Array<String>] :tags An array of tags
210
+ # @example Record a unique visitory by id:
211
+ # $statsd.set('visitors.uniques', User.id)
212
+ def set(stat, value, opts={})
213
+ send_stats stat, value, :s, opts
214
+ end
215
+
216
+
217
+ # This method allows you to send custom service check statuses.
218
+ #
219
+ # @param [String] name Service check name
220
+ # @param [String] status Service check status.
221
+ # @param [Hash] opts the additional data about the service check
222
+ # @option opts [Integer, nil] :timestamp (nil) Assign a timestamp to the event. Default is now when none
223
+ # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
224
+ # @option opts [Array<String>, nil] :tags (nil) An array of tags
225
+ # @option opts [String, nil] :message (nil) A message to associate with this service check status
226
+ # @example Report a critical service check status
227
+ # $statsd.service_check('my.service.check', Dogstatsd::CRITICAL, :tags=>['urgent'])
228
+ def service_check(name, status, opts={})
229
+ service_check_string = format_service_check(name, status, opts)
230
+ send_to_socket service_check_string
231
+ end
232
+ def format_service_check(name, status, opts={})
233
+ sc_string = "_sc|#{name}|#{status}"
234
+
235
+ SC_OPT_KEYS.each do |name_key|
236
+ if opts[name_key[0].to_sym]
237
+ if name_key[0] == 'tags'
238
+ tags = opts[:tags]
239
+ tags.each do |tag|
240
+ rm_pipes tag
241
+ end
242
+ tags = "#{tags.join(",")}" unless tags.empty?
243
+ sc_string << "|##{tags}"
244
+ elsif name_key[0] == 'message'
245
+ message = opts[:message]
246
+ rm_pipes message
247
+ escape_service_check_message message
248
+ sc_string << "|m:#{message}"
249
+ else
250
+ value = opts[name_key[0].to_sym]
251
+ rm_pipes value
252
+ sc_string << "|#{name_key[1]}#{value}"
253
+ end
254
+ end
255
+ end
256
+ return sc_string
257
+ end
258
+
259
+ # This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
260
+ #
261
+ # Aggregation in the stream is made on hostname/event_type/source_type/aggregation_key.
262
+ # If there's no event type, for example, then that won't matter;
263
+ # it will be grouped with other events that don't have an event type.
264
+ #
265
+ # @param [String] title Event title
266
+ # @param [String] text Event text. Supports \n
267
+ # @param [Hash] opts the additional data about the event
268
+ # @option opts [Integer, nil] :date_happened (nil) Assign a timestamp to the event. Default is now when none
269
+ # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
270
+ # @option opts [String, nil] :aggregation_key (nil) Assign an aggregation key to the event, to group it with some others
271
+ # @option opts [String, nil] :priority ('normal') Can be "normal" or "low"
272
+ # @option opts [String, nil] :source_type_name (nil) Assign a source type to the event
273
+ # @option opts [String, nil] :alert_type ('info') Can be "error", "warning", "info" or "success".
274
+ # @option opts [Array<String>] :tags tags to be added to every metric
275
+ # @example Report an awful event:
276
+ # $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
277
+ def event(title, text, opts={})
278
+ event_string = format_event(title, text, opts)
279
+ raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string.length > 8 * 1024
280
+
281
+ send_to_socket event_string
282
+ end
283
+
284
+ # Send several metrics in the same UDP Packet
285
+ # They will be buffered and flushed when the block finishes
286
+ #
287
+ # @example Send several metrics in one packet:
288
+ # $statsd.batch do |s|
289
+ # s.gauge('users.online',156)
290
+ # s.increment('page.views')
291
+ # end
292
+ def batch()
293
+ alias :send_stat :send_to_buffer
294
+ yield self
295
+ flush_buffer
296
+ alias :send_stat :send_to_socket
297
+ end
298
+
299
+ def format_event(title, text, opts={})
300
+ escape_event_content title
301
+ escape_event_content text
302
+ event_string_data = "_e{#{title.length},#{text.length}}:#{title}|#{text}"
303
+
304
+ # We construct the string to be sent by adding '|key:value' parts to it when needed
305
+ # All pipes ('|') in the metadata are removed. Title and Text can keep theirs
306
+ OPTS_KEYS.each do |name_key|
307
+ if name_key[0] != 'tags' && opts[name_key[0].to_sym]
308
+ value = opts[name_key[0].to_sym]
309
+ rm_pipes value
310
+ event_string_data << "|#{name_key[1]}:#{value}"
311
+ end
312
+ end
313
+ full_tags = tags + (opts[:tags] || [])
314
+ # Tags are joined and added as last part to the string to be sent
315
+ unless full_tags.empty?
316
+ full_tags.each do |tag|
317
+ rm_pipes tag
318
+ end
319
+ event_string_data << "|##{full_tags.join(',')}"
320
+ end
321
+
322
+ raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string_data.length > 8 * 1024
323
+ return event_string_data
324
+ end
325
+
326
+ private
327
+
328
+ def escape_event_content(msg)
329
+ msg.gsub! "\n", "\\n"
330
+ end
331
+
332
+ def rm_pipes(msg)
333
+ msg.gsub! "|", ""
334
+ end
335
+
336
+ def escape_service_check_message(msg)
337
+ msg.gsub! 'm:', 'm\:'
338
+ msg.gsub! "\n", "\\n"
339
+ end
340
+
341
+ def time_since(stat, start, opts)
342
+ timing(stat, ((Time.now - start) * 1000).round, opts)
343
+ end
344
+
345
+ def send_stats(stat, delta, type, opts={})
346
+ sample_rate = opts[:sample_rate] || 1
347
+ if sample_rate == 1 or rand < sample_rate
348
+ # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
349
+ stat = stat.to_s.gsub('::', '.').tr(':|@', '_')
350
+ rate = "|@#{sample_rate}" unless sample_rate == 1
351
+ ts = (tags || []) + (opts[:tags] || [])
352
+ tags = "|##{ts.join(",")}" unless ts.empty?
353
+ send_stat "#{@prefix}#{stat}:#{delta}|#{type}#{rate}#{tags}"
354
+ end
355
+ end
356
+
357
+ def send_to_buffer(message)
358
+ @buffer << message
359
+ if @buffer.length >= @max_buffer_size
360
+ flush_buffer
361
+ end
362
+ end
363
+
364
+ def flush_buffer()
365
+ send_to_socket(@buffer.join("\n"))
366
+ @buffer = Array.new
367
+ end
368
+
369
+ def send_to_socket(message)
370
+ self.class.logger.debug { "Dogstatsd: #{message}" } if self.class.logger
371
+ @socket.send(message, 0, @host, @port)
372
+ rescue => boom
373
+ self.class.logger.error { "Dogstatsd: #{boom.class} #{boom}" } if self.class.logger
374
+ nil
375
+ end
376
+ end
377
+
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dogstatsd
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Rein Henrichs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: yard
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.6.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.6.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: jeweler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A Ruby DogStastd client
70
+ email: code@datadoghq.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files:
74
+ - LICENSE.txt
75
+ - README.md
76
+ files:
77
+ - LICENSE.txt
78
+ - README.md
79
+ - lib/dogstatsd.rb
80
+ homepage: http://github.com/datadog/dogstatsd-ruby
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.4.5
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: A Ruby DogStatsd client
104
+ test_files: []
105
+ has_rdoc: