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 }