diff --git a/Makefile b/Makefile index 739e3be0ad..635db1e3c2 100644 --- a/Makefile +++ b/Makefile @@ -96,8 +96,9 @@ RUST_SOURCES := $(WORKSPACE_CARGO_FILES) $(CRATE_MANIFESTS) $(RUST_RS_FILES) KT_WRAPPER = ./crypto-ffi/bindings/shared/src/commonMain/kotlin KT_TESTS = ./crypto-ffi/bindings/shared/src/commonTest +KT_BENCHMARKS = ./crypto-ffi/bindings/jvm/src/jmh/ KT_INTEROP = ./interop/src/clients/android-interop/src/main/java -KT_FILES := $(shell find $(KT_WRAPPER) $(KT_TESTS) $(KT_INTEROP) -type f -name '*.kt') +KT_FILES := $(shell find $(KT_WRAPPER) $(KT_TESTS) $(KT_INTEROP) $(KT_BENCHMARKS) -type f -name '*.kt') KT_GRADLE_FILES = $(shell find . -type f -name '*.kts') #------------------------------------------------------------------------------- @@ -446,6 +447,11 @@ $(STAMPS)/jvm-test: $(jvm-test-deps) ./gradlew jvm:test --rerun $(TOUCH_STAMP) +$(STAMPS)/jvm-bench: $(jvm-test-deps) $(KT_BENCHMARKS) + cd crypto-ffi/bindings && \ + ./gradlew :jvm:jmh + $(TOUCH_STAMP) + #------------------------------------------------------------------------------- # KMP (Kotlin Multiplatform) builds #------------------------------------------------------------------------------- @@ -728,7 +734,7 @@ swift-check: $(STAMPS)/swift-check ## Lint Swift files via swift-format and swif # Kotlin $(STAMPS)/kotlin-fmt: $(KT_FILES) $(KT_GRADLE_FILES) - ktlint --format $(KT_WRAPPER) $(KT_TESTS) $(KT_INTEROP) $(KT_GRADLE_FILES) + ktlint --format $(KT_FILES) $(KT_GRADLE_FILES) $(TOUCH_STAMP) .PHONY: kotlin-fmt @@ -769,11 +775,12 @@ check: rust-check swift-check kotlin-check ts-check ## Run all linters # Lazy targets #------------------------------------------------------------------------------- -LAZY_TARGETS := jvm-test kmp-jvm-test ts-test android-test ios-test interop-test +LAZY_TARGETS := jvm-test kmp-jvm-test ts-test android-test ios-test interop-test jvm-bench ts-test: ## Run TypeScript wrapper tests via wdio and bun. Optionally pass TEST= to filter by test name. kmp-jvm-test: ## Run Kotlin multi-platform tests on JVM jvm-test: ## Run Kotlin tests on JVM +jvm-bench: ## Run the JVM benchmarks android-test: ## Run Kotlin tests on Android ios-test: ## Run Swift tests on iOS (macOS only) interop-test: ## Run e2e interop test diff --git a/crypto-ffi/bindings/jvm/build.gradle.kts b/crypto-ffi/bindings/jvm/build.gradle.kts index 767af75c13..f08510dd2a 100644 --- a/crypto-ffi/bindings/jvm/build.gradle.kts +++ b/crypto-ffi/bindings/jvm/build.gradle.kts @@ -4,6 +4,7 @@ plugins { id("maven-publish") id("signing") id(libs.plugins.detekt.get().pluginId) version libs.versions.detekt + id("me.champeau.jmh") version "0.7.3" } java { @@ -77,6 +78,12 @@ sourceSets { } } +jmh { + fork.set(1) + resultFormat.set("CSV") + resultsFile.set(layout.buildDirectory.file("reports/jmh/results.csv").get().asFile) +} + // Allows skipping signing jars published to 'MavenLocal' repository project.afterEvaluate { tasks.named("signMavenPublication").configure { diff --git a/crypto-ffi/bindings/jvm/src/jmh/kotlin/com/wire/benchmark/CreateMessage.kt b/crypto-ffi/bindings/jvm/src/jmh/kotlin/com/wire/benchmark/CreateMessage.kt new file mode 100644 index 0000000000..99e70581b9 --- /dev/null +++ b/crypto-ffi/bindings/jvm/src/jmh/kotlin/com/wire/benchmark/CreateMessage.kt @@ -0,0 +1,61 @@ +@file:Suppress("ktlint:standard:no-wildcard-imports") + +package com.wire.benchmark +import com.wire.crypto.* +import kotlinx.coroutines.runBlocking +import org.openjdk.jmh.annotations.* +import testutils.* +import java.nio.file.Files +import java.util.concurrent.TimeUnit + +// This benchmark measures throughput of encrypting messages in a transaction and committing these. +// It includes the database interaction, because want to be able to answer to a user how long creating messages takes. +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) +@State(Scope.Benchmark) +open class CreateMessage { + @Param( + "MLS_128_DHKEMX25519_AES128GCM_SHA256_ED25519", + "MLS_128_DHKEMP256_AES128GCM_SHA256_P256", + "MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_ED25519", + "MLS_256_DHKEMP521_AES256GCM_SHA512_P521", + "MLS_256_DHKEMP384_AES256GCM_SHA384_P384" + ) + var cipherSuite: String = "" + + @Param("1", "10", "100") + var messageCount: Int = 0 + + @Param("16", "1024", "65536") + var messageSize: Int = 0 + + private lateinit var messages: List + private lateinit var conversationId: ConversationId + private lateinit var cc: CoreCrypto + + @Setup(Level.Iteration) + fun setup() = runBlocking { + val aliceId = genClientId() + conversationId = genConversationId() + cc = initCc() + cc.transaction { + it.mlsInit(aliceId, MockMlsTransportSuccessProvider()) + val credentialRef = it.addCredential(Credential.basic(Ciphersuite.valueOf(cipherSuite), aliceId)) + it.createConversation(conversationId, credentialRef, null) + } + messages = List(messageCount) { + ByteArray(messageSize) { 'A'.code.toByte() } + } + } + + @Benchmark + fun createMessages() = runBlocking { + cc.transaction { + for (msg in messages) { + it.encryptMessage(conversationId, msg) + } + } + } +} diff --git a/crypto-ffi/bindings/shared/src/commonTest/kotlin/com/wire/crypto/MLSTest.kt b/crypto-ffi/bindings/shared/src/commonTest/kotlin/com/wire/crypto/MLSTest.kt index 551b553201..d1373220fd 100644 --- a/crypto-ffi/bindings/shared/src/commonTest/kotlin/com/wire/crypto/MLSTest.kt +++ b/crypto-ffi/bindings/shared/src/commonTest/kotlin/com/wire/crypto/MLSTest.kt @@ -25,7 +25,7 @@ class MLSTest : HasMockDeliveryService() { @Test fun set_client_data_persists() = runTest { - val cc = initCc(this@MLSTest) + val cc = initCc() val data = "my message processing checkpoint".toByteArray() @@ -39,7 +39,7 @@ class MLSTest : HasMockDeliveryService() { @Test fun interaction_with_invalid_context_throws_error() = runTest { - val cc = initCc(this@MLSTest) + val cc = initCc() var context: CoreCryptoContext? = null cc.transaction { ctx -> context = ctx } @@ -54,7 +54,7 @@ class MLSTest : HasMockDeliveryService() { @Test fun error_is_propagated_by_transaction() = runTest { - val cc = initCc(this@MLSTest) + val cc = initCc() val expectedException = RuntimeException("Expected Exception") val actualException = @@ -303,7 +303,7 @@ class MLSTest : HasMockDeliveryService() { @Test fun givenTransactionRunsSuccessfully_thenShouldBeAbleToFinishOtherTransactions() = runTest { - val coreCrypto = initCc(this@MLSTest) + val coreCrypto = initCc() val someWork = Job() val firstTransactionJob = launch { coreCrypto.transaction { @@ -324,7 +324,7 @@ class MLSTest : HasMockDeliveryService() { @Test fun givenTransactionIsCancelled_thenShouldBeAbleToFinishOtherTransactions() = runTest { - val coreCrypto = initCc(this@MLSTest) + val coreCrypto = initCc() val firstTransactionJob = launch { coreCrypto.transaction { @@ -529,7 +529,7 @@ class MLSTest : HasMockDeliveryService() { val clientId = genClientId() val credential = Credential.basic(CIPHERSUITE_DEFAULT, clientId) - val cc = initCc(this@MLSTest) + val cc = initCc() val ref = cc.transaction { ctx -> ctx.mlsInitShort(clientId) ctx.addCredential(credential) @@ -550,7 +550,7 @@ class MLSTest : HasMockDeliveryService() { val clientId = genClientId() val credential = Credential.basic(CIPHERSUITE_DEFAULT, clientId) - val cc = initCc(this@MLSTest) + val cc = initCc() val ref = cc.transaction { ctx -> ctx.mlsInitShort(clientId) ctx.addCredential(credential) @@ -577,7 +577,7 @@ class MLSTest : HasMockDeliveryService() { Ciphersuite.MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_ED25519 val credential2 = Credential.basic(ciphersuite2, clientId) - val cc = initCc(this@MLSTest) + val cc = initCc() cc.transaction { ctx -> ctx.mlsInitShort(clientId) ctx.addCredential(credential1) @@ -616,7 +616,7 @@ class MLSTest : HasMockDeliveryService() { val clientId = genClientId() val credential = Credential.basic(CIPHERSUITE_DEFAULT, clientId) - val cc = initCc(this@MLSTest) + val cc = initCc() val credentialRef = cc.transaction { ctx -> ctx.mlsInitShort(clientId) ctx.addCredential(credential) @@ -637,7 +637,7 @@ class MLSTest : HasMockDeliveryService() { val clientId = genClientId() val credential = Credential.basic(CIPHERSUITE_DEFAULT, clientId) - val cc = initCc(this@MLSTest) + val cc = initCc() val credentialRef = cc.transaction { ctx -> ctx.mlsInitShort(clientId) ctx.addCredential(credential) @@ -666,7 +666,7 @@ class MLSTest : HasMockDeliveryService() { val clientId = genClientId() val credential = Credential.basic(CIPHERSUITE_DEFAULT, clientId) - val cc = initCc(this@MLSTest) + val cc = initCc() val credentialRef = cc.transaction { ctx -> ctx.mlsInitShort(clientId) ctx.addCredential(credential) @@ -693,7 +693,7 @@ class MLSTest : HasMockDeliveryService() { val clientId = genClientId() val credential = Credential.basic(CIPHERSUITE_DEFAULT, clientId) - val cc = initCc(this@MLSTest) + val cc = initCc() val credentialRef = cc.transaction { ctx -> ctx.mlsInitShort(clientId) ctx.addCredential(credential) @@ -737,7 +737,7 @@ class MLSTest : HasMockDeliveryService() { clientId ) - val cc = initCc(this@MLSTest) + val cc = initCc() cc.transaction { ctx -> ctx.mlsInitShort(clientId) diff --git a/crypto-ffi/bindings/shared/src/commonTest/kotlin/com/wire/crypto/testutils/TestUtils.kt b/crypto-ffi/bindings/shared/src/commonTest/kotlin/com/wire/crypto/testutils/TestUtils.kt index f826efb617..ac093a3435 100644 --- a/crypto-ffi/bindings/shared/src/commonTest/kotlin/com/wire/crypto/testutils/TestUtils.kt +++ b/crypto-ffi/bindings/shared/src/commonTest/kotlin/com/wire/crypto/testutils/TestUtils.kt @@ -106,7 +106,7 @@ abstract class HasMockDeliveryService { fun newClients(instance: HasMockDeliveryService, vararg clientIds: ClientId) = runBlocking { clientIds.map { clientID -> - val cc = initCc(instance) + val cc = initCc() cc.transaction { ctx -> ctx.mlsInitShort(clientID) ctx.addCredential(Credential.basic(CIPHERSUITE_DEFAULT, clientID)) @@ -115,7 +115,7 @@ fun newClients(instance: HasMockDeliveryService, vararg clientIds: ClientId) = r } } -fun initCc(_instance: HasMockDeliveryService): CoreCrypto = runBlocking { +fun initCc(): CoreCrypto = runBlocking { val root = Files.createTempDirectory("mls").toFile() val path = root.resolve("keystore-${randomIdentifier()}") val key = genDatabaseKey()