Vishnu Gopal

blog.vishnugopal.com

Tag: development

Loving Git

I suppose this must be the zillionth post about how Git is so cool, but I’ll tell you what I love about it:

  1. Ultra fast commits. Coming from subversion, typing commit and getting a prompt back instantly is so surprising that you double check the first few times.
  2. Branching! God, you don’t realize how much you miss it until you have fast branching and merging. Without the pain of creating cps, just a simple git checkout -b branch_name and git merge branch_name and everything just works.
  3. The above was the reason I tried git in the first place. mobME’s an SVN shop and we do the usual trunk, tags, branches dance. When the trunk shapes up to be stable though I can’t seem to do anything on it. I can fork off a new branch in SVN but that’s too painful to even think about. What do I do now? git clone it, create a branch, and do regular git svn rebases and dcommits.
  4. Oh did I mention bidirectional SVN support? Without which I wouldn’t/couldn’t have switched no matter how much I wanted to try this cool new thing.

But it’s great and it really changes the way you think about VCS. Notice something? I didn’t even talk about distributed source control, and that’s coz even when I use git like I use SVN – committing to a central repo at the end and pulling changes from it, it’s brilliant. I’d definitely want to explore cool stuff like github soon for personal use (some gracious soul gave me an invite some time back).

An Orkut Application via a JSON API

I talked about delegating rendering in Symfony for creating a JSON API. Now here’s a consumer: an Orkut opensocial gadget:


MobshareOrkutAPI = {

	api_url: 'http://api.mobshare/api.php',
	cache_time: 0, //0 to disable

	makeCachedRequest: function(url, callback, params, refreshInterval) {
	  var ts = new Date().getTime();
	  var sep = "?";
	  if (refreshInterval && refreshInterval > 0) {
	    ts = Math.floor(ts / (refreshInterval * 1000));
	  }
	  if (url.indexOf("?") > -1) {
	    sep = "&";
	  }
	  url = [ url, sep, "nocache=", ts ].join("");
	  gadgets.io.makeRequest(url, callback, params);
	},

	call: function(module, action, params, callback) {
		var options = {};
		options[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;
		this.makeCachedRequest(this.api_url + '/' + module + '/' + action + '?' + params, callback, options, this.cache_time);
	}

};

makeCachedRequest has been plaigarized from the Opensocial documentation and it’s very useful to bust the cache for requests. Also, notice this line for setting the content-type to JSON:


options[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;

This is how we access that API, a code fragment:


...
authenticate: function(alias, mobile_no, password) {
	MobshareOrkutAPI.call('user', 'authenticate',
		'alias=' + encodeURIComponent(alias) + '&mobile_no=' + encodeURIComponent(mobile_no) +
			'&password=' + encodeURIComponent(password),
 		MyOrkutApp.login
	);
}
...
login: function(orkut_response) {
	response = orkut_response.data;
	data_success = response['success'];
	data_error = response['error'];

	if(data_success) {
		html = '<h2>Login Successful!</h2>';
		html += '<p>Welcome: ' + data_success.name + '!</p>';
	} else if(data_error) {
		html = '<h2>Login Unsuccessful!</h2>';
		html += '<p>' + data_error + '!</p>';
	}

	document.getElementById('mobshare_login').innerHTML += html;
},
...

Note that orkut_response.data is automatically set by Orkut because you passed in the JSON content type; it parses the data received and creates a javascript object. Cool, ain’t it? It’s very easy to create a proper interactive Opensocial app this way.

Delegate Rendering in Symfony

Warning: pretty advanced Symfony ahead: if you’re not familiar with the framework, this wouldn’t make sense.

I recently developed a bare-bones API for Mobshare (it’s not yet live), and to keep everything clean, I abstracted away the rendering bit from the controller to an external class. It ended up being a sweet solution, so here it is!

I wanted this to be a JSONP API (the major use case would be a JS client, and parsing XML etc. via JS is a pain. Besides JSON is much shorter over the wire). I didn’t want to rewrite a lot of code: checking for a callback parameter and wrapping the returned string around the JSON output was just begging to be refactored away. So here it is, a generic Symfony JSON API wrapper class:


<?php

class JSONPAPI {

	const CALLBACK_PARAMETER = 'callback';

	var $status;
	var $data;
	var $callback;


	public function __construct($status, $data) {
		$this->status = $status;
		$this->data = $data;

		$callback_parameter_value = $this->getCurrentAction()->getRequestParameter(self::CALLBACK_PARAMETER);
		if($callback_parameter_value)
			$this->callback = $callback_parameter_value;
	}

	public function render() {
		$render_text = json_encode(array($this->status => $this->data));

		if($this->callback)
			$render_text = $this->callback . '(' . $render_text . ');';

		$this->setJavascriptHeaders();
		return $this->getCurrentAction()->renderText($render_text);
	}

	//hack to get the current action
	private function getCurrentAction() {
		return sfContext::getInstance()->getActionStack()->getLastEntry()->getActionInstance();
	}

	private function setJavascriptHeaders() {
		sfContext::getInstance()->getResponse()->setParameter('Content-Type', 'application/javascript', 'symfony/response/http/headers');
	}
}

The bits of magic here are the getCurrentAction() function and the render() call. It works on one very simple idea: everything in Symfony can be accessed from the sfContext::getInstance() object, you just need to dig deep enough.

Once you write this boiler-plate code, using it is very elegant. First, subclass it for your API:


<?php

require_once('JSONPAPI.class.php');

class MobshareAPI extends JSONPAPI {


}

And then, use it like so within your controller:


<?php
class userActions extends sfActions
{

	public function executeAuthenticate() {
		$valid_user = UserPeer::authenticate($this->getRequestParameter('alias'),
			$this->getRequestParameter('mobile_no'), $this->getRequestParameter('password'));
		if($valid_user instanceOf User) {
			$success = new MobshareAPI('success', $valid_user->toArray());
			return $success->render();
		} else {
			$error = new MobshareAPI('error', 'Authentication failed: Alias, mobile number or password invalid.');
			return $error->render();
		}
	}

}

Note: the rendering has been delegated to the $success and $error MobshareAPI objects. This allows for a really maintainable API. Adding functionality is much simpler since you don’t have to worry about the boilerplate.

You end up calling the API like this:


http://api.mobshare/user/authenticate?alias=vish&password=xxx&callback=handler

and you get back data which looks like this:


handler({"success":{"alias":"vish","name":"Vishnu Gopal","photo_mini": ...);

Note that callback handling is done entirely by the API and the controller needn’t worry about this parameter at all!

Mouse Wheel Handling in Prototype/Javascript

Here’s how to handle mouse wheel events in prototype, code heavily borrowed from other places on the net, but brought together and made unobtrusive:


/* Mouse Wheel */

Object.extend(Event, {
	wheel:function (event){
		var delta = 0;
		if (!event) event = window.event;
		if (event.wheelDelta) {
			delta = event.wheelDelta/120;
			if (window.opera) delta = -delta;
		} else if (event.detail) { delta = -event.detail/3;	}
		return Math.round(delta); //Safari Round
	}
});

SetupMouseWheel = {
	initialize: function() {
		Event.observe(document, 'dom:loaded', this.setup_mouse_wheel);
	},

	setup_mouse_wheel: function() {
		Event.observe($('element'), "mousewheel", function(e) { SetupMouseWheel.handleDiv(e) }, false);
		Event.observe($('element'), "DOMMouseScroll", function(e) { SetupMouseWheel.handleDiv(e) }, false); // Firefox
	},

	handleDiv: function(e) {
		direction = Event.wheel(e) < 0 ? leftSeek : rightSeek;
		console.log(direction); //handle scroll
		console.log(Event.wheel(e));
		direction(); //call leftSeek or rightSeek depending on direction.
	}
};

The problem with this code though, and the reason I haven’t found a productive use for this yet is that the browser always scrolls the viewport when it extends beyond screen dimensions. So you have your scroll handler and the browser’s scrolling viewport playing hanky-panky, and it’s not a good sight to see. For applications which always stays within the browser window though, this is useful code.

Simple Javascript Validation

I hunted around for a good Javascript class to use for validation and finally clobbered together this from various sources on the net. This should be extremely useful for anyone doing client-side validation.


Validate = {
	forMinLength: function(whatYouTyped, length_min) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		if (txt.length >= length_min) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},

	forMaxLength: function(whatYouTyped, length_max) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		if (txt.length < = length_max) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},

	forLength: function(whatYouTyped, length) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		if (txt.length == length) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},

	asEmail: function(whatYouTyped) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(txt)) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},

	asNumber: function(whatYouTyped) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		if (/^\d+$/.test(txt)) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},

	asEqualTo: function(whatYouTyped, whatYouMatchWith) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		var expect = whatYouMatchWith.value;
		if (txt == expect && txt != "") {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},

	asNotEqualTovalue: function(whatYouTyped, whatYouMatchWith) {
		var fieldset = whatYouTyped.parentNode;
		var txt = whatYouTyped.value;
		var expect = whatYouMatchWith;
		if (txt != expect) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	},

	withRegex: function(whatYouTyped, regex) {
		var fieldset = whatYouTyped.parentNode;
		txt = whatYouTyped.value;
		if (regex.test(txt)) {
			fieldset.className = "valid";
		} else {
			fieldset.className = "invalid";
		}
	}

};

How do you use it? This is how:

HTML:


<form name="signup" id="signup">
<label for="alias">Alias <sup>*</sup></label>
<fieldset>
<input type="text" name="alias" id="alias" value="" />
</fieldset>
<br />
<label for="mobile_no">Mobile Number <sup>*</sup></label>
<fieldset>
<input type="text" name="mobile_no" id="mobile_no" />
</fieldset>
<br />
[..]
</form>

CSS (different images for fieldset.valid and fieldset.invalid):


fieldset.valid {
	background:transparent url(/images/bg-fieldset-valid.gif) no-repeat 194px 0px;
}

fieldset.invalid {
	background:transparent url(/images/bg-fieldset-invalid.gif) no-repeat 194px 0px;
}

And I hate onclick and onkeyup, so here’s some unobtrusive Javascript to tie it all in:


SignupValidation = {
	initialize: function() {
		Event.observe(document, "dom:loaded", this.setup_validation)
	},

	setup_validation: function() {
		if($('signup_body')) {
			Event.observe($('alias'), 'keyup', function() {
				Validate.withRegex($('alias'), /^[A-Za-z]+[A-Za-z0-9]{4,}$/);
			});
			Event.observe($('mobile_no'), 'keyup', function() {
				Validate.withRegex($('mobile_no'), /^9[0-9]{9,9}$/)
			});
		}
	}
};

SignupValidation.initialize();

That’s it. The idea (for those who can’t bother to grok code) is that a fieldset changes its class from valid to invalid depending on the status of the field, this is then reflected in the styling.

AsktheCSSGuy was of much help.

Multiple DB connections in Rails/ActiveRecord

You might often want to connect to different databases using ActiveRecord. Here’s how you do it:


#DB definitions:

class DatabaseCurrent < ActiveRecord::Base
  self.abstract_class = true
  establish_connection settings['database']
end

class DatabaseOld < ActiveRecord::Base
  self.abstract_class = true
  establish_connection settings['database2']
end

#Model definitions (current):

class Video < DatabaseCurrent
  belongs_to :user

  set_table_name :videos
end

class Photo < DatabaseCurrent
  belongs_to :user

  set_table_name :photos
end

class User < DatabaseCurrent
  has_many :videos
  has_many :photos

  set_table_name :user
end

class Tag < DatabaseCurrent
  set_table_name :tag
end

#Model definitions (old):

class FileDB < DatabaseOld
  set_table_name :file
end

Pretty easy. The only thing to note is that you should set_table_name, otherwise, AR chokes up. Also, often you want to directly play directly with SQL. Unfortunately, ActiveRecord::Base.execute doesn’t work any longer (coz it doesn’t have a connection), but you can do it this way:


DatabaseCurrent.connection.execute("SQL")

That’s the tutorial for the day!

Deploying JAR/JAD content via WAP

An easy way to deploy JAR/JAD content on all phones with GPRS is to create a WML site and provide a link to the JAR file that you have. [As an aside, the whole J2ME scene is so fragmented that it sucks completely. Let's hope Android sorts it out.]

Here’s how you go about setting up that in Lighttpd:

Add these mimetypes (to lighttpd.conf or mime-types.conf):

mimetype.assign += (
  ".wml"     =>      "text/vnd.wap.wml",
  ".wmlc"   =>      "application/vnd.wap.wmlc",
  ".wmls"   =>      "text/vnd.wap.vmlscript",
  ".vmlsc"  =>      "application/vnd.wap.wmlscriptc",
  ".wbmp"   =>     "image/vnd.wap.wbmp",
  ".jar"      =>      "application/java-archive",
  ".jad"     =>      "text/vnd.sun.j2me.app-descriptor"
)

Add the index.wml file as a valid DirectoryIndex:

server.indexfiles    += ("index.wml")

Make this .wml file and upload it somewhere (replace JARCLIENT with your .jar file, and JADCLIENT with your .jad file):

<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">

<wml>
<card title="Download">

<p>Download the Jar file:</p>
<p>
<a href="JARCLIENT.jar">Download</a>
</p>
<p>
<a href="JADCLIENT.jad">Download (JAD)</a>
</p>

</card>
</wml>

Restart your server, and that should be it! If you’re using Apache, the steps are similar.

GOSU-N3WTON

For BarcampKerala, I made a small 2D game in Ruby and Gosu, which is a clone of a popular flash-based game by the same name.

I call it Gosu-N3wton, and I had great fun developing this game. Go check it out! (The code is opensource)

Gosu-N3wton screenshot

Hint for OpenSocial Orkut Designers

A hint for developers on the Orkut Sandbox, download its two CSS files: here and here and use those classes to style your content. It feels much more a part of orkut that way. To figure out what styles to use, firebug the orkut page.

OpenSocial and Facebook

A lot has been said about OpenSocial and Facebook, this is an attempt to say a bit more. As usual, a free-flowing list of observations:

  • Google uses Javascript as its base client-side language and extends gadgets with OpenSocial features. This I consider “good enough” but perhaps more importantly has a much lower barrier to adoption. The way a FB app is delivered (request-mapping, restricted FBML, fine-grained privilege control) feels much more professional, but at the same time a bit intimidating for newbies to the field. Libraries for almost all languages (esp. rfacebook) lower the barrier, but it’s never as simple as a .xml file which is mostly html and javascript (this is what a gadget is).
  • It’s currently not clear how OpenSocial apps communicate between each other across networks and how one app maps a user profile in one network to another. Without this, it’s mostly a single API which works across multiple containers in isolation. A single API that collates multiple containers is much more interesting.
  • Is there any set of UI guidelines? Is a gadget supposed to emulate the look and feel of its container or is it supposed to look the same across networks? Facebook provides extensive CSS classes and Javascript goodies (AJAX friend selector) to keep the experience uniform. I hope OpenSocial addresses this and soon.

Web.py on MacOSX

I’m loving web.py for developing tiny web apps. Here’s how to get it going on a Mac:


sudo port -v install python2.4 py-setuptools mysql5
#coz macports insists on adding a '5' suffix to all mysql5 tools.
sudo ln -s /opt/local/bin/mysql_config5 /opt/local/bin/mysql_config
sudo easy_install web.py cheetah markdown MySQL-python DBUtils

Not the twenty odd steps that’s on the web.py page (that even includes installing and configuring postgresql – bizarre!).

What I love about web.py is that this small bit of code:


#code.py
import web

urls = (
'/', 'index')

class index:
    def GET(self):
        print "Hello, world!"

if __name__ == "__main__": web.run(urls, globals())

… and running python code.py in a terminal gets you a working dev environment. Even camping doesn’t feel this tiny.

Edit: the performance numbers for this simple action are also very impressive:


time httperf --client=0/1 --server=localhost --port=8080 --uri=/ --send-buffer=4096 --recv-buffer=16384 --num-conns=10000 --rate=500
httperf --client=0/1 --server=localhost --port=8080 --uri=/ --rate=500 --send-buffer=4096 --recv-buffer=16384 --num-conns=10000 --num-calls=1
Maximum connect burst length: 17

Total: connections 10000 requests 10000 replies 10000 test-duration 20.000 s

Connection rate: 500.0 conn/s (2.0 ms/conn, <=24 concurrent connections)
Connection time [ms]: min 0.1 avg 1.8 max 2987.4 median 0.5 stddev 42.2
Connection time [ms]: connect 0.7
Connection length [replies/conn]: 1.000

Request rate: 500.0 req/s (2.0 ms/req)
Request size [B]: 60.0

Reply rate [replies/s]: min 499.8 avg 500.0 max 500.0 stddev 0.1 (4 samples)
Reply time [ms]: response 1.0 transfer 0.0
Reply size [B]: header 108.0 content 14.0 footer 2.0 (total 124.0)
Reply status: 1xx=0 2xx=10000 3xx=0 4xx=0 5xx=0

CPU time [s]: user 3.75 system 9.17 (user 18.7% system 45.8% total 64.6%)
Net I/O: 88.9 KB/s (0.7*10^6 bps)

Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0
httperf --client=0/1 --server=localhost --port=8080 --uri=/ --send-buffer=409  3.75s user 9.17s system 64% cpu 20.028 total

That’s 10K connections and more than 500req/s in a 20 sec timeframe. Really good.

SlideShare Twitter Mashup

So here’s a SlideShare/Twitter Mashup (command-line ruby code) that does these things:

  1. Gets buzzwords from Twitter. It does this by analyzing tweets and getting popular words (filtering out common ones) with the Twitter API.
  2. Gets the most popular buzz word and searches the SlideShare tag database with it (using the SlideShare API).
  3. Prints out the buzzword and the slideshows that’s associated with it.

The source code might not be a great example of filtering and getting popular words, but it’s a good demo of how simple the Twitter and SlideShare APIs are (REST yay) and how easy Hpricot makes parsing XML docs.

There’s a zip (with the source code, common_words.txt, twitter_words.txt) here: slideshare_twitter.zip. Enjoy. :-)

Tail Recursion

Ponder this:

fact (0) ->
	1;
fact (N) ->
	N * fact (N - 1).

versus this:

fact(N) ->
	fact_helper(N, 1).
fact_helper(1, T) ->
	T;
fact_helper(N, T) ->
	fact_helper(N - 1, T * N).

The advantage of learning Erlang (albeit very slowly, with lots of interruptions) is that it directly introduces a lot of concepts I’ve been marginally aware of before. For instance, the second example implements factorial using tail recursion. The advantage is that a compiler doesn’t have to implement a call-stack when playing with arguments. See wikipedia entry.

Reg. the facebook code leak

Surprise, surprise! Facebook code sucks as much as anything else out there. For people who don’t get the sarcasm, it’s what gets stuffed onto the browser that matters in the end. Having said that, clean aesthetics and a professional layout, a pluggable architecture, and even a PhD. paper doesn’t mean your code is of the Field-Medal caliber. Capish? (Bonus: funny comments).

Adobe AIR: Calculator

If you’ve installed the AIR SDK (and it’s dead easy, just extract and copy it somewhere), you’ve got a lightweight cross-plaform VM to program apps in. Best of all, it works with HTML and Javascript.

In about an hour, I hitched up a calculator application. It’s very easy:

Calculator.xml:

<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://ns.adobe.com/air/application/1.0.M4"
	appId="in.vish.Calculator"
	version="1.0">
	<name>Calculator</name>
	<title>Calculator Installer</title>
	<description>Simple Calculator in HTML</description>
	<copyright></copyright>
	<rootContent systemChrome="standard" transparent="false" visible="true">
		Calculator.html
	</rootContent>
</application>

Calculator.html:

<!DOCTYPE html
     PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
	<title>Calculator</title>
	<style>
		body, html { margin: 0; padding: 0; }
		body { background-color: black; color: #888; line-height: 1.5em;
			font-family: verdana, arial, sans-serif;
		}
		#wrap { margin: auto; width: 70%; }
		h1 { font-family: Tahoma, Arial, serif; font-size:2em; color: orange; }
		#sum_button { margin-top: 5px; }
	</style>
	<script>
		init = function() {
			runtime.trace("init function called");
		}
		calculate = function() {
			runtime.trace("calculate called!");
			value1 = document.getElementById('val1').value;
			value2 = document.getElementById('val2').value;
			sum_value = parseInt(value1) + parseInt(value2);
			if (isNaN(sum_value)) {
				document.getElementById('result').innerHTML = "You didn't enter valid numbers.";
			} else {
				document.getElementById('result').innerHTML = "The sum is " + sum_value + ".";
			}
		}
		resetForm = function() {
			runtime.trace("resetForm called!");
			document.getElementById('sum').reset();
		}
	</script>
</head>
<body onload="init()">
	<div id="wrap">
		<h1>Calculate a Sum!</h1>
		<form id="sum">
			<label for="val1">Value 1:
				<input type="text" name="val1" id="val1" size="4"/>
			</label>
			<label for="val2">Value 2:
				<input type="text" name="val2" id="val2" size="4"/>
			</label>
			<input type="button" value="Sum!" id="sum_button" onclick="calculate();">
			<input type="button" value="Reset" onclick="resetForm();">
			<p id="result"></p>
		</form>
	</div>
</body>
</html>

And voila, there you have it: a simple calculator:

There’s also a zip file with the code: calculator.zip and the AIR file for installation (to use that you need the AIR runtime)

JRuby/Glassfish Investigation

I’ve been keeping an eye on the JRuby scene for a while now and I recently went about benchmarking again. JRuby is interesting because it has the potential to bring robust Java deployment and tools to the Ruby world.

I made a simple widgets application as outlined here and installed JRuby/Glassfish as outlined here (both great articles at AD TechFL) and then proceeded to benchmark:

Glassfish/Jruby on httperf:

Elena:~/Applications/jruby/lib vishnu$ time httperf --client=0/1 --server=localhost --port=8080 --uri=/jruby-demo/widgets/list --send-buffer=4096 --recv-buffer=16384 --num-conns=480 --rate=60
httperf --client=0/1 --server=localhost --port=8080 --uri=/jruby-demo/widgets/list --rate=60 --send-buffer=4096 --recv-buffer=16384 --num-conns=480 --num-calls=1
httperf: warning: open file limit > FD_SETSIZE; limiting max. # of open files to FD_SETSIZE
Maximum connect burst length: 2

Total: connections 480 requests 480 replies 480 test-duration 7.999 s

Connection rate: 60.0 conn/s (16.7 ms/conn, <=7 concurrent connections)
Connection time [ms]: min 1.4 avg 24.6 max 251.3 median 15.5 stddev 35.7
Connection time [ms]: connect 0.1
Connection length [replies/conn]: 1.000

Request rate: 60.0 req/s (16.7 ms/req)
Request size [B]: 83.0

Reply rate [replies/s]: min 60.0 avg 60.0 max 60.0 stddev 0.0 (1 samples)
Reply time [ms]: response 24.5 transfer 0.0
Reply size [B]: header 360.0 content 1077.0 footer 0.0 (total 1437.0)
Reply status: 1xx=0 2xx=438 3xx=0 4xx=0 5xx=42

CPU time [s]: user 1.42 system 5.79 (user 17.7% system 72.4% total 90.1%)
Net I/O: 89.1 KB/s (0.7*10^6 bps)

Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

real    0m8.008s
user    0m1.418s
sys     0m5.799s

Glassfish/Jruby on ab:

Elena:~/Applications/jruby/lib vishnu$ ab -n400 http://localhost:8080/jruby-demo/widgets/show/1
This is ApacheBench, Version 1.3d  apache-1.3
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Finished 400 requests
Server Software:        Sun
Server Hostname:        localhost
Server Port:            8080

Document Path:          /jruby-demo/widgets/show/1
Document Length:        611 bytes

Concurrency Level:      1
Time taken for tests:   8.551 seconds
Complete requests:      400
Failed requests:        0
Broken pipe errors:     0
Total transferred:      397600 bytes
HTML transferred:       244400 bytes
Requests per second:    46.78 [#/sec] (mean)
Time per request:       21.38 [ms] (mean)
Time per request:       21.38 [ms] (mean, across all concurrent requests)
Transfer rate:          46.50 [Kbytes/sec] received

Connnection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0     0    0.0      0     0
Processing:    13    21   52.6     16  1019
Waiting:       13    21   52.6     16  1019
Total:         13    21   52.6     16  1019

Percentage of the requests served within a certain time (ms)
  50%     16
  66%     17
  75%     17
  80%     17
  90%     18
  95%     20
  98%     37
  99%    116
 100%   1019 (last request)

Let’s compare. Here’s how a single Mongrel/C does with httperf:

Elena:~/Applications/jruby/lib vishnu$ time httperf --client=0/1 --server=localhost --port=3000 --uri=/widgets/list --send-buffer=4096 --recv-buffer=16384 --num-conns=480 --rate=30
httperf --client=0/1 --server=localhost --port=3000 --uri=/widgets/list --rate=30 --send-buffer=4096 --recv-buffer=16384 --num-conns=480 --num-calls=1
httperf: warning: open file limit > FD_SETSIZE; limiting max. # of open files to FD_SETSIZE
Maximum connect burst length: 3

Total: connections 480 requests 480 replies 480 test-duration 16.009 s

Connection rate: 30.0 conn/s (33.4 ms/conn, <=18 concurrent connections)
Connection time [ms]: min 9.0 avg 56.4 max 607.6 median 9.5 stddev 99.4
Connection time [ms]: connect 0.2
Connection length [replies/conn]: 1.000

Request rate: 30.0 req/s (33.4 ms/req)
Request size [B]: 72.0

Reply rate [replies/s]: min 29.4 avg 29.8 max 30.0 stddev 0.3 (3 samples)
Reply time [ms]: response 55.7 transfer 0.5
Reply size [B]: header 278.0 content 1011.0 footer 0.0 (total 1289.0)
Reply status: 1xx=0 2xx=480 3xx=0 4xx=0 5xx=0

CPU time [s]: user 3.91 system 11.35 (user 24.4% system 70.9% total 95.3%)
Net I/O: 39.9 KB/s (0.3*10^6 bps)

Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

real    0m16.017s
user    0m3.912s
sys     0m11.354s

And Mongrel/C with ab:

Elena:~/Applications/jruby/lib vishnu$ ab -n400 http://localhost:3000/widgets/list
This is ApacheBench, Version 1.3d  apache-1.3
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Finished 400 requests
Server Software:        Mongrel
Server Hostname:        localhost
Server Port:            3000

Document Path:          /widgets/list
Document Length:        1011 bytes

Concurrency Level:      1
Time taken for tests:   7.974 seconds
Complete requests:      400
Failed requests:        0
Broken pipe errors:     0
Total transferred:      515600 bytes
HTML transferred:       404400 bytes
Requests per second:    50.16 [#/sec] (mean)
Time per request:       19.93 [ms] (mean)
Time per request:       19.93 [ms] (mean, across all concurrent requests)
Transfer rate:          64.66 [Kbytes/sec] received

Connnection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0     0    0.0      0     0
Processing:     9    19   63.4     11  1167
Waiting:        9    19   63.4     11  1167
Total:          9    19   63.4     11  1167

Percentage of the requests served within a certain time (ms)
  50%     11
  66%     12
  75%     12
  80%     12
  90%     13
  95%     15
  98%    113
  99%    149
 100%   1167 (last request)

Observations

  • Advantage: For the same number of connections, Glassfish/Jruby versus a single instance Mongrel/C has twice the reply rate in half the benchmark time.
  • Disadvantage: Request latency is more for Glassfish/Jruby. (around 5ms av. more)
  • Disadvantage: JRuby Ruby/Rails support is not perfect but very good.
  • Advantage: Much simpler to set up. Copy the war file to the server and deployment is done, autodeployment is also possible w/o restarting Glassfish.
  • Advantage: Much simpler to install and get going on the server, just requires a JDK, glassfish installed anywhere, etc.
  • Advantage: Runs on Java, so Java deployment experience will do.
  • Disadvantage: Does not support C library extensions for Ruby, question: how many of them are there & useful (memcached?) ?
  • Advantage: Supports Java libraries kind of easily, again question: how many of them are there, useful and relevant?
  • Question: This benchmark is versus a single mongrel, how does cluster vs. cluster compare?
  • Question: Is glassfish clustering easy? Observation: mongrel clustering is cumbersome at present. Maybe swiftiply will help there with dynamic addition of mongrels?

New picture

So the old mustache was getting to be a bit long in the tooth, so I have a new stainglass one. To go with it, a tightened sidebar too. What do you guys think?

Safari Web Inspector

Safari Web Inspector
I make my living as a web developer these days and a good “page inspector” tool is an absolute essential. For that reason, I switch to Firefox and the amazing Firebug tool when I write webdev code. I used a recent nightly of Webkit and was pleasantly surprised by its inbuilt Web Inspector. It lists the DOM, the CSS style computation, the box model and some bit of element properties. The interface is amazing, I love the HUD style effect, and it’s much less distracting when the element is shown as the parent in the hierarchy (see the top div in the image above) instead of it being placed in a pageful of tags.

What it still doesn’t do is display any kind of javascript information. The Webkit guys seem to have gone with a separate application called Drosera (which allows people to debug javascript, attach to any Webkit-ish app etc.) and it makes my browser hang badly when attached to it (and hence I couldn’t test and name a verdict).

So my personal feature request list for the Web Inspector would be:

  1. A Javascript console, watch/breakpoint functionality is not a must, but definitely must have a console.
  2. A Request/Response Inspector, especially those of XHR Requests.

If I get these two things I’ll switch to Webkit entirely. As it is, I love it’s Mac-iness, but like Firebug too much to let FF go.

(and yeah, what you see in the pic is the Web Inspector inspecting itself. Apparently, it’s written mostly in HTML and JS. Cool!)

LinuxAsia2007

I’m thinking of attending LinuxAsia 2007. Got myself the free Community Pass. Anybody else going to be there? The programme seems decent if a bit enterprisey.

Barcamp Delhi Two: Quicker Web Applications

This is absurdly mixing up dates, but I thought I should put up my slides for my talk at Delhi here as well. It’s about caching, and how a simple solution can enable page caching (and its speeds) to be used in a lot of situations. You’ll need the audio of my talk as well. The title is inspired from Do Androids Dream of Electric Sheep?, a brilliant novella by Philip K. Dick. :-)

Follow

Get every new post delivered to your Inbox.

Join 4,618 other followers