libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 38ce7fa7d360786da460493fb1887daf19199976
parent 29b412cea0846c416eed2c83b8236162b7fbeb61
Author: Antoine A <>
Date:   Tue,  4 Mar 2025 12:42:00 +0100

common: fix bench db

Diffstat:
Mbank/src/test/kotlin/bench.kt | 431+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mcommon/src/main/kotlin/test/bench.kt | 11++++++++---
Mnexus/src/test/kotlin/bench.kt | 44+++++++++++++++++++++++++++-----------------
3 files changed, 245 insertions(+), 241 deletions(-)

diff --git a/bank/src/test/kotlin/bench.kt b/bank/src/test/kotlin/bench.kt @@ -115,237 +115,226 @@ class Bench { } @Test - fun benchDb() { - val ITER = System.getenv("BENCH_ITER")?.toIntOrNull() ?: 1 - val AMOUNT = System.getenv("BENCH_AMOUNT")?.toIntOrNull() ?: 10 + fun benchDb() = bench { AMOUNT -> bankSetup { db -> + // Prepare custoemr accounts + fillCashoutInfo("customer") + setMaxDebt("customer", "KUDOS:1000000") - if (ITER == 0) { - println("Skip benchmark, missing BENCH_ITER") - return + // Generate data + db.conn { genData(it, AMOUNT) } + + // Warm HTTP client + client.get("/config").assertOk() + + // Accounts + measureAction("account_create") { + client.post("/accounts") { + json { + "username" to "account_bench_$it" + "password" to "account_bench_$it-password" + "name" to "Bench Account $it" + } + }.assertOkJson<RegisterAccountResponse>().internal_payto_uri + } + measureAction("account_reconfig") { + client.patchA("/accounts/account_bench_$it") { + json { + "name" to "New Bench Account $it" + } + }.assertNoContent() + } + measureAction("account_reconfig_auth") { + client.patchA("/accounts/account_bench_$it/auth") { + json { + "old_password" to "account_bench_$it-password" + "new_password" to "account_bench_$it-password" + } + }.assertNoContent() + } + measureAction("account_list") { + client.getAdmin("/accounts").assertOk() + } + measureAction("account_list_public") { + client.get("/public-accounts").assertOk() + } + measureAction("account_get") { + client.getA("/accounts/account_bench_$it").assertOk() + } + + // Tokens + val tokens = measureAction("token_create") { + client.postPw("/accounts/customer/token") { + json { + "scope" to "readonly" + "refreshable" to true + } + }.assertOkJson<TokenSuccessResponse>().access_token + } + measureAction("token_refresh") { + client.post("/accounts/customer/token") { + headers[HttpHeaders.Authorization] = "Bearer ${tokens[it]}" + json { "scope" to "readonly" } + }.assertOk() + } + measureAction("token_list") { + client.getA("/accounts/customer/tokens").assertOk() + } + measureAction("token_delete") { + client.delete("/accounts/customer/token") { + headers[HttpHeaders.Authorization] = "Bearer ${tokens[it]}" + }.assertNoContent() } - println("Bench $ITER times with $AMOUNT rows") - bench(ITER) { bankSetup { db -> - // Prepare custoemr accounts - fillCashoutInfo("customer") - setMaxDebt("customer", "KUDOS:1000000") + // Transaction + val transactions = measureAction("transaction_create") { + client.postA("/accounts/customer/transactions") { + json { + "payto_uri" to "$merchantPayto?receiver-name=Test&message=payout" + "amount" to "KUDOS:0.0001" + } + }.assertOkJson<TransactionCreateResponse>().row_id + } + measureAction("transaction_get") { + client.getA("/accounts/customer/transactions/${transactions[it]}").assertOk() + } + measureAction("transaction_history") { + client.getA("/accounts/customer/transactions").assertOk() + } + measureAction("transaction_revenue") { + client.getA("/accounts/merchant/taler-revenue/history").assertOk() + } - // Generate data - db.conn { genData(it, AMOUNT) } + // Withdrawal + val withdrawals = measureAction("withdrawal_create") { + client.postA("/accounts/customer/withdrawals") { + json { + "amount" to "KUDOS:0.0001" + } + }.assertOkJson<BankAccountCreateWithdrawalResponse>().withdrawal_id + } + measureAction("withdrawal_get") { + client.get("/withdrawals/${withdrawals[it]}").assertOk() + } + measureAction("withdrawal_status") { + client.get("/taler-integration/withdrawal-operation/${withdrawals[it]}").assertOk() + } + measureAction("withdrawal_select") { + client.post("/taler-integration/withdrawal-operation/${withdrawals[it]}") { + json { + "reserve_pub" to EddsaPublicKey.rand() + "selected_exchange" to exchangePayto + } + }.assertOk() + } + measureAction("withdrawal_confirm") { + client.postA("/accounts/customer/withdrawals/${withdrawals[it]}/confirm") + .assertNoContent() + } + measureAction("withdrawal_abort") { + val uuid = client.postA("/accounts/customer/withdrawals") { + json { + "amount" to "KUDOS:0.0001" + } + }.assertOkJson<BankAccountCreateWithdrawalResponse>().withdrawal_id + client.postA("/accounts/customer/withdrawals/$uuid/abort") + .assertNoContent() + } - // Warm HTTP client - client.get("/config").assertOk() - - // Accounts - measureAction("account_create") { - client.post("/accounts") { - json { - "username" to "account_bench_$it" - "password" to "account_bench_$it-password" - "name" to "Bench Account $it" - } - }.assertOkJson<RegisterAccountResponse>().internal_payto_uri - } - measureAction("account_reconfig") { - client.patchA("/accounts/account_bench_$it") { - json { - "name" to "New Bench Account $it" - } - }.assertNoContent() - } - measureAction("account_reconfig_auth") { - client.patchA("/accounts/account_bench_$it/auth") { - json { - "old_password" to "account_bench_$it-password" - "new_password" to "account_bench_$it-password" - } - }.assertNoContent() - } - measureAction("account_list") { - client.getAdmin("/accounts").assertOk() - } - measureAction("account_list_public") { - client.get("/public-accounts").assertOk() - } - measureAction("account_get") { - client.getA("/accounts/account_bench_$it").assertOk() - } - - // Tokens - val tokens = measureAction("token_create") { - client.postPw("/accounts/customer/token") { - json { - "scope" to "readonly" - "refreshable" to true - } - }.assertOkJson<TokenSuccessResponse>().access_token - } - measureAction("token_refresh") { - client.post("/accounts/customer/token") { - headers[HttpHeaders.Authorization] = "Bearer ${tokens[it]}" - json { "scope" to "readonly" } - }.assertOk() - } - measureAction("token_list") { - client.getA("/accounts/customer/tokens").assertOk() - } - measureAction("token_delete") { - client.delete("/accounts/customer/token") { - headers[HttpHeaders.Authorization] = "Bearer ${tokens[it]}" - }.assertNoContent() - } - - // Transaction - val transactions = measureAction("transaction_create") { - client.postA("/accounts/customer/transactions") { - json { - "payto_uri" to "$merchantPayto?receiver-name=Test&message=payout" - "amount" to "KUDOS:0.0001" - } - }.assertOkJson<TransactionCreateResponse>().row_id - } - measureAction("transaction_get") { - client.getA("/accounts/customer/transactions/${transactions[it]}").assertOk() - } - measureAction("transaction_history") { - client.getA("/accounts/customer/transactions").assertOk() - } - measureAction("transaction_revenue") { - client.getA("/accounts/merchant/taler-revenue/history").assertOk() - } - - // Withdrawal - val withdrawals = measureAction("withdrawal_create") { - client.postA("/accounts/customer/withdrawals") { - json { - "amount" to "KUDOS:0.0001" - } - }.assertOkJson<BankAccountCreateWithdrawalResponse>().withdrawal_id - } - measureAction("withdrawal_get") { - client.get("/withdrawals/${withdrawals[it]}").assertOk() - } - measureAction("withdrawal_status") { - client.get("/taler-integration/withdrawal-operation/${withdrawals[it]}").assertOk() - } - measureAction("withdrawal_select") { - client.post("/taler-integration/withdrawal-operation/${withdrawals[it]}") { - json { - "reserve_pub" to EddsaPublicKey.rand() - "selected_exchange" to exchangePayto - } - }.assertOk() - } - measureAction("withdrawal_confirm") { - client.postA("/accounts/customer/withdrawals/${withdrawals[it]}/confirm") - .assertNoContent() - } - measureAction("withdrawal_abort") { - val uuid = client.postA("/accounts/customer/withdrawals") { - json { - "amount" to "KUDOS:0.0001" - } - }.assertOkJson<BankAccountCreateWithdrawalResponse>().withdrawal_id - client.postA("/accounts/customer/withdrawals/$uuid/abort") - .assertNoContent() - } - - // Cashout - convert("KUDOS:0.1") - val cashouts = measureAction("cashout_create") { - client.postA("/accounts/customer/cashouts") { - json { - "request_uid" to ShortHashCode.rand() - "amount_debit" to "KUDOS:0.1" - "amount_credit" to convert("KUDOS:0.1") - } - }.assertOkJson<CashoutResponse>().cashout_id - } - measureAction("cashout_get") { - client.getA("/accounts/customer/cashouts/${cashouts[it]}").assertOk() - } - measureAction("cashout_history") { - client.getA("/accounts/customer/cashouts").assertOk() - } - measureAction("cashout_history_admin") { - client.getAdmin("/cashouts").assertOk() - } + // Cashout + convert("KUDOS:0.1") + val cashouts = measureAction("cashout_create") { + client.postA("/accounts/customer/cashouts") { + json { + "request_uid" to ShortHashCode.rand() + "amount_debit" to "KUDOS:0.1" + "amount_credit" to convert("KUDOS:0.1") + } + }.assertOkJson<CashoutResponse>().cashout_id + } + measureAction("cashout_get") { + client.getA("/accounts/customer/cashouts/${cashouts[it]}").assertOk() + } + measureAction("cashout_history") { + client.getA("/accounts/customer/cashouts").assertOk() + } + measureAction("cashout_history_admin") { + client.getAdmin("/cashouts").assertOk() + } - // Wire gateway - val transfers = measureAction("wg_transfer") { - client.postA("/accounts/exchange/taler-wire-gateway/transfer") { - json { - "request_uid" to HashCode.rand() - "amount" to "KUDOS:0.0001" - "exchange_base_url" to "http://exchange.example.com/" - "wtid" to ShortHashCode.rand() - "credit_account" to customerPayto.canonical - } - }.assertOkJson<TransferResponse>().row_id - } - measureAction("wg_transfer_get") { - client.getA("/accounts/exchange/taler-wire-gateway/transfers/${transfers[it]}").assertOk() - } - measureAction("wg_transfer_page") { - client.getA("/accounts/exchange/taler-wire-gateway/transfers").assertOk() - } - measureAction("wg_transfer_page_filter") { - client.getA("/accounts/exchange/taler-wire-gateway/transfers?status=success").assertOk() - } - measureAction("wg_add") { - client.postA("/accounts/exchange/taler-wire-gateway/admin/add-incoming") { - json { - "amount" to "KUDOS:0.0001" - "reserve_pub" to EddsaPublicKey.rand() - "debit_account" to customerPayto.canonical - } - }.assertOk() - } - measureAction("wg_incoming") { - client.getA("/accounts/exchange/taler-wire-gateway/history/incoming") - .assertOk() - } - measureAction("wg_outgoing") { - client.getA("/accounts/exchange/taler-wire-gateway/history/outgoing") - .assertOk() - } + // Wire gateway + val transfers = measureAction("wg_transfer") { + client.postA("/accounts/exchange/taler-wire-gateway/transfer") { + json { + "request_uid" to HashCode.rand() + "amount" to "KUDOS:0.0001" + "exchange_base_url" to "http://exchange.example.com/" + "wtid" to ShortHashCode.rand() + "credit_account" to customerPayto.canonical + } + }.assertOkJson<TransferResponse>().row_id + } + measureAction("wg_transfer_get") { + client.getA("/accounts/exchange/taler-wire-gateway/transfers/${transfers[it]}").assertOk() + } + measureAction("wg_transfer_page") { + client.getA("/accounts/exchange/taler-wire-gateway/transfers").assertOk() + } + measureAction("wg_transfer_page_filter") { + client.getA("/accounts/exchange/taler-wire-gateway/transfers?status=success").assertOk() + } + measureAction("wg_add") { + client.postA("/accounts/exchange/taler-wire-gateway/admin/add-incoming") { + json { + "amount" to "KUDOS:0.0001" + "reserve_pub" to EddsaPublicKey.rand() + "debit_account" to customerPayto.canonical + } + }.assertOk() + } + measureAction("wg_incoming") { + client.getA("/accounts/exchange/taler-wire-gateway/history/incoming") + .assertOk() + } + measureAction("wg_outgoing") { + client.getA("/accounts/exchange/taler-wire-gateway/history/outgoing") + .assertOk() + } - // TAN challenges - val challenges = measureAction("tan_send") { - val id = client.patchA("/accounts/account_bench_$it") { - json { - "contact_data" to obj { - "phone" to "+99" - "email" to "email@example.com" - } - "tan_channel" to "sms" + // TAN challenges + val challenges = measureAction("tan_send") { + val id = client.patchA("/accounts/account_bench_$it") { + json { + "contact_data" to obj { + "phone" to "+99" + "email" to "email@example.com" } - }.assertAcceptedJson<TanChallenge>().challenge_id - val res = client.postA("/accounts/account_bench_$it/challenge/$id").assertOkJson<TanTransmission>() - val code = tanCode(res.tan_info) - Pair(id, code) - } - measureAction("tan_confirm") { - val (id, code) = challenges[it] - client.postA("/accounts/account_bench_$it/challenge/$id/confirm") { - json { "tan" to code } - }.assertNoContent() - } + "tan_channel" to "sms" + } + }.assertAcceptedJson<TanChallenge>().challenge_id + val res = client.postA("/accounts/account_bench_$it/challenge/$id").assertOkJson<TanTransmission>() + val code = tanCode(res.tan_info) + Pair(id, code) + } + measureAction("tan_confirm") { + val (id, code) = challenges[it] + client.postA("/accounts/account_bench_$it/challenge/$id/confirm") { + json { "tan" to code } + }.assertNoContent() + } - // Delete accounts + // Delete accounts - measureAction("account_delete") { - client.deleteA("/accounts/account_bench_$it").assertNoContent() - } + measureAction("account_delete") { + client.deleteA("/accounts/account_bench_$it").assertNoContent() + } - // Other - measureAction("monitor") { - client.getAdmin("/monitor").assertOk() - } + // Other + measureAction("monitor") { + client.getAdmin("/monitor").assertOk() + } + db.gc.collect(Instant.now(), java.time.Duration.ZERO, java.time.Duration.ZERO, java.time.Duration.ZERO) + measureAction("gc") { db.gc.collect(Instant.now(), java.time.Duration.ZERO, java.time.Duration.ZERO, java.time.Duration.ZERO) - measureAction("gc") { - db.gc.collect(Instant.now(), java.time.Duration.ZERO, java.time.Duration.ZERO, java.time.Duration.ZERO) - } - } } - } + } + } } } \ No newline at end of file diff --git a/common/src/main/kotlin/test/bench.kt b/common/src/main/kotlin/test/bench.kt @@ -83,9 +83,14 @@ class Benchmark(private val iter: Int) { } } -fun bench(iter: Int, lambda: Benchmark.() -> Unit) { - val bench = Benchmark(iter) - lambda(bench) +fun bench(lambda: Benchmark.(Int) -> Unit) { + val ITER = System.getenv("BENCH_ITER")?.toIntOrNull() ?: 10 + val AMOUNT = System.getenv("BENCH_AMOUNT")?.toIntOrNull() ?: 100 + + println("Bench $ITER times with $AMOUNT rows") + + val bench = Benchmark(ITER) + bench.lambda(AMOUNT) printTable( listOf("benchmark", "min", "mean", "max", "std").map { ANSI.bold(it) }, bench.measures, diff --git a/nexus/src/test/kotlin/bench.kt b/nexus/src/test/kotlin/bench.kt @@ -24,6 +24,7 @@ import tech.libeufin.common.test.* import tech.libeufin.nexus.* import tech.libeufin.nexus.cli.* import kotlin.math.max +import java.util.UUID; class Bench { @@ -34,14 +35,32 @@ class Bench { val token64 = ByteArray(64) conn.genData(amount, sequenceOf( - "incoming_transactions(amount, subject, execution_time, debit_payto, bank_id)" to { - "(42,0)\tsubject\t0\tdebit_payto\tBANK_ID${it*2}\n" + - "(42,0)\tsubject\t0\tdebit_payto\tBANK_ID${it*2+1}\n" + - "(42,0)\tsubject\t0\tdebit_payto\t\\N\n" + "incoming_transactions(amount, subject, execution_time, debit_payto, uetr, tx_id, acct_svcr_ref)" to { + val subject = if (it % 4 == 0) null else "subject ${it}" + val debtor = if (it % 3 == 0) null else "debit_payto" + if (it % 3 == 0) { + "(20,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\t\\N\t\\N\n" + + "(21,0)\t$subject\t0\t$debtor\t\\N\tTX_ID${it*2}\t\\N\n" + + "(22,0)\t$subject\t0\t$debtor\t\\N\t\\N\tREF${it*2}\n" + } else if (it % 3 == 1) { + "(30,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\tTX_ID${it*2}\t\\N\n" + + "(31,0)\t$subject\t0\t$debtor\t\\N\tTX_ID${it*2+1}\tREF${it*2}\n" + + "(32,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\t\\N\tREF${it*2+1}\n" + } else { + "(40,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\tTX_ID${it*2}\tREF${it*2}\n" + + "(41,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\tTX_ID${it*2+1}\tREF${it*2+1}\n" + } }, - "outgoing_transactions(amount, subject, execution_time, credit_payto, end_to_end_id)" to { - "(42,0)\tsubject\t0\tcredit_payto\tE2E_ID${it*2}\n" + - "(42,0)\tsubject\t0\tcredit_payto\tE2E_ID${it*2+1}\n" + "outgoing_transactions(amount, subject, execution_time, credit_payto, end_to_end_id, acct_svcr_ref)" to { + val subject = if (it % 4 == 0) null else "subject ${it}" + val creditor = if (it % 3 == 0) null else "credit_payto" + if (it % 2 == 0) { + "(30,0)\t$subject\t0\t$creditor\t\\N\tREF${it*2}\n" + + "(31,0)\t$subject\t0\t$creditor\tE2E_ID${it*2}\t\\N\n" + } else { + "(30,0)\t$subject\t0\t$creditor\tE2E_ID${it*2}\tREF${it*2}\n" + + "(31,0)\t$subject\t0\t$creditor\tE2E_ID${it*2+1}\tREF${it*2+1}\n" + } }, "initiated_outgoing_transactions(amount, subject, initiation_time, credit_payto, outgoing_transaction_id, end_to_end_id)" to { "(42,0)\tsubject\t0\tcredit_payto\t${it*2}\tE2E_ID$it\n" @@ -68,18 +87,9 @@ class Bench { @Test fun benchDb() { - val ITER = System.getenv("BENCH_ITER")?.toIntOrNull() ?: 0 - val AMOUNT = System.getenv("BENCH_AMOUNT")?.toIntOrNull() ?: 0 - - if (ITER == 0) { - println("Skip benchmark, missing BENCH_ITER") - return - } - println("Bench $ITER times with $AMOUNT rows") - val ingestCfg = NexusIngestConfig.default(AccountType.exchange) - bench(ITER) { serverSetup { db -> + bench { AMOUNT -> serverSetup { db -> // Generate data db.conn { genData(it, AMOUNT) }