Browse Source

Merge branch 'master' of https://github.com/MalcolmEvershed/FrameworkBenchmarks into sqlserver

Malcolm Evershed 12 years ago
parent
commit
5787657351
76 changed files with 1802 additions and 226 deletions
  1. 0 33
      README.md
  2. 23 0
      aspnet/src/Application.cs
  3. 6 2
      aspnet/src/Controllers/AdoController.cs
  4. 10 0
      aspnet/src/Web.config
  5. 1 0
      benchmarker.py
  6. 2 1
      config/benchmark_profile
  7. 7 6
      cpoll_cppsp/Makefile
  8. 21 1
      cpoll_cppsp/benchmark_config
  9. 27 1
      cpoll_cppsp/www/connectioninfo.H
  10. 126 49
      cpoll_cppsp/www/db
  11. 63 0
      cpoll_cppsp/www/db_old
  12. 119 0
      cpoll_cppsp/www/db_pg_async
  13. 106 0
      cpoll_cppsp/www/db_pg_threadpool
  14. 52 19
      cpoll_cppsp/www/fortune
  15. 63 0
      cpoll_cppsp/www/fortune_old
  16. 29 0
      cpoll_cppsp/www/generic_pool.H
  17. 6 0
      cpoll_cppsp/www/json
  18. 180 0
      cpoll_cppsp/www/update
  19. 5 0
      cpoll_cppsp/www/world.H
  20. 1 0
      dart/.gitignore
  21. 42 0
      dart/README.md
  22. 0 0
      dart/__init__.py
  23. 15 0
      dart/benchmark_config
  24. 20 0
      dart/fortunes.mustache
  25. 5 0
      dart/postgresql.yaml
  26. 8 0
      dart/pubspec.yaml
  27. 239 0
      dart/server.dart
  28. 74 0
      dart/setup.py
  29. 41 11
      framework_test.py
  30. 1 1
      gemini/.classpath
  31. BIN
      gemini/Docroot/WEB-INF/lib/gemini-1.3.7.jar
  32. 3 2
      gemini/Docroot/WEB-INF/mustache/fortunes.mustache
  33. 1 1
      gemini/Docroot/WEB-INF/mustache/layout.mustache
  34. 29 9
      gemini/Source/hello/home/handler/HelloHandler.java
  35. 2 1
      gemini/benchmark_config
  36. 2 1
      go/benchmark_config
  37. 9 1
      go/src/hello/hello.go
  38. 33 12
      installer.ps1
  39. 30 7
      installer.py
  40. 2 0
      jester/.gitignore
  41. 20 0
      jester/README.md
  42. 0 0
      jester/__init__.py
  43. 11 0
      jester/benchmark_config
  44. 66 0
      jester/config/nginx.conf
  45. 9 0
      jester/hello.nim
  46. 0 0
      jester/public/.gitkeep
  47. 29 0
      jester/setup.py
  48. 2 1
      netty/benchmark_config
  49. 18 13
      netty/src/main/java/hello/HelloServerHandler.java
  50. 9 3
      php-codeigniter/deploy/nginx.conf
  51. 1 0
      php-kohana/application/bootstrap.php
  52. 10 11
      php-kohana/application/classes/Controller/Bench.php
  53. 7 3
      php-phalcon-micro/deploy/nginx.conf
  54. 8 2
      php-phalcon/deploy/nginx.conf
  55. 10 3
      php/deploy/nginx.conf
  56. 0 0
      plack/__init__.py
  57. 2 0
      revel/benchmark_config
  58. 13 2
      revel/setup.py
  59. 63 6
      revel/src/benchmark/app/controllers/app.go
  60. 1 1
      revel/src/benchmark/conf/app.conf
  61. 3 1
      revel/src/benchmark/conf/routes
  62. 0 0
      ringojs-convenient/README.md
  63. 0 0
      ringojs-convenient/__init__.py
  64. 0 0
      ringojs-convenient/app/models.js
  65. 31 0
      ringojs-convenient/app/views.js
  66. 2 0
      ringojs-convenient/benchmark_config
  67. 0 0
      ringojs-convenient/ringo-main.js
  68. 7 1
      ringojs-convenient/setup.py
  69. 0 0
      ringojs-convenient/templates/fortunes.reinhardt
  70. 2 7
      ringojs/benchmark_config
  71. 32 12
      ringojs/ringo-main.js
  72. 2 1
      ringojs/setup.py
  73. 1 1
      run-tests.py
  74. 1 0
      servlet/src/main/java/hello/Common.java
  75. 33 0
      servlet/src/main/java/hello/PlaintextServlet.java
  76. 6 0
      servlet/src/main/webapp/WEB-INF/web.xml

+ 0 - 33
README.md

@@ -125,39 +125,6 @@ This will run the full set of tests. Results of all the tests will output to ~/F
 ### Windows Instructions
 Generously provided by [@pdonald](https://github.com/pdonald)
 
-Note: The following tests are the only known tests to run on Windows
-* aspnet
-* aspnet-mysql-raw
-* aspnet-postgresql-raw
-* aspnet-mongodb-raw
-* aspnet-mysql-entityframework
-* aspnet-postgres-entityframework
-* cake
-* express
-* express-mongodb
-* go
-* nodejs
-* nodejs-mongodb
-* express
-* express-mongodb
-* php
-* php-raw
-* codeigniter
-* codeigniter-raw
-* fuel
-* kohana
-* kohana-raw
-* laravel
-* laravel-raw
-* lithium
-* micromvc
-* slim
-* phreeze
-* servlet
-* servlet-raw
-* servlet-postgres-raw
-* webgo
-
 Server installation scripts for Windows Server 2012 R2 on Amazon EC2.
 
 Instructions:

+ 23 - 0
aspnet/src/Application.cs

@@ -1,3 +1,6 @@
+using System;
+using System.Configuration;
+using System.Threading;
 using System.Web;
 using System.Web.Mvc;
 using System.Web.Routing;
@@ -25,6 +28,7 @@ namespace Benchmarks.AspNet
         {
             Routes();
             Views();
+            Threads();
         }
 
         private void Routes()
@@ -57,6 +61,25 @@ namespace Benchmarks.AspNet
             ViewEngines.Engines.Add(new RazorViewEngine { ViewLocationFormats = new[] { "~/Views/{0}.cshtml" } });
         }
 
+        private void Threads()
+        {
+            // To improve CPU utilization, increase the number of threads that the .NET thread pool expands by when
+            // a burst of requests come in. We could do this by editing machine.config/system.web/processModel/minWorkerThreads,
+            // but that seems too global a change, so we do it in code for just our AppPool. More info:
+            //
+            // http://support.microsoft.com/kb/821268
+            // http://blogs.msdn.com/b/tmarq/archive/2007/07/21/asp-net-thread-usage-on-iis-7-0-and-6-0.aspx
+            // http://blogs.msdn.com/b/perfworld/archive/2010/01/13/how-can-i-improve-the-performance-of-asp-net-by-adjusting-the-clr-thread-throttling-properties.aspx
+
+            int newMinWorkerThreads = Convert.ToInt32(ConfigurationManager.AppSettings["minWorkerThreadsPerLogicalProcessor"]);
+            if (newMinWorkerThreads > 0)
+            {
+                int minWorkerThreads, minCompletionPortThreads;
+                ThreadPool.GetMinThreads(out minWorkerThreads, out minCompletionPortThreads);
+                ThreadPool.SetMinThreads(Environment.ProcessorCount * newMinWorkerThreads, minCompletionPortThreads);
+            }
+        }
+
         public void Dispose()
         {
         }

+ 6 - 2
aspnet/src/Controllers/AdoController.cs

@@ -45,7 +45,9 @@ namespace Benchmarks.AspNet.Controllers
                         command.Parameters.Clear();
                         command.Parameters.Add(parameter);
                         
-                        using (DbDataReader reader = command.ExecuteReader(CommandBehavior.SingleRow))
+                        // Don't use CommandBehavior.SingleRow because that will make the MySql provider
+                        // send two extra commands to limit the result to one row.
+                        using (DbDataReader reader = command.ExecuteReader())
                         {
                             if (reader.Read())
                             {
@@ -124,7 +126,9 @@ namespace Benchmarks.AspNet.Controllers
 
                         World world = null;
 
-                        using (DbDataReader reader = selectCommand.ExecuteReader(CommandBehavior.SingleRow))
+                        // Don't use CommandBehavior.SingleRow because that will make the MySql provider
+                        // send two extra commands to limit the result to one row.
+                        using (DbDataReader reader = selectCommand.ExecuteReader())
                         {
                             if (reader.Read())
                             {

+ 10 - 0
aspnet/src/Web.config

@@ -28,6 +28,16 @@
       <!-- TODO: Need for SQL Server -->
     </providers>
   </entityFramework>
+  <appSettings>
+    <!-- Disable support for directly accessing *.cshtml/*.vbhtml files because that is a perf killer
+         and because we don't use such functionality. -->
+    <add key="webpages:Enabled" value="false" />
+    <!-- To fully saturate the CPUs, we need to allow the .NET thread pool to create many threads
+         when a large burst of requests come in. We do this by boosting the minWorkerThreads value
+         from the default of 1 per logical processor to 8 per logical processor. This seems to be
+         pretty conservative as http://support.microsoft.com/kb/821268 recommends 50.-->
+    <add key="minWorkerThreadsPerLogicalProcessor" value="8" />
+  </appSettings>
   <system.web>
     <!-- Show errors -->
     <customErrors mode="Off"/>

+ 1 - 0
benchmarker.py

@@ -516,6 +516,7 @@ class Benchmarker:
       self.results['rawData']['query'] = dict()
       self.results['rawData']['fortune'] = dict()
       self.results['rawData']['update'] = dict()
+      self.results['rawData']['plaintext'] = dict()
     else:
       #for x in self.__gather_tests():
       #  if x.name not in self.results['frameworks']:

+ 2 - 1
config/benchmark_profile

@@ -10,7 +10,8 @@ export PLAY_HOME=~/FrameworkBenchmarks/installs/play-2.1.2-RC1
 export PLAY1_HOME=~/FrameworkBenchmarks/installs/play-1.2.5
 export MAVEN_HOME=~/FrameworkBenchmarks/installs/apache-maven-3.0.5
 export PERL_HOME=/opt/ActivePerl-5.16
-export PATH="$JAVA_HOME/bin:$GRAILS_HOME/bin:$PLAY_HOME:$PLAY1_HOME:$VERTX_HOME/bin:$GOROOT/bin:$NODE_HOME/bin:$HOME/FrameworkBenchmarks/installs/bin:$MAVEN_HOME/bin:$PERL_HOME/bin:$PERL_HOME/site/bin:$PATH"
+export DART_HOME=~/FrameworkBenchmarks/installs/dart-sdk
+export PATH="$JAVA_HOME/bin:$GRAILS_HOME/bin:$PLAY_HOME:$PLAY1_HOME:$VERTX_HOME/bin:$GOROOT/bin:$NODE_HOME/bin:$HOME/FrameworkBenchmarks/installs/bin:$MAVEN_HOME/bin:$PERL_HOME/bin:$PERL_HOME/site/bin:$DART_HOME/bin:$PATH"
 
 export LD_LIBRARY_PATH='$LD_LIBRARY_PATH:/usr/local/apr/lib'
 

+ 7 - 6
cpoll_cppsp/Makefile

@@ -1,12 +1,13 @@
 all: cppsp_0.2
 
 clean:
-	rm -rf cppsp_0.2-rc3 cppsp_0.2
-cppsp_0.2: cppsp_0.2-rc3.tar.xz
-	tar xf cppsp_0.2-rc3.tar.xz
-	ln -s cppsp_0.2-rc3 cppsp_0.2
+	rm -rf cppsp_0.2-rc9 cppsp_0.2
+	rm -f www/*.so www/*.txt
+cppsp_0.2: cppsp_0.2-rc9.tar.xz
+	tar xf cppsp_0.2-rc9.tar.xz
+	ln -s cppsp_0.2-rc9 cppsp_0.2
 	$(MAKE) CXX=g++-4.8 -C cppsp_0.2 all
 
-cppsp_0.2-rc3.tar.xz:
-	wget -c https://downloads.sourceforge.net/project/cpollcppsp/CPPSP%200.2%20%28unstable%29/cppsp_0.2-rc3.tar.xz
+cppsp_0.2-rc9.tar.xz:
+	wget -c http://downloads.sourceforge.net/project/cpollcppsp/CPPSP%200.2%20%28testing%29/cppsp_0.2-rc9.tar.xz
 

+ 21 - 1
cpoll_cppsp/benchmark_config

@@ -4,11 +4,31 @@
     "default": {
       "setup_file": "setup",
       "json_url": "/json",
+      "port": 16969,
+      "sort": 115
+    },
+    "raw": {
+      "setup_file": "setup",
       "db_url": "/db",
       "query_url": "/db?queries=",
       "fortune_url": "/fortune",
+      "update_url": "/update?queries=",
       "port": 16969,
-      "sort": 115
+      "sort": 130
+    },
+    "postgres-raw": {
+      "setup_file": "setup",
+      "db_url": "/db_pg_async",
+      "query_url": "/db_pg_async?queries=", 
+      "port": 16969,
+      "sort": 131
+    },
+    "postgres-raw-threadpool": {
+      "setup_file": "setup",
+      "db_url": "/db_pg_threadpool",
+      "query_url": "/db_pg_threadpool?queries=", 
+      "port": 16969,
+      "sort": 132
     }
   }]
 }

+ 27 - 1
cpoll_cppsp/www/connectioninfo.H

@@ -1,10 +1,13 @@
 #define BENCHMARK_DB_HOST "localhost"
+#define MYSQL_MAX_CONNECTIONS 3000
 
 #include <stdexcept>
 using namespace std;
 
+#ifdef _mysql_h
+
 MYSQL* doConnect(void*) {
-	printf("doConnect()\n");
+	//printf("doConnect()\n");
 	MYSQL* tmp=mysql_init(NULL);
 	if(tmp==NULL) throw bad_alloc();
 	if (mysql_real_connect(tmp, BENCHMARK_DB_HOST, 
@@ -13,8 +16,31 @@ MYSQL* doConnect(void*) {
 		mysql_close(tmp);
 		throw runtime_error(s);
 	}
+	mysql_set_character_set(tmp, "utf8");
+	mysql_options(tmp, MYSQL_SET_CHARSET_NAME, "utf8");
 	return tmp;
 }
 void doDisconnect(void*,MYSQL* conn) {
 	mysql_close(conn);
 }
+#endif
+#ifdef LIBPQ_FE_H
+PGconn* doConnect_pg(void*) {
+	//printf("doConnect_pg()\n");
+	const char* connString="host='" BENCHMARK_DB_HOST 
+		"' user='benchmarkdbuser' password='benchmarkdbpass' dbname='hello_world' sslmode='allow'";
+	PGconn* conn;
+	conn = PQconnectdb(connString);
+	if(conn==NULL) throw bad_alloc();
+	if (PQstatus(conn) != CONNECTION_OK)
+	{
+		string err=PQerrorMessage(conn);
+		PQfinish(conn);
+		throw runtime_error(err);
+	}
+	return conn;
+}
+void doDisconnect_pg(void*,PGconn* conn) {
+	PQfinish(conn);
+}
+#endif

+ 126 - 49
cpoll_cppsp/www/db

@@ -1,63 +1,140 @@
-<%!-lmysqlclient%><%#
+<%!-lmysqlclient%><%@ class mypage %><%#
 #include <mysql/mysql.h>
 #include <json/json.h>
+#include <cpoll/threadpool.H>
+#include <list>
 #include "connectioninfo.H"
+#include "generic_pool.H"
+#include "world.H"
+
 using namespace CP;
 using namespace cppsp;
+using namespace std;
+
+class myStatement
+{
+public:
+	MYSQL* db;
+	int rn;
+	int r;
+	long unsigned int len1;
+	MYSQL_STMT* stmt;
+	MYSQL_BIND param[1], results[1];
+	myStatement() {
+		db=doConnect(NULL);
+		stmt=mysql_stmt_init(db);
+		const char* sql="SELECT randomNumber FROM World WHERE id = ?";
+		mysql_stmt_prepare(stmt,sql,strlen(sql));
+		memset(param, 0, sizeof(param));
+		memset(results, 0, sizeof(results));
+		param[0].buffer_type = MYSQL_TYPE_LONG;
+		param[0].buffer = (char *)&rn;
+		param[0].buffer_length = sizeof(rn);
+		param[0].is_null = 0;
+		param[0].length = NULL;
+		results[0].buffer_type= MYSQL_TYPE_LONG;
+		results[0].buffer = &r;
+		results[0].buffer_length = sizeof(r);
+		results[0].is_null = 0;
+		results[0].length = &len1;
+		if(mysql_stmt_bind_param(stmt,param)) throw runtime_error(mysql_stmt_error(stmt));
+		if(mysql_stmt_bind_result(stmt, results)) throw runtime_error(mysql_stmt_error(stmt));
+	}
+	~myStatement() {
+		mysql_stmt_close(stmt);
+		doDisconnect(NULL,db);
+	}
+};
+myStatement* cStatement(void*) {
+	return new myStatement();
+}
+void dStatement(void*, myStatement* s) {
+	delete s;
+}
+genericPool<myStatement*,128> stmtPool(&cStatement,&dStatement);
 
-MYSQL* db=NULL;
-int rn;
-int r;
-long unsigned int len1;
-MYSQL_STMT* stmt;
-MYSQL_BIND param[1], results[1];
-%>
-{ "json": [ <%
+ThreadPool tp(32);
+
+int curOperations=0;
+static const int maxOperations=MYSQL_MAX_CONNECTIONS;
+list<EventFD*> waitingThreads;
+
+void do_init_thread(void*) {
+	mysql_thread_init();
+}
+void do_deinit_thread(void*) {
+	mysql_thread_end();
+}
+extern "C" void initModule() {
+	mysql_library_init(0,NULL,NULL);
+	tp.afterStart=&do_init_thread;
+	tp.beforeExit=&do_deinit_thread;
+}
+
+%><%$
+EventFD efd{0,EFD_SEMAPHORE};
 int queries=1;
-auto it=request->queryString.find("queries");
-if(it!=request->queryString.end()) {
-	queries=atoi((*it).second);
-}
-if(queries<1)queries=1;
-if(queries>500)queries=500;
-int i;
-char query[256];
-
-if(db==NULL) {
-	db=doConnect(NULL);
-	stmt=mysql_stmt_init(db);
-	const char* sql="SELECT randomNumber FROM World WHERE id = ?";
-	mysql_stmt_prepare(stmt,sql,strlen(sql));
-	memset(param, 0, sizeof(param));
-	memset(results, 0, sizeof(results));
-	param[0].buffer_type = MYSQL_TYPE_LONG;
-	param[0].buffer = (char *)&rn;
-	param[0].buffer_length = sizeof(rn);
-	param[0].is_null = 0;
-	param[0].length = NULL;
-	results[0].buffer_type= MYSQL_TYPE_LONG;
-	results[0].buffer = &r;
-	results[0].buffer_length = sizeof(r);
-	results[0].is_null = 0;
-	results[0].length = &len1;
-	if(mysql_stmt_bind_param(stmt,param)) throw runtime_error(mysql_stmt_error(stmt));
-	if(mysql_stmt_bind_result(stmt, results)) throw runtime_error(mysql_stmt_error(stmt));
-}
-int items[queries];
-for (int i=0;i<queries;i++){
-	rn=rand()%10000;
-	if(mysql_stmt_execute(stmt)) throw runtime_error(mysql_stmt_error(stmt));
-	if(mysql_stmt_fetch(stmt)==0) {
-		items[i]=r;
-	} else {
-		items[i]=0;
+world* items;
+myStatement* stmt;
+
+void tpFunc() {
+	//mysql_thread_init();
+	for (int i=0;i<queries;i++){
+		items[i].id=stmt->rn=rand()%10000;
+		if(mysql_stmt_execute(stmt->stmt)) throw runtime_error(mysql_stmt_error(stmt->stmt));
+		if(mysql_stmt_fetch(stmt->stmt)==0) {
+			items[i].rnd=stmt->r;
+		} else {
+			items[i].rnd=0;
+		}
+	}
+	efd.sendEvent(1);
+}
+//asynchronously load the data in the doInit() function, and defer page rendering until data is available
+void doInit() override {
+	if(unlikely(curOperations>=maxOperations)) {
+		//too many threads. need to wait.
+		waitingThreads.push_back(&efd);
+		efd.getEvent({&mypage::waitCB,this});
+		poll->add(efd);
+		return;
 	}
+	curOperations++;
+	auto it=request->queryString.find("queries");
+	if(it!=request->queryString.end()) {
+		queries=atoi((*it).second);
+	}
+	if(queries<1)queries=1;
+	if(queries>500)queries=500;
+	int i;
+	
+	items=(world*)sp->alloc(sizeof(world)*queries);
+	stmt=stmtPool.get();
+	poll->add(efd);
+	tp.invoke({&mypage::tpFunc,this});
+	efd.getEvent({&mypage::efdCB,this});
+}
+void efdCB(eventfd_t efdVal) {
+	curOperations--;
+	if(curOperations<maxOperations) {
+		if(unlikely(!waitingThreads.empty())) {
+			waitingThreads.front()->sendEvent(1);
+			waitingThreads.pop_front();
+		}
+	}
+	stmtPool.put(stmt);
+	Page::doInit();
 }
+void waitCB(eventfd_t efdVal) {
+	this->doInit();
+}
+
+%>[<%
 for (int i=0;i<queries;i++){
-	if(i>0) {%>, <%}
-	%>{ "randomNumber": <%=items[i]%> }<%
+	if(i>0) output.write(',');
+	%>{"id":<%=items[i].id%>,"randomNumber":<%=items[i].rnd%>}<%
 }
 
 response->headers["Content-Type"]="application/json";
 response->headers["Server"]="cppsp/0.2";
-%> ] }
+%>]

+ 63 - 0
cpoll_cppsp/www/db_old

@@ -0,0 +1,63 @@
+<%!-lmysqlclient%><%#
+#include <mysql/mysql.h>
+#include <json/json.h>
+#include "connectioninfo.H"
+using namespace CP;
+using namespace cppsp;
+
+MYSQL* db=NULL;
+int rn;
+int r;
+long unsigned int len1;
+MYSQL_STMT* stmt;
+MYSQL_BIND param[1], results[1];
+%>
+{ "json": [ <%
+int queries=1;
+auto it=request->queryString.find("queries");
+if(it!=request->queryString.end()) {
+	queries=atoi((*it).second);
+}
+if(queries<1)queries=1;
+if(queries>500)queries=500;
+int i;
+char query[256];
+
+if(db==NULL) {
+	db=doConnect(NULL);
+	stmt=mysql_stmt_init(db);
+	const char* sql="SELECT randomNumber FROM World WHERE id = ?";
+	mysql_stmt_prepare(stmt,sql,strlen(sql));
+	memset(param, 0, sizeof(param));
+	memset(results, 0, sizeof(results));
+	param[0].buffer_type = MYSQL_TYPE_LONG;
+	param[0].buffer = (char *)&rn;
+	param[0].buffer_length = sizeof(rn);
+	param[0].is_null = 0;
+	param[0].length = NULL;
+	results[0].buffer_type= MYSQL_TYPE_LONG;
+	results[0].buffer = &r;
+	results[0].buffer_length = sizeof(r);
+	results[0].is_null = 0;
+	results[0].length = &len1;
+	if(mysql_stmt_bind_param(stmt,param)) throw runtime_error(mysql_stmt_error(stmt));
+	if(mysql_stmt_bind_result(stmt, results)) throw runtime_error(mysql_stmt_error(stmt));
+}
+int items[queries];
+for (int i=0;i<queries;i++){
+	rn=rand()%10000;
+	if(mysql_stmt_execute(stmt)) throw runtime_error(mysql_stmt_error(stmt));
+	if(mysql_stmt_fetch(stmt)==0) {
+		items[i]=r;
+	} else {
+		items[i]=0;
+	}
+}
+for (int i=0;i<queries;i++){
+	if(i>0) {%>, <%}
+	%>{ "randomNumber": <%=items[i]%> }<%
+}
+
+response->headers["Content-Type"]="application/json";
+response->headers["Server"]="cppsp/0.2";
+%> ] }

+ 119 - 0
cpoll_cppsp/www/db_pg_async

@@ -0,0 +1,119 @@
+<%!-lpq%><%!-I/usr/include/postgresql%><%@ class mypage %><%#
+#include <libpq-fe.h>
+#include <postgres.h>
+#include <catalog/pg_type.h>
+#include <json/json.h>
+#include <cpoll/threadpool.H>
+#include <list>
+#include "connectioninfo.H"
+#include "generic_pool.H"
+#include "world.H"
+
+using namespace CP;
+using namespace cppsp;
+using namespace std;
+
+
+struct myStatement
+{
+public:
+	PGconn* db;
+	int paramLengths[1];
+	int paramFormats[1];
+	File f;
+	Poll* p;
+	bool addedToPoll=false;
+	myStatement() {
+		db=doConnect_pg(NULL);
+		Oid oid=INT4OID;
+		PGresult* res;
+		if((res=PQprepare(db, "zxcvb", "SELECT randomnumber FROM World where id=$1", 1, &oid))==NULL)
+			goto fail;
+		PQclear(res);
+		paramLengths[0] = sizeof(int);
+		paramFormats[0] = 1; //binary
+		return;
+	fail:
+		doDisconnect_pg(NULL,db);
+		throw runtime_error("error preparing statement");
+	}
+	~myStatement() {
+		if(addedToPoll) {
+			p->del(f);
+		}
+		f.deinit();
+		doDisconnect_pg(NULL,db);
+	}
+	void exec(int id) {
+		const char *params[1];
+		id=htonl(id);
+		params[0]=(const char*)&id;
+		PQsendQueryPrepared(db,"zxcvb",1,params,paramLengths,paramFormats,1/*binary*/);
+	}
+};
+myStatement* cStatement(void*) {
+	return new myStatement();
+}
+void dStatement(void*, myStatement* s) {
+	delete s;
+}
+genericPool<myStatement*,128> stmtPool(&cStatement,&dStatement);
+
+%><%$
+int queries=1;
+world* items;
+int n=0;
+myStatement* stmt;
+//asynchronously load the data in the doInit() function, and defer page rendering until data is available
+void doInit() override {
+	auto it=request->queryString.find("queries");
+	if(it!=request->queryString.end()) {
+		queries=atoi((*it).second);
+	}
+	if(queries<1)queries=1;
+	if(queries>500)queries=500;
+	int i;
+	items=(world*)sp->alloc(sizeof(world)*queries);
+	stmt=stmtPool.get();
+	if(!stmt->addedToPoll) {
+		poll->add(stmt->f);
+		stmt->addedToPoll=true;
+		stmt->p=poll;
+	}
+	beginGetItems();
+}
+void beginGetItems() {
+	if(n>=queries) {
+		stmtPool.put(stmt);
+		Page::doInit();
+		return;
+	}
+	items[n++].id=rand()%10000;
+	stmt->exec(items[n-1].id);
+	stmt->f.waitForEvent(Events::in,{&mypage::evtIn,this});
+}
+void evtIn(int) {
+	PGresult* res;
+	res=PQgetResult(stmt->db);
+	PGresult* res1;
+	while((res1=PQgetResult(stmt->db))!=NULL) {
+		PQclear(res1);
+	}
+	int tmp;
+	if(PQntuples(res)>0)
+		tmp=*(const int*)PQgetvalue(res,0,0);
+	else tmp=0;
+	items[n-1].rnd=ntohl(tmp);
+	PQclear(res);
+	beginGetItems();
+}
+
+%>[<%
+for (int i=0;i<queries;i++){
+	if(i>0) output.write(',');
+	%>{"id":<%=items[i].id%>,"randomNumber":<%=items[i].rnd%>}<%
+}
+
+response->headers["Content-Type"]="application/json";
+response->headers["Server"]="cppsp/0.2";
+%>]

+ 106 - 0
cpoll_cppsp/www/db_pg_threadpool

@@ -0,0 +1,106 @@
+<%!-lpq%><%!-I/usr/include/postgresql%><%@ class mypage %><%#
+#include <libpq-fe.h>
+#include <postgres.h>
+#include <catalog/pg_type.h>
+#include <json/json.h>
+#include <cpoll/threadpool.H>
+#include <list>
+#include "connectioninfo.H"
+#include "generic_pool.H"
+#include "world.H"
+
+using namespace CP;
+using namespace cppsp;
+using namespace std;
+
+class myStatement
+{
+public:
+	PGconn* db;
+	int paramLengths[1];
+	int paramFormats[1];
+	myStatement() {
+		db=doConnect_pg(NULL);
+		Oid oid=INT4OID;
+		PGresult* res;
+		if((res=PQprepare(db, "zxcvb", "SELECT randomnumber FROM World where id=$1", 1, &oid))==NULL)
+			goto fail;
+		PQclear(res);
+		paramLengths[0] = sizeof(int);
+		paramFormats[0] = 1; //binary
+		return;
+	fail:
+		doDisconnect_pg(NULL,db);
+		throw runtime_error("error preparing statement");
+	}
+	~myStatement() {
+		doDisconnect_pg(NULL,db);
+	}
+	PGresult* exec(int id) {
+		const char *params[1];
+		id=htonl(id);
+		params[0]=(const char*)&id;
+		return PQexecPrepared(db,"zxcvb",1,params,paramLengths,paramFormats,1/*binary*/);
+	}
+};
+myStatement* cStatement(void*) {
+	return new myStatement();
+}
+void dStatement(void*, myStatement* s) {
+	delete s;
+}
+genericPool<myStatement*,64> stmtPool(&cStatement,&dStatement);
+
+ThreadPool tp(32);
+
+
+%><%$
+EventFD efd{0,EFD_SEMAPHORE};
+int queries=1;
+world* items;
+myStatement* stmt;
+
+void tpFunc() {
+	for (int i=0;i<queries;i++){
+		PGresult* res;
+		if((res=stmt->exec(items[i].id=(rand()%10000)))==NULL) throw bad_alloc();
+		if(PQntuples(res)>0) {
+			items[i].rnd=ntohl(*(const int*)PQgetvalue(res,0,0));
+		}
+		else items[i].rnd=0;
+		PQclear(res);
+	}
+	efd.sendEvent(1);
+}
+//asynchronously load the data in the doInit() function, and defer page rendering until data is available
+void doInit() override {
+	auto it=request->queryString.find("queries");
+	if(it!=request->queryString.end()) {
+		queries=atoi((*it).second);
+	}
+	if(queries<1)queries=1;
+	if(queries>500)queries=500;
+	
+	items=(world*)sp->alloc(sizeof(world)*queries);
+	stmt=stmtPool.get();
+	poll->add(efd);
+	tp.invoke({&mypage::tpFunc,this});
+	efd.getEvent({&mypage::efdCB,this});
+}
+void efdCB(eventfd_t efdVal) {
+	stmtPool.put(stmt);
+	Page::doInit();
+}
+void waitCB(eventfd_t efdVal) {
+	this->doInit();
+}
+
+%>[<%
+for (int i=0;i<queries;i++){
+	if(i>0) output.write(',');
+	%>{"id":<%=items[i].id%>,"randomNumber":<%=items[i].rnd%>}<%
+}
+
+response->headers["Content-Type"]="application/json";
+response->headers["Server"]="cppsp/0.2";
+%>]

+ 52 - 19
cpoll_cppsp/www/fortune

@@ -1,12 +1,16 @@
-<%!-lmysqlclient%><%#
+<%!-lmysqlclient%><%@ class mypage %><%#
 #include <mysql/mysql.h>
 #include <json/json.h>
+#include <cpoll/threadpool.H>
+#include <list>
 #include "connectioninfo.H"
+#include "generic_pool.H"
+
 using namespace CP;
 using namespace cppsp;
+using namespace std;
 
 
-MYSQL* db=NULL;
 
 
 bool comp_string(const String& x, const String& y) {
@@ -17,16 +21,6 @@ bool comp_string(const String& x, const String& y) {
 	else return tmp < 0;
 }
 
-%><%
-if(db==NULL) {
-	db=doConnect(NULL);
-	mysql_set_character_set(db, "utf8");
-	mysql_options(db, MYSQL_SET_CHARSET_NAME, "utf8");
-}
-mysql_query(db, "SELECT id, message FROM Fortune;");
-MYSQL_RES *sqlres = mysql_store_result(db);
-if (sqlres==NULL) throw runtime_error(mysql_error(db));
-MYSQL_ROW row;
 struct fortune
 {
 	String message;
@@ -35,16 +29,55 @@ struct fortune
 		return comp_string(message,other.message);
 	}
 };
-vector<fortune> fortunes;
-while( (row=mysql_fetch_row(sqlres)) ){
-	fortunes.push_back({sp->addString(row[1]),atoi(row[0])});
+genericPool<MYSQL*,64> sqlPool(&doConnect,&doDisconnect);
+ThreadPool tp(16);
+
+void do_init_thread(void*) {
+	mysql_thread_init();
 }
-mysql_free_result(sqlres);
+void do_deinit_thread(void*) {
+	mysql_thread_end();
+}
+extern "C" void initModule() {
+	mysql_library_init(0,NULL,NULL);
+	tp.afterStart=&do_init_thread;
+	tp.beforeExit=&do_deinit_thread;
+}
+%><%$
+EventFD efd{0,EFD_SEMAPHORE};
+vector<fortune> fortunes;
+MYSQL* db;
 
-fortunes.push_back({"Additional fortune added at request time.",0});
-sort(fortunes.begin(),fortunes.end());
 
-response->headers["Server"]="cppsp/0.2";
+//function to be executed in the thread pool
+void tpFunc() {
+	mysql_query(db, "SELECT id, message FROM Fortune;");
+	MYSQL_RES *sqlres = mysql_store_result(db);
+	if (sqlres==NULL) throw runtime_error(mysql_error(db));
+	MYSQL_ROW row;
+	while( (row=mysql_fetch_row(sqlres)) ){
+		fortunes.push_back({sp->addString(row[1]),atoi(row[0])});
+	}
+	mysql_free_result(sqlres);
+
+	fortunes.push_back({"Additional fortune added at request time.",0});
+	sort(fortunes.begin(),fortunes.end());
+	efd.sendEvent(1);
+}
+
+//asynchronously load the data in the doInit() function, and defer page rendering until data is available
+void doInit() override {
+	response->headers["Server"]="cppsp/0.2";
+	
+	db=sqlPool.get();
+	poll->add(efd);
+	tp.invoke({&mypage::tpFunc,this});
+	efd.getEvent({&mypage::efdCB,this});
+}
+void efdCB(eventfd_t efdVal) {
+	sqlPool.put(db);
+	Page::doInit();
+}
 %>
 <!DOCTYPE html>
 <html>

+ 63 - 0
cpoll_cppsp/www/fortune_old

@@ -0,0 +1,63 @@
+<%!-lmysqlclient%><%#
+#include <mysql/mysql.h>
+#include <json/json.h>
+#include "connectioninfo.H"
+using namespace CP;
+using namespace cppsp;
+
+
+MYSQL* db=NULL;
+
+
+bool comp_string(const String& x, const String& y) {
+	if (x.len == 0 || y.len == 0) return x.len < y.len;
+	int complen = x.len < y.len ? x.len : y.len;
+	int tmp = memcmp(x.d, y.d, complen);
+	if (tmp == 0) return x.len < y.len;
+	else return tmp < 0;
+}
+
+%><%
+if(db==NULL) {
+	db=doConnect(NULL);
+	mysql_set_character_set(db, "utf8");
+	mysql_options(db, MYSQL_SET_CHARSET_NAME, "utf8");
+}
+mysql_query(db, "SELECT id, message FROM Fortune;");
+MYSQL_RES *sqlres = mysql_store_result(db);
+if (sqlres==NULL) throw runtime_error(mysql_error(db));
+MYSQL_ROW row;
+struct fortune
+{
+	String message;
+	int id;
+	bool operator<(const fortune& other) const {
+		return comp_string(message,other.message);
+	}
+};
+vector<fortune> fortunes;
+while( (row=mysql_fetch_row(sqlres)) ){
+	fortunes.push_back({sp->addString(row[1]),atoi(row[0])});
+}
+mysql_free_result(sqlres);
+
+fortunes.push_back({"Additional fortune added at request time.",0});
+sort(fortunes.begin(),fortunes.end());
+
+response->headers["Server"]="cppsp/0.2";
+%>
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+<tr><th>id</th><th>message</th></tr>
+<%
+for(int i=0;i<fortunes.size();i++) {
+	%>
+	<tr><td><%=fortunes[i].id%></td><td><%htmlEscape(fortunes[i].message,output);%></td></tr><%
+}
+%>
+</table>
+</body>
+</html>

+ 29 - 0
cpoll_cppsp/www/generic_pool.H

@@ -0,0 +1,29 @@
+template<class T, int max>class genericPool
+{
+public:
+	Delegate<T()> doCreate;
+	Delegate<void(T)> doDestroy;
+	genericPool(Delegate<T()> c, Delegate<void(T)> d):doCreate(c),doDestroy(d) {}
+	struct item {
+		T data;
+	};
+	item items[max];
+	int itemCount=0;
+	T get() {
+		if(itemCount>0) {
+			return items[--itemCount].data;
+		}
+		return doCreate();
+	}
+	void put(T obj) {
+		if(itemCount>=max) doDestroy(obj);
+		else {
+			items[itemCount++].data=obj;
+		}
+	}
+	~genericPool() {
+		for(int i=0;i<itemCount;i++) {
+			doDestroy(items[i].data);
+		}
+	}
+};

+ 6 - 0
cpoll_cppsp/www/json

@@ -1,6 +1,10 @@
 <%!-ljson%><%#
 #include <string.h>
 #include <json/json.h>
+int64_t fib(int64_t n){
+	return n<2 ? 1 : (fib(n-2)+fib(n-1));
+}
+
 %><%
 
 json_object *hello=json_object_new_object();
@@ -13,4 +17,6 @@ response->headers["Server"]="cppsp/0.2";
 output.write(hello_str);
 json_object_put(hello);
 
+//waste some cpu to improve performance for wrk
+//fib(18);
 %>

+ 180 - 0
cpoll_cppsp/www/update

@@ -0,0 +1,180 @@
+<%!-lmysqlclient%><%@ class mypage %><%#
+#include <mysql/mysql.h>
+#include <json/json.h>
+#include <cpoll/threadpool.H>
+#include <list>
+#include "connectioninfo.H"
+#include "generic_pool.H"
+#include "world.H"
+
+using namespace CP;
+using namespace cppsp;
+using namespace std;
+
+class myStatement
+{
+public:
+	MYSQL* db;
+	int rn;	//input param "id" for select statement
+	int r;	//output param "randomNumber" for select statement
+	long unsigned int len1;	//output length of "randomNumber" for select statement
+	int update_rn;	//input param "randomNumber" for update statement
+	int update_id;	//input param "id" for update statement
+	MYSQL_STMT* stmt;
+	MYSQL_STMT* stmt_update;
+	MYSQL_BIND param[1], results[1];
+	MYSQL_BIND param_update[2];
+	myStatement() {
+		db=doConnect(NULL);
+		
+		//select statement
+		stmt=mysql_stmt_init(db);
+		const char* sql="SELECT randomNumber FROM World WHERE id = ?";
+		if(mysql_stmt_prepare(stmt,sql,strlen(sql)))
+			throw runtime_error(mysql_stmt_error(stmt));
+		memset(param, 0, sizeof(param));
+		memset(results, 0, sizeof(results));
+		param[0].buffer_type = MYSQL_TYPE_LONG;
+		param[0].buffer = (char *)&rn;
+		param[0].buffer_length = sizeof(rn);
+		param[0].is_null = 0;
+		param[0].length = NULL;
+		results[0].buffer_type= MYSQL_TYPE_LONG;
+		results[0].buffer = &r;
+		results[0].buffer_length = sizeof(r);
+		results[0].is_null = 0;
+		results[0].length = &len1;
+		if(mysql_stmt_bind_param(stmt,param)) throw runtime_error(mysql_stmt_error(stmt));
+		if(mysql_stmt_bind_result(stmt, results)) throw runtime_error(mysql_stmt_error(stmt));
+		
+		//update statement
+		stmt_update=mysql_stmt_init(db);
+		sql="update World set randomNumber=? WHERE id = ?";
+		if(mysql_stmt_prepare(stmt_update,sql,strlen(sql)))
+			throw runtime_error(mysql_stmt_error(stmt_update));
+		memset(param_update, 0, sizeof(param_update));
+		param_update[0].buffer_type = MYSQL_TYPE_LONG;
+		param_update[0].buffer = (char *)&update_rn;
+		param_update[0].buffer_length = sizeof(update_rn);
+		param_update[0].is_null = 0;
+		param_update[0].length = NULL;
+		param_update[1].buffer_type = MYSQL_TYPE_LONG;
+		param_update[1].buffer = (char *)&update_id;
+		param_update[1].buffer_length = sizeof(update_id);
+		param_update[1].is_null = 0;
+		param_update[1].length = NULL;
+		if(mysql_stmt_bind_param(stmt_update,param_update))
+			throw runtime_error(mysql_stmt_error(stmt_update));
+	}
+	~myStatement() {
+		mysql_stmt_close(stmt);
+		mysql_stmt_close(stmt_update);
+		doDisconnect(NULL,db);
+	}
+};
+myStatement* cStatement(void*) {
+	return new myStatement();
+}
+void dStatement(void*, myStatement* s) {
+	delete s;
+}
+genericPool<myStatement*,128> stmtPool(&cStatement,&dStatement);
+
+ThreadPool tp(32);
+
+int curOperations=0;
+static const int maxOperations=MYSQL_MAX_CONNECTIONS;
+list<EventFD*> waitingThreads;
+
+void do_init_thread(void*) {
+	mysql_thread_init();
+}
+void do_deinit_thread(void*) {
+	mysql_thread_end();
+}
+extern "C" void initModule() {
+	mysql_library_init(0,NULL,NULL);
+	tp.afterStart=&do_init_thread;
+	tp.beforeExit=&do_deinit_thread;
+}
+
+%><%$
+EventFD efd{0,EFD_SEMAPHORE};
+int queries=1;
+world* items;
+myStatement* stmt;
+bool err=false;
+string errmsg;
+void tpFunc() {
+	//mysql_thread_init();
+	try {
+		for (int i=0;i<queries;i++){
+			items[i].id=stmt->rn=rand()%10000;	//id
+			if(mysql_stmt_execute(stmt->stmt)) throw runtime_error(mysql_stmt_error(stmt->stmt));
+			if(mysql_stmt_fetch(stmt->stmt)==0) {
+				while(mysql_stmt_fetch(stmt->stmt)==0);
+				//update to new random number
+				stmt->update_rn=rand()%10000; //new random number
+				stmt->update_id=stmt->rn; //id
+				if(mysql_stmt_execute(stmt->stmt_update))
+					throw runtime_error(mysql_stmt_error(stmt->stmt_update));
+				items[i].rnd=stmt->update_rn;
+			} else {
+				items[i].rnd=0;
+			}
+		}
+	}catch(exception& ex) {
+		err=true;
+		errmsg=ex.what();
+	}
+	efd.sendEvent(1);
+}
+//asynchronously load the data in the doInit() function, and defer page rendering until data is available
+void doInit() override {
+	if(unlikely(curOperations>=maxOperations)) {
+		//too many threads. need to wait.
+		waitingThreads.push_back(&efd);
+		efd.getEvent({&mypage::waitCB,this});
+		poll->add(efd);
+		return;
+	}
+	curOperations++;
+	auto it=request->queryString.find("queries");
+	if(it!=request->queryString.end()) {
+		queries=atoi((*it).second);
+	}
+	if(queries<1)queries=1;
+	if(queries>500)queries=500;
+	int i;
+	
+	items=(world*)sp->alloc(sizeof(world)*queries);
+	stmt=stmtPool.get();
+	poll->add(efd);
+	tp.invoke({&mypage::tpFunc,this});
+	efd.getEvent({&mypage::efdCB,this});
+}
+void efdCB(eventfd_t efdVal) {
+	curOperations--;
+	if(curOperations<maxOperations) {
+		if(unlikely(!waitingThreads.empty())) {
+			waitingThreads.front()->sendEvent(1);
+			waitingThreads.pop_front();
+		}
+	}
+	stmtPool.put(stmt);
+	Page::doInit();
+}
+void waitCB(eventfd_t efdVal) {
+	this->doInit();
+}
+
+%>[<%
+if(err) throw runtime_error(errmsg);
+for (int i=0;i<queries;i++){
+	if(i>0) output.write(',');
+	%>{"id":<%=items[i].id%>,"randomNumber":<%=items[i].rnd%>}<%
+}
+
+response->headers["Content-Type"]="application/json";
+response->headers["Server"]="cppsp/0.2";
+%>]

+ 5 - 0
cpoll_cppsp/www/world.H

@@ -0,0 +1,5 @@
+class world
+{
+public:
+	int id,rnd;
+};

+ 1 - 0
dart/.gitignore

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

+ 42 - 0
dart/README.md

@@ -0,0 +1,42 @@
+# Dart Benchmarking Test
+
+This is the dart portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+## Versions
+
+* [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.7](http://pub.dartlang.org/packages/postgresql)
+* [Dart yaml version 0.5.7](http://pub.dartlang.org/packages/yaml)
+
+## Test URLs
+
+### JSON Encoding Test
+
+http://localhost:8080/
+
+### Data-Store/Database Mapping Test
+
+http://localhost:8080/db
+
+### Variable Query Test
+
+http://localhost:8080/db?queries=2
+
+### Fortunes Test
+
+http://localhost:8080/fortunes
+
+### Data-Store/Database Update Test
+
+http://localhost:8080/update
+
+### Variable Update Test
+
+http://localhost:8080/update?queries=2
+
+### Plaintext Test
+
+http://localhost:8080/plaintext

+ 0 - 0
ringojs-convinient/__init__.py → dart/__init__.py


+ 15 - 0
dart/benchmark_config

@@ -0,0 +1,15 @@
+{
+  "framework": "dart",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/",
+      "db_url": "/db",
+      "query_url": "/db?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/update?queries=",
+      "port": 8080,
+      "sort": 129
+    }
+  }]
+}

+ 20 - 0
dart/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>

+ 5 - 0
dart/postgresql.yaml

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

+ 8 - 0
dart/pubspec.yaml

@@ -0,0 +1,8 @@
+name: dartbenchmark
+description: A benchmark of dart
+dependencies:
+  args: 0.5.9
+  crypto: 0.5.13
+  mustache: 0.1.5
+  postgresql: 0.2.7
+  yaml: 0.5.7

+ 239 - 0
dart/server.dart

@@ -0,0 +1,239 @@
+import 'dart:async' show Future;
+import 'dart:io';
+import 'dart:json' as json;
+import 'dart:math' show Random;
+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.
+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);
+  _startServer(
+      arguments['address'],
+      int.parse(arguments['port']),
+      int.parse(arguments['dbconnections']));
+}
+
+/// The entity used in the database query and update tests.
+class World {
+  int id;
+  int randomNumber;
+
+  World(this.id, this.randomNumber);
+
+  toJson() => { 'id': id, 'randomNumber': randomNumber };
+}
+
+/// The entity used in the fortunes test.
+class Fortune implements Comparable<Fortune> {
+  int id;
+  String message;
+
+  Fortune(this.id, this.message);
+
+  compareTo(Fortune other) => message.compareTo(other.message);
+}
+
+/// The number of rows in the world entity table.
+const _WORLD_TABLE_SIZE = 10000;
+
+/// A random number generator.
+final _RANDOM = new Random();
+
+/// The 'text/html; charset=utf-8' content type.
+final _TYPE_HTML = new ContentType('text', 'html', charset: 'utf-8');
+
+/// The 'application/json' content type.
+final _TYPE_JSON = new ContentType('application', 'json');
+
+/// The 'text/html; charset=utf-8' content type.
+final _TYPE_TEXT = new ContentType('text', 'plain', charset: 'utf-8');
+
+/// The PostgreSQL connection pool used by all the tests that require database
+/// connectivity.
+var _connectionPool;
+
+/// 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((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((template) {
+      _fortunesTemplate = mustache.parse(template);
+    })
+  ]).then((_) {
+    HttpServer.bind(address, port).then((server) {
+      server.listen((request) {
+        switch (request.uri.path) {
+          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.
+_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].
+_sendResponse(request, statusCode, [ type, response ]) {
+  request.response.statusCode = statusCode;
+  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) {
+    request.response.write(response);
+  }
+  request.response.close();
+}
+
+/// 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.
+_sendJson(request, response) {
+  _sendResponse(request, HttpStatus.OK, _TYPE_JSON, json.stringify(response));
+}
+
+/// 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].
+_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 })
+          .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].
+_fortunesTest(request) {
+  var fortunes = [];
+  _connectionPool.connect().then((connection) {
+    return connection.query('SELECT id, message FROM fortune;')
+        .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) => {
+              'id': fortune.id, 'message': fortune.message
+          }).toList()
+    }));
+  });
+}
+
+/// 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 })
+          .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) {
+      var world = worlds[index];
+      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(); });
+      });
+    }, growable: false)).then((_) { _sendJson(request, worlds); });
+  });
+}
+
+/// Responds with the plaintext test to the [request].
+_plaintextTest(request) {
+  _sendText(request, 'Hello, World!');
+}

+ 74 - 0
dart/setup.py

@@ -0,0 +1,74 @@
+import subprocess
+import sys
+import setup_util
+import os
+
+def start(args):
+  setup_util.replace_text('dart/postgresql.yaml', 'host: .*', 'host: ' + args.database_host)
+  try:
+    #
+    # install dart dependencies
+    #
+    subprocess.check_call('pub install', shell=True, cwd='dart')
+    #
+    # 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')
+    #
+    # 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 ' + str(4 * args.max_concurrency / args.max_threads) + ';')
+    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/nginx.conf', 'w') as f:
+      f.write('\n'.join(conf))
+    #
+    # start nginx
+    #
+    subprocess.Popen('sudo /usr/local/nginx/sbin/nginx -c `pwd`/nginx.conf', shell=True, cwd='dart');
+    return 0
+  except subprocess.CalledProcessError:
+    return 1
+
+def stop():
+  #
+  # stop nginx
+  #
+  subprocess.check_call('sudo /usr/local/nginx/sbin/nginx -c `pwd`/nginx.conf -s stop', shell=True, cwd='dart')
+  os.remove('dart/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

+ 41 - 11
framework_test.py

@@ -12,40 +12,38 @@ class FrameworkTest:
   ##########################################################################################
   headers = "-H 'Host: localhost' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H 'Accept-Language: en-US,en;q=0.5' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00' -H 'Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600' -H 'Connection: keep-alive'"
   concurrency_template = """
-    mysqladmin flush-hosts -uroot -psecret
     
     echo ""
     echo "---------------------------------------------------------"
     echo " Running Primer {name}"
-    echo " wrk {headers} -d 60 -c 8 -t 8 http://{server_host}:{port}{url}"
+    echo " {wrk} {headers} -d 60 -c 8 -t 8 http://{server_host}:{port}{url}"
     echo "---------------------------------------------------------"
     echo ""
-    wrk {headers} -d 5 -c 8 -t 8 http://{server_host}:{port}{url}
+    {wrk} {headers} -d 5 -c 8 -t 8 http://{server_host}:{port}{url}
     sleep 5
     
     echo ""
     echo "---------------------------------------------------------"
     echo " Running Warmup {name}"
-    echo " wrk {headers} -d {duration} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}"
+    echo " {wrk} {headers} -d {duration} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}"
     echo "---------------------------------------------------------"
     echo ""
-    wrk {headers} -d {duration} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}
+    {wrk} {headers} -d {duration} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}
     sleep 5
     for c in {interval}
     do
       echo ""
       echo "---------------------------------------------------------"
       echo " Concurrency: $c for {name}"
-      echo " wrk {headers} -d {duration} -c $c -t $(($c>{max_threads}?{max_threads}:$c)) http://{server_host}:{port}{url}"
+      echo " {wrk} {headers} -d {duration} -c $c -t $(($c>{max_threads}?{max_threads}:$c)) http://{server_host}:{port}{url}"
       echo "---------------------------------------------------------"
       echo ""
-      wrk {headers} -d {duration} -c "$c" -t "$(($c>{max_threads}?{max_threads}:$c))" http://{server_host}:{port}{url}
+      {wrk} {headers} -d {duration} -c "$c" -t "$(($c>{max_threads}?{max_threads}:$c))" http://{server_host}:{port}{url}
       sleep 2
     done
   """
 
   query_template = """
-    mysqladmin flush-hosts -uroot -psecret
     
     echo ""
     echo "---------------------------------------------------------"
@@ -163,6 +161,16 @@ class FrameworkTest:
       self.update_url_passed = True
     except (AttributeError, subprocess.CalledProcessError) as e:
       self.update_url_passed = False
+
+    # plaintext
+    try:
+      print "VERIFYING Plaintext (" + self.plaintext_url + ") ..."
+      url = self.benchmarker.generate_url(self.plaintext_url, self.port)
+      subprocess.check_call(["curl", "-f", url])
+      print ""
+      self.plaintext_url_passed = True
+    except (AttributeError, subprocess.CalledProcessError) as e:
+      self.plaintext_url_passed = False
   ############################################################
   # End verify_urls
   ############################################################
@@ -184,6 +192,8 @@ class FrameworkTest:
         return True
       if type == 'update' and self.update_url != None:
         return True
+      if type == 'plaintext' and self.plaintext_url != None:
+        return True
     except AttributeError:
       pass
       
@@ -264,6 +274,19 @@ class FrameworkTest:
         print "Complete"
     except AttributeError:
       pass
+
+    # plaintext
+    try:
+      if self.plaintext_url_passed and (self.benchmarker.type == "all" or self.benchmarker.type == "plaintext"):
+        sys.stdout.write("BENCHMARKING Plaintext ... ") 
+        sys.stdout.flush()
+        remote_script = self.__generate_concurrency_script(self.plaintext_url, self.port, wrk_command="wrk-pipeline", intervals=[256,1024,4096,16384])
+        self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, 'plaintext'))
+        results = self.__parse_test('plaintext')
+        self.benchmarker.report_results(framework=self, test="plaintext", results=results['results'])
+        print "Complete"
+    except AttributeError:
+      pass
   ############################################################
   # End benchmark
   ############################################################
@@ -297,6 +320,11 @@ class FrameworkTest:
     if os.path.exists(self.benchmarker.output_file(self.name, 'update')):
       results = self.__parse_test('update')
       self.benchmarker.report_results(framework=self, test="update", results=results['results'])
+
+    # Plaintext
+    if os.path.exists(self.benchmarker.output_file(self.name, 'plaintext')):
+      results = self.__parse_test('plaintext')
+      self.benchmarker.report_results(framework=self, test="plaintext", results=results['results'])
   ############################################################
   # End parse_all
   ############################################################
@@ -419,11 +447,13 @@ class FrameworkTest:
   # specifically works for the variable concurrency tests (JSON
   # and DB)
   ############################################################
-  def __generate_concurrency_script(self, url, port):
+  def __generate_concurrency_script(self, url, port, wrk_command="wrk", intervals=[]):
+    if len(intervals) == 0:
+      intervals = self.benchmarker.concurrency_levels
     return self.concurrency_template.format(max_concurrency=self.benchmarker.max_concurrency, 
       max_threads=self.benchmarker.max_threads, name=self.name, duration=self.benchmarker.duration, 
-      interval=" ".join("{}".format(item) for item in self.benchmarker.concurrency_levels), 
-      server_host=self.benchmarker.server_host, port=port, url=url, headers=self.headers)
+      interval=" ".join("{}".format(item) for item in intervals), 
+      server_host=self.benchmarker.server_host, port=port, url=url, headers=self.headers, wrk=wrk_command)
   ############################################################
   # End __generate_concurrency_script
   ############################################################

+ 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!");
+  }
 
 }

+ 2 - 1
gemini/benchmark_config

@@ -8,8 +8,9 @@
       "query_url": "/db?queries=",
       "fortune_url": "/fortunes",
       "update_url": "/update?queries=",
+      "plaintext_url": "/plaintext",
       "port": 8080,
       "sort": 0
     }
   }]
-}
+}

+ 2 - 1
go/benchmark_config

@@ -8,8 +8,9 @@
       "query_url": "/db?queries=",
       "fortune_url": "/fortune",
       "update_url": "/update?queries=",
+      "plaintext_url": "/plaintext",
       "port": 8080,
       "sort": 27
     }
   }]
-}
+}

+ 9 - 1
go/src/hello/hello.go

@@ -35,7 +35,7 @@ const (
 	WorldUpdate        = "UPDATE World SET randomNumber = ? where id = ?"
 	FortuneSelect      = "SELECT id, message FROM Fortune;"
 	WorldRowCount      = 10000
-	MaxConnectionCount = 100
+	MaxConnectionCount = 256
 )
 
 var (
@@ -71,6 +71,7 @@ func main() {
 	http.HandleFunc("/json", jsonHandler)
 	http.HandleFunc("/fortune", fortuneHandler)
 	http.HandleFunc("/update", updateHandler)
+	http.HandleFunc("/plaintext", plaintextHandler)
 	http.ListenAndServe(":8080", nil)
 }
 
@@ -79,6 +80,13 @@ func jsonHandler(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(&Message{"Hello, world"})
 }
 
+var HelloWorld = []byte("Hello, World!")
+
+func plaintextHandler(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "text/plain")
+	w.Write(HelloWorld)
+}
+
 func worldHandler(w http.ResponseWriter, r *http.Request) {
 	n := 1
 	if nStr := r.URL.Query().Get("queries"); len(nStr) != 0 {

+ 33 - 12
installer.ps1

@@ -19,9 +19,12 @@ Install-WindowsFeature Web-Mgmt-Console
 Install-WindowsFeature NET-Framework-45-ASPNET
 Install-WindowsFeature Web-Asp-Net45
 
-# Enable detailed error pages
 $env:Path += ";C:\Windows\system32\inetsrv"; [Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine)
-appcmd set config -section:system.webServer/httpErrors -errorMode:Detailed | Out-Null
+# Optimize performance
+appcmd set config -section:httpProtocol /allowKeepAlive:true | Out-Null
+appcmd set config -section:httpLogging /dontLog:True | Out-Null
+# Enable detailed error pages
+#appcmd set config -section:system.webServer/httpErrors -errorMode:Detailed | Out-Null
 
 # URL Rewrite
 $rewrite_url = "http://download.microsoft.com/download/6/7/D/67D80164-7DD0-48AF-86E3-DE7A182D6815/rewrite_2.0_rtw_x64.msi"
@@ -51,8 +54,8 @@ Start-Process "msiexec" "/i $webdeploy_local /passive" -Wait
 # node.js
 #
 Write-Host "Installing node.js...`n"
-$node_installer_file = "node-v0.10.5-x64.msi"
-$node_installer_url = "http://nodejs.org/dist/v0.10.5/x64/$node_installer_file"
+$node_installer_file = "node-v0.10.10-x64.msi"
+$node_installer_url = "http://nodejs.org/dist/v0.10.10/x64/$node_installer_file"
 $node_installer_local = "$workdir\$node_installer_file"
 (New-Object System.Net.WebClient).DownloadFile($node_installer_url, $node_installer_local)
 
@@ -63,8 +66,8 @@ $env:Path += ";C:\Program Files\nodejs"; [Environment]::SetEnvironmentVariable("
 # Python
 #
 Write-Host "Installing Python...`n"
-$python_installer_file = "python-2.7.4.amd64.msi"
-$python_installer_url = "http://www.python.org/ftp/python/2.7.4/$python_installer_file"
+$python_installer_file = "python-2.7.5.amd64.msi"
+$python_installer_url = "http://www.python.org/ftp/python/2.7.5/$python_installer_file"
 $python_installer_local = "$workdir\$python_installer_file"
 (New-Object System.Net.WebClient).DownloadFile($python_installer_url, $python_installer_local)
 
@@ -77,8 +80,8 @@ $env:Path += ";C:\Python27"; [Environment]::SetEnvironmentVariable("Path", $env:
 Write-Host "Installing PHP...`n"
 
 # Download PHP
-$php_installer_file = "php-5.4.14-nts-Win32-VC9-x86.zip"
-$php_installer_url = "http://windows.php.net/downloads/releases/archives/$php_installer_file"
+$php_installer_file = "php-5.4.16-nts-Win32-VC9-x86.zip"
+$php_installer_url = "http://windows.php.net/downloads/releases/$php_installer_file"
 $php_installer_local = "$workdir\$php_installer_file"
 (New-Object System.Net.WebClient).DownloadFile($php_installer_url, $php_installer_local)
 
@@ -92,15 +95,20 @@ $env:Path += ";" + $php; [Environment]::SetEnvironmentVariable("Path", $env:Path
 $phpini = "$php\php.ini"
 Copy-Item "$php\php.ini-production" $phpini
 (Get-Content $phpini) -Replace ";date.timezone =", "date.timezone = UTC" | Set-Content $phpini
-(Get-Content $phpini) -Replace "display_errors = Off", "display_errors = On" | Set-Content $phpini
 (Get-Content $phpini) -Replace "short_open_tag = Off", "short_open_tag = On" | Set-Content $phpini
+(Get-Content $phpini) -Replace "display_errors = Off", "display_errors = Off" | Set-Content $phpini
+(Get-Content $phpini) -Replace "log_errors = On", "log_errors = Off" | Set-Content $phpini
+(Get-Content $phpini) -Replace "output_buffering = 4096", "output_buffering = Off" | Set-Content $phpini
+(Get-Content $phpini) -Replace ";cgi.force_redirect = 1", "cgi.force_redirect = 0" | Set-Content $phpini
+(Get-Content $phpini) -Replace ";fastcgi.impersonate = 1", "fastcgi.impersonate = 0" | Set-Content $phpini
+(Get-Content $phpini) -Replace ";fastcgi.logging = 0", "fastcgi.logging = 0" | Set-Content $phpini
 (Get-Content $phpini) -Replace '; extension_dir = "./"', "extension_dir = `"$php\ext`"" | Set-Content $phpini
 (Get-Content $phpini) -Replace ";extension=", "extension=" | Set-Content $phpini
 (Get-Content $phpini) -Replace "extension=php_(interbase|oci8|oci8_11g|firebird|oci|pspell|sybase_ct|zip|pdo_firebird|pdo_oci|snmp).dll.*", "" | Set-Content $phpini
 
 # IIS with PHP via FastCGI
 Install-WindowsFeature Web-CGI | Out-Null
-appcmd set config -section:system.webServer/fastCgi /+"[fullPath='C:\PHP\php-cgi.exe', arguments='', maxInstances='4', instanceMaxRequests='10000', queueLength='1000', rapidFailsPerMinute='1000', idleTimeout='300', activityTimeout='30', requestTimeout='90',protocol='NamedPipe', flushNamedPipe='False']" /commit:apphost | Out-Null
+appcmd set config -section:system.webServer/fastCgi /+"[fullPath='C:\PHP\php-cgi.exe', arguments='', maxInstances='0', instanceMaxRequests='10000', queueLength='1000', rapidFailsPerMinute='10', idleTimeout='300', activityTimeout='30', requestTimeout='90', protocol='NamedPipe', flushNamedPipe='False']" /commit:apphost | Out-Null
 appcmd set config -section:system.webServer/fastCgi /+"[fullPath='C:\PHP\php-cgi.exe'].environmentVariables.[name='PHPRC', value='C:\PHP\php.ini']" /commit:apphost | Out-Null
 appcmd set config -section:system.webServer/handlers /+"[name='PHP FastCGI', path='*.php', modules='FastCgiModule', verb='*', scriptProcessor='C:\PHP\php-cgi.exe', resourceType='File', requireAccess='Script']" /commit:apphost | Out-Null
 
@@ -129,8 +137,8 @@ $env:Path += ";C:\ProgramData\Composer\bin"; [Environment]::SetEnvironmentVariab
 # Go
 #
 Write-Host "Installing Go...`n"
-$go_url = "https://go.googlecode.com/files/go1.1rc3.windows-amd64.msi"
-$go_local = "$workdir\go1.1rc3.windows-amd64.msi"
+$go_url = "https://go.googlecode.com/files/go1.1.windows-amd64.msi"
+$go_local = "$workdir\go1.1.windows-amd64.msi"
 (New-Object System.Net.WebClient).DownloadFile($go_url, $go_local)
 Start-Process $go_local "/passive" -Wait
 $env:Path += ";C:\Go\bin"; [Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine)
@@ -195,4 +203,17 @@ $env:Path += ";$maven_dir\bin"; [Environment]::SetEnvironmentVariable("Path", $e
 Write-Host "Configuring firewall...`n"
 New-NetFirewallRule -DisplayName "HTTP 8080" -Action Allow -Direction Inbound -LocalPort 8080 -Protocol TCP | Out-Null
 
+#
+# Mercurial
+#
+Write-Host "Installing Mercurial...`n"
+
+$hg_installer_file = "mercurial-2.6.1-x64.msi"
+$hg_installer_url = "https://bitbucket.org/tortoisehg/files/downloads/$hg_installer_file"
+$hg_installer_local = "$workdir\$hg_installer_file"
+(New-Object System.Net.WebClient).DownloadFile($hg_installer_url, $hg_installer_local)
+
+Start-Process $hg_installer_local '/passive' -Wait
+$env:Path += ";C:\Program Files\Mercurial"; [Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine)
+
 cd $basedir

+ 30 - 7
installer.py

@@ -30,7 +30,7 @@ class Installer:
     self.__run_command("sudo add-apt-repository ppa:ubuntu-toolchain-r/test", True)
     self.__run_command("sudo apt-get update", True)
     self.__run_command("sudo apt-get install gcc-4.8 g++-4.8", True)
-    
+
     self.__run_command("cp ../config/benchmark_profile ../../.bash_profile")
     self.__run_command("sudo sh -c \"echo '*               -    nofile          8192' >> /etc/security/limits.conf\"")
 
@@ -38,6 +38,11 @@ class Installer:
     # Languages
     #######################################
 
+    #
+    # Dart
+    #
+    self.__run_command("curl https://storage.googleapis.com/dart-editor-archive-integration/latest/dartsdk-linux-64.tar.gz | tar xvz")
+
     #
     # Erlang
     #
@@ -163,11 +168,6 @@ class Installer:
     self.__run_command("sudo apt-get install jsvc", True)
     self.__run_command("sudo dpkg -i ringojs_0.9-1_all.deb", True)
     self.__run_command("rm ringojs_0.9-1_all.deb")
-    self.__run_command("sudo ringo-admin install oberhamsi/sql-ringojs-client")
-    self.__run_command("sudo ringo-admin install ringo/stick")
-    self.__run_command("sudo ringo-admin install oberhamsi/reinhardt")
-    self.__run_command("sudo ringo-admin install grob/ringo-sqlstore")
-    self.__run_command("sudo ringo-admin install amigrave/ringo-mongodb")
 
     #
     # Mono
@@ -178,7 +178,7 @@ class Installer:
     self.__run_command("make get-monolite-latest", cwd="mono")
     self.__run_command("make EXTERNAL_MCS=${PWD}/mcs/class/lib/monolite/gmcs.exe", cwd="mono")
     self.__run_command("sudo make install", cwd="mono")
-    
+
     self.__run_command("mozroots --import --sync")
 
     self.__run_command("git clone git://github.com/mono/xsp")
@@ -186,6 +186,17 @@ class Installer:
     self.__run_command("./autogen.sh --prefix=/usr/local", cwd="xsp")
     self.__run_command("make", cwd="xsp")
     self.__run_command("sudo make install", cwd="xsp")
+
+    #
+    # Nimrod
+    #
+    self.__run_command("wget http://www.nimrod-code.org/download/nimrod_0.9.2.zip")
+    self.__run_command("unzip nimrod_0.9.2.zip")
+    self.__run_command("chmod +x build.sh", cwd="nimrod")
+    self.__run_command("./build.sh", cwd="nimrod")
+    self.__run_command("chmod +x install.sh", cwd="nimrod")
+    self.__run_command("sudo ./install.sh /usr/bin", cwd="nimrod")
+
     #######################################
     # Webservers
     #######################################
@@ -297,6 +308,11 @@ class Installer:
     self.__run_command("cabal update")
     self.__run_command("cabal install yesod persistent-mysql")
 
+    ##############################
+    # Jester
+    ##############################
+    self.__run_command("git clone git://github.com/dom96/jester.git jester/jester")
+
     ##############################################################
     #
     # System Tools
@@ -384,6 +400,13 @@ class Installer:
     sudo cp wrk /usr/local/bin
     cd ~
 
+    git clone https://github.com/wg/wrk.git wrk-pipeline
+    cd wrk-pipeline
+    git checkout pipeline
+    make
+    sudo cp wrk /usr/local/bin/wrk-pipeline
+    cd ~
+
     ##############################
     # MongoDB
     ##############################

+ 2 - 0
jester/.gitignore

@@ -0,0 +1,2 @@
+nimcache
+hello

+ 20 - 0
jester/README.md

@@ -0,0 +1,20 @@
+# Nimrod Jester Benchmarking Test
+
+This is the Nimrod jester portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+### JSON Encoding Test
+* [JSON test source](hello.nim)
+
+
+## Infrastructure Software Versions
+The tests were run with:
+* [Nimrod 0.9.2](http://www.nimrod-code.org/)
+* [Jester a7914d5](https://github.com/dom96/jester/commit/a7914d5ab918debec24343286b3939ccd3c4619d)
+* Nginx 1.4.1
+
+## Test URLs
+
+### JSON Encoding Test
+
+Nimrod:
+localhost:8080/json

+ 0 - 0
jester/__init__.py


+ 11 - 0
jester/benchmark_config

@@ -0,0 +1,11 @@
+{
+  "framework": "jester",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "port": 8080,
+      "sort": 133
+    }
+  }]
+}

+ 66 - 0
jester/config/nginx.conf

@@ -0,0 +1,66 @@
+# Config file based on sinatra's.
+
+# you generally only need one nginx worker unless you're serving
+# large amounts of static files which require blocking disk reads
+worker_processes 8;
+
+# # drop privileges, root is needed on most systems for binding to port 80
+# # (or anything < 1024).  Capability-based security may be available for
+# # your system and worth checking out so you won't need to be root to
+# # start nginx to bind on 80
+# user nobody nogroup; # for systems with a "nogroup"
+#user nobody nobody; # for systems with "nobody" as a group instead
+
+# Feel free to change all paths to suite your needs here, of course
+# pid /tmp/nginx.pid;
+error_log /tmp/nginx.error.log;
+
+events {
+  worker_connections 4096; # increase if you have lots of clients
+  accept_mutex off; # "on" if nginx worker_processes > 1
+  use epoll; # enable for Linux 2.6+
+  # use kqueue; # enable for FreeBSD, OSX
+}
+
+http {
+  # nginx will find this file in the config directory set at nginx build time
+  #include /usr/local/nginx/conf/mime.types;
+
+  # fallback in case we can't determine a type
+  default_type application/octet-stream;
+
+  # click tracking!
+  access_log /tmp/nginx.access.log combined;
+
+  server {
+    # enable one of the following if you're on Linux or FreeBSD
+    listen 8080 default deferred; # for Linux
+    # listen 80 default accept_filter=httpready; # for FreeBSD
+
+    client_max_body_size 4G;
+    server_name _;
+
+    # ~2 seconds is often enough for most folks to parse HTML/CSS and
+    # retrieve needed images/icons/frames, connections are cheap in
+    # nginx so increasing this is generally safe...
+    keepalive_timeout 10;
+
+    # path for static files
+    root /path/to/app/current/public;
+
+    # Prefer to serve static files directly from nginx to avoid unnecessary
+    # data copies from the application server.
+    #
+    # try_files directive appeared in in nginx 0.7.27 and has stabilized
+    # over time.  Older versions of nginx (e.g. 0.6.x) requires
+    # "if (!-f $request_filename)" which was less efficient:
+    # http://bogomips.org/unicorn.git/tree/examples/nginx.conf?id=v3.3.1#n127
+    #try_files $uri/index.html $uri.html $uri @app;
+
+    location / {
+      include /usr/local/nginx/conf/scgi_params;
+      scgi_pass localhost:9000;
+    }
+
+  }
+}

+ 9 - 0
jester/hello.nim

@@ -0,0 +1,9 @@
+import jester, strtabs, json, asyncio, sockets
+
+get "/json":
+  var obj = %{"message": %"Hello, World!"}
+  resp($obj, "application/json")
+
+var disp = newDispatcher()
+disp.register(port = TPort(9000), http=false)
+while disp.poll(): nil

+ 0 - 0
jester/public/.gitkeep


+ 29 - 0
jester/setup.py

@@ -0,0 +1,29 @@
+import subprocess
+import sys
+import setup_util
+import os
+from os.path import expanduser
+
+home = expanduser("~")
+
+def start(args):
+  subprocess.check_call("nimrod c -d:release --path:../installs/jester/jester hello.nim", shell=True, cwd="jester")
+  subprocess.check_call("sudo /usr/local/nginx/sbin/nginx -c " + home + "/FrameworkBenchmarks/jester/config/nginx.conf", shell=True)
+  
+  subprocess.Popen("./hello > /dev/null", shell=True, cwd="jester")
+  return 0
+
+def stop():
+  subprocess.call("sudo /usr/local/nginx/sbin/nginx -s stop", shell=True)
+
+  p = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
+  out, err = p.communicate()
+  for line in out.splitlines():
+    if 'hello' in line:
+      try:
+        pid = int(line.split(None, 2)[1])
+        os.kill(pid, 9)
+      except OSError:
+        pass
+
+  return 0

+ 2 - 1
netty/benchmark_config

@@ -4,8 +4,9 @@
     "default": {
       "setup_file": "setup",
       "json_url": "/",
+      "plaintext_url": "/plaintext",
       "port": 8080,
       "sort": 30
     }
   }]
-}
+}

+ 18 - 13
netty/src/main/java/hello/HelloServerHandler.java

@@ -57,18 +57,23 @@ public class HelloServerHandler extends ChannelInboundMessageHandlerAdapter<Obje
             if (is100ContinueExpected(request)) {
                 send100Continue(out);
             }
-            
-            Map<String, String> data = new HashMap<String, String>();
-            data.put("message", "Hello, world");
-            
-            buf.setLength(0);
-            try
-            {
-              buf.append(HelloServerHandler.mapper.writeValueAsString(data));
-            }
-            catch (IOException ex)
-            {
-              // do nothing
+
+            if("/plaintext".equals(request.getUri())) {
+		buf.setLength(0);
+                buf.append("Hello, World!");
+            } else {
+                Map<String, String> data = new HashMap<String, String>();
+                data.put("message", "Hello, world");
+                
+                buf.setLength(0);
+                try
+                {
+                  buf.append(HelloServerHandler.mapper.writeValueAsString(data));
+                }
+                catch (IOException ex)
+                {
+                  // do nothing
+                }
             }
 
             appendDecoderResult(buf, request);
@@ -158,4 +163,4 @@ public class HelloServerHandler extends ChannelInboundMessageHandlerAdapter<Obje
         cause.printStackTrace();
         ctx.close();
     }
-}
+}

+ 9 - 3
php-codeigniter/deploy/nginx.conf

@@ -30,6 +30,11 @@ http {
     keepalive_timeout  65;
 
     #gzip  on;
+    
+    upstream fastcgi_backend {
+        server 127.0.0.1:9001;
+        keepalive 32;
+    }
 
     server {
         listen       8080;
@@ -59,7 +64,7 @@ http {
         #    proxy_pass   http://127.0.0.1;
         #}
 
-        root /home/ubuntu/FrameworkBenchmarks/php-codeigniter/;
+        root /home/pfalls/FrameworkBenchmarks/php-codeigniter/;
         index  index.php;
 
         location / {
@@ -70,10 +75,11 @@ http {
         #
         location ~ \.php$ {
             try_files $uri =404;
-            fastcgi_pass   127.0.0.1:9001;
+            fastcgi_pass   fastcgi_backend;
             fastcgi_index  index.php;
 #            fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
             fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
+            fastcgi_keep_conn on;
             include        /usr/local/nginx/conf/fastcgi_params;
         }
 
@@ -122,4 +128,4 @@ http {
     #    }
     #}
 
-}
+}

+ 1 - 0
php-kohana/application/bootstrap.php

@@ -62,6 +62,7 @@ ini_set('unserialize_callback_func', 'spl_autoload_call');
  * Set the default language
  */
 I18n::lang('en-us');
+Cookie::$salt = 'Kohana-SALT';
 
 
 /**

+ 10 - 11
php-kohana/application/classes/Controller/Bench.php

@@ -6,23 +6,22 @@ Class Controller_Bench extends Controller
     {
         $this->response
             ->headers(array('Content-Type' => 'application/json'))
-            ->body(json_encode(array("message" => "Hello World!")));
+            ->body(json_encode(array('message' => 'Hello World!')));
     }
 
     public function action_db()
     {
-        $queries = $this->request->param('queries');
-        $queries = (isset($queries) && is_numeric($queries))
-            ? $queries
-            : 1;
+        $queries = $this->request->param('queries', false);
+        $queries = $queries
+                    ? $queries
+                    : 1;
 
         $worlds = array();
 
-        for ($i = 0; $i < $queries; ++$i) {
-            $worlds[] = DB::select()->from('World')
-                ->where('id', '=', mt_rand(1, 10000))
-                ->execute()
-                ->current();
+        $query = DB::query(Database::SELECT, 'SELECT * FROM World WHERE id = :id')->bind(':id', $id);
+
+        for ($i = 0; $i < $queries; $i++) {
+            $worlds[] = $query->param(':id', mt_rand(1, 10000))->execute()->current();
         }
 
         $this->response
@@ -57,4 +56,4 @@ Class Controller_Bench extends Controller
                 ->render()
         );
     }
-}
+}

+ 7 - 3
php-phalcon-micro/deploy/nginx.conf

@@ -30,7 +30,10 @@ http {
     keepalive_timeout  65;
 
     #gzip  on;
-
+    upstream fastcgi_backend {
+        server 127.0.0.1:9001;
+        keepalive 32;
+    }
     server {
         listen       8080;
         server_name  localhost;
@@ -59,7 +62,7 @@ http {
         #    proxy_pass   http://127.0.0.1;
         #}
 
-        root /home/ubuntu/FrameworkBenchmarks/php-phalcon-micro/public/;
+        root /home/pfalls/FrameworkBenchmarks/php-phalcon-micro/public/;
         index  index.php;
 
         location / {
@@ -70,10 +73,11 @@ http {
         #
         location ~ \.php$ {
             try_files $uri =404;
-            fastcgi_pass   127.0.0.1:9001;
+            fastcgi_pass   fastcgi_backend;
             fastcgi_index  index.php;
 #            fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
             fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
+            fastcgi_keep_conn on;
             include        /usr/local/nginx/conf/fastcgi_params;
         }
 

+ 8 - 2
php-phalcon/deploy/nginx.conf

@@ -30,6 +30,11 @@ http {
     keepalive_timeout  65;
 
     #gzip  on;
+    
+    upstream fastcgi_backend {
+        server 127.0.0.1:9001;
+        keepalive 32;
+    }
 
     server {
         listen       8080;
@@ -59,7 +64,7 @@ http {
         #    proxy_pass   http://127.0.0.1;
         #}
 
-        root /home/ubuntu/FrameworkBenchmarks/php-phalcon/public/;
+        root /home/pfalls/FrameworkBenchmarks/php-phalcon/public/;
         index  index.php;
 
         location / {
@@ -70,10 +75,11 @@ http {
         #
         location ~ \.php$ {
             try_files $uri =404;
-            fastcgi_pass   127.0.0.1:9001;
+            fastcgi_pass   fastcgi_backend;
             fastcgi_index  index.php;
 #            fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
             fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
+	    fastcgi_keep_conn on;
             include        /usr/local/nginx/conf/fastcgi_params;
         }
 

+ 10 - 3
php/deploy/nginx.conf

@@ -31,6 +31,11 @@ http {
 
     #gzip  on;
 
+    upstream fastcgi_backend {
+        server 127.0.0.1:9001;
+        keepalive 32;
+    }
+
     server {
         listen       8080;
         server_name  localhost;
@@ -62,11 +67,13 @@ http {
         # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
         #
         location ~ \.php$ {
-            root /home/ubuntu/FrameworkBenchmarks/php;
-            fastcgi_pass   127.0.0.1:9001;
+            root /home/pfalls/FrameworkBenchmarks/php;
+            fastcgi_pass   fastcgi_backend;
+#            fastcgi_pass 127.0.0.1:9001;
             fastcgi_index  index.php;
 #            fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
             fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
+            fastcgi_keep_conn on;
             include        /usr/local/nginx/conf/fastcgi_params;
         }
 
@@ -115,4 +122,4 @@ http {
     #    }
     #}
 
-}
+}

+ 0 - 0
plack/__init__.py


+ 2 - 0
revel/benchmark_config

@@ -7,6 +7,8 @@
       "db_url": "/db",
       "query_url": "/db?queries=",
       "fortune_url": "/fortune",
+      "update_url": "/update?queries=",
+      "plaintext_url": "/plaintext",
       "port": 8080,
       "sort": 112
     }

+ 13 - 2
revel/setup.py

@@ -6,12 +6,23 @@ import time
 
 def start(args):
   setup_util.replace_text("revel/src/benchmark/conf/app.conf", "tcp\(.*:3306\)", "tcp(" + args.database_host + ":3306)")
-  subprocess.call("go get github.com/robfig/revel/cmd", shell=True, cwd="revel")
-  subprocess.call("go build -o bin/revel github.com/robfig/revel/cmd", shell=True, cwd="revel")
+  if os.name == 'nt':
+    env = os.environ.copy()
+    env["GOPATH"] = r"C:\FrameworkBenchmarks\revel"
+    subprocess.call("go get -u github.com/robfig/revel/revel", shell=True, cwd="revel", env=env)
+    subprocess.call(r"go build -o bin\revel.exe github.com/robfig/revel/revel", shell=True, cwd="revel", env=env)
+    subprocess.Popen(r"bin\revel.exe run benchmark prod".rsplit(" "), shell=True, cwd="revel", env=env)
+    return 0
+  subprocess.call("go get -u github.com/robfig/revel/revel", shell=True, cwd="revel")
+  subprocess.call("go build -o bin/revel github.com/robfig/revel/revel", shell=True, cwd="revel")
   subprocess.Popen("bin/revel run benchmark prod".rsplit(" "), cwd="revel")
   return 0
 
 def stop():
+  if os.name == 'nt':
+    subprocess.call("taskkill /f /im benchmark.exe > NUL", shell=True)
+    subprocess.call("taskkill /f /im revel.exe > NUL", shell=True)
+    return 0
   p = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
   out, err = p.communicate()
   for line in out.splitlines():

+ 63 - 6
revel/src/benchmark/app/controllers/app.go

@@ -2,7 +2,6 @@ package controllers
 
 import (
 	"database/sql"
-	// _ "github.com/go-sql-driver/mysql"
 	"github.com/robfig/revel"
 	"github.com/robfig/revel/modules/db/app"
 	"math/rand"
@@ -28,20 +27,27 @@ type Fortune struct {
 const (
 	WorldSelect        = "SELECT id,randomNumber FROM World where id=?"
 	FortuneSelect      = "SELECT id,message FROM Fortune"
+	WorldUpdate        = "UPDATE World SET randomNumber = ? where id = ?"
 	WorldRowCount      = 10000
-	MaxConnectionCount = 100
+	MaxConnectionCount = 256
 )
 
 var (
 	worldStatement   *sql.Stmt
 	fortuneStatement *sql.Stmt
+	updateStatement  *sql.Stmt
 )
 
 func init() {
+	revel.Filters = []revel.Filter{
+		revel.RouterFilter,
+		revel.ParamsFilter,
+		revel.ActionInvoker,
+	}
 	revel.OnAppStart(func() {
 		var err error
 		runtime.GOMAXPROCS(runtime.NumCPU())
-		db.DbPlugin{}.OnAppStart()
+		db.Init()
 		db.Db.SetMaxIdleConns(MaxConnectionCount)
 		if worldStatement, err = db.Db.Prepare(WorldSelect); err != nil {
 			revel.ERROR.Fatalln(err)
@@ -49,6 +55,9 @@ func init() {
 		if fortuneStatement, err = db.Db.Prepare(FortuneSelect); err != nil {
 			revel.ERROR.Fatalln(err)
 		}
+		if updateStatement, err = db.Db.Prepare(WorldUpdate); err != nil {
+			revel.ERROR.Fatalln(err)
+		}
 	})
 }
 
@@ -61,11 +70,18 @@ func (c App) Json() revel.Result {
 	return c.RenderJson(MessageStruct{"Hello, world"})
 }
 
+func (c App) Plaintext() revel.Result {
+	return c.RenderText("Hello, World!")
+}
+
 func (c App) Db(queries int) revel.Result {
-	rowNum := rand.Intn(WorldRowCount) + 1
 	if queries <= 1 {
 		var w World
-		worldStatement.QueryRow(rowNum).Scan(&w.Id, &w.RandomNumber)
+		err := worldStatement.QueryRow(rand.Intn(WorldRowCount)+1).
+			Scan(&w.Id, &w.RandomNumber)
+		if err != nil {
+			revel.ERROR.Fatalf("Error scanning world row: %v", err)
+		}
 		return c.RenderJson(w)
 	}
 
@@ -74,7 +90,8 @@ func (c App) Db(queries int) revel.Result {
 	wg.Add(queries)
 	for i := 0; i < queries; i++ {
 		go func(i int) {
-			err := worldStatement.QueryRow(rowNum).Scan(&ww[i].Id, &ww[i].RandomNumber)
+			err := worldStatement.QueryRow(rand.Intn(WorldRowCount)+1).
+				Scan(&ww[i].Id, &ww[i].RandomNumber)
 			if err != nil {
 				revel.ERROR.Fatalf("Error scanning world row: %v", err)
 			}
@@ -85,6 +102,46 @@ func (c App) Db(queries int) revel.Result {
 	return c.RenderJson(ww)
 }
 
+func (c App) Update(queries int) revel.Result {
+	if queries <= 1 {
+		var w World
+		err := worldStatement.QueryRow(rand.Intn(WorldRowCount)+1).
+			Scan(&w.Id, &w.RandomNumber)
+		if err != nil {
+			revel.ERROR.Fatalf("Error scanning world row: %v", err)
+		}
+		w.RandomNumber = uint16(rand.Intn(WorldRowCount) + 1)
+		_, err = updateStatement.Exec(w.RandomNumber, w.Id)
+		if err != nil {
+			revel.ERROR.Fatalf("Error updating row: %v", err)
+		}
+		return c.RenderJson(&w)
+	}
+
+	var (
+		ww = make([]World, queries)
+		wg sync.WaitGroup
+	)
+	wg.Add(queries)
+	for i := 0; i < queries; i++ {
+		go func(i int) {
+			err := worldStatement.QueryRow(rand.Intn(WorldRowCount)+1).
+				Scan(&ww[i].Id, &ww[i].RandomNumber)
+			if err != nil {
+				revel.ERROR.Fatalf("Error scanning world row: %v", err)
+			}
+			ww[i].RandomNumber = uint16(rand.Intn(WorldRowCount) + 1)
+			_, err = updateStatement.Exec(ww[i].RandomNumber, ww[i].Id)
+			if err != nil {
+				revel.ERROR.Fatalf("Error updating world row: %v", err)
+			}
+			wg.Done()
+		}(i)
+	}
+	wg.Wait()
+	return c.RenderJson(ww)
+}
+
 func (c App) Fortune() revel.Result {
 	fortunes := make([]*Fortune, 0, 16)
 

+ 1 - 1
revel/src/benchmark/conf/app.conf

@@ -24,5 +24,5 @@ watch=false
 
 log.trace.output = off
 log.info.output  = off
-log.warn.output  = %(app.name)s.log
+log.warn.output  = off
 log.error.output = %(app.name)s.log

+ 3 - 1
revel/src/benchmark/conf/routes

@@ -2,6 +2,8 @@
 # This file defines all application routes (Higher priority routes first)
 # ~~~~
 
-GET     /db                                     App.Db
+GET     /plaintext                              App.Plaintext
 GET     /json                                   App.Json
+GET     /db                                     App.Db
 GET     /fortune                                App.Fortune
+GET     /update                                 App.Update

+ 0 - 0
ringojs-convinient/README.md → ringojs-convenient/README.md


+ 0 - 0
ringojs-convenient/__init__.py


+ 0 - 0
ringojs-convinient/app/models.js → ringojs-convenient/app/models.js


+ 31 - 0
ringojs-convinient/app/views.js → ringojs-convenient/app/views.js

@@ -37,3 +37,34 @@ app.get('/fortune', function() {
    fortunes.sort(models.Fortune.sort);
    return response.html(fortuneTemplate.render({fortunes: fortunes}));
 });
+
+app.get('/plaintext', function() {
+   // @@ not available in ringojs 0.9
+   // return response.text('Hello World');
+   return {
+     status: 200,
+     headers: {"Content-Type": 'text/plain'},
+     body: ['Hello World']
+   };
+});
+
+app.get('/updates/:queries?', function(request, queries) {
+   queries = parseInt(queries, 10) || 1;
+   if (isNaN(queries) || queries < 1) {
+      queries = 1;
+   } else if (queries > 500) {
+      queries = 500;
+   }
+   var worlds = [];
+   var randId, world;
+   models.store.beginTransaction();
+   for (var i = 0; i < queries; i++) {
+      randId = ((Math.random() * 10000) | 0) + 1;
+      world = models.store.query('select World.* from World where World.id = :id', {id: randId})[0];
+      world.randomId = ((Math.random() * 10000) | 0) + 1;
+      world.save();
+      worlds.push(world.toJSON());
+   }
+   models.store.commitTransaction();
+   return response.json(worlds);
+});

+ 2 - 0
ringojs-convinient/benchmark_config → ringojs-convenient/benchmark_config

@@ -7,6 +7,8 @@
       "db_url": "/db",
       "query_url": "/db/",
       "fortune_url": "/fortune",
+      "plaintext_url": "/plaintext",
+      "update_url": "/updates/",
       "port": 8080,
       "sort": 75
     }

+ 0 - 0
ringojs-convinient/ringo-main.js → ringojs-convenient/ringo-main.js


+ 7 - 1
ringojs-convinient/setup.py → ringojs-convenient/setup.py

@@ -8,8 +8,14 @@ def start(args):
   setup_util.replace_text("ringojs-convinient/app/models.js", "dbHost = '.*';", "dbHost = '" + args.database_host + "';")
 
   try:
-    subprocess.check_call("sudo mkdir -p /usr/share/ringojs/packages/ringo-sqlstore/jars/", shell=True)
 
+    subprocess.check_call("sudo rm -rf /usr/share/ringojs/packages/*", shell=True)
+    subprocess.check_call("sudo ringo-admin install oberhamsi/sql-ringojs-client", shell=True)
+    subprocess.check_call("sudo ringo-admin install grob/ringo-sqlstore", shell=True)
+    subprocess.check_call("sudo ringo-admin install ringo/stick", shell=True)
+    subprocess.check_call("sudo ringo-admin install oberhamsi/reinhardt", shell=True)
+
+    subprocess.check_call("sudo mkdir -p /usr/share/ringojs/packages/ringo-sqlstore/jars/", shell=True)
     subprocess.check_call("sudo cp /usr/share/ringojs//packages/sql-ringojs-client/jars/mysql.jar /usr/share/ringojs/packages/ringo-sqlstore/jars/", shell=True)
     subprocess.Popen("ringo --production -Dserver -DXmx=512m -DXms=512m ringo-main.js", shell=True, cwd="ringojs-convinient")
     return 0

+ 0 - 0
ringojs-convinient/templates/fortunes.reinhardt → ringojs-convenient/templates/fortunes.reinhardt


+ 2 - 7
ringojs/benchmark_config

@@ -4,6 +4,7 @@
     "default": {
       "setup_file": "setup",
       "json_url": "/json",
+      "plaintext_url": "/plaintext",
       "port": 8080,
       "sort": 73
     },
@@ -12,15 +13,9 @@
       "db_url": "/db",
       "query_url": "/db?queries=",
       "fortune_url": "/fortune",
+      "update_url": "/updates?queries=",
       "port": 8080,
       "sort": 74
-    },
-    "mongodb": {
-      "setup_file": "setup",
-      "db_url": "/mongodb",
-      "query_url": "/mongodb?queries=",
-      "port": 8080,
-      "sort": 116
     }
   }]
 }

+ 32 - 12
ringojs/ringo-main.js

@@ -1,6 +1,5 @@
 var sql = require('sql-ringojs-client');
 var mustache = require('ringo/mustache');
-var mongodb = require('ringo-mongodb');
 
 // DO NOT TOUCH THE FOLLOWING LINE.
 // THIS VARIABLE IS REGEX REPLACED BY setup.py
@@ -80,21 +79,42 @@ exports.app = function(req) {
             connection.close();
          }
       }
-   } else if (path === '/mongodb') {
-      var queryCount = req.env.servletRequest.getParameter('queries') | 1;
-      var col = mongodb.connect(mongodbUri).getCollection('world');
-      var body = [];
-      var randId, world;
-      for (var i = 0; i < queryCount; i++) {
-         randId = ((Math.random() * 10000) | 0) + 1;
-         world = col.findOne({id: randId});
-         body.push(world ? world.data : "Record not found for id#" + randId);
+   } else if (path === '/plaintext') {
+      return {
+        status: 200,
+        headers: {"Content-Type": 'text/plain'},
+        body: ['Hello World']
+      };
+   } else if (path === '/updates') {
+      var queryCount = parseInt(req.env.servletRequest.getParameter('queries'), 10);
+      if (isNaN(queryCount) || queryCount < 1) {
+         queryCount = 1;
+      } else if (queryCount > 500) {
+         queryCount = 500;
+      }
+      try {
+         var connection = datasource.getConnection();
+         var body = [];
+         for (var i = 0; i < queryCount; i++) {
+            let randId = ((Math.random() * 10000) | 0) + 1;
+            world = sql.query(connection, 'select * from World where World.id = ' + randId)[0];
+            world.randomNumber = ((Math.random() * 10000) | 0) + 1;
+            sql.execute(connection, 'UPDATE World SET randomNumber = ' + world.randomNumber + ' WHERE id = ' + world.id);
+            body.push(world);
+         }
+      } catch (e) {
+         connection.close();
+         connection = null;
+      } finally {
+         if (connection !== null) {
+            connection.close();
+         }
       }
       return {
          status: 200,
          headers: {"Content-Type": "application/json; charset=UTF-8"},
-         body: [mongodb.JSON.to(body)]
-      };
+         body: [JSON.stringify(body)]
+      }
    }
 };
 

+ 2 - 1
ringojs/setup.py

@@ -6,9 +6,10 @@ import os
 
 def start(args):
   setup_util.replace_text("ringojs/ringo-main.js", "dbHost = '.*';", "dbHost = '" + args.database_host + "';")
-  setup_util.replace_text("ringojs/ringo-main.js", "mongodb:\/\/.*\/hello_world", "mongodb://" + args.database_host + "/hello_world")
 
   try:
+    subprocess.check_call("sudo rm -rf /usr/share/ringojs/packages/*", shell=True)
+    subprocess.check_call("sudo ringo-admin install oberhamsi/sql-ringojs-client", shell=True)
     subprocess.Popen("ringo --production -Dserver -DXmx=512m -DXms=512m ringo-main.js", shell=True, cwd="ringojs")
     return 0
   except subprocess.CalledProcessError:

+ 1 - 1
run-tests.py

@@ -20,7 +20,7 @@ parser.add_argument('--install-software', action='store_true', help='runs the in
 parser.add_argument('--install', choices=['client', 'server', 'all'], default='all', help='Allows you to only install the server or client software')
 parser.add_argument('--test', nargs='+', help='names of tests to run')
 parser.add_argument('--exclude', nargs='+', help='names of tests to exclude')
-parser.add_argument('--type', choices=['all', 'json', 'db', 'query', 'fortune', 'update'], default='all', help='which type of test to run')
+parser.add_argument('--type', choices=['all', 'json', 'db', 'query', 'fortune', 'update', 'plaintext'], default='all', help='which type of test to run')
 parser.add_argument('-m', '--mode', choices=['benchmark', 'verify'], default='benchmark', help='verify mode will only start up the tests, curl the urls and shutdown')
 parser.add_argument('--list-tests', action='store_true', default=False, help='lists all the known tests that can run')
 parser.add_argument('--next-sort', action='store_true', default=False, help='displays the next value that can be used as a sort value')

+ 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>