unicorn-rupcio 6.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.
- checksums.yaml +7 -0
- data/.CHANGELOG.old +25 -0
- data/.document +28 -0
- data/.gitattributes +5 -0
- data/.gitignore +25 -0
- data/.mailmap +26 -0
- data/.manifest +144 -0
- data/.olddoc.yml +25 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +39 -0
- data/COPYING +674 -0
- data/DESIGN +99 -0
- data/Documentation/.gitignore +3 -0
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +70 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +39 -0
- data/GNUmakefile +318 -0
- data/HACKING +117 -0
- data/ISSUES +102 -0
- data/KNOWN_ISSUES +79 -0
- data/LICENSE +67 -0
- data/Links +58 -0
- data/PHILOSOPHY +139 -0
- data/README +165 -0
- data/Rakefile +17 -0
- data/SIGNALS +123 -0
- data/Sandbox +104 -0
- data/TODO +1 -0
- data/TUNING +119 -0
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/bin/unicorn +129 -0
- data/bin/unicorn_rails +210 -0
- data/examples/big_app_gc.rb +3 -0
- data/examples/echo.ru +27 -0
- data/examples/init.sh +102 -0
- data/examples/logger_mp_safe.rb +26 -0
- data/examples/logrotate.conf +44 -0
- data/examples/nginx.conf +156 -0
- data/examples/unicorn.conf.minimal.rb +14 -0
- data/examples/unicorn.conf.rb +111 -0
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +115 -0
- data/ext/unicorn_http/common_field_optimization.h +128 -0
- data/ext/unicorn_http/epollexclusive.h +128 -0
- data/ext/unicorn_http/ext_help.h +38 -0
- data/ext/unicorn_http/extconf.rb +40 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +91 -0
- data/ext/unicorn_http/unicorn_http.c +4348 -0
- data/ext/unicorn_http/unicorn_http.rl +1054 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn/app/old_rails/static.rb +60 -0
- data/lib/unicorn/app/old_rails.rb +36 -0
- data/lib/unicorn/cgi_wrapper.rb +148 -0
- data/lib/unicorn/configurator.rb +749 -0
- data/lib/unicorn/const.rb +22 -0
- data/lib/unicorn/http_request.rb +180 -0
- data/lib/unicorn/http_response.rb +95 -0
- data/lib/unicorn/http_server.rb +860 -0
- data/lib/unicorn/launcher.rb +63 -0
- data/lib/unicorn/oob_gc.rb +82 -0
- data/lib/unicorn/preread_input.rb +34 -0
- data/lib/unicorn/select_waiter.rb +7 -0
- data/lib/unicorn/socket_helper.rb +186 -0
- data/lib/unicorn/stream_input.rb +152 -0
- data/lib/unicorn/tee_input.rb +132 -0
- data/lib/unicorn/tmpio.rb +34 -0
- data/lib/unicorn/util.rb +91 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +166 -0
- data/lib/unicorn.rb +137 -0
- data/man/man1/unicorn.1 +222 -0
- data/man/man1/unicorn_rails.1 +207 -0
- data/setup.rb +1587 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +5 -0
- data/t/README +49 -0
- data/t/active-unix-socket.t +110 -0
- data/t/back-out-of-upgrade.t +44 -0
- data/t/bin/unused_listen +40 -0
- data/t/client_body_buffer_size.ru +15 -0
- data/t/client_body_buffer_size.t +79 -0
- data/t/detach.ru +12 -0
- data/t/env.ru +4 -0
- data/t/fails-rack-lint.ru +6 -0
- data/t/heartbeat-timeout.ru +13 -0
- data/t/heartbeat-timeout.t +60 -0
- data/t/integration.ru +129 -0
- data/t/integration.t +509 -0
- data/t/lib.perl +309 -0
- data/t/listener_names.ru +5 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +18 -0
- data/t/oob_gc_path.ru +18 -0
- data/t/pid.ru +4 -0
- data/t/preread_input.ru +23 -0
- data/t/reload-bad-config.t +49 -0
- data/t/reopen-logs.ru +14 -0
- data/t/reopen-logs.t +36 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0012-reload-empty-config.sh +86 -0
- data/t/t0013-rewindable-input-false.sh +24 -0
- data/t/t0013.ru +13 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/t/t0014.ru +13 -0
- data/t/t0015-configurator-internals.sh +25 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/t/t0021-process_detach.sh +29 -0
- data/t/t0022-listener_names-preload_app.sh +32 -0
- data/t/t0300-no-default-middleware.sh +20 -0
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +14 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +125 -0
- data/t/winch_ttin.t +64 -0
- data/t/working_directory.t +86 -0
- data/test/aggregate.rb +16 -0
- data/test/benchmark/README +60 -0
- data/test/benchmark/dd.ru +19 -0
- data/test/benchmark/ddstream.ru +51 -0
- data/test/benchmark/readinput.ru +41 -0
- data/test/benchmark/stack.ru +9 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1030 -0
- data/test/test_helper.rb +307 -0
- data/test/unit/test_configurator.rb +176 -0
- data/test/unit/test_droplet.rb +29 -0
- data/test/unit/test_http_parser.rb +885 -0
- data/test/unit/test_http_parser_ng.rb +715 -0
- data/test/unit/test_server.rb +245 -0
- data/test/unit/test_signals.rb +189 -0
- data/test/unit/test_socket_helper.rb +160 -0
- data/test/unit/test_stream_input.rb +211 -0
- data/test/unit/test_tee_input.rb +304 -0
- data/test/unit/test_util.rb +132 -0
- data/test/unit/test_waiter.rb +35 -0
- data/unicorn.gemspec +49 -0
- metadata +266 -0
data/t/winch_ttin.t
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
#!perl -w
|
2
|
+
# Copyright (C) unicorn hackers <unicorn-public@yhbt.net>
|
3
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
|
+
use v5.14; BEGIN { require './t/lib.perl' };
|
5
|
+
use autodie;
|
6
|
+
use POSIX qw(mkfifo);
|
7
|
+
my $u_sock = "$tmpdir/u.sock";
|
8
|
+
my $fifo = "$tmpdir/fifo";
|
9
|
+
mkfifo($fifo, 0666) or die "mkfifo($fifo): $!";
|
10
|
+
|
11
|
+
write_file '>', $u_conf, <<EOM;
|
12
|
+
pid "$tmpdir/pid"
|
13
|
+
listen "$u_sock"
|
14
|
+
stderr_path "$err_log"
|
15
|
+
after_fork do |server, worker|
|
16
|
+
# test script will block while reading from $fifo,
|
17
|
+
File.open("$fifo", "wb") { |fp| fp.syswrite worker.nr.to_s }
|
18
|
+
end
|
19
|
+
EOM
|
20
|
+
|
21
|
+
unicorn('-D', '-c', $u_conf, 't/integration.ru')->join;
|
22
|
+
is($?, 0, 'daemonized properly');
|
23
|
+
open my $fh, '<', "$tmpdir/pid";
|
24
|
+
chomp(my $pid = <$fh>);
|
25
|
+
ok(kill(0, $pid), 'daemonized PID works');
|
26
|
+
my $quit = sub { kill('QUIT', $pid) if $pid; $pid = undef };
|
27
|
+
END { $quit->() };
|
28
|
+
|
29
|
+
open $fh, '<', $fifo;
|
30
|
+
my $worker_nr = <$fh>;
|
31
|
+
close $fh;
|
32
|
+
is($worker_nr, '0', 'initial worker spawned');
|
33
|
+
|
34
|
+
my ($status, $hdr, $worker_pid) = do_req($u_sock, 'GET /pid HTTP/1.0');
|
35
|
+
like($status, qr/ 200\b/, 'got 200 response');
|
36
|
+
like($worker_pid, qr/\A[0-9]+\n\z/s, 'PID in response');
|
37
|
+
chomp $worker_pid;
|
38
|
+
ok(kill(0, $worker_pid), 'worker_pid is valid');
|
39
|
+
|
40
|
+
ok(kill('WINCH', $pid), 'SIGWINCH can be sent');
|
41
|
+
|
42
|
+
my $tries = 1000;
|
43
|
+
while (CORE::kill(0, $worker_pid) && --$tries) { sleep 0.01 }
|
44
|
+
ok(!CORE::kill(0, $worker_pid), 'worker not running');
|
45
|
+
|
46
|
+
ok(kill('TTIN', $pid), 'SIGTTIN to restart worker');
|
47
|
+
|
48
|
+
open $fh, '<', $fifo;
|
49
|
+
$worker_nr = <$fh>;
|
50
|
+
close $fh;
|
51
|
+
is($worker_nr, '0', 'worker restarted');
|
52
|
+
|
53
|
+
($status, $hdr, my $new_worker_pid) = do_req($u_sock, 'GET /pid HTTP/1.0');
|
54
|
+
like($status, qr/ 200\b/, 'got 200 response');
|
55
|
+
like($new_worker_pid, qr/\A[0-9]+\n\z/, 'got new worker PID');
|
56
|
+
chomp $new_worker_pid;
|
57
|
+
ok(kill(0, $new_worker_pid), 'got a valid worker PID');
|
58
|
+
isnt($worker_pid, $new_worker_pid, 'worker PID changed');
|
59
|
+
|
60
|
+
$quit->();
|
61
|
+
|
62
|
+
check_stderr;
|
63
|
+
undef $tmpdir;
|
64
|
+
done_testing;
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#!perl -w
|
2
|
+
# Copyright (C) unicorn hackers <unicorn-public@yhbt.net>
|
3
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
|
+
use v5.14; BEGIN { require './t/lib.perl' };
|
5
|
+
use autodie;
|
6
|
+
mkdir "$tmpdir/alt";
|
7
|
+
my $ru = "$tmpdir/alt/config.ru";
|
8
|
+
write_file '>', $u_conf, <<EOM;
|
9
|
+
pid "$pid_file"
|
10
|
+
preload_app true
|
11
|
+
stderr_path "$err_log"
|
12
|
+
working_directory "$tmpdir/alt" # the whole point of this test
|
13
|
+
before_fork { |_,_| \$master_ppid = Process.ppid }
|
14
|
+
EOM
|
15
|
+
|
16
|
+
my $common_ru = <<'EOM';
|
17
|
+
use Rack::ContentLength
|
18
|
+
use Rack::ContentType, 'text/plain'
|
19
|
+
run lambda { |env| [ 200, {}, [ "#{$master_ppid}\n" ] ] }
|
20
|
+
EOM
|
21
|
+
|
22
|
+
write_file '>', $ru, <<EOM;
|
23
|
+
#\\--daemonize --listen $u_sock
|
24
|
+
$common_ru
|
25
|
+
EOM
|
26
|
+
|
27
|
+
unicorn('-c', $u_conf)->join; # will daemonize
|
28
|
+
chomp($daemon_pid = slurp($pid_file));
|
29
|
+
|
30
|
+
my ($status, $hdr, $bdy) = do_req($u_sock, 'GET / HTTP/1.0');
|
31
|
+
is($bdy, "1\n", 'got expected $master_ppid');
|
32
|
+
|
33
|
+
stop_daemon;
|
34
|
+
check_stderr;
|
35
|
+
|
36
|
+
if ('test without CLI switches in config.ru') {
|
37
|
+
truncate $err_log, 0;
|
38
|
+
write_file '>', $ru, $common_ru;
|
39
|
+
|
40
|
+
unicorn('-D', '-l', $u_sock, '-c', $u_conf)->join; # will daemonize
|
41
|
+
chomp($daemon_pid = slurp($pid_file));
|
42
|
+
|
43
|
+
($status, $hdr, $bdy) = do_req($u_sock, 'GET / HTTP/1.0');
|
44
|
+
is($bdy, "1\n", 'got expected $master_ppid');
|
45
|
+
|
46
|
+
stop_daemon;
|
47
|
+
check_stderr;
|
48
|
+
}
|
49
|
+
|
50
|
+
if ('ensures broken working_directory (missing config.ru) is OK') {
|
51
|
+
truncate $err_log, 0;
|
52
|
+
unlink $ru;
|
53
|
+
|
54
|
+
my $auto_reap = unicorn('-c', $u_conf);
|
55
|
+
$auto_reap->join;
|
56
|
+
isnt($?, 0, 'exited with error due to missing config.ru');
|
57
|
+
|
58
|
+
like(slurp($err_log), qr/rackup file \Q(config.ru)\E not readable/,
|
59
|
+
'noted unreadability of config.ru in stderr');
|
60
|
+
}
|
61
|
+
|
62
|
+
if ('fooapp.rb (not config.ru) works with working_directory') {
|
63
|
+
truncate $err_log, 0;
|
64
|
+
my $fooapp = "$tmpdir/alt/fooapp.rb";
|
65
|
+
write_file '>', $fooapp, <<EOM;
|
66
|
+
class Fooapp
|
67
|
+
def self.call(env)
|
68
|
+
b = "dir=#{Dir.pwd}"
|
69
|
+
h = { 'content-type' => 'text/plain', 'content-length' => b.bytesize.to_s }
|
70
|
+
[ 200, h, [ b ] ]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
EOM
|
74
|
+
my $srv = tcp_server;
|
75
|
+
my $auto_reap = unicorn(qw(-c), $u_conf, qw(-I. fooapp.rb),
|
76
|
+
{ -C => '/', 3 => $srv });
|
77
|
+
($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0');
|
78
|
+
is($bdy, "dir=$tmpdir/alt",
|
79
|
+
'fooapp.rb (w/o config.ru) w/ working_directory');
|
80
|
+
$auto_reap->join('TERM');
|
81
|
+
is($?, 0, 'fooapp.rb process exited');
|
82
|
+
check_stderr;
|
83
|
+
}
|
84
|
+
|
85
|
+
undef $tmpdir;
|
86
|
+
done_testing;
|
data/test/aggregate.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/ruby -n
|
2
|
+
# -*- encoding: binary -*-
|
3
|
+
# frozen_string_literal: false
|
4
|
+
|
5
|
+
BEGIN { $tests = $assertions = $failures = $errors = 0 }
|
6
|
+
|
7
|
+
$_ =~ /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/ or next
|
8
|
+
$tests += $1.to_i
|
9
|
+
$assertions += $2.to_i
|
10
|
+
$failures += $3.to_i
|
11
|
+
$errors += $4.to_i
|
12
|
+
|
13
|
+
END {
|
14
|
+
printf("\n%d tests, %d assertions, %d failures, %d errors\n",
|
15
|
+
$tests, $assertions, $failures, $errors)
|
16
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
= Performance
|
2
|
+
|
3
|
+
Unicorn is pretty fast, and we want it to get faster. Unicorn strives
|
4
|
+
to get HTTP requests to your application and write HTTP responses back
|
5
|
+
as quickly as possible. Unicorn does not do any background processing
|
6
|
+
while your app runs, so your app will get all the CPU time provided to
|
7
|
+
it by your OS kernel.
|
8
|
+
|
9
|
+
A gentle reminder: Unicorn is NOT for serving clients over slow network
|
10
|
+
connections. Use nginx (or something similar) to complement Unicorn if
|
11
|
+
you have slow clients.
|
12
|
+
|
13
|
+
== dd.ru
|
14
|
+
|
15
|
+
This is a pure I/O benchmark. In the context of Unicorn, this is the
|
16
|
+
only one that matters. It is a standard rackup-compatible .ru file and
|
17
|
+
may be used with other Rack-compatible servers.
|
18
|
+
|
19
|
+
unicorn -E none dd.ru
|
20
|
+
|
21
|
+
You can change the size and number of chunks in the response with
|
22
|
+
the "bs" and "count" environment variables. The following command
|
23
|
+
will cause dd.ru to return 4 chunks of 16384 bytes each, leading to
|
24
|
+
65536 byte response:
|
25
|
+
|
26
|
+
bs=16384 count=4 unicorn -E none dd.ru
|
27
|
+
|
28
|
+
Or if you want to add logging (small performance impact):
|
29
|
+
|
30
|
+
unicorn -E deployment dd.ru
|
31
|
+
|
32
|
+
Eric runs then runs clients on a LAN it in several different ways:
|
33
|
+
|
34
|
+
client@host1 -> unicorn@host1(tcp)
|
35
|
+
client@host2 -> unicorn@host1(tcp)
|
36
|
+
client@host3 -> nginx@host1 -> unicorn@host1(tcp)
|
37
|
+
client@host3 -> nginx@host1 -> unicorn@host1(unix)
|
38
|
+
client@host3 -> nginx@host2 -> unicorn@host1(tcp)
|
39
|
+
|
40
|
+
The benchmark client is usually httperf.
|
41
|
+
|
42
|
+
Another gentle reminder: performance with slow networks/clients
|
43
|
+
is NOT our problem. That is the job of nginx (or similar).
|
44
|
+
|
45
|
+
== ddstream.ru
|
46
|
+
|
47
|
+
Standalone Rack app intended to show how BAD we are at slow clients.
|
48
|
+
See usage in comments.
|
49
|
+
|
50
|
+
== readinput.ru
|
51
|
+
|
52
|
+
Standalone Rack app intended to show how bad we are with slow uploaders.
|
53
|
+
See usage in comments.
|
54
|
+
|
55
|
+
== Contributors
|
56
|
+
|
57
|
+
This directory is intended to remain stable. Do not make changes
|
58
|
+
to benchmarking code which can change performance and invalidate
|
59
|
+
results across revisions. Instead, write new benchmarks and update
|
60
|
+
coments/documentation as necessary.
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
# This benchmark is the simplest test of the I/O facilities in
|
3
|
+
# unicorn. It is meant to return a fixed-sized blob to test
|
4
|
+
# the performance of things in Unicorn, _NOT_ the app.
|
5
|
+
#
|
6
|
+
# Adjusting this benchmark is done via the "bs" (byte size) and "count"
|
7
|
+
# environment variables. "count" designates the count of elements of
|
8
|
+
# "bs" length in the Rack response body. The defaults are bs=4096, count=1
|
9
|
+
# to return one 4096-byte chunk.
|
10
|
+
bs = ENV['bs'] ? ENV['bs'].to_i : 4096
|
11
|
+
count = ENV['count'] ? ENV['count'].to_i : 1
|
12
|
+
slice = (' ' * bs).freeze
|
13
|
+
body = (1..count).map { slice }.freeze
|
14
|
+
hdr = {
|
15
|
+
'Content-Length' => (bs * count).to_s.freeze,
|
16
|
+
'Content-Type' => 'text/plain'.freeze
|
17
|
+
}.freeze
|
18
|
+
response = [ 200, hdr, body ].freeze
|
19
|
+
run(lambda { |env| response })
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
# This app is intended to test large HTTP responses with or without
|
3
|
+
# a fully-buffering reverse proxy such as nginx. Without a fully-buffering
|
4
|
+
# reverse proxy, unicorn will be unresponsive when client count exceeds
|
5
|
+
# worker_processes.
|
6
|
+
#
|
7
|
+
# To demonstrate how bad unicorn is at slowly reading clients:
|
8
|
+
#
|
9
|
+
# # in one terminal, start unicorn with one worker:
|
10
|
+
# unicorn -E none -l 127.0.0.1:8080 test/benchmark/ddstream.ru
|
11
|
+
#
|
12
|
+
# # in a different terminal, start more slow curl processes than
|
13
|
+
# # unicorn workers and watch time outputs
|
14
|
+
# curl --limit-rate 8K --trace-time -vsN http://127.0.0.1:8080/ >/dev/null &
|
15
|
+
# curl --limit-rate 8K --trace-time -vsN http://127.0.0.1:8080/ >/dev/null &
|
16
|
+
# wait
|
17
|
+
#
|
18
|
+
# The last client won't see a response until the first one is done reading
|
19
|
+
#
|
20
|
+
# nginx note: do not change the default "proxy_buffering" behavior.
|
21
|
+
# Setting "proxy_buffering off" prevents nginx from protecting unicorn.
|
22
|
+
|
23
|
+
# totally standalone rack app to stream a giant response
|
24
|
+
class BigResponse
|
25
|
+
def initialize(bs, count)
|
26
|
+
@buf = "#{bs.to_s(16)}\r\n#{' ' * bs}\r\n"
|
27
|
+
@count = count
|
28
|
+
@res = [ 200,
|
29
|
+
{ 'Transfer-Encoding' => -'chunked', 'Content-Type' => 'text/plain' },
|
30
|
+
self
|
31
|
+
]
|
32
|
+
end
|
33
|
+
|
34
|
+
# rack response body iterator
|
35
|
+
def each
|
36
|
+
(1..@count).each { yield @buf }
|
37
|
+
yield -"0\r\n\r\n"
|
38
|
+
end
|
39
|
+
|
40
|
+
# rack app entry endpoint
|
41
|
+
def call(_env)
|
42
|
+
@res
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# default to a giant (128M) response because kernel socket buffers
|
47
|
+
# can be ridiculously large on some systems
|
48
|
+
bs = ENV['bs'] ? ENV['bs'].to_i : 65536
|
49
|
+
count = ENV['count'] ? ENV['count'].to_i : 2048
|
50
|
+
warn "serving response with bs=#{bs} count=#{count} (#{bs*count} bytes)"
|
51
|
+
run BigResponse.new(bs, count)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
# This app is intended to test large HTTP requests with or without
|
3
|
+
# a fully-buffering reverse proxy such as nginx. Without a fully-buffering
|
4
|
+
# reverse proxy, unicorn will be unresponsive when client count exceeds
|
5
|
+
# worker_processes.
|
6
|
+
|
7
|
+
DOC = <<DOC
|
8
|
+
To demonstrate how bad unicorn is at slowly uploading clients:
|
9
|
+
|
10
|
+
# in one terminal, start unicorn with one worker:
|
11
|
+
unicorn -E none -l 127.0.0.1:8080 test/benchmark/readinput.ru
|
12
|
+
|
13
|
+
# in a different terminal, upload 45M from multiple curl processes:
|
14
|
+
dd if=/dev/zero bs=45M count=1 | curl -T- -HExpect: --limit-rate 1M \
|
15
|
+
--trace-time -v http://127.0.0.1:8080/ &
|
16
|
+
dd if=/dev/zero bs=45M count=1 | curl -T- -HExpect: --limit-rate 1M \
|
17
|
+
--trace-time -v http://127.0.0.1:8080/ &
|
18
|
+
wait
|
19
|
+
|
20
|
+
# The last client won't see a response until the first one is done uploading
|
21
|
+
# You also won't be able to make GET requests to view this documentation
|
22
|
+
# while clients are uploading. You can also view the stderr debug output
|
23
|
+
# of unicorn (see logging code in #{__FILE__}).
|
24
|
+
DOC
|
25
|
+
|
26
|
+
run(lambda do |env|
|
27
|
+
input = env['rack.input']
|
28
|
+
buf = ''.b
|
29
|
+
|
30
|
+
# default logger contains timestamps, rely on that so users can
|
31
|
+
# see what the server is doing
|
32
|
+
l = env['rack.logger']
|
33
|
+
|
34
|
+
l.debug('BEGIN reading input ...') if l
|
35
|
+
:nop while input.read(16384, buf)
|
36
|
+
l.debug('DONE reading input ...') if l
|
37
|
+
|
38
|
+
buf.clear
|
39
|
+
[ 200, [ %W(Content-Length #{DOC.size}), %w(Content-Type text/plain) ],
|
40
|
+
[ DOC ] ]
|
41
|
+
end)
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/perl -w
|
2
|
+
# Benchmark script to spawn some processes and hammer a local unicorn
|
3
|
+
# to test accept loop performance. This only does Unix sockets.
|
4
|
+
# There's plenty of TCP benchmarking tools out there, and TCP port reuse
|
5
|
+
# has predictability problems since unicorn can't do persistent connections.
|
6
|
+
# Written in Perl for the same reason: predictability.
|
7
|
+
# Ruby GC is not as predictable as Perl refcounting.
|
8
|
+
use strict;
|
9
|
+
use Socket qw(AF_UNIX SOCK_STREAM sockaddr_un);
|
10
|
+
use POSIX qw(:sys_wait_h);
|
11
|
+
use Getopt::Std;
|
12
|
+
# -c / -n switches stolen from ab(1)
|
13
|
+
my $usage = "$0 [-c CONCURRENCY] [-n NUM_REQUESTS] SOCKET_PATH\n";
|
14
|
+
our $opt_c = 2;
|
15
|
+
our $opt_n = 1000;
|
16
|
+
getopts('c:n:') or die $usage;
|
17
|
+
my $unix_path = shift or die $usage;
|
18
|
+
use constant REQ => "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
|
19
|
+
use constant REQ_LEN => length(REQ);
|
20
|
+
use constant BUFSIZ => 8192;
|
21
|
+
$^F = 99; # don't waste syscall time with FD_CLOEXEC
|
22
|
+
|
23
|
+
my %workers; # pid => worker num
|
24
|
+
die "-n $opt_n not evenly divisible by -c $opt_c\n" if $opt_n % $opt_c;
|
25
|
+
my $n_per_worker = $opt_n / $opt_c;
|
26
|
+
my $addr = sockaddr_un($unix_path);
|
27
|
+
|
28
|
+
for my $num (1..$opt_c) {
|
29
|
+
defined(my $pid = fork) or die "fork failed: $!\n";
|
30
|
+
if ($pid) {
|
31
|
+
$workers{$pid} = $num;
|
32
|
+
} else {
|
33
|
+
work($n_per_worker);
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
reap_worker(0) while scalar keys %workers;
|
38
|
+
exit;
|
39
|
+
|
40
|
+
sub work {
|
41
|
+
my ($n) = @_;
|
42
|
+
my ($buf, $x);
|
43
|
+
for (1..$n) {
|
44
|
+
socket(S, AF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
|
45
|
+
connect(S, $addr) or die "connect: $!";
|
46
|
+
defined($x = syswrite(S, REQ)) or die "write: $!";
|
47
|
+
$x == REQ_LEN or die "short write: $x != ".REQ_LEN."\n";
|
48
|
+
do {
|
49
|
+
$x = sysread(S, $buf, BUFSIZ);
|
50
|
+
unless (defined $x) {
|
51
|
+
next if $!{EINTR};
|
52
|
+
die "sysread: $!\n";
|
53
|
+
}
|
54
|
+
} until ($x == 0);
|
55
|
+
}
|
56
|
+
exit 0;
|
57
|
+
}
|
58
|
+
|
59
|
+
sub reap_worker {
|
60
|
+
my ($flags) = @_;
|
61
|
+
my $pid = waitpid(-1, $flags);
|
62
|
+
return if !defined $pid || $pid <= 0;
|
63
|
+
my $p = delete $workers{$pid} || '(unknown)';
|
64
|
+
warn("$pid [$p] exited with $?\n") if $?;
|
65
|
+
$p;
|
66
|
+
}
|
data/test/exec/README
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
These tests require the "unicorn" executable script to be installed in
|
2
|
+
PATH and rack being directly "require"-able ("rubygems" will not be
|
3
|
+
loaded for you). The tester is responsible for setting up RUBYLIB and
|
4
|
+
PATH environment variables (or running tests via GNU Make instead of
|
5
|
+
Rake).
|