Just my stuff http://s2.diffuse.it/ Tue, 10 Aug 2010 08:31:00 GMT my blog. mostly things about developing. working http://s2.diffuse.it/blog/show/14522-working <p>on <a href="http://31337.it/">http://31337.it/</a></p> Tue, 10 Aug 2010 08:31:00 GMT http://s2.diffuse.it/blog/show/14522-working S2 Benchmark the load time of a page with Javascript http://s2.diffuse.it/blog/show/14387-Benchmark_the_load_time_of_a_page_with_Javascript Sometimes the bottleneck is not the Database or the Application itself, but something between the client and the app. So it can be useful to track the load time with js on the client directly. This script measures the load time and then sends it back to the server with an <span class="caps">AJAX GET</span> request, so the server can save it somewhere.<br /> <pre name="code" class="JScript"> &lt;script Language="JavaScript"&gt; var from_time = new Date(); from_time = from_time.getTime(); function benchmark_loading_time() { var to_time = new Date(); to_time = to_time.getTime(); var msecs = (to_time - from_time); //submit the result var req = null; try { req = new XMLHttpRequest(); } catch(e) {} if (!req) try { req = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) {} if (!req) try { req = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) {} req.open("GET", '/benchmark_loading_time/?msecs=' + msecs + '&#38;url=' + location.href, false); req.send(null); } &lt;/script&gt; </pre><br /> <pre name="code" class="html"> &lt;body onLoad="benchmark_loading_time()"&gt; </pre> Wed, 29 Jul 2009 07:20:00 GMT http://s2.diffuse.it/blog/show/14387-Benchmark_the_load_time_of_a_page_with_Javascript S2 Oracle concat function http://s2.diffuse.it/blog/show/13586-Oracle_concat_function If you need to concatenate varchars:<br /> <pre name="code" class="sql"> CREATE OR REPLACE FUNCTION Fnc_Concat_List (cur SYS_REFCURSOR, separator VARCHAR2) RETURN VARCHAR2 IS ret VARCHAR2(32000); tmp VARCHAR2(4000); BEGIN LOOP FETCH cur INTO tmp; EXIT WHEN cur%NOTFOUND; ret := ret || separator || tmp; END LOOP; ret := SUBSTR(ret, LENGTH(separator) + 1); RETURN ret; END; / </pre><br /> Example:<br /><pre name="code" class="sql"> CREATE TABLE hallo (ID number(10) PRIMARY KEY, NAME varchar2(255)); INSERT INTO hallo (ID, NAME) VALUES (1, 'a'); INSERT INTO hallo (ID, NAME) VALUES (2, 'b'); INSERT INTO hallo (ID, NAME) VALUES (3, 'c'); COMMIT; SELECT fnc_concat_list(CURSOR(SELECT name from hallo), ', ') from dual; </pre> Tue, 14 Apr 2009 07:57:00 GMT http://s2.diffuse.it/blog/show/13586-Oracle_concat_function S2 Oracle pipelined function http://s2.diffuse.it/blog/show/12412-Oracle_pipelined_function <pre name="code" class="sql"> CREATE TABLE hallo (ID number(10) PRIMARY KEY, NAME varchar2(255)); INSERT INTO hallo (ID, NAME) VALUES (1, 'a'); INSERT INTO hallo (ID, NAME) VALUES (2, 'b'); INSERT INTO hallo (ID, NAME) VALUES (3, 'c'); COMMIT; </pre><br /> <pre name="code" class="sql"> CREATE OR REPLACE PACKAGE hoi IS TYPE hallo_lines IS TABLE OF hallo%ROWTYPE; FUNCTION h RETURN hallo_lines pipelined; END; CREATE OR REPLACE PACKAGE BODY hoi IS FUNCTION h RETURN hallo_lines pipelined IS r_hallo hallo%ROWTYPE; BEGIN FOR i IN (SELECT ID, NAME FROM hallo) LOOP r_hallo := i; -- r_hallo.ID := i.ID; -- r_hallo.NAME := i.NAME; PIPE ROW(r_hallo); END LOOP; END; END; </pre><br /> <pre name="code" class="sql"> select * from table(hoi.h); </pre> Wed, 18 Mar 2009 10:31:00 GMT http://s2.diffuse.it/blog/show/12412-Oracle_pipelined_function S2 JBoss and SPNEGO authentication with GSS-API http://s2.diffuse.it/blog/show/12019-JBoss_and_SPNEGO_authentication_with_GSS-API This took about a million google searches to get done, so here is a blog post to explain the steps that need to be done to get a Java application deployed on JBoss to use <span class="caps">GSS</span>-API to authenticate the user making the request.<br /> <h2>Prerequisites</h2><br /><ul><li>A working <span class="caps">KDC</span> (Active Directory or something equivalent)</li><li><a href="http://www.jboss.org/jbossas/downloads/">JBoss</a> (I used 5.0.1.GA)</li><li>a keytab file</li></ul><br /> <h2>Configure JBoss</h2><br />On a clean JBoss installation, open <code>server/default/conf/login-config.xml</code> and add the following at the end (just before <code>&lt;/policy&gt;</code>):<br /><pre name="code" class="Xml"> &lt;application-policy name="com.sun.security.jgss.accept"&gt; &lt;authentication&gt; &lt;login-module code="com.sun.security.auth.module.Krb5LoginModule" flag="required"&gt; &lt;module-option name="debug"&gt;true&lt;/module-option&gt; &lt;module-option name="principal"&gt;HTTP/principal@REALM&lt;/module-option&gt; &lt;module-option name="storeKey"&gt;true&lt;/module-option&gt; &lt;module-option name="useKeyTab"&gt;true&lt;/module-option&gt; &lt;module-option name="doNotPrompt"&gt;true&lt;/module-option&gt; &lt;module-option name="keyTab"&gt;/path/to/keytabfile.keytab&lt;/module-option&gt; &lt;/login-module&gt; &lt;/authentication&gt; &lt;/application-policy&gt; </pre><br />and comment the whole <code>&lt;application-policy name="Others"&gt;</code> section right above.<br />Now we need to add some global VM parameters to Java: open <code>/bin/run.conf</code> and add<br /><pre name="code">JAVA_OPTS="$JAVA_OPTS -Djavax.security.auth.useSubjectCredsOnly=false" JAVA_OPTS="$JAVA_OPTS -Djava.security.krb5.conf=/path/to/krb.conf" </pre><br /><code>/path/to/krb.conf</code> looks like this<br /><pre name="code">[libdefaults] default_realm = YOURREALM [realms] YOURREALM = { kdc = your.kdc } </pre><br />and is the global Kerberos config file for your apps.<br /> You should also increase the maximum <span class="caps">HTTP</span> header size permitted by the embedded Tomcat installation, since with Single-Sign-On <span class="caps">HTTP</span> headers may exceed the default of 4kb in complex Active Directory environments. Add a property <code>maxHttpHeaderSize="32768"</code> to your <span class="caps">HTTP</span> connector configuration in <code>server/default/deploy/jboss-web.deployer</code>. If your <span class="caps">HTTP</span> headers become larger than this setting, Tomcat just discards the requests without any log output, which can cause a lot of trouble.<br /> Well, almost done. Now you are free to get the <span class="caps">SPNEGO</span> token from inside your app and do with it whatever you want (for ex. <a href="http://s2.diffuse.it/blog/show/6602-SPNEGO_authentication_and_credential_delegation_with_Java">delegate the credentials to call some other service</a>). Mon, 09 Mar 2009 12:55:00 GMT http://s2.diffuse.it/blog/show/12019-JBoss_and_SPNEGO_authentication_with_GSS-API S2 SPNEGO authentication and credential delegation with Java http://s2.diffuse.it/blog/show/6602-SPNEGO_authentication_and_credential_delegation_with_Java Almost all our Web Services are secured with <span class="caps">SPNEGO</span>. This way we can forward the identity of the user calling a service to another service, or/and through the tiers of the service. If, for example, a user calls the &#8220;check my mail&#8221; WS, the service can get the identity of the user from the <span class="caps">SPNEGO</span> token, request a new kerberos token from the <span class="caps">KDC</span> for that user, and use that token to check for new mail on the <span class="caps">IMAP</span> server on behalf of the user.<br /><pre> Client[1] ---&gt; WS[2] ---&gt; more services[3] </pre><br /> Client calls WS, authenticates with his kerberos token, and WS can use that token to authenticate to more services on behalf of the Client.<br /> I had to search the Web for a few hours to find out how <span class="caps">SPNEGO</span> and delegation works with Java, so here is a summary:<br /> <ol><li>The client sends a request to the server</li><li>The server answers with <br /><pre> Status: 401 - Authorization Required WWW-Authenticate: Negotiate </pre></li><li>The client receives the 401 and sends the <span class="caps">SPNEGO</span> token to the server:<br /><pre> Authorization=Negotiate YIIGHwYGKwYBBQUCoIIGE[...] </pre></li><li>The server then has to decode and validate that token. If the token is forwardable then the server can use it to request a new token to authenticate to other services.</li></ol><br /> This is how to decode a <span class="caps">SPNEGO</span> token in Java:<br /><pre name="code" class="Java"> byte[] token = null; byte[] tokenForPeer = null; byte[] tokenForEndpoint = new byte[0]; String endpointSPN = null; GSSManager manager = GSSManager.getInstance(); GSSContext context = null; GSSCredential clientCred = null; GSSCredential myCred = null; try { //Oid krb5MechOid = new Oid("1.2.840.113554.1.2.2"); Oid spnegoMechOid = new Oid("1.3.6.1.5.5.2"); //first obtain it's own credentials... myCred = manager.createCredential(null, GSSCredential.DEFAULT_LIFETIME, spnegoMechOid, GSSCredential.ACCEPT_ONLY); //...and create a context for this credentials... context = manager.createContext(myCred); //...then use that context to authenticate the calling peer by reading his //spnego token System.out.println(authorization); token = Base64.decode(authorization); tokenForPeer = context.acceptSecContext(token, 0, token.length); if (!context.isEstablished()) return false; if (tokenForPeer != null) { System.out.println("there is a token to send back to the peer, but I leave this out for now"); } System.out.println("Context Established! "); System.out.println("Client principal is " + context.getSrcName()); System.out.println("Server principal is " + context.getTargName()); } catch (WSSecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (GSSException e) { // TODO Auto-generated catch block e.printStackTrace(); } </pre><br /> the authorization String contains the <span class="caps">SPNEGO</span> token from the request. Once we have established the context, we can check if the credentials can be delegated, and then request the new token:<br /> <pre name="code" class="Java"> //check if the credentials can be delegated if (!context.getCredDelegState()) { System.out.println("credentials can not be delegated!"); return false; } //get the delegated credentials from the calling peer... clientCred = context.getDelegCred(); //now create the spnego token to send to the endpoint: //create target server SPN endpointSPN = "HTTP/spnegotestserver.domain.com@REALM.COM"; System.out.println("Endpoint: " + endpointSPN); GSSName gssServerName = manager.createName(endpointSPN, GSSName.NT_USER_NAME); //...and create a new context pretending to be the caller clientContext = manager.createContext(gssServerName.canonicalize(spnegoMechOid), spnegoMechOid, clientCred, GSSContext.DEFAULT_LIFETIME); //this should be an option: enable GSS credential delegation clientContext.requestCredDeleg(true); // create a SPNEGO token for the target server tokenForEndpoint = clientContext.initSecContext(tokenForEndpoint, 0, tokenForEndpoint.length); </pre><br /> Done. Now we have a new tokenForEndpoint object that contains a valid <span class="caps">SPNEGO</span> token with the delegated credentials from the calling user. Insert it in the headers for the <span class="caps">HTTP</span> request to the next service in the chain like this<br /><pre name="code" class="Java"> "Negotiate " + Base64.encode(tokenForEndpoint) </pre><br />and you are good to go. Thu, 16 Oct 2008 12:40:00 GMT http://s2.diffuse.it/blog/show/6602-SPNEGO_authentication_and_credential_delegation_with_Java S2 Automating svn delete and svn add http://s2.diffuse.it/blog/show/1795-Automating_svn_delete_and_svn_add <p>Patrick asked me on Friday if I could somehow make<br />svn move <i>file</i><br />svn delete <i>file</i><br />and<br />svn add <i>file</i><br />somehow transparent to his not-so-computer-savy secretary. He is using svn to centrally store documents and files other people in his company edit and create, so the people can comment on changes, and work on the files while offline.<br />We thought a bit about it, and this is what came out:<br /><pre name="code" class="bash"> #!/bin/bash #this depends on inotify-tools #folder to watch FOLDER=~/tmp/svn_test/repo ############################### inotifywait -m --format '%e %w %f' -r $FOLDER -e move -e create -e delete --exclude .svn 2&gt;/dev/null| while read EVENT do EV=`echo $EVENT|cut -d' ' -f1|cut -d',' -f1` FOLDER=`echo $EVENT|cut -d' ' -f2` FILE=`echo $EVENT|cut -d' ' -f3` echo event $EVENT #continue if [ "$EV" == "CREATE" ] || [ "$EV" == "MOVED_TO" ]; then svn add --force "$FOLDER/$FILE" elif [ "$EV" == "DELETE" ] || [ "$EV" == "MOVED_FROM" ]; then svn delete "$FOLDER/$FILE" fi done </pre><br /> This way, if Ms. Secretary works on files on her working copy of the svn repository, and this script is running, for every file creation, move or delete the corresponding svn command is executed.<br />This of course works while offline too, so when Ms. Secretary is back in the office, she can just execute the svn update &#38;&#38; svn commit command and everything is back in sync.</p> Mon, 30 Jun 2008 15:52:00 GMT http://s2.diffuse.it/blog/show/1795-Automating_svn_delete_and_svn_add S2 Consume SSL protected Web Services with soap4r http://s2.diffuse.it/blog/show/62-Consume_SSL_protected_Web_Services_with_soap4r After hours of google this deserves a blog post. I did not find a clear example about this, so I am writing one.<br />I had the need to call a .NET Web Service over https with <a href="http://s2.diffuse.it/blog/show/61_Apache+mod_ssl+mutual+authentication">mutual authentication</a> and basic authentication.<br />First of all I installed the soap4r gem, then the httpclient gem (because that one supports basic authentication).<br />Then I made a folder called &#8220;certs&#8221; with all the certificates and key files I had:<br />- ca.cer &#8211; the certificate of the certification authority that signed the server certificate<br />- server.cer &#8211; the certificate of the server (signed by the guys who own ca.cer)<br />- client.cer &#8211; the client certificate I need to send along the request to get the content<br />- client.key &#8211; the key file for the client certificate<br />That&#8217;s all the certs and key files I needed.<br />Now it was time to try to get the wsdl:<br /><pre name="code" class="ruby"> require 'http-access2' url = 'https://secure.example.com/web_service/wsdl' client = HTTPAccess2::Client.new() client.ssl_config.set_client_cert_file('certs/client.cer', 'certs/client.key') client.ssl_config.set_trust_ca('certs/ca.cer') client.set_basic_auth(url, 'username', 'password') puts client.get(url).content </pre><br />This worked.<br />Time to try soap4r:<br /> <pre name="code" class="ruby"> require 'rubygems' #if you installed httpclient with rubygems you need this require 'soap/wsdlDriver' #this validates the server certificate #so you can be sure that the server you are #sending data to is the server you have the #certificate of in certs/server.cer def validate_certificate(is_ok, ctx) cert = ctx.current_cert unless (cert.subject.to_s == cert.issuer.to_s) #check the server certificate only is_ok &#38;&#38;= File.open('certs/server.cer').read == ctx.current_cert.to_pem end is_ok end wsdl = 'https://secure.example.com/web_service/wsdl' driver = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver #driver.wiredump_dev = STDOUT driver.options['protocol.http.ssl_config.verify_callback'] = method(:validate_certificate) results = driver.web_service_method(arg1, arg2) p results </pre><br /> To tell soap4r that you want basic authentication and where the certificate files are, you need to create a soap/property file with the following content:<br /><pre> client.protocol.http.basic_auth.1.url = https://secure.example.com/web_service/wsdl client.protocol.http.basic_auth.1.userid = username client.protocol.http.basic_auth.1.password = password client.protocol.http.ssl_config.client_cert = certs/client.cer client.protocol.http.ssl_config.client_key = certs/client.key client.protocol.http.ssl_config.ca_file = certs/ca.cer client.protocol.http.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER client.protocol.http.ssl_config.ciphers = ALL client.protocol.http.ssl_config.verify_depth = 1 </pre><br />This file is loaded at startup (you can find other options in soap/lib/soap/httpconfigloader.rb), and configures the ssl and basic auth stuff for soap4r. Fri, 23 May 2008 09:07:00 GMT http://s2.diffuse.it/blog/show/62-Consume_SSL_protected_Web_Services_with_soap4r S2 Apache mod_ssl mutual authentication http://s2.diffuse.it/blog/show/61-Apache_mod_ssl_mutual_authentication <p>Certificates are usually used to authenticate the server only: you connect to your banks site, and you know that it&#8217;s the bank you connected to, because the certificate they send to your browser is valid and signed by a Certification Authority you (or your browser) trust. But you can use Certificates to authenticate the user too, and that is called mutual authentication. How does it work?<br /> First of all we need to generate a CA certificate, that we then are going to use to sign the server and the client cert:<br /><pre> #generate the key openssl genrsa -out ./CA/freshCA.key 1024 #generate a certificate request openssl req -new -key ./CA/freshCA.key -out ./CA/freshCA.csr #self-sign the request openssl x509 -req -days 3650 -in ./CA/freshCA.csr \ -out ./CA/freshCA.crt -signkey ./CA/freshCA.key </pre><br />now we have a valid, self-signed CA certificate.<br />Next we are going to generate a certificate for the web server, but first we need to change some defaults in <pre>/etc/ssl/openssl.cnf</pre> and create some initial files:<br /><pre> vi /etc/ssl/openssl.cnf #and change demoCA to CA mkdir -p CA/newcerts touch CA/index.txt echo '100001' &gt;CA/serial #this is the first serial number of the certificate you are going to generate </pre><br />Next, generate the server certificate:<br /><pre> openssl genrsa -out ./server/keys/fresh.key 1024 openssl req -new -key ./server/keys/fresh.key -out ./server/requests/new_server.csr openssl ca -days 3650 -in server/requests/new_server.csr -cert \ ./CA/freshCA.crt -keyfile ./CA/freshCA.key \ -out ./server/certificates/new_server.crt </pre><br />We can use this certificate in our apache config file right away:<br /><pre> ServerName new_server:443 SSLEngine on SSLCertificateFile conf/certs/server/certificates/new_server.crt SSLCertificateKeyFile conf/certs/server/keys/new_server.key SSLCACertificateFile conf/certs/CA/freshCA.crt </pre><br />Now we have apache listening on 443 for requests, using the new_server.crt certificate that is signed by freshCA.crt.<br />Now we can generate a client certificate for a user:<br /><pre> #first generate a key openssl genrsa –des3 –out ./user/keys/simon.key 1024 #then the request openssl req –new –key ./user/keys/simon.key –out ./user/requests/simon.csr #then sign it openssl ca -in ./user/requests/simon.csr \ –cert ./CA/freshCA.crt –keyfile ./CA/freshCA.key \ –out ./user/certificates/simon.crt #convert it to p12 openssl pkcs12 -export -in ./user/certificates/simon.crt -inkey ./user/keys/simon.key -out simon.p12 </pre><br />This generates a new, signed certificate that can be installed in the browser. Just put it somewhere on your server for the user to download.<br />Once the client certificate is installed on the browser side, we have to instruct apache to check it. Just add<br /><pre> SSLVerifyClient require SSLVerifyDepth 2 </pre><br />to your config file, and nobody without a valid client certificate will be able to connect on 443 of your server. You may want to forward the email field, or the Common Name of the client certificate to your web application to be able to know who the user is:<br /><pre> #Forward Client certificate CN RequestHeader set X_SSL_CLIENT_DN_Email "%{SSL_CLIENT_S_DN_Email}s" RequestHeader set X_SSL_CLIENT_DN_CN "%{SSL_CLIENT_S_DN_CN}s" </pre><br />This makes those fields available in the request headers for your app to consume.<br /> If you ever need to revoke a cert, the steps are:<br /><pre> openssl ca –revoke ./user/certificates/simon.crt #generate the certifacate revocation list openssl ca –gencrl –out ./CA/freshCA.crl </pre><br />and tell apache to check the revocation list with this line in your config file:<br /><pre> SSLCARevocationFile conf/certs/CA/freshCA.crl </pre><br /> Other random stuff:<br />- Strip the passphrase from a key file:<br /><pre>openssl rsa -in key.pem -out newkey.pem</pre><br />- Convert a pfx file to a key and a crt file:<br /><pre>openssl pkcs12 -in filename.p12 -nocerts -out privatekey.key openssl pkcs12 -in filename.p12 -clcerts -nokeys -out sslcert.crt</pre><br />- Convert a pem file to a pkcs12 file:<br /><pre>openssl pkcs12 -export -in cert.pem -inkey key.pem -out cred.p12</pre><br />- Generate a self-signed certificate<br /><pre>openssl req -new -newkey rsa:1024 -days 365 -nodes -x509 -keyout www.example.com.pem -out www.example.com.pem</pre></p> Thu, 15 May 2008 08:59:00 GMT http://s2.diffuse.it/blog/show/61-Apache_mod_ssl_mutual_authentication S2 Benchmarking mod_rails against mongrel http://s2.diffuse.it/blog/show/53-Benchmarking_mod_rails_against_mongrel <p>I&#8217;d like to use mod_rails instead of the apache-&gt;haproxy-&gt;mongrel configuration, but before I do I wanted to make sure I don&#8217;t lose to much speed, so I decided to benchmark mod_rails against mongrel. mod_rails was <a href="http://izumi.plan99.net/blog/index.php/2008/03/31/benchmark-passenger-mod_rails-vs-mongrel-vs-thin/">already benchmarked</a>, but I don&#8217;t believe it if I don&#8217;t see it :)<br /> To benchmark it I created a new rails application with a single controller and a hello.html.erb view with only &#8216;Hello World&#8217; in it.<br /> For the test I took a very low end machine, because the virtual server I rent for the production site is not much better anyway:<br /><pre> s2@fresh:~$ cat /proc/cpuinfo processor : 0 vendor_id : CentaurHauls cpu family : 6 model : 9 model name : VIA Nehemiah stepping : 8 cpu MHz : 532.000 cache size : 64 KB fdiv_bug : no hlt_bug : no f00f_bug : no coma_bug : no fpu : yes fpu_exception : yes cpuid level : 1 wp : yes flags : fpu vme de pse tsc msr cx8 sep mtrr pge cmov pat mmx fxsr sse rng rng_en ace ace_en bogomips : 1067.72 clflush size : 32 </pre><br />with 483608 bytes of <span class="caps">RAM</span> on Ubuntu 7.10 and ruby 1.8.6.<br /> The mongrel setup consists of 3 mongrels running in production mode behind haproxy balancing the requests coming from apache.<br />This is the apache config:<br /><pre name="code" class="xml"> &lt;VirtualHost *:80&gt; ServerName benchmark.fresh ProxyRequests Off ProxyPass / http://127.0.0.1:8010/ ProxyPassReverse / http://127.0.0.1:8010/ &lt;/VirtualHost&gt; </pre><br />I keep things simple because I want to test the speed of the app running with mongrel against mod_rails. I don&#8217;t care about static stuff, url rewriting and other things in this test.<br />haproxy is configured like this:<br /><pre> ... listen rails :8010 server rails-1 localhost:8011 maxconn 1 server rails-2 localhost:8012 maxconn 1 server rails-3 localhost:8013 maxconn 1 </pre><br />I left the sessions active, and the configured database is a postrges connection. As this will be the same in the two setups, I hope this will not make any difference.<br />Now I get the mongrels running and start the benchmark (after a dry run of 1000 requests):<br /><pre> $ ab -n 1000 -c 100 http://benchmark.fresh/hello/hello This is ApacheBench, Version 2.0.40-dev &lt;$Revision: 1.146 $&gt; apache-2.0 Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Copyright 2006 The Apache Software Foundation, http://www.apache.org/ Benchmarking benchmark.fresh (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Finished 1000 requests Server Software: Mongrel Server Hostname: benchmark.fresh Server Port: 80 Document Path: /hello/hello Document Length: 12 bytes Concurrency Level: 100 Time taken for tests: 25.184929 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 480000 bytes HTML transferred: 12000 bytes Requests per second: 39.71 [#/sec] (mean) Time per request: 2518.493 [ms] (mean) Time per request: 25.185 [ms] (mean, across all concurrent requests) Transfer rate: 18.58 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 107 375.1 0 1874 Processing: 47 2272 771.9 2414 11236 Waiting: 5 2264 740.8 2413 11236 Total: 483 2380 711.4 2415 13110 Percentage of the requests served within a certain time (ms) 50% 2415 66% 2456 75% 2484 80% 2562 90% 2793 95% 2863 98% 3291 99% 4396 100% 13110 (longest request) </pre><br /> Now it&#8217;s time for mod_rails. I stopped the mongrels and haproxy to free up some precious <span class="caps">RAM</span>.<br />Apache config:<br /><pre name="code" class="xml"> &lt;VirtualHost *:80&gt; ServerName benchmark.fresh DocumentRoot /home/s2/tmp/benchmark/public/ &lt;/VirtualHost&gt; LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-1.0.1/ext/apache2/mod_passenger.so RailsSpawnServer /usr/lib/ruby/gems/1.8/gems/passenger-1.0.1/bin/passenger-spawn-server RailsRuby /usr/bin/ruby1.8 RailsEnv production </pre><br /> and after a dry run of 1000 requests:<br /><pre> $ ab -n 1000 -c 100 http://benchmark.fresh/hello/hello This is ApacheBench, Version 2.0.40-dev &lt;$Revision: 1.146 $&gt; apache-2.0 Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Copyright 2006 The Apache Software Foundation, http://www.apache.org/ Benchmarking benchmark.fresh (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Finished 1000 requests Server Software: Apache/2.2.4 Server Hostname: benchmark.fresh Server Port: 80 Document Path: /hello/hello Document Length: 12 bytes Concurrency Level: 100 Time taken for tests: 22.329544 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 514001 bytes HTML transferred: 12000 bytes Requests per second: 44.78 [#/sec] (mean) Time per request: 2232.954 [ms] (mean) Time per request: 22.330 [ms] (mean, across all concurrent requests) Transfer rate: 22.44 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 2 4.9 0 41 Processing: 48 2069 2922.9 1445 22101 Waiting: 45 2064 2922.5 1443 22101 Total: 61 2071 2922.8 1445 22114 Percentage of the requests served within a certain time (ms) 50% 1445 66% 1589 75% 1735 80% 1808 90% 2339 95% 3249 98% 17180 99% 19692 100% 22114 (longest request) </pre><br /> A pretty graph (because every serious benchmark has at least one):<br /><img src="http://s2.diffuse.it/pictures/t/807.jpg?w=400" alt="" /><br /> mod_rails can handle 44,78 req/sec, and mongrel 39,71. I think I can ditch the complicated mongrel setup after I make sure mod_rails is as stable as the mongrels are.<br /><br /><br /><strong>Update:</strong> Dan and Sean pointed out in the comments that a<br />RailsMaxPoolSize 3<br />for mod_rails would be more appropriate. Here you go:<br /><pre> $ ab -n 1000 -c 100 http://benchmark.fresh/hello/hello ... Concurrency Level: 100 Time taken for tests: 20.94626 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 514000 bytes HTML transferred: 12000 bytes Requests per second: 49.76 [#/sec] (mean) Time per request: 2009.463 [ms] (mean) Time per request: 20.095 [ms] (mean, across all concurrent requests) Transfer rate: 24.93 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 8 28.1 0 113 Processing: 131 1902 1518.9 1240 6861 Waiting: 127 1900 1518.9 1238 6860 Total: 149 1910 1515.4 1253 6861 Percentage of the requests served within a certain time (ms) 50% 1253 66% 2038 75% 3334 80% 3656 90% 4322 95% 4644 98% 5431 99% 5696 100% 6861 (longest request) </pre><br />49.76. It&#8217;s actually quicker?! I could not believe this, so I repeated the test twice, with and without RailsMaxPoolSize 3.<br /> With: 46.92, 46.51<br />Without: 10.22, 22.32<br /> Ok. Without RailsMaxPoolSize set to 3 the box started to swap, that&#8217;s why it was slower.</p> Tue, 22 Apr 2008 15:29:00 GMT http://s2.diffuse.it/blog/show/53-Benchmarking_mod_rails_against_mongrel S2