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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v0.5.0.GIT
4
+ DEF_VER=v0.6.0.GIT
5
5
 
6
6
  LF='
7
7
  '
@@ -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
@@ -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(SOCK_RAW);
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.in6_u.u6_addr32,
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(rb_thread_blocking_region(diag, args, 0, 0));
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(rb_thread_blocking_region(diag, &args, NULL, 0));
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);
@@ -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://example.com/active/0.0.0.0%3A80.txt
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://example.com/active/0.0.0.0%3A80.html
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://example.com/queued/0.0.0.0%3A80.txt
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://example.com/queued/0.0.0.0%3A80.html
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
- # http://raindrops-demo.bogomips.org/_raindrops
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
- Rack::Response.new(["Not Found"], 404).finish
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'>#{escape_html addr}</a></td>" \
299
- "<td><a href='/active/#{e_addr}.html'>#{stats.active}</a></td>" \
300
- "<td><a href='/queued/#{e_addr}.html'>#{stats.queued}</a></td>" \
302
+ "<td><a href='/tail/#{e_addr}.txt' " \
303
+ "title='&quot;tail&quot; 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 type='submit' name='x' value='x' /></form></td>" \
310
+ "<input title='reset statistics' " \
311
+ "type='submit' name='x' value='x' /></form></td>" \
303
312
  "</tr>" \
304
- end.join << "</table></body></html>"
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 = { "Content-Type" => "text/plain" }
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
- version: 0.5.0
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-17 00:00:00 +00:00
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.2
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