Browse Source

Added Rikulo stream benchmark.

Lucian Pacurar 12 years ago
parent
commit
9a3c7abfe0

+ 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