statsd-ruby-tcp 0.0.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 +7 -0
- data/.document +5 -0
- data/.github/workflows/ci.yml +34 -0
- data/.gitignore +43 -0
- data/.travis.yml +19 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +86 -0
- data/Rakefile +15 -0
- data/lib/statsd-ruby.rb +1 -0
- data/lib/statsd.rb +513 -0
- data/lib/statsd/monotonic_time.rb +35 -0
- data/spec/helper.rb +43 -0
- data/spec/statsd_admin_spec.rb +117 -0
- data/spec/statsd_spec.rb +593 -0
- data/statsd-ruby-tcp.gemspec +24 -0
- metadata +151 -0
@@ -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
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.start
|
5
|
+
|
6
|
+
require 'minitest/autorun'
|
7
|
+
require 'statsd'
|
8
|
+
require 'logger'
|
9
|
+
require 'timeout'
|
10
|
+
|
11
|
+
class FakeUDPSocket
|
12
|
+
def initialize
|
13
|
+
@buffer = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def write(message)
|
17
|
+
@buffer.push [message]
|
18
|
+
message.length
|
19
|
+
end
|
20
|
+
|
21
|
+
def recv
|
22
|
+
@buffer.shift
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear
|
26
|
+
@buffer = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
inspect
|
31
|
+
end
|
32
|
+
|
33
|
+
def 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
|
42
|
+
end
|
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
|
+
|
data/spec/statsd_spec.rb
ADDED
@@ -0,0 +1,593 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Statsd do
|
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
|
+
|
15
|
+
@statsd = Statsd.new('localhost', 1234)
|
16
|
+
@socket = @statsd.instance_variable_set(:@socket, FakeUDPSocket.new)
|
17
|
+
end
|
18
|
+
|
19
|
+
after do
|
20
|
+
class Statsd
|
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
|
+
@statsd.host.must_equal 'localhost'
|
30
|
+
@statsd.port.must_equal 1234
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should default the host to 127.0.0.1 and port to 8125" do
|
34
|
+
statsd = Statsd.new
|
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 "."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#host and #port" do
|
45
|
+
it "should set host and port" do
|
46
|
+
@statsd.host = '1.2.3.4'
|
47
|
+
@statsd.port = 5678
|
48
|
+
@statsd.host.must_equal '1.2.3.4'
|
49
|
+
@statsd.port.must_equal 5678
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should not resolve hostnames to IPs" do
|
53
|
+
@statsd.host = 'localhost'
|
54
|
+
@statsd.host.must_equal 'localhost'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should set nil host to default" do
|
58
|
+
@statsd.host = nil
|
59
|
+
@statsd.host.must_equal '127.0.0.1'
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should set nil port to default" do
|
63
|
+
@statsd.port = nil
|
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 "."
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#increment" do
|
86
|
+
it "should format the message according to the statsd spec" do
|
87
|
+
@statsd.increment('foobar')
|
88
|
+
@socket.recv.must_equal ['foobar:1|c']
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "with a sample rate" do
|
92
|
+
before { class << @statsd; def rand; 0; end; end } # ensure delivery
|
93
|
+
it "should format the message according to the statsd spec" do
|
94
|
+
@statsd.increment('foobar', 0.5)
|
95
|
+
@socket.recv.must_equal ['foobar:1|c|@0.5']
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "#decrement" do
|
101
|
+
it "should format the message according to the statsd spec" do
|
102
|
+
@statsd.decrement('foobar')
|
103
|
+
@socket.recv.must_equal ['foobar:-1|c']
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "with a sample rate" do
|
107
|
+
before { class << @statsd; def rand; 0; end; end } # ensure delivery
|
108
|
+
it "should format the message according to the statsd spec" do
|
109
|
+
@statsd.decrement('foobar', 0.5)
|
110
|
+
@socket.recv.must_equal ['foobar:-1|c|@0.5']
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "#gauge" do
|
116
|
+
it "should send a message with a 'g' type, per the nearbuy fork" do
|
117
|
+
@statsd.gauge('begrutten-suffusion', 536)
|
118
|
+
@socket.recv.must_equal ['begrutten-suffusion:536|g']
|
119
|
+
@statsd.gauge('begrutten-suffusion', -107.3)
|
120
|
+
@socket.recv.must_equal ['begrutten-suffusion:-107.3|g']
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "with a sample rate" do
|
124
|
+
before { class << @statsd; def rand; 0; end; end } # ensure delivery
|
125
|
+
it "should format the message according to the statsd spec" do
|
126
|
+
@statsd.gauge('begrutten-suffusion', 536, 0.1)
|
127
|
+
@socket.recv.must_equal ['begrutten-suffusion:536|g|@0.1']
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "#timing" do
|
133
|
+
it "should format the message according to the statsd spec" do
|
134
|
+
@statsd.timing('foobar', 500)
|
135
|
+
@socket.recv.must_equal ['foobar:500|ms']
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "with a sample rate" do
|
139
|
+
before { class << @statsd; def rand; 0; end; end } # ensure delivery
|
140
|
+
it "should format the message according to the statsd spec" do
|
141
|
+
@statsd.timing('foobar', 500, 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']
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "#time" do
|
163
|
+
it "should format the message according to the statsd spec" do
|
164
|
+
@statsd.time('foobar') { 'test' }
|
165
|
+
@socket.recv.must_equal ['foobar:0|ms']
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should return the result of the block" do
|
169
|
+
result = @statsd.time('foobar') { '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
|
183
|
+
end
|
184
|
+
|
185
|
+
describe "with a sample rate" do
|
186
|
+
before { class << @statsd; def rand; 0; end; end } # ensure delivery
|
187
|
+
|
188
|
+
it "should format the message according to the statsd spec" do
|
189
|
+
@statsd.time('foobar', 0.5) { 'test' }
|
190
|
+
@socket.recv.must_equal ['foobar:0|ms|@0.5']
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe "#sampled" do
|
196
|
+
describe "when the sample rate is 1" do
|
197
|
+
before { class << @statsd; def rand; raise end; end }
|
198
|
+
it "should send" do
|
199
|
+
@statsd.timing('foobar', 500, 1)
|
200
|
+
@socket.recv.must_equal ['foobar:500|ms']
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "when the sample rate is greater than a random value [0,1]" do
|
205
|
+
before { class << @statsd; def rand; 0; end; end } # ensure delivery
|
206
|
+
it "should send" do
|
207
|
+
@statsd.timing('foobar', 500, 0.5)
|
208
|
+
@socket.recv.must_equal ['foobar:500|ms|@0.5']
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe "when the sample rate is less than a random value [0,1]" do
|
213
|
+
before { class << @statsd; def rand; 1; end; end } # ensure no delivery
|
214
|
+
it "should not send" do
|
215
|
+
assert_nil @statsd.timing('foobar', 500, 0.5)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe "when the sample rate is equal to a random value [0,1]" do
|
220
|
+
before { class << @statsd; def rand; 0; end; end } # ensure delivery
|
221
|
+
it "should send" do
|
222
|
+
@statsd.timing('foobar', 500, 0.5)
|
223
|
+
@socket.recv.must_equal ['foobar:500|ms|@0.5']
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe "with namespace" do
|
229
|
+
before { @statsd.namespace = 'service' }
|
230
|
+
|
231
|
+
it "should add namespace to increment" do
|
232
|
+
@statsd.increment('foobar')
|
233
|
+
@socket.recv.must_equal ['service.foobar:1|c']
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should add namespace to decrement" do
|
237
|
+
@statsd.decrement('foobar')
|
238
|
+
@socket.recv.must_equal ['service.foobar:-1|c']
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should add namespace to timing" do
|
242
|
+
@statsd.timing('foobar', 500)
|
243
|
+
@socket.recv.must_equal ['service.foobar:500|ms']
|
244
|
+
end
|
245
|
+
|
246
|
+
it "should add namespace to gauge" do
|
247
|
+
@statsd.gauge('foobar', 500)
|
248
|
+
@socket.recv.must_equal ['service.foobar:500|g']
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
describe "with postfix" do
|
253
|
+
before { @statsd.postfix = 'ip-23-45-56-78' }
|
254
|
+
|
255
|
+
it "should add postfix to increment" do
|
256
|
+
@statsd.increment('foobar')
|
257
|
+
@socket.recv.must_equal ['foobar.ip-23-45-56-78:1|c']
|
258
|
+
end
|
259
|
+
|
260
|
+
it "should add postfix to decrement" do
|
261
|
+
@statsd.decrement('foobar')
|
262
|
+
@socket.recv.must_equal ['foobar.ip-23-45-56-78:-1|c']
|
263
|
+
end
|
264
|
+
|
265
|
+
it "should add namespace to timing" do
|
266
|
+
@statsd.timing('foobar', 500)
|
267
|
+
@socket.recv.must_equal ['foobar.ip-23-45-56-78:500|ms']
|
268
|
+
end
|
269
|
+
|
270
|
+
it "should add namespace to gauge" do
|
271
|
+
@statsd.gauge('foobar', 500)
|
272
|
+
@socket.recv.must_equal ['foobar.ip-23-45-56-78:500|g']
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
describe '#postfix=' do
|
277
|
+
describe "when nil, false, or empty" do
|
278
|
+
it "should set postfix to nil" do
|
279
|
+
[nil, false, ''].each do |value|
|
280
|
+
@statsd.postfix = 'a postfix'
|
281
|
+
@statsd.postfix = value
|
282
|
+
assert_nil @statsd.postfix
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
describe "with logging" do
|
289
|
+
require 'stringio'
|
290
|
+
before { Statsd.logger = Logger.new(@log = StringIO.new)}
|
291
|
+
|
292
|
+
it "should write to the log in debug" do
|
293
|
+
Statsd.logger.level = Logger::DEBUG
|
294
|
+
|
295
|
+
@statsd.increment('foobar')
|
296
|
+
|
297
|
+
@log.string.must_match "Statsd: foobar:1|c"
|
298
|
+
end
|
299
|
+
|
300
|
+
it "should not write to the log unless debug" do
|
301
|
+
Statsd.logger.level = Logger::INFO
|
302
|
+
|
303
|
+
@statsd.increment('foobar')
|
304
|
+
|
305
|
+
@log.string.must_be_empty
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
describe "stat names" do
|
310
|
+
it "should accept anything as stat" do
|
311
|
+
@statsd.increment(Object, 1)
|
312
|
+
end
|
313
|
+
|
314
|
+
it "should replace ruby constant delimeter with graphite package name" do
|
315
|
+
class Statsd::SomeClass; end
|
316
|
+
@statsd.increment(Statsd::SomeClass, 1)
|
317
|
+
|
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
|
332
|
+
end
|
333
|
+
|
334
|
+
it "should replace statsd reserved chars in the stat name" do
|
335
|
+
@statsd.increment('ray@hostname.blah|blah.blah:blah', 1)
|
336
|
+
@socket.recv.must_equal ['ray_hostname.blah_blah.blah_blah:1|c']
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
describe "handling socket errors" do
|
341
|
+
before do
|
342
|
+
require 'stringio'
|
343
|
+
Statsd.logger = Logger.new(@log = StringIO.new)
|
344
|
+
@socket.instance_eval { def write(*) raise SocketError end }
|
345
|
+
end
|
346
|
+
|
347
|
+
it "should ignore socket errors" do
|
348
|
+
assert_nil @statsd.increment('foobar')
|
349
|
+
end
|
350
|
+
|
351
|
+
it "should log socket errors" do
|
352
|
+
@statsd.increment('foobar')
|
353
|
+
@log.string.must_match 'Statsd: SocketError'
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
describe "batching" do
|
358
|
+
it "should have a default batch size of 10" do
|
359
|
+
@statsd.batch_size.must_equal 10
|
360
|
+
end
|
361
|
+
|
362
|
+
it "should have a default batch byte size of nil" do
|
363
|
+
assert_nil @statsd.batch_byte_size
|
364
|
+
end
|
365
|
+
|
366
|
+
it "should have a default flush interval of nil" do
|
367
|
+
assert_nil @statsd.flush_interval
|
368
|
+
end
|
369
|
+
|
370
|
+
it "should have a modifiable batch size" do
|
371
|
+
@statsd.batch_size = 7
|
372
|
+
@statsd.batch_size.must_equal 7
|
373
|
+
@statsd.batch do |b|
|
374
|
+
b.batch_size.must_equal 7
|
375
|
+
end
|
376
|
+
|
377
|
+
@statsd.batch_size = nil
|
378
|
+
@statsd.batch_byte_size = 1472
|
379
|
+
@statsd.batch do |b|
|
380
|
+
assert_nil b.batch_size
|
381
|
+
b.batch_byte_size.must_equal 1472
|
382
|
+
end
|
383
|
+
|
384
|
+
end
|
385
|
+
|
386
|
+
it 'should have a modifiable flush interval' do
|
387
|
+
@statsd.flush_interval = 1
|
388
|
+
@statsd.flush_interval.must_equal 1
|
389
|
+
@statsd.batch do |b|
|
390
|
+
b.flush_interval.must_equal 1
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
it "should flush the batch at the batch size or at the end of the block" do
|
395
|
+
@statsd.batch do |b|
|
396
|
+
b.batch_size = 3
|
397
|
+
|
398
|
+
# The first three should flush, the next two will be flushed when the
|
399
|
+
# block is done.
|
400
|
+
5.times { b.increment('foobar') }
|
401
|
+
|
402
|
+
@socket.recv.must_equal [(["foobar:1|c"] * 3).join("\n")]
|
403
|
+
end
|
404
|
+
|
405
|
+
@socket.recv.must_equal [(["foobar:1|c"] * 2).join("\n")]
|
406
|
+
end
|
407
|
+
|
408
|
+
it "should flush based on batch byte size" do
|
409
|
+
@statsd.batch do |b|
|
410
|
+
b.batch_size = nil
|
411
|
+
b.batch_byte_size = 22
|
412
|
+
|
413
|
+
# The first two should flush, the last will be flushed when the
|
414
|
+
# block is done.
|
415
|
+
3.times { b.increment('foobar') }
|
416
|
+
|
417
|
+
@socket.recv.must_equal [(["foobar:1|c"] * 2).join("\n")]
|
418
|
+
end
|
419
|
+
|
420
|
+
@socket.recv.must_equal ["foobar:1|c"]
|
421
|
+
end
|
422
|
+
|
423
|
+
it "should flush immediately when the queue is exactly a batch size" do
|
424
|
+
@statsd.batch do |b|
|
425
|
+
b.batch_size = nil
|
426
|
+
b.batch_byte_size = 21
|
427
|
+
|
428
|
+
# The first two should flush together
|
429
|
+
2.times { b.increment('foobar') }
|
430
|
+
|
431
|
+
@socket.recv.must_equal [(["foobar:1|c"] * 2).join("\n")]
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
it "should flush when the interval has passed" do
|
436
|
+
@statsd.batch do |b|
|
437
|
+
b.batch_size = nil
|
438
|
+
b.flush_interval = 0.01
|
439
|
+
|
440
|
+
# The first two should flush, the last will be flushed when the
|
441
|
+
# block is done.
|
442
|
+
2.times { b.increment('foobar') }
|
443
|
+
sleep(0.03)
|
444
|
+
b.increment('foobar')
|
445
|
+
|
446
|
+
@socket.recv.must_equal [(["foobar:1|c"] * 2).join("\n")]
|
447
|
+
end
|
448
|
+
|
449
|
+
@socket.recv.must_equal ["foobar:1|c"]
|
450
|
+
end
|
451
|
+
|
452
|
+
it "should not flush to the socket if the backlog is empty" do
|
453
|
+
batch = Statsd::Batch.new(@statsd)
|
454
|
+
batch.flush
|
455
|
+
@socket.recv.must_be :nil?
|
456
|
+
|
457
|
+
batch.increment 'foobar'
|
458
|
+
batch.flush
|
459
|
+
@socket.recv.must_equal %w[foobar:1|c]
|
460
|
+
end
|
461
|
+
|
462
|
+
it "should support setting namespace for the underlying instance" do
|
463
|
+
batch = Statsd::Batch.new(@statsd)
|
464
|
+
batch.namespace = 'ns'
|
465
|
+
@statsd.namespace.must_equal 'ns'
|
466
|
+
end
|
467
|
+
|
468
|
+
it "should support setting host for the underlying instance" do
|
469
|
+
batch = Statsd::Batch.new(@statsd)
|
470
|
+
batch.host = '1.2.3.4'
|
471
|
+
@statsd.host.must_equal '1.2.3.4'
|
472
|
+
end
|
473
|
+
|
474
|
+
it "should support setting port for the underlying instance" do
|
475
|
+
batch = Statsd::Batch.new(@statsd)
|
476
|
+
batch.port = 42
|
477
|
+
@statsd.port.must_equal 42
|
478
|
+
end
|
479
|
+
|
480
|
+
end
|
481
|
+
|
482
|
+
describe "#connect" do
|
483
|
+
it "should reconnect" do
|
484
|
+
c = $connect_count
|
485
|
+
@statsd.connect
|
486
|
+
($connect_count - c).must_equal 1
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
end
|
491
|
+
|
492
|
+
describe Statsd do
|
493
|
+
describe "with a real UDP socket" do
|
494
|
+
it "should actually send stuff over the socket" do
|
495
|
+
family = Addrinfo.udp(UDPSocket.getaddress('localhost'), 0).afamily
|
496
|
+
begin
|
497
|
+
socket = UDPSocket.new family
|
498
|
+
host, port = 'localhost', 0
|
499
|
+
socket.bind(host, port)
|
500
|
+
port = socket.addr[1]
|
501
|
+
|
502
|
+
statsd = Statsd.new(host, port)
|
503
|
+
statsd.increment('foobar')
|
504
|
+
message = socket.recvfrom(16).first
|
505
|
+
message.must_equal 'foobar:1|c'
|
506
|
+
ensure
|
507
|
+
socket.close
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
it "should send stuff over an IPv4 socket" do
|
512
|
+
begin
|
513
|
+
socket = UDPSocket.new Socket::AF_INET
|
514
|
+
host, port = '127.0.0.1', 0
|
515
|
+
socket.bind(host, port)
|
516
|
+
port = socket.addr[1]
|
517
|
+
|
518
|
+
statsd = Statsd.new(host, port)
|
519
|
+
statsd.increment('foobar')
|
520
|
+
message = socket.recvfrom(16).first
|
521
|
+
message.must_equal 'foobar:1|c'
|
522
|
+
ensure
|
523
|
+
socket.close
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
it "should send stuff over an IPv6 socket" do
|
528
|
+
begin
|
529
|
+
socket = UDPSocket.new Socket::AF_INET6
|
530
|
+
host, port = '::1', 0
|
531
|
+
socket.bind(host, port)
|
532
|
+
port = socket.addr[1]
|
533
|
+
|
534
|
+
statsd = Statsd.new(host, port)
|
535
|
+
statsd.increment('foobar')
|
536
|
+
message = socket.recvfrom(16).first
|
537
|
+
message.must_equal 'foobar:1|c'
|
538
|
+
ensure
|
539
|
+
socket.close
|
540
|
+
end
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
describe "supports TCP sockets" do
|
545
|
+
it "should connect to and send stats over TCPv4" do
|
546
|
+
begin
|
547
|
+
host, port = '127.0.0.1', 0
|
548
|
+
server = TCPServer.new host, port
|
549
|
+
port = server.addr[1]
|
550
|
+
|
551
|
+
socket = nil
|
552
|
+
Thread.new { socket = server.accept }
|
553
|
+
|
554
|
+
statsd = Statsd.new(host, port, :tcp)
|
555
|
+
statsd.increment('foobar')
|
556
|
+
|
557
|
+
Timeout.timeout(5) do
|
558
|
+
Thread.pass while socket == nil
|
559
|
+
end
|
560
|
+
|
561
|
+
message = socket.recvfrom(16).first
|
562
|
+
message.must_equal "foobar:1|c\n"
|
563
|
+
ensure
|
564
|
+
socket.close if socket
|
565
|
+
server.close
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
it "should connect to and send stats over TCPv6" do
|
570
|
+
begin
|
571
|
+
host, port = '::1', 0
|
572
|
+
server = TCPServer.new host, port
|
573
|
+
port = server.addr[1]
|
574
|
+
|
575
|
+
socket = nil
|
576
|
+
Thread.new { socket = server.accept }
|
577
|
+
|
578
|
+
statsd = Statsd.new(host, port, :tcp)
|
579
|
+
statsd.increment('foobar')
|
580
|
+
|
581
|
+
Timeout.timeout(5) do
|
582
|
+
Thread.pass while socket == nil
|
583
|
+
end
|
584
|
+
|
585
|
+
message = socket.recvfrom(16).first
|
586
|
+
message.must_equal "foobar:1|c\n"
|
587
|
+
ensure
|
588
|
+
socket.close if socket
|
589
|
+
server.close
|
590
|
+
end
|
591
|
+
end
|
592
|
+
end
|
593
|
+
end
|