statsd-ruby 1.0.0 → 1.1.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.
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