Browse Source

Merge branch 'master' of gitlab.techempower.com:techempower/frameworkbenchmarks

Patrick Falls 12 years ago
parent
commit
b8d10af7f1

+ 7 - 2
dart/README.md

@@ -4,10 +4,11 @@ This is the dart portion of a [benchmarking test suite](../) comparing a variety
 
 ## Versions
 
-* [Dart SDK version 0.5.11.1_r23200](http://www.dartlang.org/)
+* [Dart SDK version 0.5.13.1_r23552](http://www.dartlang.org/)
 * [Dart args version 0.5.9](http://pub.dartlang.org/packages/args)
+* [Dart crypto version 0.5.13](http://pub.dartlang.org/packages/crypto)
 * [Dart mustache version 0.1.5](http://pub.dartlang.org/packages/mustache)
-* [Dart postgresql version 0.2.6](http://pub.dartlang.org/packages/postgresql)
+* [Dart postgresql version 0.2.7](http://pub.dartlang.org/packages/postgresql)
 * [Dart yaml version 0.5.7](http://pub.dartlang.org/packages/yaml)
 
 ## Test URLs
@@ -35,3 +36,7 @@ http://localhost:8080/update
 ### Variable Update Test
 
 http://localhost:8080/update?queries=2
+
+### Plaintext Test
+
+http://localhost:8080/plaintext

+ 2 - 1
dart/pubspec.yaml

@@ -2,6 +2,7 @@ name: dartbenchmark
 description: A benchmark of dart
 dependencies:
   args: 0.5.9
+  crypto: 0.5.13
   mustache: 0.1.5
-  postgresql: 0.2.6
+  postgresql: 0.2.7
   yaml: 0.5.7

+ 124 - 144
dart/server.dart

@@ -2,34 +2,29 @@ import 'dart:async' show Future;
 import 'dart:io';
 import 'dart:json' as json;
 import 'dart:math' show Random;
-import 'dart:utf' as utf;
-import 'package:args/args.dart' show ArgParser, ArgResults;
+import 'package:args/args.dart' show ArgParser;
 import 'package:mustache/mustache.dart' as mustache;
 import 'package:postgresql/postgresql.dart' as pg;
 import 'package:postgresql/postgresql_pool.dart' as pgpool;
 import 'package:yaml/yaml.dart' as yaml;
 
-/**
- * Starts a new HTTP server that implements the tests to be benchmarked.  The
- * address and port for incoming connections is configurable via command line
- * arguments, as is the number of database connections to be maintained in the
- * connection pool.
- */
+/// Starts a new HTTP server that implements the tests to be benchmarked.  The
+/// address and port for incoming connections is configurable via command line
+/// arguments, as is the number of database connections to be maintained in the
+/// connection pool.
 main() {
-  ArgParser parser = new ArgParser();
+  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');
-  ArgResults arguments = parser.parse(new Options().arguments);
+  var arguments = parser.parse(new Options().arguments);
   _startServer(
       arguments['address'],
       int.parse(arguments['port']),
       int.parse(arguments['dbconnections']));
 }
 
-/**
- * The entity used in the database query and update tests.
- */
+/// The entity used in the database query and update tests.
 class World {
   int id;
   int randomNumber;
@@ -39,221 +34,206 @@ class World {
   toJson() => { 'id': id, 'randomNumber': randomNumber };
 }
 
-/**
- * The entity used in the fortunes test.
- */
+/// The entity used in the fortunes test.
 class Fortune implements Comparable<Fortune> {
   int id;
   String message;
 
   Fortune(this.id, this.message);
 
-  int compareTo(Fortune other) => message.compareTo(other.message);
+  compareTo(Fortune other) => message.compareTo(other.message);
 }
 
-/**
- * The number of rows in the world entity table.
- */
+/// The number of rows in the world entity table.
 const _WORLD_TABLE_SIZE = 10000;
 
-/**
- * A random number generator.
- */
+/// A random number generator.
 final _RANDOM = new Random();
 
-/**
- * The 'text/html; charset=utf-8' content type.
- */
+/// The 'text/html; charset=utf-8' content type.
 final _TYPE_HTML = new ContentType('text', 'html', charset: 'utf-8');
 
-/**
- * The 'application/json; charset=utf-8' content type.
- */
-final _TYPE_JSON = new ContentType('application', 'json', charset: 'utf-8');
+/// The 'application/json' content type.
+final _TYPE_JSON = new ContentType('application', 'json');
 
-/**
- * The PostgreSQL connection pool used by all the tests that require database
- * connectivity.
- */
-pgpool.Pool _connectionPool;
+/// The 'text/html; charset=utf-8' content type.
+final _TYPE_TEXT = new ContentType('text', 'plain', charset: 'utf-8');
 
-/**
- * The mustache template which is rendered in the fortunes test.
- */
-mustache.Template _fortunesTemplate;
+/// The PostgreSQL connection pool used by all the tests that require database
+/// connectivity.
+var _connectionPool;
 
-/**
- * Starts a benchmark server, which listens for connections from
- * '[address] : [port]' and maintains [dbConnections] connections to the
- * database.
- */
-void _startServer(String address, int port, int dbConnections) {
+/// The mustache template which is rendered in the fortunes test.
+var _fortunesTemplate;
+
+/// Starts a benchmark server, which listens for connections from
+/// '[address] : [port]' and maintains [dbConnections] connections to the
+/// database.
+_startServer(address, port, dbConnections) {
   Future.wait([
-    new File('postgresql.yaml').readAsString().then((String config) {
+    new File('postgresql.yaml').readAsString().then((config) {
       _connectionPool = new pgpool.Pool(
           new pg.Settings.fromMap(yaml.loadYaml(config)).toUri(),
           min: dbConnections,
           max: dbConnections);
       return _connectionPool.start();
     }),
-    new File('fortunes.mustache').readAsString().then((String template) {
+    new File('fortunes.mustache').readAsString().then((template) {
       _fortunesTemplate = mustache.parse(template);
     })
   ]).then((_) {
-    HttpServer.bind(address, port).then((HttpServer server) {
-      server.listen((HttpRequest request) {
+    HttpServer.bind(address, port).then((server) {
+      server.listen((request) {
         switch (request.uri.path) {
-          case '/':         return _jsonTest(request);
-          case '/db':       return _dbTest(request);
-          case '/fortunes': return _fortunesTest(request);
-          case '/update':   return _updateTest(request);
-          default:          return _sendResponse(request, HttpStatus.NOT_FOUND);
+          case '/':
+            _jsonTest(request);
+            break;
+          case '/db':
+            _dbTest(request);
+            break;
+          case '/fortunes':
+            _fortunesTest(request);
+            break;
+          case '/update':
+            _updateTest(request);
+            break;
+          case '/plaintext':
+            _plaintextTest(request);
+            break;
+          default:
+            _sendResponse(request, HttpStatus.NOT_FOUND);
+            break;
         }
       });
     });
   });
 }
 
-/**
- * Returns the given [text] parsed as a base 10 integer.  If the text is null
- * or is an otherwise invalid representation of a base 10 integer, zero is
- * returned.
- */
-int _parseInt(String text) =>
+/// Returns the given [text] parsed as a base 10 integer.  If the text is null
+/// or is an otherwise invalid representation of a base 10 integer, zero is
+/// returned.
+_parseInt(text) =>
     (text == null) ? 0 : int.parse(text, radix: 10, onError: ((_) => 0));
 
-/**
- * Completes the given [request] by writing the [response] with the given
- * [statusCode] and [type].
- */
-void _sendResponse(HttpRequest request, int statusCode,
-                   [ ContentType type, String response ]) {
+/// Completes the given [request] by writing the [response] with the given
+/// [statusCode] and [type].
+_sendResponse(request, statusCode, [ type, response ]) {
   request.response.statusCode = statusCode;
-  request.response.headers.add(
-      HttpHeaders.CONNECTION,
-      (request.persistentConnection) ? 'keep-alive' : 'close');
   request.response.headers.add(HttpHeaders.SERVER, 'dart');
   request.response.headers.date = new DateTime.now();
+  //
+  // Prevent GZIP encoding, because it is disallowed in the rules for these
+  // benchmark tests.
+  //
+  request.response.headers.add(HttpHeaders.CONTENT_ENCODING, '');
   if (type != null) {
     request.response.headers.contentType = type;
   }
   if (response != null) {
-    //
-    // A simpler way to write a response would be to:
-    //
-    //   1. Not set the contentLength header.
-    //   2. Use response.write instead of response.add.
-    //
-    // However, doing that results in a chunked, gzip-encoded response, and
-    // gzip is explicitly disallowed by the requirements for these benchmark
-    // tests.
-    //
-    // See:  http://www.techempower.com/benchmarks/#section=code
-    //
-    List<int> encodedResponse = utf.encodeUtf8(response);
-    request.response.headers.contentLength = encodedResponse.length;
-    request.response.add(encodedResponse);
+    request.response.write(response);
   }
-  //
-  // The load-testing tool will close any currently open connection at the end
-  // of each run.  That potentially causes an error to be thrown here.  Since
-  // we want the server to remain alive for subsequent runs, we catch the
-  // error.
-  //
-  request.response.close().catchError(print);
+  request.response.close();
 }
 
-/**
- * Completes the given [request] by writing the [response] as HTML.
- */
-void _sendHtml(HttpRequest request, String response) {
+/// Completes the given [request] by writing the [response] as HTML.
+_sendHtml(request, response) {
   _sendResponse(request, HttpStatus.OK, _TYPE_HTML, response);
 }
 
-/**
- * Completes the given [request] by writing the [response] as JSON.
- */
-void _sendJson(HttpRequest request, Object response) {
+/// Completes the given [request] by writing the [response] as JSON.
+_sendJson(request, response) {
   _sendResponse(request, HttpStatus.OK, _TYPE_JSON, json.stringify(response));
 }
 
-/**
- * Responds with the JSON test to the [request].
- */
-void _jsonTest(HttpRequest request) {
+/// Completes the given [request] by writing the [response] as plain text.
+_sendText(request, response) {
+  _sendResponse(request, HttpStatus.OK, _TYPE_TEXT, response);
+}
+
+/// Responds with the JSON test to the [request].
+_jsonTest(request) {
   _sendJson(request, { 'message': 'Hello, World!' });
 }
 
-/**
- * Responds with the database query test to the [request].
- */
-void _dbTest(HttpRequest request) {
-  int queries = _parseInt(request.queryParameters['queries']).clamp(1, 500);
-  List<World> worlds = new List<World>(queries);
-  Future.wait(new List.generate(queries, (int index) {
-    return _connectionPool.connect().then((pg.Connection connection) {
+/// Responds with the database query test to the [request].
+_dbTest(request) {
+  var queries = _parseInt(request.uri.queryParameters['queries']).clamp(1, 500);
+  var worlds = new List<World>(queries);
+  Future.wait(new List.generate(queries, (index) {
+    return _connectionPool.connect().then((connection) {
       return connection.query(
               'SELECT id, randomNumber FROM world WHERE id = @id;',
               { 'id': _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1 })
-          .map((row) => new World(row[0], row[1]))
-          //
-          // The benchmark's constraints tell us there is exactly one row here.
-          //
-          .forEach((World world) { worlds[index] = world; })
-          .then((_) { connection.close(); });
+          .toList()
+          .then((rows) {
+            //
+            // The benchmark's constraints tell us there is exactly one row.
+            //
+            var row = rows[0];
+            worlds[index] = new World(row[0], row[1]);
+          })
+          .whenComplete(() { connection.close(); });
     });
   }, growable: false)).then((_) { _sendJson(request, worlds); });
 }
 
-/**
- * Responds with the fortunes test to the [request].
- */
-void _fortunesTest(HttpRequest request) {
-  List<Fortune> fortunes = [];
-  _connectionPool.connect().then((pg.Connection connection) {
+/// Responds with the fortunes test to the [request].
+_fortunesTest(request) {
+  var fortunes = [];
+  _connectionPool.connect().then((connection) {
     return connection.query('SELECT id, message FROM fortune;')
-        .map((row) => new Fortune(row[0], row[1]))
-        .forEach(fortunes.add)
-        .then((_) { connection.close(); });
+        .toList()
+        .then((rows) {
+          for (var row in rows) {
+            fortunes.add(new Fortune(row[0], row[1]));
+          }
+        })
+        .whenComplete(() { connection.close(); });
   }).then((_) {
     fortunes.add(new Fortune(0, 'Additional fortune added at request time.'));
     fortunes.sort();
     _sendHtml(request, _fortunesTemplate.renderString({
-      'fortunes': fortunes.map((Fortune fortune) => {
+      'fortunes': fortunes.map((fortune) => {
               'id': fortune.id, 'message': fortune.message
           }).toList()
     }));
   });
 }
 
-/**
- * Responds with the updates test to the [request].
- */
-void _updateTest(HttpRequest request) {
-  int queries = _parseInt(request.queryParameters['queries']).clamp(1, 500);
-  List<World> worlds = new List<World>(queries);
-  Future.wait(new List.generate(queries, (int index) {
-    return _connectionPool.connect().then((pg.Connection connection) {
+/// Responds with the updates test to the [request].
+_updateTest(request) {
+  var queries = _parseInt(request.uri.queryParameters['queries']).clamp(1, 500);
+  var worlds = new List<World>(queries);
+  Future.wait(new List.generate(queries, (index) {
+    return _connectionPool.connect().then((connection) {
       return connection.query(
               'SELECT id, randomNumber FROM world WHERE id = @id;',
               { 'id': _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1 })
-          .map((row) => new World(row[0], row[1]))
-          //
-          // The benchmark's constraints tell us there is exactly one row here.
-          //
-          .forEach((World world) { worlds[index] = world; })
-          .then((_) { connection.close(); });
+          .toList()
+          .then((rows) {
+            //
+            // The benchmark's constraints tell us there is exactly one row.
+            //
+            var row = rows[0];
+            worlds[index] = new World(row[0], row[1]);
+          })
+          .whenComplete(() { connection.close(); });
     });
   }, growable: false)).then((_) {
     Future.wait(new List.generate(queries, (int index) {
-      World world = worlds[index];
+      var world = worlds[index];
       world.randomNumber = _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1;
-      return _connectionPool.connect().then((pg.Connection connection) {
+      return _connectionPool.connect().then((connection) {
         return connection.execute(
                 'UPDATE world SET randomNumber = @randomNumber WHERE id = @id;',
                 { 'randomNumber': world.randomNumber, 'id': world.id })
-            .then((_) { connection.close(); });
+            .whenComplete(() { connection.close(); });
       });
     }, growable: false)).then((_) { _sendJson(request, worlds); });
   });
 }
+
+/// Responds with the plaintext test to the [request].
+_plaintextTest(request) {
+  _sendText(request, 'Hello, World!');
+}

+ 2 - 3
dart/setup.py

@@ -3,8 +3,6 @@ import sys
 import setup_util
 import os
 
-#DART_SDK = os.environ('DART_SDK')
-
 def start(args):
   setup_util.replace_text('dart/postgresql.yaml', 'host: .*', 'host: ' + args.database_host)
   try:
@@ -34,19 +32,20 @@ def start(args):
     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
     #
-    #os.remove('dart/nginx.conf')
     with open('dart/nginx.conf', 'w') as f:
       f.write('\n'.join(conf))
     #

+ 1 - 1
gemini/.classpath

@@ -8,8 +8,8 @@
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
 	<classpathentry kind="var" path="Resin4Jee"/>
 	<classpathentry kind="lib" path="Docroot/WEB-INF/lib/mysql-connector-java-5.1.23-bin.jar"/>
-	<classpathentry kind="lib" path="Docroot/WEB-INF/lib/gemini-1.3.6.jar"/>
 	<classpathentry kind="lib" path="Docroot/WEB-INF/lib/guava-14.0.1.jar"/>
 	<classpathentry kind="lib" path="Docroot/WEB-INF/lib/trove4j-3.0.3.jar"/>
+	<classpathentry kind="lib" path="Docroot/WEB-INF/lib/gemini-1.3.7.jar"/>
 	<classpathentry kind="output" path="Docroot/WEB-INF/classes"/>
 </classpath>

BIN
gemini/Docroot/WEB-INF/lib/gemini-1.3.6.jar → gemini/Docroot/WEB-INF/lib/gemini-1.3.7.jar


+ 3 - 2
gemini/Docroot/WEB-INF/mustache/fortunes.mustache

@@ -1,16 +1,17 @@
 {{<layout}}
+{{$title}}Fortunes{{/title}}
 {{$body}}
 <table>
 <tr>
 <th>id</th>
 <th>message</th>
 </tr>
-{{#.}}
+{{#req}}
 <tr>
 <td>{{id}}</td>
 <td>{{message}}</td>
 </tr>
-{{/.}}
+{{/req}}
 </table>
 {{/body}}
 {{/layout}}

+ 1 - 1
gemini/Docroot/WEB-INF/mustache/layout.mustache

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
-<title>Fortunes</title>
+<title>{{$title}}{{/title}}</title>
 </head>
 <body>
 {{$body}}{{/body}}

+ 29 - 9
gemini/Source/hello/home/handler/HelloHandler.java

@@ -11,11 +11,10 @@ import com.techempower.gemini.path.*;
 import com.techempower.gemini.path.annotation.*;
 
 /**
- * Responds to the framework benchmarking requests for "hello, world" and
- * simple database queries.
+ * Handles the various framework benchmark request types.
  */
 public class HelloHandler
-    extends  BasicPathHandler<Context>
+    extends  MethodPathHandler<Context>
 {
 
   private static final int DB_ROWS = 10000;
@@ -34,6 +33,7 @@ public class HelloHandler
   /**
    * Return "hello world" as a JSON-encoded message.
    */
+  @PathSegment("json")
   @PathDefault
   public boolean helloworld()
   {
@@ -41,14 +41,24 @@ public class HelloHandler
   }
 
   /**
-   * Return a list of World objects as JSON, selected randomly from the World
-   * table.  For consistency, we have assumed the table has 10,000 rows.
+   * Return a single World objects as JSON, selected randomly from the World
+   * table.  Assume the table has 10,000 rows.
    */
   @PathSegment
   public boolean db()
+  {
+    return json(store.get(World.class, ThreadLocalRandom.current().nextInt(DB_ROWS) + 1));
+  }
+
+  /**
+   * Return a list of World objects as JSON, selected randomly from the World
+   * table.  Assume the table has 10,000 rows.
+   */
+  @PathSegment("query")
+  public boolean multipleQueries()
   {
     final Random random = ThreadLocalRandom.current();
-    final int queries = context().getInt("queries", 1, 1, 500);
+    final int queries = query().getInt("queries", 1, 1, 500);
     final World[] worlds = new World[queries];
 
     for (int i = 0; i < queries; i++)
@@ -75,14 +85,15 @@ public class HelloHandler
 
   /**
    * Return a list of World objects as JSON, selected randomly from the World
-   * table.  For each row that is retrieved, that row will have it's randomNumber
-   * field updated and persisted. For consistency, we have assumed the table has 10,000 rows.
+   * table.  For each row that is retrieved, that row will have its 
+   * randomNumber field updated and then the row will be persisted.  We
+   * assume the table has 10,000 rows.
    */
   @PathSegment
   public boolean update()
   {
     final Random random = ThreadLocalRandom.current();
-    final int queries = context().getInt("queries", 1, 1, 500);
+    final int queries = query().getInt("queries", 1, 1, 500);
     final World[] worlds = new World[queries];
 
     for (int i = 0; i < queries; i++)
@@ -95,5 +106,14 @@ public class HelloHandler
     
     return json(worlds);
   }
+  
+  /**
+   * Responds with a plaintext "Hello, World!" 
+   */
+  @PathSegment
+  public boolean plaintext()
+  {
+    return text("Hello, World!");
+  }
 
 }

+ 1 - 0
servlet/src/main/java/hello/Common.java

@@ -13,6 +13,7 @@ public class Common
   // Constants for setting the content type.
   protected static final String HEADER_CONTENT_TYPE    = "Content-Type";
   protected static final String CONTENT_TYPE_JSON      = "application/json";
+  protected static final String CONTENT_TYPE_TEXT      = "text/plain";
   protected static final String CONTENT_TYPE_HTML      = "text/html";
 
   // Jackson encoder, reused for each response.

+ 33 - 0
servlet/src/main/java/hello/PlaintextServlet.java

@@ -0,0 +1,33 @@
+package hello;
+
+import java.io.*;
+
+import javax.servlet.*;
+import javax.servlet.http.*;
+
+/**
+ * Plaintext rendering Test
+ */
+@SuppressWarnings("serial")
+public class PlaintextServlet extends HttpServlet
+{
+
+  @Override
+  protected void doGet(HttpServletRequest req, HttpServletResponse res)
+      throws ServletException, IOException
+  {
+    // Set content type to text/plain.
+    res.setHeader(Common.HEADER_CONTENT_TYPE, Common.CONTENT_TYPE_TEXT);
+
+    // Write plaintext "Hello, World!" to the response.
+    try
+    {
+      res.getWriter().write("Hello, World!");
+    }
+    catch (IOException ioe) 
+    {
+      // do nothing
+    }
+  }
+  
+}

+ 6 - 0
servlet/src/main/webapp/WEB-INF/web.xml

@@ -5,6 +5,12 @@
     <load-on-startup/>
   </servlet>
   <servlet-mapping url-regexp='^/json$' servlet-name='json'/>
+  <servlet>
+    <servlet-name>plaintext</servlet-name>
+    <servlet-class>hello.PlaintextServlet</servlet-class>
+    <load-on-startup/>
+  </servlet>
+  <servlet-mapping url-regexp='^/plaintext$' servlet-name='plaintext'/>
   <servlet>
     <servlet-name>db</servlet-name>
     <servlet-class>hello.DbPoolServlet</servlet-class>