Skip to content

Commit

Permalink
added cookies and random cleanup
Browse files Browse the repository at this point in the history
- improved static file callbacks
- better functional tests
- headers are now an array
- content length
- updated benchmarks
  • Loading branch information
Kellen Presley committed Dec 4, 2009
1 parent e08b3c7 commit f35bba9
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 155 deletions.
168 changes: 86 additions & 82 deletions README.textile
Expand Up @@ -75,127 +75,131 @@ h3. Benchmarks
p. The included sinatra_app_for_benchmarks is a Ruby port of the Picard sample_app. According to these benchmarks, the Picard app on node.js runs about twice as fast as the Sinatra app on Thin and can handle around 12x as many requests per second (due to the asynchronous nature of node.js). Below is a sample output of the benchmarks:

<pre>
$ cd sinatra_app_for_benchmarks
$ ruby benchmark.rb

Running Sinatra specs 10 times:

Finished in 0.083148 seconds
Finished in 0.083371 seconds
Finished in 0.112633 seconds
Finished in 0.083442 seconds
Finished in 0.083424 seconds
Finished in 0.082644 seconds
Finished in 0.083286 seconds
Finished in 0.084136 seconds
Finished in 0.113771 seconds
Finished in 0.08323 seconds
Average time was 0.0893085 seconds

Running Picard specs 10 times:

Finished in 0.040487 seconds
Finished in 0.040587 seconds
Finished in 0.041176 seconds
Finished in 0.042727 seconds
Finished in 0.04083 seconds
Finished in 0.040875 seconds
Finished in 0.040128 seconds
Finished in 0.039876 seconds
Finished in 0.040797 seconds
Finished in 0.041551 seconds
Average time was 0.0409034 seconds

Picard was 2.18 times faster!
$ cd sinatra_app_for_benchmarks
$ ruby benchmark.rb

Running Sinatra specs 10 times:

Finished in 0.083148 seconds
Finished in 0.083371 seconds
Finished in 0.112633 seconds
Finished in 0.083442 seconds
Finished in 0.083424 seconds
Finished in 0.082644 seconds
Finished in 0.083286 seconds
Finished in 0.084136 seconds
Finished in 0.113771 seconds
Finished in 0.08323 seconds
Average time was 0.0893085 seconds

Running Picard specs 10 times:

Finished in 0.040487 seconds
Finished in 0.040587 seconds
Finished in 0.041176 seconds
Finished in 0.042727 seconds
Finished in 0.04083 seconds
Finished in 0.040875 seconds
Finished in 0.040128 seconds
Finished in 0.039876 seconds
Finished in 0.040797 seconds
Finished in 0.041551 seconds
Average time was 0.0409034 seconds

Picard was 2.18 times faster!
</pre>

h4. Apache Benchmark Test for Sinatra app:

<pre>
$ ab -n 1000 -c 10 http://127.0.0.1:3000/haml

$ ab -n 5000 -c 50 http://127.0.0.1:3000/haml

...

Server Software: thin
Server Hostname: 127.0.0.1
Server Port: 3000

Document Path: /haml
Document Length: 697 bytes

Concurrency Level: 10
Time taken for tests: 10.570 seconds
Complete requests: 1000
Concurrency Level: 50
Time taken for tests: 52.174 seconds
Complete requests: 5000
Failed requests: 0
Write errors: 0
Total transferred: 835000 bytes
HTML transferred: 697000 bytes
Requests per second: 94.61 [#/sec] (mean)
Time per request: 105.696 [ms] (mean)
Time per request: 10.570 [ms] (mean, across all concurrent requests)
Transfer rate: 77.15 [Kbytes/sec] received
Total transferred: 4175000 bytes
HTML transferred: 3485000 bytes
Requests per second: 95.83 [#/sec] (mean)
Time per request: 521.740 [ms] (mean)
Time per request: 10.435 [ms] (mean, across all concurrent requests)
Transfer rate: 78.15 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.3 0 3
Processing: 11 105 19.4 96 202
Waiting: 10 99 24.0 95 178
Total: 11 106 19.4 97 202
Connect: 0 0 0.9 0 15
Processing: 189 520 33.6 517 794
Waiting: 100 418 33.2 420 701
Total: 191 520 33.6 518 794

Percentage of the requests served within a certain time (ms)
50% 97
66% 118
75% 120
80% 121
90% 125
95% 136
98% 144
99% 179
100% 202 (longest request)
50% 518
66% 521
75% 524
80% 525
90% 528
95% 532
98% 615
99% 670
100% 794 (longest request)
</pre>

h4. Apache Benchmark Test for Picard app:

<pre>
$ ab -n 1000 -c 10 http://127.0.0.1:9900/haml

Server Software:
$ ab -n 5000 -c 50 http://127.0.0.1:9900/haml

...

Server Software: Picard
Server Hostname: 127.0.0.1
Server Port: 9900

Document Path: /haml
Document Length: 547 bytes

Concurrency Level: 10
Time taken for tests: 0.880 seconds
Complete requests: 1000
Concurrency Level: 50
Time taken for tests: 5.058 seconds
Complete requests: 5000
Failed requests: 0
Write errors: 0
Total transferred: 638638 bytes
HTML transferred: 547547 bytes
Requests per second: 1136.19 [#/sec] (mean)
Time per request: 8.801 [ms] (mean)
Time per request: 0.880 [ms] (mean, across all concurrent requests)
Transfer rate: 708.60 [Kbytes/sec] received
Total transferred: 3490000 bytes
HTML transferred: 2735000 bytes
Requests per second: 988.58 [#/sec] (mean)
Time per request: 50.578 [ms] (mean)
Time per request: 1.012 [ms] (mean, across all concurrent requests)
Transfer rate: 673.85 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.4 0 3
Processing: 4 8 5.0 8 51
Waiting: 1 6 5.2 6 50
Total: 5 9 5.0 8 51
Connect: 0 2 1.5 1 10
Processing: 10 49 13.0 45 102
Waiting: 5 35 13.1 33 91
Total: 10 50 12.7 46 102

Percentage of the requests served within a certain time (ms)
50% 8
66% 8
75% 9
80% 9
90% 10
95% 11
98% 26
99% 50
100% 51 (longest request)
50% 46
66% 49
75% 51
80% 55
90% 68
95% 84
98% 91
99% 94
100% 102 (longest request)
</pre>

p. 1136.19 requests per second for Picard verses 94.61 for Sinatra!
p. 988.58 requests per second for Picard verses 95.83 for Sinatra!

p. Specs: MacBook Pro 2 x 2.53 GHz, 4 GB RAM, Ruby v1.8.7 on Thin 1.2.5, node.js v0.1.18

Expand Down
9 changes: 8 additions & 1 deletion picard/lib/picard.js
Expand Up @@ -4,10 +4,17 @@ require('./picard/mime_types')

picard.start = function() {
require('http').createServer(function(request, response) {

process.mixin(request, req_ex.get_extensions())
request.response = response

request.addListener('body', request.extract_form_params)
request.addListener('complete', request.resolve)

request.addListener('complete', function(){
request.parse_cookies()
request.resolve()
})

}).listen(picard.env.port)

require('sys').
Expand Down
93 changes: 44 additions & 49 deletions picard/lib/picard/request_extensions.js
Expand Up @@ -3,7 +3,7 @@ var posix = require('posix')
var haml = require('./haml')

var request_extensions = {

extract_form_params: function(chunk){
if( chunk == undefined ) { return }
var chunks = chunk.split('&')
Expand All @@ -27,7 +27,6 @@ var request_extensions = {
},

resolve: function(){
this.parse_cookies()
var scope = picard.routes.execute_callback(this)

if( scope == 'static' )
Expand All @@ -41,56 +40,57 @@ var request_extensions = {
serve_static: function(){
var request = this
var filename = picard.env.root + picard.env.public + this.uri.path

// non-blocking static file access
posix.cat(filename).addCallback(function(content){
request.on_screen({
body: content,
type: picard.mime.lookup_extension(filename.match(/.[^.]*$/)[0])
})
}).addErrback(function(){
request.on_screen(null)
})
},

sys.exec("[ -f " + filename + " ] && echo '1' || echo '0'").addCallback(function(stdout){
if( stdout == 1 ){
posix.cat(filename).addCallback(function(content){
request.on_screen({
body: content,
type: picard.mime.lookup_extension(filename.match(/.[^.]*$/)[0])
})
})
} else {
request.on_screen(null)
}
});
send_data: function(status, headers, body){
this.response.sendHeader(status, headers)
this.response.sendBody(body)
this.response.finish()
},

on_screen: function(scope){
if( this.response.finished ){ return }
var res = this.response

if ( scope == null )
scope = { status: 404, body: "<h1> 404 Not Found </h1>" }

var body = scope.text || scope.body || ''
var headers = scope.headers || {}
var req = this
var status = scope.status || 200
var headers = scope.headers || []
var body = scope.text || scope.body || ''

if(typeof(scope) == 'string')
body = scope

headers['content-type'] = scope.type || "text/html"
headers = this.format_headers(headers)
headers = this.set_cookies(headers)

res.sendHeader(status, headers)
headers.push([ 'Server', 'Picard v0.1 "Prime Directive"' ])
headers.push([ 'Content-Type', scope.type || 'text/html' ])
headers = req.set_cookies(headers)

if(scope.template){
var template_path = picard.env.root + picard.env.views + '/' + scope.template
haml.render(scope, template_path, function(body){
res.sendBody(body)
res.finish()
headers.push([ 'Content-Length', body.length ])
req.send_data(status, headers, body)
})
} else {
res.sendBody(body)
res.finish()
headers.push([ 'Content-Length', body.length ])
req.send_data(status, headers, body)
}

sys.puts('\n' + (this._method || this.method).toUpperCase() + ' ' + this.uri.path + ' ' + status)
sys.puts((this._method || this.method).toUpperCase() + ' ' + this.uri.path + ' ' + status)

if(picard.env.mode == 'development')
sys.puts(sys.inspect(this)) // request params logging
sys.puts(sys.inspect(this) + '\n') // request params logging
},

handle_exception: function(ex) {
Expand Down Expand Up @@ -118,48 +118,43 @@ var request_extensions = {
var ret, name, options

for (name in this.cookies) {
ret = []

ret.push(name, "=", this.cookies[name].value)

if( this.cookies[name].preset ){ continue }
options = this.cookies[name]
ret = name + '=' + options.value

if (options.expires)
ret.push("; expires=", options.expires.toUTCString())

ret += '; expires=' + options.expires.toUTCString()
if (options.path)
ret.push("; path=", options.path)

ret += '; path=' + options.path
if (options.domain)
ret.push("; domain=", options.domain)

ret += '; domain=' + options.domain
if (options.secure)
ret.push("; secure")
ret += '; secure'

headers[headers.length] = [ "Set-Cookie", ret.join("") ]
headers.push([ "Set-Cookie", ret ])
}
return headers
},

parse_cookies: function(){
var cookieHeader = this.headers["cookie"]
var cookies = {};
this.cookies = {}
var self = this
var cookieHeader = self.headers["cookie"]

if (cookieHeader){
cookieHeader.split(";").forEach(function(cookie){
var parts = cookie.split("=")
cookies[ parts[0].trim() ] = parts[1].trim()
self.cookie(parts[0].trim(), parts[1].trim(), { preset: true })
})
}
this.cookies = cookies
},

format_headers: function(headers){ // make header object an array
var ara = []
for( var h in headers ){
ara[ara.length] = [ h, headers[h] ]
redirect: function(location){
return {
status: 302,
headers: [[ 'Location', location ]],
body: '<a href="'+ location + '">' + location + '</a>'
}
return ara
}

}
Expand Down

0 comments on commit f35bba9

Please sign in to comment.