mirror of
https://github.com/jlengrand/ktor.git
synced 2026-03-10 08:31:20 +00:00
Client benchmarks
This commit is contained in:
committed by
Leonid Stashevsky
parent
dfc4d6bcf3
commit
ae4d6efbfe
@@ -4,7 +4,7 @@ buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven { url "https://plugins.gradle.org/m2/" }
|
||||
gradlePluginPortal()
|
||||
maven { url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies" }
|
||||
maven { url "https://dl.bintray.com/kotlin/kotlin-dev" }
|
||||
maven { url "https://dl.bintray.com/orangy/maven" }
|
||||
@@ -18,6 +18,7 @@ buildscript {
|
||||
classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomic_fu_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
classpath "me.champeau.gradle:jmh-gradle-plugin:$jmh_plugin_version"
|
||||
classpath "org.jetbrains.gradle.benchmarks:benchmarks.plugin:$benchmarks_version"
|
||||
classpath "kotlinx.team:kotlinx.team.infra:$infra_version"
|
||||
}
|
||||
}
|
||||
@@ -72,6 +73,7 @@ allprojects {
|
||||
}
|
||||
maven { url "https://dl.bintray.com/kotlin/kotlin-eap" }
|
||||
maven { url "https://dl.bintray.com/kotlin/kotlin-dev" }
|
||||
maven { url "https://dl.bintray.com/orangy/maven" }
|
||||
jcenter()
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ kotlin_version=1.3.31
|
||||
|
||||
# kotlin libraries
|
||||
infra_version=0.1.0-dev-42
|
||||
benchmarks_version=0.1.7-dev-17
|
||||
|
||||
kotlinx_io_version=0.1.8
|
||||
serialization_version=0.11.0
|
||||
|
||||
@@ -7,7 +7,8 @@ dependencies {
|
||||
'ktor-client-curl',
|
||||
'ktor-client-ios',
|
||||
'ktor-client',
|
||||
'ktor-client-features'
|
||||
'ktor-client-features',
|
||||
'ktor-client-benchmarks'
|
||||
].toSet()
|
||||
|
||||
def projects = [].toSet()
|
||||
|
||||
52
ktor-client/ktor-client-benchmarks/build.gradle.kts
Normal file
52
ktor-client/ktor-client-benchmarks/build.gradle.kts
Normal file
@@ -0,0 +1,52 @@
|
||||
import org.jetbrains.gradle.benchmarks.*
|
||||
|
||||
plugins {
|
||||
id("org.jetbrains.gradle.benchmarks.plugin")
|
||||
id("kotlin-allopen")
|
||||
id("kotlinx-atomicfu")
|
||||
}
|
||||
|
||||
allOpen {
|
||||
annotation("org.openjdk.jmh.annotations.State")
|
||||
}
|
||||
|
||||
val jmh_version by extra.properties
|
||||
val benchmarks_version by extra.properties
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation(project(":ktor-client:ktor-client-core"))
|
||||
implementation("org.jetbrains.gradle.benchmarks:runtime:$benchmarks_version")
|
||||
}
|
||||
}
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
implementation(project(":ktor-client:ktor-client-cio"))
|
||||
implementation(project(":ktor-client:ktor-client-apache"))
|
||||
implementation(project(":ktor-client:ktor-client-android"))
|
||||
implementation(project(":ktor-client:ktor-client-okhttp"))
|
||||
implementation(project(":ktor-client:ktor-client-jetty"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
benchmark {
|
||||
configurations {
|
||||
(register("jvm") as? JvmBenchmarkConfiguration)?.apply {
|
||||
jmhVersion = "$jmh_version"
|
||||
}
|
||||
}
|
||||
|
||||
defaults.apply {
|
||||
iterationTime = 100
|
||||
iterations = 3
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run benchmarks:
|
||||
* ./gradlew :ktor-client:ktor-client-benchmarks:benchmark
|
||||
**/
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.ktor.client.benchmarks
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
internal fun <T> runBenchmark(block: suspend CoroutineScope.() -> T): Unit = runBlocking<Unit> {
|
||||
block()
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.ktor.client.benchmarks
|
||||
|
||||
import io.ktor.client.engine.android.*
|
||||
import io.ktor.client.engine.apache.*
|
||||
import io.ktor.client.engine.cio.*
|
||||
import io.ktor.client.engine.jetty.*
|
||||
import io.ktor.client.engine.okhttp.*
|
||||
|
||||
internal class ApacheClientBenchmarks : KtorClientBenchmarks(Apache)
|
||||
|
||||
internal class OkHttpClientBenchmarks : KtorClientBenchmarks(OkHttp)
|
||||
|
||||
internal class AndroidClientBenchmarks : KtorClientBenchmarks(Android)
|
||||
|
||||
internal class CIOClientBenchmarks : KtorClientBenchmarks(CIO)
|
||||
|
||||
internal class JettyClientBenchmarks : KtorClientBenchmarks(Jetty)
|
||||
@@ -0,0 +1,60 @@
|
||||
package io.ktor.client.benchmarks
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.*
|
||||
import io.ktor.client.request.*
|
||||
import org.jetbrains.gradle.benchmarks.*
|
||||
|
||||
internal const val TEST_BENCHMARKS_SERVER = "http://127.0.0.1:8080/benchmarks"
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
internal abstract class KtorClientBenchmarks(
|
||||
private val factory: HttpClientEngineFactory<*>
|
||||
) {
|
||||
lateinit var client: HttpClient
|
||||
|
||||
@Setup
|
||||
fun start() {
|
||||
client = HttpClient(factory)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun download1K() = runBenchmark {
|
||||
client.download(1)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun download16K() = runBenchmark {
|
||||
client.download(16)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun download32K() = runBenchmark {
|
||||
client.download(32)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun download64K() = runBenchmark {
|
||||
client.download(64)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun download256K() = runBenchmark {
|
||||
client.download(256)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun download1024K() = runBenchmark {
|
||||
client.download(1024)
|
||||
}
|
||||
|
||||
@TearDown
|
||||
fun stop() {
|
||||
client.close()
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend inline fun HttpClient.download(size: Int) {
|
||||
val data = get<ByteArray>("$TEST_BENCHMARKS_SERVER/bytes?size=$size")
|
||||
check(data.size == size * 1024)
|
||||
}
|
||||
@@ -47,6 +47,19 @@ suspend fun HttpClient.wsRaw(
|
||||
request: HttpRequestBuilder.() -> Unit = {}, block: suspend ClientWebSocketSession.() -> Unit
|
||||
): Unit = webSocketRaw(method, host, port, path, request, block)
|
||||
|
||||
|
||||
/**
|
||||
* Open [DefaultClientWebSocketSession].
|
||||
*/
|
||||
suspend fun HttpClient.ws(
|
||||
urlString: String,
|
||||
request: HttpRequestBuilder.() -> Unit = {},
|
||||
block: suspend DefaultClientWebSocketSession.() -> Unit
|
||||
): Unit {
|
||||
val url = Url(urlString)
|
||||
webSocket(HttpMethod.Get, url.host, url.port, url.encodedPath, request, block)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create secure raw [ClientWebSocketSession]: no ping-pong and other service messages are used.
|
||||
*/
|
||||
|
||||
@@ -53,7 +53,8 @@ internal suspend fun HttpRequestData.executeRequest(
|
||||
internal suspend fun HTTP2Client.connect(
|
||||
url: Url, config: JettyEngineConfig
|
||||
): Session = withPromise { promise ->
|
||||
connect(config.sslContextFactory, InetSocketAddress(url.host, url.port), Session.Listener.Adapter(), promise)
|
||||
val factory = if (url.protocol.isSecure()) config.sslContextFactory else null
|
||||
connect(factory, InetSocketAddress(url.host, url.port), Session.Listener.Adapter(), promise)
|
||||
}
|
||||
|
||||
private fun HttpRequestData.prepareHeadersFrame(): HeadersFrame {
|
||||
|
||||
@@ -36,6 +36,16 @@ class OkHttpEngine(
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
super.close()
|
||||
|
||||
coroutineContext[Job]?.invokeOnCompletion {
|
||||
engine.dispatcher().executorService().shutdown()
|
||||
engine.connectionPool().evictAll()
|
||||
engine.cache()?.close()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun executeWebSocketRequest(
|
||||
engineRequest: Request,
|
||||
callContext: CoroutineContext
|
||||
|
||||
@@ -74,15 +74,17 @@ task startTestServer(type: KtorTestServer, dependsOn: assemble) {
|
||||
classpath = kotlin.targets.jvm.compilations["test"].runtimeDependencyFiles
|
||||
}
|
||||
|
||||
if (!project.ext.ideaActive) {
|
||||
def testTasks = [
|
||||
'macosX64Test', 'linuxX64Test', 'iosTest', 'mingwX64Test', 'jvmTest', 'jsTestMochaChrome', 'jsTestMochaNode'
|
||||
]
|
||||
def testTasks = [
|
||||
'jvmTest', 'jvmBenchmark'
|
||||
]
|
||||
|
||||
rootProject.allprojects {
|
||||
it.tasks.matching { it.name in testTasks }*.configure { testTask ->
|
||||
testTask.dependsOn startTestServer
|
||||
}
|
||||
if (!project.ext.ideaActive) {
|
||||
testTasks += ['macosX64Test', 'linuxX64Test', 'iosTest', 'mingwX64Test', 'jsTestMochaChrome', 'jsTestMochaNode']
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
it.tasks.matching { it.name in testTasks }*.configure { testTask ->
|
||||
testTask.dependsOn startTestServer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package io.ktor.client.tests.utils
|
||||
|
||||
import io.ktor.application.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.http.cio.websocket.*
|
||||
import io.ktor.http.content.*
|
||||
import io.ktor.request.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.websocket.*
|
||||
import kotlinx.coroutines.io.*
|
||||
|
||||
internal fun Application.benchmarks() {
|
||||
routing {
|
||||
route("/benchmarks") {
|
||||
val testBytes = makeArray(1024 * 1024)
|
||||
val testData = "{'message': 'Hello World'}"
|
||||
|
||||
/**
|
||||
* Receive json data-class.
|
||||
*/
|
||||
get("/json") {
|
||||
call.respondText(testData, ContentType.Application.Json)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send json data-class.
|
||||
*/
|
||||
post("/json") {
|
||||
val request = call.receiveText()
|
||||
check(testData == request)
|
||||
call.respond(HttpStatusCode.OK, "OK")
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit url form.
|
||||
*/
|
||||
get("/form-url") {
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit body form.
|
||||
*/
|
||||
post("/form-body") {
|
||||
}
|
||||
|
||||
/**
|
||||
* Download file.
|
||||
*/
|
||||
get("/bytes") {
|
||||
val size = call.request.queryParameters["size"]!!.toInt()
|
||||
call.respond(object : OutgoingContent.WriteChannelContent() {
|
||||
override suspend fun writeTo(channel: ByteWriteChannel) {
|
||||
channel.writeFully(testBytes, 0, size * 1024)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file
|
||||
*/
|
||||
post("/bytes") {}
|
||||
|
||||
route("/websockets") {
|
||||
webSocket("/get/{count}") {
|
||||
println("connected")
|
||||
val count = call.parameters["count"]!!.toInt()
|
||||
|
||||
repeat(count) {
|
||||
send("$it")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package io.ktor.client.tests.utils
|
||||
|
||||
import io.ktor.application.*
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.features.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.request.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.websocket.*
|
||||
|
||||
fun Application.tests() {
|
||||
install(WebSockets)
|
||||
|
||||
install(Authentication) {
|
||||
basic("test-basic") {
|
||||
realm = "my-server"
|
||||
validate { call ->
|
||||
if (call.name == "user1" && call.password == "Password1")
|
||||
UserIdPrincipal("user1")
|
||||
else null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
routing {
|
||||
post("/echo") {
|
||||
val response = call.receiveText()
|
||||
call.respond(response)
|
||||
}
|
||||
get("/bytes") {
|
||||
val size = call.request.queryParameters["size"]!!.toInt()
|
||||
call.respondBytes(makeArray(size))
|
||||
}
|
||||
route("/json") {
|
||||
get("/users") {
|
||||
call.respondText("[{'id': 42, 'login': 'TestLogin'}]", contentType = ContentType.Application.Json)
|
||||
}
|
||||
get("/photos") {
|
||||
call.respondText("[{'id': 4242, 'path': 'cat.jpg'}]", contentType = ContentType.Application.Json)
|
||||
}
|
||||
}
|
||||
route("/compression") {
|
||||
route("/deflate") {
|
||||
install(Compression) { deflate() }
|
||||
setCompressionEndpoints()
|
||||
}
|
||||
route("/gzip") {
|
||||
install(Compression) { gzip() }
|
||||
setCompressionEndpoints()
|
||||
}
|
||||
route("/identity") {
|
||||
install(Compression) { identity() }
|
||||
setCompressionEndpoints()
|
||||
}
|
||||
}
|
||||
|
||||
route("/auth") {
|
||||
route("/basic") {
|
||||
authenticate("test-basic") {
|
||||
post {
|
||||
val requestData = call.receiveText()
|
||||
if (requestData == "{\"test\":\"text\"}")
|
||||
call.respondText("OK")
|
||||
else
|
||||
call.respond(HttpStatusCode.BadRequest)
|
||||
}
|
||||
route("/ws") {
|
||||
route("/echo") {
|
||||
webSocket(protocol = "ocpp2.0,ocpp1.6") {
|
||||
for (message in incoming) {
|
||||
send(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Route.setCompressionEndpoints() {
|
||||
get {
|
||||
call.respondText("Compressed response!")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
package io.ktor.client.tests.utils
|
||||
|
||||
import ch.qos.logback.classic.*
|
||||
import io.ktor.application.*
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.features.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.request.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.server.engine.*
|
||||
import io.ktor.server.jetty.*
|
||||
import io.ktor.websocket.*
|
||||
import org.slf4j.*
|
||||
|
||||
private val DEFAULT_PORT: Int = 8080
|
||||
@@ -20,80 +12,11 @@ internal fun startServer(): ApplicationEngine {
|
||||
logger.level = Level.WARN
|
||||
|
||||
return embeddedServer(Jetty, DEFAULT_PORT) {
|
||||
install(WebSockets)
|
||||
install(Authentication) {
|
||||
basic("test-basic") {
|
||||
realm = "my-server"
|
||||
validate { call ->
|
||||
if (call.name == "user1" && call.password == "Password1")
|
||||
UserIdPrincipal("user1")
|
||||
else null
|
||||
}
|
||||
}
|
||||
}
|
||||
routing {
|
||||
post("/echo") {
|
||||
val response = call.receiveText()
|
||||
call.respond(response)
|
||||
}
|
||||
get("/bytes") {
|
||||
val size = call.request.queryParameters["size"]!!.toInt()
|
||||
call.respondBytes(makeArray(size))
|
||||
}
|
||||
route("/json") {
|
||||
get("/users") {
|
||||
call.respondText("[{'id': 42, 'login': 'TestLogin'}]", contentType = ContentType.Application.Json)
|
||||
}
|
||||
get("/photos") {
|
||||
call.respondText("[{'id': 4242, 'path': 'cat.jpg'}]", contentType = ContentType.Application.Json)
|
||||
}
|
||||
}
|
||||
route("/compression") {
|
||||
route("/deflate") {
|
||||
install(Compression) { deflate() }
|
||||
setCompressionEndpoints()
|
||||
}
|
||||
route("/gzip") {
|
||||
install(Compression) { gzip() }
|
||||
setCompressionEndpoints()
|
||||
}
|
||||
route("/identity") {
|
||||
install(Compression) { identity() }
|
||||
setCompressionEndpoints()
|
||||
}
|
||||
}
|
||||
route("/auth") {
|
||||
route("/basic") {
|
||||
authenticate("test-basic") {
|
||||
post {
|
||||
val requestData = call.receiveText()
|
||||
if (requestData == "{\"test\":\"text\"}")
|
||||
call.respondText("OK")
|
||||
else
|
||||
call.respond(HttpStatusCode.BadRequest)
|
||||
}
|
||||
route("/ws") {
|
||||
route("/echo") {
|
||||
webSocket(protocol = "ocpp2.0,ocpp1.6") {
|
||||
for (message in incoming) {
|
||||
send(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tests()
|
||||
benchmarks()
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun Route.setCompressionEndpoints() {
|
||||
get {
|
||||
call.respondText("Compressed response!")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start server for tests.
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
gradlePluginPortal()
|
||||
maven { url "https://dl.bintray.com/orangy/maven" }
|
||||
}
|
||||
resolutionStrategy {
|
||||
eachPlugin {
|
||||
if (requested.id.id == "kotlin2js") {
|
||||
@@ -41,6 +47,7 @@ includeEx ':ktor-client:ktor-client-jetty'
|
||||
includeEx ':ktor-client:ktor-client-js'
|
||||
includeEx ':ktor-client:ktor-client-mock'
|
||||
includeEx ':ktor-client:ktor-client-okhttp'
|
||||
includeEx ':ktor-client:ktor-client-benchmarks'
|
||||
includeEx ':ktor-client:ktor-client-features'
|
||||
includeEx ':ktor-client:ktor-client-features:ktor-client-json'
|
||||
includeEx ':ktor-client:ktor-client-features:ktor-client-json:ktor-client-json-tests'
|
||||
|
||||
Reference in New Issue
Block a user