Browse Source

http4k: move to java 21, upgrades and new graalvm for helidon backend (#8511)

* new java 21 upgrades and graalvm for helidon

* refactor database code (pgclient and jdbc) to use execute batch

* Revert "refactor database code (pgclient and jdbc) to use execute batch"

This reverts commit 58083555ce66f9efd80edb123b52a4fdf823fe18.

* pull JSON representation of World out to HTTP layer instead of at DB layer

* pull out Random to be a shared value

* reupdate JDBC example to use batch execute on update

* reupdate PGclient implementations to use batch execute on update

* bump (for flakey testrun in sunhttploom (consistently passes locally)

* actually sort the list of worlds in PGclient

* Tweak SunHttpLoom configuration

* tweaks to pgclient database calls

* tweaks to pgclient database calls

* tweaks to jdbc database calls

* use prepared statements for JDBC queries
David Denton 1 year ago
parent
commit
a1221e7611
37 changed files with 368 additions and 157 deletions
  1. 1 1
      frameworks/Kotlin/http4k/README.md
  2. 0 6
      frameworks/Kotlin/http4k/apache-graalvm/build.gradle.kts
  3. 21 1
      frameworks/Kotlin/http4k/benchmark_config.json
  4. 20 0
      frameworks/Kotlin/http4k/build.gradle.kts
  5. 17 1
      frameworks/Kotlin/http4k/config.toml
  6. 47 57
      frameworks/Kotlin/http4k/core-jdbc/src/main/kotlin/PostgresDatabase.kt
  7. 37 30
      frameworks/Kotlin/http4k/core-pgclient/src/main/kotlin/PostgresDatabase.kt
  8. 1 1
      frameworks/Kotlin/http4k/core/build.gradle.kts
  9. 27 0
      frameworks/Kotlin/http4k/core/src/main/kotlin/CachedDatabase.kt
  10. 6 31
      frameworks/Kotlin/http4k/core/src/main/kotlin/Database.kt
  11. 1 1
      frameworks/Kotlin/http4k/core/src/main/kotlin/PlainTextRoute.kt
  12. 13 5
      frameworks/Kotlin/http4k/core/src/main/kotlin/WorldRoutes.kt
  13. 0 6
      frameworks/Kotlin/http4k/graalvm/build.gradle.kts
  14. BIN
      frameworks/Kotlin/http4k/gradle/wrapper/gradle-wrapper.jar
  15. 1 1
      frameworks/Kotlin/http4k/gradle/wrapper/gradle-wrapper.properties
  16. 14 0
      frameworks/Kotlin/http4k/helidon-graalvm/build.gradle.kts
  17. 92 0
      frameworks/Kotlin/http4k/helidon-graalvm/config/reflect-config.json
  18. 9 0
      frameworks/Kotlin/http4k/helidon-graalvm/config/resource-config.json
  19. 5 0
      frameworks/Kotlin/http4k/helidon-graalvm/src/main/kotlin/http4k/Http4kGraalVMBenchmarkServer.kt
  20. 1 1
      frameworks/Kotlin/http4k/http4k-apache-graalvm.dockerfile
  21. 1 1
      frameworks/Kotlin/http4k/http4k-apache.dockerfile
  22. 1 1
      frameworks/Kotlin/http4k/http4k-apache4.dockerfile
  23. 1 1
      frameworks/Kotlin/http4k/http4k-graalvm.dockerfile
  24. 25 0
      frameworks/Kotlin/http4k/http4k-helidon-graalvm.dockerfile
  25. 1 1
      frameworks/Kotlin/http4k/http4k-helidon-jdbc.dockerfile
  26. 1 1
      frameworks/Kotlin/http4k/http4k-helidon-pgclient.dockerfile
  27. 1 1
      frameworks/Kotlin/http4k/http4k-jetty.dockerfile
  28. 1 1
      frameworks/Kotlin/http4k/http4k-jettyloom-jdbc.dockerfile
  29. 1 1
      frameworks/Kotlin/http4k/http4k-jettyloom-pgclient.dockerfile
  30. 1 1
      frameworks/Kotlin/http4k/http4k-ktorcio.dockerfile
  31. 1 1
      frameworks/Kotlin/http4k/http4k-ktornetty.dockerfile
  32. 1 1
      frameworks/Kotlin/http4k/http4k-netty.dockerfile
  33. 1 1
      frameworks/Kotlin/http4k/http4k-ratpack.dockerfile
  34. 1 1
      frameworks/Kotlin/http4k/http4k-sunhttploom.dockerfile
  35. 1 1
      frameworks/Kotlin/http4k/http4k-undertow.dockerfile
  36. 1 1
      frameworks/Kotlin/http4k/http4k.dockerfile
  37. 15 0
      frameworks/Kotlin/http4k/settings.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/README.md

@@ -19,7 +19,7 @@ The tests were run with:
 - SunHttp/SunHttpLoom (default - bundled with core module - zero dependencies)
 - SunHttp/SunHttpLoom (default - bundled with core module - zero dependencies)
 - Apache (5)
 - Apache (5)
 - Apache4
 - Apache4
-- Helidon (Nima)
+- Helidon
 - KtorCIO
 - KtorCIO
 - KtorNetty
 - KtorNetty
 - Jetty/JettyLoom
 - Jetty/JettyLoom

+ 0 - 6
frameworks/Kotlin/http4k/apache-graalvm/build.gradle.kts

@@ -1,11 +1,5 @@
 application.mainClass.set("Http4kGraalVMBenchmarkServerKt")
 application.mainClass.set("Http4kGraalVMBenchmarkServerKt")
 
 
-kotlin {
-    jvmToolchain {
-        languageVersion.set(JavaLanguageVersion.of(20))
-    }
-}
-
 dependencies {
 dependencies {
     api(project(":core-jdbc"))
     api(project(":core-jdbc"))
     api(project(":apache"))
     api(project(":apache"))

+ 21 - 1
frameworks/Kotlin/http4k/benchmark_config.json

@@ -34,7 +34,6 @@
         "fortune_url": "/fortunes",
         "fortune_url": "/fortunes",
         "plaintext_url": "/plaintext",
         "plaintext_url": "/plaintext",
         "query_url": "/queries?queries=",
         "query_url": "/queries?queries=",
-        "update_url": "/updates?queries=",
         "database": "Postgres",
         "database": "Postgres",
         "port": 9000,
         "port": 9000,
         "approach": "Realistic",
         "approach": "Realistic",
@@ -134,6 +133,27 @@
         "notes": "https://http4k.org",
         "notes": "https://http4k.org",
         "versus": "servlet"
         "versus": "servlet"
       },
       },
+      "helidon-graalvm": {
+        "orm": "Raw",
+        "database_os": "Linux",
+        "cached_query_url": "/cached?queries=",
+        "db_url": "/db",
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "database": "Postgres",
+        "port": 9000,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "framework": "http4k",
+        "language": "Kotlin",
+        "platform": "graalvm",
+        "webserver": "None",
+        "os": "Linux",
+        "notes": "https://http4k.org",
+        "versus": "helidon-jdbc"
+      },
       "helidon-jdbc": {
       "helidon-jdbc": {
         "orm": "Raw",
         "orm": "Raw",
         "database_os": "Linux",
         "database_os": "Linux",

+ 20 - 0
frameworks/Kotlin/http4k/build.gradle.kts

@@ -1,4 +1,6 @@
 import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
 import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+import org.gradle.api.JavaVersion.VERSION_1_8
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 
 plugins {
 plugins {
     kotlin("jvm") version "1.9.10"
     kotlin("jvm") version "1.9.10"
@@ -16,6 +18,12 @@ buildscript {
     }
     }
 }
 }
 
 
+kotlin {
+    jvmToolchain {
+        languageVersion.set(JavaLanguageVersion.of(21))
+    }
+}
+
 allprojects {
 allprojects {
     apply(plugin = "kotlin")
     apply(plugin = "kotlin")
     apply(plugin = "com.github.johnrengelman.shadow")
     apply(plugin = "com.github.johnrengelman.shadow")
@@ -25,7 +33,19 @@ allprojects {
         mavenCentral()
         mavenCentral()
     }
     }
 
 
+    java {
+        sourceCompatibility = VERSION_1_8
+        targetCompatibility = VERSION_1_8
+    }
+
     tasks {
     tasks {
+        withType<KotlinCompile> {
+            kotlinOptions {
+                jvmTarget = "1.8"
+                allWarningsAsErrors = true
+            }
+        }
+
         named<ShadowJar>("shadowJar") {
         named<ShadowJar>("shadowJar") {
             archiveBaseName.set("http4k-benchmark")
             archiveBaseName.set("http4k-benchmark")
             archiveClassifier.set("")
             archiveClassifier.set("")

+ 17 - 1
frameworks/Kotlin/http4k/config.toml

@@ -80,7 +80,6 @@ urls.fortune = "/fortunes"
 urls.json = "/json"
 urls.json = "/json"
 urls.plaintext = "/plaintext"
 urls.plaintext = "/plaintext"
 urls.query = "/queries?queries="
 urls.query = "/queries?queries="
-urls.update = "/updates?queries="
 approach = "Realistic"
 approach = "Realistic"
 classification = "Micro"
 classification = "Micro"
 database = "Postgres"
 database = "Postgres"
@@ -217,6 +216,23 @@ platform = "apache-httpcore"
 webserver = "None"
 webserver = "None"
 versus = "servlet"
 versus = "servlet"
 
 
+[helidon-graalvm]
+urls.cached_query = "/cached?queries="
+urls.db = "/db"
+urls.json = "/json"
+urls.plaintext = "/plaintext"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "graalvm"
+webserver = "None"
+versus = "helidon-jdbc"
+
 [apache-graalvm]
 [apache-graalvm]
 urls.cached_query = "/cached?queries="
 urls.cached_query = "/cached?queries="
 urls.db = "/db"
 urls.db = "/db"

+ 47 - 57
frameworks/Kotlin/http4k/core-jdbc/src/main/kotlin/PostgresDatabase.kt

@@ -1,39 +1,39 @@
 import com.zaxxer.hikari.HikariConfig
 import com.zaxxer.hikari.HikariConfig
 import com.zaxxer.hikari.HikariDataSource
 import com.zaxxer.hikari.HikariDataSource
-import org.http4k.format.Argo.number
-import org.http4k.format.Argo.obj
 import java.sql.Connection
 import java.sql.Connection
-import java.sql.PreparedStatement
 import java.sql.ResultSet
 import java.sql.ResultSet
+import java.util.Random
 import javax.sql.DataSource
 import javax.sql.DataSource
 
 
 class PostgresDatabase private constructor(private val dataSource: DataSource) : Database {
 class PostgresDatabase private constructor(private val dataSource: DataSource) : Database {
+    private val random = Random()
 
 
-    override fun findWorld() = withConnection { findWorld(randomWorld()) }
+    override fun findWorld() = withConnection { findWorld(random.world()) }
 
 
-    override fun loadAll() = withConnection { findAll() }
+    override fun loadAll() = withConnection {
+        executeQuery("SELECT id, randomNumber FROM world") { it.toResultsList(::toWorld) }
+    }
 
 
     override fun findWorlds(count: Int) = withConnection {
     override fun findWorlds(count: Int) = withConnection {
-        (1..count).map { findWorld(randomWorld()) }
+        (1..count).map { findWorld(random.world()) }
     }
     }
 
 
     override fun updateWorlds(count: Int) = withConnection {
     override fun updateWorlds(count: Int) = withConnection {
-        (1..count).map {
-            val id = randomWorld()
-            updateWorld(id)
-            findWorld(id)
-        }
-    }
+        val updatedAndSorted = (1..count)
+            .map { findWorld(random.world()).first to random.world() }
+            .sortedBy { it.first }
 
 
-    private fun Connection.updateWorld(id: Int) = withStatement("UPDATE world SET randomNumber = ? WHERE id = ?") {
-        setInt(1, randomWorld())
-        setInt(2, id)
-        executeUpdate()
+        createStatement().use { stmt ->
+            updatedAndSorted.forEach {
+                stmt.addBatch("UPDATE world SET randomNumber = ${it.second} WHERE id = ${it.first}")
+            }
+            stmt.executeBatch()
+        }
+        updatedAndSorted
     }
     }
 
 
     override fun fortunes() = withConnection {
     override fun fortunes() = withConnection {
-        val original =
-            withStatement("select * from fortune") { executeQuery().toResultsList { Fortune(getInt(1), getString(2)) } }
+        val original = executeQuery("select * from fortune") { it.toResultsList(::toFortune) }
         (original + Fortune(0, "Additional fortune added at request time.")).sortedBy { it.message }
         (original + Fortune(0, "Additional fortune added at request time.")).sortedBy { it.message }
     }
     }
 
 
@@ -43,52 +43,42 @@ class PostgresDatabase private constructor(private val dataSource: DataSource) :
                 username = "benchmarkdbuser"
                 username = "benchmarkdbuser"
                 password = "benchmarkdbpass"
                 password = "benchmarkdbpass"
                 jdbcUrl = "jdbc:postgresql://tfb-database:5432/hello_world?" +
                 jdbcUrl = "jdbc:postgresql://tfb-database:5432/hello_world?" +
-                    "useSSL=false&" +
-                    "jdbcCompliantTruncation=false&" +
-                    "elideSetAutoCommits=true&" +
-                    "useLocalSessionState=true&" +
-                    "cachePrepStmts=true&" +
-                    "cacheCallableStmts=true&" +
-                    "alwaysSendSetIsolation=false&" +
-                    "prepStmtCacheSize=4096&" +
-                    "cacheServerConfiguration=true&" +
-                    "prepStmtCacheSqlLimit=2048&" +
-                    "traceProtocol=false&" +
-                    "useUnbufferedInput=false&" +
-                    "useReadAheadInput=false&" +
-                    "maintainTimeStats=false&" +
-                    "useServerPrepStmts=true&" +
-                    "cacheRSMetadata=true"
+                        "useSSL=false&" +
+                        "jdbcCompliantTruncation=false&" +
+                        "elideSetAutoCommits=true&" +
+                        "useLocalSessionState=true&" +
+                        "cachePrepStmts=true&" +
+                        "cacheCallableStmts=true&" +
+                        "alwaysSendSetIsolation=false&" +
+                        "prepStmtCacheSize=4096&" +
+                        "cacheServerConfiguration=true&" +
+                        "prepStmtCacheSqlLimit=2048&" +
+                        "traceProtocol=false&" +
+                        "useUnbufferedInput=false&" +
+                        "useReadAheadInput=false&" +
+                        "maintainTimeStats=false&" +
+                        "useServerPrepStmts=true&" +
+                        "cacheRSMetadata=true"
                 maximumPoolSize = 100
                 maximumPoolSize = 100
                 HikariDataSource(this)
                 HikariDataSource(this)
             })
             })
     }
     }
 
 
     private inline fun <T> withConnection(fn: Connection.() -> T): T = dataSource.connection.use(fn)
     private inline fun <T> withConnection(fn: Connection.() -> T): T = dataSource.connection.use(fn)
+}
 
 
-    private inline fun <T> Connection.withStatement(stmt: String, fn: PreparedStatement.() -> T): T =
-        prepareStatement(stmt).use(fn)
+private fun Connection.findWorld(id: Int) = prepareStatement("SELECT id, randomNumber FROM world WHERE id = ?").use {
+    toWorld(it.apply { setInt(1, id) }.executeQuery().also { it.next() })
+}
 
 
-    private fun Connection.findWorld(id: Int) =
-        withStatement("SELECT id, randomNumber FROM world WHERE id = ?") {
-            setInt(1, id)
-            executeQuery().toResultsList {
-                obj("id" to number(getInt("id")), "randomNumber" to number(getInt("randomNumber")))
-            }.first()
-        }
+private inline fun <T> ResultSet.toResultsList(fn: (ResultSet) -> T): List<T> =
+    mutableListOf<T>().apply {
+        while (next()) add(fn(this@toResultsList))
+    }
 
 
-    private fun Connection.findAll() =
-        withStatement("SELECT id, randomNumber FROM world") {
-            executeQuery().toResultsList {
-                val id = getInt("id")
-                id to obj("id" to number(id), "randomNumber" to number(getInt("randomNumber")))
-            }.toMap()
-        }
+private inline fun <T> Connection.executeQuery(stmt: String, fn: (ResultSet) -> T): T =
+    prepareStatement(stmt).use { fn(it.executeQuery()) }
 
 
-    private inline fun <T> ResultSet.toResultsList(fn: ResultSet.() -> T): List<T> =
-        mutableListOf<T>().apply {
-            while (next()) {
-                add(fn(this@toResultsList))
-            }
-        }
-}
+private fun toFortune(it: ResultSet) = Fortune(it.getInt(1), it.getString(2))
+
+private fun toWorld(resultSet: ResultSet) = resultSet.getInt("id") to resultSet.getInt("randomNumber")

+ 37 - 30
frameworks/Kotlin/http4k/core-pgclient/src/main/kotlin/PostgresDatabase.kt

@@ -1,17 +1,20 @@
+import io.vertx.core.Future
 import io.vertx.core.Vertx
 import io.vertx.core.Vertx
 import io.vertx.core.VertxOptions
 import io.vertx.core.VertxOptions
 import io.vertx.pgclient.PgConnectOptions
 import io.vertx.pgclient.PgConnectOptions
 import io.vertx.pgclient.PgPool.client
 import io.vertx.pgclient.PgPool.client
 import io.vertx.sqlclient.PoolOptions
 import io.vertx.sqlclient.PoolOptions
+import io.vertx.sqlclient.Row
 import io.vertx.sqlclient.SqlClient
 import io.vertx.sqlclient.SqlClient
 import io.vertx.sqlclient.Tuple
 import io.vertx.sqlclient.Tuple
-import org.http4k.format.Argo.number
-import org.http4k.format.Argo.obj
+import java.util.Random
 
 
 class PostgresDatabase : Database {
 class PostgresDatabase : Database {
     private val queryPool: SqlClient
     private val queryPool: SqlClient
     private val updatePool: SqlClient
     private val updatePool: SqlClient
 
 
+    private val random = Random()
+
     init {
     init {
         val vertx = Vertx.vertx(VertxOptions().setPreferNativeTransport(true))
         val vertx = Vertx.vertx(VertxOptions().setPreferNativeTransport(true))
         val connectOptions = PgConnectOptions().apply {
         val connectOptions = PgConnectOptions().apply {
@@ -28,49 +31,53 @@ class PostgresDatabase : Database {
     }
     }
 
 
     override fun findWorld() =
     override fun findWorld() =
-        findWorld(randomWorld(), queryPool).map { it.toJson() }.toCompletionStage().toCompletableFuture().get()
+        queryPool.findWorld(random.world()).toCompletionStage().toCompletableFuture().get()
 
 
     override fun loadAll() = queryPool.preparedQuery("SELECT id, randomnumber FROM world ")
     override fun loadAll() = queryPool.preparedQuery("SELECT id, randomnumber FROM world ")
         .execute()
         .execute()
-        .map {
-            it.associate {
-                it.getInteger("id") to (it.getInteger("id") to it.getInteger("randomnumber")).toJson()
-            }
-        }
+        .map { it.map(::toWorld) }
         .toCompletionStage().toCompletableFuture().get()
         .toCompletionStage().toCompletableFuture().get()
 
 
     override fun findWorlds(count: Int) =
     override fun findWorlds(count: Int) =
-        (1..count).map {
-            findWorld(randomWorld(), queryPool)
-                .map { it.toJson() }.toCompletionStage().toCompletableFuture().get()
-        }
+        Future
+            .all(
+                (1..count).map { queryPool.findWorld(random.world()) }
+            ).toCompletionStage().toCompletableFuture().get().list<World>()
+
+    override fun updateWorlds(count: Int): List<Pair<Int, Int>> {
+        val updatedAndSorted = Future
+            .all(
+                (1..count)
+                    .map {
+                        queryPool.findWorld(random.world())
+                            .map { it.first to random.world() }
+                    }
+            )
+            .toCompletionStage().toCompletableFuture().get().list<World>()
+            .sortedBy { it.first }
 
 
-    override fun updateWorlds(count: Int) =
-        (1..count)
-            .map { randomWorld() to randomWorld() }
-            .map { update ->
-                updatePool.preparedQuery("UPDATE world SET randomnumber = $1 WHERE id = $2")
-                    .execute(Tuple.of(update.first, update.second))
-                    .flatMap { findWorld(update.first, queryPool).map { it.toJson() } }
-                    .toCompletionStage().toCompletableFuture().get()
-            }
+        updatePool.preparedQuery("UPDATE world SET randomnumber = $1 WHERE id = $2")
+            .executeBatch(updatedAndSorted.map { Tuple.of(it.first, it.second) })
+            .toCompletionStage().toCompletableFuture().get()
+
+        return updatedAndSorted
+    }
 
 
     override fun fortunes() = queryPool.preparedQuery("SELECT id, message FROM fortune")
     override fun fortunes() = queryPool.preparedQuery("SELECT id, message FROM fortune")
         .execute()
         .execute()
-        .map { it.map { Fortune(it.getInteger(0), it.getString(1)) } }
+        .map { it.map(::toFortune) }
         .map { (it + Fortune(0, "Additional fortune added at request time.")) }
         .map { (it + Fortune(0, "Additional fortune added at request time.")) }
+        .map { it.sortedBy { it.message } }
         .toCompletionStage().toCompletableFuture().get()
         .toCompletionStage().toCompletableFuture().get()
-        .sortedBy { it.message }
 
 
     companion object {
     companion object {
-        private fun findWorld(id: Int, pool: SqlClient) =
-            pool.preparedQuery("SELECT id, randomnumber FROM world WHERE id = $1")
+        private fun SqlClient.findWorld(id: Int) =
+            preparedQuery("SELECT id, randomnumber FROM world WHERE id = $1")
                 .execute(Tuple.of(id))
                 .execute(Tuple.of(id))
-                .map { rows ->
-                    val r = rows.iterator().next()
-                    r.getInteger("id") to r.getInteger("randomnumber")
-                }
+                .map { toWorld(it.single()) }
     }
     }
 }
 }
 
 
-private fun Pair<Int, Int>.toJson() = obj("id" to number(first), "randomNumber" to number(second))
+private fun toWorld(r: Row) = r.getInteger("id") to r.getInteger("randomnumber")
+
+private fun toFortune(it: Row) = Fortune(it.getInteger(0), it.getString(1))

+ 1 - 1
frameworks/Kotlin/http4k/core/build.gradle.kts

@@ -4,7 +4,7 @@ plugins {
 }
 }
 
 
 dependencies {
 dependencies {
-    api(platform("org.http4k:http4k-bom:5.8.2.0"))
+    api(platform("org.http4k:http4k-bom:5.9.0.0"))
     api("org.jetbrains.kotlin:kotlin-stdlib:1.9.10")
     api("org.jetbrains.kotlin:kotlin-stdlib:1.9.10")
     api("org.jetbrains.kotlin:kotlin-reflect:1.9.10")
     api("org.jetbrains.kotlin:kotlin-reflect:1.9.10")
     api("org.http4k:http4k-core")
     api("org.http4k:http4k-core")

+ 27 - 0
frameworks/Kotlin/http4k/core/src/main/kotlin/CachedDatabase.kt

@@ -0,0 +1,27 @@
+import org.cache2k.Cache
+import org.cache2k.Cache2kBuilder
+import java.util.Random
+
+class CachedDatabase(private val delegate: Database) : Database by delegate {
+
+    private val random = Random()
+
+    private val cache = object : Cache2kBuilder<Int, Int>() {}
+        .name("cachedWorld")
+        .eternal(true)
+        .entryCapacity(TOTAL_DB_ROWS.toLong())
+        .build()
+        .apply { refresh() }
+
+    private fun Cache<Int, Int>.refresh() = putAll(delegate.loadAll().toMap())
+
+    override fun findWorlds(count: Int) = (1..count).mapNotNull {
+        val randomWorld = random.world()
+        cache.peek(randomWorld)?.let { randomWorld to it }
+    }
+
+    override fun updateWorlds(count: Int) =
+        delegate.updateWorlds(count).onEach { cache.put(it.first, it.second) }
+
+    override fun loadAll() = cache.asMap().toList()
+}

+ 6 - 31
frameworks/Kotlin/http4k/core/src/main/kotlin/Database.kt

@@ -1,38 +1,13 @@
-import argo.jdom.JsonNode
-import org.cache2k.Cache
-import org.cache2k.Cache2kBuilder
 import java.util.Random
 import java.util.Random
 
 
 interface Database {
 interface Database {
-    fun findWorld(): JsonNode
-    fun loadAll(): Map<Int, JsonNode>
-    fun findWorlds(count: Int): List<JsonNode>
-    fun updateWorlds(count: Int): List<JsonNode>
+    fun findWorld(): World
+    fun loadAll(): List<World>
+    fun findWorlds(count: Int): List<World>
+    fun updateWorlds(count: Int): List<World>
     fun fortunes(): List<Fortune>
     fun fortunes(): List<Fortune>
 }
 }
 
 
-private const val TOTAL_DB_ROWS = 10000
+const val TOTAL_DB_ROWS = 10000
 
 
-class CachedDatabase(private val delegate: Database) : Database by delegate {
-    private val cache = object : Cache2kBuilder<Int, JsonNode>() {}
-        .name("cachedWorld")
-        .eternal(true)
-        .entryCapacity(TOTAL_DB_ROWS.toLong())
-        .build()
-        .apply {
-            refresh()
-        }
-
-    private fun Cache<Int, JsonNode>.refresh() {
-        putAll(delegate.loadAll())
-    }
-
-    override fun findWorlds(count: Int) = (1..count).map { cache.peek(randomWorld()) }
-
-    override fun updateWorlds(count: Int) =
-        delegate.updateWorlds(count).onEach { cache.put(it.getNumberValue("id").toInt(), it) }
-
-    override fun loadAll(): Map<Int, JsonNode> = cache.asMap()
-}
-
-fun randomWorld() = Random().nextInt(TOTAL_DB_ROWS - 1) + 1
+fun Random.world() = nextInt(TOTAL_DB_ROWS - 1) + 1

+ 1 - 1
frameworks/Kotlin/http4k/core/src/main/kotlin/PlainTextRoute.kt

@@ -12,4 +12,4 @@ object PlainTextRoute {
     operator fun invoke() = "/plaintext" bind GET to {
     operator fun invoke() = "/plaintext" bind GET to {
         Response(OK).body(preAllocatedHelloWorldText).with(CONTENT_TYPE of TEXT_PLAIN)
         Response(OK).body(preAllocatedHelloWorldText).with(CONTENT_TYPE of TEXT_PLAIN)
     }
     }
-}
+}

+ 13 - 5
frameworks/Kotlin/http4k/core/src/main/kotlin/WorldRoutes.kt

@@ -1,8 +1,10 @@
+import argo.jdom.JsonNode
 import org.http4k.core.Body
 import org.http4k.core.Body
 import org.http4k.core.Method.GET
 import org.http4k.core.Method.GET
 import org.http4k.core.Response
 import org.http4k.core.Response
 import org.http4k.core.Status.Companion.OK
 import org.http4k.core.Status.Companion.OK
 import org.http4k.core.with
 import org.http4k.core.with
+import org.http4k.format.Argo
 import org.http4k.format.Argo.array
 import org.http4k.format.Argo.array
 import org.http4k.format.Argo.json
 import org.http4k.format.Argo.json
 import org.http4k.lens.Query
 import org.http4k.lens.Query
@@ -23,22 +25,28 @@ object WorldRoutes {
     }.defaulted("queries", 1)
     }.defaulted("queries", 1)
 
 
     fun queryRoute(db: Database) = "/db" bind GET to {
     fun queryRoute(db: Database) = "/db" bind GET to {
-        let { Response(OK).with(jsonBody of db.findWorld()) }
+        let { Response(OK).with(jsonBody of toJson(db.findWorld())) }
     }
     }
 
 
     fun multipleRoute(db: Database) = "/queries" bind GET to {
     fun multipleRoute(db: Database) = "/queries" bind GET to {
-        Response(OK).with(jsonBody of array(db.findWorlds(numberOfQueries(it))))
+        Response(OK).with(jsonBody of array(db.findWorlds(numberOfQueries(it)).map(::toJson)))
     }
     }
 
 
     fun cachedRoute(db: Database): RoutingHttpHandler {
     fun cachedRoute(db: Database): RoutingHttpHandler {
         val cachedDb = CachedDatabase(db)
         val cachedDb = CachedDatabase(db)
 
 
         return "/cached" bind GET to {
         return "/cached" bind GET to {
-            Response(OK).with(jsonBody of array(cachedDb.findWorlds(numberOfQueries(it))))
+            val findWorlds = cachedDb.findWorlds(numberOfQueries(it))
+            Response(OK).with(jsonBody of array(findWorlds.map(::toJson)))
         }
         }
     }
     }
 
 
     fun updateRoute(db: Database) = "/updates" bind GET to {
     fun updateRoute(db: Database) = "/updates" bind GET to {
-        Response(OK).with(jsonBody of array(db.updateWorlds(numberOfQueries(it))))
+        Response(OK).with(jsonBody of array(db.updateWorlds(numberOfQueries(it)).map(::toJson)))
     }
     }
-}
+}
+
+private fun toJson(world: World): JsonNode =
+    Argo.obj("id" to Argo.number(world.first), "randomNumber" to Argo.number(world.second))
+
+typealias World = Pair<Int, Int>

+ 0 - 6
frameworks/Kotlin/http4k/graalvm/build.gradle.kts

@@ -1,11 +1,5 @@
 application.mainClass.set("Http4kGraalVMBenchmarkServerKt")
 application.mainClass.set("Http4kGraalVMBenchmarkServerKt")
 
 
-kotlin {
-    jvmToolchain {
-        languageVersion.set(JavaLanguageVersion.of(20))
-    }
-}
-
 dependencies {
 dependencies {
     api(project(":core-jdbc"))
     api(project(":core-jdbc"))
     api(project(":sunhttp"))
     api(project(":sunhttp"))

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


+ 1 - 1
frameworks/Kotlin/http4k/gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
 networkTimeout=10000
 networkTimeout=10000
 validateDistributionUrl=true
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
 zipStoreBase=GRADLE_USER_HOME

+ 14 - 0
frameworks/Kotlin/http4k/helidon-graalvm/build.gradle.kts

@@ -0,0 +1,14 @@
+application.mainClass.set("Http4kGraalVMBenchmarkServerKt")
+
+dependencies {
+    api(project(":core-jdbc"))
+    api(project(":helidon-jdbc"))
+}
+
+tasks {
+    named<Jar>("jar") {
+        manifest {
+            attributes["Main-Class"] = "Http4kGraalVMBenchmarkServerKt"
+        }
+    }
+}

+ 92 - 0
frameworks/Kotlin/http4k/helidon-graalvm/config/reflect-config.json

@@ -0,0 +1,92 @@
+[
+  {
+    "name": "com.zaxxer.hikari.HikariConfig",
+    "allDeclaredFields": true
+  },
+  {
+    "name": "com.zaxxer.hikari.HikariDataSource",
+    "methods": [
+      {
+        "name": "<init>",
+        "parameterTypes": []
+      }
+    ],
+    "allDeclaredFields": true
+  },
+  {
+    "name": "com.zaxxer.hikari.util.ConcurrentBag$IConcurrentBagEntry[]"
+  },
+  {
+    "name": "Fortune",
+    "allDeclaredConstructors": true,
+    "allPublicConstructors": true,
+    "allDeclaredMethods": true,
+    "allPublicMethods": true,
+    "allDeclaredClasses": true,
+    "allPublicClasses": true
+  },
+  {
+    "name": "FortunesList",
+    "allDeclaredConstructors": true,
+    "allPublicConstructors": true,
+    "allDeclaredMethods": true,
+    "allPublicMethods": true,
+    "allDeclaredClasses": true,
+    "allPublicClasses": true
+  },
+  {
+    "name": "com.zaxxer.hikari.util.ConcurrentBag",
+    "allDeclaredConstructors": true,
+    "allPublicConstructors": true,
+    "allDeclaredMethods": true,
+    "allPublicMethods": true,
+    "allDeclaredClasses": true,
+    "allPublicClasses": true
+  },
+  {
+    "name": "com.zaxxer.hikari.pool.PoolEntry",
+    "allDeclaredConstructors": true,
+    "allPublicConstructors": true,
+    "allDeclaredMethods": true,
+    "allPublicMethods": true,
+    "allDeclaredClasses": true,
+    "allPublicClasses": true
+  },
+  {
+    "name": "java.sql.Statement[]"
+  },
+  {
+    "name": "org.postgresql.Driver"
+  },
+  {
+    "name": "java.lang.SecurityManager",
+    "methods": [
+      {
+        "name": "checkPermission",
+        "parameterTypes": [
+          "java.security.Permission"
+        ]
+      }
+    ]
+  },
+  {
+    "name": "java.lang.System",
+    "methods": [
+      {
+        "name": "getSecurityManager",
+        "parameterTypes": []
+      }
+    ]
+  },
+  {
+    "name": "java.security.AccessController",
+    "methods": [
+      {
+        "name": "doPrivileged",
+        "parameterTypes": [
+          "java.security.PrivilegedExceptionAction"
+        ]
+      }
+    ]
+  }
+]

+ 9 - 0
frameworks/Kotlin/http4k/helidon-graalvm/config/resource-config.json

@@ -0,0 +1,9 @@
+{
+  "resources": {
+    "includes": [
+      {
+        "pattern": ".*peb$"
+      }
+    ]
+  }
+}

+ 5 - 0
frameworks/Kotlin/http4k/helidon-graalvm/src/main/kotlin/http4k/Http4kGraalVMBenchmarkServer.kt

@@ -0,0 +1,5 @@
+import org.http4k.server.Helidon
+
+fun main() {
+    Http4kBenchmarkServer(PostgresDatabase()).start(Helidon(9000))
+}

+ 1 - 1
frameworks/Kotlin/http4k/http4k-apache-graalvm.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20 as gradle
+FROM gradle:8.4.0-jdk21 as gradle
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k-apache.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k-apache4.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k-graalvm.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20 as gradle
+FROM gradle:8.4.0-jdk21 as gradle
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 25 - 0
frameworks/Kotlin/http4k/http4k-helidon-graalvm.dockerfile

@@ -0,0 +1,25 @@
+FROM gradle:8.4.0-jdk21 as gradle
+USER root
+WORKDIR /http4k
+COPY build.gradle.kts build.gradle.kts
+COPY settings.gradle.kts settings.gradle.kts
+COPY core core
+COPY core-jdbc core-jdbc
+COPY core-pgclient core-pgclient
+COPY helidon-jdbc helidon-jdbc
+COPY helidon-graalvm helidon-graalvm
+
+RUN gradle --quiet --no-daemon helidon-graalvm:shadowJar
+FROM ghcr.io/graalvm/graalvm-community:21.0.0-ol9-20230919 as graalvm
+COPY --from=gradle /http4k/core/src/main/resources/* /home/app/http4k-helidon-graalvm/
+COPY --from=gradle /http4k/helidon-graalvm/build/libs/http4k-benchmark.jar /home/app/http4k-helidon-graalvm/
+COPY --from=gradle /http4k/helidon-graalvm/config/*.json /home/app/http4k-helidon-graalvm/
+WORKDIR /home/app/http4k-helidon-graalvm
+RUN native-image \
+    -H:ReflectionConfigurationFiles=reflect-config.json \
+    -H:ResourceConfigurationFiles=resource-config.json \
+    --initialize-at-build-time="org.slf4j.LoggerFactory,org.slf4j.simple.SimpleLogger,org.slf4j.impl.StaticLoggerBinder" \
+    --no-fallback -cp http4k-benchmark.jar Http4kGraalVMBenchmarkServerKt
+
+EXPOSE 9000
+ENTRYPOINT ["/home/app/http4k-helidon-graalvm/http4kgraalvmbenchmarkserverkt"]

+ 1 - 1
frameworks/Kotlin/http4k/http4k-helidon-jdbc.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k-helidon-pgclient.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k-jetty.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k-jettyloom-jdbc.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k-jettyloom-pgclient.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k-ktorcio.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k-ktornetty.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k-netty.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k-ratpack.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k-sunhttploom.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k-undertow.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 1 - 1
frameworks/Kotlin/http4k/http4k.dockerfile

@@ -1,4 +1,4 @@
-FROM gradle:8.3.0-jdk20
+FROM gradle:8.4.0-jdk21
 USER root
 USER root
 WORKDIR /http4k
 WORKDIR /http4k
 COPY build.gradle.kts build.gradle.kts
 COPY build.gradle.kts build.gradle.kts

+ 15 - 0
frameworks/Kotlin/http4k/settings.gradle.kts

@@ -5,6 +5,20 @@ pluginManagement {
     }
     }
 }
 }
 
 
+plugins {
+    id("org.gradle.toolchains.foojay-resolver") version "0.4.0"
+}
+
+toolchainManagement {
+    jvm {
+        javaRepositories {
+            repository("foojay") {
+                resolverClass.set(org.gradle.toolchains.foojay.FoojayToolchainResolver::class.java)
+            }
+        }
+    }
+}
+
 rootProject.name = "http4k-benchmark"
 rootProject.name = "http4k-benchmark"
 include("core")
 include("core")
 include("core-jdbc")
 include("core-jdbc")
@@ -18,6 +32,7 @@ include("jettyloom-jdbc")
 include("jettyloom-pgclient")
 include("jettyloom-pgclient")
 include("helidon-jdbc")
 include("helidon-jdbc")
 include("helidon-pgclient")
 include("helidon-pgclient")
+include("helidon-graalvm")
 include("ktorcio")
 include("ktorcio")
 include("ktornetty")
 include("ktornetty")
 include("netty")
 include("netty")