statsd-ruby 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,4 +1,4 @@
1
- = statsd-ruby {<img src="https://secure.travis-ci.org/reinh/statsd-ruby.png" />}[http://travis-ci.org/reinh/statsd-ruby]
1
+ = statsd-ruby {<img src="https://secure.travis-ci.org/reinh/statsd.png" />}[http://travis-ci.org/reinh/statsd]
2
2
 
3
3
  A Ruby client for {StatsD}[https://github.com/etsy/statsd]
4
4
 
@@ -7,6 +7,23 @@ A Ruby client for {StatsD}[https://github.com/etsy/statsd]
7
7
  Bundler:
8
8
  gem "statsd-ruby", :require => "statsd"
9
9
 
10
+ = Basic Usage
11
+
12
+ # Set up a global Statsd client for a server on localhost:9125
13
+ $statsd = Statsd.new 'localhost', 9125
14
+
15
+ # Send some stats
16
+ $statsd.increment 'garets'
17
+ $statsd.timing 'glork', 320
18
+ $statsd.gauge 'bork', 100
19
+
20
+ # Use {#time} to time the execution of a block
21
+ $statsd.time('account.activate') { @account.activate! }
22
+
23
+ # Create a namespaced statsd client and increment 'account.activate'
24
+ statsd = Statsd.new('localhost').tap{|sd| sd.namespace = 'account'}
25
+ statsd.increment 'activate'
26
+
10
27
  = Testing
11
28
 
12
29
  Run the specs with <tt>rake spec</tt>
data/lib/statsd.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'socket'
2
+ require 'forwardable'
2
3
 
3
4
  # = Statsd: A Statsd client (https://github.com/etsy/statsd)
4
5
  #
@@ -21,6 +22,68 @@ require 'socket'
21
22
  # users either mutex around their Statsd object, or create separate objects for
22
23
  # each namespace / host+port combination.
23
24
  class Statsd
25
+
26
+ # = Batch: A batching statsd proxy
27
+ #
28
+ # @example Batch a set of instruments using Batch and manual flush:
29
+ # $statsd = Statsd.new 'localhost', 8125
30
+ # batch = Statsd::Batch.new($statsd)
31
+ # batch.increment 'garets'
32
+ # batch.timing 'glork', 320
33
+ # batch.gauge 'bork', 100
34
+ # batch.flush
35
+ #
36
+ # Batch is a subclass of Statsd, but with a constructor that proxies to a
37
+ # normal Statsd instance. It has it's own batch_size and namespace parameters
38
+ # (that inherit defaults from the supplied Statsd instance). It is recommended
39
+ # that some care is taken if setting very large batch sizes. If the batch size
40
+ # exceeds the allowed packet size for UDP on your network, communication
41
+ # troubles may occur and data will be lost.
42
+ class Batch < Statsd
43
+
44
+ extend Forwardable
45
+ def_delegators :@statsd,
46
+ :namespace, :namespace=, :host, :port, :prefix, :postfix
47
+
48
+ attr_accessor :batch_size
49
+
50
+ # @param [Statsd] requires a configured Statsd instance
51
+ def initialize(statsd)
52
+ @statsd = statsd
53
+ @batch_size = statsd.batch_size
54
+ @backlog = []
55
+ end
56
+
57
+ # @yields [Batch] yields itself
58
+ #
59
+ # A convenience method to ensure that data is not lost in the event of an
60
+ # exception being thrown. Batches will be transmitted on the parent socket
61
+ # as soon as the batch is full, and when the block finishes.
62
+ def easy
63
+ yield self
64
+ ensure
65
+ flush
66
+ end
67
+
68
+ def flush
69
+ unless @backlog.empty?
70
+ @statsd.send_to_socket @backlog.join("\n")
71
+ @backlog.clear
72
+ end
73
+ end
74
+
75
+ protected
76
+
77
+ def send_to_socket(message)
78
+ @backlog << message
79
+ if @backlog.size >= @batch_size
80
+ flush
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+
24
87
  # A namespace to prepend to all statsd calls.
25
88
  attr_reader :namespace
26
89
 
@@ -30,6 +93,15 @@ class Statsd
30
93
  # StatsD port. Defaults to 8125.
31
94
  attr_reader :port
32
95
 
96
+ # StatsD namespace prefix, generated from #namespace
97
+ attr_reader :prefix
98
+
99
+ # The default batch size for new batches (default: 10)
100
+ attr_accessor :batch_size
101
+
102
+ # a postfix to append to all metrics
103
+ attr_reader :postfix
104
+
33
105
  class << self
34
106
  # Set to a standard logger instance to enable debug logging.
35
107
  attr_accessor :logger
@@ -40,6 +112,8 @@ class Statsd
40
112
  def initialize(host = '127.0.0.1', port = 8125)
41
113
  self.host, self.port = host, port
42
114
  @prefix = nil
115
+ @batch_size = 10
116
+ @postfix = nil
43
117
  end
44
118
 
45
119
  # @attribute [w] namespace
@@ -49,6 +123,10 @@ class Statsd
49
123
  @prefix = "#{namespace}."
50
124
  end
51
125
 
126
+ def postfix=(pf)
127
+ @postfix = ".#{pf}"
128
+ end
129
+
52
130
  # @attribute [w] host
53
131
  # Writes are not thread safe.
54
132
  def host=(host)
@@ -95,7 +173,7 @@ class Statsd
95
173
  # counters.
96
174
  #
97
175
  # @param [String] stat stat name.
98
- # @param [Numeric] gauge value.
176
+ # @param [Numeric] value gauge value.
99
177
  # @param [Numeric] sample_rate sample rate, 1 for always
100
178
  # @example Report the current user count:
101
179
  # $statsd.gauge('user.count', User.count)
@@ -130,17 +208,22 @@ class Statsd
130
208
  result
131
209
  end
132
210
 
133
- private
134
-
135
- def send_stats(stat, delta, type, sample_rate=1)
136
- if sample_rate == 1 or rand < sample_rate
137
- # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
138
- stat = stat.to_s.gsub('::', '.').tr(':|@', '_')
139
- rate = "|@#{sample_rate}" unless sample_rate == 1
140
- send_to_socket "#{@prefix}#{stat}:#{delta}|#{type}#{rate}"
141
- end
211
+ # Creates and yields a Batch that can be used to batch instrument reports into
212
+ # larger packets. Batches are sent either when the packet is "full" (defined
213
+ # by batch_size), or when the block completes, whichever is the sooner.
214
+ #
215
+ # @yield [Batch] a statsd subclass that collects and batches instruments
216
+ # @example Batch two instument operations:
217
+ # $statsd.batch do |batch|
218
+ # batch.increment 'sys.requests'
219
+ # batch.gauge('user.count', User.count)
220
+ # end
221
+ def batch(&block)
222
+ Batch.new(self).easy &block
142
223
  end
143
224
 
225
+ protected
226
+
144
227
  def send_to_socket(message)
145
228
  self.class.logger.debug { "Statsd: #{message}" } if self.class.logger
146
229
  socket.send(message, 0, @host, @port)
@@ -149,6 +232,17 @@ class Statsd
149
232
  nil
150
233
  end
151
234
 
235
+ private
236
+
237
+ def send_stats(stat, delta, type, sample_rate=1)
238
+ if sample_rate == 1 or rand < sample_rate
239
+ # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
240
+ stat = stat.to_s.gsub('::', '.').tr(':|@', '_')
241
+ rate = "|@#{sample_rate}" unless sample_rate == 1
242
+ send_to_socket "#{prefix}#{stat}#{postfix}:#{delta}|#{type}#{rate}"
243
+ end
244
+ end
245
+
152
246
  def socket
153
247
  Thread.current[:statsd_socket] ||= UDPSocket.new
154
248
  end
data/spec/helper.rb CHANGED
@@ -17,7 +17,7 @@ class FakeUDPSocket
17
17
  end
18
18
 
19
19
  def recv
20
- res = @buffer.shift
20
+ @buffer.shift
21
21
  end
22
22
 
23
23
  def clear
data/spec/statsd_spec.rb CHANGED
@@ -113,12 +113,12 @@ describe Statsd do
113
113
 
114
114
  describe "#time" do
115
115
  it "should format the message according to the statsd spec" do
116
- @statsd.time('foobar') { sleep(0.001); 'test' }
117
- @socket.recv.must_equal ['foobar:1|ms']
116
+ @statsd.time('foobar') { 'test' }
117
+ @socket.recv.must_equal ['foobar:0|ms']
118
118
  end
119
119
 
120
120
  it "should return the result of the block" do
121
- result = @statsd.time('foobar') { sleep(0.001); 'test' }
121
+ result = @statsd.time('foobar') { 'test' }
122
122
  result.must_equal 'test'
123
123
  end
124
124
 
@@ -126,8 +126,8 @@ describe Statsd do
126
126
  before { class << @statsd; def rand; 0; end; end } # ensure delivery
127
127
 
128
128
  it "should format the message according to the statsd spec" do
129
- result = @statsd.time('foobar', 0.5) { sleep(0.001); 'test' }
130
- @socket.recv.must_equal ['foobar:1|ms|@0.5']
129
+ @statsd.time('foobar', 0.5) { 'test' }
130
+ @socket.recv.must_equal ['foobar:0|ms|@0.5']
131
131
  end
132
132
  end
133
133
  end
@@ -189,6 +189,30 @@ describe Statsd do
189
189
  end
190
190
  end
191
191
 
192
+ describe "with postfix" do
193
+ before { @statsd.postfix = 'ip-23-45-56-78' }
194
+
195
+ it "should add postfix to increment" do
196
+ @statsd.increment('foobar')
197
+ @socket.recv.must_equal ['foobar.ip-23-45-56-78:1|c']
198
+ end
199
+
200
+ it "should add postfix to decrement" do
201
+ @statsd.decrement('foobar')
202
+ @socket.recv.must_equal ['foobar.ip-23-45-56-78:-1|c']
203
+ end
204
+
205
+ it "should add namespace to timing" do
206
+ @statsd.timing('foobar', 500)
207
+ @socket.recv.must_equal ['foobar.ip-23-45-56-78:500|ms']
208
+ end
209
+
210
+ it "should add namespace to gauge" do
211
+ @statsd.gauge('foobar', 500)
212
+ @socket.recv.must_equal ['foobar.ip-23-45-56-78:500|g']
213
+ end
214
+ end
215
+
192
216
  describe "with logging" do
193
217
  require 'stringio'
194
218
  before { Statsd.logger = Logger.new(@log = StringIO.new)}
@@ -245,6 +269,45 @@ describe Statsd do
245
269
  end
246
270
  end
247
271
 
272
+ describe "batching" do
273
+ it "should have a default batch size of 10" do
274
+ @statsd.batch_size.must_equal 10
275
+ end
276
+
277
+ it "should have a modifiable batch size" do
278
+ @statsd.batch_size = 7
279
+ @statsd.batch_size.must_equal 7
280
+ @statsd.batch do |b|
281
+ b.batch_size.must_equal 7
282
+ end
283
+ end
284
+
285
+ it "should flush the batch at the batch size or at the end of the block" do
286
+ @statsd.batch do |b|
287
+ b.batch_size = 3
288
+
289
+ # The first three should flush, the next two will be flushed when the
290
+ # block is done.
291
+ 5.times { b.increment('foobar') }
292
+
293
+ @socket.recv.must_equal [(["foobar:1|c"] * 3).join("\n")]
294
+ end
295
+
296
+ @socket.recv.must_equal [(["foobar:1|c"] * 2).join("\n")]
297
+ end
298
+
299
+ it "should not flush to the socket if the backlog is empty" do
300
+ batch = Statsd::Batch.new(@statsd)
301
+ batch.flush
302
+ @socket.recv.must_be :nil?
303
+
304
+ batch.increment 'foobar'
305
+ batch.flush
306
+ @socket.recv.must_equal %w[foobar:1|c]
307
+ end
308
+
309
+ end
310
+
248
311
  describe "thread safety" do
249
312
 
250
313
  it "should use a thread local socket" do
data/statsd-ruby.gemspec CHANGED
@@ -1,13 +1,13 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
- Gem::Specification.new("statsd-ruby", "1.0.0") do |s|
3
+ Gem::Specification.new("statsd-ruby", "1.1.0") do |s|
4
4
  s.authors = ["Rein Henrichs"]
5
5
  s.email = "rein@phpfog.com"
6
6
 
7
7
  s.summary = "A Ruby StatsD client"
8
8
  s.description = "A Ruby StatsD client (https://github.com/etsy/statsd)"
9
9
 
10
- s.homepage = "https://github.com/reinh/statsd-ruby"
10
+ s.homepage = "https://github.com/reinh/statsd"
11
11
  s.licenses = %w[MIT]
12
12
 
13
13
  s.extra_rdoc_files = %w[LICENSE.txt README.rdoc]
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statsd-ruby
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
+ - 1
8
9
  - 0
9
- - 0
10
- version: 1.0.0
10
+ version: 1.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Rein Henrichs
@@ -15,11 +15,12 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-07-02 00:00:00 Z
18
+ date: 2012-12-05 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
+ name: minitest
21
22
  prerelease: false
22
- version_requirements: &id001 !ruby/object:Gem::Requirement
23
+ requirement: &id001 !ruby/object:Gem::Requirement
23
24
  none: false
24
25
  requirements:
25
26
  - - ">="
@@ -30,12 +31,12 @@ dependencies:
30
31
  - 2
31
32
  - 0
32
33
  version: 3.2.0
33
- requirement: *id001
34
- name: minitest
35
34
  type: :development
35
+ version_requirements: *id001
36
36
  - !ruby/object:Gem::Dependency
37
+ name: yard
37
38
  prerelease: false
38
- version_requirements: &id002 !ruby/object:Gem::Requirement
39
+ requirement: &id002 !ruby/object:Gem::Requirement
39
40
  none: false
40
41
  requirements:
41
42
  - - ">="
@@ -44,12 +45,12 @@ dependencies:
44
45
  segments:
45
46
  - 0
46
47
  version: "0"
47
- requirement: *id002
48
- name: yard
49
48
  type: :development
49
+ version_requirements: *id002
50
50
  - !ruby/object:Gem::Dependency
51
+ name: simplecov
51
52
  prerelease: false
52
- version_requirements: &id003 !ruby/object:Gem::Requirement
53
+ requirement: &id003 !ruby/object:Gem::Requirement
53
54
  none: false
54
55
  requirements:
55
56
  - - ">="
@@ -60,12 +61,12 @@ dependencies:
60
61
  - 6
61
62
  - 4
62
63
  version: 0.6.4
63
- requirement: *id003
64
- name: simplecov
65
64
  type: :development
65
+ version_requirements: *id003
66
66
  - !ruby/object:Gem::Dependency
67
+ name: rake
67
68
  prerelease: false
68
- version_requirements: &id004 !ruby/object:Gem::Requirement
69
+ requirement: &id004 !ruby/object:Gem::Requirement
69
70
  none: false
70
71
  requirements:
71
72
  - - ">="
@@ -74,9 +75,8 @@ dependencies:
74
75
  segments:
75
76
  - 0
76
77
  version: "0"
77
- requirement: *id004
78
- name: rake
79
78
  type: :development
79
+ version_requirements: *id004
80
80
  description: A Ruby StatsD client (https://github.com/etsy/statsd)
81
81
  email: rein@phpfog.com
82
82
  executables: []
@@ -98,7 +98,7 @@ files:
98
98
  - spec/helper.rb
99
99
  - spec/statsd_spec.rb
100
100
  - statsd-ruby.gemspec
101
- homepage: https://github.com/reinh/statsd-ruby
101
+ homepage: https://github.com/reinh/statsd
102
102
  licenses:
103
103
  - MIT
104
104
  post_install_message:
@@ -127,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
127
  requirements: []
128
128
 
129
129
  rubyforge_project:
130
- rubygems_version: 1.8.15
130
+ rubygems_version: 1.8.24
131
131
  signing_key:
132
132
  specification_version: 3
133
133
  summary: A Ruby StatsD client