Move everything under kotlin-native folder
I was forced to manually do update the following files, because otherwise they would be ignored according .gitignore settings. Probably they should be deleted from repo. Interop/.idea/compiler.xml Interop/.idea/gradle.xml Interop/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_3.xml Interop/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_3.xml Interop/.idea/modules.xml Interop/.idea/modules/Indexer/Indexer.iml Interop/.idea/modules/Runtime/Runtime.iml Interop/.idea/modules/StubGenerator/StubGenerator.iml backend.native/backend.native.iml backend.native/bc.frontend/bc.frontend.iml backend.native/cli.bc/cli.bc.iml backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt backend.native/tests/link/lib/foo.kt backend.native/tests/link/lib/foo2.kt backend.native/tests/teamcity-test.property
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
buildscript {
|
||||
ext.rootBuildDirectory = file('../..')
|
||||
|
||||
apply from: "$rootBuildDirectory/gradle/loadRootProperties.gradle"
|
||||
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin-multiplatform'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url buildKotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
def getHostName() {
|
||||
def target = System.getProperty("os.name")
|
||||
if (target == 'Linux') return 'linux'
|
||||
if (target.startsWith('Windows')) return 'windows'
|
||||
if (target.startsWith('Mac')) return 'macos'
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir '../benchmarks/shared/src'
|
||||
kotlin.srcDir 'src/main/kotlin'
|
||||
kotlin.srcDir '../../endorsedLibraries/kotlinx.cli/src/main/kotlin'
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-test-common:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir 'src/tests'
|
||||
}
|
||||
jvmTest {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
jsTest {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-test-js:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
nativeMain {
|
||||
dependsOn commonMain
|
||||
kotlin.srcDir 'src/main/kotlin-native'
|
||||
kotlin.srcDir '../../endorsedLibraries/kotlinx.cli/src/main/kotlin-native'
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir 'src/main/kotlin-jvm'
|
||||
kotlin.srcDir '../../endorsedLibraries/kotlinx.cli/src/main/kotlin-jvm'
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir 'src/main/kotlin-js'
|
||||
kotlin.srcDir '../../endorsedLibraries/kotlinx.cli/src/main/kotlin-js'
|
||||
}
|
||||
linuxMain { dependsOn nativeMain }
|
||||
windowsMain { dependsOn nativeMain }
|
||||
macosMain {dependsOn nativeMain }
|
||||
}
|
||||
|
||||
targets {
|
||||
fromPreset(presets.jvm, 'jvm') {
|
||||
compilations.all {
|
||||
tasks[compileKotlinTaskName].kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
tasks[compileKotlinTaskName].kotlinOptions.suppressWarnings = true
|
||||
}
|
||||
}
|
||||
|
||||
fromPreset(presets.mingwX64, 'windows') {
|
||||
binaries.all {
|
||||
linkerOpts = ["-L${getMingwPath()}/lib".toString()]
|
||||
}
|
||||
compilations.main.cinterops {
|
||||
libcurl {
|
||||
includeDirs.headerFilterOnly "${getMingwPath()}/include"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fromPreset(presets.linuxX64, 'linux') {
|
||||
compilations.main.cinterops {
|
||||
libcurl {
|
||||
includeDirs.headerFilterOnly '/usr/include', '/usr/include/x86_64-linux-gnu'
|
||||
}
|
||||
}
|
||||
}
|
||||
fromPreset(presets.macosX64, 'macos') {
|
||||
compilations.main.cinterops {
|
||||
libcurl {
|
||||
includeDirs.headerFilterOnly '/opt/local/include', '/usr/local/include'
|
||||
}
|
||||
}
|
||||
}
|
||||
fromPreset(presets.js, 'js') {
|
||||
compilations.main.kotlinOptions {
|
||||
main = "noCall"
|
||||
}
|
||||
}
|
||||
|
||||
configure([windows, linux, macos]) {
|
||||
def isCurrentHost = (name == getHostName())
|
||||
compilations.all {
|
||||
cinterops.all {
|
||||
project.tasks[interopProcessingTaskName].enabled = isCurrentHost
|
||||
}
|
||||
compileKotlinTask.enabled = isCurrentHost
|
||||
}
|
||||
binaries.all {
|
||||
linkTask.enabled = isCurrentHost
|
||||
}
|
||||
|
||||
binaries {
|
||||
executable('benchmarksAnalyzer', [RELEASE]) {
|
||||
if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
|
||||
linkerOpts("-L${getMingwPath()}/lib")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
js {
|
||||
browser {
|
||||
distribution {
|
||||
directory = new File("$projectDir/web/")
|
||||
}
|
||||
dceTask {
|
||||
keep 'benchmarksAnalyzer.main_kand9s$'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getMingwPath() {
|
||||
def directory = System.getenv("MINGW64_DIR")
|
||||
if (directory == null)
|
||||
directory = "c:/msys64/mingw64"
|
||||
return directory
|
||||
}
|
||||
|
||||
task assembleWeb(type: Sync) {
|
||||
def runtimeDependencies = kotlin.targets.js.compilations.main.runtimeDependencyFiles
|
||||
from(files {
|
||||
runtimeDependencies.collect { File file ->
|
||||
zipTree(file.absolutePath)
|
||||
}
|
||||
}.builtBy(runtimeDependencies)) {
|
||||
includeEmptyDirs = false
|
||||
include { fileTreeElement ->
|
||||
def path = fileTreeElement.path
|
||||
path.endsWith(".js") && (path.startsWith("META-INF/resources/") ||
|
||||
!path.startsWith("META-INF/"))
|
||||
}
|
||||
}
|
||||
|
||||
from compileKotlinJs.destinationDir
|
||||
into "${projectDir}/web"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
org.jetbrains.kotlin.native.home=../../dist
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
# Avoid building platform libraries by the MPP plugin.
|
||||
kotlin.native.distribution.type=prebuilt
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
import org.w3c.xhr.*
|
||||
import kotlin.browser.*
|
||||
import kotlin.js.*
|
||||
|
||||
actual fun readFile(fileName: String): String {
|
||||
error("Reading from local file for JS isn't supported")
|
||||
}
|
||||
|
||||
actual fun Double.format(decimalNumber: Int): String =
|
||||
this.asDynamic().toFixed(decimalNumber)
|
||||
|
||||
actual fun writeToFile(fileName: String, text: String) {
|
||||
if (fileName != "html")
|
||||
error("Writing to local file for JS isn't supported")
|
||||
val bodyPart = text.substringAfter("<body>").substringBefore("</body>")
|
||||
document.body?.innerHTML = bodyPart
|
||||
}
|
||||
|
||||
actual fun assert(value: Boolean, lazyMessage: () -> Any) {
|
||||
if (!value) error(lazyMessage)
|
||||
}
|
||||
|
||||
actual fun sendGetRequest(url: String, user: String?, password: String?, followLocation: Boolean) : String {
|
||||
val proxyServerAddress = "https://perf-proxy.labs.jb.gg/"
|
||||
val newUrl = proxyServerAddress + url
|
||||
val request = XMLHttpRequest()
|
||||
|
||||
request.open("GET", newUrl, false, user, password)
|
||||
request.send()
|
||||
if (request.status == 200.toShort()) {
|
||||
return request.responseText
|
||||
}
|
||||
error("Request to $url has status ${request.status}")
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.Base64
|
||||
|
||||
actual fun readFile(fileName: String): String {
|
||||
val inputStream = File(fileName).inputStream()
|
||||
val inputString = inputStream.bufferedReader().use { it.readText() }
|
||||
return inputString
|
||||
}
|
||||
|
||||
actual fun Double.format(decimalNumber: Int): String =
|
||||
"%.${decimalNumber}f".format(this)
|
||||
|
||||
actual fun writeToFile(fileName: String, text: String) {
|
||||
File(fileName).printWriter().use { out ->
|
||||
out.println(text)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun assert(value: Boolean, lazyMessage: () -> Any) =
|
||||
kotlin.assert(value, lazyMessage)
|
||||
|
||||
// Create http(-s) request.
|
||||
fun getHttpRequest(url: String, user: String?, password: String?): HttpURLConnection {
|
||||
val connection = URL(url).openConnection() as HttpURLConnection
|
||||
if (user != null && password != null) {
|
||||
val auth = Base64.getEncoder().encode((user + ":" + password).toByteArray()).toString(Charsets.UTF_8)
|
||||
connection.addRequestProperty("Authorization", "Basic $auth")
|
||||
}
|
||||
connection.setRequestProperty("Accept", "application/json")
|
||||
return connection
|
||||
}
|
||||
|
||||
actual fun sendGetRequest(url: String, user: String?, password: String?, followLocation: Boolean) : String {
|
||||
val connection = getHttpRequest(url, user, password)
|
||||
connection.connect()
|
||||
val responseCode = connection.responseCode
|
||||
if (!followLocation) {
|
||||
connection.connect()
|
||||
return connection.inputStream.use { it.reader().use { reader -> reader.readText() } }
|
||||
}
|
||||
|
||||
// Request with redirect.
|
||||
if (responseCode != HttpURLConnection.HTTP_MOVED_TEMP &&
|
||||
responseCode != HttpURLConnection.HTTP_MOVED_PERM &&
|
||||
responseCode != HttpURLConnection.HTTP_SEE_OTHER) {
|
||||
error("No opportunity to redirect, but flag for redirecting to location was provided!")
|
||||
}
|
||||
val newUrl = connection.getHeaderField("Location")
|
||||
val cookies = connection.getHeaderField("Set-Cookie")
|
||||
val redirect = getHttpRequest(newUrl, user, password)
|
||||
redirect.setRequestProperty("Cookie", cookies)
|
||||
redirect.connect()
|
||||
return redirect.inputStream.use { it.reader().use { reader -> reader.readText() } }
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
import platform.posix.*
|
||||
import kotlinx.cinterop.*
|
||||
import libcurl.*
|
||||
|
||||
actual fun readFile(fileName: String): String {
|
||||
val file = fopen(fileName, "r") ?: error("Cannot read file '$fileName'")
|
||||
var buffer = ByteArray(1024)
|
||||
var text = StringBuilder()
|
||||
try {
|
||||
while (true) {
|
||||
val nextLine = fgets(buffer.refTo(0), buffer.size, file)?.toKString()
|
||||
if (nextLine == null) break
|
||||
text.append(nextLine)
|
||||
}
|
||||
} finally {
|
||||
fclose(file)
|
||||
}
|
||||
return text.toString()
|
||||
}
|
||||
|
||||
actual fun Double.format(decimalNumber: Int): String {
|
||||
var buffer = ByteArray(1024)
|
||||
snprintf(buffer.refTo(0), buffer.size.toULong(), "%.${decimalNumber}f", this)
|
||||
return buffer.toKString()
|
||||
}
|
||||
|
||||
actual fun writeToFile(fileName: String, text: String) {
|
||||
val file = fopen(fileName, "wt") ?: error("Cannot write file '$fileName'")
|
||||
try {
|
||||
if (fputs(text, file) == EOF) throw Error("File write error")
|
||||
} finally {
|
||||
fclose(file)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun assert(value: Boolean, lazyMessage: () -> Any) =
|
||||
kotlin.assert(value, lazyMessage)
|
||||
|
||||
class CUrl(url: String, user: String? = null, password: String? = null, followLocation: Boolean = false) {
|
||||
private val stableRef = StableRef.create(this)
|
||||
|
||||
private val curl = curl_easy_init()
|
||||
|
||||
init {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url)
|
||||
val writeData = staticCFunction(::collectResponse)
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData)
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, stableRef.asCPointer())
|
||||
if (followLocation) {
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L)
|
||||
}
|
||||
user ?.let {
|
||||
curl_easy_setopt(curl, CURLOPT_USERNAME, it)
|
||||
}
|
||||
password ?.let {
|
||||
curl_easy_setopt(curl, CURLOPT_PASSWORD, it)
|
||||
}
|
||||
}
|
||||
|
||||
val body = StringBuilder()
|
||||
|
||||
fun fetch() {
|
||||
memScoped {
|
||||
val res = curl_easy_perform(curl)
|
||||
if (res != CURLE_OK)
|
||||
error("curl_easy_perform() failed: ${curl_easy_strerror(res)?.toKString()}")
|
||||
val http_code = alloc<LongVar>()
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, http_code.ptr)
|
||||
if (http_code.value >= 400L) {
|
||||
error("Error http code ${http_code.value}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun close() {
|
||||
curl_easy_cleanup(curl)
|
||||
stableRef.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
fun CPointer<ByteVar>.toKString(length: Int): String {
|
||||
val bytes = this.readBytes(length)
|
||||
return bytes.toKString()
|
||||
}
|
||||
|
||||
fun collectResponse(buffer: CPointer<ByteVar>?, size: size_t, nitems: size_t, userdata: COpaquePointer?): size_t {
|
||||
buffer ?: return 0u
|
||||
userdata ?. let {
|
||||
val data = buffer.toKString((size * nitems).toInt()).trim()
|
||||
val curl = userdata.asStableRef<CUrl>().get()
|
||||
curl.body.append(data)
|
||||
}
|
||||
return size * nitems
|
||||
}
|
||||
|
||||
actual fun sendGetRequest(url: String, user: String?, password: String?, followLocation: Boolean) : String {
|
||||
val curl = CUrl(url, user, password, followLocation)
|
||||
curl.fetch()
|
||||
curl.close()
|
||||
return curl.body.toString()
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
import kotlinx.cli.*
|
||||
import org.jetbrains.analyzer.sendGetRequest
|
||||
import org.jetbrains.analyzer.readFile
|
||||
import org.jetbrains.analyzer.SummaryBenchmarksReport
|
||||
import org.jetbrains.renders.*
|
||||
import org.jetbrains.report.*
|
||||
import org.jetbrains.report.json.*
|
||||
|
||||
abstract class Connector {
|
||||
abstract val connectorPrefix: String
|
||||
|
||||
fun isCompatible(fileName: String) =
|
||||
fileName.startsWith(connectorPrefix)
|
||||
|
||||
abstract fun getFileContent(fileLocation: String, user: String? = null): String
|
||||
}
|
||||
|
||||
object ArtifactoryConnector : Connector() {
|
||||
override val connectorPrefix = "artifactory:"
|
||||
val artifactoryUrl = "https://repo.labs.intellij.net/kotlin-native-benchmarks"
|
||||
|
||||
override fun getFileContent(fileLocation: String, user: String?): String {
|
||||
val fileParametersSize = 3
|
||||
val fileDescription = fileLocation.substringAfter(connectorPrefix)
|
||||
val fileParameters = fileDescription.split(':', limit = fileParametersSize)
|
||||
|
||||
// Right link to Artifactory file.
|
||||
if (fileParameters.size == 1) {
|
||||
val accessFileUrl = "$artifactoryUrl/${fileParameters[0]}"
|
||||
return sendGetRequest(accessFileUrl, followLocation = true)
|
||||
}
|
||||
// Used builds description format.
|
||||
if (fileParameters.size != fileParametersSize) {
|
||||
error("To get file from Artifactory, please, specify, build number from TeamCity and target" +
|
||||
" in format artifactory:build_number:target:filename")
|
||||
}
|
||||
val (buildNumber, target, fileName) = fileParameters
|
||||
val accessFileUrl = "$artifactoryUrl/$target/$buildNumber/$fileName"
|
||||
return sendGetRequest(accessFileUrl, followLocation = true)
|
||||
}
|
||||
}
|
||||
|
||||
object TeamCityConnector : Connector() {
|
||||
override val connectorPrefix = "teamcity:"
|
||||
val teamCityUrl = "http://buildserver.labs.intellij.net"
|
||||
|
||||
override fun getFileContent(fileLocation: String, user: String?): String {
|
||||
val fileDescription = fileLocation.substringAfter(connectorPrefix)
|
||||
val buildLocator = fileDescription.substringBeforeLast(':')
|
||||
val fileName = fileDescription.substringAfterLast(':')
|
||||
if (fileDescription == fileLocation ||
|
||||
fileDescription == buildLocator || fileName == fileDescription) {
|
||||
error("To get file from TeamCity, please, specify, build locator and filename on TeamCity" +
|
||||
" in format teamcity:build_locator:filename")
|
||||
}
|
||||
val accessFileUrl = "$teamCityUrl/app/rest/builds/$buildLocator/artifacts/content/$fileName"
|
||||
val userName = user?.substringBefore(':')
|
||||
val password = user?.substringAfter(':')
|
||||
return sendGetRequest(accessFileUrl, userName, password)
|
||||
}
|
||||
}
|
||||
|
||||
object DBServerConnector : Connector() {
|
||||
override val connectorPrefix = ""
|
||||
val serverUrl = "https://kotlin-native-perf-summary.labs.jb.gg"
|
||||
|
||||
override fun getFileContent(fileLocation: String, user: String?): String {
|
||||
val buildNumber = fileLocation.substringBefore(':')
|
||||
val target = fileLocation.substringAfter(':')
|
||||
if (target == buildNumber) {
|
||||
error("To get file from database, please, specify, target and build number" +
|
||||
" in format target:build_number")
|
||||
}
|
||||
val accessFileUrl = "$serverUrl/report/$target/$buildNumber"
|
||||
return sendGetRequest(accessFileUrl)
|
||||
}
|
||||
}
|
||||
|
||||
fun getFileContent(fileName: String, user: String? = null): String {
|
||||
return when {
|
||||
ArtifactoryConnector.isCompatible(fileName) -> ArtifactoryConnector.getFileContent(fileName, user)
|
||||
TeamCityConnector.isCompatible(fileName) -> TeamCityConnector.getFileContent(fileName, user)
|
||||
fileName.endsWith(".json") -> readFile(fileName)
|
||||
else -> DBServerConnector.getFileContent(fileName, user)
|
||||
}
|
||||
}
|
||||
|
||||
fun getBenchmarkReport(fileName: String, user: String? = null): List<BenchmarksReport> {
|
||||
val jsonEntity = JsonTreeParser.parse(getFileContent(fileName, user))
|
||||
return when (jsonEntity) {
|
||||
is JsonObject -> listOf(BenchmarksReport.create(jsonEntity))
|
||||
is JsonArray -> jsonEntity.map { BenchmarksReport.create(it) }
|
||||
else -> error("Wrong format of report. Expected object or array of objects.")
|
||||
}
|
||||
}
|
||||
|
||||
fun parseNormalizeResults(results: String): Map<String, Map<String, Double>> {
|
||||
val parsedNormalizeResults = mutableMapOf<String, MutableMap<String, Double>>()
|
||||
val tokensNumber = 3
|
||||
results.lines().forEach {
|
||||
if (!it.isEmpty()) {
|
||||
val tokens = it.split(",").map { it.trim() }
|
||||
if (tokens.size != tokensNumber) {
|
||||
error("Data for normalization should include benchmark name, metric name and value. Got $it")
|
||||
}
|
||||
parsedNormalizeResults.getOrPut(tokens[0], { mutableMapOf<String, Double>() })[tokens[1]] = tokens[2].toDouble()
|
||||
}
|
||||
}
|
||||
return parsedNormalizeResults
|
||||
}
|
||||
|
||||
fun mergeCompilerFlags(reports: List<BenchmarksReport>): List<String> {
|
||||
val flagsMap = mutableMapOf<String, MutableList<String>>()
|
||||
reports.forEach {
|
||||
val benchmarks = it.benchmarks.values.flatten().asSequence().filter { it.metric == BenchmarkResult.Metric.COMPILE_TIME }
|
||||
.map { it.shortName }.toList()
|
||||
if (benchmarks.isNotEmpty())
|
||||
(flagsMap.getOrPut("${it.compiler.backend.flags.joinToString()}") { mutableListOf<String>() }).addAll(benchmarks)
|
||||
}
|
||||
return flagsMap.map { (flags, benchmarks) -> "$flags for [${benchmarks.distinct().sorted().joinToString()}]" }
|
||||
}
|
||||
|
||||
fun mergeReportsWithDetailedFlags(reports: List<BenchmarksReport>) =
|
||||
if (reports.size > 1) {
|
||||
// Merge reports.
|
||||
val detailedFlags = mergeCompilerFlags(reports)
|
||||
reports.map {
|
||||
BenchmarksReport(it.env, it.benchmarks.values.flatten(),
|
||||
Compiler(Compiler.Backend(it.compiler.backend.type, it.compiler.backend.version, detailedFlags),
|
||||
it.compiler.kotlinVersion))
|
||||
}.reduce { result, it -> result + it }
|
||||
} else {
|
||||
reports.first()
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
// Parse args.
|
||||
val argParser = ArgParser("benchmarksAnalyzer")
|
||||
|
||||
val mainReport by argParser.argument(ArgType.String, description = "Main report for analysis")
|
||||
val compareToReport by argParser.argument(ArgType.String, description = "Report to compare to").optional()
|
||||
|
||||
val output by argParser.option(ArgType.String, shortName = "o", description = "Output file")
|
||||
val epsValue by argParser.option(ArgType.Double, "eps", "e",
|
||||
"Meaningful performance changes").default(1.0)
|
||||
val useShortForm by argParser.option(ArgType.Boolean, "short", "s",
|
||||
"Show short version of report").default(false)
|
||||
val renders by argParser.option(ArgType.Choice(listOf("text", "html", "teamcity", "statistics", "metrics")),
|
||||
shortName = "r", description = "Renders for showing information").multiple().default(listOf("text"))
|
||||
val user by argParser.option(ArgType.String, shortName = "u", description = "User access information for authorization")
|
||||
|
||||
argParser.parse(args)
|
||||
// Read contents of file.
|
||||
val mainBenchsReport = mergeReportsWithDetailedFlags(getBenchmarkReport(mainReport, user))
|
||||
|
||||
var compareToBenchsReport = compareToReport?.let {
|
||||
mergeReportsWithDetailedFlags(getBenchmarkReport(it, user))
|
||||
}
|
||||
|
||||
// Generate comparasion report.
|
||||
val summaryReport = SummaryBenchmarksReport(mainBenchsReport,
|
||||
compareToBenchsReport,
|
||||
epsValue)
|
||||
|
||||
var outputFile = output
|
||||
renders.forEach {
|
||||
Render.getRenderByName(it).print(summaryReport, useShortForm, outputFile)
|
||||
outputFile = null
|
||||
}
|
||||
}
|
||||
+672
@@ -0,0 +1,672 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.renders
|
||||
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.*
|
||||
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
|
||||
private fun <T : Comparable<T>> clamp(value: T, minValue: T, maxValue: T): T =
|
||||
minOf(maxOf(value, minValue), maxValue)
|
||||
|
||||
// Natural number.
|
||||
class Natural(initValue: Int) {
|
||||
val value = if (initValue > 0) initValue else error("Provided value $initValue isn't natural")
|
||||
|
||||
override fun toString(): String {
|
||||
return value.toString()
|
||||
}
|
||||
}
|
||||
|
||||
interface Element {
|
||||
fun render(builder: StringBuilder, indent: String)
|
||||
}
|
||||
|
||||
class TextElement(val text: String) : Element {
|
||||
override fun render(builder: StringBuilder, indent: String) {
|
||||
builder.append("$indent$text\n")
|
||||
}
|
||||
}
|
||||
|
||||
@DslMarker
|
||||
annotation class HtmlTagMarker
|
||||
|
||||
@HtmlTagMarker
|
||||
abstract class Tag(val name: String) : Element {
|
||||
val children = arrayListOf<Element>()
|
||||
val attributes = hashMapOf<String, String>()
|
||||
|
||||
protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T {
|
||||
tag.init()
|
||||
children.add(tag)
|
||||
return tag
|
||||
}
|
||||
|
||||
override fun render(builder: StringBuilder, indent: String) {
|
||||
builder.append("$indent<$name${renderAttributes()}>\n")
|
||||
for (c in children) {
|
||||
c.render(builder, indent + " ")
|
||||
}
|
||||
builder.append("$indent</$name>\n")
|
||||
}
|
||||
|
||||
private fun renderAttributes(): String =
|
||||
attributes.map { (attr, value) ->
|
||||
"$attr=\"$value\""
|
||||
}.joinToString(separator = " ", prefix = " ")
|
||||
|
||||
override fun toString(): String {
|
||||
val builder = StringBuilder()
|
||||
render(builder, "")
|
||||
return builder.toString()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TagWithText(name: String) : Tag(name) {
|
||||
operator fun String.unaryPlus() {
|
||||
children.add(TextElement(this))
|
||||
}
|
||||
}
|
||||
|
||||
class HTML : TagWithText("html") {
|
||||
fun head(init: Head.() -> Unit) = initTag(Head(), init)
|
||||
fun body(init: Body.() -> Unit) = initTag(Body(), init)
|
||||
}
|
||||
|
||||
class Head : TagWithText("head") {
|
||||
fun title(init: Title.() -> Unit) = initTag(Title(), init)
|
||||
fun link(init: Link.() -> Unit) = initTag(Link(), init)
|
||||
fun script(init: Script.() -> Unit) = initTag(Script(), init)
|
||||
}
|
||||
|
||||
class Title : TagWithText("title")
|
||||
class Link : TagWithText("link")
|
||||
class Script : TagWithText("script")
|
||||
|
||||
abstract class BodyTag(name: String) : TagWithText(name) {
|
||||
fun b(init: B.() -> Unit) = initTag(B(), init)
|
||||
fun p(init: P.() -> Unit) = initTag(P(), init)
|
||||
fun h1(init: H1.() -> Unit) = initTag(H1(), init)
|
||||
fun h2(init: H2.() -> Unit) = initTag(H2(), init)
|
||||
fun h4(init: H4.() -> Unit) = initTag(H4(), init)
|
||||
fun hr(init: HR.() -> Unit) = initTag(HR(), init)
|
||||
fun a(href: String, init: A.() -> Unit) {
|
||||
val a = initTag(A(), init)
|
||||
a.href = href
|
||||
}
|
||||
|
||||
fun img(src: String, init: Image.() -> Unit) {
|
||||
val element = initTag(Image(), init)
|
||||
element.src = src
|
||||
}
|
||||
|
||||
fun table(init: Table.() -> Unit) = initTag(Table(), init)
|
||||
fun div(classAttr: String, init: Div.() -> Unit) = initTag(Div(classAttr), init)
|
||||
fun button(classAttr: String, init: Button.() -> Unit) = initTag(Button(classAttr), init)
|
||||
fun header(classAttr: String, init: Header.() -> Unit) = initTag(Header(classAttr), init)
|
||||
fun span(classAttr: String, init: Span.() -> Unit) = initTag(Span(classAttr), init)
|
||||
}
|
||||
|
||||
abstract class BodyTagWithClass(name: String, val classAttr: String) : BodyTag(name) {
|
||||
init {
|
||||
attributes["class"] = classAttr
|
||||
}
|
||||
}
|
||||
|
||||
class Body : BodyTag("body")
|
||||
class B : BodyTag("b")
|
||||
class P : BodyTag("p")
|
||||
class H1 : BodyTag("h1")
|
||||
class H2 : BodyTag("h2")
|
||||
class H4 : BodyTag("h4")
|
||||
class HR : BodyTag("hr")
|
||||
class Div(classAttr: String) : BodyTagWithClass("div", classAttr)
|
||||
class Header(classAttr: String) : BodyTagWithClass("header", classAttr)
|
||||
class Button(classAttr: String) : BodyTagWithClass("button", classAttr)
|
||||
class Span(classAttr: String) : BodyTagWithClass("span", classAttr)
|
||||
|
||||
class A : BodyTag("a") {
|
||||
var href: String by attributes
|
||||
}
|
||||
|
||||
class Image : BodyTag("img") {
|
||||
var src: String by attributes
|
||||
}
|
||||
|
||||
abstract class TableTag(name: String) : BodyTag(name) {
|
||||
fun thead(init: THead.() -> Unit) = initTag(THead(), init)
|
||||
fun tbody(init: TBody.() -> Unit) = initTag(TBody(), init)
|
||||
fun tfoot(init: TFoot.() -> Unit) = initTag(TFoot(), init)
|
||||
}
|
||||
|
||||
abstract class TableBlock(name: String) : TableTag(name) {
|
||||
fun tr(init: TableRow.() -> Unit) = initTag(TableRow(), init)
|
||||
}
|
||||
|
||||
class Table : TableTag("table")
|
||||
class THead : TableBlock("thead")
|
||||
class TFoot : TableBlock("tfoot")
|
||||
class TBody : TableBlock("tbody")
|
||||
|
||||
abstract class TableRowTag(name: String) : TableBlock(name) {
|
||||
var colspan = Natural(1)
|
||||
set(value) {
|
||||
attributes["colspan"] = value.toString()
|
||||
}
|
||||
var rowspan = Natural(1)
|
||||
set(value) {
|
||||
attributes["rowspan"] = value.toString()
|
||||
}
|
||||
|
||||
fun th(rowspan: Natural = Natural(1), colspan: Natural = Natural(1), init: TableHeadInfo.() -> Unit) {
|
||||
val element = initTag(TableHeadInfo(), init)
|
||||
element.rowspan = rowspan
|
||||
element.colspan = colspan
|
||||
}
|
||||
|
||||
fun td(rowspan: Natural = Natural(1), colspan: Natural = Natural(1), init: TableDataInfo.() -> Unit) {
|
||||
val element = initTag(TableDataInfo(), init)
|
||||
element.rowspan = rowspan
|
||||
element.colspan = colspan
|
||||
}
|
||||
}
|
||||
|
||||
class TableRow : TableRowTag("tr")
|
||||
class TableHeadInfo : TableRowTag("th")
|
||||
class TableDataInfo : TableRowTag("td")
|
||||
|
||||
fun html(init: HTML.() -> Unit): HTML {
|
||||
val html = HTML()
|
||||
html.init()
|
||||
return html
|
||||
}
|
||||
|
||||
// Report render to html format.
|
||||
class HTMLRender: Render() {
|
||||
override val name: String
|
||||
get() = "html"
|
||||
|
||||
override fun render (report: SummaryBenchmarksReport, onlyChanges: Boolean) =
|
||||
html {
|
||||
head {
|
||||
title { +"Benchmarks report" }
|
||||
|
||||
// Links to bootstrap files.
|
||||
link {
|
||||
attributes["href"] = "https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css"
|
||||
attributes["rel"] = "stylesheet"
|
||||
}
|
||||
script {
|
||||
attributes["src"] = "https://code.jquery.com/jquery-3.3.1.slim.min.js"
|
||||
}
|
||||
script {
|
||||
attributes["src"] = "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"
|
||||
}
|
||||
script {
|
||||
attributes["src"] = "https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
header("navbar navbar-expand navbar-dark flex-column flex-md-row bd-navbar") {
|
||||
attributes["style"] = "background-color:#161616;"
|
||||
img("https://dashboard.snapcraft.io/site_media/appmedia/2018/04/256px-kotlin-logo-svg.png") {
|
||||
attributes["style"] = "width:60px;height:60px;"
|
||||
}
|
||||
|
||||
span("navbar-brand mb-0 h1") { +"Benchmarks report" }
|
||||
}
|
||||
div("container-fluid") {
|
||||
p{}
|
||||
renderEnvironmentTable(report.environments)
|
||||
renderCompilerTable(report.compilers)
|
||||
hr {}
|
||||
renderStatusSummary(report)
|
||||
hr {}
|
||||
renderPerformanceSummary(report)
|
||||
renderPerformanceDetails(report, onlyChanges)
|
||||
}
|
||||
}
|
||||
}.toString()
|
||||
|
||||
private fun TableRowTag.formatComparedTableData(data: String, compareToData: String?) {
|
||||
td {
|
||||
compareToData?. let {
|
||||
// Highlight changed data.
|
||||
if (it != data)
|
||||
attributes["bgcolor"] = "yellow"
|
||||
}
|
||||
if (data.isEmpty()) {
|
||||
+"-"
|
||||
} else {
|
||||
+data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TableTag.renderEnvironment(environment: Environment, name: String, compareTo: Environment? = null) {
|
||||
tbody {
|
||||
tr {
|
||||
th {
|
||||
attributes["scope"] = "row"
|
||||
+name
|
||||
}
|
||||
formatComparedTableData(environment.machine.os, compareTo?.machine?.os)
|
||||
formatComparedTableData(environment.machine.cpu, compareTo?.machine?.cpu)
|
||||
formatComparedTableData(environment.jdk.version, compareTo?.jdk?.version)
|
||||
formatComparedTableData(environment.jdk.vendor, compareTo?.jdk?.vendor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BodyTag.renderEnvironmentTable(environments: Pair<Environment, Environment?>) {
|
||||
h4 { +"Environment" }
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-bordered table-hover"
|
||||
attributes["style"] = "width:initial;"
|
||||
val firstEnvironment = environments.first
|
||||
val secondEnvironment = environments.second
|
||||
// Table header.
|
||||
thead {
|
||||
tr {
|
||||
th(rowspan = Natural(2)) { +"Run" }
|
||||
th(colspan = Natural(2)) { +"Machine" }
|
||||
th(colspan = Natural(2)) { +"JDK" }
|
||||
}
|
||||
tr {
|
||||
th { + "OS" }
|
||||
th { + "CPU" }
|
||||
th { + "Version"}
|
||||
th { + "Vendor"}
|
||||
}
|
||||
}
|
||||
renderEnvironment(firstEnvironment, "First")
|
||||
secondEnvironment?. let { renderEnvironment(it, "Second", firstEnvironment) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun TableTag.renderCompiler(compiler: Compiler, name: String, compareTo: Compiler? = null) {
|
||||
tbody {
|
||||
tr {
|
||||
th {
|
||||
attributes["scope"] = "row"
|
||||
+name
|
||||
}
|
||||
formatComparedTableData(compiler.backend.type.type, compareTo?.backend?.type?.type)
|
||||
formatComparedTableData(compiler.backend.version, compareTo?.backend?.version)
|
||||
formatComparedTableData(compiler.backend.flags.joinToString(), compareTo?.backend?.flags?.joinToString())
|
||||
formatComparedTableData(compiler.kotlinVersion, compareTo?.kotlinVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BodyTag.renderCompilerTable(compilers: Pair<Compiler, Compiler?>) {
|
||||
h4 { +"Compiler" }
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-bordered table-hover"
|
||||
attributes["style"] = "width:initial;"
|
||||
val firstCompiler = compilers.first
|
||||
val secondCompiler = compilers.second
|
||||
|
||||
// Table header.
|
||||
thead {
|
||||
tr {
|
||||
th(rowspan = Natural(2)) { +"Run" }
|
||||
th(colspan = Natural(3)) { +"Backend" }
|
||||
th(rowspan = Natural(2)) { +"Kotlin" }
|
||||
}
|
||||
tr {
|
||||
th { + "Type" }
|
||||
th { + "Version" }
|
||||
th { + "Flags"}
|
||||
}
|
||||
}
|
||||
renderCompiler(firstCompiler, "First")
|
||||
secondCompiler?. let { renderCompiler(it, "Second", firstCompiler) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun TableBlock.renderBucketInfo(bucket: Collection<Any>, name: String) {
|
||||
if (!bucket.isEmpty()) {
|
||||
tr {
|
||||
th {
|
||||
attributes["scope"] = "row"
|
||||
+name
|
||||
}
|
||||
td {
|
||||
+"${bucket.size}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BodyTag.renderCollapsedData(name: String, isCollapsed: Boolean = false, colorStyle: String = "",
|
||||
init: BodyTag.() -> Unit) {
|
||||
val show = if (!isCollapsed) "show" else ""
|
||||
val tagName = name.replace(' ', '_')
|
||||
div("accordion") {
|
||||
div("card") {
|
||||
attributes["style"] = "border-bottom: 1px solid rgba(0,0,0,.125);"
|
||||
div("card-header") {
|
||||
attributes["id"] = "heading"
|
||||
attributes["style"] = "padding: 0;$colorStyle"
|
||||
button("btn btn-link") {
|
||||
attributes["data-toggle"] = "collapse"
|
||||
attributes["data-target"] = "#$tagName"
|
||||
+name
|
||||
}
|
||||
}
|
||||
|
||||
div("collapse $show") {
|
||||
attributes["id"] = tagName
|
||||
div("accordion-inner") {
|
||||
init()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TableTag.renderTableFromList(list: List<String>, name: String) {
|
||||
if (!list.isEmpty()) {
|
||||
thead {
|
||||
tr {
|
||||
th { +name }
|
||||
}
|
||||
}
|
||||
list.forEach {
|
||||
tbody {
|
||||
tr {
|
||||
td { +it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BodyTag.renderStatusSummary(report: SummaryBenchmarksReport) {
|
||||
h4 { +"Status Summary" }
|
||||
val failedBenchmarks = report.failedBenchmarks
|
||||
if (failedBenchmarks.isEmpty()) {
|
||||
div("alert alert-success") {
|
||||
attributes["role"] = "alert"
|
||||
+"All benchmarks passed!"
|
||||
}
|
||||
} else {
|
||||
div("alert alert-danger") {
|
||||
attributes["role"] = "alert"
|
||||
+"There are failed benchmarks!"
|
||||
}
|
||||
}
|
||||
|
||||
val benchmarksWithChangedStatus = report.getBenchmarksWithChangedStatus()
|
||||
val newFailures = benchmarksWithChangedStatus
|
||||
.filter { it.current == BenchmarkResult.Status.FAILED }
|
||||
val newPasses = benchmarksWithChangedStatus
|
||||
.filter { it.current == BenchmarkResult.Status.PASSED }
|
||||
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-striped table-hover"
|
||||
attributes["style"] = "width:initial; font-size: 11pt;"
|
||||
thead {
|
||||
tr {
|
||||
th { +"Status Group" }
|
||||
th { +"#" }
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
renderBucketInfo(failedBenchmarks, "Failed (total)")
|
||||
renderBucketInfo(newFailures, "New Failures")
|
||||
renderBucketInfo(newPasses, "New Passes")
|
||||
renderBucketInfo(report.addedBenchmarks, "Added")
|
||||
renderBucketInfo(report.removedBenchmarks, "Removed")
|
||||
}
|
||||
tfoot {
|
||||
tr {
|
||||
th { +"Total becnhmarks number" }
|
||||
th { +"${report.benchmarksNumber}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!failedBenchmarks.isEmpty()) {
|
||||
renderCollapsedData("Failures", colorStyle = "background-color: lightpink") {
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-striped table-hover"
|
||||
attributes["style"] = "width:initial; font-size: 11pt;"
|
||||
val newFailuresList = newFailures.map { it.field }
|
||||
renderTableFromList(newFailuresList, "New Failures")
|
||||
|
||||
val existingFailures = failedBenchmarks.filter { it !in newFailuresList }
|
||||
renderTableFromList(existingFailures, "Existing Failures")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!newPasses.isEmpty()) {
|
||||
renderCollapsedData("New Passes", colorStyle = "background-color: lightgreen") {
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-striped table-hover"
|
||||
attributes["style"] = "width:initial; font-size: 11pt;"
|
||||
renderTableFromList(newPasses.map { it.field }, "New Passes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!report.addedBenchmarks.isEmpty()) {
|
||||
renderCollapsedData("Added", true) {
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-striped table-hover"
|
||||
attributes["style"] = "width:initial; font-size: 11pt;"
|
||||
renderTableFromList(report.addedBenchmarks, "Added benchmarks")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!report.removedBenchmarks.isEmpty()) {
|
||||
renderCollapsedData("Removed", true) {
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-striped table-hover"
|
||||
attributes["style"] = "width:initial; font-size: 11pt;"
|
||||
renderTableFromList(report.removedBenchmarks, "Removed benchmarks")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BodyTag.renderPerformanceSummary(report: SummaryBenchmarksReport) {
|
||||
if (!report.improvements.isEmpty() || !report.regressions.isEmpty()) {
|
||||
h4 { +"Performance Summary" }
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-striped table-hover"
|
||||
attributes["style"] = "width:initial;"
|
||||
thead {
|
||||
tr {
|
||||
th { +"Change" }
|
||||
th { +"#" }
|
||||
th { +"Maximum" }
|
||||
th { +"Geometric mean" }
|
||||
}
|
||||
}
|
||||
val maximumRegression = report.maximumRegression
|
||||
val maximumImprovement = report.maximumImprovement
|
||||
val regressionsGeometricMean = report.regressionsGeometricMean
|
||||
val improvementsGeometricMean = report.improvementsGeometricMean
|
||||
tbody {
|
||||
if (!report.regressions.isEmpty()) {
|
||||
tr {
|
||||
th { +"Regressions" }
|
||||
td { +"${report.regressions.size}" }
|
||||
td {
|
||||
attributes["bgcolor"] = ColoredCell(
|
||||
maximumRegression/maxOf(maximumRegression, abs(maximumImprovement)))
|
||||
.backgroundStyle
|
||||
+formatValue(maximumRegression, true)
|
||||
}
|
||||
td {
|
||||
attributes["bgcolor"] = ColoredCell(
|
||||
regressionsGeometricMean/maxOf(regressionsGeometricMean,
|
||||
abs(improvementsGeometricMean)))
|
||||
.backgroundStyle
|
||||
+formatValue(report.regressionsGeometricMean, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!report.improvements.isEmpty()) {
|
||||
tr {
|
||||
th { +"Improvements" }
|
||||
td { +"${report.improvements.size}" }
|
||||
td {
|
||||
attributes["bgcolor"] = ColoredCell(
|
||||
maximumImprovement/maxOf(maximumRegression, abs(maximumImprovement)))
|
||||
.backgroundStyle
|
||||
+formatValue(report.maximumImprovement, true)
|
||||
}
|
||||
td {
|
||||
attributes["bgcolor"] = ColoredCell(
|
||||
improvementsGeometricMean/maxOf(regressionsGeometricMean,
|
||||
abs(improvementsGeometricMean)))
|
||||
.backgroundStyle
|
||||
+formatValue(report.improvementsGeometricMean, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TableBlock.renderBenchmarksDetails(fullSet: Map<String, SummaryBenchmark>,
|
||||
bucket: Map<String, ScoreChange>? = null, rowStyle: String? = null) {
|
||||
if (bucket != null && !bucket.isEmpty()) {
|
||||
// Find max ratio.
|
||||
val maxRatio = bucket.values.map { it.second.mean }.maxOrNull()!!
|
||||
// There are changes in performance.
|
||||
// Output changed benchmarks.
|
||||
for ((name, change) in bucket) {
|
||||
tr {
|
||||
rowStyle?. let {
|
||||
attributes["style"] = rowStyle
|
||||
}
|
||||
th { +name }
|
||||
td { +"${fullSet.getValue(name).first?.description}" }
|
||||
td { +"${fullSet.getValue(name).second?.description}" }
|
||||
td {
|
||||
attributes["bgcolor"] = ColoredCell(if (bucket.values.first().first.mean == 0.0) null
|
||||
else change.first.mean / abs(bucket.values.first().first.mean))
|
||||
.backgroundStyle
|
||||
+"${change.first.description + " %"}"
|
||||
}
|
||||
td {
|
||||
val scaledRatio = if (maxRatio == 0.0) null else change.second.mean / maxRatio
|
||||
attributes["bgcolor"] = ColoredCell(scaledRatio,
|
||||
borderPositive = { cellValue -> cellValue > 1.0 / maxRatio }).backgroundStyle
|
||||
+"${change.second.description}"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (bucket == null) {
|
||||
// Output all values without performance changes.
|
||||
val placeholder = "-"
|
||||
for ((name, value) in fullSet) {
|
||||
tr {
|
||||
th { +name }
|
||||
td { +"${value.first?.description ?: placeholder}" }
|
||||
td { +"${value.second?.description ?: placeholder}" }
|
||||
td { +placeholder }
|
||||
td { +placeholder }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BodyTag.renderPerformanceDetails(report: SummaryBenchmarksReport, onlyChanges: Boolean) {
|
||||
if (onlyChanges) {
|
||||
if (report.regressions.isEmpty() && report.improvements.isEmpty()) {
|
||||
div("alert alert-success") {
|
||||
attributes["role"] = "alert"
|
||||
+"All becnhmarks are stable!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
attributes["id"] = "result"
|
||||
attributes["class"] = "table table-striped table-bordered"
|
||||
thead {
|
||||
tr {
|
||||
th { +"Benchmark" }
|
||||
th { +"First score" }
|
||||
th { +"Second score" }
|
||||
th { +"Percent" }
|
||||
th { +"Ratio" }
|
||||
}
|
||||
}
|
||||
val geoMeanChangeMap = report.geoMeanScoreChange?.
|
||||
let { mapOf(report.geoMeanBenchmark.first!!.name to report.geoMeanScoreChange!!) }
|
||||
|
||||
tbody {
|
||||
renderBenchmarksDetails(
|
||||
mutableMapOf(report.geoMeanBenchmark.first!!.name to report.geoMeanBenchmark),
|
||||
geoMeanChangeMap, "border-bottom: 2.3pt solid black; border-top: 2.3pt solid black")
|
||||
renderBenchmarksDetails(report.mergedReport, report.regressions)
|
||||
renderBenchmarksDetails(report.mergedReport, report.improvements)
|
||||
if (!onlyChanges) {
|
||||
// Print all remaining results.
|
||||
renderBenchmarksDetails(report.mergedReport.filter { it.key !in report.regressions.keys &&
|
||||
it.key !in report.improvements.keys })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Color(val red: Double, val green: Double, val blue: Double) {
|
||||
operator fun times(coefficient: Double) =
|
||||
Color(red * coefficient, green * coefficient, blue * coefficient)
|
||||
|
||||
operator fun plus(other: Color) =
|
||||
Color(red + other.red, green + other.green, blue + other.blue)
|
||||
|
||||
override fun toString() =
|
||||
"#" + buildString {
|
||||
listOf(red, green, blue).forEach {
|
||||
append(clamp((it * 255).toInt(), 0, 255).toString(16).padStart(2, '0'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ColoredCell(val scaledValue: Double?, val reverse: Boolean = false,
|
||||
val borderPositive: (cellValue: Double) -> Boolean = { cellValue -> cellValue > 0 }) {
|
||||
val value: Double
|
||||
val neutralColor = Color(1.0,1.0 , 1.0)
|
||||
val negativeColor = Color(0.0, 1.0, 0.0)
|
||||
val positiveColor = Color(1.0, 0.0, 0.0)
|
||||
|
||||
init {
|
||||
value = scaledValue?.let { if (abs(scaledValue) <= 1.0) scaledValue else error ("Value should be scaled in range [-1.0; 1.0]") }
|
||||
?: 0.0
|
||||
}
|
||||
|
||||
val backgroundStyle: String
|
||||
get() = scaledValue?.let { getColor().toString() } ?: ""
|
||||
|
||||
fun getColor(): Color {
|
||||
val currentValue = clamp(value, -1.0, 1.0)
|
||||
val cellValue = if (reverse) -currentValue else currentValue
|
||||
val baseColor = if (borderPositive(cellValue)) positiveColor else negativeColor
|
||||
// Smooth mapping to put first 20% of change into 50% of range,
|
||||
// although really we should compensate for luma.
|
||||
val color = sin((abs(cellValue).pow(.477)) * kotlin.math.PI * .5)
|
||||
return linearInterpolation(neutralColor, baseColor, color)
|
||||
}
|
||||
|
||||
private fun linearInterpolation(a: Color, b: Color, coefficient: Double): Color {
|
||||
val reversedCoefficient = 1.0 - coefficient
|
||||
return a * reversedCoefficient + b * coefficient
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.renders
|
||||
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.*
|
||||
|
||||
// Report render to text format.
|
||||
class JsonResultsRender: Render() {
|
||||
override val name: String
|
||||
get() = "json"
|
||||
|
||||
override fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean) =
|
||||
report.getBenchmarksReport().toJson()
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.renders
|
||||
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.BenchmarkResult
|
||||
|
||||
// Report render to text format.
|
||||
class MetricResultsRender: Render() {
|
||||
override val name: String
|
||||
get() = "metrics"
|
||||
|
||||
override fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean): String {
|
||||
val results = report.mergedReport.map { entry ->
|
||||
buildString {
|
||||
val metric = entry.value.first!!.metric
|
||||
append("{ \"benchmarkName\": \"${entry.key.removeSuffix(metric.suffix)}\",")
|
||||
append("\"metric\": \"${metric}\",")
|
||||
append("\"value\": \"${entry.value.first!!.score}\" }")
|
||||
}
|
||||
}.joinToString(", ")
|
||||
return "[ $results ]"
|
||||
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.renders
|
||||
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.*
|
||||
|
||||
import kotlin.math.abs
|
||||
|
||||
// Base class for printing report in different formats.
|
||||
abstract class Render {
|
||||
|
||||
companion object {
|
||||
fun getRenderByName(name: String) =
|
||||
when (name) {
|
||||
"text" -> TextRender()
|
||||
"html" -> HTMLRender()
|
||||
"teamcity" -> TeamCityStatisticsRender()
|
||||
"statistics" -> StatisticsRender()
|
||||
"metrics" -> MetricResultsRender()
|
||||
else -> error("Unknown render $name")
|
||||
}
|
||||
}
|
||||
|
||||
abstract val name: String
|
||||
|
||||
abstract fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean = false): String
|
||||
|
||||
// Print report using render.
|
||||
fun print(report: SummaryBenchmarksReport, onlyChanges: Boolean = false, outputFile: String? = null) {
|
||||
val content = render(report, onlyChanges)
|
||||
outputFile?.let {
|
||||
writeToFile(outputFile, content)
|
||||
} ?: println(content)
|
||||
}
|
||||
|
||||
protected fun formatValue(number: Double, isPercent: Boolean = false): String =
|
||||
if (isPercent) number.format(2) + "%" else number.format()
|
||||
}
|
||||
|
||||
// Report render to text format.
|
||||
class TextRender: Render() {
|
||||
override val name: String
|
||||
get() = "text"
|
||||
|
||||
private val content = StringBuilder()
|
||||
private val headerSeparator = "================="
|
||||
private val wideColumnWidth = 50
|
||||
private val standardColumnWidth = 25
|
||||
|
||||
private fun append(text: String = "") {
|
||||
content.append("$text\n")
|
||||
}
|
||||
|
||||
override fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean): String {
|
||||
renderEnvChanges(report.envChanges, "Environment")
|
||||
renderEnvChanges(report.kotlinChanges, "Compiler")
|
||||
renderStatusSummary(report)
|
||||
renderStatusChangesDetails(report.getBenchmarksWithChangedStatus())
|
||||
renderPerformanceSummary(report)
|
||||
renderPerformanceDetails(report, onlyChanges)
|
||||
return content.toString()
|
||||
}
|
||||
|
||||
private fun printBucketInfo(bucket: Collection<Any>, name: String) {
|
||||
if (!bucket.isEmpty()) {
|
||||
append("$name: ${bucket.size}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> printStatusChangeInfo(bucket: List<FieldChange<T>>, name: String) {
|
||||
if (!bucket.isEmpty()) {
|
||||
append("$name:")
|
||||
for (change in bucket) {
|
||||
append(change.renderAsText())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun renderEnvChanges(envChanges: List<FieldChange<String>>, bucketName: String) {
|
||||
if (!envChanges.isEmpty()) {
|
||||
append(ChangeReport(bucketName, envChanges).renderAsTextReport())
|
||||
}
|
||||
}
|
||||
|
||||
fun renderStatusChangesDetails(benchmarksWithChangedStatus: List<FieldChange<BenchmarkResult.Status>>) {
|
||||
if (!benchmarksWithChangedStatus.isEmpty()) {
|
||||
append("Changes in status")
|
||||
append(headerSeparator)
|
||||
printStatusChangeInfo(benchmarksWithChangedStatus
|
||||
.filter { it.current == BenchmarkResult.Status.FAILED }, "New failures")
|
||||
printStatusChangeInfo(benchmarksWithChangedStatus
|
||||
.filter { it.current == BenchmarkResult.Status.PASSED }, "New passes")
|
||||
append()
|
||||
}
|
||||
}
|
||||
|
||||
fun renderStatusSummary(report: SummaryBenchmarksReport) {
|
||||
append("Status summary")
|
||||
append(headerSeparator)
|
||||
|
||||
val failedBenchmarks = report.failedBenchmarks
|
||||
val addedBenchmarks = report.addedBenchmarks
|
||||
val removedBenchmarks = report.removedBenchmarks
|
||||
if (failedBenchmarks.isEmpty()) {
|
||||
append("All benchmarks passed!")
|
||||
}
|
||||
if (!failedBenchmarks.isEmpty() || !addedBenchmarks.isEmpty() || !removedBenchmarks.isEmpty()) {
|
||||
printBucketInfo(failedBenchmarks, "Failed benchmarks")
|
||||
printBucketInfo(addedBenchmarks, "Added benchmarks")
|
||||
printBucketInfo(removedBenchmarks, "Removed benchmarks")
|
||||
}
|
||||
append("Total becnhmarks number: ${report.benchmarksNumber}")
|
||||
append()
|
||||
}
|
||||
|
||||
fun renderPerformanceSummary(report: SummaryBenchmarksReport) {
|
||||
if (!report.regressions.isEmpty() || !report.improvements.isEmpty()) {
|
||||
append("Performance summary")
|
||||
append(headerSeparator)
|
||||
if (!report.regressions.isEmpty()) {
|
||||
append("Regressions: Maximum = ${formatValue(report.maximumRegression, true)}," +
|
||||
" Geometric mean = ${formatValue(report.regressionsGeometricMean, true)}")
|
||||
}
|
||||
if (!report.improvements.isEmpty()) {
|
||||
append("Improvements: Maximum = ${formatValue(report.maximumImprovement, true)}," +
|
||||
" Geometric mean = ${formatValue(report.improvementsGeometricMean, true)}")
|
||||
}
|
||||
append()
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatColumn(content:String, isWide: Boolean = false): String =
|
||||
content.padEnd(if (isWide) wideColumnWidth else standardColumnWidth, ' ')
|
||||
|
||||
private fun printBenchmarksDetails(fullSet: Map<String, SummaryBenchmark>,
|
||||
bucket: Map<String, ScoreChange>? = null) {
|
||||
val placeholder = "-"
|
||||
if (bucket != null) {
|
||||
// There are changes in performance.
|
||||
// Output changed benchmarks.
|
||||
for ((name, change) in bucket) {
|
||||
append(formatColumn(name, true) +
|
||||
formatColumn(fullSet.getValue(name).first?.description ?: placeholder) +
|
||||
formatColumn(fullSet.getValue(name).second?.description ?: placeholder) +
|
||||
formatColumn(change.first.description + " %") +
|
||||
formatColumn(change.second.description))
|
||||
}
|
||||
} else {
|
||||
// Output all values without performance changes.
|
||||
for ((name, value) in fullSet) {
|
||||
append(formatColumn(name, true) +
|
||||
formatColumn(value.first?.description ?: placeholder) +
|
||||
formatColumn(value.second?.description ?: placeholder) +
|
||||
formatColumn(placeholder) +
|
||||
formatColumn(placeholder))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun printTableLineSeparator(tableWidth: Int) =
|
||||
append("${"-".padEnd(tableWidth, '-')}")
|
||||
|
||||
private fun printPerformanceTableHeader(): Int {
|
||||
val wideColumns = listOf(formatColumn("Benchmark", true))
|
||||
val standardColumns = listOf(formatColumn("First score"),
|
||||
formatColumn("Second score"),
|
||||
formatColumn("Percent"),
|
||||
formatColumn("Ratio"))
|
||||
val tableWidth = wideColumnWidth * wideColumns.size + standardColumnWidth * standardColumns.size
|
||||
append("${wideColumns.joinToString(separator = "")}${standardColumns.joinToString(separator = "")}")
|
||||
printTableLineSeparator(tableWidth)
|
||||
return tableWidth
|
||||
}
|
||||
|
||||
fun renderPerformanceDetails(report: SummaryBenchmarksReport, onlyChanges: Boolean = false) {
|
||||
append("Performance details")
|
||||
append(headerSeparator)
|
||||
|
||||
if (onlyChanges) {
|
||||
if (report.regressions.isEmpty() && report.improvements.isEmpty()) {
|
||||
append("All becnhmarks are stable.")
|
||||
}
|
||||
}
|
||||
|
||||
val tableWidth = printPerformanceTableHeader()
|
||||
// Print geometric mean.
|
||||
val geoMeanChangeMap = report.geoMeanScoreChange?.
|
||||
let { mapOf(report.geoMeanBenchmark.first!!.name to report.geoMeanScoreChange!!) }
|
||||
printBenchmarksDetails(
|
||||
mutableMapOf(report.geoMeanBenchmark.first!!.name to report.geoMeanBenchmark),
|
||||
geoMeanChangeMap)
|
||||
printTableLineSeparator(tableWidth)
|
||||
printBenchmarksDetails(report.mergedReport, report.regressions)
|
||||
printBenchmarksDetails(report.mergedReport, report.improvements)
|
||||
if (!onlyChanges) {
|
||||
// Print all remaining results.
|
||||
printBenchmarksDetails(report.mergedReport.filter { it.key !in report.regressions.keys &&
|
||||
it.key !in report.improvements.keys })
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.renders
|
||||
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.BenchmarkResult
|
||||
|
||||
enum class Status {
|
||||
FAILED, FIXED, IMPROVED, REGRESSED, STABLE, UNSTABLE
|
||||
}
|
||||
|
||||
// Report render to short summary statistics.
|
||||
class StatisticsRender: Render() {
|
||||
override val name: String
|
||||
get() = "statistics"
|
||||
|
||||
private var content = StringBuilder()
|
||||
|
||||
override fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean): String {
|
||||
val benchmarksWithChangedStatus = report.getBenchmarksWithChangedStatus()
|
||||
val newPasses = benchmarksWithChangedStatus
|
||||
.filter { it.current == BenchmarkResult.Status.PASSED }
|
||||
val newFailures = benchmarksWithChangedStatus
|
||||
.filter { it.current == BenchmarkResult.Status.FAILED }
|
||||
if (report.failedBenchmarks.isNotEmpty()) {
|
||||
content.append("failed: ${report.failedBenchmarks.size}\n")
|
||||
}
|
||||
val status = when {
|
||||
newFailures.isNotEmpty() -> {
|
||||
content.append("new failures: ${newFailures.size}\n")
|
||||
Status.FAILED
|
||||
}
|
||||
newPasses.isNotEmpty() -> {
|
||||
content.append("new passes: ${newPasses.size}\n")
|
||||
Status.FIXED
|
||||
}
|
||||
report.improvements.isNotEmpty() && report.regressions.isNotEmpty() -> {
|
||||
content.append("regressions: ${report.regressions.size}\nimprovements: ${report.improvements.size}")
|
||||
Status.UNSTABLE
|
||||
}
|
||||
report.improvements.isNotEmpty() && report.regressions.isEmpty() -> {
|
||||
content.append("improvements: ${report.improvements.size}")
|
||||
Status.IMPROVED
|
||||
}
|
||||
report.improvements.isEmpty() && report.regressions.isNotEmpty() -> {
|
||||
content.append("regressions: ${report.regressions.size}")
|
||||
Status.REGRESSED
|
||||
}
|
||||
else -> Status.STABLE
|
||||
}
|
||||
return """
|
||||
status: $status
|
||||
total: ${report.benchmarksNumber}
|
||||
""".trimIndent() + "\n$content"
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.renders
|
||||
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.BenchmarkResult
|
||||
import org.jetbrains.report.MeanVarianceBenchmark
|
||||
|
||||
// Report render to text format.
|
||||
class TeamCityStatisticsRender: Render() {
|
||||
override val name: String
|
||||
get() = "teamcity"
|
||||
|
||||
private var content = StringBuilder()
|
||||
|
||||
override fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean): String {
|
||||
val currentDurations = report.currentBenchmarksDuration
|
||||
|
||||
content.append("##teamcity[testSuiteStarted name='Benchmarks']\n")
|
||||
|
||||
// For current benchmarks print score as TeamCity Test Metadata
|
||||
report.currentMeanVarianceBenchmarks.forEach { benchmark ->
|
||||
renderBenchmark(benchmark, currentDurations[benchmark.name]!!)
|
||||
renderSummaryBecnhmarkValue(benchmark)
|
||||
}
|
||||
content.append("##teamcity[testSuiteFinished name='Benchmarks']\n")
|
||||
|
||||
// Report geometric mean as build statistic value
|
||||
renderGeometricMean(report.geoMeanBenchmark.first!!)
|
||||
|
||||
return content.toString()
|
||||
}
|
||||
|
||||
private fun renderSummaryBecnhmarkValue(benchmark: MeanVarianceBenchmark) {
|
||||
content.append("##teamcity[testMetadata testName='${benchmark.name}' name='Mean'" +
|
||||
" type='number' value='${benchmark.score}']\n")
|
||||
content.append("##teamcity[testMetadata testName='${benchmark.name}' name='Variance'" +
|
||||
" type='number' value='${benchmark.variance}']\n")
|
||||
}
|
||||
|
||||
// Produce benchmark as test in TeamCity
|
||||
private fun renderBenchmark(benchmark: BenchmarkResult , duration: Double) {
|
||||
content.append("##teamcity[testStarted name='${benchmark.name}']\n")
|
||||
if (benchmark.status == BenchmarkResult.Status.FAILED) {
|
||||
content.append("##teamcity[testFailed name='${benchmark.name}']\n")
|
||||
}
|
||||
// test_duration_in_milliseconds is set for TeamCity
|
||||
content.append("##teamcity[testFinished name='${benchmark.name}' duration='${(duration / 1000).toInt()}']\n")
|
||||
}
|
||||
|
||||
private fun renderGeometricMean(geoMeanBenchmark: MeanVarianceBenchmark) {
|
||||
content.append("##teamcity[buildStatisticValue key='Geometric mean' value='${geoMeanBenchmark.score}']\n")
|
||||
content.append("##teamcity[buildStatisticValue key='Geometric mean variance' value='${geoMeanBenchmark.variance}']\n")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
headers = curl/curl.h
|
||||
headerFilter = curl/*
|
||||
linkerOpts.osx = -L/opt/local/lib -L/usr/local/opt/curl/lib -lcurl
|
||||
linkerOpts.linux = -L/usr/lib64 -L/usr/lib/x86_64-linux-gnu -lcurl
|
||||
linkerOpts.mingw = -lcurl
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
import kotlin.test.*
|
||||
import kotlin.math.abs
|
||||
import org.jetbrains.report.BenchmarkResult
|
||||
import org.jetbrains.report.MeanVarianceBenchmark
|
||||
|
||||
class AnalyzerTests {
|
||||
private val eps = 0.000001
|
||||
|
||||
private fun createMeanVarianceBenchmarks(): Pair<MeanVarianceBenchmark, MeanVarianceBenchmark> {
|
||||
val first = MeanVarianceBenchmark("testBenchmark", BenchmarkResult.Status.PASSED, 9.0, BenchmarkResult.Metric.EXECUTION_TIME, 9.0, 10, 10, 0.0001)
|
||||
val second = MeanVarianceBenchmark("testBenchmark", BenchmarkResult.Status.PASSED, 10.0, BenchmarkResult.Metric.EXECUTION_TIME, 10.0, 10, 10, 0.0001)
|
||||
|
||||
return Pair(first, second)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGeoMean() {
|
||||
val numbers = listOf(4.0, 6.0, 9.0)
|
||||
val value = geometricMean(numbers)
|
||||
val expected = 6.0
|
||||
assertTrue(abs(value - expected) < eps)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testComputeMeanVariance() {
|
||||
val numbers = listOf(10.1, 10.2, 10.3)
|
||||
val value = computeMeanVariance(numbers)
|
||||
val expectedMean = 10.2
|
||||
val expectedVariance = 0.07872455
|
||||
assertTrue(abs(value.mean - expectedMean) < eps)
|
||||
assertTrue(abs(value.variance - expectedVariance) < eps)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun calcPercentageDiff() {
|
||||
val inputs = createMeanVarianceBenchmarks()
|
||||
|
||||
val percent = inputs.first.calcPercentageDiff(inputs.second)
|
||||
val expectedMean = -9.99809998
|
||||
val expectedVariance = 0.0021
|
||||
assertTrue(abs(percent.mean - expectedMean) < eps)
|
||||
//assertTrue(abs(percent.variance - expectedVariance) < eps)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun calcRatio() {
|
||||
val inputs = createMeanVarianceBenchmarks()
|
||||
|
||||
val ratio = inputs.first.calcRatio(inputs.second)
|
||||
val expectedMean = 0.9
|
||||
val expectedVariance = 0.00001899
|
||||
|
||||
assertTrue(abs(ratio.mean - expectedMean) < eps)
|
||||
assertTrue(abs(ratio.variance - expectedVariance) < eps)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user