Browse Source

Merge branch 'master' of github.com:TechEmpower/FrameworkBenchmarks

Conflicts:
	revel/src/benchmark/app/controllers/app.go
Rob Figueiredo 12 years ago
parent
commit
0b853ce831

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

+ 37 - 0
dart/README.md

@@ -0,0 +1,37 @@
+# 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.11.1_r23200](http://www.dartlang.org/)
+* [Dart args version 0.5.9](http://pub.dartlang.org/packages/args)
+* [Dart mustache version 0.1.5](http://pub.dartlang.org/packages/mustache)
+* [Dart postgresql version 0.2.6](http://pub.dartlang.org/packages/postgresql)
+* [Dart 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

+ 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

+ 7 - 0
dart/pubspec.yaml

@@ -0,0 +1,7 @@
+name: dartbenchmark
+description: A benchmark of dart
+dependencies:
+  args: 0.5.9
+  mustache: 0.1.5
+  postgresql: 0.2.6
+  yaml: 0.5.7

+ 259 - 0
dart/server.dart

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

+ 75 - 0
dart/setup.py

@@ -0,0 +1,75 @@
+import subprocess
+import sys
+import setup_util
+import os
+
+#DART_SDK = os.environ('DART_SDK')
+
+def start(args):
+  setup_util.replace_text('dart/postgresql.yaml', 'host: .*', 'host: ' + args.database_host)
+  try:
+    #
+    # 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('    }')
+    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('        }')
+    conf.append('    }')
+    conf.append('}')
+    #
+    # write nginx configuration to disk
+    #
+    #os.remove('dart/nginx.conf')
+    with open('dart/nginx.conf', 'w') as f:
+      f.write('\n'.join(conf))
+    #
+    # 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

+ 21 - 0
installer.py

@@ -37,6 +37,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
     #
@@ -175,6 +180,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
     #######################################
@@ -286,6 +302,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

+ 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

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