dogstatsd-ruby 1.6.0 → 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 +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