Browse Source

Added Dart Start Framework to the benchmarks using PostgreSQL and MongoDB.

Lucian Pacurar 12 years ago
parent
commit
a158bcb8d5

+ 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.17+2
+  mustache: 0.1.5
+  postgresql: 0.2.8
+  yaml: 0.6.17+2
+  mongo_dart: 0.1.27
+  crypto: 0.6.17+2

+ 350 - 0
dart-start/server.dart

@@ -0,0 +1,350 @@
+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) {
+          _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"));
+          
+          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"));
+          
+          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) {
+          print("works");
+          _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

+ 3 - 1
installer.py

@@ -43,7 +43,9 @@ class Installer:
     #
     #
     # Dart
     # Dart
     #
     #
-    self.__run_command("curl https://storage.googleapis.com/dart-editor-archive-integration/latest/dartsdk-linux-64.tar.gz | tar xvz")
+    self.__run_command("sudo add-apt-repository ppa:hachre/dart", True)
+    self.__run_command("sudo apt-get update", True)
+    self.__run_command("sudo apt-get install dartvm dartsdk", True)
 
 
     #
     #
     # Erlang
     # Erlang