statsd-ruby 1.2.1 → 1.3.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.
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: