Browse Source

Updates and cleanup for Ktor benchmarks (#9790)

Bruce Hamilton 4 months ago
parent
commit
d7920aff9a
33 changed files with 497 additions and 402 deletions
  1. 4 1
      frameworks/Kotlin/ktor/Readme.md
  2. 4 4
      frameworks/Kotlin/ktor/benchmark_config.json
  3. 4 3
      frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts
  4. 10 0
      frameworks/Kotlin/ktor/ktor-asyncdb/src/main/kotlin/Models.kt
  5. 20 0
      frameworks/Kotlin/ktor/ktor-asyncdb/src/main/kotlin/Responses.kt
  6. 32 58
      frameworks/Kotlin/ktor/ktor-asyncdb/src/main/kotlin/main.kt
  7. 12 0
      frameworks/Kotlin/ktor/ktor-asyncdb/src/main/resources/application.conf
  8. 21 0
      frameworks/Kotlin/ktor/ktor-asyncdb/src/main/resources/logback.xml
  9. 1 1
      frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts
  10. 1 2
      frameworks/Kotlin/ktor/ktor-exposed/app/src/main/kotlin/App.kt
  11. 1 1
      frameworks/Kotlin/ktor/ktor-pgclient.dockerfile
  12. 6 6
      frameworks/Kotlin/ktor/ktor-pgclient/README.md
  13. 4 3
      frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts
  14. 10 0
      frameworks/Kotlin/ktor/ktor-pgclient/src/main/kotlin/Models.kt
  15. 20 0
      frameworks/Kotlin/ktor/ktor-pgclient/src/main/kotlin/Responses.kt
  16. 30 55
      frameworks/Kotlin/ktor/ktor-pgclient/src/main/kotlin/main.kt
  17. 12 0
      frameworks/Kotlin/ktor/ktor-pgclient/src/main/resources/application.conf
  18. 21 0
      frameworks/Kotlin/ktor/ktor-pgclient/src/main/resources/logback.xml
  19. 7 2
      frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml
  20. 15 36
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt
  21. 12 0
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Models.kt
  22. 22 0
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Responses.kt
  23. 0 6
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Fortune.kt
  24. 0 11
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Message.kt
  25. 0 6
      frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/World.kt
  26. 2 0
      frameworks/Kotlin/ktor/ktor/README.md
  27. 8 3
      frameworks/Kotlin/ktor/ktor/pom.xml
  28. 32 0
      frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Database.kt
  29. 0 203
      frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt
  30. 12 0
      frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Models.kt
  31. 22 0
      frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Responses.kt
  32. 151 0
      frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/main.kt
  33. 1 1
      frameworks/Kotlin/ktor/ktor/src/main/resources/application.conf

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

@@ -1,4 +1,7 @@
 See subprojects
 
 * [Ktor](ktor/) Ktor using traditional JDBC using various servers
-* [Ktor-asyncdb](ktor-asyncdb/) Ktor with Netty-based PostgreSQL clients
+* [Ktor-jasync](ktor-asyncdb/) Ktor with Netty-based PostgreSQL clients
+* [Ktor-r2dbc](ktor-r2dbc/) Ktor with R2DBC for reactive database access
+* [Ktor-exposed](ktor-exposed/) Ktor with the Exposed database abstraction library
+* [Ktor-pgclient](ktor-pgclient/) Ktor with Reactive PostgreSQL Client

+ 4 - 4
frameworks/Kotlin/ktor/benchmark_config.json

@@ -101,7 +101,7 @@
         "query_url": "/query/?queries=",
         "fortune_url": "/fortunes",
         "update_url": "/updates?queries=",
-        "port": 8080,
+        "port": 9090,
         "approach": "Realistic",
         "classification": "Fullstack",
         "database": "Postgres",
@@ -125,7 +125,7 @@
         "update_url": "/updates?queries=",
         "fortune_url": "/fortunes",
 
-        "port": 8080,
+        "port": 9090,
         "approach": "Realistic",
         "classification": "Micro",
         "database": "Postgres",
@@ -145,7 +145,7 @@
         "query_url": "/queries?queries=",
         "update_url": "/updates?queries=",
         "fortune_url": "/fortunes",
-        "port": 8080,
+        "port": 9090,
         "approach": "Realistic",
         "classification": "Micro",
         "database": "postgres",
@@ -164,7 +164,7 @@
       "exposed-dao": {
         "db_url": "/db",
         "fortune_url": "/fortunes",
-        "port": 8080,
+        "port": 9090,
         "approach": "Realistic",
         "classification": "Micro",
         "database": "postgres",

+ 4 - 3
frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts

@@ -13,11 +13,11 @@ repositories {
 }
 
 application {
-    mainClass.set("MainKt")
+    mainClass = "io.ktor.server.netty.EngineMain"
 }
 
-val ktor_version = "2.3.12"
-val kotlinx_serialization_version = "1.7.3"
+val ktor_version = "3.1.2"
+val kotlinx_serialization_version = "1.8.1"
 
 dependencies {
     implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinx_serialization_version")
@@ -26,6 +26,7 @@ dependencies {
     implementation("io.ktor:ktor-server-default-headers:$ktor_version")
     implementation("io.ktor:ktor-server-html-builder:$ktor_version")
     implementation("com.github.jasync-sql:jasync-postgresql:2.2.4")
+    implementation("ch.qos.logback:logback-classic:1.5.12")
 }
 
 java {

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

@@ -0,0 +1,10 @@
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Message(val message: String)
+
+@Serializable
+data class World(val id: Int, var randomNumber: Int)
+
+@Serializable
+data class Fortune(val id: Int, var message: String)

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

@@ -0,0 +1,20 @@
+import io.ktor.http.*
+import io.ktor.http.content.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import kotlinx.serialization.json.Json
+
+// Optimized JSON instance with better performance settings
+internal val json = Json {
+    prettyPrint = false
+    isLenient = true
+    ignoreUnknownKeys = true
+    coerceInputValues = true
+}
+
+internal suspend inline fun <reified E> RoutingCall.respondJson(response: E) {
+    respond(TextContent(
+        json.encodeToString(response),
+        ContentType.Application.Json
+    ))
+}

+ 32 - 58
frameworks/Kotlin/ktor/ktor-asyncdb/src/main/kotlin/main.kt

@@ -3,31 +3,16 @@ import com.github.jasync.sql.db.QueryResult
 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.http.ContentType
 import io.ktor.server.application.*
-import io.ktor.server.engine.embeddedServer
 import io.ktor.server.html.*
-import io.ktor.server.netty.Netty
 import io.ktor.server.plugins.defaultheaders.*
 import io.ktor.server.response.*
 import io.ktor.server.routing.*
 import kotlinx.coroutines.*
 import kotlinx.html.*
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.encodeToString
-import kotlinx.serialization.json.Json
-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 {
@@ -121,57 +106,46 @@ class FortuneTemplate(private val fortunes: List<Fortune>, private val main: Mai
     }
 }
 
-fun main(args: Array<String>) {
-    val db = when(args.firstOrNull()) {
-        "jasync-sql" -> JasyncRepository()
-        else -> throw IllegalArgumentException("Must specify a postgres client")
-    }
+fun Application.main() {
 
-    val server = embeddedServer(Netty, 8080, configure = {
-        shareWorkGroup = true
-    }) {
-        install(DefaultHeaders)
-        routing {
-            get("/plaintext") {
-                call.respondText("Hello, World!")
-            }
+    val db = JasyncRepository()
 
-            get("/json") {
-                call.respondText(
-                    Json.encodeToString(Message("Hello, World!")),
-                    ContentType.Application.Json
-                )
-            }
+    install(DefaultHeaders)
+    routing {
+        get("/plaintext") {
+            call.respondText("Hello, World!")
+        }
 
-            get("/db") {
-                call.respondText(Json.encodeToString(db.getWorld()), ContentType.Application.Json)
-            }
+        get("/json") {
+            call.respondJson(Message("Hello, World!"))
+        }
 
-            get("/query/") {
-                val queries = call.parameters["queries"]?.toBoxedInt(1..500) ?: 1
-                val worlds = (1..queries).map { db.getWorld() }
-                call.respondText(Json.encodeToString(worlds), ContentType.Application.Json)
-            }
+        get("/db") {
+            call.respondJson(db.getWorld())
+        }
 
-            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("/query/") {
+            val queries = call.parameters["queries"]?.toBoxedInt(1..500) ?: 1
+            val worlds = (1..queries).map { db.getWorld() }
+            call.respondJson(worlds)
+        }
 
-            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)) }
+        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)) { }
+        }
 
-                db.updateWorlds(newWorlds)
+        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)) }
 
-                call.respondText(Json.encodeToString(newWorlds), ContentType.Application.Json)
-            }
+            db.updateWorlds(newWorlds)
+
+            call.respondJson(newWorlds)
         }
     }
-
-    server.start(wait = true)
 }

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

@@ -0,0 +1,12 @@
+ktor {
+    deployment {
+        port = 9090
+        autoreload = false
+        watch = [ ]
+        shareWorkGroup = true
+    }
+
+    application {
+        modules = [MainKt.main]
+    }
+}

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

@@ -0,0 +1,21 @@
+<configuration>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+     <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
+         <neverBlock>true</neverBlock>
+         <appender-ref ref="STDOUT" />
+     </appender>
+
+
+    <root level="INFO">
+        <appender-ref ref="ASYNC"/>
+    </root>
+
+    <logger name="org.eclipse.jetty" level="INFO"/>
+    <logger name="io.netty" level="INFO"/>
+
+</configuration>

+ 1 - 1
frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts

@@ -9,7 +9,7 @@ repositories {
     mavenCentral()
 }
 
-val ktorVersion = "3.1.1"
+val ktorVersion = "3.1.2"
 val kotlinxSerializationVersion = "1.8.0"
 val exposedVersion = "0.59.0"
 

+ 1 - 2
frameworks/Kotlin/ktor/ktor-exposed/app/src/main/kotlin/App.kt

@@ -13,7 +13,6 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 import kotlinx.html.*
 import kotlinx.serialization.Serializable
-import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
 import org.jetbrains.exposed.dao.IntEntity
 import org.jetbrains.exposed.dao.IntEntityClass
@@ -70,7 +69,7 @@ enum class ExposedMode {
 
 fun main(args: Array<String>) {
     val exposedMode = valueOf(args.first())
-    embeddedServer(Netty, port = 8080) { module(exposedMode) }.start(wait = true)
+    embeddedServer(Netty, port = 9090) { module(exposedMode) }.start(wait = true)
 }
 
 fun Application.module(exposedMode: ExposedMode) {

+ 1 - 1
frameworks/Kotlin/ktor/ktor-pgclient.dockerfile

@@ -10,6 +10,6 @@ FROM amazoncorretto:21-al2023-headless
 WORKDIR /app
 COPY --from=build /app/build/libs/ktor-pgclient.jar ktor-pgclient.jar
 
-EXPOSE 8080
+EXPOSE 9090
 
 CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "ktor-pgclient.jar"]

+ 6 - 6
frameworks/Kotlin/ktor/ktor-pgclient/README.md

@@ -7,27 +7,27 @@ The client features batching, pipelining and supports coroutines.
 
 ### Plain Text Test
 
-    http://localhost:8080/plaintext
+    http://localhost:9090/plaintext
 
 ### JSON Encoding Test
 
-    http://localhost:8080/json
+    http://localhost:9090/json
 
 ### Single Query Test
 
-    http://localhost:8080/db
+    http://localhost:9090/db
 
 ### Multiple Queries Test
 
-    http://localhost:8080/query?queries=
+    http://localhost:9090/query?queries=
 
 ### Database updates Test
 
-    http://localhost:8080/updates?queries=
+    http://localhost:9090/updates?queries=
 
 ### Fortunes Test
 
-    http://localhost:8080/fortunes
+    http://localhost:9090/fortunes
 
 ## build
 

+ 4 - 3
frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts

@@ -13,20 +13,21 @@ repositories {
 }
 
 application {
-    mainClass.set("MainKt")
+    mainClass = "io.ktor.server.netty.EngineMain"
 }
 
-val ktor_version = "2.3.12"
+val ktor_version = "3.1.2"
 val vertx_version = "4.5.11"
 
 dependencies {
-    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
+    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1")
     implementation("io.ktor:ktor-server-netty:$ktor_version")
     implementation("io.ktor:ktor-server-html-builder-jvm:$ktor_version")
     implementation("io.ktor:ktor-server-default-headers-jvm:$ktor_version")
     implementation("io.vertx:vertx-pg-client:$vertx_version")
     implementation("io.vertx:vertx-lang-kotlin:$vertx_version")
     implementation("io.vertx:vertx-lang-kotlin-coroutines:$vertx_version")
+    implementation("ch.qos.logback:logback-classic:1.5.12")
 }
 
 java {

+ 10 - 0
frameworks/Kotlin/ktor/ktor-pgclient/src/main/kotlin/Models.kt

@@ -0,0 +1,10 @@
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Message(val message: String)
+
+@Serializable
+data class World(val id: Int, var randomNumber: Int)
+
+@Serializable
+data class Fortune(val id: Int, var message: String)

+ 20 - 0
frameworks/Kotlin/ktor/ktor-pgclient/src/main/kotlin/Responses.kt

@@ -0,0 +1,20 @@
+import io.ktor.http.*
+import io.ktor.http.content.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import kotlinx.serialization.json.Json
+
+// Optimized JSON instance with better performance settings
+internal val json = Json {
+    prettyPrint = false
+    isLenient = true
+    ignoreUnknownKeys = true
+    coerceInputValues = true
+}
+
+internal suspend inline fun <reified E> RoutingCall.respondJson(response: E) {
+    respond(TextContent(
+        json.encodeToString(response),
+        ContentType.Application.Json
+    ))
+}

+ 30 - 55
frameworks/Kotlin/ktor/ktor-pgclient/src/main/kotlin/main.kt

@@ -1,32 +1,16 @@
-import io.ktor.http.*
 import io.ktor.server.application.*
-import io.ktor.server.engine.*
 import io.ktor.server.html.*
-import io.ktor.server.netty.*
 import io.ktor.server.plugins.defaultheaders.*
 import io.ktor.server.response.*
 import io.ktor.server.routing.*
-import io.vertx.kotlin.coroutines.await
 import io.vertx.kotlin.coroutines.coAwait
 import io.vertx.pgclient.PgBuilder
 import io.vertx.pgclient.PgConnectOptions
-import io.vertx.pgclient.PgPool
 import io.vertx.sqlclient.PoolOptions
 import io.vertx.sqlclient.Tuple
 import kotlinx.html.*
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.encodeToString
-import kotlinx.serialization.json.Json
 import java.util.concurrent.ThreadLocalRandom
 
-@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: ThreadLocalRandom
     get() = ThreadLocalRandom.current()
 
@@ -125,54 +109,45 @@ class FortuneTemplate(
     }
 }
 
-fun main() {
+fun Application.main() {
     val db = PgclientRepository()
 
-    val server = embeddedServer(Netty, 8080, configure = {
-        shareWorkGroup = true
-    }) {
-        install(DefaultHeaders)
-        routing {
-            get("/plaintext") {
-                call.respondText("Hello, World!")
-            }
+    install(DefaultHeaders)
+    routing {
+        get("/plaintext") {
+            call.respondText("Hello, World!")
+        }
 
-            get("/json") {
-                call.respondText(
-                    Json.encodeToString(Message("Hello, World!")),
-                    ContentType.Application.Json
-                )
-            }
+        get("/json") {
+            call.respondJson(Message("Hello, World!"))
+        }
 
-            get("/db") {
-                call.respondText(Json.encodeToString(db.getWorld()), ContentType.Application.Json)
-            }
+        get("/db") {
+            call.respondJson(db.getWorld())
+        }
 
-            get("/query") {
-                val queries = call.parameters["queries"]?.toBoxedInt(1..500) ?: 1
-                val worlds = List(queries) { db.getWorld() }
-                call.respondText(Json.encodeToString(worlds), ContentType.Application.Json)
-            }
+        get("/query") {
+            val queries = call.parameters["queries"]?.toBoxedInt(1..500) ?: 1
+            val worlds = List(queries) { db.getWorld() }
+            call.respondJson(worlds)
+        }
 
-            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("/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 = List(queries) { db.getWorld() }
-                val newWorlds = worlds.map { it.copy(randomNumber = rand.nextInt(1, 10001)) }
+        get("/updates") {
+            val queries = call.parameters["queries"]?.toBoxedInt(1..500) ?: 1
+            val worlds = List(queries) { db.getWorld() }
+            val newWorlds = worlds.map { it.copy(randomNumber = rand.nextInt(1, 10001)) }
 
-                db.updateWorlds(newWorlds)
+            db.updateWorlds(newWorlds)
 
-                call.respondText(Json.encodeToString(newWorlds), ContentType.Application.Json)
-            }
+            call.respondJson(newWorlds)
         }
     }
-
-    server.start(wait = true)
 }

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

@@ -0,0 +1,12 @@
+ktor {
+    deployment {
+        port = 9090
+        autoreload = false
+        watch = [ ]
+        shareWorkGroup = true
+    }
+
+    application {
+        modules = [MainKt.main]
+    }
+}

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

@@ -0,0 +1,21 @@
+<configuration>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+     <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
+         <neverBlock>true</neverBlock>
+         <appender-ref ref="STDOUT" />
+     </appender>
+
+
+    <root level="INFO">
+        <appender-ref ref="ASYNC"/>
+    </root>
+
+    <logger name="org.eclipse.jetty" level="INFO"/>
+    <logger name="io.netty" level="INFO"/>
+
+</configuration>

+ 7 - 2
frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml

@@ -14,8 +14,8 @@
     <properties>
         <kotlin.version>2.1.20</kotlin.version>
         <kotlin.coroutines.version>1.10.1</kotlin.coroutines.version>
-        <ktor.version>3.1.1</ktor.version>
-        <serialization.version>1.8.0</serialization.version>
+        <ktor.version>3.1.2</ktor.version>
+        <serialization.version>1.8.1</serialization.version>
         <kotlinx.html.version>0.12.0</kotlinx.html.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <logback.version>1.5.12</logback.version>
@@ -42,6 +42,11 @@
             <artifactId>kotlinx-serialization-json</artifactId>
             <version>${serialization.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-serialization-json-io</artifactId>
+            <version>${serialization.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.jetbrains.kotlinx</groupId>
             <artifactId>kotlinx-html-jvm</artifactId>

+ 15 - 36
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt

@@ -4,7 +4,7 @@ import io.ktor.http.*
 import io.ktor.http.content.*
 import io.ktor.server.application.*
 import io.ktor.server.config.*
-import io.ktor.server.html.*
+import io.ktor.server.html.respondHtml
 import io.ktor.server.plugins.defaultheaders.*
 import io.ktor.server.response.*
 import io.ktor.server.routing.*
@@ -20,35 +20,25 @@ import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.reactive.awaitFirst
 import kotlinx.coroutines.reactive.awaitFirstOrNull
 import kotlinx.html.*
-import kotlinx.serialization.encodeToString
-import kotlinx.serialization.json.Json
-import org.jetbrains.ktor.benchmarks.Constants.DB_ROWS
-import org.jetbrains.ktor.benchmarks.Constants.FORTUNES_QUERY
-import org.jetbrains.ktor.benchmarks.Constants.UPDATE_QUERY
-import org.jetbrains.ktor.benchmarks.Constants.WORLD_QUERY
-import org.jetbrains.ktor.benchmarks.models.Fortune
-import org.jetbrains.ktor.benchmarks.models.Message
-import org.jetbrains.ktor.benchmarks.models.World
 import reactor.core.publisher.Flux
 import reactor.core.publisher.Mono
-import kotlin.random.Random
 import java.time.Duration
+import kotlin.random.Random
 
-private val json = Json {
-    prettyPrint = false
-    isLenient = true
-    ignoreUnknownKeys = true
-    coerceInputValues = true
-}
+const val HELLO_WORLD = "Hello, World!"
+const val WORLD_QUERY = "SELECT id, randomnumber FROM world WHERE id = $1"
+const val FORTUNES_QUERY = "SELECT id, message FROM fortune"
+const val UPDATE_QUERY = "UPDATE world SET randomnumber = $1 WHERE id = $2"
+const val DB_ROWS = 10000
 
 fun Application.main() {
     val config = ApplicationConfig("application.conf")
     val dbConnFactory = configurePostgresR2DBC(config)
 
-    install(DefaultHeaders)
-
     val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain)
-    val helloWorldMsg = Message("Hello, world!")
+    val random = Random.Default
+
+    install(DefaultHeaders)
 
     routing {
         get("/plaintext") {
@@ -56,15 +46,14 @@ fun Application.main() {
         }
 
         get("/json") {
-            call.respondText(json.encodeToString(helloWorldMsg), ContentType.Application.Json)
+            call.respondJson(Message(HELLO_WORLD))
         }
 
         get("/db") {
-            val random = Random.Default
             val request = getWorld(dbConnFactory, random)
             val result = request.awaitFirstOrNull()
 
-            call.respondText(json.encodeToString(result), ContentType.Application.Json)
+            call.respondJson(result)
         }
 
         fun selectWorlds(queries: Int, random: Random): Flow<World> = flow {
@@ -75,7 +64,6 @@ fun Application.main() {
 
         get("/queries") {
             val queries = call.queries()
-            val random = Random.Default
 
             val result = buildList {
                 selectWorlds(queries, random).collect {
@@ -83,7 +71,7 @@ fun Application.main() {
                 }
             }
 
-            call.respondText(json.encodeToString(result), ContentType.Application.Json)
+            call.respondJson(result)
         }
 
         get("/fortunes") {
@@ -124,7 +112,6 @@ fun Application.main() {
 
         get("/updates") {
             val queries = call.queries()
-            val random = Random.Default
 
             val worlds = selectWorlds(queries, random)
 
@@ -144,7 +131,7 @@ fun Application.main() {
                 }
             }
 
-            call.respondText(json.encodeToString(worldsUpdated), ContentType.Application.Json)
+            call.respondJson(worldsUpdated)
         }
     }
 }
@@ -194,12 +181,4 @@ private fun configurePostgresR2DBC(config: ApplicationConfig): ConnectionFactory
     return ConnectionPool(cp)
 }
 
-private fun ApplicationCall.queries() = request.queryParameters["queries"]?.toIntOrNull()?.coerceIn(1, 500) ?: 1
-
-
-object Constants {
-    const val WORLD_QUERY = "SELECT id, randomnumber FROM world WHERE id = $1"
-    const val FORTUNES_QUERY = "SELECT id, message FROM fortune"
-    const val UPDATE_QUERY = "UPDATE world SET randomnumber = $1 WHERE id = $2"
-    const val DB_ROWS = 10000
-}
+private fun ApplicationCall.queries() = request.queryParameters["queries"]?.toIntOrNull()?.coerceIn(1, 500) ?: 1

+ 12 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Models.kt

@@ -0,0 +1,12 @@
+package org.jetbrains.ktor.benchmarks
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Message(val message: String)
+
+@Serializable
+data class World(val id: Int, var randomNumber: Int)
+
+@Serializable
+data class Fortune(val id: Int, var message: String)

+ 22 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Responses.kt

@@ -0,0 +1,22 @@
+package org.jetbrains.ktor.benchmarks
+
+import io.ktor.http.*
+import io.ktor.http.content.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import kotlinx.serialization.json.Json
+
+// Optimized JSON instance with better performance settings
+internal val json = Json {
+    prettyPrint = false
+    isLenient = true
+    ignoreUnknownKeys = true
+    coerceInputValues = true
+}
+
+internal suspend inline fun <reified E> RoutingCall.respondJson(response: E) {
+    respond(TextContent(
+        json.encodeToString(response),
+        ContentType.Application.Json
+    ))
+}

+ 0 - 6
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Fortune.kt

@@ -1,6 +0,0 @@
-package org.jetbrains.ktor.benchmarks.models
-
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class Fortune(val id: Int, var message: String)

+ 0 - 11
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Message.kt

@@ -1,11 +0,0 @@
-package org.jetbrains.ktor.benchmarks.models
-
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class Message(val message: String)
-
-// Cache common messages to reduce allocations
-object MessageCache {
-    val helloWorld = Message("Hello, world!")
-}

+ 0 - 6
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/World.kt

@@ -1,6 +0,0 @@
-package org.jetbrains.ktor.benchmarks.models
-
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class World(val id: Int, var randomNumber: Int)

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

@@ -39,6 +39,8 @@ Please note that the server holds tty so you may need nohup. See `setup.sh` for
 
 [Leonid Stashevsky](https://github.com/e5l)
 
+[Bruce Hamilton](https://github.com/bjhham)
+
 [Sergey Mashkov](https://github.com/cy6erGn0m)
 
 [Ilya Ryzhenkov](https://github.com/orangy) 

+ 8 - 3
frameworks/Kotlin/ktor/ktor/pom.xml

@@ -13,12 +13,12 @@
 
     <properties>
         <kotlin.version>2.1.20</kotlin.version>
-        <ktor.version>3.1.1</ktor.version>
-        <serialization.version>1.8.0</serialization.version>
+        <ktor.version>3.1.2</ktor.version>
+        <serialization.version>1.8.1</serialization.version>
         <kotlinx.html.version>0.12.0</kotlinx.html.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <hikaricp.version>5.1.0</hikaricp.version>
-        <logback.version>1.2.13</logback.version>
+        <logback.version>1.5.12</logback.version>
         <mysql.version>8.0.33</mysql.version>
         <postgresql.version>42.7.4</postgresql.version>
     </properties>
@@ -39,6 +39,11 @@
             <artifactId>kotlinx-serialization-json</artifactId>
             <version>${serialization.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-serialization-json-io</artifactId>
+            <version>${serialization.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.jetbrains.kotlinx</groupId>
             <artifactId>kotlinx-html-jvm</artifactId>

+ 32 - 0
frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Database.kt

@@ -0,0 +1,32 @@
+package org.jetbrains.ktor.benchmarks
+
+import com.zaxxer.hikari.HikariConfig
+
+fun HikariConfig.configurePostgres(poolSize: Int) {
+    jdbcUrl = "jdbc:postgresql://tfb-database/hello_world?useSSL=false"
+    driverClassName = org.postgresql.Driver::class.java.name
+    configureCommon(poolSize)
+}
+
+fun HikariConfig.configureCommon(poolSize: Int) {
+    username = "benchmarkdbuser"
+    password = "benchmarkdbpass"
+    addDataSourceProperty("cacheServerConfiguration", true)
+    addDataSourceProperty("cachePrepStmts", "true")
+    addDataSourceProperty("useUnbufferedInput", "false")
+    addDataSourceProperty("prepStmtCacheSize", "4096")
+    addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
+    connectionTimeout = 5000
+    maximumPoolSize = poolSize
+    minimumIdle = poolSize
+    idleTimeout = 300000 // 5 minutes
+    maxLifetime = 600000 // 10 minutes
+    validationTimeout = 5000
+    leakDetectionThreshold = 60000
+}
+
+fun HikariConfig.configureMySql(poolSize: Int) {
+    jdbcUrl = "jdbc:mysql://tfb-database:3306/hello_world?useSSL=false"
+    driverClassName = com.mysql.jdbc.Driver::class.java.name
+    configureCommon(poolSize)
+}

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

@@ -1,203 +0,0 @@
-package org.jetbrains.ktor.benchmarks
-
-import com.zaxxer.hikari.HikariConfig
-import com.zaxxer.hikari.HikariDataSource
-import io.ktor.http.*
-import io.ktor.http.content.*
-import io.ktor.server.application.*
-import io.ktor.server.html.*
-import io.ktor.server.plugins.defaultheaders.*
-import io.ktor.server.response.*
-import io.ktor.server.routing.*
-import kotlinx.coroutines.*
-import kotlinx.html.*
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.encodeToString
-import kotlinx.serialization.json.Json
-import org.jetbrains.ktor.benchmarks.Constants.FORTUNES_QUERY
-import org.jetbrains.ktor.benchmarks.Constants.UPDATE_QUERY
-import org.jetbrains.ktor.benchmarks.Constants.WORLD_QUERY
-import java.sql.Connection
-import java.util.concurrent.ThreadLocalRandom
-import kotlin.random.Random
-import kotlinx.serialization.Contextual
-
-@Serializable
-data class Message(val message: String)
-
-@Serializable
-data class World(val id: Int, var randomNumber: Int)
-
-@Serializable
-data class Fortune(val id: Int, var message: String)
-
-// Optimized JSON instance with better performance settings
-private val json = Json {
-    prettyPrint = false
-    isLenient = true
-    ignoreUnknownKeys = true
-    coerceInputValues = true
-}
-
-fun Application.main() {
-    val dbRows = 10000
-    val poolSize = Runtime.getRuntime().availableProcessors() * 2
-    val pool = HikariDataSource(HikariConfig().apply { configurePostgres(poolSize) })
-    
-    // Create a dedicated dispatcher for database operations
-    val databaseDispatcher = Dispatchers.IO.limitedParallelism(poolSize)
-
-    install(DefaultHeaders)
-
-    val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain)
-    val jsonResponse = json.encodeToString(Message("Hello, world!"))
-
-    routing {
-        get("/plaintext") {
-            call.respond(helloWorldContent)
-        }
-
-        get("/json") {
-            call.respondText(jsonResponse, ContentType.Application.Json)
-        }
-
-        get("/db") {
-            val random = Random.Default
-
-            val world = withContext(databaseDispatcher) {
-                pool.connection.use { connection ->
-                    connection.prepareStatement(WORLD_QUERY).use { statement ->
-                        statement.setInt(1, random.nextInt(dbRows) + 1)
-                        statement.executeQuery().use { rs ->
-                            rs.next()
-                            World(rs.getInt(1), rs.getInt(2))
-                        }
-                    }
-                }
-            }
-
-            call.respondText(json.encodeToString(world), ContentType.Application.Json)
-        }
-
-        fun Connection.selectWorlds(queries: Int, random: Random): List<World> {
-            val result = ArrayList<World>(queries)
-            prepareStatement(WORLD_QUERY).use { statement ->
-                repeat(queries) {
-                    statement.setInt(1, random.nextInt(dbRows) + 1)
-                    statement.executeQuery().use { rs ->
-                        rs.next()
-                        result += World(rs.getInt(1), rs.getInt(2))
-                    }
-                }
-            }
-            return result
-        }
-
-        get("/queries") {
-            val queries = call.queries()
-            val random = Random.Default
-
-            val result = withContext(databaseDispatcher) {
-                pool.connection.use { it.selectWorlds(queries, random) }
-            }
-
-            call.respondText(json.encodeToString(result), ContentType.Application.Json)
-        }
-
-        get("/fortunes") {
-            val result = mutableListOf<Fortune>()
-            withContext(databaseDispatcher) {
-                pool.connection.use { connection ->
-                    connection.prepareStatement(FORTUNES_QUERY).use { statement ->
-                        statement.executeQuery().use { rs ->
-                            while (rs.next()) {
-                                result += Fortune(rs.getInt(1), rs.getString(2))
-                            }
-                        }
-                    }
-                }
-            }
-            result.add(Fortune(0, "Additional fortune added at request time."))
-            result.sortBy { it.message }
-            call.respondHtml {
-                head { title { +"Fortunes" } }
-                body {
-                    table {
-                        tr {
-                            th { +"id" }
-                            th { +"message" }
-                        }
-                        for (fortune in result) {
-                            tr {
-                                td { +fortune.id.toString() }
-                                td { +fortune.message }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        get("/updates") {
-            val queries = call.queries()
-            val random = Random.Default
-            val result: List<World>
-
-            withContext(databaseDispatcher) {
-                pool.connection.use { connection ->
-                    result = connection.selectWorlds(queries, random)
-                    result.forEach { it.randomNumber = random.nextInt(dbRows) + 1 }
-
-                    connection.prepareStatement(UPDATE_QUERY).use { updateStatement ->
-                        for ((id, randomNumber) in result) {
-                            updateStatement.setInt(1, randomNumber)
-                            updateStatement.setInt(2, id)
-                            updateStatement.addBatch()
-                        }
-                        updateStatement.executeBatch()
-                    }
-                }
-            }
-
-            call.respondText(json.encodeToString(result), ContentType.Application.Json)
-        }
-    }
-}
-
-fun HikariConfig.configurePostgres(poolSize: Int) {
-    jdbcUrl = "jdbc:postgresql://tfb-database/hello_world?useSSL=false"
-    driverClassName = org.postgresql.Driver::class.java.name
-    configureCommon(poolSize)
-}
-
-fun HikariConfig.configureCommon(poolSize: Int) {
-    username = "benchmarkdbuser"
-    password = "benchmarkdbpass"
-    addDataSourceProperty("cacheServerConfiguration", true)
-    addDataSourceProperty("cachePrepStmts", "true")
-    addDataSourceProperty("useUnbufferedInput", "false")
-    addDataSourceProperty("prepStmtCacheSize", "4096")
-    addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
-    connectionTimeout = 5000 
-    maximumPoolSize = poolSize
-    minimumIdle = poolSize
-    idleTimeout = 300000 // 5 minutes
-    maxLifetime = 600000 // 10 minutes
-    validationTimeout = 5000
-    leakDetectionThreshold = 60000
-}
-
-fun HikariConfig.configureMySql(poolSize: Int) {
-    jdbcUrl = "jdbc:mysql://tfb-database:3306/hello_world?useSSL=false"
-    driverClassName = com.mysql.jdbc.Driver::class.java.name
-    configureCommon(poolSize)
-}
-
-fun ApplicationCall.queries() =
-    request.queryParameters["queries"]?.toIntOrNull()?.coerceIn(1, 500) ?: 1
-
-object Constants {
-    const val WORLD_QUERY = "SELECT id, randomNumber FROM World WHERE id = ?"
-    const val FORTUNES_QUERY = "SELECT id, message FROM fortune"
-    const val UPDATE_QUERY = "UPDATE World SET randomNumber = ? WHERE id = ?"
-}

+ 12 - 0
frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Models.kt

@@ -0,0 +1,12 @@
+package org.jetbrains.ktor.benchmarks
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Message(val message: String)
+
+@Serializable
+data class World(val id: Int, var randomNumber: Int)
+
+@Serializable
+data class Fortune(val id: Int, var message: String)

+ 22 - 0
frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Responses.kt

@@ -0,0 +1,22 @@
+package org.jetbrains.ktor.benchmarks
+
+import io.ktor.http.*
+import io.ktor.http.content.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import kotlinx.serialization.json.Json
+
+// Optimized JSON instance with better performance settings
+internal val json = Json {
+    prettyPrint = false
+    isLenient = true
+    ignoreUnknownKeys = true
+    coerceInputValues = true
+}
+
+internal suspend inline fun <reified E> RoutingCall.respondJson(response: E) {
+    respond(TextContent(
+        json.encodeToString(response),
+        ContentType.Application.Json
+    ))
+}

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

@@ -0,0 +1,151 @@
+package org.jetbrains.ktor.benchmarks
+
+import com.zaxxer.hikari.HikariConfig
+import com.zaxxer.hikari.HikariDataSource
+import io.ktor.http.*
+import io.ktor.http.content.*
+import io.ktor.server.application.*
+import io.ktor.server.html.respondHtml
+import io.ktor.server.plugins.defaultheaders.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.withContext
+import kotlinx.html.*
+import java.sql.Connection
+import java.util.StringJoiner
+import kotlin.random.Random
+
+const val HELLO_WORLD = "Hello, World!"
+const val WORLD_QUERY = "SELECT id, randomNumber FROM World WHERE id = ?"
+const val FORTUNES_QUERY = "SELECT id, message FROM fortune"
+const val DB_ROWS = 10_000
+
+@OptIn(ExperimentalCoroutinesApi::class)
+fun Application.main() {
+    val poolSize = Runtime.getRuntime().availableProcessors() * 2
+    val pool = HikariDataSource(HikariConfig().apply { configurePostgres(poolSize) })
+
+    // Create a dedicated dispatcher for database operations
+    val databaseDispatcher = Dispatchers.IO.limitedParallelism(poolSize)
+    val helloWorldContent = TextContent(HELLO_WORLD, ContentType.Text.Plain)
+    val random = Random.Default
+
+    install(DefaultHeaders)
+
+    routing {
+        get("/plaintext") {
+            call.respond(helloWorldContent)
+        }
+
+        get("/json") {
+            call.respondJson(Message(HELLO_WORLD))
+        }
+
+        get("/db") {
+            val world = withContext(databaseDispatcher) {
+                pool.connection.use { connection ->
+                    connection.prepareStatement(WORLD_QUERY).use { statement ->
+                        statement.setInt(1, random.nextInt(DB_ROWS) + 1)
+                        statement.executeQuery().use { rs ->
+                            rs.next()
+                            World(rs.getInt(1), rs.getInt(2))
+                        }
+                    }
+                }
+            }
+            call.respondJson(world)
+        }
+
+        fun Connection.selectWorlds(queries: Int): Array<World> =
+            prepareStatement(WORLD_QUERY).use { statement ->
+                Array<World>(queries) { i ->
+                    statement.setInt(1, random.nextInt(DB_ROWS) + 1)
+                    statement.executeQuery().use { rs ->
+                        rs.next()
+                        World(rs.getInt(1), rs.getInt(2))
+                    }
+                }
+            }
+
+        get("/queries") {
+            val queries = call.queries()
+            val result = withContext(databaseDispatcher) {
+                pool.connection.use { it.selectWorlds(queries) }
+            }
+            call.respondJson(result)
+        }
+
+        get("/fortunes") {
+            val result = mutableListOf<Fortune>()
+            withContext(databaseDispatcher) {
+                pool.connection.use { connection ->
+                    connection.prepareStatement(FORTUNES_QUERY).use { statement ->
+                        statement.executeQuery().use { rs ->
+                            while (rs.next()) {
+                                result += Fortune(rs.getInt(1), rs.getString(2))
+                            }
+                        }
+                    }
+                }
+            }
+            result.add(Fortune(0, "Additional fortune added at request time."))
+            result.sortBy { it.message }
+            call.respondHtml {
+                head { title { +"Fortunes" } }
+                body {
+                    table {
+                        tr {
+                            th { +"id" }
+                            th { +"message" }
+                        }
+                        for (fortune in result) {
+                            tr {
+                                td { +fortune.id.toString() }
+                                td { +fortune.message }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        get("/updates") {
+            val queries = call.queries()
+            val result: Array<World>
+
+            withContext(databaseDispatcher) {
+                pool.connection.use { connection ->
+                    result = connection.selectWorlds(queries)
+
+                    val updateSql = StringJoiner(
+                        ", ",
+                        "UPDATE World SET randomNumber = temp.randomNumber FROM (VALUES ",
+                        " ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = World.id"
+                    )
+
+                    for (i in result.indices) {
+                        result[i].randomNumber = random.nextInt(DB_ROWS) + 1
+                        updateSql.add("(?, ?)")
+                    }
+
+                    connection.prepareStatement(updateSql.toString()).use { statement ->
+                        var paramIndex = 0
+                        for (world in result) {
+                            statement.setInt(++paramIndex, world.id)
+                            statement.setInt(++paramIndex, world.randomNumber)
+                        }
+                        statement.executeUpdate()
+                    }
+                }
+            }
+
+            call.respondJson(result)
+        }
+    }
+}
+
+
+fun ApplicationCall.queries() =
+    request.queryParameters["queries"]?.toIntOrNull()?.coerceIn(1, 500) ?: 1

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

@@ -7,6 +7,6 @@ ktor {
     }
 
     application {
-        modules = [ org.jetbrains.ktor.benchmarks.HelloKt.main ]
+        modules = [org.jetbrains.ktor.benchmarks.MainKt.main]
     }
 }