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