raindrops 0.7.0 → 0.8.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.
- 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
|