statsd-ruby 1.1.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,35 @@
1
+ class Statsd
2
+ # = MonotonicTime: a helper for getting monotonic time
3
+ #
4
+ # @example
5
+ # MonotonicTime.time_in_ms #=> 287138801.144576
6
+
7
+ # MonotonicTime guarantees that the time is strictly linearly
8
+ # increasing (unlike realtime).
9
+ # @see http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html
10
+ module MonotonicTime
11
+ class << self
12
+ # @return [Integer] current monotonic time in milliseconds
13
+ def time_in_ms
14
+ time_in_nanoseconds / (10.0 ** 6)
15
+ end
16
+
17
+ private
18
+
19
+ if defined?(Process::CLOCK_MONOTONIC)
20
+ def time_in_nanoseconds
21
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
22
+ end
23
+ elsif RUBY_ENGINE == 'jruby'
24
+ def time_in_nanoseconds
25
+ java.lang.System.nanoTime
26
+ end
27
+ else
28
+ def time_in_nanoseconds
29
+ t = Time.now
30
+ t.to_i * (10 ** 9) + t.nsec
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,19 +1,21 @@
1
1
  require 'bundler/setup'
2
- require 'minitest/autorun'
3
2
 
4
3
  require 'simplecov'
5
4
  SimpleCov.start
6
5
 
6
+ require 'minitest/autorun'
7
7
  require 'statsd'
8
8
  require 'logger'
9
+ require 'timeout'
9
10
 
10
11
  class FakeUDPSocket
11
12
  def initialize
12
13
  @buffer = []
13
14
  end
14
15
 
15
- def send(message, *rest)
16
+ def write(message)
16
17
  @buffer.push [message]
18
+ message.length
17
19
  end
18
20
 
19
21
  def recv
@@ -29,6 +31,13 @@ class FakeUDPSocket
29
31
  end
30
32
 
31
33
  def inspect
32
- "<FakeUDPSocket: #{@buffer.inspect}>"
34
+ "<#{self.class.name}: #{@buffer.inspect}>"
35
+ end
36
+ end
37
+
38
+ class FakeTCPSocket < FakeUDPSocket
39
+ alias_method :readline, :recv
40
+ def write(message)
41
+ @buffer.push message
33
42
  end
34
43
  end
@@ -0,0 +1,117 @@
1
+ require 'helper'
2
+
3
+ describe Statsd::Admin do
4
+
5
+ before do
6
+ class Statsd::Admin
7
+ o, $VERBOSE = $VERBOSE, nil
8
+ alias connect_old connect
9
+ def connect
10
+ $connect_count ||= 0
11
+ $connect_count += 1
12
+ end
13
+ $VERBOSE = o
14
+ end
15
+ @admin = Statsd::Admin.new('localhost', 1234)
16
+ @socket = @admin.instance_variable_set(:@socket, FakeTCPSocket.new)
17
+ end
18
+
19
+ after do
20
+ class Statsd::Admin
21
+ o, $VERBOSE = $VERBOSE, nil
22
+ alias connect connect_old
23
+ $VERBOSE = o
24
+ end
25
+ end
26
+
27
+ describe "#initialize" do
28
+ it "should set the host and port" do
29
+ _(@admin.host).must_equal 'localhost'
30
+ _(@admin.port).must_equal 1234
31
+ end
32
+
33
+ it "should default the host to 127.0.0.1 and port to 8126" do
34
+ statsd = Statsd::Admin.new
35
+ _(statsd.host).must_equal '127.0.0.1'
36
+ _(statsd.port).must_equal 8126
37
+ end
38
+ end
39
+
40
+ describe "#host and #port" do
41
+ it "should set host and port" do
42
+ @admin.host = '1.2.3.4'
43
+ @admin.port = 5678
44
+ _(@admin.host).must_equal '1.2.3.4'
45
+ _(@admin.port).must_equal 5678
46
+ end
47
+
48
+ it "should not resolve hostnames to IPs" do
49
+ @admin.host = 'localhost'
50
+ _(@admin.host).must_equal 'localhost'
51
+ end
52
+
53
+ it "should set nil host to default" do
54
+ @admin.host = nil
55
+ _(@admin.host).must_equal '127.0.0.1'
56
+ end
57
+
58
+ it "should set nil port to default" do
59
+ @admin.port = nil
60
+ _(@admin.port).must_equal 8126
61
+ end
62
+ end
63
+
64
+ %w(gauges counters timers).each do |action|
65
+ describe "##{action}" do
66
+ it "should send a command and return a Hash" do
67
+ ["{'foo.bar': 0,\n",
68
+ "'foo.baz': 1,\n",
69
+ "'foo.quux': 2 }\n",
70
+ "END\n","\n"].each do |line|
71
+ @socket.write line
72
+ end
73
+ result = @admin.send action.to_sym
74
+ _(result).must_be_kind_of Hash
75
+ _(result.size).must_equal 3
76
+ _(@socket.readline).must_equal "#{action}\n"
77
+ end
78
+ end
79
+
80
+ describe "#del#{action}" do
81
+ it "should send a command and return an Array" do
82
+ ["deleted: foo.bar\n",
83
+ "deleted: foo.baz\n",
84
+ "deleted: foo.quux\n",
85
+ "END\n", "\n"].each do |line|
86
+ @socket.write line
87
+ end
88
+ result = @admin.send "del#{action}", "foo.*"
89
+ _(result).must_be_kind_of Array
90
+ _(result.size).must_equal 3
91
+ _(@socket.readline).must_equal "del#{action} foo.*\n"
92
+ end
93
+ end
94
+ end
95
+
96
+ describe "#stats" do
97
+ it "should send a command and return a Hash" do
98
+ ["whatever: 0\n", "END\n", "\n"].each do |line|
99
+ @socket.write line
100
+ end
101
+ result = @admin.stats
102
+ _(result).must_be_kind_of Hash
103
+ _(result["whatever"]).must_equal 0
104
+ _(@socket.readline).must_equal "stats\n"
105
+ end
106
+ end
107
+
108
+ describe "#connect" do
109
+ it "should reconnect" do
110
+ c = $connect_count
111
+ @admin.connect
112
+ _(($connect_count - c)).must_equal 1
113
+ end
114
+ end
115
+ end
116
+
117
+
@@ -1,27 +1,43 @@
1
1
  require 'helper'
2
2
 
3
3
  describe Statsd do
4
- class Statsd
5
- public :socket
6
- end
7
-
8
4
  before do
5
+ class Statsd
6
+ o, $VERBOSE = $VERBOSE, nil
7
+ alias connect_old connect
8
+ def connect
9
+ $connect_count ||= 1
10
+ $connect_count += 1
11
+ end
12
+ $VERBOSE = o
13
+ end
14
+
9
15
  @statsd = Statsd.new('localhost', 1234)
10
- @socket = Thread.current[:statsd_socket] = FakeUDPSocket.new
16
+ @socket = @statsd.instance_variable_set(:@socket, FakeUDPSocket.new)
11
17
  end
12
18
 
13
- after { Thread.current[:statsd_socket] = nil }
19
+ after do
20
+ class Statsd
21
+ o, $VERBOSE = $VERBOSE, nil
22
+ alias connect connect_old
23
+ $VERBOSE = o
24
+ end
25
+ end
14
26
 
15
27
  describe "#initialize" do
16
28
  it "should set the host and port" do
17
- @statsd.host.must_equal 'localhost'
18
- @statsd.port.must_equal 1234
29
+ _(@statsd.host).must_equal 'localhost'
30
+ _(@statsd.port).must_equal 1234
19
31
  end
20
32
 
21
33
  it "should default the host to 127.0.0.1 and port to 8125" do
22
34
  statsd = Statsd.new
23
- statsd.host.must_equal '127.0.0.1'
24
- statsd.port.must_equal 8125
35
+ _(statsd.host).must_equal '127.0.0.1'
36
+ _(statsd.port).must_equal 8125
37
+ end
38
+
39
+ it "should set delimiter to period by default" do
40
+ _(@statsd.delimiter).must_equal "."
25
41
  end
26
42
  end
27
43
 
@@ -29,37 +45,54 @@ describe Statsd do
29
45
  it "should set host and port" do
30
46
  @statsd.host = '1.2.3.4'
31
47
  @statsd.port = 5678
32
- @statsd.host.must_equal '1.2.3.4'
33
- @statsd.port.must_equal 5678
48
+ _(@statsd.host).must_equal '1.2.3.4'
49
+ _(@statsd.port).must_equal 5678
34
50
  end
35
51
 
36
52
  it "should not resolve hostnames to IPs" do
37
53
  @statsd.host = 'localhost'
38
- @statsd.host.must_equal 'localhost'
54
+ _(@statsd.host).must_equal 'localhost'
39
55
  end
40
56
 
41
57
  it "should set nil host to default" do
42
58
  @statsd.host = nil
43
- @statsd.host.must_equal '127.0.0.1'
59
+ _(@statsd.host).must_equal '127.0.0.1'
44
60
  end
45
61
 
46
62
  it "should set nil port to default" do
47
63
  @statsd.port = nil
48
- @statsd.port.must_equal 8125
64
+ _(@statsd.port).must_equal 8125
65
+ end
66
+
67
+ it "should allow an IPv6 address" do
68
+ @statsd.host = '::1'
69
+ _(@statsd.host).must_equal '::1'
70
+ end
71
+ end
72
+
73
+ describe "#delimiter" do
74
+ it "should set delimiter" do
75
+ @statsd.delimiter = "-"
76
+ _(@statsd.delimiter).must_equal "-"
77
+ end
78
+
79
+ it "should set default to period if not given a value" do
80
+ @statsd.delimiter = nil
81
+ _(@statsd.delimiter).must_equal "."
49
82
  end
50
83
  end
51
84
 
52
85
  describe "#increment" do
53
86
  it "should format the message according to the statsd spec" do
54
87
  @statsd.increment('foobar')
55
- @socket.recv.must_equal ['foobar:1|c']
88
+ _(@socket.recv).must_equal ['foobar:1|c']
56
89
  end
57
90
 
58
91
  describe "with a sample rate" do
59
92
  before { class << @statsd; def rand; 0; end; end } # ensure delivery
60
93
  it "should format the message according to the statsd spec" do
61
94
  @statsd.increment('foobar', 0.5)
62
- @socket.recv.must_equal ['foobar:1|c|@0.5']
95
+ _(@socket.recv).must_equal ['foobar:1|c|@0.5']
63
96
  end
64
97
  end
65
98
  end
@@ -67,14 +100,14 @@ describe Statsd do
67
100
  describe "#decrement" do
68
101
  it "should format the message according to the statsd spec" do
69
102
  @statsd.decrement('foobar')
70
- @socket.recv.must_equal ['foobar:-1|c']
103
+ _(@socket.recv).must_equal ['foobar:-1|c']
71
104
  end
72
105
 
73
106
  describe "with a sample rate" do
74
107
  before { class << @statsd; def rand; 0; end; end } # ensure delivery
75
108
  it "should format the message according to the statsd spec" do
76
109
  @statsd.decrement('foobar', 0.5)
77
- @socket.recv.must_equal ['foobar:-1|c|@0.5']
110
+ _(@socket.recv).must_equal ['foobar:-1|c|@0.5']
78
111
  end
79
112
  end
80
113
  end
@@ -82,16 +115,16 @@ describe Statsd do
82
115
  describe "#gauge" do
83
116
  it "should send a message with a 'g' type, per the nearbuy fork" do
84
117
  @statsd.gauge('begrutten-suffusion', 536)
85
- @socket.recv.must_equal ['begrutten-suffusion:536|g']
118
+ _(@socket.recv).must_equal ['begrutten-suffusion:536|g']
86
119
  @statsd.gauge('begrutten-suffusion', -107.3)
87
- @socket.recv.must_equal ['begrutten-suffusion:-107.3|g']
120
+ _(@socket.recv).must_equal ['begrutten-suffusion:-107.3|g']
88
121
  end
89
122
 
90
123
  describe "with a sample rate" do
91
124
  before { class << @statsd; def rand; 0; end; end } # ensure delivery
92
125
  it "should format the message according to the statsd spec" do
93
126
  @statsd.gauge('begrutten-suffusion', 536, 0.1)
94
- @socket.recv.must_equal ['begrutten-suffusion:536|g|@0.1']
127
+ _(@socket.recv).must_equal ['begrutten-suffusion:536|g|@0.1']
95
128
  end
96
129
  end
97
130
  end
@@ -99,14 +132,29 @@ describe Statsd do
99
132
  describe "#timing" do
100
133
  it "should format the message according to the statsd spec" do
101
134
  @statsd.timing('foobar', 500)
102
- @socket.recv.must_equal ['foobar:500|ms']
135
+ _(@socket.recv).must_equal ['foobar:500|ms']
103
136
  end
104
137
 
105
138
  describe "with a sample rate" do
106
139
  before { class << @statsd; def rand; 0; end; end } # ensure delivery
107
140
  it "should format the message according to the statsd spec" do
108
141
  @statsd.timing('foobar', 500, 0.5)
109
- @socket.recv.must_equal ['foobar:500|ms|@0.5']
142
+ _(@socket.recv).must_equal ['foobar:500|ms|@0.5']
143
+ end
144
+ end
145
+ end
146
+
147
+ describe "#set" do
148
+ it "should format the message according to the statsd spec" do
149
+ @statsd.set('foobar', 765)
150
+ _(@socket.recv).must_equal ['foobar:765|s']
151
+ end
152
+
153
+ describe "with a sample rate" do
154
+ before { class << @statsd; def rand; 0; end; end } # ensure delivery
155
+ it "should format the message according to the statsd spec" do
156
+ @statsd.set('foobar', 500, 0.5)
157
+ _(@socket.recv).must_equal ['foobar:500|s|@0.5']
110
158
  end
111
159
  end
112
160
  end
@@ -114,12 +162,24 @@ describe Statsd do
114
162
  describe "#time" do
115
163
  it "should format the message according to the statsd spec" do
116
164
  @statsd.time('foobar') { 'test' }
117
- @socket.recv.must_equal ['foobar:0|ms']
165
+ _(@socket.recv).must_equal ['foobar:0|ms']
118
166
  end
119
167
 
120
168
  it "should return the result of the block" do
121
169
  result = @statsd.time('foobar') { 'test' }
122
- result.must_equal 'test'
170
+ _(result).must_equal 'test'
171
+ end
172
+
173
+ describe "when given a block with an explicit return" do
174
+ it "should format the message according to the statsd spec" do
175
+ lambda { @statsd.time('foobar') { return 'test' } }.call
176
+ _(@socket.recv).must_equal ['foobar:0|ms']
177
+ end
178
+
179
+ it "should return the result of the block" do
180
+ result = lambda { @statsd.time('foobar') { return 'test' } }.call
181
+ _(result).must_equal 'test'
182
+ end
123
183
  end
124
184
 
125
185
  describe "with a sample rate" do
@@ -127,7 +187,7 @@ describe Statsd do
127
187
 
128
188
  it "should format the message according to the statsd spec" do
129
189
  @statsd.time('foobar', 0.5) { 'test' }
130
- @socket.recv.must_equal ['foobar:0|ms|@0.5']
190
+ _(@socket.recv).must_equal ['foobar:0|ms|@0.5']
131
191
  end
132
192
  end
133
193
  end
@@ -137,7 +197,7 @@ describe Statsd do
137
197
  before { class << @statsd; def rand; raise end; end }
138
198
  it "should send" do
139
199
  @statsd.timing('foobar', 500, 1)
140
- @socket.recv.must_equal ['foobar:500|ms']
200
+ _(@socket.recv).must_equal ['foobar:500|ms']
141
201
  end
142
202
  end
143
203
 
@@ -145,14 +205,14 @@ describe Statsd do
145
205
  before { class << @statsd; def rand; 0; end; end } # ensure delivery
146
206
  it "should send" do
147
207
  @statsd.timing('foobar', 500, 0.5)
148
- @socket.recv.must_equal ['foobar:500|ms|@0.5']
208
+ _(@socket.recv).must_equal ['foobar:500|ms|@0.5']
149
209
  end
150
210
  end
151
211
 
152
212
  describe "when the sample rate is less than a random value [0,1]" do
153
213
  before { class << @statsd; def rand; 1; end; end } # ensure no delivery
154
214
  it "should not send" do
155
- @statsd.timing('foobar', 500, 0.5).must_equal nil
215
+ assert_nil @statsd.timing('foobar', 500, 0.5)
156
216
  end
157
217
  end
158
218
 
@@ -160,7 +220,7 @@ describe Statsd do
160
220
  before { class << @statsd; def rand; 0; end; end } # ensure delivery
161
221
  it "should send" do
162
222
  @statsd.timing('foobar', 500, 0.5)
163
- @socket.recv.must_equal ['foobar:500|ms|@0.5']
223
+ _(@socket.recv).must_equal ['foobar:500|ms|@0.5']
164
224
  end
165
225
  end
166
226
  end
@@ -170,22 +230,22 @@ describe Statsd do
170
230
 
171
231
  it "should add namespace to increment" do
172
232
  @statsd.increment('foobar')
173
- @socket.recv.must_equal ['service.foobar:1|c']
233
+ _(@socket.recv).must_equal ['service.foobar:1|c']
174
234
  end
175
235
 
176
236
  it "should add namespace to decrement" do
177
237
  @statsd.decrement('foobar')
178
- @socket.recv.must_equal ['service.foobar:-1|c']
238
+ _(@socket.recv).must_equal ['service.foobar:-1|c']
179
239
  end
180
240
 
181
241
  it "should add namespace to timing" do
182
242
  @statsd.timing('foobar', 500)
183
- @socket.recv.must_equal ['service.foobar:500|ms']
243
+ _(@socket.recv).must_equal ['service.foobar:500|ms']
184
244
  end
185
245
 
186
246
  it "should add namespace to gauge" do
187
247
  @statsd.gauge('foobar', 500)
188
- @socket.recv.must_equal ['service.foobar:500|g']
248
+ _(@socket.recv).must_equal ['service.foobar:500|g']
189
249
  end
190
250
  end
191
251
 
@@ -194,22 +254,22 @@ describe Statsd do
194
254
 
195
255
  it "should add postfix to increment" do
196
256
  @statsd.increment('foobar')
197
- @socket.recv.must_equal ['foobar.ip-23-45-56-78:1|c']
257
+ _(@socket.recv).must_equal ['foobar.ip-23-45-56-78:1|c']
198
258
  end
199
259
 
200
260
  it "should add postfix to decrement" do
201
261
  @statsd.decrement('foobar')
202
- @socket.recv.must_equal ['foobar.ip-23-45-56-78:-1|c']
262
+ _(@socket.recv).must_equal ['foobar.ip-23-45-56-78:-1|c']
203
263
  end
204
264
 
205
265
  it "should add namespace to timing" do
206
266
  @statsd.timing('foobar', 500)
207
- @socket.recv.must_equal ['foobar.ip-23-45-56-78:500|ms']
267
+ _(@socket.recv).must_equal ['foobar.ip-23-45-56-78:500|ms']
208
268
  end
209
269
 
210
270
  it "should add namespace to gauge" do
211
271
  @statsd.gauge('foobar', 500)
212
- @socket.recv.must_equal ['foobar.ip-23-45-56-78:500|g']
272
+ _(@socket.recv).must_equal ['foobar.ip-23-45-56-78:500|g']
213
273
  end
214
274
  end
215
275
 
@@ -219,7 +279,7 @@ describe Statsd do
219
279
  [nil, false, ''].each do |value|
220
280
  @statsd.postfix = 'a postfix'
221
281
  @statsd.postfix = value
222
- @statsd.postfix.must_equal nil
282
+ assert_nil @statsd.postfix
223
283
  end
224
284
  end
225
285
  end
@@ -234,7 +294,7 @@ describe Statsd do
234
294
 
235
295
  @statsd.increment('foobar')
236
296
 
237
- @log.string.must_match "Statsd: foobar:1|c"
297
+ _(@log.string).must_match "Statsd: foobar:1|c"
238
298
  end
239
299
 
240
300
  it "should not write to the log unless debug" do
@@ -242,7 +302,7 @@ describe Statsd do
242
302
 
243
303
  @statsd.increment('foobar')
244
304
 
245
- @log.string.must_be_empty
305
+ _(@log.string).must_be_empty
246
306
  end
247
307
  end
248
308
 
@@ -255,12 +315,25 @@ describe Statsd do
255
315
  class Statsd::SomeClass; end
256
316
  @statsd.increment(Statsd::SomeClass, 1)
257
317
 
258
- @socket.recv.must_equal ['Statsd.SomeClass:1|c']
318
+ _(@socket.recv).must_equal ['Statsd.SomeClass:1|c']
319
+ end
320
+
321
+ describe "custom delimiter" do
322
+ before do
323
+ @statsd.delimiter = "-"
324
+ end
325
+
326
+ it "should replace ruby constant delimiter with custom delimiter" do
327
+ class Statsd::SomeOtherClass; end
328
+ @statsd.increment(Statsd::SomeOtherClass, 1)
329
+
330
+ _(@socket.recv).must_equal ['Statsd-SomeOtherClass:1|c']
331
+ end
259
332
  end
260
333
 
261
334
  it "should replace statsd reserved chars in the stat name" do
262
335
  @statsd.increment('ray@hostname.blah|blah.blah:blah', 1)
263
- @socket.recv.must_equal ['ray_hostname.blah_blah.blah_blah:1|c']
336
+ _(@socket.recv).must_equal ['ray_hostname.blah_blah.blah_blah:1|c']
264
337
  end
265
338
  end
266
339
 
@@ -268,29 +341,61 @@ describe Statsd do
268
341
  before do
269
342
  require 'stringio'
270
343
  Statsd.logger = Logger.new(@log = StringIO.new)
271
- @socket.instance_eval { def send(*) raise SocketError end }
344
+ @socket.instance_variable_set(:@err_count, 0)
345
+ @socket.instance_eval { def write(*) @err_count+=1; raise SocketError end }
272
346
  end
273
347
 
274
348
  it "should ignore socket errors" do
275
- @statsd.increment('foobar').must_equal nil
349
+ assert_nil @statsd.increment('foobar')
276
350
  end
277
351
 
278
352
  it "should log socket errors" do
279
353
  @statsd.increment('foobar')
280
- @log.string.must_match 'Statsd: SocketError'
354
+ _(@log.string).must_match 'Statsd: SocketError'
355
+ end
356
+
357
+ it "should retry and reconnect on socket errors" do
358
+ $connect_count = 0
359
+ @statsd.increment('foobar')
360
+ _(@socket.instance_variable_get(:@err_count)).must_equal 5
361
+ _($connect_count).must_equal 5
281
362
  end
282
363
  end
283
364
 
284
365
  describe "batching" do
285
366
  it "should have a default batch size of 10" do
286
- @statsd.batch_size.must_equal 10
367
+ _(@statsd.batch_size).must_equal 10
368
+ end
369
+
370
+ it "should have a default batch byte size of nil" do
371
+ assert_nil @statsd.batch_byte_size
372
+ end
373
+
374
+ it "should have a default flush interval of nil" do
375
+ assert_nil @statsd.flush_interval
287
376
  end
288
377
 
289
378
  it "should have a modifiable batch size" do
290
379
  @statsd.batch_size = 7
291
- @statsd.batch_size.must_equal 7
380
+ _(@statsd.batch_size).must_equal 7
381
+ @statsd.batch do |b|
382
+ _(b.batch_size).must_equal 7
383
+ end
384
+
385
+ @statsd.batch_size = nil
386
+ @statsd.batch_byte_size = 1472
387
+ @statsd.batch do |b|
388
+ assert_nil b.batch_size
389
+ _(b.batch_byte_size).must_equal 1472
390
+ end
391
+
392
+ end
393
+
394
+ it 'should have a modifiable flush interval' do
395
+ @statsd.flush_interval = 1
396
+ _(@statsd.flush_interval).must_equal 1
292
397
  @statsd.batch do |b|
293
- b.batch_size.must_equal 7
398
+ _(b.flush_interval).must_equal 1
294
399
  end
295
400
  end
296
401
 
@@ -302,50 +407,195 @@ describe Statsd do
302
407
  # block is done.
303
408
  5.times { b.increment('foobar') }
304
409
 
305
- @socket.recv.must_equal [(["foobar:1|c"] * 3).join("\n")]
410
+ _(@socket.recv).must_equal [(["foobar:1|c"] * 3).join("\n")]
306
411
  end
307
412
 
308
- @socket.recv.must_equal [(["foobar:1|c"] * 2).join("\n")]
413
+ _(@socket.recv).must_equal [(["foobar:1|c"] * 2).join("\n")]
414
+ end
415
+
416
+ it "should flush based on batch byte size" do
417
+ @statsd.batch do |b|
418
+ b.batch_size = nil
419
+ b.batch_byte_size = 22
420
+
421
+ # The first two should flush, the last will be flushed when the
422
+ # block is done.
423
+ 3.times { b.increment('foobar') }
424
+
425
+ _(@socket.recv).must_equal [(["foobar:1|c"] * 2).join("\n")]
426
+ end
427
+
428
+ _(@socket.recv).must_equal ["foobar:1|c"]
429
+ end
430
+
431
+ it "should flush immediately when the queue is exactly a batch size" do
432
+ @statsd.batch do |b|
433
+ b.batch_size = nil
434
+ b.batch_byte_size = 21
435
+
436
+ # The first two should flush together
437
+ 2.times { b.increment('foobar') }
438
+
439
+ _(@socket.recv).must_equal [(["foobar:1|c"] * 2).join("\n")]
440
+ end
441
+ end
442
+
443
+ it "should flush when the interval has passed" do
444
+ @statsd.batch do |b|
445
+ b.batch_size = nil
446
+ b.flush_interval = 0.01
447
+
448
+ # The first two should flush, the last will be flushed when the
449
+ # block is done.
450
+ 2.times { b.increment('foobar') }
451
+ sleep(0.03)
452
+ b.increment('foobar')
453
+
454
+ _(@socket.recv).must_equal [(["foobar:1|c"] * 2).join("\n")]
455
+ end
456
+
457
+ _(@socket.recv).must_equal ["foobar:1|c"]
309
458
  end
310
459
 
311
460
  it "should not flush to the socket if the backlog is empty" do
312
461
  batch = Statsd::Batch.new(@statsd)
313
462
  batch.flush
314
- @socket.recv.must_be :nil?
463
+ _(@socket.recv).must_be :nil?
315
464
 
316
465
  batch.increment 'foobar'
317
466
  batch.flush
318
- @socket.recv.must_equal %w[foobar:1|c]
467
+ _(@socket.recv).must_equal %w[foobar:1|c]
319
468
  end
320
469
 
321
- end
322
-
323
- describe "thread safety" do
470
+ it "should support setting namespace for the underlying instance" do
471
+ batch = Statsd::Batch.new(@statsd)
472
+ batch.namespace = 'ns'
473
+ _(@statsd.namespace).must_equal 'ns'
474
+ end
324
475
 
325
- it "should use a thread local socket" do
326
- Thread.current[:statsd_socket].must_equal @socket
327
- @statsd.send(:socket).must_equal @socket
476
+ it "should support setting host for the underlying instance" do
477
+ batch = Statsd::Batch.new(@statsd)
478
+ batch.host = '1.2.3.4'
479
+ _(@statsd.host).must_equal '1.2.3.4'
328
480
  end
329
481
 
330
- it "should create a new socket when used in a new thread" do
331
- sock = @statsd.send(:socket)
332
- Thread.new { Thread.current[:statsd_socket].wont_equal sock }.join
482
+ it "should support setting port for the underlying instance" do
483
+ batch = Statsd::Batch.new(@statsd)
484
+ batch.port = 42
485
+ _(@statsd.port).must_equal 42
333
486
  end
334
487
 
335
488
  end
489
+
490
+ describe "#connect" do
491
+ it "should reconnect" do
492
+ c = $connect_count
493
+ @statsd.connect
494
+ _(($connect_count - c)).must_equal 1
495
+ end
496
+ end
497
+
336
498
  end
337
499
 
338
500
  describe Statsd do
339
501
  describe "with a real UDP socket" do
340
502
  it "should actually send stuff over the socket" do
341
- socket = UDPSocket.new
342
- host, port = 'localhost', 12345
343
- socket.bind(host, port)
503
+ family = Addrinfo.udp(UDPSocket.getaddress('localhost'), 0).afamily
504
+ begin
505
+ socket = UDPSocket.new family
506
+ host, port = 'localhost', 0
507
+ socket.bind(host, port)
508
+ port = socket.addr[1]
509
+
510
+ statsd = Statsd.new(host, port)
511
+ statsd.increment('foobar')
512
+ message = socket.recvfrom(16).first
513
+ _(message).must_equal 'foobar:1|c'
514
+ ensure
515
+ socket.close
516
+ end
517
+ end
518
+
519
+ it "should send stuff over an IPv4 socket" do
520
+ begin
521
+ socket = UDPSocket.new Socket::AF_INET
522
+ host, port = '127.0.0.1', 0
523
+ socket.bind(host, port)
524
+ port = socket.addr[1]
525
+
526
+ statsd = Statsd.new(host, port)
527
+ statsd.increment('foobar')
528
+ message = socket.recvfrom(16).first
529
+ _(message).must_equal 'foobar:1|c'
530
+ ensure
531
+ socket.close
532
+ end
533
+ end
344
534
 
345
- statsd = Statsd.new(host, port)
346
- statsd.increment('foobar')
347
- message = socket.recvfrom(16).first
348
- message.must_equal 'foobar:1|c'
535
+ it "should send stuff over an IPv6 socket" do
536
+ begin
537
+ socket = UDPSocket.new Socket::AF_INET6
538
+ host, port = '::1', 0
539
+ socket.bind(host, port)
540
+ port = socket.addr[1]
541
+
542
+ statsd = Statsd.new(host, port)
543
+ statsd.increment('foobar')
544
+ message = socket.recvfrom(16).first
545
+ _(message).must_equal 'foobar:1|c'
546
+ ensure
547
+ socket.close
548
+ end
349
549
  end
350
550
  end
351
- end if ENV['LIVE']
551
+
552
+ describe "supports TCP sockets" do
553
+ it "should connect to and send stats over TCPv4" do
554
+ begin
555
+ host, port = '127.0.0.1', 0
556
+ server = TCPServer.new host, port
557
+ port = server.addr[1]
558
+
559
+ socket = nil
560
+ Thread.new { socket = server.accept }
561
+
562
+ statsd = Statsd.new(host, port, :tcp)
563
+ statsd.increment('foobar')
564
+
565
+ Timeout.timeout(5) do
566
+ Thread.pass while socket == nil
567
+ end
568
+
569
+ message = socket.recvfrom(16).first
570
+ _(message).must_equal "foobar:1|c\n"
571
+ ensure
572
+ socket.close if socket
573
+ server.close
574
+ end
575
+ end
576
+
577
+ it "should connect to and send stats over TCPv6" do
578
+ begin
579
+ host, port = '::1', 0
580
+ server = TCPServer.new host, port
581
+ port = server.addr[1]
582
+
583
+ socket = nil
584
+ Thread.new { socket = server.accept }
585
+
586
+ statsd = Statsd.new(host, port, :tcp)
587
+ statsd.increment('foobar')
588
+
589
+ Timeout.timeout(5) do
590
+ Thread.pass while socket == nil
591
+ end
592
+
593
+ message = socket.recvfrom(16).first
594
+ _(message).must_equal "foobar:1|c\n"
595
+ ensure
596
+ socket.close if socket
597
+ server.close
598
+ end
599
+ end
600
+ end
601
+ end