statsd-ruby 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- NjhjMzVkODc3OGI5MDRkOGIzNTE4NzMwY2Q3NWU4YjAzYWRhYTg0MQ==
5
- data.tar.gz: !binary |-
6
- OTU3NjZmN2NhOGQ3ZWM5OGQwNjIyNDg0ZDc2ZTAzZWEyMjMzNjA3Zg==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- OTNkNWQ4NTAwZTA5NzY2MDk3Njg5ZjJhODM2Mjg1MTZmYmE1YmNjNDg5Mjc0
10
- YzU5OWZjMWIxY2JmOTBhYjMwMGM5NDIyNmUxYmE5MTllNmE3ZDNmMWEzNjll
11
- MDY3NDc4MzU4ZDdlNGRhYjZjMTdiNTY0MGVlMjhkZmZiNjg5Yzk=
12
- data.tar.gz: !binary |-
13
- ZmI2YzQyNDUxZWNjOGRmMjE0NjhiOGNjYTg4NjRlZjU2NDIxMjkyMmZmMzEx
14
- YjBmZDA5MmZhY2RmODEzODk1NGY3ZmJmNjRhNWZjYmM5OTExYmY1MGI2OTNl
15
- YmFjOTkwOTk4MjBmYmY4YWJkYTc5YWFiOTE2ZjE4MzgxYTBmODk=
2
+ SHA1:
3
+ metadata.gz: e4f5fc1d6357476dfd6402b233f22f43907c9fd7
4
+ data.tar.gz: 6210b9a5fc2b4221d0cf4cb3a1b11a4e9b6fdb14
5
+ SHA512:
6
+ metadata.gz: a71f111d63ae9d67eb355aa8f692bffa86336632a83e4f3947e148c208169ad379f2ef614498f9c89dec926dab79c5766c623b53fea38cb7a58976ad4c8b75c0
7
+ data.tar.gz: d4cb7379bf93758dec529e682f30724ff68a3d2f02c1acc3b6393256bebf0dcd04d988db05c3c346dae21901e63f6aa4092f47b6c98831e791a491acb3c59a97
data/.travis.yml CHANGED
@@ -1,6 +1,21 @@
1
1
  ---
2
2
  language: ruby
3
+
3
4
  rvm:
4
- - 1.8.7
5
- - 1.9.3
6
- - jruby-19mode
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - 2.1
8
+ - 2.2
9
+ - 2.3.0
10
+ - rbx-2
11
+ - jruby
12
+ - jruby-head
13
+ - ruby-head
14
+
15
+ sudo: false
16
+
17
+ matrix:
18
+ allow_failures:
19
+ - rvm: rbx-2
20
+ - rvm: ruby-head
21
+ - rvm: jruby
data/README.rdoc CHANGED
@@ -12,6 +12,9 @@ Bundler:
12
12
  # Set up a global Statsd client for a server on localhost:9125
13
13
  $statsd = Statsd.new 'localhost', 9125
14
14
 
15
+ # Set up a global Statsd client for a server on IPv6 port 9125
16
+ $statsd = Statsd.new '::1', 9125
17
+
15
18
  # Send some stats
16
19
  $statsd.increment 'garets'
17
20
  $statsd.timing 'glork', 320
@@ -28,12 +31,14 @@ Bundler:
28
31
 
29
32
  Run the specs with <tt>rake spec</tt>
30
33
 
31
- Run the specs and include live integration specs with <tt>LIVE=true rake spec</tt>. Note: This will test over a real UDP socket.
32
-
33
34
  = Performance
34
35
 
35
36
  * A short note about DNS: If you use a dns name for the host option, then you will want to use a local caching dns service for optimial performance (e.g. nscd).
36
37
 
38
+ = Extensions / Libraries / Extra Docs
39
+
40
+ * See the wiki[https://github.com/reinh/statsd/wiki]
41
+
37
42
  == Contributing to statsd
38
43
 
39
44
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
data/lib/statsd.rb CHANGED
@@ -1,10 +1,13 @@
1
1
  require 'socket'
2
2
  require 'forwardable'
3
+ require 'json'
3
4
 
4
5
  # = Statsd: A Statsd client (https://github.com/etsy/statsd)
5
6
  #
6
- # @example Set up a global Statsd client for a server on localhost:9125
7
+ # @example Set up a global Statsd client for a server on localhost:8125
7
8
  # $statsd = Statsd.new 'localhost', 8125
9
+ # @example Set up a global Statsd client for a server on IPv6 port 8125
10
+ # $statsd = Statsd.new '::1', 8125
8
11
  # @example Send some stats
9
12
  # $statsd.increment 'garets'
10
13
  # $statsd.timing 'glork', 320
@@ -15,8 +18,8 @@ require 'forwardable'
15
18
  # statsd = Statsd.new('localhost').tap{|sd| sd.namespace = 'account'}
16
19
  # statsd.increment 'activate'
17
20
  #
18
- # Statsd instances are thread safe for general usage, by using a thread local
19
- # UDPSocket and carrying no state. The attributes are stateful, and are not
21
+ # Statsd instances are thread safe for general usage, by utilizing the thread
22
+ # safe nature of UDP sends. The attributes are stateful, and are not
20
23
  # mutexed, it is expected that users will not change these at runtime in
21
24
  # threaded environments. If users require such use cases, it is recommend that
22
25
  # users either mutex around their Statsd object, or create separate objects for
@@ -47,7 +50,8 @@ class Statsd
47
50
  :host, :host=,
48
51
  :port, :port=,
49
52
  :prefix,
50
- :postfix
53
+ :postfix,
54
+ :delimiter, :delimiter=
51
55
 
52
56
  attr_accessor :batch_size
53
57
 
@@ -87,6 +91,146 @@ class Statsd
87
91
 
88
92
  end
89
93
 
94
+ class Admin
95
+ # StatsD host. Defaults to 127.0.0.1.
96
+ attr_reader :host
97
+
98
+ # StatsD admin port. Defaults to 8126.
99
+ attr_reader :port
100
+
101
+ class << self
102
+ # Set to a standard logger instance to enable debug logging.
103
+ attr_accessor :logger
104
+ end
105
+
106
+ # @attribute [w] host.
107
+ # Users should call connect after changing this.
108
+ def host=(host)
109
+ @host = host || '127.0.0.1'
110
+ end
111
+
112
+ # @attribute [w] port.
113
+ # Users should call connect after changing this.
114
+ def port=(port)
115
+ @port = port || 8126
116
+ end
117
+
118
+ # @param [String] host your statsd host
119
+ # @param [Integer] port your statsd port
120
+ def initialize(host = '127.0.0.1', port = 8126)
121
+ @host = host || '127.0.0.1'
122
+ @port = port || 8126
123
+ # protects @socket transactions
124
+ @socket = nil
125
+ @s_mu = Mutex.new
126
+ connect
127
+ end
128
+
129
+ # Reads all gauges from StatsD.
130
+ def gauges
131
+ read_metric :gauges
132
+ end
133
+
134
+ # Reads all timers from StatsD.
135
+ def timers
136
+ read_metric :timers
137
+ end
138
+
139
+ # Reads all counters from StatsD.
140
+ def counters
141
+ read_metric :counters
142
+ end
143
+
144
+ # @param[String] item
145
+ # Deletes one or more gauges. Wildcards are allowed.
146
+ def delgauges item
147
+ delete_metric :gauges, item
148
+ end
149
+
150
+ # @param[String] item
151
+ # Deletes one or more timers. Wildcards are allowed.
152
+ def deltimers item
153
+ delete_metric :timers, item
154
+ end
155
+
156
+ # @param[String] item
157
+ # Deletes one or more counters. Wildcards are allowed.
158
+ def delcounters item
159
+ delete_metric :counters, item
160
+ end
161
+
162
+ def stats
163
+ result = @s_mu.synchronize do
164
+ # the format of "stats" isn't JSON, who knows why
165
+ send_to_socket "stats"
166
+ read_from_socket
167
+ end
168
+ items = {}
169
+ result.split("\n").each do |line|
170
+ key, val = line.chomp.split(": ")
171
+ items[key] = val.to_i
172
+ end
173
+ items
174
+ end
175
+
176
+ # Reconnects the socket, for when the statsd address may have changed. Users
177
+ # do not normally need to call this, but calling it may be appropriate when
178
+ # reconfiguring a process (e.g. from HUP)
179
+ def connect
180
+ @s_mu.synchronize do
181
+ begin
182
+ @socket.flush rescue nil
183
+ @socket.close if @socket
184
+ rescue
185
+ # Ignore socket errors on close.
186
+ end
187
+ @socket = TCPSocket.new(host, port)
188
+ end
189
+ end
190
+
191
+ private
192
+
193
+ def read_metric name
194
+ result = @s_mu.synchronize do
195
+ send_to_socket name
196
+ read_from_socket
197
+ end
198
+ # for some reason, the reply looks like JSON, but isn't, quite
199
+ JSON.parse result.gsub("'", "\"")
200
+ end
201
+
202
+ def delete_metric name, item
203
+ result = @s_mu.synchronize do
204
+ send_to_socket "del#{name} #{item}"
205
+ read_from_socket
206
+ end
207
+ deleted = []
208
+ result.split("\n").each do |line|
209
+ deleted << line.chomp.split(": ")[-1]
210
+ end
211
+ deleted
212
+ end
213
+
214
+ def send_to_socket(message)
215
+ self.class.logger.debug { "Statsd: #{message}" } if self.class.logger
216
+ @socket.write(message.to_s + "\n")
217
+ rescue => boom
218
+ self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
219
+ nil
220
+ end
221
+
222
+
223
+ def read_from_socket
224
+ buffer = ""
225
+ loop do
226
+ line = @socket.readline
227
+ break if line == "END\n"
228
+ buffer += line
229
+ end
230
+ @socket.readline # clear the closing newline out of the socket
231
+ buffer
232
+ end
233
+ end
90
234
 
91
235
  # A namespace to prepend to all statsd calls.
92
236
  attr_reader :namespace
@@ -106,6 +250,9 @@ class Statsd
106
250
  # a postfix to append to all metrics
107
251
  attr_reader :postfix
108
252
 
253
+ # The replacement of :: on ruby module names when transformed to statsd metric names
254
+ attr_reader :delimiter
255
+
109
256
  class << self
110
257
  # Set to a standard logger instance to enable debug logging.
111
258
  attr_accessor :logger
@@ -113,11 +260,18 @@ class Statsd
113
260
 
114
261
  # @param [String] host your statsd host
115
262
  # @param [Integer] port your statsd port
116
- def initialize(host = '127.0.0.1', port = 8125)
117
- self.host, self.port = host, port
263
+ # @param [Symbol] :tcp for TCP, :udp or any other value for UDP
264
+ def initialize(host = '127.0.0.1', port = 8125, protocol = :udp)
265
+ @host = host || '127.0.0.1'
266
+ @port = port || 8125
267
+ self.delimiter = "."
118
268
  @prefix = nil
119
269
  @batch_size = 10
120
270
  @postfix = nil
271
+ @socket = nil
272
+ @protocol = protocol || :udp
273
+ @s_mu = Mutex.new
274
+ connect
121
275
  end
122
276
 
123
277
  # @attribute [w] namespace
@@ -139,16 +293,24 @@ class Statsd
139
293
 
140
294
  # @attribute [w] host
141
295
  # Writes are not thread safe.
296
+ # Users should call hup after making changes.
142
297
  def host=(host)
143
298
  @host = host || '127.0.0.1'
144
299
  end
145
300
 
146
301
  # @attribute [w] port
147
302
  # Writes are not thread safe.
303
+ # Users should call hup after making changes.
148
304
  def port=(port)
149
305
  @port = port || 8125
150
306
  end
151
307
 
308
+ # @attribute [w] stat_delimiter
309
+ # Allows for custom delimiter replacement for :: when Ruby modules are transformed to statsd metric name
310
+ def delimiter=(delimiter)
311
+ @delimiter = delimiter || "."
312
+ end
313
+
152
314
  # Sends an increment (count = 1) for the given stat to the statsd server.
153
315
  #
154
316
  # @param [String] stat stat name
@@ -230,6 +392,7 @@ class Statsd
230
392
  def time(stat, sample_rate=1)
231
393
  start = Time.now
232
394
  result = yield
395
+ ensure
233
396
  timing(stat, ((Time.now - start) * 1000).round, sample_rate)
234
397
  result
235
398
  end
@@ -245,14 +408,53 @@ class Statsd
245
408
  # batch.gauge('user.count', User.count)
246
409
  # end
247
410
  def batch(&block)
248
- Batch.new(self).easy &block
411
+ Batch.new(self).easy(&block)
412
+ end
413
+
414
+ # Reconnects the socket, useful if the address of the statsd has changed. This
415
+ # method is not thread safe from a perspective of stat submission. It is safe
416
+ # from resource leaks. Users do not normally need to call this, but calling it
417
+ # may be appropriate when reconfiguring a process (e.g. from HUP).
418
+ def connect
419
+ @s_mu.synchronize do
420
+ begin
421
+ @socket.close if @socket
422
+ rescue
423
+ # Errors are ignored on reconnects.
424
+ end
425
+
426
+ case @protocol
427
+ when :tcp
428
+ @socket = TCPSocket.new @host, @port
429
+ else
430
+ @socket = UDPSocket.new Addrinfo.ip(@host).afamily
431
+ @socket.connect host, port
432
+ end
433
+ end
249
434
  end
250
435
 
251
436
  protected
252
437
 
253
438
  def send_to_socket(message)
254
439
  self.class.logger.debug { "Statsd: #{message}" } if self.class.logger
255
- socket.send(message, 0, @host, @port)
440
+
441
+ retries = 0
442
+ n = 0
443
+ while true
444
+ # send(2) is atomic, however, in stream cases (TCP) the socket is left
445
+ # in an inconsistent state if a partial message is written. If that case
446
+ # occurs, the socket is closed down and we retry on a new socket.
447
+ n = socket.write(message)
448
+
449
+ if n == message.length
450
+ break
451
+ end
452
+
453
+ connect
454
+ retries += 1
455
+ raise "statsd: Failed to send after #{retries} attempts" if retries >= 5
456
+ end
457
+ n
256
458
  rescue => boom
257
459
  self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
258
460
  nil
@@ -263,13 +465,15 @@ class Statsd
263
465
  def send_stats(stat, delta, type, sample_rate=1)
264
466
  if sample_rate == 1 or rand < sample_rate
265
467
  # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
266
- stat = stat.to_s.gsub('::', '.').tr(':|@', '_')
468
+ stat = stat.to_s.gsub('::', delimiter).tr(':|@', '_')
267
469
  rate = "|@#{sample_rate}" unless sample_rate == 1
268
470
  send_to_socket "#{prefix}#{stat}#{postfix}:#{delta}|#{type}#{rate}"
269
471
  end
270
472
  end
271
473
 
272
474
  def socket
273
- Thread.current[:statsd_socket] ||= UDPSocket.new
475
+ # Subtle: If the socket is half-way through initialization in connect, it
476
+ # cannot be used yet.
477
+ @s_mu.synchronize { @socket } || raise(ThreadError, "socket missing")
274
478
  end
275
479
  end
data/spec/helper.rb CHANGED
@@ -1,9 +1,9 @@
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
9
 
@@ -12,8 +12,9 @@ class FakeUDPSocket
12
12
  @buffer = []
13
13
  end
14
14
 
15
- def send(message, *rest)
15
+ def write(message)
16
16
  @buffer.push [message]
17
+ message.length
17
18
  end
18
19
 
19
20
  def recv
@@ -29,6 +30,13 @@ class FakeUDPSocket
29
30
  end
30
31
 
31
32
  def inspect
32
- "<FakeUDPSocket: #{@buffer.inspect}>"
33
+ "<#{self.class.name}: #{@buffer.inspect}>"
34
+ end
35
+ end
36
+
37
+ class FakeTCPSocket < FakeUDPSocket
38
+ alias_method :readline, :recv
39
+ def write(message)
40
+ @buffer.push message
33
41
  end
34
42
  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
+
data/spec/statsd_spec.rb CHANGED
@@ -1,16 +1,28 @@
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
@@ -23,6 +35,10 @@ describe Statsd do
23
35
  statsd.host.must_equal '127.0.0.1'
24
36
  statsd.port.must_equal 8125
25
37
  end
38
+
39
+ it "should set delimiter to period by default" do
40
+ @statsd.delimiter.must_equal "."
41
+ end
26
42
  end
27
43
 
28
44
  describe "#host and #port" do
@@ -47,6 +63,23 @@ describe Statsd do
47
63
  @statsd.port = nil
48
64
  @statsd.port.must_equal 8125
49
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 "."
82
+ end
50
83
  end
51
84
 
52
85
  describe "#increment" do
@@ -137,6 +170,18 @@ describe Statsd do
137
170
  result.must_equal 'test'
138
171
  end
139
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
183
+ end
184
+
140
185
  describe "with a sample rate" do
141
186
  before { class << @statsd; def rand; 0; end; end } # ensure delivery
142
187
 
@@ -273,6 +318,19 @@ describe Statsd do
273
318
  @socket.recv.must_equal ['Statsd.SomeClass:1|c']
274
319
  end
275
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
332
+ end
333
+
276
334
  it "should replace statsd reserved chars in the stat name" do
277
335
  @statsd.increment('ray@hostname.blah|blah.blah:blah', 1)
278
336
  @socket.recv.must_equal ['ray_hostname.blah_blah.blah_blah:1|c']
@@ -283,7 +341,7 @@ describe Statsd do
283
341
  before do
284
342
  require 'stringio'
285
343
  Statsd.logger = Logger.new(@log = StringIO.new)
286
- @socket.instance_eval { def send(*) raise SocketError end }
344
+ @socket.instance_eval { def write(*) raise SocketError end }
287
345
  end
288
346
 
289
347
  it "should ignore socket errors" do
@@ -353,32 +411,115 @@ describe Statsd do
353
411
 
354
412
  end
355
413
 
356
- describe "thread safety" do
357
-
358
- it "should use a thread local socket" do
359
- Thread.current[:statsd_socket].must_equal @socket
360
- @statsd.send(:socket).must_equal @socket
361
- end
362
-
363
- it "should create a new socket when used in a new thread" do
364
- sock = @statsd.send(:socket)
365
- Thread.new { Thread.current[:statsd_socket] }.value.wont_equal sock
414
+ describe "#connect" do
415
+ it "should reconnect" do
416
+ c = $connect_count
417
+ @statsd.connect
418
+ ($connect_count - c).must_equal 1
366
419
  end
367
-
368
420
  end
421
+
369
422
  end
370
423
 
371
424
  describe Statsd do
372
425
  describe "with a real UDP socket" do
373
426
  it "should actually send stuff over the socket" do
374
- socket = UDPSocket.new
375
- host, port = 'localhost', 12345
376
- socket.bind(host, port)
427
+ family = Addrinfo.udp(UDPSocket.getaddress('localhost'), 0).afamily
428
+ begin
429
+ socket = UDPSocket.new family
430
+ host, port = 'localhost', 0
431
+ socket.bind(host, port)
432
+ port = socket.addr[1]
433
+
434
+ statsd = Statsd.new(host, port)
435
+ statsd.increment('foobar')
436
+ message = socket.recvfrom(16).first
437
+ message.must_equal 'foobar:1|c'
438
+ ensure
439
+ socket.close
440
+ end
441
+ end
442
+
443
+ it "should send stuff over an IPv4 socket" do
444
+ begin
445
+ socket = UDPSocket.new Socket::AF_INET
446
+ host, port = '127.0.0.1', 0
447
+ socket.bind(host, port)
448
+ port = socket.addr[1]
377
449
 
378
- statsd = Statsd.new(host, port)
379
- statsd.increment('foobar')
380
- message = socket.recvfrom(16).first
381
- message.must_equal 'foobar:1|c'
450
+ statsd = Statsd.new(host, port)
451
+ statsd.increment('foobar')
452
+ message = socket.recvfrom(16).first
453
+ message.must_equal 'foobar:1|c'
454
+ ensure
455
+ socket.close
456
+ end
457
+ end
458
+
459
+ it "should send stuff over an IPv6 socket" do
460
+ begin
461
+ socket = UDPSocket.new Socket::AF_INET6
462
+ host, port = '::1', 0
463
+ socket.bind(host, port)
464
+ port = socket.addr[1]
465
+
466
+ statsd = Statsd.new(host, port)
467
+ statsd.increment('foobar')
468
+ message = socket.recvfrom(16).first
469
+ message.must_equal 'foobar:1|c'
470
+ ensure
471
+ socket.close
472
+ end
382
473
  end
383
474
  end
384
- end if ENV['LIVE']
475
+
476
+ describe "supports TCP sockets" do
477
+ it "should connect to and send stats over TCPv4" do
478
+ begin
479
+ host, port = '127.0.0.1', 0
480
+ server = TCPServer.new host, port
481
+ port = server.addr[1]
482
+
483
+ socket = nil
484
+ Thread.new { socket = server.accept }
485
+
486
+ statsd = Statsd.new(host, port, :tcp)
487
+ statsd.increment('foobar')
488
+
489
+ Timeout.timeout(5) do
490
+ Thread.pass while socket == nil
491
+ end
492
+
493
+ message = socket.recvfrom(16).first
494
+ message.must_equal 'foobar:1|c'
495
+ ensure
496
+ socket.close if socket
497
+ server.close
498
+ end
499
+ end
500
+
501
+ it "should connect to and send stats over TCPv6" do
502
+ begin
503
+ host, port = '::1', 0
504
+ server = TCPServer.new host, port
505
+ port = server.addr[1]
506
+
507
+ socket = nil
508
+ Thread.new { socket = server.accept }
509
+
510
+ statsd = Statsd.new(host, port, :tcp)
511
+ statsd.increment('foobar')
512
+
513
+ Timeout.timeout(5) do
514
+ Thread.pass while socket == nil
515
+ end
516
+
517
+ message = socket.recvfrom(16).first
518
+ message.must_equal 'foobar:1|c'
519
+ ensure
520
+ socket.close if socket
521
+ server.close
522
+ end
523
+ end
524
+ end
525
+ end
data/statsd-ruby.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
- Gem::Specification.new("statsd-ruby", "1.2.1") do |s|
3
+ Gem::Specification.new("statsd-ruby", "1.3.0") do |s|
4
4
  s.authors = ["Rein Henrichs"]
5
5
  s.email = "reinh@reinh.com"
6
6
 
metadata CHANGED
@@ -1,69 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statsd-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.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: 2013-06-12 00:00:00.000000000 Z
11
+ date: 2016-01-17 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: 3.2.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: 3.2.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'
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'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: simplecov
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ! '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: 0.6.4
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: 0.6.4
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
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 StatsD client (https://github.com/etsy/statsd)
@@ -74,9 +74,9 @@ extra_rdoc_files:
74
74
  - LICENSE.txt
75
75
  - README.rdoc
76
76
  files:
77
- - .document
78
- - .gitignore
79
- - .travis.yml
77
+ - ".document"
78
+ - ".gitignore"
79
+ - ".travis.yml"
80
80
  - Gemfile
81
81
  - LICENSE.txt
82
82
  - README.rdoc
@@ -84,6 +84,7 @@ files:
84
84
  - lib/statsd-ruby.rb
85
85
  - lib/statsd.rb
86
86
  - spec/helper.rb
87
+ - spec/statsd_admin_spec.rb
87
88
  - spec/statsd_spec.rb
88
89
  - statsd-ruby.gemspec
89
90
  homepage: https://github.com/reinh/statsd
@@ -96,21 +97,22 @@ require_paths:
96
97
  - lib
97
98
  required_ruby_version: !ruby/object:Gem::Requirement
98
99
  requirements:
99
- - - ! '>='
100
+ - - ">="
100
101
  - !ruby/object:Gem::Version
101
102
  version: '0'
102
103
  required_rubygems_version: !ruby/object:Gem::Requirement
103
104
  requirements:
104
- - - ! '>='
105
+ - - ">="
105
106
  - !ruby/object:Gem::Version
106
107
  version: '0'
107
108
  requirements: []
108
109
  rubyforge_project:
109
- rubygems_version: 2.0.3
110
+ rubygems_version: 2.5.1
110
111
  signing_key:
111
112
  specification_version: 4
112
113
  summary: A Ruby StatsD client
113
114
  test_files:
114
115
  - spec/helper.rb
116
+ - spec/statsd_admin_spec.rb
115
117
  - spec/statsd_spec.rb
116
118
  has_rdoc: