Last active
December 12, 2024 22:51
-
-
Save teenriot/7fcfde4ad498a46c99602f24a969460c to your computer and use it in GitHub Desktop.
HTML-Report generation for kotlinx benchmarks - JVM only
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import kotlinx.benchmark.gradle.BenchmarkConfiguration | |
import java.io.File | |
import java.nio.charset.StandardCharsets.UTF_8 | |
import java.util.zip.ZipInputStream | |
import kotlin.text.replace | |
plugins { | |
// dependency ist only needed by HtmlReportGenerator to extract files from: | |
// https://github.com/jzillmann/gradle-jmh-report/blob/master/src/main/resources/jmh-visualizer.zip | |
id("io.morethan.jmhreport") version "0.9.0" | |
} | |
// ########################################################################## | |
val benchmarkHtmlReportGenerator = HtmlReportGenerator( | |
outputDir = project.file("benchmark-reports"), | |
resourceDirectoryName = "resources", | |
openNewReportsInBrowser = true | |
) | |
// TODO this is a hack | |
// how to correctly start HTML-Generator after a benchmark run produced a json report file? | |
// Kotlinx benchmark tasks are created on the fly and have no output! | |
tasks["benchmarkClasses"].doLast { | |
// here we can get benchmark tasks that were created on the fly | |
tasks.filter{ it.name.startsWith("benchmark") && it.name.endsWith("Benchmark") }.forEach { task -> | |
task.doLast { | |
val benchmarkDescriptionFile = (task as? JavaExec) | |
?.args?.getOrNull(0) | |
?.let(::File)?.takeIf { it.exists() } | |
val jsonReportFile = benchmarkDescriptionFile | |
?.readText(UTF_8)?.lineSequence() | |
?.firstOrNull { it.startsWith("reportFile:") } | |
?.substringAfter("reportFile:") | |
?.takeIf{ it.endsWith(".json", ignoreCase = true) } | |
?.let(::File)?.takeIf { it.exists() } | |
if (jsonReportFile!=null) { | |
benchmarkHtmlReportGenerator.generateHtmlReport(jsonReportFile) | |
} | |
} | |
} | |
} | |
/** based on: https://github.com/jzillmann/gradle-jmh-report/blob/master/src/main/kotlin/io/morethan/jmhreport/gradle/task/JmhReportTask.kt */ | |
class HtmlReportGenerator( | |
val outputDir : File, | |
val resourceDirectoryName : String, | |
val openNewReportsInBrowser : Boolean | |
) { | |
private fun jsonToHtmlReportFile(jsonReportFile: File): File { | |
// TODO: UNSAFE since it depends on a file & directory name structure | |
// that may change in future or become configurable by end user | |
val benchmarkName = jsonReportFile.parentFile.parentFile.name | |
val benchmarkDateTime = jsonReportFile.parentFile.name | |
val benchmarkDate = benchmarkDateTime.substringBefore("T") | |
val benchmarkTime = benchmarkDateTime.substringAfter("T").substringBeforeLast(".").replace('.', '-') | |
return outputDir.resolve("${benchmarkName}__${benchmarkDate}__${benchmarkTime}.html") | |
} | |
fun generateHtmlReport(jsonFile: File) { | |
if (!jsonFile.exists()) return | |
val reportFile = jsonToHtmlReportFile(jsonFile) | |
if (reportFile.exists()) return | |
val resourceDirectory = reportFile.parentFile.resolve(resourceDirectoryName) | |
val json = jsonFile.readText(UTF_8) | |
generateResourceDirectory(resourceDirectory) | |
generateReportFile(json, reportFile, resourceDirectory) | |
println("HTML-Report generated: ${reportFile.asConsoleLink}") | |
println() | |
if (openNewReportsInBrowser) { | |
reportFile.openInBrowser() | |
} | |
} | |
private fun generateResourceDirectory(resourceDirectory : File) { | |
if (resourceDirectory.exists()) return | |
val zipStream = this.javaClass.getResourceAsStream("/jmh-visualizer.zip") | |
?: error("'jmh-visualizer.zip' not found!") | |
ZipInputStream(zipStream).use { | |
it.extractTo(resourceDirectory) | |
} | |
} | |
private fun generateReportFile(json: String, reportFile: File, resourceDirectory: File) { | |
val originalHtml = resourceDirectory.resolve("index.html").readText(UTF_8) | |
val html = originalHtml | |
.replace( | |
"src=\"provided.js\">", | |
"""> | |
var providedBenchmarks = ['Benchmark']; | |
var providedBenchmarkStore = { 'Benchmark': $json }; | |
""".trimIndent() | |
) | |
.replace("href=\"", "href=\"$resourceDirectoryName/") | |
.replace("src=\"", "src=\"$resourceDirectoryName/") | |
reportFile.parentFile.mkdirs() | |
reportFile.writeText(html, UTF_8) | |
} | |
// H E L P E R | |
private fun File.openInBrowser() { | |
val os = org.gradle.internal.os.OperatingSystem.current() | |
val uri = toURI() | |
when { | |
os?.isWindows == true -> Runtime.getRuntime().exec(arrayOf("cmd", "/c", "start $uri")) | |
os?.isMacOsX == true -> Runtime.getRuntime().exec(arrayOf("open", "$uri")) | |
else -> error("open files in browser is not implemented for OS: ${os.name ?: "unknown"}") | |
} | |
} | |
private val File.asConsoleLink : String | |
get() = "file:///${absolutePath.replace('\\', '/')}" | |
private fun ZipInputStream.extractTo(directory: File) { | |
directory.mkdirs() | |
while (true) { | |
val zipEntry = getNextEntry() ?: break | |
directory.resolve(zipEntry.name).apply { | |
if (zipEntry.isDirectory) mkdirs() | |
else outputStream().use(::copyTo) | |
} | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment