raindrops 0.5.0 → 0.6.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/examples/zbatery.conf.rb +1 -1
- data/ext/raindrops/extconf.rb +2 -3
- data/ext/raindrops/linux_inet_diag.c +28 -6
- data/lib/raindrops/watcher.rb +37 -18
- data/test/test_inet_diag_socket.rb +3 -0
- data/test/test_watcher.rb +14 -0
- metadata +19 -3
data/GIT-VERSION-GEN
CHANGED
data/examples/zbatery.conf.rb
CHANGED
@@ -8,6 +8,6 @@ log_dir = "/var/log/zbatery"
|
|
8
8
|
if File.writable?(log_dir) && File.directory?(log_dir)
|
9
9
|
stderr_path "#{log_dir}/raindrops-demo.stderr.log"
|
10
10
|
stdout_path "#{log_dir}/raindrops-demo.stdout.log"
|
11
|
-
user "www-data", "www-data"
|
12
11
|
listen "/tmp/.raindrops"
|
12
|
+
pid "/tmp/.raindrops.pid"
|
13
13
|
end
|
data/ext/raindrops/extconf.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'mkmf'
|
2
2
|
|
3
|
+
dir_config('atomic_ops')
|
3
4
|
have_func('mmap', 'sys/mman.h') or abort 'mmap() not found'
|
4
5
|
have_func('munmap', 'sys/mman.h') or abort 'munmap() not found'
|
5
6
|
|
@@ -8,8 +9,8 @@ have_func('mremap', 'sys/mman.h')
|
|
8
9
|
|
9
10
|
$CPPFLAGS += " -D_BSD_SOURCE -D_XOPEN_SOURCE=600 "
|
10
11
|
have_func("getpagesize", "unistd.h")
|
11
|
-
have_func("rb_struct_alloc_noinit")
|
12
12
|
have_func('rb_thread_blocking_region')
|
13
|
+
have_func('rb_thread_io_blocking_region')
|
13
14
|
|
14
15
|
checking_for "GCC 4+ atomic builtins" do
|
15
16
|
src = <<SRC
|
@@ -43,6 +44,4 @@ Users of Debian-based distros may run:
|
|
43
44
|
|
44
45
|
apt-get install libatomic-ops-dev
|
45
46
|
SRC
|
46
|
-
|
47
|
-
dir_config('raindrops')
|
48
47
|
create_makefile('raindrops_ext')
|
@@ -15,6 +15,7 @@
|
|
15
15
|
/* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
|
16
16
|
#ifndef HAVE_RB_THREAD_BLOCKING_REGION
|
17
17
|
# include <rubysig.h>
|
18
|
+
# define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
|
18
19
|
typedef void rb_unblock_function_t(void *);
|
19
20
|
typedef VALUE rb_blocking_function_t(void *);
|
20
21
|
static VALUE
|
@@ -32,12 +33,18 @@ rb_thread_blocking_region(
|
|
32
33
|
}
|
33
34
|
#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
|
34
35
|
|
36
|
+
#ifndef HAVE_RB_THREAD_IO_BLOCKING_REGION
|
37
|
+
# define rb_thread_io_blocking_region(fn,data,fd) \
|
38
|
+
rb_thread_blocking_region((fn),(data),RUBY_UBF_IO,0)
|
39
|
+
#endif /* HAVE_RB_THREAD_IO_BLOCKING_REGION */
|
40
|
+
|
35
41
|
#include <assert.h>
|
36
42
|
#include <errno.h>
|
37
43
|
#include <sys/socket.h>
|
38
44
|
#include <sys/types.h>
|
39
45
|
#include <netdb.h>
|
40
46
|
#include <unistd.h>
|
47
|
+
#include <fcntl.h>
|
41
48
|
#include <string.h>
|
42
49
|
#include <asm/types.h>
|
43
50
|
#include <netinet/in.h>
|
@@ -69,6 +76,21 @@ struct nogvl_args {
|
|
69
76
|
int fd;
|
70
77
|
};
|
71
78
|
|
79
|
+
#ifdef SOCK_CLOEXEC
|
80
|
+
# define my_SOCK_RAW (SOCK_RAW|SOCK_CLOEXEC)
|
81
|
+
# define FORCE_CLOEXEC(v) (v)
|
82
|
+
#else
|
83
|
+
# define my_SOCK_RAW SOCK_RAW
|
84
|
+
static VALUE FORCE_CLOEXEC(VALUE io)
|
85
|
+
{
|
86
|
+
int fd = my_fileno(io);
|
87
|
+
int flags = fcntl(fd, F_SETFD, FD_CLOEXEC);
|
88
|
+
if (flags == -1)
|
89
|
+
rb_sys_fail("fcntl(F_SETFD, FD_CLOEXEC)");
|
90
|
+
return io;
|
91
|
+
}
|
92
|
+
#endif
|
93
|
+
|
72
94
|
/*
|
73
95
|
* call-seq:
|
74
96
|
* Raindrops::InetDiagSocket.new -> Socket
|
@@ -78,11 +100,12 @@ struct nogvl_args {
|
|
78
100
|
static VALUE ids_s_new(VALUE klass)
|
79
101
|
{
|
80
102
|
VALUE argv[3];
|
103
|
+
|
81
104
|
argv[0] = INT2NUM(AF_NETLINK);
|
82
|
-
argv[1] = INT2NUM(
|
105
|
+
argv[1] = INT2NUM(my_SOCK_RAW);
|
83
106
|
argv[2] = INT2NUM(NETLINK_INET_DIAG);
|
84
107
|
|
85
|
-
return rb_call_super(3, argv);
|
108
|
+
return FORCE_CLOEXEC(rb_call_super(3, argv));
|
86
109
|
}
|
87
110
|
|
88
111
|
/* creates a Ruby ListenStats Struct based on our internal listen_stats */
|
@@ -180,8 +203,7 @@ static struct listen_stats *stats_for(st_table *table, struct inet_diag_msg *r)
|
|
180
203
|
case AF_INET6: {
|
181
204
|
struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&ss;
|
182
205
|
in6->sin6_port = r->id.idiag_sport;
|
183
|
-
memcpy(&in6->sin6_addr.
|
184
|
-
&r->id.idiag_src, sizeof(__be32[4]));
|
206
|
+
memcpy(&in6->sin6_addr, &r->id.idiag_src, sizeof(__be32[4]));
|
185
207
|
keylen = INET6_ADDRSTRLEN;
|
186
208
|
/* [ ] */
|
187
209
|
alloca_len = 1 + keylen + 1 + 1 + portlen;
|
@@ -543,7 +565,7 @@ static VALUE tcp_stats(struct nogvl_args *args, VALUE addr)
|
|
543
565
|
gen_bytecode(&args->iov[2], &query_addr);
|
544
566
|
|
545
567
|
memset(&args->stats, 0, sizeof(struct listen_stats));
|
546
|
-
nl_errcheck(
|
568
|
+
nl_errcheck(rb_thread_io_blocking_region(diag, args, args->fd));
|
547
569
|
|
548
570
|
return rb_listen_stats(&args->stats);
|
549
571
|
}
|
@@ -610,7 +632,7 @@ static VALUE tcp_listener_stats(int argc, VALUE *argv, VALUE self)
|
|
610
632
|
"addr must be an array of strings, a string, or nil");
|
611
633
|
}
|
612
634
|
|
613
|
-
nl_errcheck(
|
635
|
+
nl_errcheck(rb_thread_io_blocking_region(diag, &args, args.fd));
|
614
636
|
|
615
637
|
st_foreach(args.table, NIL_P(addrs) ? st_to_hash : st_AND_hash, rv);
|
616
638
|
st_free_table(args.table);
|
data/lib/raindrops/watcher.rb
CHANGED
@@ -35,28 +35,28 @@ require "aggregate"
|
|
35
35
|
# Returns a plain text summary + histogram with X-* HTTP headers for
|
36
36
|
# active connections.
|
37
37
|
#
|
38
|
-
# e.g.: curl http://
|
38
|
+
# e.g.: curl http://raindrops-demo.bogomips.org/active/0.0.0.0%3A80.txt
|
39
39
|
#
|
40
40
|
# === GET /active/$LISTENER.html
|
41
41
|
#
|
42
42
|
# Returns an HTML summary + histogram with X-* HTTP headers for
|
43
43
|
# active connections.
|
44
44
|
#
|
45
|
-
# e.g.: curl http://
|
45
|
+
# e.g.: curl http://raindrops-demo.bogomips.org/active/0.0.0.0%3A80.html
|
46
46
|
#
|
47
47
|
# === GET /queued/$LISTENER.txt
|
48
48
|
#
|
49
49
|
# Returns a plain text summary + histogram with X-* HTTP headers for
|
50
50
|
# queued connections.
|
51
51
|
#
|
52
|
-
# e.g.: curl http://
|
52
|
+
# e.g.: curl http://raindrops-demo.bogomips.org/queued/0.0.0.0%3A80.txt
|
53
53
|
#
|
54
54
|
# === GET /queued/$LISTENER.html
|
55
55
|
#
|
56
56
|
# Returns an HTML summary + histogram with X-* HTTP headers for
|
57
57
|
# queued connections.
|
58
58
|
#
|
59
|
-
# e.g.: curl http://
|
59
|
+
# e.g.: curl http://raindrops-demo.bogomips.org/queued/0.0.0.0%3A80.html
|
60
60
|
#
|
61
61
|
# === GET /tail/$LISTENER.txt?active_min=1&queued_min=1
|
62
62
|
#
|
@@ -90,7 +90,7 @@ require "aggregate"
|
|
90
90
|
#
|
91
91
|
# There is a server running this app at http://raindrops-demo.bogomips.org/
|
92
92
|
# The Raindrops::Middleware demo is also accessible at
|
93
|
-
#
|
93
|
+
# http://raindrops-demo.bogomips.org/_raindrops
|
94
94
|
#
|
95
95
|
# The demo server is only limited to 30 users, so be sure not to abuse it
|
96
96
|
# by using the /tail/ endpoint too much.
|
@@ -99,6 +99,7 @@ class Raindrops::Watcher
|
|
99
99
|
attr_reader :snapshot
|
100
100
|
include Rack::Utils
|
101
101
|
include Raindrops::Linux
|
102
|
+
DOC_URL = "http://raindrops.bogomips.org/Raindrops/Watcher.html"
|
102
103
|
|
103
104
|
def initialize(opts = {})
|
104
105
|
@tcp_listeners = @unix_listeners = nil
|
@@ -167,14 +168,14 @@ class Raindrops::Watcher
|
|
167
168
|
def active_stats(addr) # :nodoc:
|
168
169
|
@lock.synchronize do
|
169
170
|
tmp = @active[addr] or return
|
170
|
-
[ @resets[addr], tmp.dup ]
|
171
|
+
[ @snapshot[0], @resets[addr], tmp.dup ]
|
171
172
|
end
|
172
173
|
end
|
173
174
|
|
174
175
|
def queued_stats(addr) # :nodoc:
|
175
176
|
@lock.synchronize do
|
176
177
|
tmp = @queued[addr] or return
|
177
|
-
[ @resets[addr], tmp.dup ]
|
178
|
+
[ @snapshot[0], @resets[addr], tmp.dup ]
|
178
179
|
end
|
179
180
|
end
|
180
181
|
|
@@ -199,16 +200,17 @@ class Raindrops::Watcher
|
|
199
200
|
end
|
200
201
|
|
201
202
|
def histogram_txt(agg)
|
202
|
-
reset_at, agg = *agg
|
203
|
+
updated_at, reset_at, agg = *agg
|
203
204
|
headers = agg_to_hash(reset_at, agg)
|
204
205
|
body = agg.to_s
|
205
206
|
headers["Content-Type"] = "text/plain"
|
207
|
+
headers["Expires"] = (updated_at + @delay).httpdate
|
206
208
|
headers["Content-Length"] = bytesize(body).to_s
|
207
209
|
[ 200, headers, [ body ] ]
|
208
210
|
end
|
209
211
|
|
210
212
|
def histogram_html(agg, addr)
|
211
|
-
reset_at, agg = *agg
|
213
|
+
updated_at, reset_at, agg = *agg
|
212
214
|
headers = agg_to_hash(reset_at, agg)
|
213
215
|
body = "<html>" \
|
214
216
|
"<head><title>#{hostname} - #{escape_html addr}</title></head>" \
|
@@ -220,6 +222,7 @@ class Raindrops::Watcher
|
|
220
222
|
"<input type='submit' name='x' value='reset' /></form>" \
|
221
223
|
"</body>"
|
222
224
|
headers["Content-Type"] = "text/html"
|
225
|
+
headers["Expires"] = (updated_at + @delay).httpdate
|
223
226
|
headers["Content-Length"] = bytesize(body).to_s
|
224
227
|
[ 200, headers, [ body ] ]
|
225
228
|
end
|
@@ -259,7 +262,7 @@ class Raindrops::Watcher
|
|
259
262
|
when %r{\A/reset/(.+)\z}
|
260
263
|
reset!(env, unescape($1))
|
261
264
|
else
|
262
|
-
|
265
|
+
not_found
|
263
266
|
end
|
264
267
|
end
|
265
268
|
|
@@ -285,6 +288,7 @@ class Raindrops::Watcher
|
|
285
288
|
headers = {
|
286
289
|
"Content-Type" => "text/html",
|
287
290
|
"Last-Modified" => updated_at.httpdate,
|
291
|
+
"Expires" => (updated_at + @delay).httpdate,
|
288
292
|
}
|
289
293
|
body = "<html><head>" \
|
290
294
|
"<title>#{hostname} - all interfaces</title>" \
|
@@ -295,13 +299,24 @@ class Raindrops::Watcher
|
|
295
299
|
all.map do |addr,stats|
|
296
300
|
e_addr = escape addr
|
297
301
|
"<tr>" \
|
298
|
-
"<td><a href='/tail/#{e_addr}.txt'
|
299
|
-
|
300
|
-
|
302
|
+
"<td><a href='/tail/#{e_addr}.txt' " \
|
303
|
+
"title='"tail" output in real time'" \
|
304
|
+
">#{escape_html addr}</a></td>" \
|
305
|
+
"<td><a href='/active/#{e_addr}.html' " \
|
306
|
+
"title='show active connection stats'>#{stats.active}</a></td>" \
|
307
|
+
"<td><a href='/queued/#{e_addr}.html' " \
|
308
|
+
"title='show queued connection stats'>#{stats.queued}</a></td>" \
|
301
309
|
"<td><form action='/reset/#{e_addr}' method='post'>" \
|
302
|
-
"<input
|
310
|
+
"<input title='reset statistics' " \
|
311
|
+
"type='submit' name='x' value='x' /></form></td>" \
|
303
312
|
"</tr>" \
|
304
|
-
end.join << "</table
|
313
|
+
end.join << "</table>" \
|
314
|
+
"<p>" \
|
315
|
+
"This is running the #{self.class}</a> service, see " \
|
316
|
+
"<a href='#{DOC_URL}'>#{DOC_URL}</a> " \
|
317
|
+
"for more information and options." \
|
318
|
+
"</p>" \
|
319
|
+
"</body></html>"
|
305
320
|
headers["Content-Length"] = bytesize(body).to_s
|
306
321
|
[ 200, headers, [ body ] ]
|
307
322
|
end
|
@@ -309,7 +324,6 @@ class Raindrops::Watcher
|
|
309
324
|
def tail(addr, env)
|
310
325
|
Tailer.new(self, addr, env).finish
|
311
326
|
end
|
312
|
-
# :startdoc:
|
313
327
|
|
314
328
|
# This is the response body returned for "/tail/$ADDRESS.txt". This
|
315
329
|
# must use a multi-threaded Rack server with streaming response support.
|
@@ -333,7 +347,11 @@ class Raindrops::Watcher
|
|
333
347
|
end
|
334
348
|
|
335
349
|
def finish
|
336
|
-
headers = {
|
350
|
+
headers = {
|
351
|
+
"Content-Type" => "text/plain",
|
352
|
+
"Cache-Control" => "no-transform",
|
353
|
+
"Expires" => Time.at(0).httpdate,
|
354
|
+
}
|
337
355
|
headers["Transfer-Encoding"] = "chunked" if @chunk
|
338
356
|
[ 200, headers, self ]
|
339
357
|
end
|
@@ -353,10 +371,11 @@ class Raindrops::Watcher
|
|
353
371
|
end
|
354
372
|
end
|
355
373
|
|
356
|
-
# shuts down the background thread
|
374
|
+
# shuts down the background thread, only for tests
|
357
375
|
def shutdown
|
358
376
|
@socket = nil
|
359
377
|
@thr.join if @thr
|
360
378
|
@thr = nil
|
361
379
|
end
|
380
|
+
# :startdoc:
|
362
381
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
require 'test/unit'
|
3
3
|
require 'raindrops'
|
4
|
+
require 'fcntl'
|
4
5
|
$stderr.sync = $stdout.sync = true
|
5
6
|
|
6
7
|
class TestInetDiagSocket < Test::Unit::TestCase
|
@@ -8,6 +9,8 @@ class TestInetDiagSocket < Test::Unit::TestCase
|
|
8
9
|
sock = Raindrops::InetDiagSocket.new
|
9
10
|
assert_kind_of Socket, sock
|
10
11
|
assert_kind_of Fixnum, sock.fileno
|
12
|
+
flags = sock.fcntl(Fcntl::F_GETFD)
|
13
|
+
assert_equal Fcntl::FD_CLOEXEC, flags & Fcntl::FD_CLOEXEC
|
11
14
|
assert_nil sock.close
|
12
15
|
end
|
13
16
|
end if RUBY_PLATFORM =~ /linux/
|
data/test/test_watcher.rb
CHANGED
@@ -50,6 +50,20 @@ class TestWatcher < Test::Unit::TestCase
|
|
50
50
|
check_headers(resp.headers)
|
51
51
|
end
|
52
52
|
|
53
|
+
def test_queued_txt
|
54
|
+
resp = @req.get "/queued/#@addr.txt"
|
55
|
+
assert_equal 200, resp.status.to_i
|
56
|
+
assert_equal "text/plain", resp.headers["Content-Type"]
|
57
|
+
check_headers(resp.headers)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_queued_html
|
61
|
+
resp = @req.get "/queued/#@addr.html"
|
62
|
+
assert_equal 200, resp.status.to_i
|
63
|
+
assert_equal "text/html", resp.headers["Content-Type"]
|
64
|
+
check_headers(resp.headers)
|
65
|
+
end
|
66
|
+
|
53
67
|
def test_reset
|
54
68
|
resp = @req.post "/reset/#@addr"
|
55
69
|
assert_equal 302, resp.status.to_i
|
metadata
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: raindrops
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 7
|
4
5
|
prerelease:
|
5
|
-
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 6
|
9
|
+
- 0
|
10
|
+
version: 0.6.0
|
6
11
|
platform: ruby
|
7
12
|
authors:
|
8
13
|
- raindrops hackers
|
@@ -10,7 +15,7 @@ autorequire:
|
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
17
|
|
13
|
-
date: 2011-03-
|
18
|
+
date: 2011-03-21 00:00:00 +00:00
|
14
19
|
default_executable:
|
15
20
|
dependencies:
|
16
21
|
- !ruby/object:Gem::Dependency
|
@@ -21,6 +26,11 @@ dependencies:
|
|
21
26
|
requirements:
|
22
27
|
- - ~>
|
23
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 10
|
24
34
|
version: 1.0.10
|
25
35
|
type: :development
|
26
36
|
version_requirements: *id001
|
@@ -130,17 +140,23 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
130
140
|
requirements:
|
131
141
|
- - ">="
|
132
142
|
- !ruby/object:Gem::Version
|
143
|
+
hash: 3
|
144
|
+
segments:
|
145
|
+
- 0
|
133
146
|
version: "0"
|
134
147
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
148
|
none: false
|
136
149
|
requirements:
|
137
150
|
- - ">="
|
138
151
|
- !ruby/object:Gem::Version
|
152
|
+
hash: 3
|
153
|
+
segments:
|
154
|
+
- 0
|
139
155
|
version: "0"
|
140
156
|
requirements: []
|
141
157
|
|
142
158
|
rubyforge_project: rainbows
|
143
|
-
rubygems_version: 1.6.
|
159
|
+
rubygems_version: 1.6.1
|
144
160
|
signing_key:
|
145
161
|
specification_version: 3
|
146
162
|
summary: real-time stats for preforking Rack servers
|