-
Notifications
You must be signed in to change notification settings - Fork 79
Adding http4k benchmark #451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kristian-petras
wants to merge
7
commits into
renaissance-benchmarks:master
Choose a base branch
from
kristian-petras:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 4 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
32aa2c7
Init http4k benchmark
kristian-petras 9c4aa88
added workload benchmark
kristian-petras f9f6e6d
added result counters and moved to coroutine based execution
kristian-petras dae7fb4
update README.md
kristian-petras 5863094
MR comments
kristian-petras b1b1c99
Removed debug println
kristian-petras 0535293
renamed property
sf-petras File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
benchmarks/http4k/src/main/kotlin/org/renaissance/http4k/Http4kBenchmark.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| package org.renaissance.http4k | ||
|
|
||
| import kotlinx.coroutines.runBlocking | ||
| import org.http4k.client.OkHttp | ||
| import org.renaissance.Benchmark | ||
| import org.renaissance.Benchmark.* | ||
| import org.renaissance.BenchmarkContext | ||
| import org.renaissance.BenchmarkResult | ||
| import org.renaissance.BenchmarkResult.Validators | ||
| import org.renaissance.License | ||
| import org.renaissance.http4k.workload.WorkloadClient | ||
| import org.renaissance.http4k.workload.WorkloadConfiguration | ||
| import org.renaissance.http4k.workload.WorkloadServer | ||
|
|
||
| @Name("http4k") | ||
| @Group("kotlin") | ||
| @Summary("Runs the http4k server and tests the throughput of the server by sending requests to the server.") | ||
| @SupportsJvm("21") | ||
| @Licenses(License.APACHE2) | ||
| @Repetitions(20) | ||
| @Parameter( | ||
| name = "host", | ||
| defaultValue = "localhost", | ||
| summary = "Host of the server." | ||
| ) | ||
| @Parameter( | ||
| name = "port", | ||
| defaultValue = "8000", | ||
| summary = "Port of the server." | ||
| ) | ||
| @Parameter( | ||
| name = "read_workload_repeat_count", | ||
| defaultValue = "5", | ||
| summary = "Number of read requests to generate." | ||
| ) | ||
| @Parameter( | ||
| name = "write_workload_repeat_count", | ||
| defaultValue = "5", | ||
| summary = "Number of write requests to generate." | ||
| ) | ||
| @Parameter( | ||
| name = "ddos_workload_repeat_count", | ||
| defaultValue = "5", | ||
| summary = "Number of ddos requests to generate." | ||
| ) | ||
| @Parameter( | ||
| name = "mixed_workload_repeat_count", | ||
| defaultValue = "5", | ||
| summary = "Number of mixed requests to generate." | ||
| ) | ||
| @Parameter( | ||
| name = "workload_count", | ||
| defaultValue = "100", | ||
|
vhotspur marked this conversation as resolved.
Outdated
|
||
| summary = "Number of workloads to generate. Each workload consists of read, write, ddos and mixed requests." | ||
| ) | ||
| @Parameter( | ||
| name = "max_threads", | ||
| defaultValue = "16", | ||
|
vhotspur marked this conversation as resolved.
Outdated
|
||
| summary = "Maximum number of threads to use for the executor of the requests." | ||
| ) | ||
| @Parameter( | ||
| name = "workload_selector_seed", | ||
| defaultValue = "42", | ||
| summary = "Seed used to generate random workloads." | ||
| ) | ||
| @Configuration(name = "jmh") | ||
| class Http4kBenchmark : Benchmark { | ||
| private lateinit var server: WorkloadServer | ||
| private lateinit var client: WorkloadClient | ||
| private lateinit var configuration: WorkloadConfiguration | ||
|
|
||
| override fun run(context: BenchmarkContext): BenchmarkResult = runBlocking { | ||
| val workloadSummary = client.workload() | ||
| Validators.simple("Workload count", configuration.workloadCount.toLong(), workloadSummary.workloadCount) | ||
| } | ||
|
|
||
| override fun setUpBeforeEach(context: BenchmarkContext) { | ||
| configuration = context.toWorkloadConfiguration() | ||
| server = configuration.toWorkloadServer() | ||
| client = configuration.toWorkloadClient() | ||
| server.start() | ||
| } | ||
|
|
||
| override fun tearDownAfterEach(context: BenchmarkContext) { | ||
| server.stop() | ||
| } | ||
|
|
||
| private fun BenchmarkContext.toWorkloadConfiguration(): WorkloadConfiguration = WorkloadConfiguration( | ||
| host = parameter("host").value(), | ||
| port = parameter("port").value().toInt(), | ||
| readWorkloadRepeatCount = parameter("read_workload_repeat_count").value().toInt(), | ||
| writeWorkloadRepeatCount = parameter("write_workload_repeat_count").value().toInt(), | ||
| ddosWorkloadRepeatCount = parameter("ddos_workload_repeat_count").value().toInt(), | ||
| mixedWorkloadRepeatCount = parameter("mixed_workload_repeat_count").value().toInt(), | ||
| workloadCount = parameter("workload_count").value().toInt(), | ||
| maxThreads = parameter("max_threads").value().toInt(), | ||
| workloadSelectorSeed = parameter("workload_selector_seed").value().toLong() | ||
| ) | ||
|
|
||
| private fun WorkloadConfiguration.toWorkloadClient(): WorkloadClient = | ||
| WorkloadClient(OkHttp(), this) | ||
|
|
||
| private fun WorkloadConfiguration.toWorkloadServer(): WorkloadServer = | ||
| WorkloadServer(port) | ||
| } | ||
|
|
||
|
|
||
|
|
||
|
|
||
11 changes: 11 additions & 0 deletions
11
benchmarks/http4k/src/main/kotlin/org/renaissance/http4k/model/Product.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package org.renaissance.http4k.model | ||
|
|
||
| import org.http4k.core.Body | ||
| import org.http4k.format.Moshi.auto | ||
|
|
||
| internal data class Product(val id: String, val name: String) { | ||
| internal companion object { | ||
| internal val productLens = Body.auto<Product>().toLens() | ||
| internal val productsLens = Body.auto<Array<Product>>().toLens() | ||
| } | ||
| } |
145 changes: 145 additions & 0 deletions
145
benchmarks/http4k/src/main/kotlin/org/renaissance/http4k/workload/WorkloadClient.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| package org.renaissance.http4k.workload | ||
|
|
||
| import kotlinx.coroutines.* | ||
| import org.http4k.core.HttpHandler | ||
| import org.http4k.core.Method | ||
| import org.http4k.core.Request | ||
| import org.renaissance.http4k.model.Product | ||
| import java.util.* | ||
| import java.util.concurrent.atomic.AtomicLong | ||
| import kotlin.random.Random | ||
|
|
||
| /** | ||
| * Client used to generate workloads for the http4k server. | ||
| * The client sends requests to the server based on the workload type. | ||
| * @param client HttpHandler used to send requests to the server. | ||
| * @param configuration WorkloadConfiguration used to generate the workload. | ||
| */ | ||
| internal class WorkloadClient( | ||
| private val client: HttpHandler, private val configuration: WorkloadConfiguration | ||
| ) { | ||
| private val getProductsCounter = AtomicLong(0) | ||
| private val getProductCounter = AtomicLong(0) | ||
| private val postProductCounter = AtomicLong(0) | ||
|
|
||
| private val readCounter = AtomicLong(0) | ||
| private val writeCounter = AtomicLong(0) | ||
| private val ddosCounter = AtomicLong(0) | ||
| private val mixedCounter = AtomicLong(0) | ||
|
|
||
| private val workloadCounter = AtomicLong(0) | ||
|
|
||
| private val dispatcher = Dispatchers.IO.limitedParallelism(configuration.maxThreads, "Workload") | ||
|
|
||
| /** | ||
| * Starts the workload on the server based on [configuration]. | ||
| * Each workload consists of read, write, ddos and mixed requests. | ||
| * The number of workloads is determined by [WorkloadConfiguration.workloadCount]. | ||
| * The number of requests for each workload type is determined by the corresponding configuration value. | ||
| * Random workload is generated for each iteration based on the seed in [WorkloadConfiguration.workloadSelectorSeed]. | ||
| * @return WorkloadResult containing number of requests per type used for validation. | ||
| */ | ||
| suspend fun workload(): WorkloadSummary = coroutineScope { | ||
| val random = Random(configuration.workloadSelectorSeed) | ||
| withContext(dispatcher) { | ||
| range(configuration.workloadCount).flatMap { | ||
| when (random.nextWorkload()) { | ||
| WorkloadType.READ -> range(configuration.readWorkloadRepeatCount).map { async { client.readWorkload() } } | ||
| WorkloadType.WRITE -> range(configuration.writeWorkloadRepeatCount).map { async { client.writeWorkload() } } | ||
| WorkloadType.DDOS -> range(configuration.ddosWorkloadRepeatCount).map { async { client.ddosWorkload() } } | ||
| WorkloadType.MIXED -> range(configuration.mixedWorkloadRepeatCount).map { async { client.mixedWorkload() } } | ||
| }.also { workloadCounter.incrementAndGet() } | ||
| }.awaitAll() | ||
|
|
||
| WorkloadSummary( | ||
| getProductsCount = getProductsCounter.get(), | ||
| getProductCount = getProductCounter.get(), | ||
| postProductCount = postProductCounter.get(), | ||
| readCount = readCounter.get(), | ||
| writeCount = writeCounter.get(), | ||
| ddosCount = ddosCounter.get(), | ||
| mixedCount = mixedCounter.get(), | ||
| workloadCount = workloadCounter.get() | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Read workload gets all products and then iterates over each one and gets the specific product. | ||
| */ | ||
| private fun HttpHandler.readWorkload() { | ||
| val products = getProducts() | ||
| products.forEach { product -> | ||
| getProduct(product.id) | ||
| } | ||
| readCounter.incrementAndGet() | ||
| } | ||
|
|
||
| /** | ||
| * Write workload creates a new product. | ||
| */ | ||
| private fun HttpHandler.writeWorkload() { | ||
| val product = generateProduct() | ||
| postProduct(product) | ||
| writeCounter.incrementAndGet() | ||
| } | ||
|
|
||
| /** | ||
| * DDOS workload reads all products 10 times in a row. | ||
| */ | ||
| private fun HttpHandler.ddosWorkload() { | ||
| repeat(10) { | ||
| getProducts() | ||
| } | ||
| ddosCounter.incrementAndGet() | ||
| } | ||
|
|
||
| /** | ||
| * Mixed workload reads all products, then creates a new product and fetches it afterward. | ||
| */ | ||
| private fun HttpHandler.mixedWorkload() { | ||
| getProducts() | ||
| val product = generateProduct() | ||
| postProduct(product) | ||
| getProduct(product.id) | ||
| mixedCounter.incrementAndGet() | ||
| } | ||
|
|
||
| /** | ||
| * Helper functions to interact with the server. | ||
| */ | ||
| private fun HttpHandler.getProducts(): List<Product> = | ||
| Product.productsLens(this(Request(Method.GET, configuration.url("product")))).toList() | ||
| .also { getProductsCounter.incrementAndGet() } | ||
|
|
||
| private fun HttpHandler.getProduct(id: String) = | ||
| this(Request(Method.GET, configuration.url("product/$id"))).also { getProductCounter.incrementAndGet() } | ||
|
|
||
| private fun HttpHandler.postProduct(product: Product) = this( | ||
| Product.productLens( | ||
| product, | ||
| Request(Method.POST, configuration.url("product")) | ||
| ) | ||
| ).also { postProductCounter.incrementAndGet() } | ||
|
|
||
| /** | ||
| * Helper function to generate a URL from the configuration. | ||
| */ | ||
| private fun WorkloadConfiguration.url(endpoint: String) = "http://$host:$port/$endpoint" | ||
|
|
||
| /** | ||
| * Helper function to generate a random workload type. | ||
| */ | ||
| private fun Random.nextWorkload() = WorkloadType.entries[nextInt(WorkloadType.entries.size)] | ||
|
|
||
| /** | ||
| * Helper function to generate a new product with random id. | ||
| */ | ||
| private fun generateProduct(): Product { | ||
| val id = UUID.randomUUID().toString() | ||
| val name = "Product $id" | ||
| return Product(id, name) | ||
| } | ||
|
|
||
| private fun range(end: Int) = (1..end) | ||
| } |
13 changes: 13 additions & 0 deletions
13
benchmarks/http4k/src/main/kotlin/org/renaissance/http4k/workload/WorkloadConfiguration.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package org.renaissance.http4k.workload | ||
|
|
||
| internal data class WorkloadConfiguration( | ||
| val host: String, | ||
| val port: Int, | ||
| val readWorkloadRepeatCount: Int, | ||
| val writeWorkloadRepeatCount: Int, | ||
| val ddosWorkloadRepeatCount: Int, | ||
| val mixedWorkloadRepeatCount: Int, | ||
| val workloadCount: Int, | ||
| val maxThreads: Int, | ||
| val workloadSelectorSeed: Long, | ||
| ) |
51 changes: 51 additions & 0 deletions
51
benchmarks/http4k/src/main/kotlin/org/renaissance/http4k/workload/WorkloadServer.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package org.renaissance.http4k.workload | ||
|
|
||
| import org.http4k.core.HttpHandler | ||
| import org.http4k.core.Method | ||
| import org.http4k.core.Response | ||
| import org.http4k.core.Status | ||
| import org.http4k.routing.bind | ||
| import org.http4k.routing.path | ||
| import org.http4k.routing.routes | ||
| import org.http4k.server.Http4kServer | ||
| import org.http4k.server.Undertow | ||
| import org.http4k.server.asServer | ||
| import org.renaissance.http4k.model.Product | ||
| import java.util.concurrent.ConcurrentHashMap | ||
|
|
||
| internal class WorkloadServer(private val port: Int) : Http4kServer { | ||
| private val server = app().asServer(Undertow(port)) | ||
| private val products: MutableMap<String, Product> = ConcurrentHashMap<String, Product>() | ||
|
|
||
| private fun app(): HttpHandler = routes( | ||
| "/product" bind Method.GET to { Product.productsLens(products.values.toTypedArray(), Response(Status.OK)) }, | ||
| "/product/{id}" bind Method.GET to { | ||
| when (val id = it.path("id")) { | ||
| null -> Response(Status.BAD_REQUEST) | ||
| !in products -> Response(Status.NOT_FOUND) | ||
| else -> { | ||
| val product = products[id] ?: error("Invariant error: Product $it should be present") | ||
| Product.productLens(product, Response(Status.OK)) | ||
| } | ||
| } | ||
| }, | ||
| "/product" bind Method.POST to { | ||
| val product = Product.productLens(it) | ||
| products[product.id] = product | ||
| Response(Status.CREATED) | ||
| } | ||
| ) | ||
|
|
||
| override fun port(): Int = port | ||
|
|
||
| override fun start(): Http4kServer { | ||
| server.start() | ||
| return this | ||
| } | ||
|
|
||
| override fun stop(): Http4kServer { | ||
| server.stop() | ||
| products.clear() | ||
| return this | ||
| } | ||
| } |
12 changes: 12 additions & 0 deletions
12
benchmarks/http4k/src/main/kotlin/org/renaissance/http4k/workload/WorkloadSummary.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package org.renaissance.http4k.workload | ||
|
|
||
| internal data class WorkloadSummary( | ||
| val getProductsCount: Long, | ||
| val getProductCount: Long, | ||
| val postProductCount: Long, | ||
| val readCount: Long, | ||
| val writeCount: Long, | ||
| val ddosCount: Long, | ||
| val mixedCount: Long, | ||
| val workloadCount: Long | ||
| ) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.