statsd-ruby 1.1.1 → 1.5.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.
@@ -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