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:
Stanislav Erokhin
2020-10-27 21:00:28 +03:00
parent 91e4162dad
commit f624800b84
2830 changed files with 0 additions and 0 deletions
@@ -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
@@ -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}")
}
@@ -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() } }
}
@@ -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
}
}
@@ -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
}
}
}
@@ -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()
}
@@ -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 ]"
}
}
@@ -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 })
}
}
}
@@ -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"
}
}
@@ -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)
}
}