dogstatsd-ruby 1.6.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +13 -5
  2. data/README.md +6 -2
  3. data/lib/datadog/statsd.rb +381 -0
  4. metadata +14 -14
  5. data/lib/statsd.rb +0 -369
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 8524384a22231a55a17d5716136f4f5bf2f3bcf2
4
- data.tar.gz: 7e4c50e0ff11758392fba14d7008c48cdfd0c020
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZGI3OTFiN2FiNmZkY2JlODliZTliMDYyMzY2MWRlNTY0ZTJmZDU3Ng==
5
+ data.tar.gz: !binary |-
6
+ NDgwM2VhNzdmYWUxYWZhMzNmZDI3MTJkMTBlNDBhNGY0MzdkYjVkYw==
5
7
  SHA512:
6
- metadata.gz: 69db873d5dc0836a5614a9b5ab97188bffedddb8d840ee7a34bd50bfeba6a57f239a72526d92280bf64b8084b956c980815bb06fdf588bc76ad2250762f46036
7
- data.tar.gz: 397ed964d3ed6557a70991e9b32371ee8c020195d46c6b970ac46ed8afe338178c3cfa2e3c875649f0f373fbb61b4b0dafedcdcebc7eaaed883469bc5eb5af2c
8
+ metadata.gz: !binary |-
9
+ OTJhMWQ5OWQwYTEyNmJkODlkZGUwMmJkMDA1MGUzMGRmZTU3YjIyNjFjZDJk
10
+ ZTdmYTdhODdmMTJiYjlmOTUwYjVkYjFjN2RkMjU4YTY0MzZlZTgxMGU1ODEz
11
+ N2EwNmUzMmNmYWZlZjZjOTI2N2E3ZjliOWRiMzc1NTgxYmU3OWY=
12
+ data.tar.gz: !binary |-
13
+ ZWJlNzRjNDdkMDM1YzFjOGQzZGRjNGFjZDllMTc4MDdmNzUzZDJhNDc1Mjc2
14
+ M2M5MWE5ZjMyMDFiYjZlNWU4ZDAyZDA1ODMzNGZkYjg5YTAzMzg5NzhhMmRh
15
+ OTczN2UxYWE1ZGM0MGU0MTVkODgwYzVmYWJhNGIwMzU0NDEwYTY=
data/README.md CHANGED
@@ -17,10 +17,14 @@ Then start instrumenting your code:
17
17
 
18
18
  ``` ruby
19
19
  # Load the dogstats module.
20
- require 'statsd'
20
+ require 'datadog/statsd'
21
21
 
22
22
  # Create a stats instance.
23
- statsd = Statsd.new('localhost', 8125)
23
+ statsd = Datadog::Statsd.new('localhost', 8125)
24
+
25
+ # you could also create a statsd class if you need a drop in replacement
26
+ # class Statsd < Datadog::Statsd
27
+ # end
24
28
 
25
29
  # Increment a counter.
26
30
  statsd.increment('page.views')
@@ -0,0 +1,381 @@
1
+ require 'socket'
2
+
3
+ # = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
4
+ #
5
+ # @example Set up a global Statsd client for a server on localhost:8125
6
+ # require 'datadog/statsd'
7
+ # $statsd = Datadog::Statsd.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 = Datadog::Statsd.new 'localhost', 8125, :namespace => 'account'
16
+ # statsd.increment 'activate'
17
+ # @example Create a statsd client with global tags
18
+ # statsd = Datadog::Statsd.new 'localhost', 8125, :tags => 'tag1:true'
19
+ module Datadog
20
+ class Statsd
21
+
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:'],
39
+ ['hostname', 'h:'],
40
+ ['tags', '#'],
41
+ ['message', 'm:']
42
+ ]
43
+ OK = 0
44
+ WARNING = 1
45
+ CRITICAL = 2
46
+ UNKNOWN = 3
47
+
48
+ # A namespace to prepend to all statsd calls. Defaults to no namespace.
49
+ attr_reader :namespace
50
+
51
+ # StatsD host. Defaults to 127.0.0.1.
52
+ attr_reader :host
53
+
54
+ # StatsD port. Defaults to 8125.
55
+ attr_reader :port
56
+
57
+ # Global tags to be added to every statsd call. Defaults to no tags.
58
+ attr_reader :tags
59
+
60
+ # Buffer containing the statsd message before they are sent in batch
61
+ attr_reader :buffer
62
+
63
+ # Maximum number of metrics in the buffer before it is flushed
64
+ attr_accessor :max_buffer_size
65
+
66
+ class << self
67
+ # Set to a standard logger instance to enable debug logging.
68
+ attr_accessor :logger
69
+ end
70
+
71
+ # Return the current version of the library.
72
+ def self.VERSION
73
+ "2.0.0"
74
+ end
75
+
76
+ # @param [String] host your statsd host
77
+ # @param [Integer] port your statsd port
78
+ # @option opts [String] :namespace set a namespace to be prepended to every metric name
79
+ # @option opts [Array<String>] :tags tags to be added to every metric
80
+ def initialize(host = DEFAULT_HOST, port = DEFAULT_PORT, opts = {}, max_buffer_size=50)
81
+ self.host, self.port = host, port
82
+ @prefix = nil
83
+ @socket = UDPSocket.new
84
+ self.namespace = opts[:namespace]
85
+ self.tags = opts[:tags]
86
+ @buffer = Array.new
87
+ self.max_buffer_size = max_buffer_size
88
+ alias :send_stat :send_to_socket
89
+ end
90
+
91
+ def namespace=(namespace) #:nodoc:
92
+ @namespace = namespace
93
+ @prefix = namespace.nil? ? nil : "#{namespace}."
94
+ end
95
+
96
+ def host=(host) #:nodoc:
97
+ @host = host || '127.0.0.1'
98
+ end
99
+
100
+ def port=(port) #:nodoc:
101
+ @port = port || 8125
102
+ end
103
+
104
+ def tags=(tags) #:nodoc:
105
+ raise ArgumentError, 'tags must be a Array<String>' unless tags.nil? or tags.is_a? Array
106
+ @tags = (tags || []).map {|tag| escape_tag_content(tag)}
107
+ end
108
+
109
+ # Sends an increment (count = 1) for the given stat to the statsd server.
110
+ #
111
+ # @param [String] stat stat name
112
+ # @param [Hash] opts the options to create the metric with
113
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
114
+ # @option opts [Array<String>] :tags An array of tags
115
+ # @see #count
116
+ def increment(stat, opts={})
117
+ count stat, 1, opts
118
+ end
119
+
120
+ # Sends a decrement (count = -1) for the given stat to the statsd server.
121
+ #
122
+ # @param [String] stat stat name
123
+ # @param [Hash] opts the options to create the metric with
124
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
125
+ # @option opts [Array<String>] :tags An array of tags
126
+ # @see #count
127
+ def decrement(stat, opts={})
128
+ count stat, -1, opts
129
+ end
130
+
131
+ # Sends an arbitrary count for the given stat to the statsd server.
132
+ #
133
+ # @param [String] stat stat name
134
+ # @param [Integer] count count
135
+ # @param [Hash] opts the options to create the metric with
136
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
137
+ # @option opts [Array<String>] :tags An array of tags
138
+ def count(stat, count, opts={})
139
+ send_stats stat, count, :c, opts
140
+ end
141
+
142
+ # Sends an arbitary gauge value for the given stat to the statsd server.
143
+ #
144
+ # This is useful for recording things like available disk space,
145
+ # memory usage, and the like, which have different semantics than
146
+ # counters.
147
+ #
148
+ # @param [String] stat stat name.
149
+ # @param [Numeric] value gauge value.
150
+ # @param [Hash] opts the options to create the metric with
151
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
152
+ # @option opts [Array<String>] :tags An array of tags
153
+ # @example Report the current user count:
154
+ # $statsd.gauge('user.count', User.count)
155
+ def gauge(stat, value, opts={})
156
+ send_stats stat, value, :g, opts
157
+ end
158
+
159
+ # Sends a value to be tracked as a histogram to the statsd server.
160
+ #
161
+ # @param [String] stat stat name.
162
+ # @param [Numeric] value histogram value.
163
+ # @param [Hash] opts the options to create the metric with
164
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
165
+ # @option opts [Array<String>] :tags An array of tags
166
+ # @example Report the current user count:
167
+ # $statsd.histogram('user.count', User.count)
168
+ def histogram(stat, value, opts={})
169
+ send_stats stat, value, :h, opts
170
+ end
171
+
172
+ # Sends a timing (in ms) for the given stat to the statsd server. The
173
+ # sample_rate determines what percentage of the time this report is sent. The
174
+ # statsd server then uses the sample_rate to correctly track the average
175
+ # timing for the stat.
176
+ #
177
+ # @param [String] stat stat name
178
+ # @param [Integer] ms timing in milliseconds
179
+ # @param [Hash] opts the options to create the metric with
180
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
181
+ # @option opts [Array<String>] :tags An array of tags
182
+ def timing(stat, ms, opts={})
183
+ send_stats stat, ms, :ms, opts
184
+ end
185
+
186
+ # Reports execution time of the provided block using {#timing}.
187
+ #
188
+ # If the block fails, the stat is still reported, then the error
189
+ # is reraised
190
+ #
191
+ # @param [String] stat stat name
192
+ # @param [Hash] opts the options to create the metric with
193
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
194
+ # @option opts [Array<String>] :tags An array of tags
195
+ # @yield The operation to be timed
196
+ # @see #timing
197
+ # @example Report the time (in ms) taken to activate an account
198
+ # $statsd.time('account.activate') { @account.activate! }
199
+ def time(stat, opts={})
200
+ start = Time.now
201
+ result = yield
202
+ time_since(stat, start, opts)
203
+ result
204
+ rescue
205
+ time_since(stat, start, opts)
206
+ raise
207
+ end
208
+ # Sends a value to be tracked as a set to the statsd server.
209
+ #
210
+ # @param [String] stat stat name.
211
+ # @param [Numeric] value set value.
212
+ # @param [Hash] opts the options to create the metric with
213
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
214
+ # @option opts [Array<String>] :tags An array of tags
215
+ # @example Record a unique visitory by id:
216
+ # $statsd.set('visitors.uniques', User.id)
217
+ def set(stat, value, opts={})
218
+ send_stats stat, value, :s, opts
219
+ end
220
+
221
+
222
+ # This method allows you to send custom service check statuses.
223
+ #
224
+ # @param [String] name Service check name
225
+ # @param [String] status Service check status.
226
+ # @param [Hash] opts the additional data about the service check
227
+ # @option opts [Integer, nil] :timestamp (nil) Assign a timestamp to the event. Default is now when none
228
+ # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
229
+ # @option opts [Array<String>, nil] :tags (nil) An array of tags
230
+ # @option opts [String, nil] :message (nil) A message to associate with this service check status
231
+ # @example Report a critical service check status
232
+ # $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
233
+ def service_check(name, status, opts={})
234
+ service_check_string = format_service_check(name, status, opts)
235
+ send_to_socket service_check_string
236
+ end
237
+ def format_service_check(name, status, opts={})
238
+ sc_string = "_sc|#{name}|#{status}"
239
+
240
+ SC_OPT_KEYS.each do |name_key|
241
+ if opts[name_key[0].to_sym]
242
+ if name_key[0] == 'tags'
243
+ tags = opts[:tags].map {|tag| escape_tag_content(tag) }
244
+ tags = "#{tags.join(",")}" unless tags.empty?
245
+ sc_string << "|##{tags}"
246
+ elsif name_key[0] == 'message'
247
+ message = remove_pipes(opts[:message])
248
+ escaped_message = escape_service_check_message(message)
249
+ sc_string << "|m:#{escaped_message}"
250
+ else
251
+ value = remove_pipes(opts[name_key[0].to_sym])
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
+ escaped_title = escape_event_content(title)
301
+ escaped_text = escape_event_content(text)
302
+ event_string_data = "_e{#{escaped_title.length},#{escaped_text.length}}:#{escaped_title}|#{escaped_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 = remove_pipes(opts[name_key[0].to_sym])
309
+ event_string_data << "|#{name_key[1]}:#{value}"
310
+ end
311
+ end
312
+ # Tags are joined and added as last part to the string to be sent
313
+ full_tags = (tags + (opts[:tags] || [])).map {|tag| escape_tag_content(tag) }
314
+ unless full_tags.empty?
315
+ event_string_data << "|##{full_tags.join(',')}"
316
+ end
317
+
318
+ raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string_data.length > 8 * 1024
319
+ return event_string_data
320
+ end
321
+
322
+ private
323
+
324
+ def escape_event_content(msg)
325
+ msg.gsub "\n", "\\n"
326
+ end
327
+
328
+ def escape_tag_content(tag)
329
+ remove_pipes(tag).gsub ",", ""
330
+ end
331
+
332
+ def remove_pipes(msg)
333
+ msg.gsub "|", ""
334
+ end
335
+
336
+ def escape_service_check_message(msg)
337
+ msg.gsub('m:', 'm\:').gsub "\n", "\\n"
338
+ end
339
+
340
+ def time_since(stat, start, opts)
341
+ timing(stat, ((Time.now - start) * 1000).round, opts)
342
+ end
343
+
344
+ def send_stats(stat, delta, type, opts={})
345
+ sample_rate = opts[:sample_rate] || 1
346
+ if sample_rate == 1 or rand < sample_rate
347
+ # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
348
+ stat = stat.to_s.gsub('::', '.').tr(':|@', '_')
349
+ rate = "|@#{sample_rate}" unless sample_rate == 1
350
+ ts = (tags || []) + (opts[:tags] || []).map {|tag| escape_tag_content(tag)}
351
+ tags = "|##{ts.join(",")}" unless ts.empty?
352
+ send_stat "#{@prefix}#{stat}:#{delta}|#{type}#{rate}#{tags}"
353
+ end
354
+ end
355
+
356
+ def send_to_buffer(message)
357
+ @buffer << message
358
+ if @buffer.length >= @max_buffer_size
359
+ flush_buffer
360
+ end
361
+ end
362
+
363
+ def flush_buffer()
364
+ send_to_socket(@buffer.join("\n"))
365
+ @buffer = Array.new
366
+ end
367
+
368
+ def send_to_socket(message)
369
+ self.class.logger.debug { "Statsd: #{message}" } if self.class.logger
370
+ @socket.send(message, 0, @host, @port)
371
+ rescue => boom
372
+ self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
373
+ nil
374
+ end
375
+
376
+ # Close the underlying socket
377
+ def close()
378
+ @socket.close
379
+ end
380
+ end
381
+ end
metadata CHANGED
@@ -1,69 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dogstatsd-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rein Henrichs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-21 00:00:00.000000000 Z
11
+ date: 2016-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - ! '>='
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - ! '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: yard
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ~>
32
32
  - !ruby/object:Gem::Version
33
33
  version: 0.6.0
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ~>
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.6.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: jeweler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ~>
46
46
  - !ruby/object:Gem::Version
47
47
  version: '1.8'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ~>
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.8'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: simplecov
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - ! '>='
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  description: A Ruby DogStastd client
@@ -76,7 +76,7 @@ extra_rdoc_files:
76
76
  files:
77
77
  - LICENSE.txt
78
78
  - README.md
79
- - lib/statsd.rb
79
+ - lib/datadog/statsd.rb
80
80
  homepage: http://github.com/datadog/dogstatsd-ruby
81
81
  licenses:
82
82
  - MIT
@@ -87,17 +87,17 @@ require_paths:
87
87
  - lib
88
88
  required_ruby_version: !ruby/object:Gem::Requirement
89
89
  requirements:
90
- - - ">="
90
+ - - ! '>='
91
91
  - !ruby/object:Gem::Version
92
92
  version: '0'
93
93
  required_rubygems_version: !ruby/object:Gem::Requirement
94
94
  requirements:
95
- - - ">="
95
+ - - ! '>='
96
96
  - !ruby/object:Gem::Version
97
97
  version: '0'
98
98
  requirements: []
99
99
  rubyforge_project:
100
- rubygems_version: 2.4.8
100
+ rubygems_version: 2.4.5
101
101
  signing_key:
102
102
  specification_version: 4
103
103
  summary: A Ruby DogStatsd client
@@ -1,369 +0,0 @@
1
- require 'socket'
2
-
3
- # = Statsd: A DogStatsd client (https://www.datadoghq.com)
4
- #
5
- # @example Set up a global Statsd client for a server on localhost:8125
6
- # require 'statsd'
7
- # $statsd = Statsd.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 = Statsd.new 'localhost', 8125, :namespace => 'account'
16
- # statsd.increment 'activate'
17
- # @example Create a statsd client with global tags
18
- # statsd = Statsd.new 'localhost', 8125, :tags => 'tag1:true'
19
- class Statsd
20
-
21
- DEFAULT_HOST = '127.0.0.1'
22
- DEFAULT_PORT = 8125
23
-
24
- # Create a dictionary to assign a key to every parameter's name, except for tags (treated differently)
25
- # Goal: Simple and fast to add some other parameters
26
- OPTS_KEYS = [
27
- ['date_happened', 'd'],
28
- ['hostname', 'h'],
29
- ['aggregation_key', 'k'],
30
- ['priority', 'p'],
31
- ['source_type_name', 's'],
32
- ['alert_type', 't']
33
- ]
34
-
35
- # Service check options
36
- SC_OPT_KEYS = [
37
- ['timestamp', 'd:'],
38
- ['hostname', 'h:'],
39
- ['tags', '#'],
40
- ['message', 'm:']
41
- ]
42
- OK = 0
43
- WARNING = 1
44
- CRITICAL = 2
45
- UNKNOWN = 3
46
-
47
- # A namespace to prepend to all statsd calls. Defaults to no namespace.
48
- attr_reader :namespace
49
-
50
- # StatsD host. Defaults to 127.0.0.1.
51
- attr_reader :host
52
-
53
- # StatsD port. Defaults to 8125.
54
- attr_reader :port
55
-
56
- # Global tags to be added to every statsd call. Defaults to no tags.
57
- attr_reader :tags
58
-
59
- # Buffer containing the statsd message before they are sent in batch
60
- attr_reader :buffer
61
-
62
- # Maximum number of metrics in the buffer before it is flushed
63
- attr_accessor :max_buffer_size
64
-
65
- class << self
66
- # Set to a standard logger instance to enable debug logging.
67
- attr_accessor :logger
68
- end
69
-
70
- # Return the current version of the library.
71
- def self.VERSION
72
- "1.6.0"
73
- end
74
-
75
- # @param [String] host your statsd host
76
- # @param [Integer] port your statsd port
77
- # @option opts [String] :namespace set a namespace to be prepended to every metric name
78
- # @option opts [Array<String>] :tags tags to be added to every metric
79
- def initialize(host = DEFAULT_HOST, port = DEFAULT_PORT, opts = {}, max_buffer_size=50)
80
- self.host, self.port = host, port
81
- @prefix = nil
82
- @socket = UDPSocket.new
83
- self.namespace = opts[:namespace]
84
- self.tags = opts[:tags]
85
- @buffer = Array.new
86
- self.max_buffer_size = max_buffer_size
87
- alias :send_stat :send_to_socket
88
- end
89
-
90
- def namespace=(namespace) #:nodoc:
91
- @namespace = namespace
92
- @prefix = namespace.nil? ? nil : "#{namespace}."
93
- end
94
-
95
- def host=(host) #:nodoc:
96
- @host = host || '127.0.0.1'
97
- end
98
-
99
- def port=(port) #:nodoc:
100
- @port = port || 8125
101
- end
102
-
103
- def tags=(tags) #:nodoc:
104
- @tags = tags || []
105
- end
106
-
107
- # Sends an increment (count = 1) for the given stat to the statsd server.
108
- #
109
- # @param [String] stat stat name
110
- # @param [Hash] opts the options to create the metric with
111
- # @option opts [Numeric] :sample_rate sample rate, 1 for always
112
- # @option opts [Array<String>] :tags An array of tags
113
- # @see #count
114
- def increment(stat, opts={})
115
- count stat, 1, opts
116
- end
117
-
118
- # Sends a decrement (count = -1) for the given stat to the statsd server.
119
- #
120
- # @param [String] stat stat name
121
- # @param [Hash] opts the options to create the metric with
122
- # @option opts [Numeric] :sample_rate sample rate, 1 for always
123
- # @option opts [Array<String>] :tags An array of tags
124
- # @see #count
125
- def decrement(stat, opts={})
126
- count stat, -1, opts
127
- end
128
-
129
- # Sends an arbitrary count for the given stat to the statsd server.
130
- #
131
- # @param [String] stat stat name
132
- # @param [Integer] count count
133
- # @param [Hash] opts the options to create the metric with
134
- # @option opts [Numeric] :sample_rate sample rate, 1 for always
135
- # @option opts [Array<String>] :tags An array of tags
136
- def count(stat, count, opts={})
137
- send_stats stat, count, :c, opts
138
- end
139
-
140
- # Sends an arbitary gauge value for the given stat to the statsd server.
141
- #
142
- # This is useful for recording things like available disk space,
143
- # memory usage, and the like, which have different semantics than
144
- # counters.
145
- #
146
- # @param [String] stat stat name.
147
- # @param [Numeric] value gauge value.
148
- # @param [Hash] opts the options to create the metric with
149
- # @option opts [Numeric] :sample_rate sample rate, 1 for always
150
- # @option opts [Array<String>] :tags An array of tags
151
- # @example Report the current user count:
152
- # $statsd.gauge('user.count', User.count)
153
- def gauge(stat, value, opts={})
154
- send_stats stat, value, :g, opts
155
- end
156
-
157
- # Sends a value to be tracked as a histogram to the statsd server.
158
- #
159
- # @param [String] stat stat name.
160
- # @param [Numeric] value histogram value.
161
- # @param [Hash] opts the options to create the metric with
162
- # @option opts [Numeric] :sample_rate sample rate, 1 for always
163
- # @option opts [Array<String>] :tags An array of tags
164
- # @example Report the current user count:
165
- # $statsd.histogram('user.count', User.count)
166
- def histogram(stat, value, opts={})
167
- send_stats stat, value, :h, opts
168
- end
169
-
170
- # Sends a timing (in ms) for the given stat to the statsd server. The
171
- # sample_rate determines what percentage of the time this report is sent. The
172
- # statsd server then uses the sample_rate to correctly track the average
173
- # timing for the stat.
174
- #
175
- # @param [String] stat stat name
176
- # @param [Integer] ms timing in milliseconds
177
- # @param [Hash] opts the options to create the metric with
178
- # @option opts [Numeric] :sample_rate sample rate, 1 for always
179
- # @option opts [Array<String>] :tags An array of tags
180
- def timing(stat, ms, opts={})
181
- send_stats stat, ms, :ms, opts
182
- end
183
-
184
- # Reports execution time of the provided block using {#timing}.
185
- #
186
- # If the block fails, the stat is still reported, then the error
187
- # is reraised
188
- #
189
- # @param [String] stat stat name
190
- # @param [Hash] opts the options to create the metric with
191
- # @option opts [Numeric] :sample_rate sample rate, 1 for always
192
- # @option opts [Array<String>] :tags An array of tags
193
- # @yield The operation to be timed
194
- # @see #timing
195
- # @example Report the time (in ms) taken to activate an account
196
- # $statsd.time('account.activate') { @account.activate! }
197
- def time(stat, opts={})
198
- start = Time.now
199
- result = yield
200
- time_since(stat, start, opts)
201
- result
202
- rescue
203
- time_since(stat, start, opts)
204
- raise
205
- end
206
- # Sends a value to be tracked as a set to the statsd server.
207
- #
208
- # @param [String] stat stat name.
209
- # @param [Numeric] value set value.
210
- # @param [Hash] opts the options to create the metric with
211
- # @option opts [Numeric] :sample_rate sample rate, 1 for always
212
- # @option opts [Array<String>] :tags An array of tags
213
- # @example Record a unique visitory by id:
214
- # $statsd.set('visitors.uniques', User.id)
215
- def set(stat, value, opts={})
216
- send_stats stat, value, :s, opts
217
- end
218
-
219
-
220
- # This method allows you to send custom service check statuses.
221
- #
222
- # @param [String] name Service check name
223
- # @param [String] status Service check status.
224
- # @param [Hash] opts the additional data about the service check
225
- # @option opts [Integer, nil] :timestamp (nil) Assign a timestamp to the event. Default is now when none
226
- # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
227
- # @option opts [Array<String>, nil] :tags (nil) An array of tags
228
- # @option opts [String, nil] :message (nil) A message to associate with this service check status
229
- # @example Report a critical service check status
230
- # $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
231
- def service_check(name, status, opts={})
232
- service_check_string = format_service_check(name, status, opts)
233
- send_to_socket service_check_string
234
- end
235
- def format_service_check(name, status, opts={})
236
- sc_string = "_sc|#{name}|#{status}"
237
-
238
- SC_OPT_KEYS.each do |name_key|
239
- if opts[name_key[0].to_sym]
240
- if name_key[0] == 'tags'
241
- tags = opts[:tags].map {|tag| remove_pipes(tag) }
242
- tags = "#{tags.join(",")}" unless tags.empty?
243
- sc_string << "|##{tags}"
244
- elsif name_key[0] == 'message'
245
- message = remove_pipes(opts[:message])
246
- escaped_message = escape_service_check_message(message)
247
- sc_string << "|m:#{escaped_message}"
248
- else
249
- value = remove_pipes(opts[name_key[0].to_sym])
250
- sc_string << "|#{name_key[1]}#{value}"
251
- end
252
- end
253
- end
254
- return sc_string
255
- end
256
-
257
- # This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
258
- #
259
- # Aggregation in the stream is made on hostname/event_type/source_type/aggregation_key.
260
- # If there's no event type, for example, then that won't matter;
261
- # it will be grouped with other events that don't have an event type.
262
- #
263
- # @param [String] title Event title
264
- # @param [String] text Event text. Supports \n
265
- # @param [Hash] opts the additional data about the event
266
- # @option opts [Integer, nil] :date_happened (nil) Assign a timestamp to the event. Default is now when none
267
- # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
268
- # @option opts [String, nil] :aggregation_key (nil) Assign an aggregation key to the event, to group it with some others
269
- # @option opts [String, nil] :priority ('normal') Can be "normal" or "low"
270
- # @option opts [String, nil] :source_type_name (nil) Assign a source type to the event
271
- # @option opts [String, nil] :alert_type ('info') Can be "error", "warning", "info" or "success".
272
- # @option opts [Array<String>] :tags tags to be added to every metric
273
- # @example Report an awful event:
274
- # $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
275
- def event(title, text, opts={})
276
- event_string = format_event(title, text, opts)
277
- raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string.length > 8 * 1024
278
-
279
- send_to_socket event_string
280
- end
281
-
282
- # Send several metrics in the same UDP Packet
283
- # They will be buffered and flushed when the block finishes
284
- #
285
- # @example Send several metrics in one packet:
286
- # $statsd.batch do |s|
287
- # s.gauge('users.online',156)
288
- # s.increment('page.views')
289
- # end
290
- def batch()
291
- alias :send_stat :send_to_buffer
292
- yield self
293
- flush_buffer
294
- alias :send_stat :send_to_socket
295
- end
296
-
297
- def format_event(title, text, opts={})
298
- escaped_title = escape_event_content(title)
299
- escaped_text = escape_event_content(text)
300
- event_string_data = "_e{#{escaped_title.length},#{escaped_text.length}}:#{escaped_title}|#{escaped_text}"
301
-
302
- # We construct the string to be sent by adding '|key:value' parts to it when needed
303
- # All pipes ('|') in the metadata are removed. Title and Text can keep theirs
304
- OPTS_KEYS.each do |name_key|
305
- if name_key[0] != 'tags' && opts[name_key[0].to_sym]
306
- value = remove_pipes(opts[name_key[0].to_sym])
307
- event_string_data << "|#{name_key[1]}:#{value}"
308
- end
309
- end
310
- # Tags are joined and added as last part to the string to be sent
311
- full_tags = (tags + (opts[:tags] || [])).map {|tag| remove_pipes(tag) }
312
- unless full_tags.empty?
313
- event_string_data << "|##{full_tags.join(',')}"
314
- end
315
-
316
- raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string_data.length > 8 * 1024
317
- return event_string_data
318
- end
319
-
320
- private
321
-
322
- def escape_event_content(msg)
323
- msg.gsub "\n", "\\n"
324
- end
325
-
326
- def remove_pipes(msg)
327
- msg.gsub "|", ""
328
- end
329
-
330
- def escape_service_check_message(msg)
331
- msg.gsub('m:', 'm\:').gsub "\n", "\\n"
332
- end
333
-
334
- def time_since(stat, start, opts)
335
- timing(stat, ((Time.now - start) * 1000).round, opts)
336
- end
337
-
338
- def send_stats(stat, delta, type, opts={})
339
- sample_rate = opts[:sample_rate] || 1
340
- if sample_rate == 1 or rand < sample_rate
341
- # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
342
- stat = stat.to_s.gsub('::', '.').tr(':|@', '_')
343
- rate = "|@#{sample_rate}" unless sample_rate == 1
344
- ts = (tags || []) + (opts[:tags] || [])
345
- tags = "|##{ts.join(",")}" unless ts.empty?
346
- send_stat "#{@prefix}#{stat}:#{delta}|#{type}#{rate}#{tags}"
347
- end
348
- end
349
-
350
- def send_to_buffer(message)
351
- @buffer << message
352
- if @buffer.length >= @max_buffer_size
353
- flush_buffer
354
- end
355
- end
356
-
357
- def flush_buffer()
358
- send_to_socket(@buffer.join("\n"))
359
- @buffer = Array.new
360
- end
361
-
362
- def send_to_socket(message)
363
- self.class.logger.debug { "Statsd: #{message}" } if self.class.logger
364
- @socket.send(message, 0, @host, @port)
365
- rescue => boom
366
- self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
367
- nil
368
- end
369
- end