Browse Source

cpoll_cppsp: many updates
* update to cppsp_0.2-rc9
* use threadpool for mysql db, query, and fortune tests
* added threadpool and async implementations of db and query for postgresql
* renamed old non-threadpool mysql tests to db_old and fortune_old
* update benchmark_config to separate the different test modes

xaxaxa 12 years ago
parent
commit
23c22b160c

+ 6 - 6
cpoll_cppsp/Makefile

@@ -1,12 +1,12 @@
 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
+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 https://downloads.sourceforge.net/project/cpollcppsp/CPPSP%200.2%20%28unstable%29/cppsp_0.2-rc9.tar.xz
 

+ 27 - 0
cpoll_cppsp/benchmark_config

@@ -4,11 +4,38 @@
     "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",
       "port": 16969,
       "sort": 115
+    },
+    "postgres-raw": {
+      "setup_file": "setup",
+      "db_url": "/db_pg_async",
+      "query_url": "/db_pg_async?queries=", 
+      "port": 16969,
+      "sort": 115
+    },
+    "postgres-raw-threadpool": {
+      "setup_file": "setup",
+      "db_url": "/db_pg_threadpool",
+      "query_url": "/db_pg_threadpool?queries=", 
+      "port": 16969,
+      "sort": 115
+    },
+    "raw-old": {
+	  "setup_file": "setup",
+      "db_url": "/db_old",
+      "query_url": "/db_old?queries=",
+      "fortune_url": "/fortune_old",
+      "port": 16969,
+      "sort": 115
     }
   }]
 }

+ 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

+ 122 - 46
cpoll_cppsp/www/db

@@ -1,58 +1,134 @@
-<%!-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;
+
+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;
+int* items;
+myStatement* stmt;
+
+void tpFunc() {
+	//mysql_thread_init();
+	for (int i=0;i<queries;i++){
+		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]=stmt->r;
+		} else {
+			items[i]=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=(int*)sp->alloc(sizeof(int)*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();
+}
+
+%>{ "json": [ <%
 for (int i=0;i<queries;i++){
 	if(i>0) {%>, <%}
 	%>{ "randomNumber": <%=items[i]%> }<%

+ 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";
+%> ] }

+ 117 - 0
cpoll_cppsp/www/db_pg_async

@@ -0,0 +1,117 @@
+<%!-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"
+
+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;
+int* 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=(int*)sp->alloc(sizeof(int)*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;
+	}
+	stmt->exec(rand()%10000);
+	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++]=ntohl(tmp);
+	PQclear(res);
+	beginGetItems();
+}
+
+%>{ "json": [ <%
+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";
+%> ] }

+ 105 - 0
cpoll_cppsp/www/db_pg_threadpool

@@ -0,0 +1,105 @@
+<%!-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"
+
+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;
+int* items;
+myStatement* stmt;
+
+void tpFunc() {
+	for (int i=0;i<queries;i++){
+		PGresult* res;
+		if((res=stmt->exec(rand()%10000))==NULL) throw bad_alloc();
+		if(PQntuples(res)>0) {
+			items[i]=ntohl(*(const int*)PQgetvalue(res,0,0));
+		}
+		else items[i]=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=(int*)sp->alloc(sizeof(int)*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();
+}
+
+%>{ "json": [ <%
+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";
+%> ] }

+ 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);
 %>