Browse Source

Merge branch 'master' of git://github.com/lucassp/FrameworkBenchmarks into lucassp-master

Conflicts:
	toolset/setup/linux/installer.py
Michael Hixson 12 years ago
parent
commit
532c7752a6

+ 1 - 0
dart-start/.gitignore

@@ -0,0 +1 @@
+packages/*

+ 61 - 0
dart-start/README.md

@@ -0,0 +1,61 @@
+# Dart Start Framework Benchmarking Test
+
+This test adds [Start](https://github.com/lvivski/start), a Sinatra inspired web development framework for Dart, to the [benchmarking test suite](../). The test is based on the Dart Benchmarking Test.
+
+## Versions
+
+* [Dart SDK version 0.6.21.3_r26639](https://launchpad.net/~hachre/+archive/dart)
+* [Dart args version 0.6.21.3](http://pub.dartlang.org/packages/args)
+* [Dart crypto version 0.6.21.3](http://pub.dartlang.org/packages/crypto)
+* [Dart mustache version 0.1.5](http://pub.dartlang.org/packages/mustache)
+* [Dart mongo_dart version 0.1.27](http://pub.dartlang.org/packages/mongo_dart)
+* [Dart postgresql version 0.2.8](http://pub.dartlang.org/packages/postgresql)
+* [Dart start version 0.0.8](http://pub.dartlang.org/packages/start)
+* [Dart yaml version 0.6.21.3](http://pub.dartlang.org/packages/yaml)
+
+## Test URLs
+
+### Common
+
+#### JSON Encoding Test
+http://localhost:8080/json
+
+#### Plaintext Test
+http://localhost:8080/plaintext
+
+
+### PostgreSQL
+
+#### Data-Store/Database Mapping Test
+http://localhost:8080/db
+
+#### Variable Query Test
+http://localhost:8080/queries?queries=2
+
+#### Fortunes Test
+http://localhost:8080/fortunes
+
+#### Data-Store/Database Update Test
+http://localhost:8080/updates
+
+#### Variable Update Test
+http://localhost:8080/updates?queries=2
+
+
+### MongoDB
+
+#### Data-Store/Database Mapping Test
+http://localhost:8080/db-mongo
+
+#### Variable Query Test
+http://localhost:8080/queries-mongo?queries=2
+
+#### Fortunes Test
+http://localhost:8080/fortunes-mongo
+
+#### Data-Store/Database Update Test
+http://localhost:8080/updates-mongo
+
+#### Variable Update Test
+http://localhost:8080/updates-mongo?queries=2
+

+ 0 - 0
dart-start/__init__.py


+ 25 - 0
dart-start/benchmark_config

@@ -0,0 +1,25 @@
+{
+  "framework": "start",
+  "tests": [{
+    "postgresql-raw": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "sort": 245
+    },
+    "mongodb-raw": {
+      "setup_file": "setup",
+      "db_url": "/db-mongo",
+      "query_url": "/queries-mongo?queries=",
+      "fortune_url": "/fortunes-mongo",
+      "update_url": "/updates-mongo?queries="
+      "port": 8080,
+      "sort": 246
+    }
+  }]
+}

+ 20 - 0
dart-start/fortunes.mustache

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Fortunes</title>
+  </head>
+  <body>
+    <table>
+      <tr>
+        <th>id</th>
+        <th>message</th>
+      </tr>
+      {{#fortunes}}
+      <tr>
+        <td>{{id}}</td>
+        <td>{{message}}</td>
+      </tr>
+      {{/fortunes}}
+    </table>
+  </body>
+</html>

+ 2 - 0
dart-start/mongodb.yaml

@@ -0,0 +1,2 @@
+host: localhost
+database: hello_world

+ 5 - 0
dart-start/postgresql.yaml

@@ -0,0 +1,5 @@
+host: localhost
+port: 5432
+user: postgres
+password: postgres
+database: hello_world

+ 10 - 0
dart-start/pubspec.yaml

@@ -0,0 +1,10 @@
+name: DartStartBenchmark
+description: A benchmark of Dart Start, a Sinatra inspired web framework
+dependencies:
+  start: 0.0.8
+  args: 0.6.21+3
+  mustache: 0.1.5
+  postgresql: 0.2.8
+  yaml: 0.6.21+3
+  mongo_dart: 0.1.27
+  crypto: 0.6.21+3

+ 358 - 0
dart-start/server.dart

@@ -0,0 +1,358 @@
+import "dart:core";
+import "dart:io";
+import 'dart:async' show Future;
+import 'dart:json' as json;
+import 'dart:math' show Random;
+import "package:start/start.dart";
+import "package:args/args.dart";
+import 'package:postgresql/postgresql.dart' as pg;
+import 'package:postgresql/postgresql_pool.dart' as pgpool;
+import 'package:yaml/yaml.dart' as yaml;
+import 'package:mustache/mustache.dart' as mustache;
+import 'package:mongo_dart/mongo_dart.dart';
+import 'package:crypto/crypto.dart';
+
+// Postgres connection pool
+var _connectionPool;
+
+// Fortunes mustache template
+var _fortunesTemplate;
+
+// MongoDB connection
+var _mongoDb;
+
+// World Collection
+var _worldCollection;
+// Fortunes Collection
+var _fortuneCollection;
+
+// World table size
+const _WORLD_TABLE_SIZE = 10000;
+// Fortune table size used only for generation of data
+const _FORTUNE_TABLE_SIZE = 100;
+
+final _RANDOM = new Random();
+
+class Fortune implements Comparable<Fortune> {
+  int id;
+  String message;
+
+  Fortune(this.id, this.message);
+
+  compareTo(Fortune other) => message.compareTo(other.message);
+}
+
+
+class World {
+  int id;
+  int randomNumber;
+
+  World(this.id, this.randomNumber);
+
+  toJson() => { "id": id, "randomNumber": randomNumber };
+}
+
+main() {
+  var parser = new ArgParser();
+  parser.addOption('address', abbr: 'a', defaultsTo: '0.0.0.0');
+  parser.addOption('port', abbr: 'p', defaultsTo: '8080');
+  parser.addOption('dbconnections', abbr: 'd', defaultsTo: '256');
+  
+  var arguments = parser.parse(new Options().arguments);
+  
+  Future.wait([
+    new File("postgresql.yaml").readAsString().then((config){
+      _connectionPool = new pgpool.Pool(
+            new pg.Settings.fromMap(yaml.loadYaml(config)).toUri(),
+            min: int.parse(arguments["dbconnections"]),
+            max: int.parse(arguments["dbconnections"])
+          );
+      return _connectionPool.start();
+    }),
+    new File("mongodb.yaml").readAsString().then((config) {
+      var mongoConfig = yaml.loadYaml(config);
+      _mongoDb = new Db("mongodb://${mongoConfig["host"]}/${mongoConfig["database"]}");
+      return _mongoDb.open().then((_) {
+        _worldCollection = _mongoDb.collection("World");
+        _fortuneCollection = _mongoDb.collection("Fortune");
+      });
+    }),
+    new File('fortunes.mustache').readAsString().then((template) {
+      _fortunesTemplate = mustache.parse(template);
+    })
+  ]).then((_) {
+    start(host: arguments["address"], public: 'web', port: int.parse(arguments["port"]))
+      .then((Server app) {
+        
+        // JSON test
+        app.get('/json').listen((request) {
+          
+          var helloWorld = {
+            "message": "Hello, World!"
+          };
+          
+          _setJsonHeaders(request.response);
+          request.response.send(json.stringify(helloWorld));
+        });
+        
+        
+        // Query test
+        app.get("/db").listen((request) {
+          
+          _setJsonHeaders(request.response);
+          
+          _query().then((data) {
+            request.response.send(json.stringify(data));
+          });
+        });
+        
+        // Queries test
+        app.get("/queries").listen((request) {
+          
+          var queries = _parseQueriesParam(request.param("queries"));
+          
+          _setJsonHeaders(request.response);
+          
+          Future.wait(
+                new List.generate(
+                  queries,
+                  (_) => _query(),
+                  growable: false
+                )
+            )
+            .then((response) => request.response.send(json.stringify(response)));
+        });
+        
+        // Fortunes test
+        app.get("/fortunes").listen((request) {
+          _setHtmlHeaders(request.response);
+          
+          _connectionPool.connect().then((connection) {
+            return connection.query('SELECT "id", "message" FROM "Fortune";')
+                .map((row) => new Fortune(row[0], row[1]))
+                .toList()
+                .whenComplete(() { connection.close(); });
+          }).then((fortunes) {
+            fortunes.add(new Fortune(0, 'Additional fortune added at request time.'));
+            fortunes.sort();
+            request.response.send(_fortunesTemplate.renderString({
+              "fortunes": fortunes.map((fortune) => {
+                "id": fortune.id,
+                "message": fortune.message
+              }).toList()
+            }));
+          });
+        });
+        
+        // Updates test
+        app.get("/updates").listen((request) {
+          
+          var queries = _parseQueriesParam(request.param("queries"));
+          
+          _setJsonHeaders(request.response);
+          
+          Future.wait(new List.generate(queries, (_) {
+            return _query()
+                .then((world) {
+                  world.randomNumber = _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1;
+                  return _connectionPool.connect()
+                      .then((connection) {
+                        return connection.execute(
+                              'UPDATE "World" SET "randomNumber" = @randomNumber WHERE "id" = @id;',
+                              { 
+                                'randomNumber': world.randomNumber,
+                                'id': world.id 
+                              }
+                            )
+                            .whenComplete(() { connection.close(); });
+                      })
+                      .then((_) => world);
+                });
+          }, growable: false))
+          .then((worlds) => request.response.send(json.stringify(worlds)));
+        });
+        
+        // Plain text test
+        app.get("/plaintext").listen((request) {
+          _setPlainHeaders(request.response);
+          request.response.send("Hello, World!");
+        });
+        
+        // Mongo World dev data generation
+        app.get("/generate-world").listen((request) {
+          
+          _worldCollection.drop()
+            .then((_) {
+              var collectionData = new List.generate(_WORLD_TABLE_SIZE, (index) {
+                return {
+                  "id": index + 1,
+                  "randomNumber": _RANDOM.nextInt(_WORLD_TABLE_SIZE)
+                };
+              });
+              return _worldCollection.insertAll(collectionData); 
+            })
+            .then((_) {
+              request.response.send("Generated");
+            });
+        });
+        
+        // Mongo Fortune dev data generation
+        app.get("/generate-fortune").listen((request) {
+          _fortuneCollection.drop()
+            .then((_) {
+              var collectionData = new List.generate(_FORTUNE_TABLE_SIZE, (index) {
+                var hash = new MD5();
+                hash.add(_RANDOM.nextInt(_FORTUNE_TABLE_SIZE).toString().codeUnits);
+                return {
+                  "id": index + 1,
+                  "message": CryptoUtils.bytesToHex(hash.close())
+                };
+              });
+              return _fortuneCollection.insertAll(collectionData); 
+            })
+            .then((_) {
+              request.response.send("Generated");
+            });
+          
+        });
+        
+        // Mongo query test
+        app.get("/db-mongo").listen((request) {
+
+          _setJsonHeaders(request.response);
+
+          _mongoQuery().then((data) {
+            request.response.json({
+              "id": data["id"],
+              "randomNumber": data["randomNumber"]
+            });
+          });
+        });
+        
+        // Mongo queries test
+        app.get("/queries-mongo").listen((request) {
+          
+          var queries = _parseQueriesParam(request.param("queries"));
+
+          _setJsonHeaders(request.response);
+          
+          Future.wait(
+              new List.generate(
+                  queries,
+                  (_) => _mongoQuery(),
+                  growable: false
+                )
+            )
+            .then((response) {
+              var results = response.map((world) {
+                return {
+                  "id": world["id"],
+                  "randomNumber": world["randomNumber"]
+                };
+              });
+              request.response.send(json.stringify(results.toList()));
+            });
+        });
+        
+        // Mongo updates test
+        app.get("/updates-mongo").listen((request) {
+          var queries = _parseQueriesParam(request.param("queries"));
+
+          _setJsonHeaders(request.response);
+
+          Future.wait(new List.generate(queries, (index) {
+            return _mongoQuery()
+                .then((world) {
+                  world["randomNumber"] = _RANDOM.nextInt(_WORLD_TABLE_SIZE);
+                  return _worldCollection.update( { "_id": world["_id"] }, world)
+                      .then((_) => world);
+                });
+          }, growable: false))
+          .then((worlds) {
+            var result = worlds.map((world) {
+              return {
+                "id": world["id"],
+                "randomNumber": world["randomNumber"]
+              };
+            });
+            request.response.send(json.stringify(result.toList()));
+          });
+        });
+        
+        
+        // Mongo fortunes test
+        app.get("/fortunes-mongo").listen((request) {
+          
+          _setHtmlHeaders(request.response);
+          
+          _fortuneCollection.find().toList().then((fortunes) {
+            fortunes = fortunes.map((fortune) {
+              return new Fortune(fortune["id"], fortune["message"]);
+            }).toList();
+            fortunes.add(new Fortune(0, 'Additional fortune added at request time.'));
+            fortunes.sort();
+            request.response.send(_fortunesTemplate.renderString({
+              "fortunes": fortunes.map((fortune) => {
+                "id": fortune.id,
+                "message": fortune.message
+              }).toList()
+            }));
+          });
+        });
+        
+      });
+  });
+}
+
+// set JSON headers
+_setJsonHeaders(response) {
+  _setHeaders(response);
+  response
+    .header(HttpHeaders.CONTENT_TYPE, 'application/json; charset=UTF-8');   
+}
+
+// set plain text headers
+_setPlainHeaders(response) {
+  _setHeaders(response);
+  response
+    .header(HttpHeaders.CONTENT_TYPE, 'text/plain; charset=UTF-8');
+}
+
+// set HTML headers
+_setHtmlHeaders(response) {
+  _setHeaders(response);
+  response
+    .header(HttpHeaders.CONTENT_TYPE, 'text/html; charset=UTF-8');
+}
+
+// set common headers
+_setHeaders(response) {
+  // disable gzip encoding
+  response.header(HttpHeaders.CONTENT_ENCODING, "")
+    ..header(HttpHeaders.DATE, new DateTime.now());
+}
+
+// parse queries param
+_parseQueriesParam(param) {
+  return param.isEmpty ? 1 : int.parse(param, radix: 10, onError: (_) => 1).clamp(1, 500);
+}
+
+// runs a query and returns a promise
+_query() {
+  return _connectionPool.connect().then((connection) {
+    return connection
+      .query('SELECT "id", "randomNumber" FROM "World" WHERE id = @id;', { 'id': _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1 })
+      .single
+      .then((row) =>new World(row[0], row[1]))
+      .whenComplete(() {
+        connection.close();
+      });
+  });
+}
+
+// runs a mongo query and returns a promise
+_mongoQuery() {
+  return _worldCollection.findOne({
+    "id": _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1
+  });
+}

+ 75 - 0
dart-start/setup.py

@@ -0,0 +1,75 @@
+import subprocess
+import sys
+import setup_util
+import os
+
+def start(args):
+  setup_util.replace_text('dart-start/postgresql.yaml', 'host: .*', 'host: ' + args.database_host)
+  setup_util.replace_text('dart-start/mongodb.yaml', 'host: .*', 'host: ' + args.database_host)
+  try:
+    #
+    # install dart dependencies
+    #
+    subprocess.check_call('pub install', shell=True, cwd='dart-start')
+    #
+    # start dart servers
+    #
+    for port in range(9001, 9001 + args.max_threads):
+      subprocess.Popen('dart server.dart -a 127.0.0.1 -p ' + str(port) + ' -d ' + str(args.max_concurrency / args.max_threads), shell=True, cwd='dart-start')
+    #
+    # create nginx configuration
+    #
+    conf = []
+    conf.append('worker_processes ' + str(args.max_threads) + ';')
+    conf.append('error_log /dev/null crit;')
+    conf.append('events {')
+    conf.append('    worker_connections 1024;')
+    conf.append('}')
+    conf.append('http {')
+    conf.append('    access_log off;')
+    conf.append('    include /usr/local/nginx/conf/mime.types;')
+    conf.append('    default_type application/octet-stream;')
+    conf.append('    sendfile on;')
+    conf.append('    upstream dart_cluster {')
+    for port in range(9001, 9001 + args.max_threads):
+      conf.append('        server 127.0.0.1:' + str(port) + ';')
+    conf.append('        keepalive ' + str(args.max_concurrency / args.max_threads) + ';')
+    conf.append('    }')
+    conf.append('    server {')
+    conf.append('        listen 8080;')
+    conf.append('        location / {')
+    conf.append('            proxy_pass http://dart_cluster;')
+    conf.append('            proxy_http_version 1.1;')
+    conf.append('            proxy_set_header Connection "";')
+    conf.append('        }')
+    conf.append('    }')
+    conf.append('}')
+    #
+    # write nginx configuration to disk
+    #
+    with open('dart-start/nginx.conf', 'w') as f:
+      f.write('\n'.join(conf))
+    #
+    # start nginx
+    #
+    subprocess.Popen('sudo /usr/sbin/nginx -c `pwd`/nginx.conf', shell=True, cwd='dart-start');
+    return 0
+  except subprocess.CalledProcessError:
+    return 1
+
+def stop():
+  #
+  # stop nginx
+  #
+  subprocess.check_call('sudo /usr/sbin/nginx -c `pwd`/nginx.conf -s stop', shell=True, cwd='dart-start')
+  os.remove('dart-start/nginx.conf')
+  #
+  # stop dart servers
+  #
+  p = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
+  out, err = p.communicate()
+  for line in out.splitlines():
+    if 'dart' in line and 'run-tests' not in line:
+      pid = int(line.split(None, 2)[1])
+      os.kill(pid, 9)
+  return 0

+ 1 - 0
dart-stream/.gitignore

@@ -0,0 +1 @@
+packages/*

+ 60 - 0
dart-stream/README.md

@@ -0,0 +1,60 @@
+# Dart Start Framework Benchmarking Test
+
+This test adds [Stream](https://github.com/rikulo/stream), a lightweight Dart web server, to the [benchmarking test suite](../). The test is based on the Dart Benchmarking Test.
+
+## Versions
+
+* [Dart SDK version 0.6.21.3_r26639](https://launchpad.net/~hachre/+archive/dart)
+* [Dart args version 0.6.21.3](http://pub.dartlang.org/packages/args)
+* [Dart crypto version 0.6.21.3](http://pub.dartlang.org/packages/crypto)
+* [Dart mongo_dart version 0.1.27](http://pub.dartlang.org/packages/mongo_dart)
+* [Dart postgresql version 0.2.8](http://pub.dartlang.org/packages/postgresql)
+* [Dart stream version 0.8.3.3](http://pub.dartlang.org/packages/start)
+* [Dart yaml version 0.6.21.3](http://pub.dartlang.org/packages/yaml)
+
+## Test URLs
+
+### Common
+
+#### JSON Encoding Test
+http://localhost:8080/json
+
+#### Plaintext Test
+http://localhost:8080/plaintext
+
+
+### PostgreSQL
+
+#### Data-Store/Database Mapping Test
+http://localhost:8080/db
+
+#### Variable Query Test
+http://localhost:8080/queries?queries=2
+
+#### Fortunes Test
+http://localhost:8080/fortunes
+
+#### Data-Store/Database Update Test
+http://localhost:8080/updates
+
+#### Variable Update Test
+http://localhost:8080/updates?queries=2
+
+
+### MongoDB
+
+#### Data-Store/Database Mapping Test
+http://localhost:8080/db-mongo
+
+#### Variable Query Test
+http://localhost:8080/queries-mongo?queries=2
+
+#### Fortunes Test
+http://localhost:8080/fortunes-mongo
+
+#### Data-Store/Database Update Test
+http://localhost:8080/updates-mongo
+
+#### Variable Update Test
+http://localhost:8080/updates-mongo?queries=2
+

+ 0 - 0
dart-stream/__init__.py


+ 25 - 0
dart-stream/benchmark_config

@@ -0,0 +1,25 @@
+{
+  "framework": "start",
+  "tests": [{
+    "postgresql-raw": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      // "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "sort": 245
+    },
+    "mongodb-raw": {
+      "setup_file": "setup",
+      "db_url": "/db-mongo",
+      "query_url": "/queries-mongo?queries=",
+      // "fortune_url": "/fortunes-mongo",
+      "update_url": "/updates-mongo?queries="
+      "port": 8080,
+      "sort": 246
+    }
+  }]
+}

+ 50 - 0
dart-stream/fortunesView.rsp.dart

@@ -0,0 +1,50 @@
+//Auto-generated by RSP Compiler
+//Source: fortunesView.rsp.html
+part of stream_benchmark;
+
+/** Template, fortunesView, for rendering the view. */
+Future fortunesView(HttpConnect connect, {List fortunes}) { //#2
+  var _t0_, _cs_ = new List<HttpConnect>();
+  HttpRequest request = connect.request;
+  HttpResponse response = connect.response;
+//  Rsp.init(connect, "text/html; charset=utf-8");
+
+  response.write("""<!DOCTYPE html>
+<html>
+  <head>
+    <title>Fortunes</title>
+  </head>
+  <body>
+    <table>
+      <tr>
+        <th>id</th>
+        <th>message</th>
+      </tr>
+"""); //#2
+
+  for (var fortune in fortunes) { //for#13
+
+    response.write("""      <tr>
+        <td>"""); //#14
+
+    response.write(Rsp.nnx(fortune.id)); //#15
+
+
+    response.write("""</td>
+        <td>"""); //#15
+
+    response.write(Rsp.nnx(fortune.message)); //#16
+
+
+    response.write("""</td>
+      </tr>
+"""); //#16
+  } //for
+
+  response.write("""    </table>
+  </body>
+</html>
+"""); //#19
+
+  return Rsp.nnf();
+}

+ 21 - 0
dart-stream/fortunesView.rsp.html

@@ -0,0 +1,21 @@
+[:page partOf="stream_benchmark" args="List fortunes"]
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Fortunes</title>
+  </head>
+  <body>
+    <table>
+      <tr>
+        <th>id</th>
+        <th>message</th>
+      </tr>
+      [:for fortune in fortunes]
+      <tr>
+        <td>[=fortune.id]</td>
+        <td>[=fortune.message]</td>
+      </tr>
+      [/for]
+    </table>
+  </body>
+</html>

+ 2 - 0
dart-stream/mongodb.yaml

@@ -0,0 +1,2 @@
+host: 127.0.0.1
+database: hello_world

+ 5 - 0
dart-stream/postgresql.yaml

@@ -0,0 +1,5 @@
+host: localhost
+port: 5432
+user: postgres
+password: postgres
+database: hello_world

+ 9 - 0
dart-stream/pubspec.yaml

@@ -0,0 +1,9 @@
+name: DartStreamBenchmark
+description: A benchmark for Stream, a lightweight Dart web server
+dependencies:
+  args: 0.6.21+3
+  crypto: 0.6.21+3
+  mongo_dart: 0.1.27
+  postgresql: 0.2.8
+  stream: 0.8.3+3
+  yaml: 0.6.21+3

+ 305 - 0
dart-stream/server.dart

@@ -0,0 +1,305 @@
+library stream_benchmark;
+
+import "dart:core";
+import "dart:io";
+import 'dart:async' show Future;
+import 'dart:json' as json;
+import 'dart:math' show Random;
+import "package:stream/stream.dart";
+import "package:args/args.dart";
+import 'package:postgresql/postgresql.dart' as pg;
+import 'package:postgresql/postgresql_pool.dart' as pgpool;
+import 'package:yaml/yaml.dart' as yaml;
+import 'package:mongo_dart/mongo_dart.dart';
+
+part "fortunesView.rsp.dart";
+
+// URI mapping to request handlers
+var _uriMapping = {
+  "/db": _dbTest,
+  "/json": _jsonTest,
+  "/queries": _queriesTest,
+  "/updates": _updatesTest,
+  "/fortunes": _fortunesTest,
+  "/plaintext": _plaintextTest,
+  "/db-mongo": _dbMongoTest,
+  "/queries-mongo": _queriesMongoTest,
+  "/updates-mongo": _updatesMongoTest,
+  "/fortunes-mongo": _fortunesMongoTest
+};
+
+// filter map to set the headers for the request
+var _filterMapping = {
+  "/db": _jsonHeadersFilter,
+  "/json": _jsonHeadersFilter,
+  "/queries": _jsonHeadersFilter,
+  "/updates": _jsonHeadersFilter,
+  "/fortunes": _htmlHeadersFilter,
+  "/plaintext": _plainHeadersFilter,
+  "/db-mongo": _jsonHeadersFilter,
+  "/queries-mongo": _jsonHeadersFilter,
+  "/updates-mongo": _jsonHeadersFilter,
+  "/fortunes-mongo": _htmlHeadersFilter
+};
+
+// Postgres connection pool
+var _connectionPool;
+
+// Fortunes mustache template
+var _fortunesTemplate;
+
+// MongoDB connection
+var _mongoDb;
+
+// World Collection
+var _worldCollection;
+
+// Fortunes Collection
+var _fortuneCollection;
+
+// World table size
+const _WORLD_TABLE_SIZE = 10000;
+// Fortune table size used only for generation of data
+const _FORTUNE_TABLE_SIZE = 100;
+
+final _RANDOM = new Random();
+
+class Fortune implements Comparable<Fortune> {
+  int id;
+
+  String message;
+
+  Fortune(this.id, this.message);
+
+  compareTo(Fortune other) => message.compareTo(other.message);
+}
+
+class World {
+  int id;
+  int randomNumber;
+
+  World(this.id, this.randomNumber);
+
+  toJson() => { "id": id, "randomNumber": randomNumber };
+}
+
+main() {
+  var parser = new ArgParser();
+  parser.addOption('address', abbr: 'a', defaultsTo: '0.0.0.0');
+  parser.addOption('port', abbr: 'p', defaultsTo: '8080');
+  parser.addOption('dbconnections', abbr: 'd', defaultsTo: '256');
+  
+  var arguments = parser.parse(new Options().arguments);
+  Future.wait([
+     new File("postgresql.yaml").readAsString().then((config){
+       _connectionPool = new pgpool.Pool(
+           new pg.Settings.fromMap(yaml.loadYaml(config)).toUri(),
+           min: int.parse(arguments["dbconnections"]),
+           max: int.parse(arguments["dbconnections"])
+       );
+       return _connectionPool.start();
+     }),
+     new File("mongodb.yaml").readAsString().then((config) {
+       var mongoConfig = yaml.loadYaml(config);
+       _mongoDb = new Db("mongodb://${mongoConfig["host"]}/${mongoConfig["database"]}");
+       return _mongoDb.open().then((_) {
+         _worldCollection = _mongoDb.collection("World");
+         _fortuneCollection = _mongoDb.collection("Fortune");
+       });
+     })
+   ]).then((_) {
+     new StreamServer(uriMapping: _uriMapping, filterMapping: _filterMapping).start(address: arguments["address"], port: int.parse(arguments["port"]));
+   });
+}
+
+_jsonTest(HttpConnect connect) {
+  var helloWorld = {
+      "message": "Hello, World!"
+  };
+  
+  connect.response.write(json.stringify(helloWorld));
+}
+
+_dbTest(HttpConnect connect) {
+  
+  return _query().then((data) {
+    connect.response.write(json.stringify(data));
+  });
+}
+
+_queriesTest(HttpConnect connect) {
+  var queries = _parseQueriesParam(connect.request.uri.queryParameters["queries"]);
+  
+  return Future.wait(
+        new List.generate(
+            queries,
+            (_) => _query(),
+            growable: false
+        )
+    )
+    .then((response) => connect.response.write(json.stringify(response)));
+}
+
+_updatesTest(HttpConnect connect) {
+  var queries = _parseQueriesParam(connect.request.uri.queryParameters["queries"]);
+  
+  return Future.wait(new List.generate(queries, (_) {
+      return _query()
+          .then((world) {
+            world.randomNumber = _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1;
+            return _connectionPool.connect()
+              .then((connection) {
+                return connection.execute(
+                      'UPDATE "World" SET "randomNumber" = @randomNumber WHERE "id" = @id;',
+                      { 
+                        'randomNumber': world.randomNumber,
+                        'id': world.id 
+                      }
+                  )
+                  .whenComplete(() { connection.close(); });
+                })
+                .then((_) => world);
+          });
+    }, growable: false))
+    .then((worlds) => connect.response.write(json.stringify(worlds)));
+}
+
+_fortunesTest(HttpConnect connect) {
+  
+  return _connectionPool.connect().then((connection) {
+    return connection.query('SELECT "id", "message" FROM "Fortune";')
+        .map((row) => new Fortune(row[0], row[1]))
+          .toList()
+            .whenComplete(() { connection.close(); });
+  }).then((fortunes) {
+    fortunes.add(new Fortune(0, 'Additional fortune added at request time.'));
+    fortunes.sort();
+    return fortunesView(connect, fortunes: fortunes);
+  });
+}
+
+_plaintextTest(HttpConnect connect) {
+ 
+  connect.response.write("Hello, World!");
+}
+
+_dbMongoTest(HttpConnect connect) {
+  
+  return _mongoQuery().then((data) {
+    connect.response.write(json.stringify({
+      "id": data["id"],
+      "randomNumber": data["randomNumber"]
+    }));
+  });
+}
+
+_queriesMongoTest(HttpConnect connect) {
+  var queries = _parseQueriesParam(connect.request.uri.queryParameters["queries"]);
+  
+  return Future.wait(
+        new List.generate(
+            queries,
+            (_) => _mongoQuery(),
+            growable: false
+        )
+    )
+    .then((response) {
+      var results = response.map((world) {
+        return {
+          "id": world["id"],
+          "randomNumber": world["randomNumber"]
+        };
+      });
+      connect.response.write(json.stringify(results.toList()));
+    });
+}
+
+_updatesMongoTest(HttpConnect connect) {
+  var queries = _parseQueriesParam(connect.request.uri.queryParameters["queries"]);
+  
+  return Future.wait(new List.generate(queries, (index) {
+      return _mongoQuery()
+          .then((world) {
+            world["randomNumber"] = _RANDOM.nextInt(_WORLD_TABLE_SIZE);
+            return _worldCollection.update( { "_id": world["_id"] }, world)
+                .then((_) => world);
+          });
+    }, growable: false))
+    .then((worlds) {
+      var result = worlds.map((world) {
+        return {
+          "id": world["id"],
+          "randomNumber": world["randomNumber"]
+        };
+      });
+      connect.response.write(json.stringify(result.toList()));
+    });
+}
+
+_fortunesMongoTest(HttpConnect connect) {
+  
+  return _fortuneCollection.find().toList().then((fortunes) {
+    fortunes = fortunes.map((fortune) {
+      return new Fortune(fortune["id"], fortune["message"]);
+    }).toList();
+    fortunes.add(new Fortune(0, 'Additional fortune added at request time.'));
+    fortunes.sort();
+    return fortunesView(connect, fortunes: fortunes);
+  });
+}
+
+// set common headers
+_setHeaders(HttpResponse response) {
+  // disable gzip encoding
+  response.headers.set(HttpHeaders.CONTENT_ENCODING, "");
+  response.headers.set(HttpHeaders.DATE, new DateTime.now());
+}
+
+// set JSON headers for matching requests
+_jsonHeadersFilter(HttpConnect connect, Future chain(HttpConnect conn)) {
+  _setHeaders(connect.response);
+  connect.response.headers.set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8");
+  
+  return chain(connect);
+}
+
+// set plain text headers for matching requests
+_plainHeadersFilter(HttpConnect connect, Future chain(HttpConnect conn)) {
+  _setHeaders(connect.response);
+  connect.response.headers.set(HttpHeaders.CONTENT_TYPE, "text/plain; charset=UTF-8");
+  
+  return chain(connect);
+}
+
+// set HTML headers for matching requests
+_htmlHeadersFilter(HttpConnect connect, Future chain(HttpConnect conn)) {
+  _setHeaders(connect.response);
+  connect.response.headers.set(HttpHeaders.CONTENT_TYPE, "text/html; charset=UTF-8");
+  
+  return chain(connect);
+}
+
+// parse queries param
+_parseQueriesParam(param) {
+  return param.isEmpty ? 1 : int.parse(param, radix: 10, onError: (_) => 1).clamp(1, 500);
+}
+
+// runs a query and returns a promise
+_query() {
+  return _connectionPool.connect().then((connection) {
+    return connection
+      .query('SELECT "id", "randomNumber" FROM "World" WHERE id = @id;', { 'id': _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1 })
+      .single
+      .then((row) =>new World(row[0], row[1]))
+      .whenComplete(() {
+        connection.close();
+      });
+  });
+}
+
+// runs a mongo query and returns a promise
+_mongoQuery() {
+  return _worldCollection.findOne({
+    "id": _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1
+  });
+}

+ 75 - 0
dart-stream/setup.py

@@ -0,0 +1,75 @@
+import subprocess
+import sys
+import setup_util
+import os
+
+def start(args):
+  setup_util.replace_text('dart-stream/postgresql.yaml', 'host: .*', 'host: ' + args.database_host)
+  setup_util.replace_text('dart-stream/mongodb.yaml', 'host: .*', 'host: ' + args.database_host)
+  try:
+    #
+    # install dart dependencies
+    #
+    subprocess.check_call('pub install', shell=True, cwd='dart-stream')
+    #
+    # start dart servers
+    #
+    for port in range(9001, 9001 + args.max_threads):
+      subprocess.Popen('dart server.dart -a 127.0.0.1 -p ' + str(port) + ' -d ' + str(args.max_concurrency / args.max_threads), shell=True, cwd='dart-stream')
+    #
+    # create nginx configuration
+    #
+    conf = []
+    conf.append('worker_processes ' + str(args.max_threads) + ';')
+    conf.append('error_log /dev/null crit;')
+    conf.append('events {')
+    conf.append('    worker_connections 1024;')
+    conf.append('}')
+    conf.append('http {')
+    conf.append('    access_log off;')
+    conf.append('    include /usr/local/nginx/conf/mime.types;')
+    conf.append('    default_type application/octet-stream;')
+    conf.append('    sendfile on;')
+    conf.append('    upstream dart_cluster {')
+    for port in range(9001, 9001 + args.max_threads):
+      conf.append('        server 127.0.0.1:' + str(port) + ';')
+    conf.append('        keepalive ' + str(args.max_concurrency / args.max_threads) + ';')
+    conf.append('    }')
+    conf.append('    server {')
+    conf.append('        listen 8080;')
+    conf.append('        location / {')
+    conf.append('            proxy_pass http://dart_cluster;')
+    conf.append('            proxy_http_version 1.1;')
+    conf.append('            proxy_set_header Connection "";')
+    conf.append('        }')
+    conf.append('    }')
+    conf.append('}')
+    #
+    # write nginx configuration to disk
+    #
+    with open('dart-stream/nginx.conf', 'w') as f:
+      f.write('\n'.join(conf))
+    #
+    # start nginx
+    #
+    subprocess.Popen('sudo /usr/sbin/nginx -c `pwd`/nginx.conf', shell=True, cwd='dart-stream');
+    return 0
+  except subprocess.CalledProcessError:
+    return 1
+
+def stop():
+  #
+  # stop nginx
+  #
+  subprocess.check_call('sudo /usr/sbin/nginx -c `pwd`/nginx.conf -s stop', shell=True, cwd='dart-stream')
+  os.remove('dart-stream/nginx.conf')
+  #
+  # stop dart servers
+  #
+  p = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
+  out, err = p.communicate()
+  for line in out.splitlines():
+    if 'dart' in line and 'run-tests' not in line:
+      pid = int(line.split(None, 2)[1])
+      os.kill(pid, 9)
+  return 0