Browse Source

Update Pellet to 0.0.14, include more benchmarks (#7442)

Carrot 3 years ago
parent
commit
18dc222e06

+ 20 - 4
frameworks/Kotlin/pellet/README.md

@@ -4,16 +4,32 @@
 
 
 This is a simple set of benchmarks as part of the TechEmpower Web Framework Benchmarks suite.
 This is a simple set of benchmarks as part of the TechEmpower Web Framework Benchmarks suite.
 
 
-Currently the suite only includes plaintext and JSON serialization benchmarks.
+The suite currently includes the plaintext, JSON serialization, single query, multiple query, database updates, and fortunes tests.
 
 
 All routes are contained within the [Benchmark.kt](sample/src/main/kotlin/benchmark/Benchmark.kt) file.
 All routes are contained within the [Benchmark.kt](sample/src/main/kotlin/benchmark/Benchmark.kt) file.
 
 
 ## Test URLs
 ## Test URLs
 
 
-### JSON
+### Plaintext
+
+http://localhost:8080/plaintext
+
+### JSON Serialization
 
 
 http://localhost:8080/json
 http://localhost:8080/json
 
 
-### Plaintext
+### Single Query
 
 
-http://localhost:8080/plaintext
+http://localhost:8080/db
+
+### Multiple Queries
+
+http://localhost:8080/queries
+
+### Database Updates
+
+http://localhost:8080/updates
+
+### Fortunes
+
+http://localhost:8080/fortunes

+ 6 - 2
frameworks/Kotlin/pellet/benchmark_config.json

@@ -5,14 +5,18 @@
       "default": {
       "default": {
         "json_url": "/json",
         "json_url": "/json",
         "plaintext_url": "/plaintext",
         "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/query?queries=",
+        "update_url": "/updates?queries=",
+        "fortune_url": "/fortunes",
         "port": 8080,
         "port": 8080,
         "approach": "Realistic",
         "approach": "Realistic",
         "classification": "Micro",
         "classification": "Micro",
-        "database": "None",
+        "database": "Postgres",
         "framework": "Pellet",
         "framework": "Pellet",
         "language": "Kotlin",
         "language": "Kotlin",
         "flavor": "None",
         "flavor": "None",
-        "orm": "None",
+        "orm": "micro",
         "platform": "None",
         "platform": "None",
         "webserver": "None",
         "webserver": "None",
         "os": "Linux",
         "os": "Linux",

+ 23 - 6
frameworks/Kotlin/pellet/sample/build.gradle.kts

@@ -1,8 +1,9 @@
 plugins {
 plugins {
     application
     application
     id("com.github.johnrengelman.shadow") version "7.1.0"
     id("com.github.johnrengelman.shadow") version "7.1.0"
-    kotlin("jvm") version "1.6.21"
-    kotlin("plugin.serialization") version "1.6.21"
+    kotlin("jvm") version "1.7.0"
+    kotlin("plugin.serialization") version "1.7.0"
+    id("nu.studer.rocker") version "3.0.4"
 }
 }
 
 
 group = "benchmark"
 group = "benchmark"
@@ -12,15 +13,31 @@ repositories {
     mavenCentral()
     mavenCentral()
 }
 }
 
 
+rocker {
+    version.set("1.3.0")
+    configurations {
+        create("main") {
+            optimize.set(true)
+            templateDir.set(file("src/main/resources"))
+            outputDir.set(file("src/generated/rocker"))
+        }
+    }
+}
+
 dependencies {
 dependencies {
-    implementation("dev.pellet:pellet-server:0.0.7")
-    implementation("dev.pellet:pellet-logging:0.0.7")
-    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
+    implementation(platform("dev.pellet:pellet-bom:0.0.14"))
+    implementation("dev.pellet:pellet-server")
+    implementation("dev.pellet:pellet-logging")
+    implementation("org.slf4j:slf4j-api:1.7.36")
+    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3")
     implementation(platform(kotlin("bom")))
     implementation(platform(kotlin("bom")))
     implementation(kotlin("stdlib-jdk8"))
     implementation(kotlin("stdlib-jdk8"))
-    implementation(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.1"))
+    implementation(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.2"))
     implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
     implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
     implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8")
     implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8")
+    implementation("io.vertx:vertx-pg-client:4.3.1")
+    implementation("io.vertx:vertx-lang-kotlin:4.3.1")
+    implementation("io.vertx:vertx-lang-kotlin-coroutines:4.3.1")
 }
 }
 
 
 java {
 java {

+ 84 - 1
frameworks/Kotlin/pellet/sample/src/main/kotlin/benchmark/Benchmark.kt

@@ -1,5 +1,8 @@
 package benchmark
 package benchmark
 
 
+import benchmark.data.Fortune
+import benchmark.data.TFBRepository
+import com.fizzed.rocker.runtime.StringBuilderOutput
 import dev.pellet.logging.pelletLogger
 import dev.pellet.logging.pelletLogger
 import dev.pellet.server.PelletBuilder.httpRouter
 import dev.pellet.server.PelletBuilder.httpRouter
 import dev.pellet.server.PelletBuilder.pelletServer
 import dev.pellet.server.PelletBuilder.pelletServer
@@ -13,15 +16,23 @@ import java.time.Instant
 import java.time.ZoneId
 import java.time.ZoneId
 import java.time.format.DateTimeFormatter
 import java.time.format.DateTimeFormatter
 import java.util.Locale
 import java.util.Locale
+import java.util.concurrent.ThreadLocalRandom
 
 
 object Benchmark
 object Benchmark
 
 
 val logger = pelletLogger<Benchmark>()
 val logger = pelletLogger<Benchmark>()
+val jsonEncoder = Json {
+    prettyPrint = false
+}
 
 
 fun main() = runBlocking {
 fun main() = runBlocking {
     val sharedRouter = httpRouter {
     val sharedRouter = httpRouter {
         get("/plaintext", ::handlePlain)
         get("/plaintext", ::handlePlain)
         get("/json", ::handleJson)
         get("/json", ::handleJson)
+        get("/db", ::handleDb)
+        get("/query", ::handleQuery)
+        get("/updates", ::handleUpdates)
+        get("/fortunes", ::handleFortunes)
     }
     }
     val pellet = pelletServer {
     val pellet = pelletServer {
         logRequests = false
         logRequests = false
@@ -62,8 +73,80 @@ private suspend fun handleJson(
     val responseBody = ResponseBody(message = "Hello, World!")
     val responseBody = ResponseBody(message = "Hello, World!")
     return HTTPRouteResponse.Builder()
     return HTTPRouteResponse.Builder()
         .statusCode(200)
         .statusCode(200)
-        .jsonEntity(Json, responseBody)
+        .jsonEntity(jsonEncoder, responseBody)
+        .header("Server", "pellet")
+        .header("Date", dateFormatter.format(Instant.now()))
+        .build()
+}
+
+private val repository = TFBRepository()
+
+private suspend fun handleDb(
+    context: PelletHTTPRouteContext
+): HTTPRouteResponse {
+    val result = repository.fetchWorld()
+    return HTTPRouteResponse.Builder()
+        .statusCode(200)
+        .jsonEntity(jsonEncoder, result)
+        .header("Server", "pellet")
+        .header("Date", dateFormatter.format(Instant.now()))
+        .build()
+}
+
+private suspend fun handleQuery(
+    context: PelletHTTPRouteContext
+): HTTPRouteResponse {
+    val rawQueries = context.firstQueryParameter("queries").getOrNull()
+    val queries = (rawQueries?.toIntOrNull() ?: 1).coerceIn(1, 500)
+    val worlds = (1 .. queries)
+        .map {
+            repository.fetchWorld()
+        }
+    return HTTPRouteResponse.Builder()
+        .statusCode(200)
+        .jsonEntity(jsonEncoder, worlds)
+        .header("Server", "pellet")
+        .header("Date", dateFormatter.format(Instant.now()))
+        .build()
+}
+
+private suspend fun handleUpdates(
+    context: PelletHTTPRouteContext
+): HTTPRouteResponse {
+    val rawQueries = context.firstQueryParameter("queries").getOrNull()
+    val queries = (rawQueries?.toIntOrNull() ?: 1).coerceIn(1, 500)
+    val worlds = (1 .. queries)
+        .map {
+            repository.fetchWorld()
+        }
+    val newWorlds = worlds.map {
+        it.copy(
+            randomNumber = ThreadLocalRandom.current().nextInt(1, 10001)
+        )
+    }
+    repository.updateWorlds(newWorlds)
+    return HTTPRouteResponse.Builder()
+        .statusCode(200)
+        .jsonEntity(jsonEncoder, newWorlds)
         .header("Server", "pellet")
         .header("Server", "pellet")
         .header("Date", dateFormatter.format(Instant.now()))
         .header("Date", dateFormatter.format(Instant.now()))
         .build()
         .build()
 }
 }
+
+private suspend fun handleFortunes(
+    context: PelletHTTPRouteContext
+): HTTPRouteResponse {
+    val newFortune = Fortune(0, "Additional fortune added at request time.")
+    val fortunes = repository.fetchFortunes().toMutableList()
+    fortunes.add(newFortune)
+    fortunes.sortBy { it.message }
+    val template = views.fortunes.template(fortunes)
+        .render(StringBuilderOutput.FACTORY)
+        .toString()
+    return HTTPRouteResponse.Builder()
+        .statusCode(200)
+        .entity(template, "text/html; charset=utf-8")
+        .header("Server", "pellet")
+        .header("Date", dateFormatter.format(Instant.now()))
+        .build()
+}

+ 3 - 0
frameworks/Kotlin/pellet/sample/src/main/kotlin/benchmark/data/Fortune.kt

@@ -0,0 +1,3 @@
+package benchmark.data
+
+data class Fortune(val id: Int, val message: String)

+ 6 - 0
frameworks/Kotlin/pellet/sample/src/main/kotlin/benchmark/data/FortuneDAO.kt

@@ -0,0 +1,6 @@
+package benchmark.data
+
+interface FortuneDAO {
+
+    suspend fun fetchFortunes(): List<Fortune>
+}

+ 59 - 0
frameworks/Kotlin/pellet/sample/src/main/kotlin/benchmark/data/TFBRepository.kt

@@ -0,0 +1,59 @@
+package benchmark.data
+
+import io.vertx.kotlin.coroutines.await
+import io.vertx.pgclient.PgConnectOptions
+import io.vertx.pgclient.PgPool
+import io.vertx.sqlclient.PoolOptions
+import io.vertx.sqlclient.Tuple
+import java.util.concurrent.ThreadLocalRandom
+
+class TFBRepository: WorldDAO, FortuneDAO {
+
+    private val connectOptions = PgConnectOptions()
+        .setPort(5432)
+        .setHost("tfb-database")
+        .setDatabase("hello_world")
+        .setUser("benchmarkdbuser")
+        .setPassword("benchmarkdbpass")
+        .apply {
+            cachePreparedStatements = true
+        }
+
+    private val poolOptions = PoolOptions()
+    private val client = PgPool.client(connectOptions, poolOptions)
+
+    override suspend fun fetchWorld(): WorldDTO {
+        val worldId = ThreadLocalRandom.current().nextInt(1, 10001)
+        val result = client
+            .preparedQuery("select id, randomNumber from world where id = $1")
+            .execute(Tuple.of(worldId))
+            .await()
+        val row = result.first()
+        return WorldDTO(
+            row.getInteger(0),
+            row.getInteger(1)
+        )
+    }
+
+    override suspend fun updateWorlds(worlds: List<WorldDTO>) {
+        val batch = worlds.map {
+            Tuple.of(it.id, it.randomNumber)
+        }
+        client
+            .preparedQuery("update world set randomNumber = $1 where id = $2")
+            .executeBatch(batch)
+            .await()
+    }
+
+    override suspend fun fetchFortunes(): List<Fortune> {
+        val results = client.preparedQuery("select id, message from fortune")
+            .execute()
+            .await()
+        return results.map {
+            Fortune(
+                it.getInteger(0),
+                it.getString(1)
+            )
+        }
+    }
+}

+ 7 - 0
frameworks/Kotlin/pellet/sample/src/main/kotlin/benchmark/data/WorldDAO.kt

@@ -0,0 +1,7 @@
+package benchmark.data
+
+interface WorldDAO {
+
+    suspend fun fetchWorld(): WorldDTO
+    suspend fun updateWorlds(worlds: List<WorldDTO>)
+}

+ 6 - 0
frameworks/Kotlin/pellet/sample/src/main/kotlin/benchmark/data/WorldDTO.kt

@@ -0,0 +1,6 @@
+package benchmark.data
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class WorldDTO(val id: Int, val randomNumber: Int)

+ 15 - 0
frameworks/Kotlin/pellet/sample/src/main/resources/views/fortunes.rocker.html

@@ -0,0 +1,15 @@
+@import java.util.List
+@import benchmark.data.Fortune
+@args (List<Fortune> fortunes)
+    <!DOCTYPE html>
+    <html>
+    <head><title>Fortunes</title></head>
+    <body>
+    <table>
+        <tr><th>id</th><th>message</th></tr>
+        @for (it: fortunes) {
+        <tr><td>@it.getId()</td><td>@it.getMessage()</td></tr>
+        }
+    </table>
+    </body>
+    </html>