raindrops 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/GIT-VERSION-GEN +1 -1
- data/TODO +1 -1
- data/lib/raindrops/watcher.rb +77 -37
- data/test/test_aggregate_pmq.rb +1 -1
- data/test/test_last_data_recv_unicorn.rb +2 -2
- data/test/test_middleware_unicorn.rb +1 -1
- data/test/test_middleware_unicorn_ipv6.rb +1 -1
- data/test/test_watcher.rb +76 -0
- metadata +4 -4
data/GIT-VERSION-GEN
CHANGED
data/TODO
CHANGED
@@ -1 +1 @@
|
|
1
|
-
* pure Ruby version for non-forking servers
|
1
|
+
* pure Ruby version for non-forking servers (patches welcome!)
|
data/lib/raindrops/watcher.rb
CHANGED
@@ -58,6 +58,10 @@ require "aggregate"
|
|
58
58
|
#
|
59
59
|
# e.g.: curl http://raindrops-demo.bogomips.org/queued/0.0.0.0%3A80.html
|
60
60
|
#
|
61
|
+
# === POST /reset/$LISTENER
|
62
|
+
#
|
63
|
+
# Resets the active and queued statistics for the given listener.
|
64
|
+
#
|
61
65
|
# === GET /tail/$LISTENER.txt?active_min=1&queued_min=1
|
62
66
|
#
|
63
67
|
# Streams chunked a response to the client.
|
@@ -85,6 +89,9 @@ require "aggregate"
|
|
85
89
|
# - X-Std-Dev - standard deviation of connection count
|
86
90
|
# - X-Outliers-Low - number of low outliers (hopefully many for queued)
|
87
91
|
# - X-Outliers-High - number of high outliers (hopefully zero for queued)
|
92
|
+
# - X-Current - current number of connections
|
93
|
+
# - X-First-Peak-At - date of when X-Max was first reached
|
94
|
+
# - X-Last-Peak-At - date of when X-Max was last reached
|
88
95
|
#
|
89
96
|
# = Demo Server
|
90
97
|
#
|
@@ -100,6 +107,7 @@ class Raindrops::Watcher
|
|
100
107
|
include Rack::Utils
|
101
108
|
include Raindrops::Linux
|
102
109
|
DOC_URL = "http://raindrops.bogomips.org/Raindrops/Watcher.html"
|
110
|
+
Peak = Struct.new(:first, :last)
|
103
111
|
|
104
112
|
def initialize(opts = {})
|
105
113
|
@tcp_listeners = @unix_listeners = nil
|
@@ -112,12 +120,14 @@ class Raindrops::Watcher
|
|
112
120
|
end
|
113
121
|
end
|
114
122
|
|
115
|
-
agg_class = opts[:agg_class] || Aggregate
|
116
|
-
|
117
|
-
@active = Hash.new { |h,k| h[k] = agg_class.new }
|
118
|
-
@queued = Hash.new { |h,k| h[k] = agg_class.new }
|
119
|
-
@resets = Hash.new { |h,k| h[k] =
|
120
|
-
@
|
123
|
+
@agg_class = opts[:agg_class] || Aggregate
|
124
|
+
@start_time = Time.now.utc
|
125
|
+
@active = Hash.new { |h,k| h[k] = @agg_class.new }
|
126
|
+
@queued = Hash.new { |h,k| h[k] = @agg_class.new }
|
127
|
+
@resets = Hash.new { |h,k| h[k] = @start_time }
|
128
|
+
@peak_active = Hash.new { |h,k| h[k] = Peak.new(@start_time, @start_time) }
|
129
|
+
@peak_queued = Hash.new { |h,k| h[k] = Peak.new(@start_time, @start_time) }
|
130
|
+
@snapshot = [ @start_time, {} ]
|
121
131
|
@delay = opts[:delay] || 1
|
122
132
|
@lock = Mutex.new
|
123
133
|
@start = Mutex.new
|
@@ -142,6 +152,16 @@ class Raindrops::Watcher
|
|
142
152
|
end
|
143
153
|
end
|
144
154
|
|
155
|
+
def aggregate!(agg_hash, peak_hash, addr, number, now)
|
156
|
+
agg = agg_hash[addr]
|
157
|
+
if (max = agg.max) && number > 0 && number >= max
|
158
|
+
peak = peak_hash[addr]
|
159
|
+
peak.first = now if number > max
|
160
|
+
peak.last = now
|
161
|
+
end
|
162
|
+
agg << number
|
163
|
+
end
|
164
|
+
|
145
165
|
def aggregator_thread(logger) # :nodoc:
|
146
166
|
@socket = sock = Raindrops::InetDiagSocket.new
|
147
167
|
thr = Thread.new do
|
@@ -149,11 +169,12 @@ class Raindrops::Watcher
|
|
149
169
|
combined = tcp_listener_stats(@tcp_listeners, sock)
|
150
170
|
combined.merge!(unix_listener_stats(@unix_listeners))
|
151
171
|
@lock.synchronize do
|
172
|
+
now = Time.now.utc
|
152
173
|
combined.each do |addr,stats|
|
153
|
-
@active
|
154
|
-
@queued
|
174
|
+
aggregate!(@active, @peak_active, addr, stats.active, now)
|
175
|
+
aggregate!(@queued, @peak_queued, addr, stats.queued, now)
|
155
176
|
end
|
156
|
-
@snapshot = [
|
177
|
+
@snapshot = [ now, combined ]
|
157
178
|
@cond.broadcast
|
158
179
|
end
|
159
180
|
rescue => e
|
@@ -165,17 +186,25 @@ class Raindrops::Watcher
|
|
165
186
|
thr
|
166
187
|
end
|
167
188
|
|
189
|
+
def non_existent_stats(time)
|
190
|
+
[ time, @start_time, @agg_class.new, 0, Peak.new(@start_time, @start_time) ]
|
191
|
+
end
|
192
|
+
|
168
193
|
def active_stats(addr) # :nodoc:
|
169
194
|
@lock.synchronize do
|
170
|
-
|
171
|
-
|
195
|
+
time, combined = @snapshot
|
196
|
+
stats = combined[addr] or return non_existent_stats(time)
|
197
|
+
tmp, peak = @active[addr], @peak_active[addr]
|
198
|
+
[ time, @resets[addr], tmp.dup, stats.active, peak ]
|
172
199
|
end
|
173
200
|
end
|
174
201
|
|
175
202
|
def queued_stats(addr) # :nodoc:
|
176
203
|
@lock.synchronize do
|
177
|
-
|
178
|
-
|
204
|
+
time, combined = @snapshot
|
205
|
+
stats = combined[addr] or return non_existent_stats(time)
|
206
|
+
tmp, peak = @queued[addr], @peak_queued[addr]
|
207
|
+
[ time, @resets[addr], tmp.dup, stats.queued, peak ]
|
179
208
|
end
|
180
209
|
end
|
181
210
|
|
@@ -186,22 +215,31 @@ class Raindrops::Watcher
|
|
186
215
|
end
|
187
216
|
end
|
188
217
|
|
189
|
-
def
|
218
|
+
def std_dev(agg)
|
219
|
+
agg.std_dev.to_s
|
220
|
+
rescue Errno::EDOM
|
221
|
+
"NaN"
|
222
|
+
end
|
223
|
+
|
224
|
+
def agg_to_hash(reset_at, agg, current, peak)
|
190
225
|
{
|
191
226
|
"X-Count" => agg.count.to_s,
|
192
227
|
"X-Min" => agg.min.to_s,
|
193
228
|
"X-Max" => agg.max.to_s,
|
194
229
|
"X-Mean" => agg.mean.to_s,
|
195
|
-
"X-Std-Dev" => agg
|
230
|
+
"X-Std-Dev" => std_dev(agg),
|
196
231
|
"X-Outliers-Low" => agg.outliers_low.to_s,
|
197
232
|
"X-Outliers-High" => agg.outliers_high.to_s,
|
198
233
|
"X-Last-Reset" => reset_at.httpdate,
|
234
|
+
"X-Current" => current.to_s,
|
235
|
+
"X-First-Peak-At" => peak.first.httpdate,
|
236
|
+
"X-Last-Peak-At" => peak.last.httpdate,
|
199
237
|
}
|
200
238
|
end
|
201
239
|
|
202
240
|
def histogram_txt(agg)
|
203
|
-
updated_at, reset_at, agg = *agg
|
204
|
-
headers = agg_to_hash(reset_at, agg)
|
241
|
+
updated_at, reset_at, agg, current, peak = *agg
|
242
|
+
headers = agg_to_hash(reset_at, agg, current, peak)
|
205
243
|
body = agg.to_s
|
206
244
|
headers["Content-Type"] = "text/plain"
|
207
245
|
headers["Expires"] = (updated_at + @delay).httpdate
|
@@ -210,8 +248,8 @@ class Raindrops::Watcher
|
|
210
248
|
end
|
211
249
|
|
212
250
|
def histogram_html(agg, addr)
|
213
|
-
updated_at, reset_at, agg = *agg
|
214
|
-
headers = agg_to_hash(reset_at, agg)
|
251
|
+
updated_at, reset_at, agg, current, peak = *agg
|
252
|
+
headers = agg_to_hash(reset_at, agg, current, peak)
|
215
253
|
body = "<html>" \
|
216
254
|
"<head><title>#{hostname} - #{escape_html addr}</title></head>" \
|
217
255
|
"<body><table>" <<
|
@@ -229,29 +267,31 @@ class Raindrops::Watcher
|
|
229
267
|
|
230
268
|
def get(env)
|
231
269
|
retried = false
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
tail(
|
247
|
-
|
248
|
-
|
249
|
-
|
270
|
+
begin
|
271
|
+
case env["PATH_INFO"]
|
272
|
+
when "/"
|
273
|
+
index
|
274
|
+
when %r{\A/active/(.+)\.txt\z}
|
275
|
+
histogram_txt(active_stats(unescape($1)))
|
276
|
+
when %r{\A/active/(.+)\.html\z}
|
277
|
+
addr = unescape $1
|
278
|
+
histogram_html(active_stats(addr), addr)
|
279
|
+
when %r{\A/queued/(.+)\.txt\z}
|
280
|
+
histogram_txt(queued_stats(unescape($1)))
|
281
|
+
when %r{\A/queued/(.+)\.html\z}
|
282
|
+
addr = unescape $1
|
283
|
+
histogram_html(queued_stats(addr), addr)
|
284
|
+
when %r{\A/tail/(.+)\.txt\z}
|
285
|
+
tail(unescape($1), env)
|
286
|
+
else
|
287
|
+
not_found
|
288
|
+
end
|
250
289
|
rescue Errno::EDOM
|
251
290
|
raise if retried
|
252
291
|
retried = true
|
253
292
|
wait_snapshot
|
254
293
|
retry
|
294
|
+
end
|
255
295
|
end
|
256
296
|
|
257
297
|
def not_found
|
data/test/test_aggregate_pmq.rb
CHANGED
@@ -6,7 +6,7 @@ require "net/http"
|
|
6
6
|
$stderr.sync = $stdout.sync = true
|
7
7
|
pmq = begin
|
8
8
|
Raindrops::Aggregate::PMQ
|
9
|
-
rescue =>
|
9
|
+
rescue LoadError => e
|
10
10
|
warn "W: #{e} skipping test"
|
11
11
|
false
|
12
12
|
end
|
@@ -40,7 +40,7 @@ class TestLastDataRecvUnicorn < Test::Unit::TestCase
|
|
40
40
|
end.to_app!
|
41
41
|
def app.arity; 0; end
|
42
42
|
def app.call; eval self; end
|
43
|
-
Unicorn.
|
43
|
+
Unicorn::HttpServer.new(app, @opts).start.join
|
44
44
|
}
|
45
45
|
400.times { assert_kind_of Net::HTTPSuccess, get("/") }
|
46
46
|
resp = get("/ldr")
|
@@ -19,7 +19,7 @@ class TestMiddlewareUnicorn < Test::Unit::TestCase
|
|
19
19
|
use Raindrops::Middleware
|
20
20
|
run Rack::Lobster.new
|
21
21
|
end
|
22
|
-
@srv = fork { Unicorn.
|
22
|
+
@srv = fork { Unicorn::HttpServer.new(@app, @opts).start.join }
|
23
23
|
|
24
24
|
s = TCPSocket.new @host, @port
|
25
25
|
s.write "GET /_raindrops HTTP/1.0\r\n\r\n"
|
@@ -20,7 +20,7 @@ class TestMiddlewareUnicornIPv6 < Test::Unit::TestCase
|
|
20
20
|
use Raindrops::Middleware
|
21
21
|
run Rack::Lobster.new
|
22
22
|
end
|
23
|
-
@srv = fork { Unicorn.
|
23
|
+
@srv = fork { Unicorn::HttpServer.new(@app, @opts).start.join }
|
24
24
|
s = TCPSocket.new @host, @port
|
25
25
|
s.write "GET /_raindrops HTTP/1.0\r\n\r\n"
|
26
26
|
resp = s.read
|
data/test/test_watcher.rb
CHANGED
@@ -43,6 +43,20 @@ class TestWatcher < Test::Unit::TestCase
|
|
43
43
|
check_headers(resp.headers)
|
44
44
|
end
|
45
45
|
|
46
|
+
def test_invalid
|
47
|
+
assert_nothing_raised do
|
48
|
+
@req.get("/active/666.666.666.666%3A666.txt")
|
49
|
+
@req.get("/queued/666.666.666.666%3A666.txt")
|
50
|
+
@req.get("/active/666.666.666.666%3A666.html")
|
51
|
+
@req.get("/queued/666.666.666.666%3A666.html")
|
52
|
+
end
|
53
|
+
addr = @app.instance_eval do
|
54
|
+
@peak_active.keys + @peak_queued.keys +
|
55
|
+
@resets.keys + @active.keys + @queued.keys
|
56
|
+
end
|
57
|
+
assert addr.grep(/666\.666\.666\.666/).empty?, addr.inspect
|
58
|
+
end
|
59
|
+
|
46
60
|
def test_active_html
|
47
61
|
resp = @req.get "/active/#@addr.html"
|
48
62
|
assert_equal 200, resp.status.to_i
|
@@ -96,4 +110,66 @@ class TestWatcher < Test::Unit::TestCase
|
|
96
110
|
break
|
97
111
|
end
|
98
112
|
end
|
113
|
+
|
114
|
+
def test_x_current_header
|
115
|
+
env = @req.class.env_for "/active/#@addr.txt"
|
116
|
+
status, headers, body = @app.call(env)
|
117
|
+
assert_equal "0", headers["X-Current"], headers.inspect
|
118
|
+
|
119
|
+
env = @req.class.env_for "/queued/#@addr.txt"
|
120
|
+
status, headers, body = @app.call(env)
|
121
|
+
assert_equal "1", headers["X-Current"], headers.inspect
|
122
|
+
|
123
|
+
@ios << @srv.accept
|
124
|
+
sleep 0.1
|
125
|
+
|
126
|
+
env = @req.class.env_for "/queued/#@addr.txt"
|
127
|
+
status, headers, body = @app.call(env)
|
128
|
+
assert_equal "0", headers["X-Current"], headers.inspect
|
129
|
+
|
130
|
+
env = @req.class.env_for "/active/#@addr.txt"
|
131
|
+
status, headers, body = @app.call(env)
|
132
|
+
assert_equal "1", headers["X-Current"], headers.inspect
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_peaks
|
136
|
+
env = @req.class.env_for "/active/#@addr.txt"
|
137
|
+
status, headers, body = @app.call(env.dup)
|
138
|
+
start = headers["X-First-Peak-At"]
|
139
|
+
assert headers["X-First-Peak-At"], headers.inspect
|
140
|
+
assert headers["X-Last-Peak-At"], headers.inspect
|
141
|
+
assert_nothing_raised { Time.parse(headers["X-First-Peak-At"]) }
|
142
|
+
assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) }
|
143
|
+
before = headers["X-Last-Peak-At"]
|
144
|
+
|
145
|
+
env = @req.class.env_for "/queued/#@addr.txt"
|
146
|
+
status, headers, body = @app.call(env)
|
147
|
+
assert_nothing_raised { Time.parse(headers["X-First-Peak-At"]) }
|
148
|
+
assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) }
|
149
|
+
assert_equal before, headers["X-Last-Peak-At"], "should not change"
|
150
|
+
|
151
|
+
sleep 2
|
152
|
+
env = @req.class.env_for "/active/#@addr.txt"
|
153
|
+
status, headers, body = @app.call(env.dup)
|
154
|
+
assert_equal before, headers["X-Last-Peak-At"], headers.inspect
|
155
|
+
|
156
|
+
@ios << @srv.accept
|
157
|
+
assert_raises(Errno::EAGAIN) { @srv.accept_nonblock }
|
158
|
+
sleep 0.1
|
159
|
+
status, headers, body = @app.call(env.dup)
|
160
|
+
assert headers["X-Last-Peak-At"], headers.inspect
|
161
|
+
assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) }
|
162
|
+
assert before != headers["X-Last-Peak-At"]
|
163
|
+
|
164
|
+
queued_before = headers["X-Last-Peak-At"]
|
165
|
+
|
166
|
+
sleep 2
|
167
|
+
|
168
|
+
env = @req.class.env_for "/queued/#@addr.txt"
|
169
|
+
status, headers, body = @app.call(env)
|
170
|
+
assert_equal "0", headers["X-Current"]
|
171
|
+
assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) }
|
172
|
+
assert_equal queued_before, headers["X-Last-Peak-At"], "should not change"
|
173
|
+
assert_equal start, headers["X-First-Peak-At"]
|
174
|
+
end
|
99
175
|
end if RUBY_PLATFORM =~ /linux/
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: raindrops
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 63
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 8
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.8.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- raindrops hackers
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-10-15 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: bundler
|