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 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 frameworks like Rails, Merb, and in the future Django.
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
- It is not meant to be a full featured web server like Lighttpd, Apache, or
7
- Nginx. Rather it should be used in multiplicity behind a
8
- [load balancer](http://brainspl.at/articles/2007/11/09/a-fair-proxy-balancer-for-nginx-and-mongrel)
9
- and a front-end server. It is not meant to serve static files in production.
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
- ## Design
17
+ A Python-WSGI binding is under development.
12
18
 
13
- The design is similar to the [Evented
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
- `gem install ebb`
23
+ gem install ebb
44
24
 
45
- Ebb depends on having glib2 headers and libraries installed. (Easily available
46
- on any UNIX system.) Manual download can be done at
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
- ## Why?
33
+ ## Running
50
34
 
51
- Because by building the server in C one is able to side-step the
52
- limitations on speed of many scripting languages. Inefficiencies are okay
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
- Following are some benchmarks. Please take these measurements with a grain
58
- of salt. Benchmarks like these are notorious for presenting an inaccurate
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
- ![Response Size](http://s3.amazonaws.com/four.livejournal/20080227/response_size.png)
41
+ When using `ebb_rails` from monit, the monitrc entry might look like this:
63
42
 
64
- This shows how the web servers perform with respect to throughput (using a
65
- simple Rack application). Concurrency is at 50 clients.
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
- ![Concurrency](http://s3.amazonaws.com/four.livejournal/20080227/concurrency.png)
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
- A simple concurrent clients benchmark serving a *hello world* page.
54
+ ## Speed
70
55
 
71
- ![Uploads](http://s3.amazonaws.com/four.livejournal/20080227/post_size.png)
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
- Ebb processes uploads before handing it over to the web application. This
74
- allows Ebb to continue to process other clients while the upload is in
75
- progress. The cliff at 40k here is because Ebb's internal request
76
- buffer is set at 40 kilobytes before it writes to file.
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! The source code
81
- is hosted at [repo.or.cz](http://repo.or.cz/w/ebb.git). It can be retrieved
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
- `git clone http://repo.or.cz/r/ebb.git`
77
+ git clone git://github.com/ry/ebb.git
85
78
 
86
- I intend to keep the C code base very small, so do email me before writing any
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
@@ -35,7 +35,7 @@ class SimpleApp
35
35
  status = 200
36
36
 
37
37
  elsif commands.include?('wait')
38
- n = commands.last.to_i
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
- require 'rubygems'
78
- require 'ruby-debug'
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
@@ -4,29 +4,40 @@ require 'google_chart'
4
4
  require 'server_test'
5
5
 
6
6
  class Array
7
- def avg
8
- sum.to_f / length
7
+ def max
8
+ inject(first) { |m, i| i > m ? i : m }
9
9
  end
10
- def sum
11
- inject(0) { |i, s| s += i }
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
- max_x = 0
19
- max_y = 0
18
+ colors = %w{F74343 444130 7DA478 E4AC3D 1F479E}
19
+ data_x = []
20
+ data_y = []
20
21
  results = ServerTestResults.open(ARGV[0])
21
- all_m = []
22
- response_chart = GoogleChart::LineChart.new('400x300', Time.now.strftime('%Y.%m.%d'), true)
23
- results.servers.each do |server|
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
- response_chart.data(server, data, colors.shift)
26
- x = data.map { |d| d[0] }.max
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 => [0,max_y])
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 => [0,max_x])
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
@@ -6,12 +6,35 @@ require 'application'
6
6
 
7
7
 
8
8
  class Array
9
- def avg
10
- sum.to_f / length
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.avg]
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 'ebb'
96
- @pid = fork { start_ebb }
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 start_ebb
142
+ def start_ebb_threaded
116
143
  require File.dirname(__FILE__) + '/../ruby_lib/ebb'
117
- server = Ebb::Server.run(app, :port => @port)
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
- puts " #{rps} req/sec (#{requests_completed} completed)"
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
@@ -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
- module Rack
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)