bench.kt (10325B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2024, 2025, 2026 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.crypto.CryptoUtil 25 import tech.libeufin.common.test.* 26 import tech.libeufin.nexus.* 27 import tech.libeufin.nexus.cli.* 28 import kotlin.math.max 29 import java.util.UUID; 30 import java.time.Instant 31 32 class Bench { 33 34 /** Generate [amount] rows to fill the database */ 35 fun genData(conn: PgConnection, amount: Int) { 36 val amount = max(amount, 10) 37 val token32 = ByteArray(32) 38 val token64 = ByteArray(64) 39 40 val accountPubs = List(amount*2) { EddsaPublicKey.randEdsaKey() } 41 42 conn.genData(amount, sequenceOf( 43 "incoming_transactions(amount, subject, execution_time, debit_payto, uetr, tx_id, acct_svcr_ref)" to { 44 val subject = if (it % 4 == 0) null else "subject ${it}" 45 val debtor = if (it % 3 == 0) null else "debit_payto" 46 if (it % 3 == 0) { 47 "(20,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\t\\N\t\\N\n" + 48 "(21,0)\t$subject\t0\t$debtor\t\\N\tTX_ID${it*2}\t\\N\n" + 49 "(22,0)\t$subject\t0\t$debtor\t\\N\t\\N\tREF${it*2}\n" 50 } else if (it % 3 == 1) { 51 "(30,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\tTX_ID${it*2}\t\\N\n" + 52 "(31,0)\t$subject\t0\t$debtor\t\\N\tTX_ID${it*2+1}\tREF${it*2}\n" + 53 "(32,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\t\\N\tREF${it*2+1}\n" 54 } else { 55 "(40,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\tTX_ID${it*2}\tREF${it*2}\n" + 56 "(41,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\tTX_ID${it*2+1}\tREF${it*2+1}\n" 57 } 58 }, 59 "outgoing_transactions(amount, subject, execution_time, credit_payto, end_to_end_id, acct_svcr_ref)" to { 60 val subject = if (it % 4 == 0) null else "subject ${it}" 61 val creditor = if (it % 3 == 0) null else "credit_payto" 62 if (it % 2 == 0) { 63 "(30,0)\t$subject\t0\t$creditor\t\\N\tREF${it*2}\n" + 64 "(31,0)\t$subject\t0\t$creditor\tE2E_ID${it*2}\t\\N\n" 65 } else { 66 "(30,0)\t$subject\t0\t$creditor\tE2E_ID${it*2}\tREF${it*2}\n" + 67 "(31,0)\t$subject\t0\t$creditor\tE2E_ID${it*2+1}\tREF${it*2+1}\n" 68 } 69 }, 70 "initiated_outgoing_transactions(amount, subject, initiation_time, credit_payto, outgoing_transaction_id, end_to_end_id)" to { 71 "(42,0)\tsubject\t0\tcredit_payto\t${it*2}\tE2E_ID$it\n" 72 }, 73 "prepared_transfers(type, account_pub, authorization_pub, authorization_sig, recurrent, reference_number, registered_at, incoming_transaction_id)" to { 74 val type = if (it % 2 == 0) "reserve" else "kyc" 75 val recurrent = if (it % 3 == 0) "true" else "false" 76 val incoming_transaction_id = if (it % 5 == 0) "\\N" else "${it*2}" 77 val reference_number = subjectFmtQrBill(accountPubs[it]) 78 val pub = accountPubs[it].raw.encodeHex() 79 val sig = token64.rand().encodeHex() 80 "$type\t\\\\x$pub\t\\\\x$pub\t\\\\x$sig\t$recurrent\t$reference_number\t0\t$incoming_transaction_id\n" 81 }, 82 "pending_recurrent_incoming_transactions(incoming_transaction_id, authorization_pub)" to { 83 val hex = accountPubs[it].raw.encodeHex() 84 "${it*2}\t\\\\x$hex\n" 85 }, 86 "bounced_transactions(incoming_transaction_id, initiated_outgoing_transaction_id)" to { 87 if (it % 10 == 0) { 88 "${it/2}\t${it/3}\n" 89 } else { 90 "" 91 } 92 }, 93 "talerable_incoming_transactions(type, metadata, incoming_transaction_id)" to { 94 val hex = token32.rand().encodeHex() 95 if (it % 2 == 0) { 96 "reserve\t\\\\x$hex\t${it*2}\n" 97 } else { 98 "kyc\t\\\\x$hex\t${it*2}\n" 99 } 100 }, 101 "talerable_outgoing_transactions(wtid, exchange_base_url, outgoing_transaction_id)" to { 102 val hex = token32.rand().encodeHex() 103 "\\\\x$hex\turl\t${it*2-1}\n" 104 }, 105 "transfer_operations(initiated_outgoing_transaction_id, request_uid, wtid, exchange_base_url)" to { 106 val hex32 = token32.rand().encodeHex() 107 val hex64 = token64.rand().encodeHex() 108 "$it\t\\\\x$hex64\t\\\\x$hex32\turl\n" 109 } 110 )) 111 } 112 113 @Test 114 fun benchDb() { 115 val ingestCfg = NexusIngestConfig.default(AccountType.exchange) 116 117 bench { AMOUNT -> serverSetup { db -> 118 // Generate data 119 db.conn { genData(it, AMOUNT) } 120 121 val accountPubs = List(AMOUNT) { EddsaPublicKey.randEdsaKeyPair() } 122 123 // Warm HTTP client 124 client.getA("/taler-revenue/config").assertOk() 125 126 // Register 127 measureAction("register_in") { 128 registerIn(db) 129 } 130 measureAction("register_incomplete_in") { 131 registerIncompleteIn(db) 132 } 133 measureAction("register_completed_in") { 134 registerCompletedIn(db) 135 } 136 measureAction("register_out") { 137 registerOut(db) 138 } 139 measureAction("register_incomplete_out") { 140 registerIncompleteOut(db) 141 } 142 measureAction("register_reserve") { 143 talerableIn(db) 144 } 145 measureAction("register_prepared_reserve") { 146 talerablePreparedIn(db) 147 } 148 measureAction("register_kyc") { 149 talerableKycIn(db) 150 } 151 152 // Revenue API 153 measureAction("transaction_revenue") { 154 client.getA("/taler-revenue/history").assertOk() 155 } 156 157 // Wire gateway 158 measureAction("wg_transfer") { 159 client.postA("/taler-wire-gateway/transfer") { 160 json { 161 "request_uid" to HashCode.rand() 162 "amount" to "CHF:0.0001" 163 "exchange_base_url" to "http://exchange.example.com/" 164 "wtid" to ShortHashCode.rand() 165 "credit_account" to grothoffPayto 166 } 167 }.assertOk() 168 } 169 measureAction("wg_transfer_get") { 170 client.getA("/taler-wire-gateway/transfers/42").assertOk() 171 } 172 measureAction("wg_transfer_page") { 173 client.getA("/taler-wire-gateway/transfers").assertOk() 174 } 175 measureAction("wg_transfer_page_filter") { 176 client.getA("/taler-wire-gateway/transfers?status=success").assertNoContent() 177 } 178 measureAction("wg_add") { 179 client.postA("/taler-wire-gateway/admin/add-incoming") { 180 json { 181 "amount" to "CHF:0.0001" 182 "reserve_pub" to EddsaPublicKey.randEdsaKey() 183 "debit_account" to grothoffPayto 184 } 185 }.assertOk() 186 } 187 measureAction("wg_incoming") { 188 client.getA("/taler-wire-gateway/history/incoming") 189 .assertOk() 190 } 191 measureAction("wg_outgoing") { 192 client.getA("/taler-wire-gateway/history/outgoing") 193 .assertOk() 194 } 195 196 // Wire transfer 197 measureAction("wt_register") { 198 val (priv, pub) = accountPubs[it] 199 val valid_req = obj { 200 "credit_amount" to "KUDOS:55" 201 "type" to "reserve" 202 "alg" to "ECDSA" 203 "account_pub" to pub 204 "authorization_pub" to pub 205 "authorization_sig" to CryptoUtil.eddsaSign(pub.raw, priv) 206 "recurrent" to false 207 } 208 client.post("/taler-wire-transfer-gateway/registration") { 209 json(valid_req) 210 }.assertOkJson<SubjectResult>() 211 client.post("/taler-wire-transfer-gateway/registration") { 212 json(valid_req) 213 }.assertOkJson<SubjectResult>() 214 } 215 measureAction("wt_unregister") { 216 val (priv, pub) = accountPubs[it] 217 val now = Instant.now().toString() 218 val valid_req = obj { 219 "timestamp" to now 220 "authorization_pub" to pub 221 "authorization_sig" to CryptoUtil.eddsaSign(now.toByteArray(), priv) 222 } 223 client.delete("/taler-wire-transfer-gateway/registration") { 224 json(valid_req) 225 }.assertNoContent() 226 client.delete("/taler-wire-transfer-gateway/registration") { 227 json(valid_req) 228 }.assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND) 229 } 230 231 // Observability 232 /*measureAction("metrics") { 233 client.get("/taler-observability/metrics") 234 .assertOk() 235 }*/ 236 }} 237 } 238 }