Browse Source

Add tests for Ktor using an async database layer (#4247)

Walton Hoops 6 years ago
parent
commit
d3644b8cbf
24 changed files with 654 additions and 6 deletions
  1. 4 0
      frameworks/Kotlin/ktor/Readme.md
  2. 46 0
      frameworks/Kotlin/ktor/benchmark_config.json
  3. 31 0
      frameworks/Kotlin/ktor/ktor-asyncdb/README.md
  4. 43 0
      frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle
  5. 4 0
      frameworks/Kotlin/ktor/ktor-asyncdb/gradle.properties
  6. BIN
      frameworks/Kotlin/ktor/ktor-asyncdb/gradle/wrapper/gradle-wrapper.jar
  7. 6 0
      frameworks/Kotlin/ktor/ktor-asyncdb/gradle/wrapper/gradle-wrapper.properties
  8. 172 0
      frameworks/Kotlin/ktor/ktor-asyncdb/gradlew
  9. 84 0
      frameworks/Kotlin/ktor/ktor-asyncdb/gradlew.bat
  10. 14 0
      frameworks/Kotlin/ktor/ktor-asyncdb/settings.gradle
  11. 224 0
      frameworks/Kotlin/ktor/ktor-asyncdb/src/main/kotlin/main.kt
  12. 2 2
      frameworks/Kotlin/ktor/ktor-cio.dockerfile
  13. 10 0
      frameworks/Kotlin/ktor/ktor-jasync.dockerfile
  14. 2 2
      frameworks/Kotlin/ktor/ktor-jetty.dockerfile
  15. 10 0
      frameworks/Kotlin/ktor/ktor-reactivepg.dockerfile
  16. 2 2
      frameworks/Kotlin/ktor/ktor.dockerfile
  17. 0 0
      frameworks/Kotlin/ktor/ktor/README.md
  18. 0 0
      frameworks/Kotlin/ktor/ktor/pom.xml
  19. 0 0
      frameworks/Kotlin/ktor/ktor/src/main/assembly/cio-bundle.xml
  20. 0 0
      frameworks/Kotlin/ktor/ktor/src/main/assembly/jetty-bundle.xml
  21. 0 0
      frameworks/Kotlin/ktor/ktor/src/main/assembly/netty-bundle.xml
  22. 0 0
      frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt
  23. 0 0
      frameworks/Kotlin/ktor/ktor/src/main/resources/application.conf
  24. 0 0
      frameworks/Kotlin/ktor/ktor/src/main/resources/logback.xml

+ 4 - 0
frameworks/Kotlin/ktor/Readme.md

@@ -0,0 +1,4 @@
+See subprojects
+
+* [Ktor](ktor/) Ktor using traditional JDBC using various servers
+* [Ktor-asyncdb](ktor-asyncdb/) Ktor with Netty-based PostgreSQL clients

+ 46 - 0
frameworks/Kotlin/ktor/benchmark_config.json

@@ -70,6 +70,52 @@
         "display_name": "ktor-cio",
         "notes": "http://ktor.io/",
         "versus": ""
+      },
+      "jasync": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/query/?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Fullstack",
+        "database": "Postgres",
+        "framework": "Ktor",
+        "language": "Kotlin",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Ktor-jasync",
+        "notes": "",
+        "versus": "netty"
+      },
+      "reactivepg": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/query/?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Fullstack",
+        "database": "Postgres",
+        "framework": "Ktor",
+        "language": "Kotlin",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Ktor-reactivepg",
+        "notes": "",
+        "versus": "netty"
       }
     }
   ]

+ 31 - 0
frameworks/Kotlin/ktor/ktor-asyncdb/README.md

@@ -0,0 +1,31 @@
+# Ktor with async postgres Benchmarking Test
+
+## Important Libraries
+This sets up testing using [Ktor](https://ktor.io/), with a couple of async PostgreSQL clients:
+* [jasync-sql](https://github.com/jasync-sql/jasync-sql) A pure Kotlin MySQl/PostgreSQL client, with coroutines integration
+* [reactive-pg-client](https://reactiverse.io/reactive-pg-client/guide/java/) A mature async PostgreSQL client with pipelining and batch support
+
+## Test URLs
+### JSON
+
+http://localhost:8080/json
+
+### PLAINTEXT
+
+http://localhost:8080/plaintext
+
+### DB
+
+http://localhost:8080/db
+
+### QUERY
+
+http://localhost:8080/query?queries=
+
+### UPDATE
+
+http://localhost:8080/update?queries=
+
+### FORTUNES
+
+http://localhost:8080/fortunes

+ 43 - 0
frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle

@@ -0,0 +1,43 @@
+plugins {
+    id "java"
+    id "application"
+    id 'org.jetbrains.kotlin.jvm'
+    id 'kotlinx-serialization'
+    id 'com.github.johnrengelman.shadow' version '4.0.3'
+}
+
+group 'org.jetbrains.ktor'
+version '1.0-SNAPSHOT'
+
+mainClassName = "MainKt"
+
+repositories {
+    mavenCentral()
+    jcenter()
+    maven { url "https://kotlin.bintray.com/kotlinx" }
+}
+
+dependencies {
+    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
+    compile "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.9.1"
+    compile "io.ktor:ktor-server-netty:$ktor_version"
+    compile "io.ktor:ktor-html-builder:$ktor_version"
+    compile "com.github.jasync-sql:jasync-postgresql:0.8.54"
+    compile "io.reactiverse:reactive-pg-client:0.11.0"
+    compile "io.reactivex.rxjava2:rxjava:2.2.4"
+    compile 'org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.0.1'
+    compile 'io.vertx:vertx-rx-java2:3.6.0'
+}
+
+compileKotlin {
+    kotlinOptions.jvmTarget = "1.8"
+}
+compileTestKotlin {
+    kotlinOptions.jvmTarget = "1.8"
+}
+
+shadowJar {
+    baseName = "bench"
+    classifier = null
+    version = null
+}

+ 4 - 0
frameworks/Kotlin/ktor/ktor-asyncdb/gradle.properties

@@ -0,0 +1,4 @@
+kotlin.code.style=official
+
+kotlin_version=1.3.11
+ktor_version=1.0.1

BIN
frameworks/Kotlin/ktor/ktor-asyncdb/gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
frameworks/Kotlin/ktor/ktor-asyncdb/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Fri Dec 07 21:01:17 MST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip

+ 172 - 0
frameworks/Kotlin/ktor/ktor-asyncdb/gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# 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_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.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+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" "$@"

+ 84 - 0
frameworks/Kotlin/ktor/ktor-asyncdb/gradlew.bat

@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+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
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@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%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 14 - 0
frameworks/Kotlin/ktor/ktor-asyncdb/settings.gradle

@@ -0,0 +1,14 @@
+pluginManagement {
+    resolutionStrategy {
+        eachPlugin {
+            if (requested.id.id == "org.jetbrains.kotlin.jvm") {
+                useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
+            }
+            if (requested.id.id == "kotlinx-serialization") {
+                useModule("org.jetbrains.kotlin:kotlin-serialization:$kotlin_version")
+            }
+        }
+    }
+}
+
+rootProject.name = 'tech-empower-framework-benchmark'

+ 224 - 0
frameworks/Kotlin/ktor/ktor-asyncdb/src/main/kotlin/main.kt

@@ -0,0 +1,224 @@
+import com.github.jasync.sql.db.ConnectionPoolConfiguration
+import com.github.jasync.sql.db.SuspendingConnection
+import com.github.jasync.sql.db.asSuspending
+import com.github.jasync.sql.db.postgresql.PostgreSQLConnectionBuilder
+import io.ktor.application.call
+import io.ktor.application.install
+import io.ktor.features.DefaultHeaders
+import io.ktor.html.Placeholder
+import io.ktor.html.Template
+import io.ktor.html.insert
+import io.ktor.html.respondHtmlTemplate
+import io.ktor.http.ContentType
+import io.ktor.response.respondText
+import io.ktor.routing.get
+import io.ktor.routing.routing
+import io.ktor.server.engine.embeddedServer
+import io.ktor.server.netty.Netty
+import io.reactiverse.pgclient.PgPoolOptions
+import io.reactiverse.reactivex.pgclient.PgClient
+import io.reactiverse.reactivex.pgclient.PgRowSet
+import io.reactiverse.reactivex.pgclient.Row
+import io.reactiverse.reactivex.pgclient.Tuple
+import kotlinx.coroutines.rx2.await
+import kotlinx.html.*
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JSON
+import kotlinx.serialization.list
+import java.lang.IllegalArgumentException
+import kotlin.random.Random
+import kotlin.random.nextInt
+
+@Serializable
+data class Message(val message: String)
+
+@Serializable
+data class World(val id: Int, val randomNumber: Int)
+
+data class Fortune(val id: Int, val message: String)
+
+val rand = Random(1)
+
+interface Repository {
+    suspend fun getWorld(): World
+    suspend fun getFortunes(): List<Fortune>
+    suspend fun updateWorlds(worlds: List<World>)
+}
+
+class JasyncRepository() : Repository {
+    private val dbConfig: ConnectionPoolConfiguration
+    private val db: SuspendingConnection
+
+    init {
+        dbConfig = ConnectionPoolConfiguration(
+            "tfb-database",
+            database = "hello_world",
+            username = "benchmarkdbuser",
+            password = "benchmarkdbpass",
+            maxActiveConnections = 64
+        )
+        db = PostgreSQLConnectionBuilder.createConnectionPool(dbConfig).asSuspending
+    }
+
+    override suspend fun getWorld(): World {
+        val worldId = rand.nextInt(1, 10000)
+        val result = db.sendPreparedStatement("select id, randomNumber from world where id = ?", listOf(worldId))
+        val row = result.rows.first()
+        return World(row.getInt(0)!!, row.getInt(1)!!)
+    }
+
+    override suspend fun getFortunes(): List<Fortune> {
+        val results = db.sendPreparedStatement("select id, message from fortune")
+        return results.rows.map { Fortune(it.getInt(0)!!, it.getString(1)!!) }
+    }
+
+    override suspend fun updateWorlds(worlds: List<World>) {
+        worlds.forEach { world ->
+            db.sendPreparedStatement(
+                "update world set randomNumber = ? where id = ?",
+                listOf(world.randomNumber, world.id)
+            )
+        }
+    }
+}
+
+fun PgRowSet.rows(): List<Row> {
+    val rows = mutableListOf<Row>()
+    val iterator = iterator()
+    while (iterator.hasNext()) {
+        rows.add(iterator.next())
+    }
+    return rows
+}
+
+class ReactivePGRepository : Repository {
+    private val poolOptions = PgPoolOptions()
+    private val db: PgClient
+
+    init {
+        poolOptions.apply {
+            host = "tfb-database"
+            database = "hello_world"
+            user = "benchmarkdbuser"
+            password = "benchmarkdbpass"
+            maxSize = 64
+            cachePreparedStatements = true
+        }
+        db = PgClient.pool(poolOptions)
+    }
+
+    override suspend fun getFortunes(): List<Fortune> {
+        val results = db.rxPreparedQuery("select id, message from fortune").await()
+        return results.rows().map { Fortune(it.getInteger(0), it.getString(1)) }
+    }
+
+    override suspend fun getWorld(): World {
+        val worldId = rand.nextInt(1, 10000)
+        val result = db.rxPreparedQuery("select id, randomNumber from world where id = $1", Tuple.of(worldId)).await()
+        val row = result.rows().first()
+        return World(row.getInteger(0), row.getInteger(1)!!)
+    }
+
+    override suspend fun updateWorlds(worlds: List<World>) {
+        val batch = worlds.map { Tuple.of(it.id, it.randomNumber) }
+        db.rxPreparedBatch("update world set randomNumber = $1 where id = $2", batch).await()
+    }
+}
+
+fun String.toBoxedInt(range: IntRange): Int = try {
+    this.toInt().coerceIn(range)
+} catch (e: NumberFormatException) {
+    1
+}
+
+class MainTemplate : Template<HTML> {
+    val content = Placeholder<HtmlBlockTag>()
+    override fun HTML.apply() {
+        head {
+            title { +"Fortunes" }
+        }
+        body {
+            insert(content)
+        }
+    }
+}
+
+class FortuneTemplate(val fortunes: List<Fortune>, val main: MainTemplate = MainTemplate()) : Template<HTML> {
+    override fun HTML.apply() {
+        insert(main) {
+            content {
+                table {
+                    tr {
+                        th { +"id" }
+                        th { +"message" }
+                    }
+                    fortunes.forEach { fortune ->
+                        tr {
+                            td { +fortune.id.toString() }
+                            td { +fortune.message }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+fun main(args: Array<String>) {
+    val db = when(args.first()) {
+        "jasync-sql" -> JasyncRepository()
+        "reactive-pg" -> ReactivePGRepository()
+        else -> throw IllegalArgumentException("Must specify a postgres client")
+    }
+
+    val messageSerializer = Message.serializer()
+    val worldSerializer = World.serializer()
+
+    val server = embeddedServer(Netty, 8080, configure = {
+        shareWorkGroup = true
+    }) {
+        install(DefaultHeaders)
+        routing {
+            get("/plaintext") {
+                call.respondText("Hello, World!")
+            }
+
+            get("/json") {
+                call.respondText(
+                    JSON.stringify(messageSerializer, Message("Hello, World!")),
+                    ContentType.Application.Json
+                )
+            }
+
+            get("/db") {
+                call.respondText(JSON.stringify(worldSerializer, db.getWorld()), ContentType.Application.Json)
+            }
+
+            get("/query/") {
+                val queries = call.parameters["queries"]?.toBoxedInt(1..500) ?: 1
+                val worlds = (1..queries).map { db.getWorld() }
+                call.respondText(JSON.stringify(worldSerializer.list, worlds), ContentType.Application.Json)
+            }
+
+            get("/fortunes") {
+                val newFortune = Fortune(0, "Additional fortune added at request time.")
+                val fortunes = db.getFortunes().toMutableList()
+                fortunes.add(newFortune)
+                fortunes.sortBy { it.message }
+                call.respondHtmlTemplate(FortuneTemplate(fortunes)) { }
+            }
+
+            get("/updates") {
+                val queries = call.parameters["queries"]?.toBoxedInt(1..500) ?: 1
+                val worlds = (1..queries).map { db.getWorld() }
+                val newWorlds = worlds.map { it.copy(randomNumber = rand.nextInt(1..10000)) }
+
+                db.updateWorlds(newWorlds)
+
+                call.respondText(JSON.stringify(worldSerializer.list, newWorlds), ContentType.Application.Json)
+            }
+        }
+    }
+
+    server.start(wait = true)
+}

+ 2 - 2
frameworks/Kotlin/ktor/ktor-cio.dockerfile

@@ -1,7 +1,7 @@
 FROM maven:3.5.3-jdk-10-slim as maven
 WORKDIR /ktor
-COPY pom.xml pom.xml
-COPY src src
+COPY ktor/pom.xml pom.xml
+COPY ktor/src src
 RUN mvn clean package -q
 
 FROM openjdk:10-jre-slim

+ 10 - 0
frameworks/Kotlin/ktor/ktor-jasync.dockerfile

@@ -0,0 +1,10 @@
+FROM openjdk:11-slim
+WORKDIR /app
+COPY ktor-asyncdb/gradle gradle
+COPY ktor-asyncdb/build.gradle build.gradle
+COPY ktor-asyncdb/gradle.properties gradle.properties
+COPY ktor-asyncdb/gradlew gradlew
+COPY ktor-asyncdb/settings.gradle settings.gradle
+COPY ktor-asyncdb/src src
+RUN /app/gradlew --no-daemon shadowJar
+CMD ["java", "-server", "-jar", "/app/build/libs/bench.jar", "jasync-sql"]

+ 2 - 2
frameworks/Kotlin/ktor/ktor-jetty.dockerfile

@@ -1,7 +1,7 @@
 FROM maven:3.5.3-jdk-10-slim as maven
 WORKDIR /ktor
-COPY pom.xml pom.xml
-COPY src src
+COPY ktor/pom.xml pom.xml
+COPY ktor/src src
 RUN mvn clean package -q
 
 FROM openjdk:10-jre-slim

+ 10 - 0
frameworks/Kotlin/ktor/ktor-reactivepg.dockerfile

@@ -0,0 +1,10 @@
+FROM openjdk:11-slim
+WORKDIR /app
+COPY ktor-asyncdb/gradle gradle
+COPY ktor-asyncdb/build.gradle build.gradle
+COPY ktor-asyncdb/gradle.properties gradle.properties
+COPY ktor-asyncdb/gradlew gradlew
+COPY ktor-asyncdb/settings.gradle settings.gradle
+COPY ktor-asyncdb/src src
+RUN /app/gradlew --no-daemon shadowJar
+CMD ["java", "-server", "-jar", "/app/build/libs/bench.jar", "reactive-pg"]

+ 2 - 2
frameworks/Kotlin/ktor/ktor.dockerfile

@@ -1,7 +1,7 @@
 FROM maven:3.5.3-jdk-10-slim as maven
 WORKDIR /ktor
-COPY pom.xml pom.xml
-COPY src src
+COPY ktor/pom.xml pom.xml
+COPY ktor/src src
 RUN mvn clean package -q
 
 FROM openjdk:10-jre-slim

+ 0 - 0
frameworks/Kotlin/ktor/README.md → frameworks/Kotlin/ktor/ktor/README.md


+ 0 - 0
frameworks/Kotlin/ktor/pom.xml → frameworks/Kotlin/ktor/ktor/pom.xml


+ 0 - 0
frameworks/Kotlin/ktor/src/main/assembly/cio-bundle.xml → frameworks/Kotlin/ktor/ktor/src/main/assembly/cio-bundle.xml


+ 0 - 0
frameworks/Kotlin/ktor/src/main/assembly/jetty-bundle.xml → frameworks/Kotlin/ktor/ktor/src/main/assembly/jetty-bundle.xml


+ 0 - 0
frameworks/Kotlin/ktor/src/main/assembly/netty-bundle.xml → frameworks/Kotlin/ktor/ktor/src/main/assembly/netty-bundle.xml


+ 0 - 0
frameworks/Kotlin/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt → frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt


+ 0 - 0
frameworks/Kotlin/ktor/src/main/resources/application.conf → frameworks/Kotlin/ktor/ktor/src/main/resources/application.conf


+ 0 - 0
frameworks/Kotlin/ktor/src/main/resources/logback.xml → frameworks/Kotlin/ktor/ktor/src/main/resources/logback.xml