[ANN] acgi-0.1.0

A

Ara.T.Howard

URIS

http://codeforpeople.com/lib/ruby/acgi/


SYNOPSIS

as=C2=B7sid=C2=B7u=C2=B7ous (adj.)

1. constant in application or attention; diligent: an assiduous work=
er who
strove for perfection.

2. unceasing; persistent: assiduous research.

acgi : assiduous or ara's cgi (emphasis on the 'ass' in assiduous) a d=
rop-in
replacement for ruby's built-in cgi that provides copious features suc=
h as

- no apache modules
- persistence
- speed
- simplicity
- familiarity
- no apache modules
- browser neutrality
- could easily be made platform independent
- no apache modules
- no special webserver setup or system privledges required
- ability to install to a webserver via ftp
- no apache modules
- session affinity, all request handled by one process
- automatic reload if code changes
- ability to run script simulaneously as acgi and cgi for debuggging
- ability to easily start/stop/restart/check-on running server
- 178 lines of ruby code (and one c program)
- no apache modules


PERFORMANCE

case one, a simple cgi that just dumps the environment:

with acgi:

[ahoward@localhost acgi-0.1.0]$ ab -n100 -c4 http://localhost/acgi/a=
cgi-0.1.0/ | grep 'Requests per second'
Requests per second: 74.93 [#/sec] (mean)

without:

[ahoward@localhost acgi-0.1.0]$ ab -n100 -c4 http://localhost/acgi/a=
cgi-0.1.0/server.cgi | grep 'Requests per second'
Requests per second: 18.76 [#/sec] (mean)


a more realistic cgi that uses sessions and sleeps for 1 second to mim=
ic
connecting to a database:

with acgi:

[ahoward@localhost acgi-0.1.0]$ ab -n100 -c4 http://localhost/acgi/a=
cgi-0.1.0/ | grep 'Requests per second'
Requests per second: 24.20 [#/sec] (mean)

without:

[ahoward@localhost acgi-0.1.0]$ ab -n100 -c4 http://localhost/acgi/a=
cgi-0.1.0/server.cgi | grep 'Requests per second'
Requests per second: 3.63 [#/sec] (mean)


ARCHITECHTURE

the design of acgi is similar to that of fastcgi (http://www.fastcgi.c=
om) but
requires no external modules, configuration of apache, etc.

a acgi application consists of a cgi server backend which loops, handl=
ing all
incoming requests; the requests are delegated to this backend server v=
ia a
simple, fast to start up, 'index.cgi' program written in c. communica=
tion
between 'index.cgi' and it's backend server is via named pipes (fifos)=
:


-------------
| index.cgi | <- transient compiled c c=
ode
-------------
| | |
| fifos for stderr, stdout, stdin, env
| | |
/ | \
------------------------------
| |
| cgi server | <- persistent looping rub=
y code
| |
------------------------------

note that the architechture is similar in spirit to fastcgi - it provi=
des
speed by avoiding startup overhead and redundant work like database co=
nnection
setup. in this case, contrasted with fastcgi, the whole thing takes p=
lace
outside of the webserver in the application domain and, therfore, comm=
unicates
via named pipes rather than unix domain sockets.


REQUEST CYCLE

- request comes in to web server

- request is passed to index.cgi which, itself, is a very simple compi=
led c
program (ultra fast startup time) which in turn does the following

- make sure the ruby server is running, spawn it in the background=
iff
required. this is a non-blocking operation that functions as a =
simple
process manager in order to ensure a server is running at all ti=
mes.

- aqurire a lock (fcntl) to prevent any other concurrent invocatio=
ns of
index.cgi from overlapping - all invocations procede one at a ti=
me in
the order of receipt. there are never concurrent requests to th=
e
server. we can't all send data down the pipe at once.

- serialize the environment and send it down the pipe

- read any stderr/stdout from the ruby server via fifos and write =
them to
stderr/stdout respectively. stdout and stderr go to the 'normal=
' places
- the client and webserver log respectively.

- release lock - automatic when index.cgi process dies anyhow

- the ruby server, for it's part, does the following

- aquire a lock which prevent multiple copies of itself from running
simoultaneously. this is the same lock the c program checks in a
non-blocking fashion to see if the server is running.

- loops doing the following

- loading the environment

- handling request with stderr/stdout/stdin redirected to pipes be=
ing read
by index.cgi, the compiled c program

this cycle is mostly transparent to the cgi progam. for instace, to c=
onvert
an existing cgi program into an acgi program one would simply change

require 'cgi'

cgi =3D CGI::new
cgi.out{ content }

to

require 'cgi'
require 'acgi

ACGI::each_cgi do |cgi|
cgi.out{ content }
end

the same cgi script acts both as the backend to the index.cgi c progra=
m and
the frontend to any 'normal' cgi requests. the works as follows: say=
you
name your cgi program 'server.cgi' and it lives in a directory under t=
he
webroot like so

acgi/server.cgi
acgi/index.cgi

then, assuming a webserver setup that uses index.cgi for directory ind=
exing,
one can hit a url like

http://localhost/acgi/

or

http://localhost/acgi/index.cgi

and the fast version will be run. hitting

http://localhost/acgi/server.cgi

results in normal (slow) cgi mode - useful for debugging.


IMPLEMENTATION

shoddy - but getting better (note move to 0.1.0 !!).

this version is proof of concept only!!! it's likely to run only on l=
inux,
though it may run on many *nix platforms. or maybe not. the sun coul=
d
explode if you run the example program. security is not considered.


RUNNING THE EXAMPLE

- unpack tarball in webroot

- make

- point browser at

http:://your.host.com/path/where/you/unpacked/

to see the acgi version or

- point browser at

http:://your.host.com/path/where/you/unpacked/server.cgi

to see the slow cgi version

obviously you'll need cgi setup for you web server, index.cgi set for
directory index, ruby installed, etc. but nothing out of the ordinary=
 
M

Matthias Georgi

Hi Ara.

Great Work, really useful.

ACGI is great for webservers without fcgi support.
I've managed acgi to work with fnord, a super small(13k static binary)
and fast webserver and indeed, benchmarking gave me impressing results.
Standard cgi is around 20 req/s as expected and acgi around 120-150
req/s .

What do you think about running multiple ruby servers, each with its
own ipc.
The index.cgi tries each server to acquire a lock and chooses the first
free one.
Should improve performance for non-trivial cgis.

Cheers,
Matthias
 
A

Ara.T.Howard

Great Work, really useful.

thanks matthias!
ACGI is great for webservers without fcgi support. I've managed acgi to
work with fnord, a super small(13k static binary) and fast webserver and
indeed, benchmarking gave me impressing results. Standard cgi is around 20
req/s as expected and acgi around 120-150 req/s .

wow. on apache i see only around 4-5 times speedup. thanks alot for testing.
What do you think about running multiple ruby servers, each with its own
ipc. The index.cgi tries each server to acquire a lock and chooses the
first free one. Should improve performance for non-trivial cgis.

exactly my plan. essentially the simplest round robin/random load balancer.
dan fitzpatrick has suggesting running multiple instances behind blance. at
this point i'm working to make things faster. i've been tweaking lots of
stuff with no real luck yet but my understanding of fcgi suggest i should be
able to be closer to it's speed than i am. also on the radar is re-writing
index.c using the apr (apache portable runtime) so it'd compile on any
platform and, along with this, a binary windows dist. the next step would be
writing some code generation/configuration script so i could do away with the
makefile. in any case it's in the works and suggestions are more than
welcome.

an io/ipc/c gurus out there please chime in with a two lines fix to make it
faster please ;-)

cheers.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| Your life dwells amoung the causes of death
| Like a lamp standing in a strong breeze. --Nagarjuna
===============================================================================
 
M

Matthias Georgi

Hi Ara,

out of curiosity, I have been playing around with my apache2 setup and
benchmarked cgi, acgi and fcgi. I used this configuration for maximum
performance:

<IfModule prefork.c>
StartServers 100
MinSpareServers 100
MaxSpareServers 100
MaxClients 250
MaxRequestsPerChild 50
</IfModule>

Running on my Athlon-2800 I get these results:

CGI:
# ab -n100 -c10 http://127.0.0.1:8080/env.cgi | grep "Requests per
second"
Requests per second: 52.00 [#/sec] (mean)

ACGI:
# ab -n100 -c10 http://127.0.0.1:8080/index.cgi | grep "Requests per
second"
Requests per second: 306.55 [#/sec] (mean)

FCGI:
# ab -n100 -c10 http://127.0.0.1:8080/server.fcgi | grep "Requests per
second"
Requests per second: 952.44 [#/sec] (mean)

I think acgi could outperform fcgi, because the fcgi-protocol takes
cares about multiplexing and the ruby-fcgi is not multi-threaded. So
this multiplexing thing is maybe overhead. Do you plan a multithreaded
implementation of acgi on the ruby side? I guess not.

On the other hand the bottleneck is maybe the additional fork and exec,
so mod_acgi is maybe worth doing.

Cheers,
Matthias
 
A

Ara.T.Howard

Hi Ara,

out of curiosity, I have been playing around with my apache2 setup and
benchmarked cgi, acgi and fcgi. I used this configuration for maximum
performance:

<IfModule prefork.c>
StartServers 100
MinSpareServers 100
MaxSpareServers 100
MaxClients 250
MaxRequestsPerChild 50
</IfModule>

valuable - thanks for sharing.
Running on my Athlon-2800 I get these results:

CGI:
# ab -n100 -c10 http://127.0.0.1:8080/env.cgi | grep "Requests per
second"
Requests per second: 52.00 [#/sec] (mean)

ACGI:
# ab -n100 -c10 http://127.0.0.1:8080/index.cgi | grep "Requests per
second"
Requests per second: 306.55 [#/sec] (mean)

FCGI:
# ab -n100 -c10 http://127.0.0.1:8080/server.fcgi | grep "Requests per
second"
Requests per second: 952.44 [#/sec] (mean)

wow - not bad. but could be better eh.
I think acgi could outperform fcgi, because the fcgi-protocol takes cares
about multiplexing and the ruby-fcgi is not multi-threaded. So this
multiplexing thing is maybe overhead. Do you plan a multithreaded
implementation of acgi on the ruby side? I guess not.

i've been playing with all sorts of things but am unclear as to what the
bottleneck is. any suggestions welcome.
On the other hand the bottleneck is maybe the additional fork and exec, so
mod_acgi is maybe worth doing.

maybe. just running

./index.cgi </dev/null>/dev/null

shows times between 0.001 and 0.01. so it seems the low side is about one
hundred and the high around 1000 rps. however, that it on my machine where i
only get about 80 and 160 rps for acgi and fcgi respectively. i've tried
various things in the c program (pthreads, forking) to make it faster and it
makes little difference afaikt - so i think the speed must come on the ruby
side. right now i'm thinking of a way operate in non-blocking mode
exclusively but it'll take a little coding on the c side. anyhow, i'm out of
town for a few days and will consider while i'm away.

again, thanks for looking at this.

cheers.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| Your life dwells amoung the causes of death
| Like a lamp standing in a strong breeze. --Nagarjuna
===============================================================================
 
D

Dan Janowski

Ara,

My applause to you on trying to replace FastCGI. I gave up on it
after inexplicable weirdness began to set in.

Fork/exec overhead is typically small, and with caching, shared
libraries, etc. there is likely little to be gained by making your
index.cgi a module (and penalty a plenty).

Try timing the fifo transit and locking. One way of side stepping the
fifo limitation is to consider shared memory mapped files (via mmap)
which are frighteningly fast and cut the kernel transit out all
together.

Dan

Hi Ara,

out of curiosity, I have been playing around with my apache2 setup
and
benchmarked cgi, acgi and fcgi. I used this configuration for maximum
performance:

<IfModule prefork.c>
StartServers 100
MinSpareServers 100
MaxSpareServers 100
MaxClients 250
MaxRequestsPerChild 50
</IfModule>

valuable - thanks for sharing.

Running on my Athlon-2800 I get these results:

CGI:
# ab -n100 -c10 http://127.0.0.1:8080/env.cgi | grep "Requests per
second"
Requests per second: 52.00 [#/sec] (mean)

ACGI:
# ab -n100 -c10 http://127.0.0.1:8080/index.cgi | grep "Requests per
second"
Requests per second: 306.55 [#/sec] (mean)

FCGI:
# ab -n100 -c10 http://127.0.0.1:8080/server.fcgi | grep "Requests
per
second"
Requests per second: 952.44 [#/sec] (mean)

wow - not bad. but could be better eh.

I think acgi could outperform fcgi, because the fcgi-protocol
takes cares
about multiplexing and the ruby-fcgi is not multi-threaded. So this
multiplexing thing is maybe overhead. Do you plan a multithreaded
implementation of acgi on the ruby side? I guess not.

i've been playing with all sorts of things but am unclear as to
what the
bottleneck is. any suggestions welcome.

On the other hand the bottleneck is maybe the additional fork and
exec, so
mod_acgi is maybe worth doing.

maybe. just running

./index.cgi </dev/null>/dev/null

shows times between 0.001 and 0.01. so it seems the low side is
about one
hundred and the high around 1000 rps. however, that it on my
machine where i
only get about 80 and 160 rps for acgi and fcgi respectively. i've
tried
various things in the c program (pthreads, forking) to make it
faster and it
makes little difference afaikt - so i think the speed must come on
the ruby
side. right now i'm thinking of a way operate in non-blocking mode
exclusively but it'll take a little coding on the c side. anyhow,
i'm out of
town for a few days and will consider while i'm away.

again, thanks for looking at this.

cheers.

-a
--
======================================================================
=========
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| Your life dwells amoung the causes of death
| Like a lamp standing in a strong breeze. --Nagarjuna
======================================================================
=========
 
A

Ara.T.Howard

My applause to you on trying to replace FastCGI. I gave up on it after
inexplicable weirdness began to set in.

i know the feelings. it can be setup well - but it's non-trivial.
Fork/exec overhead is typically small, and with caching, shared libraries,
etc. there is likely little to be gained by making your index.cgi a module
(and penalty a plenty).

this is what i'm hoping. also i strongly feel that writing an apache module
would be the path to ruin.
Try timing the fifo transit and locking. One way of side stepping the fifo
limitation is to consider shared memory mapped files (via mmap) which are
frighteningly fast and cut the kernel transit out all together.

yeah... but an mmap protocol would not block when the the 'pipe was full' and
would require a custom com protocol. i am considering it though. i haven't
given up on other alternative yet thought - not least of which a c cgi
extension - perhaps a wrapper on cgic.

btw. i you are interested in playing around with the code let me know - i
could use some help ;-)

cheers.
Dan

Hi Ara,

out of curiosity, I have been playing around with my apache2 setup and
benchmarked cgi, acgi and fcgi. I used this configuration for maximum
performance:

<IfModule prefork.c>
StartServers 100
MinSpareServers 100
MaxSpareServers 100
MaxClients 250
MaxRequestsPerChild 50
</IfModule>

valuable - thanks for sharing.

Running on my Athlon-2800 I get these results:

CGI:
# ab -n100 -c10 http://127.0.0.1:8080/env.cgi | grep "Requests per
second"
Requests per second: 52.00 [#/sec] (mean)

ACGI:
# ab -n100 -c10 http://127.0.0.1:8080/index.cgi | grep "Requests per
second"
Requests per second: 306.55 [#/sec] (mean)

FCGI:
# ab -n100 -c10 http://127.0.0.1:8080/server.fcgi | grep "Requests per
second"
Requests per second: 952.44 [#/sec] (mean)

wow - not bad. but could be better eh.

I think acgi could outperform fcgi, because the fcgi-protocol takes cares
about multiplexing and the ruby-fcgi is not multi-threaded. So this
multiplexing thing is maybe overhead. Do you plan a multithreaded
implementation of acgi on the ruby side? I guess not.

i've been playing with all sorts of things but am unclear as to what the
bottleneck is. any suggestions welcome.

On the other hand the bottleneck is maybe the additional fork and exec, so
mod_acgi is maybe worth doing.

maybe. just running

./index.cgi </dev/null>/dev/null

shows times between 0.001 and 0.01. so it seems the low side is about one
hundred and the high around 1000 rps. however, that it on my machine where
i
only get about 80 and 160 rps for acgi and fcgi respectively. i've tried
various things in the c program (pthreads, forking) to make it faster and
it
makes little difference afaikt - so i think the speed must come on the ruby
side. right now i'm thinking of a way operate in non-blocking mode
exclusively but it'll take a little coding on the c side. anyhow, i'm out
of
town for a few days and will consider while i'm away.

again, thanks for looking at this.

cheers.

-a
--
======================================================================
=========
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| Your life dwells amoung the causes of death
| Like a lamp standing in a strong breeze. --Nagarjuna
======================================================================
=========

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| Your life dwells amoung the causes of death
| Like a lamp standing in a strong breeze. --Nagarjuna
===============================================================================
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
473,968
Messages
2,570,154
Members
46,701
Latest member
XavierQ83

Latest Threads

Top