bench.kt (7477B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2024-2025 Taler Systems S.A. 4 5 * LibEuFin is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation; either version 3, or 8 * (at your option) any later version. 9 10 * LibEuFin is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 13 * Public License for more details. 14 15 * You should have received a copy of the GNU Affero General Public 16 * License along with LibEuFin; see the file COPYING. If not, see 17 * <http://www.gnu.org/licenses/> 18 */ 19 20 import org.junit.Test 21 import org.postgresql.jdbc.PgConnection 22 import io.ktor.client.request.* 23 import tech.libeufin.common.* 24 import tech.libeufin.common.test.* 25 import tech.libeufin.nexus.* 26 import tech.libeufin.nexus.cli.* 27 import kotlin.math.max 28 import java.util.UUID; 29 30 class Bench { 31 32 /** Generate [amount] rows to fill the database */ 33 fun genData(conn: PgConnection, amount: Int) { 34 val amount = max(amount, 10) 35 val token32 = ByteArray(32) 36 val token64 = ByteArray(64) 37 38 conn.genData(amount, sequenceOf( 39 "incoming_transactions(amount, subject, execution_time, debit_payto, uetr, tx_id, acct_svcr_ref)" to { 40 val subject = if (it % 4 == 0) null else "subject ${it}" 41 val debtor = if (it % 3 == 0) null else "debit_payto" 42 if (it % 3 == 0) { 43 "(20,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\t\\N\t\\N\n" + 44 "(21,0)\t$subject\t0\t$debtor\t\\N\tTX_ID${it*2}\t\\N\n" + 45 "(22,0)\t$subject\t0\t$debtor\t\\N\t\\N\tREF${it*2}\n" 46 } else if (it % 3 == 1) { 47 "(30,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\tTX_ID${it*2}\t\\N\n" + 48 "(31,0)\t$subject\t0\t$debtor\t\\N\tTX_ID${it*2+1}\tREF${it*2}\n" + 49 "(32,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\t\\N\tREF${it*2+1}\n" 50 } else { 51 "(40,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\tTX_ID${it*2}\tREF${it*2}\n" + 52 "(41,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\tTX_ID${it*2+1}\tREF${it*2+1}\n" 53 } 54 }, 55 "outgoing_transactions(amount, subject, execution_time, credit_payto, end_to_end_id, acct_svcr_ref)" to { 56 val subject = if (it % 4 == 0) null else "subject ${it}" 57 val creditor = if (it % 3 == 0) null else "credit_payto" 58 if (it % 2 == 0) { 59 "(30,0)\t$subject\t0\t$creditor\t\\N\tREF${it*2}\n" + 60 "(31,0)\t$subject\t0\t$creditor\tE2E_ID${it*2}\t\\N\n" 61 } else { 62 "(30,0)\t$subject\t0\t$creditor\tE2E_ID${it*2}\tREF${it*2}\n" + 63 "(31,0)\t$subject\t0\t$creditor\tE2E_ID${it*2+1}\tREF${it*2+1}\n" 64 } 65 }, 66 "initiated_outgoing_transactions(amount, subject, initiation_time, credit_payto, outgoing_transaction_id, end_to_end_id)" to { 67 "(42,0)\tsubject\t0\tcredit_payto\t${it*2}\tE2E_ID$it\n" 68 }, 69 "bounced_transactions(incoming_transaction_id, initiated_outgoing_transaction_id)" to { 70 if (it % 10 == 0) { 71 "${it/2}\t${it/3}\n" 72 } else { 73 "" 74 } 75 }, 76 "talerable_incoming_transactions(type, metadata, incoming_transaction_id)" to { 77 val hex = token32.rand().encodeHex() 78 if (it % 2 == 0) { 79 "reserve\t\\\\x$hex\t${it*2}\n" 80 } else { 81 "kyc\t\\\\x$hex\t${it*2}\n" 82 } 83 }, 84 "talerable_outgoing_transactions(wtid, exchange_base_url, outgoing_transaction_id)" to { 85 val hex = token32.rand().encodeHex() 86 "\\\\x$hex\turl\t${it*2-1}\n" 87 }, 88 "transfer_operations(initiated_outgoing_transaction_id, request_uid, wtid, exchange_base_url)" to { 89 val hex32 = token32.rand().encodeHex() 90 val hex64 = token64.rand().encodeHex() 91 "$it\t\\\\x$hex64\t\\\\x$hex32\turl\n" 92 } 93 )) 94 } 95 96 @Test 97 fun benchDb() { 98 val ingestCfg = NexusIngestConfig.default(AccountType.exchange) 99 100 bench { AMOUNT -> serverSetup { db -> 101 // Generate data 102 db.conn { genData(it, AMOUNT) } 103 104 // Warm HTTP client 105 client.getA("/taler-revenue/config").assertOk() 106 107 // Register 108 measureAction("register_in") { 109 registerIn(db) 110 } 111 measureAction("register_incomplete_in") { 112 registerIncompleteIn(db) 113 } 114 measureAction("register_completed_in") { 115 registerCompletedIn(db) 116 } 117 measureAction("register_out") { 118 registerOut(db) 119 } 120 measureAction("register_incomplete_out") { 121 registerIncompleteOut(db) 122 } 123 measureAction("register_reserve") { 124 talerableIn(db) 125 } 126 measureAction("register_kyc") { 127 talerableKycIn(db) 128 } 129 130 // Revenue API 131 measureAction("transaction_revenue") { 132 client.getA("/taler-revenue/history").assertOk() 133 } 134 135 // Wire gateway 136 measureAction("wg_transfer") { 137 client.postA("/taler-wire-gateway/transfer") { 138 json { 139 "request_uid" to HashCode.rand() 140 "amount" to "CHF:0.0001" 141 "exchange_base_url" to "http://exchange.example.com/" 142 "wtid" to ShortHashCode.rand() 143 "credit_account" to grothoffPayto 144 } 145 }.assertOk() 146 } 147 measureAction("wg_transfer_get") { 148 client.getA("/taler-wire-gateway/transfers/42").assertOk() 149 } 150 measureAction("wg_transfer_page") { 151 client.getA("/taler-wire-gateway/transfers").assertOk() 152 } 153 measureAction("wg_transfer_page_filter") { 154 client.getA("/taler-wire-gateway/transfers?status=success").assertNoContent() 155 } 156 measureAction("wg_add") { 157 client.postA("/taler-wire-gateway/admin/add-incoming") { 158 json { 159 "amount" to "CHF:0.0001" 160 "reserve_pub" to EddsaPublicKey.rand() 161 "debit_account" to grothoffPayto 162 } 163 }.assertOk() 164 } 165 measureAction("wg_incoming") { 166 client.getA("/taler-wire-gateway/history/incoming") 167 .assertOk() 168 } 169 measureAction("wg_outgoing") { 170 client.getA("/taler-wire-gateway/history/outgoing") 171 .assertOk() 172 } 173 174 // Observability 175 measureAction("metrics") { 176 client.get("/taler-observability/metrics") 177 .assertOk() 178 } 179 }} 180 } 181 }