ebb 0.0.4 → 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/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
|
+

|
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)
|