libeufin

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

bench.kt (3486B)


      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 package tech.libeufin.common.test
     21 
     22 import org.postgresql.copy.*
     23 import org.postgresql.jdbc.*
     24 import tech.libeufin.common.*
     25 import kotlin.math.pow
     26 import kotlin.math.sqrt
     27 import kotlin.time.DurationUnit
     28 import kotlin.time.measureTime
     29 import kotlin.time.toDuration
     30 
     31 fun PgConnection.genData(amount: Int, generators: Sequence<Pair<String, (Int) -> String>>) {
     32     for ((table, generator) in generators) {
     33         println("Gen rows for $table")
     34         PGCopyOutputStream(this, "COPY $table FROM STDIN", 1024 * 1024).use { out -> 
     35             repeat(amount) { 
     36                 val str = generator(it+1)
     37                 val bytes = str.toByteArray()
     38                 out.write(bytes)
     39             }
     40         }
     41     }
     42 
     43     // Update database statistics for better perf
     44     this.execSQLUpdate("VACUUM ANALYZE")
     45 }
     46 
     47 class Benchmark(private val iter: Int) {
     48     private val WARN = 4.toDuration(DurationUnit.MILLISECONDS)
     49     private val ERR = 50.toDuration(DurationUnit.MILLISECONDS)
     50     internal val measures: MutableList<List<String>> = mutableListOf()
     51 
     52     internal fun fmtMeasures(times: LongArray): List<String> {
     53         val min: Long = times.min()
     54         val max: Long = times.max()
     55         val mean: Long = times.average().toLong()
     56         val variance = times.map { (it.toDouble() - mean).pow(2) }.average()
     57         val stdVar: Long = sqrt(variance.toDouble()).toLong()
     58         return sequenceOf(min, mean, max, stdVar).map {
     59             val duration = it.toDuration(DurationUnit.MICROSECONDS)
     60             val str = duration.toString()
     61             if (duration > ERR) {
     62                 ANSI.red(str)
     63             } else if (duration > WARN) {
     64                 ANSI.yellow(str)
     65             } else {
     66                 ANSI.green(str)
     67             }
     68             
     69         }.toList()
     70     }
     71 
     72     suspend fun <R> measureAction(name: String, lambda: suspend (Int) -> R): List<R> {
     73         println("Measure action $name")
     74         val results = mutableListOf<R>()
     75         val times = LongArray(iter) { idx ->
     76             measureTime { 
     77                 val result = lambda(idx)
     78                 results.add(result)
     79             }.inWholeMicroseconds
     80         }
     81         measures.add(listOf(ANSI.magenta(name)) + fmtMeasures(times))
     82         return results
     83     }
     84 }
     85 
     86 fun bench(lambda: Benchmark.(Int) -> Unit) {
     87     val ITER = System.getenv("BENCH_ITER")?.toIntOrNull() ?: 10
     88     val AMOUNT = System.getenv("BENCH_AMOUNT")?.toIntOrNull() ?: 100
     89 
     90     println("Bench $ITER times with $AMOUNT rows")
     91 
     92     val bench = Benchmark(ITER)
     93     bench.lambda(AMOUNT)
     94     printTable(
     95         listOf("benchmark", "min", "mean", "max", "std").map { ANSI.bold(it) },
     96         bench.measures,
     97         ' ',
     98         listOf(ColumnStyle.DEFAULT) + List(5) { ColumnStyle(false) }
     99     )
    100 }