ebb 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +53 -62
- data/benchmark/application.rb +3 -9
- data/benchmark/bench_results.rb +30 -17
- data/benchmark/server_test.rb +54 -12
- data/bin/ebb_rails +1 -76
- data/ruby_lib/ebb.rb +136 -107
- data/ruby_lib/ebb/runner.rb +135 -0
- data/ruby_lib/ebb/runner/rails.rb +34 -0
- data/ruby_lib/rack/adapter/rails.rb +2 -0
- data/src/ebb.c +174 -142
- data/src/ebb.h +14 -15
- data/src/ebb_ruby.c +120 -85
- data/src/parser.c +322 -410
- data/test/basic_test.rb +6 -170
- data/test/ebb_rails_test.rb +34 -0
- data/test/env_test.rb +8 -12
- data/test/helper.rb +86 -0
- metadata +21 -11
- data/VERSION +0 -1
- data/ruby_lib/daemonizable.rb +0 -98
- data/test/echo_server.rb +0 -16
- data/test/test.py +0 -15
data/README
CHANGED
@@ -1,91 +1,82 @@
|
|
1
1
|
# A Web Server Called *Ebb*
|
2
2
|
|
3
3
|
Ebb aims to be a small and fast web server specifically for hosting
|
4
|
-
web
|
4
|
+
dynamic web applications. It is not meant to be a full featured web server
|
5
|
+
like Lighttpd, Apache, or Nginx. Rather it should be used in multiplicity
|
6
|
+
behind a load balancer and a front-end server. It is not meant to serve static files in production.
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
[
|
9
|
-
|
8
|
+
At one level Ebb is a minimalist C library that ties together the
|
9
|
+
[Mongrel state machine](http://mongrel.rubyforge.org/browser/tags/rel_1-0-1/ext/http11/http11_parser.rl)
|
10
|
+
and [libev](http://software.schmorp.de/pkg/libev.html) event loop. One can use
|
11
|
+
this library to drive a web application written in C. (Perhaps for embedded
|
12
|
+
devices?) However, most people will be interested in the binding of this
|
13
|
+
library to the Ruby programming language. The binding provides a
|
14
|
+
[Rack](http://rack.rubyforge.org/) server interface that allows it to host
|
15
|
+
Rails, Merb, or other frameworks.
|
10
16
|
|
11
|
-
|
17
|
+
A Python-WSGI binding is under development.
|
12
18
|
|
13
|
-
|
14
|
-
Mongrel](http://swiftiply.swiftcore.org/mongrel.html) web server; except
|
15
|
-
instead of using [EventMachine](http://rubyeventmachine.com/) to drive
|
16
|
-
network interactions, the Ebb web server handles sockets directly in C and
|
17
|
-
employs the use of the [libev](http://software.schmorp.de/pkg/libev.html)
|
18
|
-
event loop.
|
19
|
-
|
20
|
-
Connections are processed as follows:
|
21
|
-
|
22
|
-
1. libev loops and waits for incoming connections.
|
23
|
-
|
24
|
-
2. When Ebb receives a connection, it passes the request into the
|
25
|
-
[mongrel state machine](http://mongrel.rubyforge.org/browser/tags/rel_1-0-1/ext/http11/http11_parser.rl)
|
26
|
-
which securely parses the headers.
|
27
|
-
|
28
|
-
3. When the request is complete, Ebb passes the information to a user
|
29
|
-
supplied callback.
|
30
|
-
|
31
|
-
4. The Ruby binding supplying this callback transforms the
|
32
|
-
request into a [Rack](http://rack.rubyforge.org/) compatible `env` hash
|
33
|
-
and passes it on a Rack adapter.
|
34
|
-
|
35
|
-
Because Ebb is written mostly in C, other language bindings can be added to
|
36
|
-
make it useful to Non-Ruby frameworks. For example, a Python WSGI interface is
|
37
|
-
forthcoming.
|
38
|
-
|
39
|
-
## Download
|
19
|
+
## Install
|
40
20
|
|
41
21
|
The Ruby binding is available as a Ruby Gem. It can be install by executing
|
42
22
|
|
43
|
-
|
23
|
+
gem install ebb
|
44
24
|
|
45
|
-
Ebb depends on having glib2 headers and libraries installed.
|
46
|
-
|
25
|
+
Ebb depends on having glib2 headers and libraries installed. For example, in
|
26
|
+
Macintosh if one is using Darwin ports then the following should do the trick
|
27
|
+
|
28
|
+
port install glib2
|
29
|
+
|
30
|
+
Downloads are available at
|
47
31
|
the [RubyForge project page](http://rubyforge.org/frs/?group_id=5640).
|
48
32
|
|
49
|
-
##
|
33
|
+
## Running
|
50
34
|
|
51
|
-
|
52
|
-
|
53
|
-
for quick and beautiful code, but for production web servers that might handle
|
54
|
-
thousands of requests a second, an attempt should be made to be as efficient
|
55
|
-
as possible in processing connections.
|
35
|
+
Using the executable `ebb_rails` one can start Ebb with a Rails project. Use
|
36
|
+
`ebb_rails -h` to see all of the options but to start one can try
|
56
37
|
|
57
|
-
|
58
|
-
|
59
|
-
or highly slanted view of how software performs.
|
60
|
-
The code for these can be found in the `benchmark` directory.
|
38
|
+
cd my_rails_project/
|
39
|
+
ebb_rails start
|
61
40
|
|
62
|
-
|
41
|
+
When using `ebb_rails` from monit, the monitrc entry might look like this:
|
63
42
|
|
64
|
-
|
65
|
-
|
43
|
+
check process myApp4000
|
44
|
+
with pidfile /home/webuser/myApp/current/tmp/ebb.4000.pid
|
45
|
+
start program = "/usr/bin/ruby /usr/bin/ebb_rails start -d -e production -p 4000 -P /home/webuser/myApp/current/tmp/ebb.4000.pid -c /home/webuser/myApp/current" as uid webuser and gid webuser
|
46
|
+
stop program = "/usr/bin/ruby /usr/bin/ebb_rails stop -P /home/webuser/myApp/current/tmp/ebb.4000.pid" as uid webuser and gid webuser
|
47
|
+
if totalmem > 120.0 MB for 2 cycles then restart
|
48
|
+
if loadavg(5min) greater than 10 for 8 cycles then restart
|
49
|
+
group myApp
|
66
50
|
|
67
|
-
|
51
|
+
To use Ebb with a different framework you will have to do a small amount of
|
52
|
+
hacking at the moment! :)
|
68
53
|
|
69
|
-
|
54
|
+
## Speed
|
70
55
|
|
71
|
-
|
56
|
+
Because Ebb-Ruby handles most of the processing in C, it is able to do work
|
57
|
+
often times more efficiently than other Ruby language web servers.
|
72
58
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
59
|
+
![Benchmark](http://s3.amazonaws.com/four.livejournal/20080311/ebb.png)
|
60
|
+
|
61
|
+
Ebb-Ruby can handle threaded processing better than the other 'evented'
|
62
|
+
servers. This won't be of any benefit to Rails applications because Rails
|
63
|
+
places a lock around each request that wouldn't allow concurrent processing
|
64
|
+
anyway. In Merb, for example, Ebb's thread handling will allow Ebb instances
|
65
|
+
to handle larger loads. [More](http://four.livejournal.com/848525.html)
|
77
66
|
|
78
67
|
## Contributions
|
79
68
|
|
80
|
-
Contributions (patches, criticism, advice) are very welcome!
|
81
|
-
|
69
|
+
Contributions (patches, criticism, advice) are very welcome!
|
70
|
+
Please send all to to
|
71
|
+
[the mailing list](http://groups.google.com/group/ebbebb).
|
72
|
+
|
73
|
+
The source code
|
74
|
+
is hosted [github](http://github.com/ry/ebb/tree/master). It can be retrieved
|
82
75
|
by executing
|
83
76
|
|
84
|
-
|
77
|
+
git clone git://github.com/ry/ebb.git
|
85
78
|
|
86
|
-
|
87
|
-
large additions. Here are some features that I would like to add:
|
88
|
-
* Streaming responses!
|
79
|
+
Here are some features that I would like to add:
|
89
80
|
* HTTP 1.1 Expect/Continue (RFC 2616, sections 8.2.3 and 10.1.1)
|
90
81
|
* A parser for multipart/form-data
|
91
82
|
* Optimize and clean up upload handling
|
data/benchmark/application.rb
CHANGED
@@ -35,7 +35,7 @@ class SimpleApp
|
|
35
35
|
status = 200
|
36
36
|
|
37
37
|
elsif commands.include?('wait')
|
38
|
-
n = commands.last.
|
38
|
+
n = commands.last.to_f
|
39
39
|
raise "wait called with n <= 0" if n <= 0
|
40
40
|
wait(n)
|
41
41
|
body = "waited about #{n} seconds"
|
@@ -74,11 +74,5 @@ end
|
|
74
74
|
|
75
75
|
if $0 == __FILE__
|
76
76
|
require DIR + '/../ruby_lib/ebb'
|
77
|
-
|
78
|
-
|
79
|
-
Debugger.start
|
80
|
-
|
81
|
-
server = Ebb::Server.new(SimpleApp.new, :port => 4001)
|
82
|
-
puts "Ebb started on http://0.0.0.0:4001/"
|
83
|
-
server.start
|
84
|
-
end
|
77
|
+
server = Ebb::start_server(SimpleApp.new, :port => 4001)
|
78
|
+
end
|
data/benchmark/bench_results.rb
CHANGED
@@ -4,29 +4,40 @@ require 'google_chart'
|
|
4
4
|
require 'server_test'
|
5
5
|
|
6
6
|
class Array
|
7
|
-
def
|
8
|
-
|
7
|
+
def max
|
8
|
+
inject(first) { |m, i| i > m ? i : m }
|
9
9
|
end
|
10
|
-
|
11
|
-
|
10
|
+
|
11
|
+
def min
|
12
|
+
inject(first) { |m, i| i < m ? i : m }
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
15
16
|
|
16
17
|
|
17
|
-
colors = %w{F74343 444130 7DA478 E4AC3D}
|
18
|
-
|
19
|
-
|
18
|
+
colors = %w{F74343 444130 7DA478 E4AC3D 1F479E}
|
19
|
+
data_x = []
|
20
|
+
data_y = []
|
20
21
|
results = ServerTestResults.open(ARGV[0])
|
21
|
-
|
22
|
-
response_chart = GoogleChart::LineChart.new('
|
23
|
-
results.servers.
|
22
|
+
|
23
|
+
response_chart = GoogleChart::LineChart.new('500x300', Time.now.strftime('%Y.%m.%d'), true)
|
24
|
+
servers = results.servers.sort_by do |x,y|
|
25
|
+
results.data(x).map { |d| d[1] }.mean
|
26
|
+
end.reverse
|
27
|
+
|
28
|
+
cmap = {}
|
29
|
+
results.servers.sort.each { |x| cmap[x] = colors.shift }
|
30
|
+
|
31
|
+
servers.each do |server|
|
32
|
+
data = results.data(server).sort
|
33
|
+
data_x += data.map { |d| d[0] }
|
34
|
+
data_y += data.map { |d| d[1] }
|
35
|
+
end
|
36
|
+
|
37
|
+
servers.each do |server|
|
24
38
|
data = results.data(server).sort
|
25
|
-
|
26
|
-
|
27
|
-
y = data.map { |d| d[1] }.max
|
28
|
-
max_x = x if x > max_x
|
29
|
-
max_y = y if y > max_y
|
39
|
+
data.map! { |d| [d[0]-data_x.min, d[1]-data_y.min]}
|
40
|
+
response_chart.data(server, data, cmap[server])
|
30
41
|
end
|
31
42
|
|
32
43
|
label = case results.benchmark
|
@@ -36,10 +47,12 @@ when "wait_fib", "concurrency"
|
|
36
47
|
"concurrency"
|
37
48
|
when "post_size"
|
38
49
|
"kilobytes uploaded"
|
50
|
+
when "wait", "wait_fib"
|
51
|
+
"seconds waited every 10 requests"
|
39
52
|
end
|
40
53
|
|
41
|
-
response_chart.axis(:y, :range => [
|
54
|
+
response_chart.axis(:y, :range => [data_y.min,data_y.max])
|
42
55
|
response_chart.axis(:y, :labels => ['req/s'], :positions => [50])
|
43
|
-
response_chart.axis(:x, :range => [
|
56
|
+
response_chart.axis(:x, :range => [data_x.min,data_x.max])
|
44
57
|
response_chart.axis(:x, :labels => [label], :positions => [50])
|
45
58
|
puts response_chart.to_url
|
data/benchmark/server_test.rb
CHANGED
@@ -6,12 +6,35 @@ require 'application'
|
|
6
6
|
|
7
7
|
|
8
8
|
class Array
|
9
|
-
def
|
10
|
-
sum
|
9
|
+
def mean
|
10
|
+
@mean ||= sum / length
|
11
|
+
end
|
12
|
+
|
13
|
+
def variance
|
14
|
+
@variance ||= map { |x| (mean - x)**2 }.sum / length
|
15
|
+
end
|
16
|
+
|
17
|
+
def stdd
|
18
|
+
@stdd ||= Math.sqrt(variance)
|
19
|
+
end
|
20
|
+
|
21
|
+
def errors
|
22
|
+
map { |x| (mean - x).abs }
|
23
|
+
end
|
24
|
+
|
25
|
+
def center_avg
|
26
|
+
return mean if stdd < 10
|
27
|
+
acceptable_error = 0
|
28
|
+
good_points = []
|
29
|
+
while good_points.empty?
|
30
|
+
acceptable_error += stdd
|
31
|
+
good_points = select { |x| (mean - x).abs < acceptable_error }
|
32
|
+
end
|
33
|
+
good_points.mean
|
11
34
|
end
|
12
35
|
|
13
36
|
def sum
|
14
|
-
inject(0) { |i, s| s += i }
|
37
|
+
inject(0.to_f) { |i, s| s += i }
|
15
38
|
end
|
16
39
|
|
17
40
|
def rand_each(&block)
|
@@ -27,7 +50,7 @@ class ServerTestResults
|
|
27
50
|
new
|
28
51
|
end
|
29
52
|
end
|
30
|
-
|
53
|
+
attr_reader :results
|
31
54
|
def initialize(results = [])
|
32
55
|
@results = results
|
33
56
|
end
|
@@ -61,7 +84,7 @@ class ServerTestResults
|
|
61
84
|
datas = []
|
62
85
|
ticks.each do |c|
|
63
86
|
measurements = server_data.find_all { |d| d[:input] == c }.map { |d| d[:rps] }
|
64
|
-
datas << [c, measurements.
|
87
|
+
datas << [c, measurements.mean] unless measurements.empty?
|
65
88
|
end
|
66
89
|
datas
|
67
90
|
end
|
@@ -80,7 +103,7 @@ class ServerTest
|
|
80
103
|
end
|
81
104
|
|
82
105
|
def kill
|
83
|
-
Process.kill('KILL', @pid)
|
106
|
+
Process.kill('KILL', @pid) if @pid
|
84
107
|
end
|
85
108
|
|
86
109
|
def running?
|
@@ -92,12 +115,16 @@ class ServerTest
|
|
92
115
|
case name
|
93
116
|
when 'emongrel'
|
94
117
|
@pid = fork { start_emongrel }
|
95
|
-
when '
|
96
|
-
@pid = fork {
|
118
|
+
when 'ebb_threaded'
|
119
|
+
@pid = fork { start_ebb_threaded }
|
120
|
+
when 'ebb_sequential'
|
121
|
+
@pid = fork { start_ebb_sequential }
|
97
122
|
when 'mongrel'
|
98
123
|
@pid = fork { start_mongrel }
|
99
124
|
when 'thin'
|
100
125
|
@pid = fork { start_thin }
|
126
|
+
when 'fcgi'
|
127
|
+
@pid = fork { start_fcgi }
|
101
128
|
end
|
102
129
|
end
|
103
130
|
|
@@ -112,9 +139,14 @@ class ServerTest
|
|
112
139
|
Rack::Handler::Mongrel.run(app, :Host => '0.0.0.0', :Port => @port.to_i)
|
113
140
|
end
|
114
141
|
|
115
|
-
def
|
142
|
+
def start_ebb_threaded
|
116
143
|
require File.dirname(__FILE__) + '/../ruby_lib/ebb'
|
117
|
-
server = Ebb::
|
144
|
+
server = Ebb::start_server(app, :port => @port, :threaded_processing => true)
|
145
|
+
end
|
146
|
+
|
147
|
+
def start_ebb_sequential
|
148
|
+
require File.dirname(__FILE__) + '/../ruby_lib/ebb'
|
149
|
+
server = Ebb::start_server(app, :port => @port, :threaded_processing => false)
|
118
150
|
end
|
119
151
|
|
120
152
|
def start_mongrel
|
@@ -128,6 +160,10 @@ class ServerTest
|
|
128
160
|
Rack::Handler::Thin.run(app, :Port => @port)
|
129
161
|
end
|
130
162
|
|
163
|
+
def start_fcgi
|
164
|
+
Rack::Handler::FastCGI.run(app, :Port => @port)
|
165
|
+
end
|
166
|
+
|
131
167
|
def trial(ab_cmd)
|
132
168
|
cmd = ab_cmd.sub('PORT', @port.to_s)
|
133
169
|
|
@@ -140,13 +176,19 @@ class ServerTest
|
|
140
176
|
if r =~ /Complete requests:\s*(\d+)/
|
141
177
|
requests_completed = $1.to_i
|
142
178
|
end
|
143
|
-
|
179
|
+
if r =~ /Failed requests:\s*(\d+)/
|
180
|
+
failed_requests = $1.to_i
|
181
|
+
else
|
182
|
+
raise "didn't get how many failed requests from ab"
|
183
|
+
end
|
184
|
+
puts " #{rps} req/sec (#{requests_completed} completed, #{failed_requests} failed)"
|
144
185
|
|
145
186
|
{
|
146
187
|
:server => @name,
|
147
188
|
:rps => rps,
|
148
189
|
:requests_completed => requests_completed,
|
190
|
+
:requests_failed => failed_requests,
|
149
191
|
:ab_cmd => cmd
|
150
192
|
}
|
151
193
|
end
|
152
|
-
end
|
194
|
+
end
|
data/bin/ebb_rails
CHANGED
@@ -1,79 +1,4 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require File.dirname(__FILE__) + '/../ruby_lib/ebb'
|
3
|
-
require 'optparse'
|
4
|
-
require 'rubygems'
|
5
|
-
require 'rack'
|
6
3
|
|
7
|
-
|
8
|
-
module Adapter
|
9
|
-
autoload :Rails, Ebb::LIBDIR + '/rack/adapter/rails'
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
options = {
|
14
|
-
:root => Dir.pwd,
|
15
|
-
:env => 'development',
|
16
|
-
:hort => '0.0.0.0',
|
17
|
-
:port => 3000,
|
18
|
-
:timeout => 60
|
19
|
-
}
|
20
|
-
|
21
|
-
|
22
|
-
opts = OptionParser.new do |opts|
|
23
|
-
opts.banner = "Usage: ebb_rails [options] start|stop"
|
24
|
-
|
25
|
-
opts.separator ""
|
26
|
-
opts.separator "Server options:"
|
27
|
-
|
28
|
-
# opts.on("-s", "--socket SOCKET", "listen on socket") { |socket| options[:socket] = socket }
|
29
|
-
opts.on("-p", "--port PORT", "use PORT (default: 3000)") { |port| options[:port] = port }
|
30
|
-
opts.on("-e", "--env ENV", "Rails environment (default: development)") { |env| options[:env] = env }
|
31
|
-
opts.on("-c", "--chdir PATH", "Rails root dir (default: current dir)") { |dir| options[:root] = dir }
|
32
|
-
opts.on("-d", "--daemonize", "Daemonize") { options[:daemonize] = true }
|
33
|
-
opts.on("-l", "--log-file FILE", "File to redirect output") { |file| options[:log_file] = file }
|
34
|
-
opts.on("-P", "--pid-file FILE", "File to store PID") { |file| options[:pid_file] = file }
|
35
|
-
opts.on("-t", "--timeout SEC", "Request or command timeout in sec",
|
36
|
-
"(default: #{options[:timeout]})") { |sec| options[:timeout] = sec }
|
37
|
-
|
38
|
-
opts.separator ""
|
39
|
-
opts.separator "Common options:"
|
40
|
-
|
41
|
-
opts.on_tail("-h", "--help", "Show this message") do
|
42
|
-
puts opts
|
43
|
-
exit
|
44
|
-
end
|
45
|
-
|
46
|
-
opts.on_tail('-v', '--version', "Show version") do
|
47
|
-
puts "Ebb #{Ebb::VERSION}"
|
48
|
-
exit
|
49
|
-
end
|
50
|
-
|
51
|
-
opts.parse! ARGV
|
52
|
-
end
|
53
|
-
|
54
|
-
case ARGV[0]
|
55
|
-
|
56
|
-
when 'start'
|
57
|
-
app = Rack::Adapter::Rails.new(options)
|
58
|
-
server = Ebb::Server.new(app, options)
|
59
|
-
|
60
|
-
server.pid_file = options[:pid_file]
|
61
|
-
server.log_file = options[:log_file]
|
62
|
-
server.timeout = options[:timeout]
|
63
|
-
|
64
|
-
server.daemonize if options[:daemonize]
|
65
|
-
|
66
|
-
server.start
|
67
|
-
|
68
|
-
when 'stop'
|
69
|
-
Ebb::Server.kill options[:pid_file], options[:timeout]
|
70
|
-
|
71
|
-
when nil
|
72
|
-
puts "Command required"
|
73
|
-
puts opts
|
74
|
-
exit 1
|
75
|
-
|
76
|
-
else
|
77
|
-
abort "Invalid command : #{ARGV[0]}"
|
78
|
-
|
79
|
-
end
|
4
|
+
Ebb::Runner::Rails.new.run(ARGV)
|