Browse Source

Update Hexagon Benchmark (#2414)

* Clean benchmark implementation

* Update Hexagon benchmark

* Benchmark tests fix

* Benchmark tests fix

* Change Resin test port and remove unneeded code

* Fix fortunes test

* Update 'setup' scripts to TFB latest version
Juanjo Aguililla 8 years ago
parent
commit
46aff3be8f

+ 0 - 1
frameworks/Kotlin/hexagon/.gitignore

@@ -2,4 +2,3 @@
 build/
 build/
 .gradle/
 .gradle/
 
 
-log/

+ 33 - 4
frameworks/Kotlin/hexagon/benchmark_config.json

@@ -6,24 +6,53 @@
                 "json_url" : "/json",
                 "json_url" : "/json",
                 "db_url" : "/db",
                 "db_url" : "/db",
                 "query_url" : "/query?queries=",
                 "query_url" : "/query?queries=",
-                "fortune_url" : "/fortune",
+                "fortune_url" : "/fortunes",
                 "update_url" : "/update?queries=",
                 "update_url" : "/update?queries=",
                 "plaintext_url" : "/plaintext",
                 "plaintext_url" : "/plaintext",
                 "port" : 9090,
                 "port" : 9090,
+
+                "approach" : "Realistic",
+                "classification" : "Micro",
+                "database" : "MongoDB",
+                "framework" : "Hexagon",
+                "language" : "Kotlin",
+                "orm" : "Raw",
+                "platform" : "Servlet",
+                "webserver" : "None",
+                "os" : "Linux",
+                "database_os" : "Linux",
+                "display_name" : "Hexagon MongoDB",
+                "notes" : "http://there4.co/hexagon",
+
                 "setup_file" : "setup",
                 "setup_file" : "setup",
+                "versus" : "servlet"
+            }
+        },
+        {
+            "resin" : {
+                "json_url" : "/json",
+                "db_url" : "/db",
+                "query_url" : "/query?queries=",
+                "fortune_url" : "/fortunes",
+                "update_url" : "/update?queries=",
+                "plaintext_url" : "/plaintext",
+
+                "port" : 8080,
+
                 "approach" : "Realistic",
                 "approach" : "Realistic",
                 "classification" : "Micro",
                 "classification" : "Micro",
                 "database" : "MongoDB",
                 "database" : "MongoDB",
                 "framework" : "Hexagon",
                 "framework" : "Hexagon",
                 "language" : "Kotlin",
                 "language" : "Kotlin",
-                "flavor" : "Java8",
                 "orm" : "Raw",
                 "orm" : "Raw",
-                "platform" : "Netty",
+                "platform" : "Servlet",
                 "webserver" : "None",
                 "webserver" : "None",
                 "os" : "Linux",
                 "os" : "Linux",
                 "database_os" : "Linux",
                 "database_os" : "Linux",
-                "display_name" : "Hexagon",
+                "display_name" : "Hexagon Resin",
                 "notes" : "http://there4.co/hexagon",
                 "notes" : "http://there4.co/hexagon",
+
+                "setup_file" : "setup_resin",
                 "versus" : "servlet"
                 "versus" : "servlet"
             }
             }
         }
         }

+ 15 - 3
frameworks/Kotlin/hexagon/build.gradle

@@ -10,14 +10,26 @@ buildscript {
     }
     }
 }
 }
 
 
-apply from: "$gradleScripts/hexagon_service.gradle"
+apply from: "$gradleScripts/hexagon.gradle"
+apply from: "$gradleScripts/service.gradle"
+apply plugin: 'war'
 
 
 mainClassName = "co.there4.hexagon.BenchmarkKt"
 mainClassName = "co.there4.hexagon.BenchmarkKt"
 applicationDefaultJvmArgs = [
 applicationDefaultJvmArgs = [
-    '-Xms64M',
-    '-Xmx64M',
+    '-Xms512M',
+    '-Xmx1024M',
     '-server',
     '-server',
     '-XX:+UseNUMA',
     '-XX:+UseNUMA',
     '-XX:+UseParallelGC',
     '-XX:+UseParallelGC',
     '-XX:+AggressiveOpts'
     '-XX:+AggressiveOpts'
 ]
 ]
+
+clean {
+    delete "systems/load_test.jtl", "systems/jmeter.log"
+}
+
+war {
+    archiveName = "ROOT.war"
+}
+
+installDist.dependsOn 'war'

+ 4 - 4
frameworks/Kotlin/hexagon/gradle.properties

@@ -3,9 +3,9 @@ version=1.0.0
 group=co.there4.hexagon
 group=co.there4.hexagon
 description=Hexagon web framework's benchmark
 description=Hexagon web framework's benchmark
 
 
-wrapperGradleVersion=3.0
+wrapperGradleVersion=3.2.1
 gradleScripts=https://raw.githubusercontent.com/jaguililla/hexagon/master/gradle
 gradleScripts=https://raw.githubusercontent.com/jaguililla/hexagon/master/gradle
 
 
-dokkaVersion=0.9.+
-kotlinVersion=1.0.3
-hexagonVersion=0.9.2
+dokkaVersion=0.9.11
+kotlinVersion=1.0.5-2
+hexagonVersion=0.9.11

+ 41 - 29
frameworks/Kotlin/hexagon/gradlew → frameworks/Kotlin/hexagon/gradle/wrapper

@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
 
 
 ##############################################################################
 ##############################################################################
 ##
 ##
@@ -6,12 +6,30 @@
 ##
 ##
 ##############################################################################
 ##############################################################################
 
 
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
 
 
 APP_NAME="Gradle"
 APP_NAME="Gradle"
 APP_BASE_NAME=`basename "$0"`
 APP_BASE_NAME=`basename "$0"`
 
 
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 MAX_FD="maximum"
 MAX_FD="maximum"
 
 
@@ -30,6 +48,7 @@ die ( ) {
 cygwin=false
 cygwin=false
 msys=false
 msys=false
 darwin=false
 darwin=false
+nonstop=false
 case "`uname`" in
 case "`uname`" in
   CYGWIN* )
   CYGWIN* )
     cygwin=true
     cygwin=true
@@ -40,27 +59,12 @@ case "`uname`" in
   MINGW* )
   MINGW* )
     msys=true
     msys=true
     ;;
     ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
 esac
 esac
 
 
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
-    ls=`ls -ld "$PRG"`
-    link=`expr "$ls" : '.*-> \(.*\)$'`
-    if expr "$link" : '/.*' > /dev/null; then
-        PRG="$link"
-    else
-        PRG=`dirname "$PRG"`"/$link"
-    fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+CLASSPATH=$APP_HOME/wrapper.jar
 
 
 # Determine the Java command to use to start the JVM.
 # Determine the Java command to use to start the JVM.
 if [ -n "$JAVA_HOME" ] ; then
 if [ -n "$JAVA_HOME" ] ; then
@@ -85,7 +89,7 @@ location of your Java installation."
 fi
 fi
 
 
 # Increase the maximum file descriptors if we can.
 # Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
     MAX_FD_LIMIT=`ulimit -H -n`
     MAX_FD_LIMIT=`ulimit -H -n`
     if [ $? -eq 0 ] ; then
     if [ $? -eq 0 ] ; then
         if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
         if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -150,11 +154,19 @@ if $cygwin ; then
     esac
     esac
 fi
 fi
 
 
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
-    JVM_OPTS=("$@")
+# Escape application args
+save ( ) {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
 }
 }
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
 
 
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+exec "$JAVACMD" "$@"

+ 5 - 11
frameworks/Kotlin/hexagon/gradlew.bat → frameworks/Kotlin/hexagon/gradle/wrapper.bat

@@ -8,14 +8,14 @@
 @rem Set local scope for the variables with windows NT shell
 @rem Set local scope for the variables with windows NT shell
 if "%OS%"=="Windows_NT" setlocal
 if "%OS%"=="Windows_NT" setlocal
 
 
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
 set DIRNAME=%~dp0
 set DIRNAME=%~dp0
 if "%DIRNAME%" == "" set DIRNAME=.
 if "%DIRNAME%" == "" set DIRNAME=.
 set APP_BASE_NAME=%~n0
 set APP_BASE_NAME=%~n0
 set APP_HOME=%DIRNAME%
 set APP_HOME=%DIRNAME%
 
 
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
 @rem Find java.exe
 @rem Find java.exe
 if defined JAVA_HOME goto findJavaFromJavaHome
 if defined JAVA_HOME goto findJavaFromJavaHome
 
 
@@ -46,10 +46,9 @@ echo location of your Java installation.
 goto fail
 goto fail
 
 
 :init
 :init
-@rem Get command-line arguments, handling Windowz variants
+@rem Get command-line arguments, handling Windows variants
 
 
 if not "%OS%" == "Windows_NT" goto win9xME_args
 if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
 
 
 :win9xME_args
 :win9xME_args
 @rem Slurp the command line arguments.
 @rem Slurp the command line arguments.
@@ -60,16 +59,11 @@ set _SKIP=2
 if "x%~1" == "x" goto execute
 if "x%~1" == "x" goto execute
 
 
 set CMD_LINE_ARGS=%*
 set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
 
 
 :execute
 :execute
 @rem Setup the command line
 @rem Setup the command line
 
 
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+set CLASSPATH=%APP_HOME%\wrapper.jar
 
 
 @rem Execute Gradle
 @rem Execute Gradle
 "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
 "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

BIN
frameworks/Kotlin/hexagon/gradle/wrapper.jar


+ 2 - 2
frameworks/Kotlin/hexagon/gradle/wrapper/gradle-wrapper.properties → frameworks/Kotlin/hexagon/gradle/wrapper.properties

@@ -1,6 +1,6 @@
-#Mon Aug 01 16:02:25 CEST 2016
+#Fri Dec 09 22:10:54 CET 2016
 distributionBase=GRADLE_USER_HOME
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-all.zip

BIN
frameworks/Kotlin/hexagon/gradle/wrapper/gradle-wrapper.jar


+ 2 - 1
frameworks/Kotlin/hexagon/setup.sh

@@ -2,5 +2,6 @@
 
 
 fw_depends mongodb java
 fw_depends mongodb java
 
 
-./gradlew
+gradle/wrapper
+
 nohup build/hexagon/bin/hexagon &
 nohup build/hexagon/bin/hexagon &

+ 8 - 0
frameworks/Kotlin/hexagon/setup_resin.sh

@@ -0,0 +1,8 @@
+#!/bin/bash
+
+fw_depends mongodb java resin
+
+gradle/wrapper
+
+cp -f build/libs/ROOT.war $RESIN_HOME/webapps
+resinctl start

+ 2 - 0
frameworks/Kotlin/hexagon/source_code

@@ -1,8 +1,10 @@
 ./hexagon/src/main/kotlin/co/there4/hexagon/Benchmark.kt
 ./hexagon/src/main/kotlin/co/there4/hexagon/Benchmark.kt
+./hexagon/src/main/kotlin/co/there4/hexagon/BenchmarkStorage.kt
 ./hexagon/src/main/resources/templates/fortunes.html
 ./hexagon/src/main/resources/templates/fortunes.html
 ./hexagon/src/main/resources/service.properties
 ./hexagon/src/main/resources/service.properties
 ./hexagon/src/main/resources/logback.xml
 ./hexagon/src/main/resources/logback.xml
 ./hexagon/src/test/kotlin/co/there4/hexagon/BenchmarkTest.kt
 ./hexagon/src/test/kotlin/co/there4/hexagon/BenchmarkTest.kt
 ./hexagon/src/test/resources/logback-test.xml
 ./hexagon/src/test/resources/logback-test.xml
+./hexagon/src/test/resources/service.yaml
 ./hexagon/build.gradle
 ./hexagon/build.gradle
 ./hexagon/gradle.properties
 ./hexagon/gradle.properties

+ 39 - 50
frameworks/Kotlin/hexagon/src/main/kotlin/co/there4/hexagon/Benchmark.kt

@@ -1,65 +1,55 @@
 package co.there4.hexagon
 package co.there4.hexagon
 
 
-import co.there4.hexagon.repository.*
+import co.there4.hexagon.rest.crud
+import co.there4.hexagon.serialization.convertToMap
 import co.there4.hexagon.serialization.serialize
 import co.there4.hexagon.serialization.serialize
-import co.there4.hexagon.settings.SettingsManager.setting
 import co.there4.hexagon.web.*
 import co.there4.hexagon.web.*
+import co.there4.hexagon.web.servlet.ServletServer
+import kotlinx.html.*
 
 
-import java.lang.System.getenv
 import java.net.InetAddress.getByName as address
 import java.net.InetAddress.getByName as address
 import java.time.LocalDateTime.now
 import java.time.LocalDateTime.now
 import java.util.concurrent.ThreadLocalRandom
 import java.util.concurrent.ThreadLocalRandom
+import javax.servlet.annotation.WebListener
 
 
-import kotlin.reflect.KProperty1
-
+// DATA CLASSES
 internal data class Message(val message: String = "Hello, World!")
 internal data class Message(val message: String = "Hello, World!")
 internal data class Fortune(val _id: Int, val message: String)
 internal data class Fortune(val _id: Int, val message: String)
-internal data class World(val _id: Int, val id: Int, val randomNumber: Int)
+internal data class World(val _id: Int, val id: Int = _id, val randomNumber: Int = rnd())
 
 
-private val DB_ROWS = 10000
+// CONSTANTS
 private val CONTENT_TYPE_JSON = "application/json"
 private val CONTENT_TYPE_JSON = "application/json"
 private val QUERIES_PARAM = "queries"
 private val QUERIES_PARAM = "queries"
 
 
-private val DB_HOST = getenv("DBHOST") ?: "localhost"
-private val DB = setting<String>("database") ?: "hello_world"
-private val WORLD: String = setting<String>("worldCollection") ?: "world"
-private val FORTUNE: String = setting<String>("fortuneCollection") ?: "fortune"
-
-private val database = mongoDatabase("mongodb://$DB_HOST/$DB")
-private val worldRepository = repository(WORLD, World::_id)
-private val fortuneRepository = repository(FORTUNE, Fortune::_id)
+// UTILITIES
+internal fun rnd() = ThreadLocalRandom.current().nextInt(DB_ROWS) + 1
 
 
-private inline fun <reified T : Any> repository(name: String, key: KProperty1<T, Int>) =
-    MongoIdRepository(T::class, mongoCollection(name, database), key)
-
-private fun rnd() = ThreadLocalRandom.current().nextInt(DB_ROWS) + 1
+private fun World.strip(): Map<*, *> = this.convertToMap().filterKeys { it != "_id" }
+private fun World.toJson(): String = this.strip().serialize()
+private fun List<World>.toJson(): String = this.map(World::strip).serialize()
 
 
 private fun Exchange.hasQueryCount() = request[QUERIES_PARAM] == null
 private fun Exchange.hasQueryCount() = request[QUERIES_PARAM] == null
 
 
 private fun Exchange.getDb() {
 private fun Exchange.getDb() {
-    val worlds = (1..getQueries()).map { worldRepository.find(rnd()) }.filterNotNull()
+    val worlds = (1..getQueries()).map { findWorld() }.filterNotNull()
 
 
-    response.contentType = CONTENT_TYPE_JSON
-    ok(if (hasQueryCount()) worlds[0].serialize() else worlds.serialize())
+    ok(if (hasQueryCount()) worlds[0].toJson() else worlds.toJson(), CONTENT_TYPE_JSON)
 }
 }
 
 
-private fun Exchange.getFortunes() {
-    val fortune = Fortune(0, "Additional fortune added at request time.")
-    val fortunes = fortuneRepository.findObjects().toList() + fortune
-
-    template("fortunes.html", mapOf("fortunes" to fortunes.sortedBy { it.message }))
-}
+private fun listFortunes() =
+    (findFortunes() + Fortune(0, "Additional fortune added at request time."))
+        .sortedBy { it.message }
 
 
+// HANDLERS
 private fun Exchange.getUpdates() {
 private fun Exchange.getUpdates() {
     val worlds = (1..getQueries()).map {
     val worlds = (1..getQueries()).map {
         val id = rnd()
         val id = rnd()
-        val newWorld = World(id, id, rnd())
-        worldRepository.replaceObject(newWorld)
+        val newWorld = World(id, id)
+        replaceWorld(newWorld)
         newWorld
         newWorld
     }
     }
 
 
-    response.contentType = CONTENT_TYPE_JSON
-    ok(if (hasQueryCount()) worlds[0].serialize() else worlds.serialize())
+    ok(if (hasQueryCount()) worlds[0].toJson() else worlds.toJson(), CONTENT_TYPE_JSON)
 }
 }
 
 
 private fun Exchange.getQueries() =
 private fun Exchange.getQueries() =
@@ -75,29 +65,28 @@ private fun Exchange.getQueries() =
         1
         1
     }
     }
 
 
-private fun Exchange.getPlaintext() {
-    response.contentType = "text/plain"
-    ok("Hello, World!")
-}
-
-private fun Exchange.getJson() {
-    response.contentType = CONTENT_TYPE_JSON
-    ok(Message().serialize())
-}
-
-fun main(args: Array<String>) {
-    before {
+fun benchmarkRoutes(srv: Router = server) {
+    srv.before {
         response.addHeader("Server", "Servlet/3.1")
         response.addHeader("Server", "Servlet/3.1")
         response.addHeader("Transfer-Encoding", "chunked")
         response.addHeader("Transfer-Encoding", "chunked")
         response.addHeader("Date", httpDate(now()))
         response.addHeader("Date", httpDate(now()))
     }
     }
 
 
-    get("/json") { getJson() }
-    get("/db") { getDb() }
-    get("/query") { getDb() }
-    get("/fortune") { getFortunes() }
-    get("/update") { getUpdates() }
-    get("/plaintext") { getPlaintext() }
+    srv.get("/plaintext") { ok("Hello, World!", "text/plain") }
+    srv.get("/json") { ok(Message().serialize(), CONTENT_TYPE_JSON) }
+    srv.get("/fortunes") { template("fortunes.html", "fortunes" to listFortunes()) }
+    srv.get("/db") { getDb() }
+    srv.get("/query") { getDb() }
+    srv.get("/update") { getUpdates() }
+}
 
 
+@WebListener class Web : ServletServer () {
+    override fun init() {
+        benchmarkRoutes(this)
+    }
+}
+
+fun main(args: Array<String>) {
+    benchmarkRoutes()
     run()
     run()
 }
 }

+ 60 - 0
frameworks/Kotlin/hexagon/src/main/kotlin/co/there4/hexagon/BenchmarkStorage.kt

@@ -0,0 +1,60 @@
+package co.there4.hexagon
+
+import co.there4.hexagon.repository.MongoIdRepository
+import co.there4.hexagon.repository.mongoCollection
+import java.lang.System.getenv
+
+import co.there4.hexagon.settings.SettingsManager.setting
+import co.there4.hexagon.repository.mongoDatabase
+import kotlin.reflect.KProperty1
+
+internal val FORTUNE_MESSAGES = setOf(
+    "fortune: No such file or directory",
+    "A computer scientist is someone who fixes things that aren't broken.",
+    "After enough decimal places, nobody gives a damn.",
+    "A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1",
+    "A computer program does what you tell it to do, not what you want it to do.",
+    "Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen",
+    "Any program that runs right is obsolete.",
+    "A list is only as strong as its weakest link. — Donald Knuth",
+    "Feature: A bug with seniority.",
+    "Computers make very fast, very accurate mistakes.",
+    "<script>alert(\"This should not be displayed in a browser alert box.\");</script>",
+    "フレームワークのベンチマーク"
+)
+
+internal val DB_ROWS = 10000
+
+private val DB_HOST = getenv("DBHOST") ?: "localhost"
+private val DB = setting<String>("database") ?: "hello_world"
+private val WORLD: String = setting<String>("worldCollection") ?: "world"
+private val FORTUNE: String = setting<String>("fortuneCollection") ?: "fortune"
+
+private val database = mongoDatabase("mongodb://$DB_HOST/$DB")
+
+internal val worldRepository = repository(WORLD, World::_id)
+internal val fortuneRepository = repository(FORTUNE, Fortune::_id)
+
+// TODO Find out why it fails when creating index '_id' with background: true
+private inline fun <reified T : Any> repository(name: String, key: KProperty1<T, Int>) =
+    MongoIdRepository(T::class, mongoCollection(name, database), key, indexOrder = null)
+
+internal fun initialize() {
+    if (fortuneRepository.isEmpty()) {
+        val fortunes = FORTUNE_MESSAGES.mapIndexed { ii, fortune -> Fortune(ii + 1, fortune) }
+        fortuneRepository.insertManyObjects(fortunes)
+    }
+
+    if (worldRepository.isEmpty()) {
+        val world = (1..DB_ROWS).map { World(it, it) }
+        worldRepository.insertManyObjects(world)
+    }
+}
+
+internal fun findFortunes() = fortuneRepository.findObjects().toList()
+
+internal fun findWorld() = worldRepository.find(rnd())
+
+internal fun replaceWorld(newWorld: World) {
+    worldRepository.replaceObject(newWorld)
+}

+ 1 - 12
frameworks/Kotlin/hexagon/src/main/resources/logback.xml

@@ -5,25 +5,14 @@
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
     <encoder>
     <encoder>
       <Pattern>
       <Pattern>
-        %d{HH:mm:ss.SSS} %highlight(%-5p) %magenta([%-15.15thread]) %-30logger{30} %cyan(%X{jvmId}) | %m%n
+%d{HH:mm:ss.SSS} %highlight(%-5p) %magenta([%-15.15thread]) %-30logger{30} %cyan(%X{jvmId}) | %m%n
       </Pattern>
       </Pattern>
     </encoder>
     </encoder>
   </appender>
   </appender>
 
 
-  <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
-    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-      <FileNamePattern>log/%d{yyyy-MM-dd}.log</FileNamePattern>
-      <MaxHistory>5</MaxHistory>
-    </rollingPolicy>
-    <encoder>
-      <Pattern>%d{HH:mm:ss.SSS} %-5p [%-15.15thread] %-30logger{30} %X{jvmId} | %m%n</Pattern>
-    </encoder>
-  </appender>
-
   <root>
   <root>
     <level value="off" />
     <level value="off" />
     <appender-ref ref="console" />
     <appender-ref ref="console" />
-    <appender-ref ref="file" />
   </root>
   </root>
 
 
   <logger name="co.there4.hexagon">
   <logger name="co.there4.hexagon">

+ 89 - 87
frameworks/Kotlin/hexagon/src/test/kotlin/co/there4/hexagon/BenchmarkTest.kt

@@ -2,6 +2,7 @@ package co.there4.hexagon
 
 
 import co.there4.hexagon.serialization.parse
 import co.there4.hexagon.serialization.parse
 import co.there4.hexagon.web.Client
 import co.there4.hexagon.web.Client
+import co.there4.hexagon.web.server
 import org.asynchttpclient.Response
 import org.asynchttpclient.Response
 import org.testng.annotations.BeforeClass
 import org.testng.annotations.BeforeClass
 import org.testng.annotations.Test
 import org.testng.annotations.Test
@@ -9,126 +10,127 @@ import org.testng.annotations.Test
 internal const val THREADS = 4
 internal const val THREADS = 4
 internal const val TIMES = 4
 internal const val TIMES = 4
 
 
-@Test (threadPoolSize = THREADS, invocationCount = TIMES)
+@Test(threadPoolSize = THREADS, invocationCount = TIMES)
 class BenchmarkTest {
 class BenchmarkTest {
-    private val client = Client("http://localhost:9090")
+    private val client by lazy { Client("http://localhost:${server.runtimePort}") }
 
 
     @BeforeClass fun warmup() {
     @BeforeClass fun warmup() {
+        initialize()
+
         main(arrayOf())
         main(arrayOf())
 
 
         val warmupRounds = if (THREADS > 1) 2 else 0
         val warmupRounds = if (THREADS > 1) 2 else 0
-        (1 ..warmupRounds).forEach {
-            json ()
-            plaintext ()
-            no_query_parameter ()
-            empty_query_parameter ()
-            text_query_parameter ()
-            zero_queries ()
-            one_thousand_queries ()
-            one_query ()
-            ten_queries ()
-            one_hundred_queries ()
-            five_hundred_queries ()
-            fortunes ()
-            no_updates_parameter ()
-            empty_updates_parameter ()
-            text_updates_parameter ()
-            zero_updates ()
-            one_thousand_updates ()
-            one_update ()
-            ten_updates ()
-            one_hundred_updates ()
-            five_hundred_updates ()
+        (1..warmupRounds).forEach {
+            json()
+            plaintext()
+            no_query_parameter()
+            empty_query_parameter()
+            text_query_parameter()
+            zero_queries()
+            one_thousand_queries()
+            one_query()
+            ten_queries()
+            one_hundred_queries()
+            five_hundred_queries()
+            fortunes()
+            no_updates_parameter()
+            empty_updates_parameter()
+            text_updates_parameter()
+            zero_updates()
+            one_thousand_updates()
+            one_update()
+            ten_updates()
+            one_hundred_updates()
+            five_hundred_updates()
         }
         }
     }
     }
 
 
-    fun json () {
-        val response = client.get ("/json")
+    fun json() {
+        val response = client.get("/json")
         val content = response.responseBody
         val content = response.responseBody
 
 
-        checkResponse (response, "application/json")
-        assert ("Hello, World!" == content.parse(Message::class).message)
+        checkResponse(response, "application/json")
+        assert("Hello, World!" == content.parse(Message::class).message)
     }
     }
 
 
-    fun plaintext () {
-        val response = client.get ("/plaintext")
+    fun plaintext() {
+        val response = client.get("/plaintext")
         val content = response.responseBody
         val content = response.responseBody
 
 
-        checkResponse (response, "text/plain")
-        assert ("Hello, World!" == content)
+        checkResponse(response, "text/plain")
+        assert("Hello, World!" == content)
     }
     }
 
 
-    fun no_query_parameter () {
-        val response = client.get ("/db")
+    fun fortunes() = fortunesCheck("/fortunes")
+
+    fun no_query_parameter() {
+        val response = client.get("/db")
         val body = response.responseBody
         val body = response.responseBody
 
 
-        checkResponse (response, "application/json")
+        checkResponse(response, "application/json")
         val bodyMap = body.parse(Map::class)
         val bodyMap = body.parse(Map::class)
-        assert(bodyMap.containsKey (World::_id.name))
-        assert(bodyMap.containsKey (World::randomNumber.name))
+        assert(bodyMap.containsKey(World::id.name))
+        assert(bodyMap.containsKey(World::randomNumber.name))
     }
     }
 
 
-    fun fortunes () {
-        val response = client.get ("/fortune")
-        val content = response.responseBody
-        val contentType = response.headers ["Content-Type"]
+    fun no_updates_parameter() {
+        val response = client.get("/update")
+        val body = response.responseBody
 
 
-        assert (response.headers ["Server"] != null)
-        assert (response.headers ["Date"] != null)
-        assert (content.contains ("&lt;script&gt;alert(&quot;This should not be displayed"))
-        assert (content.contains ("フレームワークのベンチマーク"))
-        assert (contentType.toLowerCase ().contains ("text/html"))
+        checkResponse(response, "application/json")
+        val bodyMap = body.parse(Map::class)
+        assert(bodyMap.containsKey(World::id.name))
+        assert(bodyMap.containsKey(World::randomNumber.name))
     }
     }
 
 
-    fun no_updates_parameter () {
-        val response = client.get ("/update")
-        val body = response.responseBody
+    fun empty_query_parameter() = checkDbRequest("/query?queries", 1)
+    fun text_query_parameter() = checkDbRequest("/query?queries=text", 1)
+    fun zero_queries() = checkDbRequest("/query?queries=0", 1)
+    fun one_thousand_queries() = checkDbRequest("/query?queries=1000", 500)
+    fun one_query() = checkDbRequest("/query?queries=1", 1)
+    fun ten_queries() = checkDbRequest("/query?queries=10", 10)
+    fun one_hundred_queries() = checkDbRequest("/query?queries=100", 100)
+    fun five_hundred_queries() = checkDbRequest("/query?queries=500", 500)
+
+    fun empty_updates_parameter() = checkDbRequest("/update?queries", 1)
+    fun text_updates_parameter() = checkDbRequest("/update?queries=text", 1)
+    fun zero_updates() = checkDbRequest("/update?queries=0", 1)
+    fun one_thousand_updates() = checkDbRequest("/update?queries=1000", 500)
+    fun one_update() = checkDbRequest("/update?queries=1", 1)
+    fun ten_updates() = checkDbRequest("/update?queries=10", 10)
+    fun one_hundred_updates() = checkDbRequest("/update?queries=100", 100)
+    fun five_hundred_updates() = checkDbRequest("/update?queries=500", 500)
+
+    private fun checkDbRequest(path: String, itemsCount: Int) {
+        val response = client.get(path)
+        val content = response.responseBody
 
 
-        checkResponse (response, "application/json")
-        val bodyMap = body.parse(Map::class)
-        assert(bodyMap.containsKey (World::_id.name))
-        assert(bodyMap.containsKey (World::randomNumber.name))
+        checkResponse(response, "application/json")
+
+        val resultsList = content.parse(List::class)
+        assert(itemsCount == resultsList.size)
+
+        (1..itemsCount).forEach {
+            val r = resultsList[it - 1] as Map<*, *>
+            assert(r.containsKey(World::id.name) && r.containsKey(World::randomNumber.name))
+            assert(!r.containsKey(World::_id.name))
+            assert((r[World::id.name] as Int) in 1..10000)
+        }
     }
     }
 
 
-    fun empty_query_parameter () = checkDbRequest ("/query?queries", 1)
-    fun text_query_parameter () = checkDbRequest ("/query?queries=text", 1)
-    fun zero_queries () = checkDbRequest ("/query?queries=0", 1)
-    fun one_thousand_queries () = checkDbRequest ("/query?queries=1000", 500)
-    fun one_query () = checkDbRequest ("/query?queries=1", 1)
-    fun ten_queries () = checkDbRequest ("/query?queries=10", 10)
-    fun one_hundred_queries () = checkDbRequest ("/query?queries=100", 100)
-    fun five_hundred_queries () = checkDbRequest ("/query?queries=500", 500)
-
-    fun empty_updates_parameter () = checkDbRequest ("/update?queries", 1)
-    fun text_updates_parameter () = checkDbRequest ("/update?queries=text", 1)
-    fun zero_updates () = checkDbRequest ("/update?queries=0", 1)
-    fun one_thousand_updates () = checkDbRequest ("/update?queries=1000", 500)
-    fun one_update () = checkDbRequest ("/update?queries=1", 1)
-    fun ten_updates () = checkDbRequest ("/update?queries=10", 10)
-    fun one_hundred_updates () = checkDbRequest ("/update?queries=100", 100)
-    fun five_hundred_updates () = checkDbRequest ("/update?queries=500", 500)
-
-    private fun checkDbRequest (path: String, itemsCount: Int) {
-        val response = client.get (path)
+    private fun fortunesCheck(url: String) {
+        val response = client.get(url)
         val content = response.responseBody
         val content = response.responseBody
 
 
-        checkResponse (response, "application/json")
-        checkResultItems (content, itemsCount)
+        checkResponse(response, "text/html;charset=utf-8")
+        assert(content.contains("<td>&lt;script&gt;alert(&quot;This should not be displayed in a browser alert box.&quot;);&lt;/script&gt;</td>"))
+        assert(content.contains("<td>フレームワークのベンチマーク</td>"))
     }
     }
 
 
-    private fun checkResponse (res: Response, contentType: String) {
+    private fun checkResponse(res: Response, contentType: String) {
+        assert(res.headers ["Date"] != null)
         assert(res.headers ["Server"] != null)
         assert(res.headers ["Server"] != null)
         assert(res.headers ["Transfer-Encoding"] != null)
         assert(res.headers ["Transfer-Encoding"] != null)
-        assert(res.headers ["Content-Type"].contains (contentType))
-    }
-
-    private fun checkResultItems (result: String, size: Int) {
-        val resultsList = result.parse(List::class)
-        assert (size == resultsList.size)
-
-        (1..size).forEach {
-            val r = resultsList[it - 1] as Map<*, *>
-            assert (r.containsKey (World::_id.name) && r.containsKey (World::randomNumber.name))
-        }
+        assert(res.headers ["Content-Type"] == contentType)
     }
     }
 }
 }

+ 1 - 12
frameworks/Kotlin/hexagon/src/test/resources/logback-test.xml

@@ -5,25 +5,14 @@
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
     <encoder>
     <encoder>
       <Pattern>
       <Pattern>
-        %d{HH:mm:ss.SSS} %highlight(%-5p) %magenta([%-15.15thread]) %-30logger{30} %cyan(%X{jvmId}) | %m%n
+%d{HH:mm:ss.SSS} %highlight(%-5p) %magenta([%-15.15thread]) %-30logger{30} %cyan(%X{jvmId}) | %m%n
       </Pattern>
       </Pattern>
     </encoder>
     </encoder>
   </appender>
   </appender>
 
 
-  <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
-    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-      <FileNamePattern>log/%d{yyyy-MM-dd}.log</FileNamePattern>
-      <MaxHistory>5</MaxHistory>
-    </rollingPolicy>
-    <encoder>
-      <Pattern>%d{HH:mm:ss.SSS} %-5p [%-15.15thread] %-30logger{30} %X{jvmId} | %m%n</Pattern>
-    </encoder>
-  </appender>
-
   <root>
   <root>
     <level value="off" />
     <level value="off" />
     <appender-ref ref="console" />
     <appender-ref ref="console" />
-    <appender-ref ref="file" />
   </root>
   </root>
 
 
   <logger name="co.there4.hexagon">
   <logger name="co.there4.hexagon">

+ 7 - 0
frameworks/Kotlin/hexagon/src/test/resources/service.yaml

@@ -0,0 +1,7 @@
+
+bindPort : 0
+bindAddress : 0.0.0.0
+
+database : hello_world
+worldCollection : world
+fortuneCollection : fortune