raindrops 0.1.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/Rakefile ADDED
@@ -0,0 +1,156 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # most tasks are in the GNUmakefile which offers better parallelism
4
+
5
+ def tags
6
+ timefmt = '%Y-%m-%dT%H:%M:%SZ'
7
+ @tags ||= `git tag -l`.split(/\n/).map do |tag|
8
+ if %r{\Av[\d\.]+\z} =~ tag
9
+ header, subject, body = `git cat-file tag #{tag}`.split(/\n\n/, 3)
10
+ header = header.split(/\n/)
11
+ tagger = header.grep(/\Atagger /).first
12
+ body ||= "initial"
13
+ {
14
+ :time => Time.at(tagger.split(/ /)[-2].to_i).utc.strftime(timefmt),
15
+ :tagger_name => %r{^tagger ([^<]+)}.match(tagger)[1].strip,
16
+ :tagger_email => %r{<([^>]+)>}.match(tagger)[1].strip,
17
+ :id => `git rev-parse refs/tags/#{tag}`.chomp!,
18
+ :tag => tag,
19
+ :subject => subject,
20
+ :body => body,
21
+ }
22
+ end
23
+ end.compact.sort { |a,b| b[:time] <=> a[:time] }
24
+ end
25
+
26
+ cgit_url = "http://git.bogomips.org/cgit/raindrops.git"
27
+ git_url = ENV['GIT_URL'] || 'git://git.bogomips.org/raindrops.git'
28
+ web_url = "http://rainbows.bogomips.org/"
29
+
30
+ desc 'prints news as an Atom feed'
31
+ task :news_atom do
32
+ require 'nokogiri'
33
+ new_tags = tags[0,10]
34
+ puts(Nokogiri::XML::Builder.new do
35
+ feed :xmlns => "http://www.w3.org/2005/Atom" do
36
+ id! "#{web_url}NEWS.atom.xml"
37
+ title "Raindrops news"
38
+ subtitle "real-time stats for Rack servers"
39
+ link! :rel => "alternate", :type => "text/html",
40
+ :href => "#{web_url}NEWS.html"
41
+ updated(new_tags.empty? ? "1970-01-01T00:00:00Z" : new_tags.first[:time])
42
+ new_tags.each do |tag|
43
+ entry do
44
+ title tag[:subject]
45
+ updated tag[:time]
46
+ published tag[:time]
47
+ author {
48
+ name tag[:tagger_name]
49
+ email tag[:tagger_email]
50
+ }
51
+ url = "#{cgit_url}/tag/?id=#{tag[:tag]}"
52
+ link! :rel => "alternate", :type => "text/html", :href =>url
53
+ id! url
54
+ message_only = tag[:body].split(/\n.+\(\d+\):\n {6}/s).first.strip
55
+ content({:type =>:text}, message_only)
56
+ content(:type =>:xhtml) { pre tag[:body] }
57
+ end
58
+ end
59
+ end
60
+ end.to_xml)
61
+ end
62
+
63
+ desc 'prints RDoc-formatted news'
64
+ task :news_rdoc do
65
+ tags.each do |tag|
66
+ time = tag[:time].tr!('T', ' ').gsub!(/:\d\dZ/, ' UTC')
67
+ puts "=== #{tag[:tag].sub(/^v/, '')} / #{time}"
68
+ puts ""
69
+
70
+ body = tag[:body]
71
+ puts tag[:body].gsub(/^/sm, " ").gsub(/[ \t]+$/sm, "")
72
+ puts ""
73
+ end
74
+ end
75
+
76
+ desc "print release changelog for Rubyforge"
77
+ task :release_changes do
78
+ version = ENV['VERSION'] or abort "VERSION= needed"
79
+ version = "v#{version}"
80
+ vtags = tags.map { |tag| tag[:tag] =~ /\Av/ and tag[:tag] }.sort
81
+ prev = vtags[vtags.index(version) - 1]
82
+ if prev
83
+ system('git', 'diff', '--stat', prev, version) or abort $?
84
+ puts ""
85
+ system('git', 'log', "#{prev}..#{version}") or abort $?
86
+ else
87
+ system('git', 'log', version) or abort $?
88
+ end
89
+ end
90
+
91
+ desc "print release notes for Rubyforge"
92
+ task :release_notes do
93
+ spec = Gem::Specification.load('raindrops.gemspec')
94
+ puts spec.description.strip
95
+ puts ""
96
+ puts "* #{spec.homepage}"
97
+ puts "* #{spec.email}"
98
+ puts "* #{git_url}"
99
+
100
+ _, _, body = `git cat-file tag v#{spec.version}`.split(/\n\n/, 3)
101
+ print "\nChanges:\n\n"
102
+ puts body
103
+ end
104
+
105
+ desc "read news article from STDIN and post to rubyforge"
106
+ task :publish_news do
107
+ require 'rubyforge'
108
+ IO.select([STDIN], nil, nil, 1) or abort "E: news must be read from stdin"
109
+ msg = STDIN.readlines
110
+ subject = msg.shift
111
+ blank = msg.shift
112
+ blank == "\n" or abort "no newline after subject!"
113
+ subject.strip!
114
+ body = msg.join("").strip!
115
+
116
+ rf = RubyForge.new.configure
117
+ rf.login
118
+ rf.post_news('rainbows', subject, body)
119
+ end
120
+
121
+ desc "post to RAA"
122
+ task :raa_update do
123
+ require 'net/http'
124
+ require 'net/netrc'
125
+ rc = Net::Netrc.locate('raindrops-raa') or abort "~/.netrc not found"
126
+ password = rc.password
127
+
128
+ s = Gem::Specification.load('raindrops.gemspec')
129
+ desc = [ s.description.strip ]
130
+ desc << ""
131
+ desc << "* #{s.email}"
132
+ desc << "* #{git_url}"
133
+ desc << "* #{cgit_url}"
134
+ desc = desc.join("\n")
135
+ uri = URI.parse('http://raa.ruby-lang.org/regist.rhtml')
136
+ form = {
137
+ :name => s.name,
138
+ :short_description => s.summary,
139
+ :version => s.version.to_s,
140
+ :status => 'experimental',
141
+ :owner => s.authors.first,
142
+ :email => s.email,
143
+ :category_major => 'Library',
144
+ :category_minor => 'Rack',
145
+ :url => s.homepage,
146
+ :download => 'http://rubyforge.org/frs/?group_id=8977',
147
+ :license => 'LGPL', # LGPLv3, actually, but RAA is ancient...
148
+ :description_style => 'Plain',
149
+ :description => desc,
150
+ :pass => password,
151
+ :submit => 'Update',
152
+ }
153
+ res = Net::HTTP.post_form(uri, form)
154
+ p res
155
+ puts res.body
156
+ end
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+ * more portable atomics for non-GCC systems (libatomicops?)
2
+ * pure Ruby version for non-forking servers
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # this is used to show or watch the number of active and queued
4
+ # connections on any listener socket from the command line
5
+
6
+ require 'raindrops'
7
+ require 'optparse'
8
+ usage = "Usage: #$0 [--loop] ADDR..."
9
+ ARGV.size > 0 or abort usage
10
+ delay = false
11
+
12
+ # "normal" exits when driven on the command-line
13
+ trap(:INT) { exit 130 }
14
+ trap(:PIPE) { exit 0 }
15
+
16
+ opts = OptionParser.new('', 24, ' ') do |opts|
17
+ opts.banner = usage
18
+ opts.on('-d', '--delay=delay') { |nr| delay = nr.to_i }
19
+ opts.parse! ARGV
20
+ end
21
+
22
+ fmt = "% 19s % 10u % 10u\n"
23
+ printf fmt.tr('u','s'), *%w(address active queued)
24
+
25
+ begin
26
+ stats = Raindrops::Linux.tcp_listener_stats(ARGV)
27
+ stats.each { |addr,stats| printf fmt, addr, stats.active, stats.queued }
28
+ end while delay && sleep(delay)
@@ -0,0 +1,11 @@
1
+ require 'mkmf'
2
+
3
+ # FIXME: test for GCC __sync_XXX builtins here, somehow...
4
+ have_func('mmap', 'sys/mman.h') or abort 'mmap() not found'
5
+ have_func('munmap', 'sys/mman.h') or abort 'munmap() not found'
6
+
7
+ have_func("rb_struct_alloc_noinit")
8
+ have_func('rb_thread_blocking_region')
9
+
10
+ dir_config('raindrops')
11
+ create_makefile('raindrops_ext')
@@ -0,0 +1,342 @@
1
+ #include <ruby.h>
2
+
3
+ /* Ruby 1.8.6+ macros (for compatibility with Ruby 1.9) */
4
+ #ifndef RSTRING_PTR
5
+ # define RSTRING_PTR(s) (RSTRING(s)->ptr)
6
+ #endif
7
+ #ifndef RSTRING_LEN
8
+ # define RSTRING_LEN(s) (RSTRING(s)->len)
9
+ #endif
10
+ #ifndef RSTRUCT_PTR
11
+ # define RSTRUCT_PTR(s) (RSTRUCT(s)->ptr)
12
+ #endif
13
+ #ifndef RSTRUCT_LEN
14
+ # define RSTRUCT_LEN(s) (RSTRUCT(s)->len)
15
+ #endif
16
+
17
+ #ifndef HAVE_RB_STRUCT_ALLOC_NOINIT
18
+ static ID id_new;
19
+ static VALUE rb_struct_alloc_noinit(VALUE class)
20
+ {
21
+ return rb_funcall(class, id_new, 0, 0);
22
+ }
23
+ #endif /* !defined(HAVE_RB_STRUCT_ALLOC_NOINIT) */
24
+
25
+ /* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
26
+ #ifndef HAVE_RB_THREAD_BLOCKING_REGION
27
+ # include <rubysig.h>
28
+ # define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
29
+ typedef void rb_unblock_function_t(void *);
30
+ typedef VALUE rb_blocking_function_t(void *);
31
+ static VALUE
32
+ rb_thread_blocking_region(
33
+ rb_blocking_function_t *func, void *data1,
34
+ rb_unblock_function_t *ubf, void *data2)
35
+ {
36
+ VALUE rv;
37
+
38
+ TRAP_BEG;
39
+ rv = func(data1);
40
+ TRAP_END;
41
+
42
+ return rv;
43
+ }
44
+ #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
45
+
46
+ #include <assert.h>
47
+ #include <errno.h>
48
+ #include <sys/socket.h>
49
+ #include <sys/types.h>
50
+ #include <unistd.h>
51
+ #include <string.h>
52
+ #include <asm/types.h>
53
+ #include <netinet/in.h>
54
+ #include <arpa/inet.h>
55
+ #include <netinet/tcp.h>
56
+ #include <linux/netlink.h>
57
+ #include <linux/rtnetlink.h>
58
+ #include <linux/inet_diag.h>
59
+
60
+ static size_t page_size;
61
+ static unsigned g_seq;
62
+ static VALUE cListenStats;
63
+
64
+ struct my_addr {
65
+ in_addr_t addr;
66
+ uint16_t port;
67
+ };
68
+
69
+ struct listen_stats {
70
+ long active;
71
+ long queued;
72
+ };
73
+
74
+ #define OPLEN (sizeof(struct inet_diag_bc_op) + \
75
+ sizeof(struct inet_diag_hostcond) + \
76
+ sizeof(in_addr_t))
77
+
78
+ struct nogvl_args {
79
+ struct iovec iov[3]; /* last iov holds inet_diag bytecode */
80
+ struct my_addr addrs;
81
+ struct listen_stats stats;
82
+ };
83
+
84
+ /* creates a Ruby ListenStats Struct based on our internal listen_stats */
85
+ static VALUE rb_listen_stats(struct listen_stats *stats)
86
+ {
87
+ VALUE rv = rb_struct_alloc_noinit(cListenStats);
88
+ VALUE *ptr = RSTRUCT_PTR(rv);
89
+
90
+ ptr[0] = LONG2NUM(stats->active);
91
+ ptr[1] = LONG2NUM(stats->queued);
92
+
93
+ return rv;
94
+ }
95
+
96
+ /*
97
+ * converts a base 10 string representing a port number into
98
+ * an unsigned 16 bit integer. Raises ArgumentError on failure
99
+ */
100
+ static uint16_t my_inet_port(const char *port)
101
+ {
102
+ char *err;
103
+ unsigned long tmp = strtoul(port, &err, 10);
104
+
105
+ if (*err != 0 || tmp > 0xffff)
106
+ rb_raise(rb_eArgError, "port not parsable: `%s'\n", port);
107
+
108
+ return (uint16_t)tmp;
109
+ }
110
+
111
+ /* inner loop of inet_diag, called for every socket returned by netlink */
112
+ static inline void r_acc(struct nogvl_args *args, struct inet_diag_msg *r)
113
+ {
114
+ /*
115
+ * inode == 0 means the connection is still in the listen queue
116
+ * and has not yet been accept()-ed by the server. The
117
+ * inet_diag bytecode cannot filter this for us.
118
+ */
119
+ if (r->idiag_inode == 0)
120
+ return;
121
+ if (r->idiag_state == TCP_ESTABLISHED)
122
+ args->stats.active++;
123
+ else /* if (r->idiag_state == TCP_LISTEN) */
124
+ args->stats.queued = r->idiag_rqueue;
125
+ /*
126
+ * we wont get anything else because of the idiag_states filter
127
+ */
128
+ }
129
+
130
+ static const char err_socket[] = "socket";
131
+ static const char err_sendmsg[] = "sendmsg";
132
+ static const char err_recvmsg[] = "recvmsg";
133
+ static const char err_nlmsg[] = "nlmsg";
134
+
135
+ /* does the inet_diag stuff with netlink(), this is called w/o GVL */
136
+ static VALUE diag(void *ptr)
137
+ {
138
+ struct nogvl_args *args = ptr;
139
+ struct sockaddr_nl nladdr;
140
+ struct rtattr rta;
141
+ struct {
142
+ struct nlmsghdr nlh;
143
+ struct inet_diag_req r;
144
+ } req;
145
+ struct msghdr msg;
146
+ const char *err = NULL;
147
+ unsigned seq = __sync_add_and_fetch(&g_seq, 1);
148
+ int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG);
149
+
150
+ if (fd < 0)
151
+ return (VALUE)err_socket;
152
+
153
+ memset(&args->stats, 0, sizeof(struct listen_stats));
154
+
155
+ memset(&nladdr, 0, sizeof(nladdr));
156
+ nladdr.nl_family = AF_NETLINK;
157
+
158
+ memset(&req, 0, sizeof(req));
159
+ req.nlh.nlmsg_len = sizeof(req) + RTA_LENGTH(args->iov[2].iov_len);
160
+ req.nlh.nlmsg_type = TCPDIAG_GETSOCK;
161
+ req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
162
+ req.nlh.nlmsg_pid = getpid();
163
+ req.nlh.nlmsg_seq = seq;
164
+ req.r.idiag_family = AF_INET;
165
+ req.r.idiag_states = (1<<TCP_ESTABLISHED) | (1<<TCP_LISTEN);
166
+ rta.rta_type = INET_DIAG_REQ_BYTECODE;
167
+ rta.rta_len = RTA_LENGTH(args->iov[2].iov_len);
168
+
169
+ args->iov[0].iov_base = &req;
170
+ args->iov[0].iov_len = sizeof(req);
171
+ args->iov[1].iov_base = &rta;
172
+ args->iov[1].iov_len = sizeof(rta);
173
+
174
+ memset(&msg, 0, sizeof(msg));
175
+ msg.msg_name = (void *)&nladdr;
176
+ msg.msg_namelen = sizeof(nladdr);
177
+ msg.msg_iov = args->iov;
178
+ msg.msg_iovlen = 3;
179
+
180
+ if (sendmsg(fd, &msg, 0) < 0) {
181
+ err = err_sendmsg;
182
+ goto out;
183
+ }
184
+
185
+ /* reuse buffer that was allocated for bytecode */
186
+ args->iov[0].iov_len = page_size;
187
+ args->iov[0].iov_base = args->iov[2].iov_base;
188
+
189
+ while (1) {
190
+ ssize_t readed;
191
+ struct nlmsghdr *h = (struct nlmsghdr *)args->iov[0].iov_base;
192
+
193
+ memset(&msg, 0, sizeof(msg));
194
+ msg.msg_name = (void *)&nladdr;
195
+ msg.msg_namelen = sizeof(nladdr);
196
+ msg.msg_iov = args->iov;
197
+ msg.msg_iovlen = 1;
198
+
199
+ readed = recvmsg(fd, &msg, 0);
200
+ if (readed < 0) {
201
+ if (errno == EINTR)
202
+ continue;
203
+ err = err_recvmsg;
204
+ goto out;
205
+ }
206
+ if (readed == 0)
207
+ goto out;
208
+
209
+ for ( ; NLMSG_OK(h, readed); h = NLMSG_NEXT(h, readed)) {
210
+ if (h->nlmsg_seq != seq)
211
+ continue;
212
+ if (h->nlmsg_type == NLMSG_DONE)
213
+ goto out;
214
+ if (h->nlmsg_type == NLMSG_ERROR) {
215
+ err = err_nlmsg;
216
+ goto out;
217
+ }
218
+ r_acc(args, NLMSG_DATA(h));
219
+ }
220
+ }
221
+ out:
222
+ {
223
+ int save_errno = errno;
224
+ close(fd);
225
+ errno = save_errno;
226
+ }
227
+ return (VALUE)err;
228
+ }
229
+
230
+ /* populates inet my_addr struct by parsing +addr+ */
231
+ static void parse_addr(struct my_addr *inet, VALUE addr)
232
+ {
233
+ char *host_port, *colon;
234
+
235
+ if (TYPE(addr) != T_STRING)
236
+ rb_raise(rb_eArgError, "addrs must be an Array of Strings");
237
+
238
+ host_port = RSTRING_PTR(addr);
239
+ colon = memchr(host_port, ':', RSTRING_LEN(addr));
240
+ if (!colon)
241
+ rb_raise(rb_eArgError, "port not found in: `%s'", host_port);
242
+
243
+ *colon = 0;
244
+ inet->addr = inet_addr(host_port);
245
+ *colon = ':';
246
+ inet->port = htons(my_inet_port(colon + 1));
247
+ }
248
+
249
+ /* generates inet_diag bytecode to match a single addr */
250
+ static void gen_bytecode(struct iovec *iov, struct my_addr *inet)
251
+ {
252
+ struct inet_diag_bc_op *op;
253
+ struct inet_diag_hostcond *cond;
254
+
255
+ /* iov_len was already set and base allocated in a parent function */
256
+ assert(iov->iov_len == OPLEN && iov->iov_base && "iov invalid");
257
+ op = iov->iov_base;
258
+ op->code = INET_DIAG_BC_S_COND;
259
+ op->yes = OPLEN;
260
+ op->no = sizeof(struct inet_diag_bc_op) + OPLEN;
261
+
262
+ cond = (struct inet_diag_hostcond *)(op + 1);
263
+ cond->family = AF_INET;
264
+ cond->port = ntohs(inet->port);
265
+ cond->prefix_len = inet->addr == 0 ? 0 : sizeof(in_addr_t) * CHAR_BIT;
266
+ *cond->addr = inet->addr;
267
+ }
268
+
269
+ static VALUE tcp_stats(struct nogvl_args *args, VALUE addr)
270
+ {
271
+ const char *err;
272
+ VALUE verr;
273
+
274
+ parse_addr(&args->addrs, addr);
275
+ gen_bytecode(&args->iov[2], &args->addrs);
276
+
277
+ verr = rb_thread_blocking_region(diag, args, RUBY_UBF_IO, 0);
278
+ err = (const char *)verr;
279
+ if (err) {
280
+ if (err == err_nlmsg)
281
+ rb_raise(rb_eRuntimeError, "NLMSG_ERROR");
282
+ else
283
+ rb_sys_fail(err);
284
+ }
285
+
286
+ return rb_listen_stats(&args->stats);
287
+ }
288
+
289
+ /*
290
+ * call-seq:
291
+ * addrs = %w(0.0.0.0:80 127.0.0.1:8080)
292
+ * Raindrops::Linux.tcp_listener_stats(addrs) => hash
293
+ *
294
+ * Takes an array of strings representing listen addresses to filter for.
295
+ * Returns a hash with given addresses as keys and ListenStats
296
+ * objects as the values.
297
+ */
298
+ static VALUE tcp_listener_stats(VALUE obj, VALUE addrs)
299
+ {
300
+ VALUE *ary;
301
+ long i;
302
+ VALUE rv;
303
+ struct nogvl_args args;
304
+
305
+ /*
306
+ * allocating page_size instead of OP_LEN since we'll reuse the
307
+ * buffer for recvmsg() later, we already checked for
308
+ * OPLEN <= page_size at initialization
309
+ */
310
+ args.iov[2].iov_len = OPLEN;
311
+ args.iov[2].iov_base = alloca(page_size);
312
+
313
+ if (TYPE(addrs) != T_ARRAY)
314
+ rb_raise(rb_eArgError, "addrs must be an Array or String");
315
+
316
+ rv = rb_hash_new();
317
+ ary = RARRAY_PTR(addrs);
318
+ for (i = RARRAY_LEN(addrs); --i >= 0; ary++)
319
+ rb_hash_aset(rv, *ary, tcp_stats(&args, *ary));
320
+
321
+ return rv;
322
+ }
323
+
324
+ void Init_raindrops_linux_inet_diag(void)
325
+ {
326
+ VALUE cRaindrops = rb_const_get(rb_cObject, rb_intern("Raindrops"));
327
+ VALUE mLinux = rb_define_module_under(cRaindrops, "Linux");
328
+
329
+ cListenStats = rb_const_get(cRaindrops, rb_intern("ListenStats"));
330
+
331
+ rb_define_module_function(mLinux, "tcp_listener_stats",
332
+ tcp_listener_stats, 1);
333
+
334
+ #ifndef HAVE_RB_STRUCT_ALLOC_NOINIT
335
+ id_new = rb_intern("new");
336
+ #endif
337
+ rb_require("raindrops/linux");
338
+
339
+ page_size = getpagesize();
340
+
341
+ assert(OPLEN <= page_size && "bytecode OPLEN is no <= PAGE_SIZE");
342
+ }