libeufin

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

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 }