Browse Source

Merge branch 'master' into travis-hotfix

Conflicts:
	toolset/benchmark/framework_test.py

[ci skip]
Hamilton Turner 11 years ago
parent
commit
d5180a5e7e

+ 107 - 0
ULib/README.md

@@ -0,0 +1,107 @@
+#ULib Benchmarking Test
+
+This is the ULib portion of a [benchmarking test suite](https://github.com/TechEmpower/FrameworkBenchmarks) comparing a variety of web development platforms.
+
+### JSON Encoding Test
+
+* [JSON test source](src/json.usp)
+
+### Data-Store/Database Mapping Test
+
+* [Database test source](src/db.usp)
+
+### Variable Query Test
+
+* [Variable Query test source](src/queries.usp)
+
+### Fortune Query Test
+
+* [Fortune Query test source](src/fortunes.usp)
+
+### Variable Query (update) Test
+
+* [Variable Query (update) test source](src/updates.usp)
+
+### Plaintext Test
+
+* [Plaintext test source](src/plaintext.usp)
+
+## Infrastructure Software Versions
+The tests were run with:
+
+* [ULib Version 1.4.1](https://github.com/stefanocasazza/ULib/archive/v1.4.1.tar.gz)
+
+Output
+======
+
+[/json](http://www.techempower.com/benchmarks/#section=json)
+-----
+```
+HTTP/1.1 200 OK
+Date: Thu, 03 Jul 2014 10:11:10 GMT
+Server: ULib 
+Content-Length: 27
+Content-Type: application/json; charset=UTF-8
+
+{"message":"Hello, World!"}
+```
+
+[/db](http://www.techempower.com/benchmarks/#section=db)
+---
+```
+HTTP/1.1 200 OK
+Date: Thu, 03 Jul 2014 10:14:51 GMT
+Server: ULib 
+Content-Length: 31
+Content-Type: application/json; charset=UTF-8
+
+{"id":6227,"randomNumber":8489}
+```
+
+[/queries?queries=10](http://www.techempower.com/benchmarks/#section=query)
+-------------------
+```
+HTTP/1.1 200 OK
+Date: Thu, 03 Jul 2014 10:14:51 GMT
+Server: ULib 
+Content-Length: 320
+Content-Type: application/json; charset=UTF-8
+
+[{"id":6851,"randomNumber":7598},{"id":3968,"randomNumber":7325},{"id":8159,"randomNumber":348},{"id":9560,"randomNumber":7333},{"id":9938,"randomNumber":9080},{"id":1598,"randomNumber":1623},{"id":3280,"randomNumber":8707},{"id":4521,"randomNumber":6063},{"id":8173,"randomNumber":3690},{"id":3648,"randomNumber":8803}]
+```
+
+[/fortunes](http://www.techempower.com/benchmarks/#section=fortune)
+---------
+```
+HTTP/1.1 200 OK
+Date: Thu, 03 Jul 2014 10:14:51 GMT
+Server: ULib 
+Content-Type: text/html; charset=UTF-8
+Content-Length: 1227
+
+<!doctype html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr><tr><td>11</td><td>&lt;script&gt;alert(&quot;This should not be displayed in a browser alert box.&quot;);&lt;/script&gt;</td></tr><tr><td>4</td><td>A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1</td></tr><tr><td>5</td><td>A computer program does what you tell it to do, not what you want it to do.</td></tr><tr><td>2</td><td>A computer scientist is someone who fixes things that aren&apos;t broken.</td></tr><tr><td>8</td><td>A list is only as strong as its weakest link. — Donald Knuth</td></tr><tr><td>0</td><td>Additional fortune added at request time.</td></tr><tr><td>3</td><td>After enough decimal places, nobody gives a damn.</td></tr><tr><td>7</td><td>Any program that runs right is obsolete.</td></tr><tr><td>10</td><td>Computers make very fast, very accurate mistakes.</td></tr><tr><td>6</td><td>Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen</td></tr><tr><td>9</td><td>Feature: A bug with seniority.</td></tr><tr><td>1</td><td>fortune: No such file or directory</td></tr><tr><td>12</td><td>フレームワークのベンチマーク</td></tr></table></body></html>
+```
+
+[/updates?queries=10](http://www.techempower.com/benchmarks/#section=update)
+-------------------
+```
+HTTP/1.1 200 OK
+Date: Thu, 03 Jul 2014 10:14:51 GMT
+Server: ULib 
+Content-Length: 319
+Content-Type: application/json; charset=UTF-8
+
+[{"id":7171,"randomNumber":351},{"id":6019,"randomNumber":9725},{"id":8118,"randomNumber":4023},{"id":7965,"randomNumber":1388},{"id":7797,"randomNumber":2249},{"id":112,"randomNumber":1108},{"id":6127,"randomNumber":4323},{"id":2597,"randomNumber":7509},{"id":2978,"randomNumber":7883},{"id":1111,"randomNumber":2228}]
+```
+
+[/plaintext](http://www.techempower.com/benchmarks/#section=plaintext)
+----------
+```
+HTTP/1.1 200 OK
+Date: Thu, 03 Jul 2014 10:14:51 GMT
+Server: ULib 
+Content-Type: text/plain; charset=UTF-8
+Content-Length: 13
+
+Hello, World!
+```

+ 0 - 0
ULib/__init__.py


+ 18 - 0
ULib/bash_profile.sh

@@ -0,0 +1,18 @@
+#---------------------------------------------------------------------------------------------------------
+# bash_profile.sh - set the environment of our ULib installation
+#---------------------------------------------------------------------------------------------------------
+# TROOT - Path of this test's directory
+# IROOT - Path of this test's install directory ($FWROOT/installs or $FWROOT/installs/pertest/<test-name>)
+#---------------------------------------------------------------------------------------------------------
+# INFO:root:Running installation for ULib
+# INSTALL: 
+#    export TROOT=$FWROOT/ULib && 
+#    export IROOT=$FWROOT/installs && 
+#    . $FWROOT/toolset/setup/linux/bash_functions.sh && 
+#    . $FWROOT/ULib/install.sh (cwd=$FWROOT//installs)
+#---------------------------------------------------------------------------------------------------------
+export ULIB_VERSION=1.4.1
+export ULIB_ROOT=${IROOT}/ULib
+export ULIB_DOCUMENT_ROOT=${ULIB_ROOT}/ULIB_DOCUMENT_ROOT
+export ULIB_BUILD_OUTPUT=${ULIB_ROOT}/ULIB_BUILD_OUTPUT.txt
+export ULIB_SERVER_OUTPUT=${ULIB_ROOT}/ULIB_SERVER_OUTPUT.txt

+ 28 - 0
ULib/benchmark_config

@@ -0,0 +1,28 @@
+{
+  "framework": "ULib",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "database": "MySQL",
+      "framework": "ULib",
+      "language": "C++",
+      "orm": "Micro",
+      "platform": "ULib",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "ULib",
+      "notes": "",
+      "versus": ""
+    }
+  }]
+}

+ 79 - 0
ULib/install.sh

@@ -0,0 +1,79 @@
+#!/bin/bash
+
+# install.sh
+# --------------------------------------------------------------------------------------------------------
+# toolset/run-tests.py --install server --test ULib --type all --verbose
+# --------------------------------------------------------------------------------------------------------
+# TROOT - Path of this test's directory
+# IROOT - Path of this test's install directory ($FWROOT/installs or $FWROOT/installs/pertest/<test-name>)
+# --------------------------------------------------------------------------------------------------------
+# INFO:root:Running installation for ULib
+# INSTALL: 
+#    export TROOT=$FWROOT/ULib && 
+#    export IROOT=$FWROOT/installs && 
+#    . $FWROOT/toolset/setup/linux/bash_functions.sh && 
+#    . $FWROOT/ULib/install.sh (cwd=$FWROOT//installs)
+# --------------------------------------------------------------------------------------------------------
+. ${TROOT}/bash_profile.sh
+
+if [ ! -d "$ULIB_ROOT" ]; then
+  mkdir -p $ULIB_ROOT
+fi
+
+cd $ULIB_ROOT
+
+if [ ! -f "benchmark.cfg" ]; then
+  cat <<EOF >benchmark.cfg
+userver {
+ PORT 8080
+ PREFORK_CHILD 8
+ LISTEN_BACKLOG 8192
+ MAX_KEEP_ALIVE 8192
+ DOCUMENT_ROOT $ULIB_DOCUMENT_ROOT
+ PID_FILE ${ULIB_ROOT}/userver_tcp.pid
+}
+EOF
+fi
+
+if [ -x "bin/userver_tcp" ] && [ -x "${ULIB_DOCUMENT_ROOT}/db.so" ]; then
+  exit 0
+fi
+
+# 1. Download ULib
+if [ ! -f "v${ULIB_VERSION}.tar.gz" ]; then
+	wget -nc --no-check-certificate --trust-server-names -O v${ULIB_VERSION}.tar.gz \
+        https://github.com/stefanocasazza/ULib/archive/v${ULIB_VERSION}.tar.gz 2>/dev/null
+
+	tar xf v${ULIB_VERSION}.tar.gz 2>/dev/null
+fi
+
+cd ULib-$ULIB_VERSION
+
+# 2. Compile application (userver_tcp)
+# ======================================================================================================
+# TO AVOID configure: error: newly created file is older than distributed files! Check your system clock
+# ======================================================================================================
+find . -exec touch {} \;
+# ======================================================================================================
+
+#           --enable-debug \
+LIBS="-lssl -lcrypto -lz" \
+./configure --prefix=$ULIB_ROOT \
+            --disable-static \
+            --with-mysql \
+            --without-ssl --without-pcre --without-expat \
+            --without-libz --without-libuuid --without-magic \
+            --enable-static-orm-driver=mysql --enable-static-server-plugin=http >$ULIB_BUILD_OUTPUT 2>&1
+
+make -j1 install >>$ULIB_BUILD_OUTPUT 2>&1
+
+# 3. Compile usp pages for benchmark
+cd src/ulib/net/server/plugin/usp
+make -j1 db.la fortunes.la json.la plaintext.la queries.la updates.la >>$ULIB_BUILD_OUTPUT 2>&1
+
+if [ ! -e .libs/db.so ]; then
+   exit 1
+fi
+
+mkdir -p $ULIB_DOCUMENT_ROOT
+cp .libs/db.so .libs/fortunes.so .libs/json.so .libs/plaintext.so .libs/queries.so .libs/updates.so $ULIB_DOCUMENT_ROOT

+ 219 - 0
ULib/setup.py

@@ -0,0 +1,219 @@
+# setup.py
+
+# THIS CALLING IS WORKING
+# ----------------------------------------------------------------------
+# toolset/run-tests.py --install server --test ULib --type all --verbose
+# ----------------------------------------------------------------------
+# ....
+# ULib: setup.py running - FWROOT is /home/tfb/FrameworkBenchmarks
+# ....
+# INFO:root:Running installation for ULib
+# INSTALL: 
+#    export TROOT=$FWROOT/ULib && 
+#    export IROOT=$FWROOT/installs && 
+#    . $FWROOT/toolset/setup/linux/bash_functions.sh && 
+#    . $FWROOT/ULib/install.sh (cwd=$FWROOT//installs)
+# ....
+# -----------------------------------------------------
+#   Running Test: ULib ...
+# -----------------------------------------------------
+# ULib: TROOT is EMPTY
+# ULib: setup.py START - IROOT is /home/tfb/FrameworkBenchmarks/installs/pertest/ULib - TROOT is /home/tfb/FrameworkBenchmarks/ULib
+# ULib: bash_profile.sh script START
+# ULib: trying to start server /home/tfb/FrameworkBenchmarks/installs/pertest/ULib/ULib/bin/userver_tcp -c /home/tfb/FrameworkBenchmarks/installs/pertest/ULib/ULib/benchmark.cfg
+# ULib: server STARTED
+# {'results': []}
+# ULib: TROOT is EMPTY
+# ULib: setup.py STOP - IROOT is /home/tfb/FrameworkBenchmarks/installs/pertest/ULib - TROOT is /home/tfb/FrameworkBenchmarks/ULib
+# ULib: bash_profile.sh script START
+# ----------------------------------------------------------------------
+
+# NOW THIS CALLING IS WORKING TOO BUT IT IS CORRECT TO CALL IT BEFORE INSTALLING...?
+# ----------------------------------------------------------------------------------
+# toolset/run-tests.py --test ULib --type all --verbose
+# ----------------------------------------------------------------------------------
+# ....
+# ULib: setup.py running - FWROOT is /home/tfb/FrameworkBenchmarks
+# ....
+# -----------------------------------------------------
+#   Running Test: ULib ...
+# -----------------------------------------------------
+# ULib: TROOT is EMPTY
+# ULib: setup.py START - IROOT is /home/tfb/FrameworkBenchmarks/installs/pertest/ULib - TROOT is /home/tfb/FrameworkBenchmarks/ULib
+# ULib: bash_profile.sh script START
+# ULib: NOT INSTALLED...
+# ULib: install.sh script START
+# ULib: trying to start server /home/tfb/FrameworkBenchmarks/installs/pertest/ULib/ULib/bin/userver_tcp -c /home/tfb/FrameworkBenchmarks/installs/pertest/ULib/ULib/benchmark.cfg
+# ULib: server STARTED
+# {'results': []}
+# ULib: TROOT is EMPTY
+# ULib: setup.py STOP - IROOT is /home/tfb/FrameworkBenchmarks/installs/pertest/ULib - TROOT is /home/tfb/FrameworkBenchmarks/ULib
+# ULib: bash_profile.sh script START
+# -----------------------------------------------------
+
+import os
+import sys
+import time
+#import logging
+import setup_util
+import subprocess
+import multiprocessing
+
+#log = logging.getLogger('framework_test')
+
+fwroot = setup_util.get_fwroot()
+
+print(    "ULib: setup.py running - FWROOT is %s" % fwroot)
+#log.info("ULib: setup.py running - FWROOT is %s" % fwroot)
+
+# Queries the shell for the value of IROOT
+def get_iroot():
+  try:
+    # Use printf to avoid getting a newline
+    # Redirect to avoid stderr printing
+    iroot = subprocess.check_output('printf \"$IROOT\" 2>/dev/null', shell=True, executable='/bin/bash')
+    if iroot != "":
+      return iroot
+    print(       'ULib: IROOT is EMPTY')
+#   log.critical('ULib: IROOT is EMPTY')
+    return setup_util.get_fwroot() + "/installs"
+  except subprocess.CalledProcessError:
+    return "";
+
+# Queries the shell for the value of TROOT
+def get_troot():
+  try:
+    # Use printf to avoid getting a newline
+    # Redirect to avoid stderr printing
+    troot = subprocess.check_output('printf \"$TROOT\" 2>/dev/null', shell=True, executable='/bin/bash')
+    if troot != "":
+      return troot
+    print(       'ULib: TROOT is EMPTY')
+#   log.critical('ULib: TROOT is EMPTY')
+    return setup_util.get_fwroot() + "/ULib"
+  except subprocess.CalledProcessError:
+    return "";
+
+def getEnvironmentVar(name, iroot, troot):
+  try:
+    script = """
+export IROOT=%s
+. %s/bash_profile.sh 2>>/tmp/ULib_setup.txt
+printf $%s           2>>/tmp/ULib_setup.txt
+"""
+    value = subprocess.check_output(script % (iroot, troot, name), shell=True, executable='/bin/bash')
+    return value
+  except subprocess.CalledProcessError:
+    return "";
+
+def callScriptAndCheckForInstallation(name, iroot, troot):
+  try:
+    script = """
+export IROOT=%s
+export TROOT=%s
+. %s/%s.sh >>/tmp/ULib_setup.txt 2>&1
+if [ ! -x "${ULIB_ROOT}/bin/userver_tcp" ] || [ ! -e "${ULIB_DOCUMENT_ROOT}/db.so" ] || [ ! -f "${ULIB_ROOT}/benchmark.cfg" ]; then
+  exit 1
+fi
+"""
+    print(   "ULib: %s.sh script START" % name)
+#   log.info("ULib: %s.sh script START" % name)
+    return subprocess.call(script % (iroot, troot, troot, name), shell=True, executable='/bin/bash')
+  except subprocess.CalledProcessError:
+    return 1
+
+# --------------------------------------------------------------------------------------------------------
+# TROOT - Path of this test's directory
+# IROOT - Path of this test's install directory ($FWROOT/installs or $FWROOT/installs/pertest/<test-name>)
+# --------------------------------------------------------------------------------------------------------
+def checkEnvironment(iroot, troot):
+  try:
+    if callScriptAndCheckForInstallation('bash_profile', iroot, troot) != 0:
+      print(       'ULib: NOT INSTALLED...')
+#     log.critical('ULib: NOT INSTALLED...')
+      if callScriptAndCheckForInstallation('install', iroot, troot) != 0:
+        print(   "ULib: install.sh script FAILED")
+#       log.info("ULib: install.sh script FAILED")
+        return False
+    return True
+  except:
+    pass
+  return False
+
+##############
+# start(args)
+##############
+def start(args, logfile, errfile):
+  try:
+    iroot = get_iroot()
+    troot = get_troot()
+
+    print(   "ULib: setup.py START - IROOT is %s - TROOT is %s" % (iroot, troot))
+#   log.info("ULib: setup.py START - IROOT is %s - TROOT is %s" % (iroot, troot))
+
+    if not checkEnvironment(iroot, troot):
+      return 1
+
+    ulib_root = getEnvironmentVar('ULIB_ROOT', iroot, troot)
+
+    fcfg = ulib_root + "/benchmark.cfg"
+
+    # 1. Change ULib Server configuration
+
+    # I don't understand if the two approach are different...
+    #threads = str(args.max_threads)
+    PROCS = str(multiprocessing.cpu_count())
+    setup_util.replace_text(fcfg, "PREFORK_CHILD .*", "PREFORK_CHILD " + PROCS)
+
+    fprg = ulib_root + "/bin/userver_tcp"
+
+    # 2. Start ULib Server (userver_tcp)
+    print(   "ULib: trying to start server " + fprg + " -c " + fcfg)
+#   log.info("ULib: trying to start server " + fprg + " -c " + fcfg)
+
+    # sudo mysqlcheck -v -r -A -u benchmarkdbuser -p
+    os.putenv("ORM_DRIVER","mysql")
+    os.putenv("ORM_OPTION","host=" + args.database_host + " user=benchmarkdbuser password=benchmarkdbpass dbname=hello_world")
+    os.putenv("UMEMPOOL", "1583,1507,-19,45,16458,523,-27,-14,27")
+
+    ulib_server_output = getEnvironmentVar('ULIB_SERVER_OUTPUT', iroot, troot)
+
+    # Run in the background, but keep stdout/stderr for easy debugging
+    subprocess.Popen(                      fprg + " -c " + fcfg + " >" + ulib_server_output + " 2>&1", shell=True, stdout=logfile, stderr=errfile)
+  # subprocess.Popen("UTRACE=\"0 50M\" " + fprg + " -c " + fcfg + " >" + ulib_server_output + " 2>&1", shell=True, stdout=logfile, stderr=errfile)
+
+    print(   "ULib: server STARTED")
+#   log.info("ULib: server STARTED")
+    return 0
+  except subprocess.CalledProcessError:
+    return 1
+
+##############
+# stop()
+##############
+def stop(logfile, errfile):
+  try:
+    iroot = get_iroot()
+    troot = get_troot()
+
+    print(   "ULib: setup.py STOP - IROOT is %s - TROOT is %s" % (iroot, troot))
+#   log.info("ULib: setup.py STOP - IROOT is %s - TROOT is %s" % (iroot, troot))
+
+    if not checkEnvironment(iroot, troot):
+        return 1
+
+    ulib_root = getEnvironmentVar('ULIB_ROOT', iroot, troot)
+
+    # Stop ULib Server (userver_tcp)
+    subprocess.check_call("kill -TERM $( cat " + ulib_root + "/userver_tcp.pid )", shell=True, stderr=errfile, stdout=logfile)
+    time.sleep(2);
+    p = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
+    out, err = p.communicate()
+    for line in out.splitlines():
+       if 'userver_tcp' in line:
+         pid = int(line.split(None, 2)[1])
+         os.kill(pid, 9)
+    subprocess.call("rm -f " + ulib_root + "/userver_tcp.pid /tmp/ULib_setup.txt", shell=True, stderr=errfile, stdout=logfile)
+    return 0
+  except subprocess.CalledProcessError:
+    return 1

+ 8 - 0
ULib/source_code

@@ -0,0 +1,8 @@
+./src/db.usp
+./src/world.h
+./src/json.usp
+./src/fortune.h
+./src/queries.usp
+./src/updates.usp
+./src/fortunes.usp
+./src/plaintext.usp

+ 120 - 0
ULib/src/db.usp

@@ -0,0 +1,120 @@
+<!--#
+Test 2: Single database query
+
+This test exercises the framework's object-relational mapper (ORM), random number generator, database driver, and database connection pool.
+
+Requirements
+
+1.  For every request, a single row from a World table must be retrieved from a database table. 
+
+2.  The recommended URI is /db.
+
+3.  The schema for World is id (int, primary key) and randomNumber (int).
+
+4.  The World table is known to contain 10,000 rows.
+
+5.  The row retrieved must be selected by its id using a random number generator (ids range from 1 to 10,000).
+
+6.  The row should be converted to an object using an object-relational mapping (ORM) tool.
+    Tests that do not use an ORM will be identified with the suffix "raw," meaning they use the platform's raw database connectivity.
+
+7.  The object (or database row, if an ORM is not used) must be serialized to JSON.
+
+8.  The response content length should be approximately 32 bytes.
+
+9.  The response content type must be set to application/json.
+
+10. The response headers must include either Content-Length or Transfer-Encoding.
+
+11. The response headers must include Server and Date.
+
+12. Use of an in-memory cache of World objects or rows by the application is not permitted.
+
+13. Use of prepared statements for SQL database tests (e.g., for MySQL) is encouraged but not required.
+
+14. gzip compression is not permitted.
+
+15. Server support for HTTP Keep-Alive is strongly encouraged but not required.
+
+16. If HTTP Keep-Alive is enabled, no maximum Keep-Alive timeout is specified by this test.
+
+17. The request handler will be exercised at concurrency levels ranging from 8 to 256.
+
+18. The request handler will be exercised using GET requests.
+
+Example request:
+
+GET /db HTTP/1.1
+Host: server
+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
+Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-US,en;q=0.5
+Connection: keep-alive
+
+Example response:
+
+HTTP/1.1 200 OK
+Content-Type: application/json; charset=UTF-8
+Content-Length: 32
+Server: Example
+Date: Wed, 17 Apr 2013 12:00:00 GMT
+{"id":3217,"randomNumber":2149}
+-->
+<!--#declaration
+#include "world.h"
+
+#define AS_cpoll_cppsp_DO
+
+#ifndef AS_cpoll_cppsp_DO
+static UValue*	pvalue;
+#endif
+static World*			 pworld_db;
+static UOrmSession*	 psql_db;
+static UOrmStatement* pstmt_db;
+
+static void usp_init_db()
+{
+	U_TRACE(5, "::usp_init_db()")
+
+	psql_db	= U_NEW(UOrmSession(U_CONSTANT_TO_PARAM("hello_world")));
+	pstmt_db	= U_NEW(UOrmStatement(*psql_db, U_CONSTANT_TO_PARAM("SELECT randomNumber FROM World WHERE id = ?")));
+
+	if (pstmt_db == 0) U_ERROR("usp_init_db(): we cound't connect to db, exiting...");
+
+	pworld_db = U_NEW(World);
+
+	pstmt_db->use( pworld_db->id);
+	pstmt_db->into(pworld_db->randomNumber);
+
+#ifndef AS_cpoll_cppsp_DO
+	pvalue = U_NEW(UValue(OBJECT_VALUE));
+#endif
+}
+
+static void usp_end_db()
+{
+	U_TRACE(5, "::usp_end_db()")
+
+	delete pstmt_db;
+	delete psql_db;
+	delete pworld_db;
+#ifndef AS_cpoll_cppsp_DO
+	delete pvalue;
+#endif
+}
+-->
+<!--#header
+Content-Type: application/json; charset=UTF-8
+-->
+<!--#code
+pworld_db->id = u_get_num_random(10000);
+
+pstmt_db->execute();
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PRINTF_ADD("{\"id\":%d,\"randomNumber\":%d}", pworld_db->id, pworld_db->randomNumber);
+#else
+USP_JSON_stringify(*pvalue, World, *pworld_db);
+#endif
+-->

+ 123 - 0
ULib/src/fortune.h

@@ -0,0 +1,123 @@
+// fortune.h
+
+#ifndef FORTUNE_H
+#define FORTUNE_H 1
+
+#include <ulib/orm/orm.h>
+#include <ulib/json/value.h>
+
+class Fortune {
+public:
+	// Check for memory error
+	U_MEMORY_TEST
+
+	// Allocator e Deallocator
+	U_MEMORY_ALLOCATOR
+	U_MEMORY_DEALLOCATOR
+
+	int id;
+	UString message;
+
+	// CONSTRUCTOR
+
+	Fortune()
+		{
+		U_TRACE_REGISTER_OBJECT(5, Fortune, "")
+		}
+
+	Fortune(int _id, const UString& _message) : id(_id), message(_message)
+		{
+		U_TRACE_REGISTER_OBJECT(5, Fortune, "%d,%.*S", _id, U_STRING_TO_TRACE(_message))
+		}
+
+	Fortune(const Fortune& f) : id(f.id), message((void*)U_STRING_TO_PARAM(f.message))
+		{
+		U_TRACE_REGISTER_OBJECT(5, Fortune, "%p", &f)
+
+		U_MEMORY_TEST_COPY(f)
+		}
+
+	~Fortune()
+		{
+		U_TRACE_UNREGISTER_OBJECT(5, Fortune)
+		}
+
+	// SERVICE
+
+	bool operator<(const Fortune& other) const { return cmp_obj(&message, &other.message); }
+
+	static int cmp_obj(const void* a, const void* b)
+		{
+		U_TRACE(5, "Fortune::cmp_obj(%p,%p)", a, b)
+
+		return (*(const Fortune**)a)->message.compare((*(const Fortune**)b)->message);
+		}
+
+#ifdef DEBUG
+	const char* dump(bool breset) const
+		{
+		*UObjectIO::os << "id               " << id				   << '\n'
+							<< "message (UString " << (void*)&message << ')';
+
+		if (breset)
+			{
+			UObjectIO::output();
+
+			return UObjectIO::buffer_output;
+			}
+
+		return 0;
+		}
+#endif
+
+private:
+	Fortune& operator=(const Fortune&) { return *this; }
+};
+
+// ORM TEMPLATE SPECIALIZATIONS
+
+template <> class U_EXPORT UOrmTypeHandler<Fortune> : public UOrmTypeHandler_Base {
+public:
+	explicit UOrmTypeHandler(Fortune& val) : UOrmTypeHandler_Base(&val) {}
+
+	void bindParam(UOrmStatement* stmt) const
+		{
+		U_TRACE(0, "UOrmTypeHandler<Fortune>::bindParam(%p)", stmt)
+
+		stmt->bindParam(U_ORM_TYPE_HANDLER(Fortune, id,		  int));
+		stmt->bindParam(U_ORM_TYPE_HANDLER(Fortune, message, UString));
+		}
+
+	void bindResult(UOrmStatement* stmt)
+		{
+		U_TRACE(0, "UOrmTypeHandler<Fortune>::bindResult(%p)", stmt)
+
+		stmt->bindResult(U_ORM_TYPE_HANDLER(Fortune, id,		int));
+		stmt->bindResult(U_ORM_TYPE_HANDLER(Fortune, message, UString));
+		}
+};
+
+// JSON TEMPLATE SPECIALIZATIONS
+
+template <> class U_EXPORT UJsonTypeHandler<Fortune> : public UJsonTypeHandler_Base {
+public:
+	explicit UJsonTypeHandler(Fortune& val) : UJsonTypeHandler_Base(&val) {}
+
+	void toJSON(UValue& json)
+		{
+		U_TRACE(0, "UJsonTypeHandler<Fortune>::toJSON(%p)", &json)
+
+		json.toJSON(U_JSON_TYPE_HANDLER(Fortune, id,		  int));
+		json.toJSON(U_JSON_TYPE_HANDLER(Fortune, message, UString));
+		}
+
+	void fromJSON(UValue& json)
+		{
+		U_TRACE(0, "UJsonTypeHandler<Fortune>::fromJSON(%p)", &json)
+
+		json.fromJSON(U_JSON_TYPE_HANDLER(Fortune, id,		 int));
+		json.fromJSON(U_JSON_TYPE_HANDLER(Fortune, message, UString));
+		}
+};
+
+#endif

+ 153 - 0
ULib/src/fortunes.usp

@@ -0,0 +1,153 @@
+<!--#
+Test 4: Fortunes
+
+This test exercises the ORM, database connectivity, dynamic-size collections,
+sorting, server-side templates, XSS countermeasures, and character encoding.
+
+Requirements
+
+1.  The recommended URI is /fortunes.
+
+2.  A Fortune database table contains a dozen Unix-style fortune-cookie messages.
+
+3.  The schema for Fortune is id (int, primary key) and message (varchar).
+
+4.  Using an ORM, all Fortune objects must be fetched from the Fortune table, and placed into a list data structure.
+    Tests that do not use an ORM will be classified as "raw" meaning they use the platform's raw database connectivity.
+
+5.  The list data structure must be a dynamic-size or equivalent and should not be dimensioned using foreknowledge of
+    the row-count of the database table.
+
+6.  Within the scope of the request, a new Fortune object must be constructed and added to the list. This confirms that
+    the data structure is dynamic-sized. The new fortune is not persisted to the database; it is ephemeral for the scope of the request.
+
+7.  The new Fortune's message must be "Additional fortune added at request time."
+
+8.  The list of Fortune objects must be sorted by the order of the message field. No ORDER BY clause is permitted in the
+    database query (ordering within the query would be of negligible value anyway since a newly instantiated Fortune is
+	 added to the list prior to sorting).
+
+9.  The sorted list must be provided to a server-side template and rendered to simple HTML (see below for minimum template).
+    The resulting HTML table displays each Fortune's id number and message text.
+
+10. This test does not include external assets (CSS, JavaScript); a later test type will include assets.
+
+11. The HTML generated by the template must be sent as a response.
+
+12. Be aware that the message text fields are stored as UTF-8 and one of the fortune cookie messages is in Japanese.
+
+13. The resulting HTML must be delivered using UTF-8 encoding.
+
+14. The Japanese fortune cookie message must be displayed correctly.
+
+15. Be aware that at least one of the message text fields includes a <script> tag.
+
+16. The server-side template must assume the message text cannot be trusted and must escape the message text properly.
+
+17. The implementation is encouraged to use best practices for templates such as layout inheritence, separate header and
+    footer files, and so on. However, this is not required. We request that implementations do not manage assets (JavaScript,
+	 CSS, images). We are deferring asset management until we can craft a more suitable test.
+
+18. The response content type must be set to text/html.
+
+19. The response headers must include either Content-Length or Transfer-Encoding.
+
+20. The response headers must include Server and Date.
+
+21. Use of an in-memory cache of Fortune objects or rows by the application is not permitted.
+
+22. Use of prepared statements for SQL database tests (e.g., for MySQL) is encouraged but not required.
+
+23. gzip compression is not permitted.
+
+24. Server support for HTTP Keep-Alive is strongly encouraged but not required.
+
+25. If HTTP Keep-Alive is enabled, no maximum Keep-Alive timeout is specified by this test.
+
+26. The request handler will be exercised at concurrency levels ranging from 8 to 256.
+
+27. The request handler will be exercised using GET requests.
+
+Example request:
+
+GET /fortunes HTTP/1.1
+Host: server
+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
+Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-US,en;q=0.5
+Connection: keep-alive
+
+Example response:
+
+HTTP/1.1 200 OK
+Content-Length: 1190
+Content-Type: text/html; charset=UTF-8
+Server: Example
+Date: Wed, 17 Apr 2013 12:00:00 GMT
+
+<!doctype html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr><tr><td>11</td><td>&lt;script&gt;alert(&quot;This should not be displayed in a browser alert box.&quot;);&lt;/script&gt;</td></tr><tr><td>4</td><td>A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1</td></tr><tr><td>5</td><td>A computer program does what you tell it to do, not what you want it to do.</td></tr><tr><td>2</td><td>A computer scientist is someone who fixes things that aren't broken.</td></tr><tr><td>8</td><td>A list is only as strong as its weakest link. â Donald Knuth</td></tr><tr><td>0</td><td>Additional fortune added at request time.</td></tr><tr><td>3</td><td>After enough decimal places, nobody gives a damn.</td></tr><tr><td>7</td><td>Any program that runs right is obsolete.</td></tr><tr><td>10</td><td>Computers make very fast, very accurate mistakes.</td></tr><tr><td>6</td><td>Emacs is a nice operating system, but I prefer UNIX. â Tom Christaensen</td></tr><tr><td>9</td><td>Feature: A bug with seniority.</td></tr><tr><td>1</td><td>fortune: No such file or directory</td></tr><tr><td>12</td><td>ã¬ã¼ãã¯ã¼ã¯ã®ãã³ããã¼ã¯</td></tr></table></body></html>
+-->
+<!--#declaration
+#include "fortune.h"
+
+static UOrmSession*		  psql_fortune;
+static UOrmStatement*	  pstmt_fortune;
+static Fortune*			  pfortune;
+static UString*           pmessage;
+static UVector<Fortune*>* pvfortune;
+
+static void usp_init_fortunes()
+{
+	U_TRACE(5, "::usp_init_fortunes()")
+
+	psql_fortune  = U_NEW(UOrmSession(U_CONSTANT_TO_PARAM("fortune")));
+	pstmt_fortune = U_NEW(UOrmStatement(*psql_fortune, U_CONSTANT_TO_PARAM("SELECT id, message FROM Fortune")));
+
+	if (pstmt_fortune == 0) U_ERROR("usp_init_fortunes(): we cound't connect to db, exiting...");
+
+	pfortune  = U_NEW(Fortune);
+	pvfortune = U_NEW(UVector<Fortune*>);
+	pmessage  = U_NEW(U_STRING_FROM_CONSTANT("Additional fortune added at request time."));
+
+	pstmt_fortune->into(*pfortune);
+}
+
+static void usp_end_fortunes()
+{
+	U_TRACE(5, "::usp_end_fortunes()")
+
+	delete pstmt_fortune;
+	delete psql_fortune;
+	delete pvfortune;
+	delete pfortune;
+	delete pmessage;
+}
+-->
+<!doctype html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr><!--#code
+Fortune* elem;
+unsigned char encoded[1024];
+
+pstmt_fortune->execute();
+
+	  pvfortune->push_back(U_NEW(Fortune(0, *pmessage)));
+do	{ pvfortune->push_back(U_NEW(Fortune(*pfortune))); } while (pstmt_fortune->nextRow());
+
+pvfortune->sort(Fortune::cmp_obj);
+
+for (uint32_t i = 0, n = pvfortune->size(); i < n; ++i)
+	{
+	elem = (*pvfortune)[i];
+
+	(void) u_xml_encode((const unsigned char*)U_STRING_TO_PARAM(elem->message), encoded);
+
+	USP_PRINTF_ADD(
+		"<tr>"
+		"<td>%d</td>"
+		"<td>%s</td>"
+		"</tr>",
+		elem->id, encoded);
+	}
+
+pvfortune->clear();
+--></table></body></html>

+ 80 - 0
ULib/src/json.usp

@@ -0,0 +1,80 @@
+<!--#
+Test 1: JSON serialization
+
+This test exercises the framework fundamentals including keep-alive support, request routing, request header
+parsing, object instantiation, JSON serialization, response header generation, and request count throughput.
+
+Requirements
+
+1.  For each request, an object mapping the key message to Hello, World! must be instantiated.
+
+2.  The recommended URI is /json.
+
+3.  A JSON serializer must be used to convert the object to JSON.
+
+4.  The response text must be {"message":"Hello, World!"}, but white-space variations are acceptable.
+
+5.  The response content length should be approximately 28 bytes.
+
+6.  The response content type must be set to application/json.
+
+7.  The response headers must include either Content-Length or Transfer-Encoding.
+
+8.  The response headers must include Server and Date.
+
+9.  gzip compression is not permitted.
+
+10. Server support for HTTP Keep-Alive is strongly encouraged but not required.
+
+11. If HTTP Keep-Alive is enabled, no maximum Keep-Alive timeout is specified by this test.
+
+12. The request handler will be exercised at concurrency levels ranging from 8 to 256.
+
+13. The request handler will be exercised using GET requests.
+
+Example request:
+
+GET /json HTTP/1.1
+Host: server
+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
+Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-US,en;q=0.5
+Connection: keep-alive
+
+Example response:
+
+HTTP/1.1 200 OK
+Content-Type: application/json; charset=UTF-8
+Content-Length: 28
+Server: Example
+Date: Wed, 17 Apr 2013 12:00:00 GMT
+{"message":"Hello, World!"}
+-->
+<!--#declaration
+static UString* pkey;
+static UString* pvalue;
+
+static void usp_init_json()
+{
+	U_TRACE(5, "::usp_init_json()")
+
+	pkey   = U_NEW(U_STRING_FROM_CONSTANT("message"));
+	pvalue = U_NEW(U_STRING_FROM_CONSTANT("Hello, World!"));
+}
+
+static void usp_end_json()
+{
+	U_TRACE(5, "::usp_end_json()")
+
+	delete pkey;
+	delete pvalue;
+}
+-->
+<!--#header
+Content-Type: application/json; charset=UTF-8
+-->
+<!--#code
+UValue json(*pkey, *pvalue);
+USP_JSON_PUTS(json);
+-->

+ 51 - 0
ULib/src/plaintext.usp

@@ -0,0 +1,51 @@
+<!--#
+Test 6: Plaintext
+
+This test is an exercise of the request-routing fundamentals only, designed to demonstrate the capacity of
+high-performance platforms in particular. The response payload is still small, meaning good performance is
+still necessary in order to saturate the gigabit Ethernet of the test environment.
+
+Requirements
+
+1.  The recommended URI is /plaintext.
+
+2.  The response content type must be set to text/plain.
+
+3.  The response body must be Hello, World!.
+
+4.  The response headers must include either Content-Length or Transfer-Encoding.
+
+5.  The response headers must include Server and Date.
+
+6.  gzip compression is not permitted.
+
+7.  Server support for HTTP Keep-Alive is strongly encouraged but not required.
+
+8.  Server support for HTTP pipelining is strongly encouraged but not required.
+
+9.  If HTTP Keep-Alive is enabled, no maximum Keep-Alive timeout is specified by this test.
+
+10. The request handler will be exercised at 256, 1024, 4096, and 16,384 concurrency.
+
+11. The request handler will be exercised using GET requests.
+
+Example request:
+
+GET /plaintext HTTP/1.1
+Host: server
+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
+Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-US,en;q=0.5
+Connection: keep-alive
+
+Example response:
+
+HTTP/1.1 200 OK
+Content-Length: 15
+Content-Type: text/plain; charset=UTF-8
+Server: Example
+Date: Wed, 17 Apr 2013 12:00:00 GMT
+Hello, World!
+-->
+Hello, World!

+ 160 - 0
ULib/src/queries.usp

@@ -0,0 +1,160 @@
+<!--#
+Test 3: Multiple database queries
+
+This test is a variation of Test #2 and also uses the World table. Multiple rows are fetched to more dramatically
+punish the database driver and connection pool. At the highest queries-per-request tested (20), this test demonstrates
+all frameworks' convergence toward zero requests-per-second as database activity increases.
+
+Requirements
+
+1.  For every request, an integer query string parameter named queries must be retrieved from the request.
+	 The parameter specifies the number of database queries to execute in preparing the HTTP response (see below).
+
+2.  The recommended URI is /queries.
+
+3.  The queries parameter must be bounded to between 1 and 500. If the parameter is missing, is not an integer,
+	 or is an integer less than 1, the value should be interpreted as 1; if greater than 500, the value should be interpreted as 500.
+
+3.  The schema for World is id (int, primary key) and randomNumber (int).
+
+4.  The request handler must retrieve a set of World objects, equal in count to the queries parameter, from the World database table.
+
+5.  Each row must be selected randomly in the same fashion as the single database query test (Test #2 above).
+
+6.  Since this test is designed to exercise multiple queries, each row must be selected individually by a query.
+	 It is not acceptable to retrieve all required rows using a SELECT ... WHERE id IN (...) clause.
+
+7.  Each World object must be added to a list or array.
+
+8.  The list or array must be serialized to JSON and sent as a response.
+
+9.  The response content type must be set to application/json.
+
+10. The response headers must include either Content-Length or Transfer-Encoding.
+
+11. The response headers must include Server and Date.
+
+12. Use of an in-memory cache of World objects or rows by the application is not permitted.
+
+13. Use of prepared statements for SQL database tests (e.g., for MySQL) is encouraged but not required.
+
+14. gzip compression is not permitted.
+
+15. Server support for HTTP Keep-Alive is strongly encouraged but not required.
+
+16. If HTTP Keep-Alive is enabled, no maximum Keep-Alive timeout is specified by this test.
+
+17. The request handler will be exercised at 256 concurrency only.
+
+18. The request handler will be exercised with query counts of 1, 5, 10, 15, and 20.
+
+19. The request handler will be exercised using GET requests.
+
+Example request:
+
+GET /queries?queries=10 HTTP/1.1
+Host: server
+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
+Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-US,en;q=0.5
+Connection: keep-alive
+
+Example response:
+
+HTTP/1.1 200 OK
+Content-Type: application/json; charset=UTF-8
+Content-Length: 315
+Server: Example
+Date: Wed, 17 Apr 2013 12:00:00 GMT
+
+[{"id":4174,"randomNumber":331},{"id":51,"randomNumber":6544},{"id":4462,"randomNumber":952},{"id":2221,"randomNumber":532},{"id":9276,"randomNumber":3097},{"id":3056,"randomNumber":7293},{"id":6964,"randomNumber":620},{"id":675,"randomNumber":6601},{"id":8414,"randomNumber":6569},{"id":2753,"randomNumber":4065}]
+-->
+<!--#declaration
+#include "world.h"
+
+#define AS_cpoll_cppsp_DO
+
+#ifndef AS_cpoll_cppsp_DO
+static UValue*	pvalue;
+#endif
+static UOrmSession*		psql_queries;
+static UOrmStatement*	pstmt_queries;
+static World*				pworld_queries;
+static UVector<World*>* pvworld_queries;
+
+static void usp_init_queries()
+{
+	U_TRACE(5, "::usp_init_queries()")
+
+	psql_queries  = U_NEW(UOrmSession(U_CONSTANT_TO_PARAM("hello_world")));
+	pstmt_queries = U_NEW(UOrmStatement(*psql_queries, U_CONSTANT_TO_PARAM("SELECT randomNumber FROM World WHERE id = ?")));
+
+	if (pstmt_queries == 0) U_ERROR("usp_init_queries(): we cound't connect to db, exiting...");
+
+	pworld_queries  = U_NEW(World);
+	pvworld_queries = U_NEW(UVector<World*>(500));
+
+	pstmt_queries->use( pworld_queries->id);
+	pstmt_queries->into(pworld_queries->randomNumber);
+
+#ifndef AS_cpoll_cppsp_DO
+	pvalue = U_NEW(UValue(ARRAY_VALUE));
+#endif
+}
+
+static void usp_end_queries()
+{
+	U_TRACE(5, "::usp_end_queries()")
+
+	delete pstmt_queries;
+	delete psql_queries;
+	delete pvworld_queries;
+	delete pworld_queries;
+#ifndef AS_cpoll_cppsp_DO
+	delete pvalue;
+#endif
+}
+-->
+<!--#args
+queries;
+-->
+<!--#header
+Content-Type: application/json; charset=UTF-8
+-->
+<!--#code
+int i = 0, num_queries = queries.strtol();
+
+	  if (num_queries <   1) num_queries = 1;
+else if (num_queries > 500) num_queries = 500;
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PUTS_CHAR('[');
+#endif
+
+while (true)
+	{
+	pworld_queries->id = u_get_num_random(10000);
+
+	pstmt_queries->execute();
+
+#ifdef AS_cpoll_cppsp_DO
+	USP_PRINTF("{\"id\":%d,\"randomNumber\":%d}", pworld_queries->id, pworld_queries->randomNumber);
+#endif
+
+	pvworld_queries->push_back(U_NEW(World(*pworld_queries)));
+
+	if (++i == num_queries) break;
+
+#ifdef AS_cpoll_cppsp_DO
+	USP_PUTS_CHAR(',');
+#endif
+	}
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PUTS_CHAR(']');
+#else
+USP_JSON_stringify(*pvalue, UVector<World*>, *pvworld_queries);
+#endif
+pvworld_queries->clear();
+-->

+ 184 - 0
ULib/src/updates.usp

@@ -0,0 +1,184 @@
+<!--#
+Test 5: Database updates
+
+This test is a variation of Test #3 that exercises the ORM's persistence of
+objects and the database driver's performance at running UPDATE statements or similar.
+
+Requirements
+
+1.  The recommended URI is /updates.
+
+2.  For every request, an integer query string parameter named queries must be retrieved from the request.
+    The parameter specifies the number of rows to fetch and update in preparing the HTTP response (see below).
+
+3.  The queries parameter must be bounded to between 1 and 500. If the parameter is missing, is not an integer,
+	 or is an integer less than 1, the value should be interpreted as 1; if greater than 500, the value should be interpreted as 500.
+
+3.  The schema for World is id (int, primary key) and randomNumber (int).
+
+4.  The request handler must retrieve a set of World objects, equal in count to the queries parameter, from the World database table.
+
+5.  Each row must be selected randomly in the same fashion as the single database query test (Test #2 above).
+
+6.  At least the randomNumber field must be read from the database result set.
+
+7.  Each World object must have its randomNumber field updated to a new random integer between 1 and 10000.
+
+8.  Each World object must be persisted to the database with its new randomNumber value.
+
+9.  Use of batch updates is acceptable but not required.
+
+10. Use of transactions is acceptable but not required.
+
+11. For raw tests (that is, tests without an ORM), each updated row must receive a unique new randomNumber value.
+    It is not acceptable to change the randomNumber value of all rows to the same random number using an UPDATE ... WHERE id IN (...) clause.
+
+12. Each World object must be added to a list or array.
+
+13. The list or array must be serialized to JSON and sent as a response.
+
+14. The response content type must be set to application/json.
+
+15. The response headers must include either Content-Length or Transfer-Encoding.
+
+16. The response headers must include Server and Date.
+
+17. Use of an in-memory cache of World objects or rows by the application is not permitted.
+
+18. Use of prepared statements for SQL database tests (e.g., for MySQL) is encouraged but not required.
+
+19. gzip compression is not permitted.
+
+20. Server support for HTTP Keep-Alive is strongly encouraged but not required.
+
+21. If HTTP Keep-Alive is enabled, no maximum Keep-Alive timeout is specified by this test.
+
+22. The request handler will be exercised at 256 concurrency only.
+
+23. The request handler will be exercised with query counts of 1, 5, 10, 15, and 20.
+
+24. The request handler will be exercised using GET requests.
+
+Example request:
+
+GET /updates?queries=10 HTTP/1.1
+Host: server
+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
+Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-US,en;q=0.5
+Connection: keep-alive
+
+Example response:
+
+HTTP/1.1 200 OK
+Content-Type: application/json; charset=UTF-8
+Content-Length: 315
+Server: Example
+Date: Wed, 17 Apr 2013 12:00:00 GMT
+
+[{"id":4174,"randomNumber":331},{"id":51,"randomNumber":6544},{"id":4462,"randomNumber":952},{"id":2221,"randomNumber":532},{"id":9276,"randomNumber":3097},{"id":3056,"randomNumber":7293},{"id":6964,"randomNumber":620},{"id":675,"randomNumber":6601},{"id":8414,"randomNumber":6569},{"id":2753,"randomNumber":4065}]
+-->
+<!--#declaration
+#include "world.h"
+
+#define AS_cpoll_cppsp_DO
+
+#ifndef AS_cpoll_cppsp_DO
+static UValue*	pvalue;
+#endif
+static UOrmSession*		psql_updates;
+static UOrmStatement*	pstmt1;
+static UOrmStatement*	pstmt2;
+static World*				pworld_updates;
+static UVector<World*>* pvworld_updates;
+
+static void usp_init_updates()
+{
+	U_TRACE(5, "::usp_init_updates()")
+
+	psql_updates = U_NEW(UOrmSession(U_CONSTANT_TO_PARAM("hello_world")));
+	pstmt1		 = U_NEW(UOrmStatement(*psql_updates, U_CONSTANT_TO_PARAM("SELECT randomNumber FROM World WHERE id = ?")));
+	pstmt2		 = U_NEW(UOrmStatement(*psql_updates, U_CONSTANT_TO_PARAM("UPDATE World SET randomNumber = ? WHERE id = ?")));
+
+	if (pstmt1 == 0 ||
+		 pstmt2 == 0)
+		{
+		U_ERROR("usp_init_updates(): we cound't connect to db, exiting...");
+		}
+
+	pworld_updates  = U_NEW(World);
+	pvworld_updates = U_NEW(UVector<World*>(500));
+
+	pstmt1->use( pworld_updates->id);
+	pstmt1->into(pworld_updates->randomNumber);
+	pstmt2->use( pworld_updates->randomNumber, pworld_updates->id);
+
+#ifndef AS_cpoll_cppsp_DO
+	pvalue = U_NEW(UValue(ARRAY_VALUE));
+#endif
+}
+
+static void usp_end_updates()
+{
+	U_TRACE(5, "::usp_end_updates()")
+
+	delete pstmt1;
+	delete pstmt2;
+	delete psql_updates;
+	delete pvworld_updates;
+	delete pworld_updates;
+#ifndef AS_cpoll_cppsp_DO
+	delete pvalue;
+#endif
+}
+-->
+<!--#args
+queries;
+-->
+<!--#header
+Content-Type: application/json; charset=UTF-8
+-->
+<!--#code
+int i = 0, num_queries = queries.strtol();
+
+	  if (num_queries <   1) num_queries = 1;
+else if (num_queries > 500) num_queries = 500;
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PUTS_CHAR('[');
+#endif
+
+while (true)
+	{
+	pworld_updates->id = u_get_num_random(10000);
+
+	pstmt1->execute();
+
+	U_INTERNAL_DUMP("pworld_updates->randomNumber = %d", pworld_updates->randomNumber)
+
+	pworld_updates->randomNumber = u_get_num_random(10000);
+
+	pstmt2->execute();
+
+#ifdef AS_cpoll_cppsp_DO
+	USP_PRINTF("{\"id\":%d,\"randomNumber\":%d}", pworld_updates->id, pworld_updates->randomNumber);
+#endif
+
+	pvworld_updates->push_back(U_NEW(World(*pworld_updates)));
+
+	if (++i == num_queries) break;
+
+#ifdef AS_cpoll_cppsp_DO
+	USP_PUTS_CHAR(',');
+#endif
+	}
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PUTS_CHAR(']');
+#else
+USP_JSON_stringify(*pvalue, UVector<World*>, *pvworld_updates);
+#endif
+
+pvworld_updates->clear();
+-->

+ 106 - 0
ULib/src/world.h

@@ -0,0 +1,106 @@
+// world.h
+
+#ifndef WORLD_H
+#define WORLD_H 1
+
+#include <ulib/orm/orm.h>
+#include <ulib/json/value.h>
+
+class World {
+public:
+	// Check for memory error
+	U_MEMORY_TEST
+
+	// Allocator e Deallocator
+	U_MEMORY_ALLOCATOR
+	U_MEMORY_DEALLOCATOR
+
+	int id, randomNumber;
+
+	// CONSTRUCTOR
+
+	World()
+		{
+		U_TRACE_REGISTER_OBJECT(5, World, "")
+		}
+
+	World(const World& w) : id(w.id), randomNumber(w.randomNumber)
+		{
+		U_TRACE_REGISTER_OBJECT(5, World, "%p", &w)
+
+		U_MEMORY_TEST_COPY(w)
+		}
+
+	~World()
+		{
+		U_TRACE_UNREGISTER_OBJECT(5, World)
+		}
+
+#ifdef DEBUG
+	const char* dump(bool breset) const
+		{
+		*UObjectIO::os << "id           " << id				<< '\n'
+							<< "randomNumber " << randomNumber;
+
+		if (breset)
+			{
+			UObjectIO::output();
+
+			return UObjectIO::buffer_output;
+			}
+
+		return 0;
+		}
+#endif
+
+private:
+	World& operator=(const World&) { return *this; }
+};
+
+// ORM TEMPLATE SPECIALIZATIONS
+
+template <> class U_EXPORT UOrmTypeHandler<World> : public UOrmTypeHandler_Base {
+public:
+	explicit UOrmTypeHandler(World& val) : UOrmTypeHandler_Base(&val) {}
+
+	void bindParam(UOrmStatement* stmt) const
+		{
+		U_TRACE(0, "UOrmTypeHandler<World>::bindParam(%p)", stmt)
+
+		stmt->bindParam(U_ORM_TYPE_HANDLER(World, id,				int));
+		stmt->bindParam(U_ORM_TYPE_HANDLER(World, randomNumber,	int));
+		}
+
+	void bindResult(UOrmStatement* stmt)
+		{
+		U_TRACE(0, "UOrmTypeHandler<World>::bindResult(%p)", stmt)
+
+		stmt->bindResult(U_ORM_TYPE_HANDLER(World, id,				int));
+		stmt->bindResult(U_ORM_TYPE_HANDLER(World, randomNumber,	int));
+		}
+};
+
+// JSON TEMPLATE SPECIALIZATIONS
+
+template <> class U_EXPORT UJsonTypeHandler<World> : public UJsonTypeHandler_Base {
+public:
+	explicit UJsonTypeHandler(World& val) : UJsonTypeHandler_Base(&val) {}
+
+	void toJSON(UValue& json)
+		{
+		U_TRACE(0, "UJsonTypeHandler<World>::toJSON(%p)", &json)
+
+		json.toJSON(U_JSON_TYPE_HANDLER(World, id,				int));
+		json.toJSON(U_JSON_TYPE_HANDLER(World, randomNumber,	int));
+		}
+
+	void fromJSON(UValue& json)
+		{
+		U_TRACE(0, "UJsonTypeHandler<World>::fromJSON(%p)", &json)
+
+		json.fromJSON(U_JSON_TYPE_HANDLER(World, id,				 int));
+		json.fromJSON(U_JSON_TYPE_HANDLER(World, randomNumber, int));
+		}
+};
+
+#endif

+ 1 - 0
dart-redstone/.gitignore

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

+ 65 - 0
dart-redstone/README.md

@@ -0,0 +1,65 @@
+# Redstone.dart Framework Benchmarking Test
+
+This test adds [Redstone.dart](http://redstonedart.org), a microframework for Dart, to the [benchmarking test suite](../). The test is based on the Dart Benchmarking Test.
+
+## Versions
+
+* [Dart SDK version >=1.3.0](https://launchpad.net/~hachre/+archive/dart)
+* [Dart args version 0.10.0+2](http://pub.dartlang.org/packages/args)
+* [Dart crypto version 0.9.0](http://pub.dartlang.org/packages/crypto)
+* [Dart mustache version 0.1.8](http://pub.dartlang.org/packages/mustache)
+* [Dart mongo_dart version 0.1.42](http://pub.dartlang.org/packages/mongo_dart)
+* [Dart postgresql version 0.2.13](http://pub.dartlang.org/packages/postgresql)
+* [Dart redstone version 0.5.9](http://pub.dartlang.org/packages/redstone)
+* [Dart yaml version 0.9.0](http://pub.dartlang.org/packages/yaml)
+* [Dart redstone_mapper version 0.1.3](http://pub.dartlang.org/packages/redstone_mapper)
+* [Dart redstone_mapper_mongo version 0.1.1+1](http://pub.dartlang.org/packages/redstone_mapper_mongo)
+* [Dart redstone_mapper_pg version 0.1.1](http://pub.dartlang.org/packages/redstone_mapper_pg)
+
+## Test URLs
+
+### Common
+
+#### JSON Encoding Test
+http://localhost:8080/json
+
+#### Plaintext Test
+http://localhost:8080/plaintext
+
+
+### PostgreSQL
+
+#### Data-Store/Database Mapping Test
+http://localhost:8080/pg/db
+
+#### Variable Query Test
+http://localhost:8080/pg/queries?queries=2
+
+#### Fortunes Test
+http://localhost:8080/pg/fortunes
+
+#### Data-Store/Database Update Test
+http://localhost:8080/pg/updates
+
+#### Variable Update Test
+http://localhost:8080/pg/updates?queries=2
+
+
+### MongoDB
+
+#### Data-Store/Database Mapping Test
+http://localhost:8080/mongo/db
+
+#### Variable Query Test
+http://localhost:8080/mongo/queries?queries=2
+
+#### Fortunes Test
+http://localhost:8080/mongo/fortunes
+
+#### Data-Store/Database Update Test
+http://localhost:8080/mongo/updates
+
+#### Variable Update Test
+http://localhost:8080/mongo/updates?queries=2
+
+

+ 0 - 0
dart-redstone/__init__.py


+ 49 - 0
dart-redstone/benchmark_config

@@ -0,0 +1,49 @@
+{
+  "framework": "redstone",
+  "tests": [{
+    "postgresql": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "db_url": "/pg/db",
+      "query_url": "/pg/queries?queries=",
+      "fortune_url": "/pg/fortunes",
+      "update_url": "/pg/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "redstone",
+      "language": "Dart",
+      "orm": "Micro",
+      "platform": "Dart",
+      "webserver": "nginx",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "redstone-postgresql",
+      "notes": "",
+      "versus": "dart"
+    },
+    "mongodb": {
+      "setup_file": "setup",
+      "db_url": "/mongo/db",
+      "query_url": "/mongo/queries?queries=",
+      "fortune_url": "/mongo/fortunes",
+      "update_url": "/mongo/updates?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "MongoDB",
+      "framework": "redstone",
+      "language": "Dart",
+      "orm": "Micro",
+      "platform": "Dart",
+      "webserver": "nginx",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "redstone-mongodb",
+      "notes": "",
+      "versus": "dart"
+    }
+  }]
+}

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

+ 3 - 0
dart-redstone/install.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+fw_depends dart nginx

+ 2 - 0
dart-redstone/mongodb.yaml

@@ -0,0 +1,2 @@
+host: localhost
+database: hello_world

+ 5 - 0
dart-redstone/postgresql.yaml

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

+ 15 - 0
dart-redstone/pubspec.yaml

@@ -0,0 +1,15 @@
+name: DartRedstoneBenchmark
+description: A benchmark of Redstone.dart, a server-side microframework for Dart
+environment:
+  sdk: ">=1.3.0 <2.0.0"
+dependencies:
+  args: 0.10.0+2
+  crypto: 0.9.0
+  mongo_dart: 0.1.42
+  mustache: 0.1.8
+  postgresql: 0.2.13
+  redstone: 0.5.9
+  redstone_mapper: 0.1.3
+  redstone_mapper_mongo: 0.1.1+1
+  redstone_mapper_pg: 0.1.1
+  yaml: 0.9.0

+ 266 - 0
dart-redstone/server.dart

@@ -0,0 +1,266 @@
+import "dart:core";
+import "dart:io";
+import 'dart:async' show Future;
+import 'dart:math' show Random;
+import "package:redstone/server.dart" as app;
+import "package:redstone_mapper/mapper.dart";
+import "package:redstone_mapper/plugin.dart";
+import "package:redstone_mapper_mongo/manager.dart";
+import "package:redstone_mapper_pg/manager.dart";
+import "package:postgresql/postgresql.dart" as pg;
+import "package:di/di.dart";
+import "package:args/args.dart";
+import 'package:yaml/yaml.dart' as yaml;
+import 'package:mustache/mustache.dart' as mustache;
+
+const _WORLD_TABLE_SIZE = 10000;
+
+final _RANDOM = new Random();
+
+class Fortune implements Comparable<Fortune> {
+  
+  @Field()
+  int id;
+  
+  @Field()
+  String message;
+  
+  Fortune([this.id, this.message]);
+
+  compareTo(Fortune other) => message.compareTo(other.message);
+  
+}
+
+class World {
+  
+  @Field()
+  int id;
+
+  @Field(model: "randomnumber")
+  int randomNumber;
+
+}
+
+class MongoFortune implements Comparable<MongoFortune> {
+  
+  int _id;
+  
+  @Field(model: "_id")
+  int get id => _id;
+  
+  @Field(model: "_id")
+  set id(num value) => _id = value.toInt();
+  
+  @Field()
+  String message;
+  
+  MongoFortune([this._id, this.message]);
+
+  compareTo(MongoFortune other) => message.compareTo(other.message);
+  
+}
+
+class MongoWorld {
+  
+  int _id;
+  int _randomNumber;
+    
+  @Field(model: "_id")
+  int get id => _id;
+  
+  @Field(model: "_id")
+  set id(num value) => _id = value.toInt();
+
+  @Field()
+  int get randomNumber => _randomNumber;
+  
+  @Field()
+  set randomNumber(num value) => _randomNumber = value.toInt();
+
+}
+
+///Handle PostgreSql connections
[email protected](r'/pg/.+')
+pgSqlManager(PostgreSqlManager pgSql) {
+  pgSql.getConnection().then((conn) {
+    app.request.attributes["dbConn"] = conn;
+    app.chain.next(() {
+      pgSql.closeConnection(conn, error: app.chain.error);
+    });
+  });
+}
+
+///Handle MongoDb connections
[email protected](r'/mongo/.+')
+mongoDbManager(MongoDbManager mongoDb) {
+  mongoDb.getConnection().then((conn) {
+    app.request.attributes["dbConn"] = conn;
+    app.chain.next(() {
+      mongoDb.closeConnection(conn, error: app.chain.error);
+    });
+  });
+}
+
+///JSON test
[email protected]("/json")
+getJson() => {"message": "Hello, World!"};
+
+///PlainText test
[email protected]("/plaintext")
+getPlainText() => "Hello, World!";
+
+///PostgreSql tests
[email protected]("/pg")
+@Encode()
+class PgTests {
+  
+  static const worldQuery = 'SELECT id, randomnumber FROM world WHERE id = @id;';
+  static const worldUpdt = 'UPDATE world SET randomnumber = @randomnumber WHERE id = @id;';
+  
+  static const fortuneQuery = 'SELECT id, message FROM fortune;';
+  
+  PostgreSql get pgSql => app.request.attributes["dbConn"];
+  
+  @app.Route("/db")
+  Future<World> queryTest() {
+    var params = { 'id': _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1 };
+    return pgSql.query(worldQuery, World, params).then((list) => list[0]);
+  }
+
+  @app.Route("/queries")
+  Future<List<World>> queriesTest() {
+    var queries = _parseQueriesParam(app.request.queryParams.queries);
+    return Future.wait(new List.generate(queries, (_) => queryTest()));
+  } 
+  
+  @app.Route("/updates")
+  Future<List<World>> updateTest() {
+    var queries = _parseQueriesParam(app.request.queryParams.queries);
+    return Future.wait(new List.generate(queries, (_) => queryTest().then((world) {
+      world.randomNumber = _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1;
+      return pgSql.execute(worldUpdt, world).then((_) => world);
+    })));
+  }
+  
+  @app.Route("/fortunes", responseType: "text/html")
+  Future<String> fortunesTest(@app.Inject() mustache.Template template) {
+    return pgSql.query(fortuneQuery, Fortune).then((values) {
+      values
+          ..add(new Fortune(0, 'Additional fortune added at request time.'))
+          ..sort();
+      
+      return template.renderString({
+        "fortunes": encode(values)
+      });
+    });
+  }
+  
+}
+
+///MongoDb tests
[email protected]("/mongo")
+@Encode()
+class MongoTests {
+  
+  static const worldCollection = "World";
+  static const fortuneCollection = "Fortune";
+  
+  MongoDb get mongoDb => app.request.attributes["dbConn"];
+  
+  @app.Route("/db")
+  Future<MongoWorld> queryTest() {
+    return mongoDb.findOne(worldCollection, MongoWorld, {
+      "_id": _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1
+    });
+  }
+  
+  @app.Route("/queries")
+  Future<List<MongoWorld>> queriesTest() {
+    var queries = _parseQueriesParam(app.request.queryParams.queries);
+    return Future.wait(new List.generate(queries, (_) => queryTest()));
+  }
+  
+  @app.Route("/updates")
+  Future<List<MongoWorld>> updateTest() {
+    var queries = _parseQueriesParam(app.request.queryParams.queries);
+    return Future.wait(new List.generate(queries, (_) => queryTest().then((world) {
+      world.randomNumber = _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1;
+      return mongoDb.update(worldCollection, { "_id": world.id }, world)
+          .then((_) => world);
+    })));
+  }
+  
+  @app.Route("/fortunes", responseType: "text/html")
+  Future<String> fortunesTest(@app.Inject() mustache.Template template) {
+    return mongoDb.find(fortuneCollection, MongoFortune).then((values) {
+      values
+          ..add(new MongoFortune(0, 'Additional fortune added at request time.'))
+          ..sort();
+      
+      return template.renderString({
+        "fortunes": encode(values)
+      });
+    });
+  }
+  
+}
+
+main(List<String> args) {
+  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(args);
+  var dbConnections = int.parse(arguments["dbconnections"]);
+  
+  MongoDbManager mongoDbManager;
+  PostgreSqlManager pgSqlManager;
+  mustache.Template fortunesTemplate;
+  
+  Future.wait([
+     
+    //load PostgreSql configuration
+    new File("postgresql.yaml").readAsString().then((config){
+      pgSqlManager = new PostgreSqlManager(
+          new pg.Settings.fromMap(yaml.loadYaml(config)).toUri(),
+          min: dbConnections,
+          max: dbConnections);
+    }),
+    
+    //load MongoDb configuration
+    new File("mongodb.yaml").readAsString().then((config) {
+      var mongoConfig = yaml.loadYaml(config);
+      mongoDbManager = new MongoDbManager(
+          "mongodb://${mongoConfig["host"]}/${mongoConfig["database"]}", 
+          poolSize: dbConnections);
+    }),
+    
+    //load fortunes mustache template
+    new File('fortunes.mustache').readAsString().then((template) {
+      fortunesTemplate = mustache.parse(template);
+    })
+    
+  ]).then((_) {
+    
+    //app.setupConsoleLog();
+    
+    //install module for dependency injection
+    app.addModule(new Module()
+                      ..bind(MongoDbManager, toValue: mongoDbManager)
+                      ..bind(PostgreSqlManager, toValue: pgSqlManager)
+                      ..bind(mustache.Template, toValue: fortunesTemplate));
+    
+    //initialize mapper plugin
+    app.addPlugin(getMapperPlugin());
+    
+    //start the server
+    app.start(address: arguments["address"], port: int.parse(arguments["port"]));
+    
+  });
+}
+
+_parseQueriesParam(param) {
+  return param == null || param.isEmpty ? 1 : 
+    int.parse(param, radix: 10, onError: (_) => 1).clamp(1, 500);
+}

+ 75 - 0
dart-redstone/setup.py

@@ -0,0 +1,75 @@
+import subprocess
+import sys
+import setup_util
+import os
+
+def start(args, logfile, errfile):
+  setup_util.replace_text('dart-redstone/postgresql.yaml', 'host: .*', 'host: ' + args.database_host)
+  setup_util.replace_text('dart-redstone/mongodb.yaml', 'host: .*', 'host: ' + args.database_host)
+  try:
+    #
+    # install dart dependencies
+    #
+    subprocess.check_call('pub upgrade', shell=True, cwd='dart-redstone', stderr=errfile, stdout=logfile)
+    #
+    # 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-redstone', stderr=errfile, stdout=logfile)
+    #
+    # create nginx configuration
+    #
+    conf = []
+    conf.append('worker_processes ' + str(args.max_threads) + ';')
+    conf.append('error_log /dev/null error;')
+    conf.append('events {')
+    conf.append('    worker_connections 1024;')
+    conf.append('}')
+    conf.append('http {')
+    conf.append('    access_log off;')
+    conf.append('    include /usr/local/nginx/conf/mime.types;')
+    conf.append('    default_type application/octet-stream;')
+    conf.append('    sendfile on;')
+    conf.append('    upstream dart_cluster {')
+    for port in range(9001, 9001 + args.max_threads):
+      conf.append('        server 127.0.0.1:' + str(port) + ';')
+    conf.append('        keepalive ' + str(args.max_concurrency / args.max_threads) + ';')
+    conf.append('    }')
+    conf.append('    server {')
+    conf.append('        listen 8080;')
+    conf.append('        location / {')
+    conf.append('            proxy_pass http://dart_cluster;')
+    conf.append('            proxy_http_version 1.1;')
+    conf.append('            proxy_set_header Connection "";')
+    conf.append('        }')
+    conf.append('    }')
+    conf.append('}')
+    #
+    # write nginx configuration to disk
+    #
+    with open('dart-redstone/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-redstone', stderr=errfile, stdout=logfile);
+    return 0
+  except subprocess.CalledProcessError:
+    return 1
+
+def stop(logfile, errfile):
+  #
+  # stop nginx
+  #
+  subprocess.check_call('sudo /usr/local/nginx/sbin/nginx -c `pwd`/nginx.conf -s stop', shell=True, cwd='dart-redstone', stderr=errfile, stdout=logfile)
+  os.remove('dart-redstone/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, 15)
+  return 0

+ 5 - 1
toolset/benchmark/fortune_html_parser.py

@@ -1,4 +1,5 @@
 # -*- coding: utf-8
+import re
 from HTMLParser import HTMLParser
 from difflib import unified_diff
 
@@ -113,5 +114,8 @@ class FortuneHTMLParser(HTMLParser):
     diff = self.valid == body
     if not diff:
       out.write("Fortune invalid. Diff following:\n")
-      out.write(''.join(unified_diff(self.valid, body, fromfile="Valid", tofile="Output")) + '\n')
+      diff_str = ''.join(unified_diff(self.valid.split(' '), body.split(' '), fromfile='Valid', tofile='Response', n=5))
+      #diff_str = re.sub(r'(?<![ +]) (?![ +])', '', diff_str)
+      diff_str = re.sub(r'  ', ' ', diff_str)
+      out.write(diff_str)
     return diff

+ 62 - 33
toolset/benchmark/framework_test.py

@@ -147,6 +147,9 @@ class FrameworkTest:
   ############################################################
   def validateJson(self, jsonString, out, err):
     err_str = ""
+    if jsonString is None or len(jsonString) == 0:
+      err_str += "Empty Response"
+      return (False, err_str)
     try:
       obj = {k.lower(): v for k,v in json.loads(jsonString).items()}
       if "message" not in obj:
@@ -154,7 +157,7 @@ class FrameworkTest:
       if  obj["message"].lower() == "hello, world!":
         err_str += "Message was '{message}', should have been 'Hello, World!' ".format(message=obj["message"])
     except:
-      err_str += "Got exception when trying to validate the JSON test: {exception}".format(exception=sys.exc_info()[0:2])
+      err_str += "Got exception when trying to validate the JSON test: {exception}".format(exception=traceback.print_exc())
     return (True, ) if len(err_str) == 0 else (False, err_str)
 
   ############################################################
@@ -164,6 +167,9 @@ class FrameworkTest:
   ############################################################
   def validateDb(self, jsonString, out, err):
     err_str = ""
+    if jsonString is None or len(jsonString) == 0:
+      err_str += "Empty Response"
+      return (False, err_str)
     try:
       obj = {k.lower(): v for k,v in json.loads(jsonString).items()}
 
@@ -195,13 +201,15 @@ class FrameworkTest:
         random_num_ret_val=False
       if not random_num_ret_val:
         err_str += "Expected id to be type int or float, got '{rand}' ".format(rand=obj["randomnumber"])
-      return id_ret_val and random_num_ret_val
     except:
-      err_str += "Got exception when trying to validate the db test: {exception}".format(exception=sys.exc_info()[0:2])
+      err_str += "Got exception when trying to validate the db test: {exception}".format(exception=traceback.print_exc())
     return (True, ) if len(err_str) == 0 else (False, err_str)
 
   def validateDbStrict(self, jsonString, out, err):
     err_str = ""
+    if jsonString is None or len(jsonString) == 0:
+      err_str += "Empty Response"
+      return (False, err_str)
     try:
       obj = {k.lower(): v for k,v in json.loads(jsonString).items()}
 
@@ -226,7 +234,7 @@ class FrameworkTest:
         err_str += "Expected id to be type int or float, got '{rand}' ".format(rand=obj["randomnumber"])
       return id_ret_val and random_num_ret_val
     except:
-      err_str += "Got exception when trying to validate the db test: {exception}".format(exception=sys.exc_info()[0:2])
+      err_str += "Got exception when trying to validate the db test: {exception}".format(exception=traceback.print_exc())
     return (True, ) if len(err_str) == 0 else (False, err_str)
 
 
@@ -238,6 +246,9 @@ class FrameworkTest:
   ############################################################
   def validateQuery(self, jsonString, out, err):
     err_str = ""
+    if jsonString is None or len(jsonString) == 0:
+      err_str += "Empty Response"
+      return (False, err_str)
     try:
       arr = [{k.lower(): v for k,v in d.items()} for d in json.loads(jsonString)]
       if len(arr) != 2:
@@ -263,7 +274,7 @@ class FrameworkTest:
         if not random_num_ret_val:
           err_str += "Expected randomNumber to be type int or float, got '{rand}' ".format(rand=obj["randomnumber"])
     except:
-      err_str += "Got exception when trying to validate the query test: {exception}".format(exception=sys.exc_info()[0:2])
+      err_str += "Got exception when trying to validate the query test: {exception}".format(exception=traceback.print_exc())
     return (True, ) if len(err_str) == 0 else (False, err_str)
 
   ############################################################
@@ -274,6 +285,9 @@ class FrameworkTest:
   ############################################################
   def validateQueryOneOrLess(self, jsonString, out, err):
     err_str = ""
+    if jsonString is None or len(jsonString) == 0:
+      err_str += "Empty Response"
+      return (False, err_str)
     try:
       json_load = json.loads(jsonString)
       if isinstance(json_load, list):
@@ -304,7 +318,7 @@ class FrameworkTest:
           ret_val = False
       return ret_val
     except:
-      err_str += "Got exception when trying to validate the query test: {exception} ".format(exception=sys.exc_info()[0:2])
+      err_str += "Got exception when trying to validate the query test: {exception} ".format(exception=traceback.print_exc())
     return (True, ) if len(err_str) == 0 else (False, err_str)
 
   ############################################################
@@ -315,8 +329,11 @@ class FrameworkTest:
   ############################################################
   def validateQueryFiveHundredOrMore(self, jsonString, out, err):
     err_str = ""
+    if jsonString is None or len(jsonString) == 0:
+      err_str += "Empty Response"
+      return (False, err_str)
     try:
-      arr = {k.lower(): v for k,v in json.loads(jsonString).items()}
+      arr = [{k.lower(): v for k,v in d.items()} for d in json.loads(jsonString)]
 
       if len(arr) != 500:
         err_str += "Expected array of length 500. Got length {length}. ".format(length=len(arr))
@@ -343,7 +360,7 @@ class FrameworkTest:
         if not random_num_ret_val:
           err_str += "Expected randomNumber to be type int or float, got '{rand}'. ".format(rand=obj["randomnumber"])
     except:
-      err_str += "Got exception when trying to validate the query test: {exception} ".format(exception=sys.exc_info()[0:2])
+      err_str += "Got exception when trying to validate the query test: {exception} ".format(exception=traceback.print_exc())
     return (True, ) if len(err_str) == 0 else (False, err_str)
 
   ############################################################
@@ -352,13 +369,16 @@ class FrameworkTest:
   ############################################################
   def validateFortune(self, htmlString, out, err):
     err_str = ""
+    if htmlString is None or len(htmlString) == 0:
+      err_str += "Empty Response"
+      return (False, err_str)
     try:
       parser = FortuneHTMLParser()
       parser.feed(htmlString)
 
       return parser.isValidFortune(out)
     except:
-      print "Got exception when trying to validate the fortune test: {exception} ".format(exception=sys.exc_info()[0:2])
+      print "Got exception when trying to validate the fortune test: {exception} ".format(exception=traceback.print_exc())
     return False
 
   ############################################################
@@ -369,9 +389,12 @@ class FrameworkTest:
   ############################################################
   def validateUpdate(self, jsonString, out, err):
     err_str = ""
+    if jsonString is None or len(jsonString) == 0:
+      err_str += "Empty Response"
+      return (False, err_str)
     try:
       arr = [{k.lower(): v for k,v in d.items()} for d in json.loads(jsonString)]
-      ret_val = True
+      print arr
       if len(arr) != 2:
         err_str += "Expected array of length 2. Got length {length}.\n".format(length=len(arr))
       for obj in arr:
@@ -395,7 +418,7 @@ class FrameworkTest:
         if not random_num_ret_val:
           err_str += "Expected randomNumber to be type int or float, got '{rand}'.\n".format(rand=obj["randomnumber"])
     except:
-      err_str += "Got exception when trying to validate the update test: {exception}\n".format(exception=sys.exc_info()[0:2])
+      err_str += "Got exception when trying to validate the update test: {exception}\n".format(exception=traceback.print_exc())
     return (True, ) if len(err_str) == 0 else (False, err_str)
 
   ############################################################
@@ -403,11 +426,14 @@ class FrameworkTest:
   ############################################################
   def validatePlaintext(self, jsonString, out, err):
     err_str = ""
+    if jsonString is None or len(jsonString) == 0:
+      err_str += "Empty Response"
+      return (False, err_str)
     try:
       if not jsonString.lower().strip() == "hello, world!":
         err_str += "Expected 'Hello, World!', got '{message}'.\n".format(message=jsonString.strip())
     except:
-      err_str += "Got exception when trying to validate the plaintext test: {exception}\n".format(exception=sys.exc_info()[0:2])
+      err_str += "Got exception when trying to validate the plaintext test: {exception}\n".format(exception=traceback.print_exc())
     return (True, ) if len(err_str) == 0 else (False, err_str)
 
   ############################################################
@@ -462,14 +488,15 @@ class FrameworkTest:
       url = self.benchmarker.generate_url(self.json_url, self.port)
       output = self.__curl_url(url, self.JSON, out, err)
       out.write("VALIDATING JSON ... ")
-      if self.validateJson(output, out, err):
+      ret_tuple = self.validateJson(output, out, err)
+      if ret_tuple[0]:
         self.json_url_passed = True
         out.write("PASS\n\n")
       else:
         self.json_url_passed = False
-        out.write("\nFAIL\n\n" + ret_tuple[1])
+        out.write("\nFAIL" + ret_tuple[1] + "\n\n")
         result = False
-      out.flush
+      out.flush()
 
     # DB
     if self.runTests[self.DB]:
@@ -482,11 +509,13 @@ class FrameworkTest:
 
       url = self.benchmarker.generate_url(self.db_url, self.port)
       output = self.__curl_url(url, self.DB, out, err)
-      if self.validateDb(output, out, err):
+      validate_ret_tuple = self.validateDb(output, out, err)
+      validate_strict_ret_tuple = self.validateDbStrict(output, out, err)
+      if validate_ret_tuple[0]:
         self.db_url_passed = True
       else:
         self.db_url_passed = False
-      if self.validateDbStrict(output, out, err):
+      if validate_strict_ret_tuple:
         self.db_url_warn = False
       else:
         self.db_url_warn = True
@@ -495,12 +524,12 @@ class FrameworkTest:
       if self.db_url_passed:
         out.write("PASS")
         if self.db_url_warn:
-          out.write(" (with warnings)")
+          out.write(" (with warnings) " + validate_strict_ret_tuple[1])
         out.write("\n\n")
       else:
-        out.write("\nFAIL\n\n" + ret_tuple[1])
+        out.write("\nFAIL" + validate_ret_tuple[1])
         result = False
-      out.flush
+      out.flush()
 
     # Query
     if self.runTests[self.QUERY]:
@@ -519,7 +548,7 @@ class FrameworkTest:
         out.write(self.query_url + "2 - PASS\n\n")
       else:
         self.query_url_passed = False
-        out.write(self.query_url + "2 - FAIL\n\n" + ret_tuple[1])
+        out.write(self.query_url + "2 - FAIL " + ret_tuple[1] + "\n\n")
       out.write("-----------------------------------------------------\n\n")
       out.flush()
 
@@ -529,7 +558,7 @@ class FrameworkTest:
       ret_tuple = self.validateQueryOneOrLess(output2, out, err)
       if not ret_tuple[0]:
         self.query_url_warn = True
-        out.write(self.query_url + "0 - WARNING\n\n" + ret_tuple[1])
+        out.write(self.query_url + "0 - WARNING " + ret_tuple[1] + "\n\n")
       else:
         out.write(self.query_url + "0 - PASS\n\n")
       out.write("-----------------------------------------------------\n\n")
@@ -540,7 +569,7 @@ class FrameworkTest:
       ret_tuple = self.validateQueryOneOrLess(output3, out, err)
       if not ret_tuple[0]:
         self.query_url_warn = True
-        out.write(self.query_url + "foo - WARNING\n\n" + ret_tuple[1])
+        out.write(self.query_url + "foo - WARNING " + ret_tuple[1] + "\n\n")
       else:
         out.write(self.query_url + "foo - PASS\n\n")
       out.write("-----------------------------------------------------\n\n")
@@ -551,7 +580,7 @@ class FrameworkTest:
       ret_tuple = self.validateQueryFiveHundredOrMore(output4, out, err)
       if not ret_tuple[0]:
         self.query_url_warn = True
-        out.write(self.query_url + "501 - WARNING\n\n" + ret_tuple[1])
+        out.write(self.query_url + "501 - WARNING " + ret_tuple[1] + "\n\n")
       else:
         out.write(self.query_url + "501 - PASS\n\n")
       out.write("-----------------------------------------------------\n\n\n")
@@ -564,9 +593,9 @@ class FrameworkTest:
           out.write(" (with warnings)")
         out.write("\n\n")
       else:
-        out.write("\nFAIL\n\n" + ret_tuple[1])
+        out.write("\nFAIL " + ret_tuple[1] + "\n\n")
         result = False
-      out.flush
+      out.flush()
 
     # Fortune
     if self.runTests[self.FORTUNE]:
@@ -587,7 +616,7 @@ class FrameworkTest:
         self.fortune_url_passed = False
         out.write("\nFAIL\n\n")
         result = False
-      out.flush
+      out.flush()
 
     # Update
     if self.runTests[self.UPDATE]:
@@ -601,14 +630,15 @@ class FrameworkTest:
       url = self.benchmarker.generate_url(self.update_url + "2", self.port)
       output = self.__curl_url(url, self.UPDATE, out, err)
       out.write("VALIDATING UPDATE ... ")
-      if self.validateUpdate(output, out, err):
+      ret_tuple = self.validateUpdate(output, out, err)
+      if ret_tuple[0]:
         self.update_url_passed = True
         out.write("PASS\n\n")
       else:
         self.update_url_passed = False
-        out.write("\nFAIL\n\n" + ret_tuple[1])
+        out.write("\nFAIL " + ret_tuple[1] + "\n\n")
         result = False
-      out.flush
+      out.flush()
 
     # plaintext
     if self.runTests[self.PLAINTEXT]:
@@ -623,15 +653,14 @@ class FrameworkTest:
       output = self.__curl_url(url, self.PLAINTEXT, out, err)
       out.write("VALIDATING PLAINTEXT ... ")
       ret_tuple = self.validatePlaintext(output, out, err)
-      print ret_tuple
       if ret_tuple[0]:
         self.plaintext_url_passed = True
         out.write("PASS\n\n")
       else:
         self.plaintext_url_passed = False
-        out.write(ret_tuple[1] + "\nFAIL\n\n")
+        out.write("\nFAIL\n\n" + ret_tuple[1] + "\n\n")
         result = False
-      out.flush
+      out.flush()
 
     return result
   ############################################################