Use ElasticSearch as data storage for performance monitoring system (#4302)
This commit is contained in:
@@ -65,7 +65,7 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sourceSets["main"].withConvention(KotlinSourceSet::class) {
|
sourceSets["main"].withConvention(KotlinSourceSet::class) {
|
||||||
kotlin.srcDir("$projectDir/../tools/benchmarks/shared/src")
|
kotlin.srcDir("$projectDir/../tools/benchmarks/shared/src/main/kotlin/report")
|
||||||
}
|
}
|
||||||
|
|
||||||
gradlePlugin {
|
gradlePlugin {
|
||||||
|
|||||||
@@ -6,46 +6,31 @@
|
|||||||
package org.jetbrains.kotlin
|
package org.jetbrains.kotlin
|
||||||
|
|
||||||
import org.gradle.api.DefaultTask
|
import org.gradle.api.DefaultTask
|
||||||
import org.gradle.api.tasks.Input
|
|
||||||
import org.gradle.api.tasks.TaskAction
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
|
||||||
import org.jetbrains.report.json.*
|
|
||||||
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.OutputStreamWriter
|
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
|
import java.io.FileInputStream
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
|
import java.io.OutputStreamWriter
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.Properties
|
import java.util.*
|
||||||
|
|
||||||
typealias performanceAdditionalResult = Triple<String, String, String>
|
|
||||||
/**
|
/**
|
||||||
* Task to produce regressions report and send it to slack. Requires a report with current benchmarks result
|
* Task to save benchmarks results on server.
|
||||||
* and path to analyzer tool
|
|
||||||
*
|
*
|
||||||
* @property currentBenchmarksReportFile path to file with becnhmarks result
|
|
||||||
* @property analyzer path to analyzer tool
|
|
||||||
* @property bundleSize size of build
|
* @property bundleSize size of build
|
||||||
* @property onlyBranch register only builds for branch
|
* @property onlyBranch register only builds for branch
|
||||||
|
* @property fileWithResult json file with benchmarks run results
|
||||||
*/
|
*/
|
||||||
open class BuildRegister : DefaultTask() {
|
open class BuildRegister : DefaultTask() {
|
||||||
@Input
|
|
||||||
lateinit var currentBenchmarksReportFile: String
|
|
||||||
@Input
|
|
||||||
lateinit var analyzer: String
|
|
||||||
|
|
||||||
var onlyBranch: String? = null
|
var onlyBranch: String? = null
|
||||||
|
|
||||||
var bundleSize: Int? = null
|
var bundleSize: Int? = null
|
||||||
|
var fileWithResult: String = "nativeReport.json"
|
||||||
|
|
||||||
val buildInfoTokens: Int = 4
|
|
||||||
val additionalInfoTokens: Int = 3
|
|
||||||
val compileTimeSamplesNumber: Int = 2
|
|
||||||
val buildNumberRegex: Regex = "\\d+(\\.\\d+)+(-M\\d)?-(\\w+)-\\d+".toRegex()
|
|
||||||
val performanceServer = "https://kotlin-native-perf-summary.labs.jb.gg"
|
val performanceServer = "https://kotlin-native-perf-summary.labs.jb.gg"
|
||||||
|
|
||||||
private fun sendPostRequest(url: String, body: String) : String {
|
private fun sendPostRequest(url: String, body: String): String {
|
||||||
val connection = URL(url).openConnection() as HttpURLConnection
|
val connection = URL(url).openConnection() as HttpURLConnection
|
||||||
return connection.apply {
|
return connection.apply {
|
||||||
setRequestProperty("Content-Type", "application/json; charset=utf-8")
|
setRequestProperty("Content-Type", "application/json; charset=utf-8")
|
||||||
@@ -71,131 +56,33 @@ open class BuildRegister : DefaultTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAdditionalInfo(analyzer: String, reportFile: String, alwaysExists: Boolean, benchmarkName: String,
|
|
||||||
showedName: String = benchmarkName) :
|
|
||||||
performanceAdditionalResult? {
|
|
||||||
val output = arrayOf(analyzer, "summary", "--compile", "samples",
|
|
||||||
"--compile-samples", benchmarkName, "--codesize-samples", benchmarkName,
|
|
||||||
"--codesize-normalize", "artifactory:builds/goldenResults.csv", reportFile)
|
|
||||||
.runCommand()
|
|
||||||
|
|
||||||
|
|
||||||
val buildInfoParts = output.split(',')
|
|
||||||
if (buildInfoParts.size != additionalInfoTokens) {
|
|
||||||
val message = "Problems with getting summary information using $analyzer and $reportFile. $output"
|
|
||||||
if (!alwaysExists) {
|
|
||||||
println(message)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
error(message)
|
|
||||||
}
|
|
||||||
val (failures, compileTime, codeSize) = buildInfoParts.map { it.trim() }
|
|
||||||
val codeSizeInfo = "$showedName-$codeSize"
|
|
||||||
val compileTimeInfo = "$showedName-$compileTime"
|
|
||||||
return performanceAdditionalResult(failures, compileTimeInfo, codeSizeInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
fun run() {
|
fun run() {
|
||||||
// Get TeamCity properties.
|
// Get TeamCity properties.
|
||||||
val teamcityConfig = System.getenv("TEAMCITY_BUILD_PROPERTIES_FILE") ?:
|
val teamcityConfig = System.getenv("TEAMCITY_BUILD_PROPERTIES_FILE") ?: error("Can't load teamcity config!")
|
||||||
error("Can't load teamcity config!")
|
|
||||||
|
|
||||||
val buildProperties = Properties()
|
val buildProperties = Properties()
|
||||||
buildProperties.load(FileInputStream(teamcityConfig))
|
buildProperties.load(FileInputStream(teamcityConfig))
|
||||||
val buildId = buildProperties.getProperty("teamcity.build.id")
|
val buildId = buildProperties.getProperty("teamcity.build.id")
|
||||||
val teamCityUser = buildProperties.getProperty("teamcity.auth.userId")
|
val teamCityUser = buildProperties.getProperty("teamcity.auth.userId")
|
||||||
val teamCityPassword = buildProperties.getProperty("teamcity.auth.password")
|
val teamCityPassword = buildProperties.getProperty("teamcity.auth.password")
|
||||||
val buildNumber = buildProperties.getProperty("build.number")
|
|
||||||
val apiKey = buildProperties.getProperty("artifactory.apikey")
|
|
||||||
|
|
||||||
// Get branch.
|
// Get branch.
|
||||||
val currentBuild = getBuild("id:$buildId", teamCityUser, teamCityPassword)
|
val currentBuild = getBuild("id:$buildId", teamCityUser, teamCityPassword)
|
||||||
val branch = getBuildProperty(currentBuild,"branchName")
|
val branch = getBuildProperty(currentBuild, "branchName")
|
||||||
|
|
||||||
val target = System.getProperty("os.name").replace("\\s".toRegex(), "")
|
|
||||||
|
|
||||||
// Get summary information.
|
|
||||||
val output = arrayOf("$analyzer", "summary", "--exec-samples", "all", "--compile", "samples",
|
|
||||||
"--compile-samples", "HelloWorld,Videoplayer", "--codesize-samples", "all",
|
|
||||||
"--exec-normalize", "artifactory:builds/goldenResults.csv",
|
|
||||||
"--codesize-normalize", "artifactory:builds/goldenResults.csv", "$currentBenchmarksReportFile")
|
|
||||||
.runCommand()
|
|
||||||
|
|
||||||
// Postprocess information.
|
|
||||||
val buildInfoParts = output.split(',')
|
|
||||||
if (buildInfoParts.size != buildInfoTokens) {
|
|
||||||
error("Problems with getting summary information using $analyzer and $currentBenchmarksReportFile. $output")
|
|
||||||
}
|
|
||||||
|
|
||||||
val (failures, executionTime, compileTime, codeSize) = buildInfoParts.map { it.trim() }
|
|
||||||
var failuresNumber = failures.toInt()
|
|
||||||
// Add legends.
|
|
||||||
val geometricMean = "Geometric Mean-"
|
|
||||||
val executionTimeInfo = "$geometricMean$executionTime"
|
|
||||||
var codeSizeInfo = "$geometricMean$codeSize"
|
|
||||||
val compileTimeSamples = compileTime.split(';')
|
|
||||||
if (compileTimeSamples.size != compileTimeSamplesNumber) {
|
|
||||||
error("Problems with getting compile time samples value. Expected at least $compileTimeSamplesNumber samples, got ${compileTimeSamples.size}")
|
|
||||||
}
|
|
||||||
val (helloWorldCompile, videoplayerCompile) = compileTimeSamples
|
|
||||||
var compileTimeInfo = "HelloWorld-$helloWorldCompile;Videoplayer-$videoplayerCompile"
|
|
||||||
|
|
||||||
// Collect framework run details.
|
|
||||||
if (target == "MacOSX") {
|
|
||||||
val frameworkResults = getAdditionalInfo(analyzer, currentBenchmarksReportFile, true,
|
|
||||||
"FrameworkBenchmarksAnalyzer")
|
|
||||||
frameworkResults?.let {
|
|
||||||
val (_, frameworkCompileTime, frameworkCodeSize) = it
|
|
||||||
codeSizeInfo += ";$frameworkCodeSize"
|
|
||||||
compileTimeInfo += ";$frameworkCompileTime"
|
|
||||||
}
|
|
||||||
|
|
||||||
val spaceResults = getAdditionalInfo(analyzer,
|
|
||||||
"artifactory:$buildNumber:$target:spaceFrameworkReport.json", false,
|
|
||||||
"circlet_iosX64", "SpaceFramework_iosX64")
|
|
||||||
spaceResults?.let {
|
|
||||||
val (failures, frameworkCompileTime, frameworkCodeSize) = it
|
|
||||||
failuresNumber += failures.toInt()
|
|
||||||
codeSizeInfo += ";$frameworkCodeSize"
|
|
||||||
compileTimeInfo += ";$frameworkCompileTime"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target == "Linux") {
|
|
||||||
val coroutinesResults = getAdditionalInfo(analyzer,
|
|
||||||
"artifactory:$buildNumber:$target:externalReport.json", false,
|
|
||||||
"kotlinx.coroutines")
|
|
||||||
coroutinesResults?.let {
|
|
||||||
val (failures, libraryCompileTime, libraryCodeSize) = it
|
|
||||||
failuresNumber += failures.toInt()
|
|
||||||
codeSizeInfo += ";$libraryCodeSize"
|
|
||||||
compileTimeInfo += ";$libraryCompileTime"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val matchResult = buildNumberRegex.find(buildNumber) ?: error("Wrong format of build number $buildNumber.")
|
|
||||||
val buildType = matchResult.groups[3]!!.value
|
|
||||||
|
|
||||||
// Send post request to register build.
|
// Send post request to register build.
|
||||||
val requestBody = buildString {
|
val requestBody = buildString {
|
||||||
append("{\"buildId\":\"$buildId\",")
|
append("{\"buildId\":\"$buildId\",")
|
||||||
append("\"teamCityUser\":\"$teamCityUser\",")
|
append("\"teamCityUser\":\"$teamCityUser\",")
|
||||||
append("\"teamCityPassword\":\"$teamCityPassword\",")
|
append("\"teamCityPassword\":\"$teamCityPassword\",")
|
||||||
append("\"artifactoryApiKey\":\"$apiKey\",")
|
append("\"fileWithResult\":\"$fileWithResult\",")
|
||||||
append("\"target\": \"$target\",")
|
append("\"bundleSize\": ${bundleSize?.let { "\"$bundleSize\"" } ?: bundleSize}}")
|
||||||
append("\"buildType\": \"$buildType\",")
|
|
||||||
append("\"failuresNumber\": $failuresNumber,")
|
|
||||||
append("\"executionTime\": \"$executionTimeInfo\",")
|
|
||||||
append("\"compileTime\": \"$compileTimeInfo\",")
|
|
||||||
append("\"codeSize\": \"$codeSizeInfo\",")
|
|
||||||
append("\"bundleSize\": ${bundleSize?.let {"\"$bundleSize\""} ?: bundleSize}}")
|
|
||||||
}
|
}
|
||||||
if (onlyBranch == null || onlyBranch == branch) {
|
if (onlyBranch == null || onlyBranch == branch) {
|
||||||
println(sendPostRequest("$performanceServer/register", requestBody))
|
println(sendPostRequest("$performanceServer/register", requestBody))
|
||||||
} else {
|
} else {
|
||||||
println("Skipping registration. Current branch $branch, need registration for $onlyBranch!")
|
println("Skipping registration. Current branch $branch, need registration for $onlyBranch!")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ fun createJsonReport(projectProperties: Map<String, Any>): String {
|
|||||||
val kotlin = Compiler(backend, getValue("kotlinVersion"))
|
val kotlin = Compiler(backend, getValue("kotlinVersion"))
|
||||||
val benchDesc = getValue("benchmarks")
|
val benchDesc = getValue("benchmarks")
|
||||||
val benchmarksArray = JsonTreeParser.parse(benchDesc)
|
val benchmarksArray = JsonTreeParser.parse(benchDesc)
|
||||||
val benchmarks = BenchmarksReport.parseBenchmarksArray(benchmarksArray)
|
val benchmarks = parseBenchmarksArray(benchmarksArray)
|
||||||
.union(projectProperties["compileTime"] as List<BenchmarkResult>).union(
|
.union(projectProperties["compileTime"] as List<BenchmarkResult>).union(
|
||||||
listOf(projectProperties["codeSize"] as? BenchmarkResult).filterNotNull()).toList()
|
listOf(projectProperties["codeSize"] as? BenchmarkResult).filterNotNull()).toList()
|
||||||
val report = BenchmarksReport(env, benchmarks, kotlin)
|
val report = BenchmarksReport(env, benchmarks, kotlin)
|
||||||
|
|||||||
+16
-13
@@ -56,7 +56,8 @@ private String findAnalyzerBinary() {
|
|||||||
|
|
||||||
// Produce and send slack report.
|
// Produce and send slack report.
|
||||||
task slackReport(type: RegressionsReporter) {
|
task slackReport(type: RegressionsReporter) {
|
||||||
def analyzerBinary = findAnalyzerBinary()
|
def analyzerBinary = MPPTools.findFile("${analyzerTool}${MPPTools.getNativeProgramExtension()}",
|
||||||
|
"${rootBuildDirectory}/${analyzerToolDirectory}")
|
||||||
def teamcityConfig = System.getenv("TEAMCITY_BUILD_PROPERTIES_FILE")
|
def teamcityConfig = System.getenv("TEAMCITY_BUILD_PROPERTIES_FILE")
|
||||||
if (teamcityConfig && analyzerBinary != null) {
|
if (teamcityConfig && analyzerBinary != null) {
|
||||||
// Create folder for report (root Kotlin project settings make create report in separate folder).
|
// Create folder for report (root Kotlin project settings make create report in separate folder).
|
||||||
@@ -159,21 +160,23 @@ task registerExternalBenchmarks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task registerBuild(type: BuildRegister) {
|
task registerBuild(type: BuildRegister) {
|
||||||
def analyzerBinary = findAnalyzerBinary()
|
onlyBranch = project.findProperty('kotlin.register.branch')
|
||||||
if (analyzerBinary != null) {
|
// Get bundle size.
|
||||||
onlyBranch = project.findProperty('kotlin.register.branch')
|
bundleSize = null
|
||||||
// Get bundle size.
|
if (project.findProperty('kotlin.bundleBuild') != null) {
|
||||||
bundleSize = null
|
def dist = findProperty('kotlin.native.home') ?: 'dist'
|
||||||
if (project.findProperty('kotlin.bundleBuild') != null) {
|
dist = (new File(dist)).isAbsolute() ? dist : "${project.getProjectDir()}/$dist"
|
||||||
def dist = findProperty('kotlin.native.home') ?: 'dist'
|
bundleSize = (new File(dist)).directorySize()
|
||||||
dist = (new File(dist)).isAbsolute() ? dist : "${project.getProjectDir()}/$dist"
|
|
||||||
bundleSize = (new File(dist)).directorySize()
|
|
||||||
}
|
|
||||||
currentBenchmarksReportFile = "${buildDir.absolutePath}/${nativeJson}"
|
|
||||||
analyzer = analyzerBinary
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task registerExternalBuild(type: BuildRegister) {
|
||||||
|
onlyBranch = project.findProperty('kotlin.register.branch')
|
||||||
|
fileWithResult = externalBenchmarksReport
|
||||||
|
}
|
||||||
|
|
||||||
|
registerExternalBenchmarks.finalizedBy registerExternalBuild
|
||||||
|
|
||||||
def mergeReports(String fileName) {
|
def mergeReports(String fileName) {
|
||||||
def reports = []
|
def reports = []
|
||||||
subprojects.each {
|
subprojects.each {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ val defaultBuildType = NativeBuildType.RELEASE
|
|||||||
|
|
||||||
benchmark {
|
benchmark {
|
||||||
applicationName = "Cinterop"
|
applicationName = "Cinterop"
|
||||||
commonSrcDirs = listOf("../../tools/benchmarks/shared/src", "src/main/kotlin", "../shared/src/main/kotlin")
|
commonSrcDirs = listOf("../../tools/benchmarks/shared/src/main/kotlin/report", "src/main/kotlin", "../shared/src/main/kotlin")
|
||||||
jvmSrcDirs = listOf("src/main/kotlin-jvm", "../shared/src/main/kotlin-jvm")
|
jvmSrcDirs = listOf("src/main/kotlin-jvm", "../shared/src/main/kotlin-jvm")
|
||||||
nativeSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/common")
|
nativeSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/common")
|
||||||
mingwSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/mingw")
|
mingwSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/mingw")
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ val defaultBuildType = NativeBuildType.RELEASE
|
|||||||
|
|
||||||
benchmark {
|
benchmark {
|
||||||
applicationName = "Numerical"
|
applicationName = "Numerical"
|
||||||
commonSrcDirs = listOf("src/main/kotlin", "../../tools/benchmarks/shared/src", "../shared/src/main/kotlin")
|
commonSrcDirs = listOf("src/main/kotlin", "../../tools/benchmarks/shared/src/main/kotlin/report", "../shared/src/main/kotlin")
|
||||||
jvmSrcDirs = listOf("src/main/kotlin-jvm", "../shared/src/main/kotlin-jvm")
|
jvmSrcDirs = listOf("src/main/kotlin-jvm", "../shared/src/main/kotlin-jvm")
|
||||||
nativeSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/common")
|
nativeSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/common")
|
||||||
mingwSrcDirs = listOf("../shared/src/main/kotlin-native/mingw")
|
mingwSrcDirs = listOf("../shared/src/main/kotlin-native/mingw")
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ val defaultBuildType = NativeBuildType.RELEASE
|
|||||||
|
|
||||||
benchmark {
|
benchmark {
|
||||||
applicationName = "ObjCInterop"
|
applicationName = "ObjCInterop"
|
||||||
commonSrcDirs = listOf("../../tools/benchmarks/shared/src", "src/main/kotlin", "../shared/src/main/kotlin")
|
commonSrcDirs = listOf("../../tools/benchmarks/shared/src/main/kotlin/report", "src/main/kotlin", "../shared/src/main/kotlin")
|
||||||
jvmSrcDirs = listOf("src/main/kotlin-jvm", "../shared/src/main/kotlin-jvm")
|
jvmSrcDirs = listOf("src/main/kotlin-jvm", "../shared/src/main/kotlin-jvm")
|
||||||
nativeSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/common")
|
nativeSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/common")
|
||||||
mingwSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/mingw")
|
mingwSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/mingw")
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ val defaultBuildType = NativeBuildType.RELEASE
|
|||||||
|
|
||||||
benchmark {
|
benchmark {
|
||||||
applicationName = "Ring"
|
applicationName = "Ring"
|
||||||
commonSrcDirs = listOf("../../tools/benchmarks/shared/src", "src/main/kotlin", "../shared/src/main/kotlin")
|
commonSrcDirs = listOf("../../tools/benchmarks/shared/src/main/kotlin/report", "src/main/kotlin", "../shared/src/main/kotlin")
|
||||||
jvmSrcDirs = listOf("src/main/kotlin-jvm", "../shared/src/main/kotlin-jvm")
|
jvmSrcDirs = listOf("src/main/kotlin-jvm", "../shared/src/main/kotlin-jvm")
|
||||||
nativeSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/common")
|
nativeSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/common")
|
||||||
mingwSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/mingw")
|
mingwSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/mingw")
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ val defaultBuildType = NativeBuildType.RELEASE
|
|||||||
|
|
||||||
benchmark {
|
benchmark {
|
||||||
applicationName = "Startup"
|
applicationName = "Startup"
|
||||||
commonSrcDirs = listOf("../../tools/benchmarks/shared/src", "src/main/kotlin", "../shared/src/main/kotlin")
|
commonSrcDirs = listOf("../../tools/benchmarks/shared/src/main/kotlin/report", "src/main/kotlin", "../shared/src/main/kotlin")
|
||||||
jvmSrcDirs = listOf("../shared/src/main/kotlin-jvm")
|
jvmSrcDirs = listOf("../shared/src/main/kotlin-jvm")
|
||||||
nativeSrcDirs = listOf("../shared/src/main/kotlin-native/common")
|
nativeSrcDirs = listOf("../shared/src/main/kotlin-native/common")
|
||||||
mingwSrcDirs = listOf("../shared/src/main/kotlin-native/mingw")
|
mingwSrcDirs = listOf("../shared/src/main/kotlin-native/mingw")
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ val defaultBuildType = NativeBuildType.RELEASE
|
|||||||
|
|
||||||
swiftBenchmark {
|
swiftBenchmark {
|
||||||
applicationName = "swiftInterop"
|
applicationName = "swiftInterop"
|
||||||
commonSrcDirs = listOf("$toolsPath/benchmarks/shared/src/main/kotlin", "src", "../shared/src/main/kotlin")
|
commonSrcDirs = listOf("$toolsPath/benchmarks/shared/src/main/kotlin/report", "src", "../shared/src/main/kotlin")
|
||||||
nativeSrcDirs = listOf("../shared/src/main/kotlin-native/common", "../shared/src/main/kotlin-native/posix")
|
nativeSrcDirs = listOf("../shared/src/main/kotlin-native/common", "../shared/src/main/kotlin-native/posix")
|
||||||
swiftSources = listOf("$projectDir/swiftSrc/benchmarks.swift", "$projectDir/swiftSrc/main.swift")
|
swiftSources = listOf("$projectDir/swiftSrc/benchmarks.swift", "$projectDir/swiftSrc/main.swift")
|
||||||
compileTasks = listOf("compileKotlinNative", "linkBenchmarkReleaseFrameworkNative")
|
compileTasks = listOf("compileKotlinNative", "linkBenchmarkReleaseFrameworkNative")
|
||||||
|
|||||||
+2
-14
@@ -1,20 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.jetbrains.analyzer
|
package org.jetbrains.analyzer
|
||||||
|
|
||||||
// Report with changes of different fields.
|
// Report with changes of different fields.
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* 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.jetbrains.report.BenchmarkResult
|
||||||
|
import org.jetbrains.report.MeanVariance
|
||||||
|
import org.jetbrains.report.MeanVarianceBenchmark
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
val MeanVariance.description: String
|
||||||
|
get() {
|
||||||
|
val format = { number: Double -> number.format(2) }
|
||||||
|
return "${format(mean)} ± ${format(variance)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
val MeanVarianceBenchmark.description: String
|
||||||
|
get() = "${score.format()} ± ${variance.format()}"
|
||||||
|
|
||||||
|
// Calculate difference in percentage compare to another.
|
||||||
|
fun MeanVarianceBenchmark.calcPercentageDiff(other: MeanVarianceBenchmark): MeanVariance {
|
||||||
|
assert(other.score >= 0 &&
|
||||||
|
other.variance >= 0 &&
|
||||||
|
other.score - other.variance != 0.0,
|
||||||
|
{ "Mean and variance should be positive and not equal!" })
|
||||||
|
// Analyze intervals. Calculate difference between border points.
|
||||||
|
val (bigValue, smallValue) = if (score > other.score) Pair(this, other) else Pair(other, this)
|
||||||
|
val bigValueIntervalStart = bigValue.score - bigValue.variance
|
||||||
|
val bigValueIntervalEnd = bigValue.score + bigValue.variance
|
||||||
|
val smallValueIntervalStart = smallValue.score - smallValue.variance
|
||||||
|
val smallValueIntervalEnd = smallValue.score + smallValue.variance
|
||||||
|
if (smallValueIntervalEnd > bigValueIntervalStart) {
|
||||||
|
// Interval intersect.
|
||||||
|
return MeanVariance(0.0, 0.0)
|
||||||
|
}
|
||||||
|
val mean = ((smallValueIntervalEnd - bigValueIntervalStart) / bigValueIntervalStart) *
|
||||||
|
(if (score > other.score) -1 else 1)
|
||||||
|
|
||||||
|
val maxValueChange = ((bigValueIntervalEnd - smallValueIntervalEnd) / bigValueIntervalEnd)
|
||||||
|
val minValueChange = ((bigValueIntervalStart - smallValueIntervalStart) / bigValueIntervalStart)
|
||||||
|
val variance = abs(abs(mean) - max(minValueChange, maxValueChange))
|
||||||
|
return MeanVariance(mean * 100, variance * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate ratio value compare to another.
|
||||||
|
fun MeanVarianceBenchmark.calcRatio(other: MeanVarianceBenchmark): MeanVariance {
|
||||||
|
assert(other.score >= 0 &&
|
||||||
|
other.variance >= 0 &&
|
||||||
|
other.score - other.variance != 0.0,
|
||||||
|
{ "Mean and variance should be positive and not equal!" })
|
||||||
|
val mean = score / other.score
|
||||||
|
val minRatio = (score - variance) / (other.score + other.variance)
|
||||||
|
val maxRatio = (score + variance) / (other.score - other.variance)
|
||||||
|
val ratioConfInt = min(abs(minRatio - mean), abs(maxRatio - mean))
|
||||||
|
return MeanVariance(mean, ratioConfInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun geometricMean(values: Collection<Double>, totalNumber: Int = values.size) =
|
||||||
|
with(values.asSequence().filter { it != 0.0 }) {
|
||||||
|
if (count() == 0) {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
map { it.pow(1.0 / totalNumber) }.reduce { a, b -> a * b }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun computeMeanVariance(samples: List<Double>): MeanVariance {
|
||||||
|
val zStar = 1.67 // Critical point for 90% confidence of normal distribution.
|
||||||
|
val mean = samples.sum() / samples.size
|
||||||
|
val variance = samples.indices.sumByDouble { (samples[it] - mean) * (samples[it] - mean) } / samples.size
|
||||||
|
val confidenceInterval = sqrt(variance / samples.size) * zStar
|
||||||
|
return MeanVariance(mean, confidenceInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate average results for benchmarks (each benchmark can be run several times).
|
||||||
|
fun collectMeanResults(benchmarks: Map<String, List<BenchmarkResult>>): BenchmarksTable {
|
||||||
|
return benchmarks.map { (name, resultsSet) ->
|
||||||
|
val repeatedSequence = IntArray(resultsSet.size)
|
||||||
|
var metric = BenchmarkResult.Metric.EXECUTION_TIME
|
||||||
|
var currentStatus = BenchmarkResult.Status.PASSED
|
||||||
|
var currentWarmup = -1
|
||||||
|
|
||||||
|
// Results can be already processed.
|
||||||
|
if (resultsSet[0] is MeanVarianceBenchmark) {
|
||||||
|
assert(resultsSet.size == 1) { "Several MeanVarianceBenchmark instances." }
|
||||||
|
name to resultsSet[0] as MeanVarianceBenchmark
|
||||||
|
} else {
|
||||||
|
// Collect common benchmark values and check them.
|
||||||
|
resultsSet.forEachIndexed { index, result ->
|
||||||
|
// If there was at least one failure, summary is marked as failure.
|
||||||
|
if (result.status == BenchmarkResult.Status.FAILED) {
|
||||||
|
currentStatus = result.status
|
||||||
|
}
|
||||||
|
repeatedSequence[index] = result.repeat
|
||||||
|
if (currentWarmup != -1)
|
||||||
|
if (result.warmup != currentWarmup)
|
||||||
|
println("Check data consistency. Warmup value for benchmark '${result.name}' differs.")
|
||||||
|
currentWarmup = result.warmup
|
||||||
|
metric = result.metric
|
||||||
|
}
|
||||||
|
|
||||||
|
repeatedSequence.sort()
|
||||||
|
// Check if there are missed loop during running benchmarks.
|
||||||
|
repeatedSequence.forEachIndexed { index, element ->
|
||||||
|
if (index != 0)
|
||||||
|
if ((element - repeatedSequence[index - 1]) != 1)
|
||||||
|
println("Check data consistency. For benchmark '$name' there is no run" +
|
||||||
|
" between ${repeatedSequence[index - 1]} and $element.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mean and variance benchmarks result.
|
||||||
|
val scoreMeanVariance = computeMeanVariance(resultsSet.map { it.score })
|
||||||
|
val runtimeInUsMeanVariance = computeMeanVariance(resultsSet.map { it.runtimeInUs })
|
||||||
|
val meanBenchmark = MeanVarianceBenchmark(name, currentStatus, scoreMeanVariance.mean, metric,
|
||||||
|
runtimeInUsMeanVariance.mean, repeatedSequence[resultsSet.size - 1],
|
||||||
|
currentWarmup, scoreMeanVariance.variance)
|
||||||
|
name to meanBenchmark
|
||||||
|
}
|
||||||
|
}.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun collectBenchmarksDurations(benchmarks: Map<String, List<BenchmarkResult>>): Map<String, Double> =
|
||||||
|
benchmarks.map { (name, resultsSet) ->
|
||||||
|
name to resultsSet.sumByDouble { it.runtimeInUs }
|
||||||
|
}.toMap()
|
||||||
+46
-56
@@ -1,27 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.jetbrains.analyzer
|
package org.jetbrains.analyzer
|
||||||
|
|
||||||
import kotlin.math.abs
|
|
||||||
import org.jetbrains.report.BenchmarkResult
|
import org.jetbrains.report.BenchmarkResult
|
||||||
import org.jetbrains.report.Environment
|
|
||||||
import org.jetbrains.report.Compiler
|
|
||||||
import org.jetbrains.report.BenchmarksReport
|
import org.jetbrains.report.BenchmarksReport
|
||||||
|
import org.jetbrains.report.Compiler
|
||||||
|
import org.jetbrains.report.Environment
|
||||||
|
import org.jetbrains.report.MeanVariance
|
||||||
|
import org.jetbrains.report.MeanVarianceBenchmark
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
typealias SummaryBenchmark = Pair<MeanVarianceBenchmark?, MeanVarianceBenchmark?>
|
typealias SummaryBenchmark = Pair<MeanVarianceBenchmark?, MeanVarianceBenchmark?>
|
||||||
typealias BenchmarksTable = Map<String, MeanVarianceBenchmark>
|
typealias BenchmarksTable = Map<String, MeanVarianceBenchmark>
|
||||||
@@ -29,12 +19,12 @@ typealias SummaryBenchmarksTable = Map<String, SummaryBenchmark>
|
|||||||
typealias ScoreChange = Pair<MeanVariance, MeanVariance>
|
typealias ScoreChange = Pair<MeanVariance, MeanVariance>
|
||||||
|
|
||||||
// Summary report with comparasion of separate benchmarks results.
|
// Summary report with comparasion of separate benchmarks results.
|
||||||
class SummaryBenchmarksReport (val currentReport: BenchmarksReport,
|
class SummaryBenchmarksReport(val currentReport: BenchmarksReport,
|
||||||
val previousReport: BenchmarksReport? = null,
|
val previousReport: BenchmarksReport? = null,
|
||||||
val meaningfulChangesValue: Double = 0.5) {
|
val meaningfulChangesValue: Double = 0.5) {
|
||||||
// Report created by joining comparing reports.
|
// Report created by joining comparing reports.
|
||||||
val mergedReport: Map<String, SummaryBenchmark>
|
val mergedReport: Map<String, SummaryBenchmark>
|
||||||
val benchmarksDurations: Map<String, Pair<Double?, Double?>>
|
private val benchmarksDurations: Map<String, Pair<Double?, Double?>>
|
||||||
|
|
||||||
// Lists of benchmarks in different status.
|
// Lists of benchmarks in different status.
|
||||||
private val benchmarksWithChangedStatus = mutableListOf<FieldChange<BenchmarkResult.Status>>()
|
private val benchmarksWithChangedStatus = mutableListOf<FieldChange<BenchmarkResult.Status>>()
|
||||||
@@ -56,8 +46,8 @@ class SummaryBenchmarksReport (val currentReport: BenchmarksReport,
|
|||||||
|
|
||||||
// Countable properties.
|
// Countable properties.
|
||||||
val failedBenchmarks: List<String>
|
val failedBenchmarks: List<String>
|
||||||
get() = mergedReport.filter { it.value.first?.meanBenchmark?.status == BenchmarkResult.Status.FAILED }
|
get() = mergedReport.filter { it.value.first?.status == BenchmarkResult.Status.FAILED }
|
||||||
.map { it.key }
|
.map { it.key }
|
||||||
|
|
||||||
val addedBenchmarks: List<String>
|
val addedBenchmarks: List<String>
|
||||||
get() = mergedReport.filter { it.value.second == null }.map { it.key }
|
get() = mergedReport.filter { it.value.second == null }.map { it.key }
|
||||||
@@ -72,7 +62,7 @@ class SummaryBenchmarksReport (val currentReport: BenchmarksReport,
|
|||||||
get() = mergedReport.filter { it.value.first != null }.map { it.value.first!! }
|
get() = mergedReport.filter { it.value.first != null }.map { it.value.first!! }
|
||||||
|
|
||||||
val currentBenchmarksDuration: Map<String, Double>
|
val currentBenchmarksDuration: Map<String, Double>
|
||||||
get() = benchmarksDurations.filter{ it.value.first != null }.map { it.key to it.value.first!! }.toMap()
|
get() = benchmarksDurations.filter { it.value.first != null }.map { it.key to it.value.first!! }.toMap()
|
||||||
|
|
||||||
val maximumRegression: Double
|
val maximumRegression: Double
|
||||||
get() = getMaximumChange(regressions)
|
get() = getMaximumChange(regressions)
|
||||||
@@ -133,8 +123,15 @@ class SummaryBenchmarksReport (val currentReport: BenchmarksReport,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get benchmark report.
|
||||||
|
fun getBenchmarksReport(takeMainReport: Boolean = true) =
|
||||||
|
if (takeMainReport)
|
||||||
|
BenchmarksReport(environments.first, mergedReport.map { (_, value) -> value.first!! }, compilers.first)
|
||||||
|
else
|
||||||
|
BenchmarksReport(environments.second!!, mergedReport.map { (_, value) -> value.second!! }, compilers.second!!)
|
||||||
|
|
||||||
fun getResultsByMetric(metric: BenchmarkResult.Metric, getGeoMean: Boolean = true, filter: List<String>? = null,
|
fun getResultsByMetric(metric: BenchmarkResult.Metric, getGeoMean: Boolean = true, filter: List<String>? = null,
|
||||||
normalizeData: Map<String, Map<String, Double>>? = null): List<Double> {
|
normalizeData: Map<String, Map<String, Double>>? = null): List<Double?> {
|
||||||
val benchmarks = filter?.let {
|
val benchmarks = filter?.let {
|
||||||
mergedReport.filter { entry ->
|
mergedReport.filter { entry ->
|
||||||
filter.find {
|
filter.find {
|
||||||
@@ -142,30 +139,26 @@ class SummaryBenchmarksReport (val currentReport: BenchmarksReport,
|
|||||||
} != null
|
} != null
|
||||||
}
|
}
|
||||||
} ?: mergedReport
|
} ?: mergedReport
|
||||||
if (benchmarks.isEmpty()) {
|
val results = benchmarks.map { entry ->
|
||||||
error("There is no benchmarks from provided list")
|
|
||||||
}
|
|
||||||
val filteredBenchmarks = benchmarks.filter { entry -> entry.value.first!!.meanBenchmark.metric == metric }
|
|
||||||
if (filteredBenchmarks.isEmpty()) {
|
|
||||||
error("There is no benchmarks for metric $metric")
|
|
||||||
}
|
|
||||||
val results = filteredBenchmarks.map { entry ->
|
|
||||||
val score = entry.value.first!!.meanBenchmark.score
|
|
||||||
val name = entry.key.removeSuffix(metric.suffix)
|
val name = entry.key.removeSuffix(metric.suffix)
|
||||||
val value = normalizeData?.let {
|
if (entry.value.first!!.metric == metric) {
|
||||||
it.get(name)?.get("$metric")?.let { score / it }
|
val score = entry.value.first!!.score
|
||||||
?: error("No normalization data for benchmark $name and metric $metric")
|
val value = normalizeData?.let {
|
||||||
} ?: score
|
it.get(name)?.get("$metric")?.let { score / it }
|
||||||
name to value }.toMap()
|
?: error("No normalization data for benchmark $name and metric $metric")
|
||||||
|
} ?: score
|
||||||
|
name to value
|
||||||
|
} else name to null
|
||||||
|
}.toMap()
|
||||||
if (getGeoMean) {
|
if (getGeoMean) {
|
||||||
return listOf(geometricMean(results.values))
|
return listOf(geometricMean(results.values.filterNotNull()))
|
||||||
}
|
}
|
||||||
return filter?.let { it.map { results[it] ?: error("Benchmark $it for metric $metric doesn't exist.") }.toList() } ?: results.values.toList()
|
return filter?.let { it.map { results[it] }.toList() } ?: results.values.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMaximumChange(bucket: Map<String, ScoreChange>): Double =
|
private fun getMaximumChange(bucket: Map<String, ScoreChange>): Double =
|
||||||
// Maps of regressions and improvements are sorted.
|
// Maps of regressions and improvements are sorted.
|
||||||
if (bucket.isEmpty()) 0.0 else bucket.values.map { it.first.mean }.first()
|
if (bucket.isEmpty()) 0.0 else bucket.values.map { it.first.mean }.first()
|
||||||
|
|
||||||
private fun getGeometricMeanOfChanges(bucket: Map<String, ScoreChange>): Double {
|
private fun getGeometricMeanOfChanges(bucket: Map<String, ScoreChange>): Double {
|
||||||
if (bucket.isEmpty())
|
if (bucket.isEmpty())
|
||||||
@@ -185,11 +178,9 @@ class SummaryBenchmarksReport (val currentReport: BenchmarksReport,
|
|||||||
// Create geometric mean.
|
// Create geometric mean.
|
||||||
private fun createGeoMeanBenchmark(benchTable: BenchmarksTable): MeanVarianceBenchmark {
|
private fun createGeoMeanBenchmark(benchTable: BenchmarksTable): MeanVarianceBenchmark {
|
||||||
val geoMeanBenchmarkName = "Geometric mean"
|
val geoMeanBenchmarkName = "Geometric mean"
|
||||||
val geoMean = geometricMean(benchTable.toList().map { (_, value) -> value.meanBenchmark.score })
|
val geoMean = geometricMean(benchTable.toList().map { (_, value) -> value.score })
|
||||||
val varianceGeoMean = geometricMean(benchTable.toList().map { (_, value) -> value.varianceBenchmark.score })
|
val varianceGeoMean = geometricMean(benchTable.toList().map { (_, value) -> value.variance })
|
||||||
val meanBenchmark = BenchmarkResult(geoMeanBenchmarkName, geoMean)
|
return MeanVarianceBenchmark(geoMeanBenchmarkName, geoMean, varianceGeoMean)
|
||||||
val varianceBenchmark = BenchmarkResult(geoMeanBenchmarkName, varianceGeoMean)
|
|
||||||
return MeanVarianceBenchmark(meanBenchmark, varianceBenchmark)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate map with summary durations of each benchmark.
|
// Generate map with summary durations of each benchmark.
|
||||||
@@ -209,16 +200,15 @@ class SummaryBenchmarksReport (val currentReport: BenchmarksReport,
|
|||||||
val mergedTable = mutableMapOf<String, SummaryBenchmark>()
|
val mergedTable = mutableMapOf<String, SummaryBenchmark>()
|
||||||
mergedTable.apply {
|
mergedTable.apply {
|
||||||
currentBenchmarks.forEach { (name, current) ->
|
currentBenchmarks.forEach { (name, current) ->
|
||||||
val currentBenchmark = current.meanBenchmark
|
|
||||||
// Check existance of benchmark in previous results.
|
// Check existance of benchmark in previous results.
|
||||||
if (previousBenchmarks == null || name !in previousBenchmarks) {
|
if (previousBenchmarks == null || name !in previousBenchmarks) {
|
||||||
getOrPut(name) { SummaryBenchmark(current, null) }
|
getOrPut(name) { SummaryBenchmark(current, null) }
|
||||||
} else {
|
} else {
|
||||||
val previousBenchmark = previousBenchmarks.getValue(name).meanBenchmark
|
val previousBenchmark = previousBenchmarks.getValue(name)
|
||||||
getOrPut(name) { SummaryBenchmark(current, previousBenchmarks[name]) }
|
getOrPut(name) { SummaryBenchmark(current, previousBenchmarks[name]) }
|
||||||
// Explore change of status.
|
// Explore change of status.
|
||||||
if (previousBenchmark.status != currentBenchmark.status) {
|
if (previousBenchmark.status != current.status) {
|
||||||
val statusChange = FieldChange("$name", previousBenchmark.status, currentBenchmark.status)
|
val statusChange = FieldChange("$name", previousBenchmark.status, current.status)
|
||||||
benchmarksWithChangedStatus.add(statusChange)
|
benchmarksWithChangedStatus.add(statusChange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,7 +230,7 @@ class SummaryBenchmarksReport (val currentReport: BenchmarksReport,
|
|||||||
SummaryBenchmark {
|
SummaryBenchmark {
|
||||||
// Calculate geometric mean.
|
// Calculate geometric mean.
|
||||||
val currentGeoMean = createGeoMeanBenchmark(currentBenchmarks)
|
val currentGeoMean = createGeoMeanBenchmark(currentBenchmarks)
|
||||||
val previousGeoMean = previousBenchmarks?. let { createGeoMeanBenchmark(previousBenchmarks) }
|
val previousGeoMean = previousBenchmarks?.let { createGeoMeanBenchmark(previousBenchmarks) }
|
||||||
return SummaryBenchmark(currentGeoMean, previousGeoMean)
|
return SummaryBenchmark(currentGeoMean, previousGeoMean)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,7 +251,7 @@ class SummaryBenchmarksReport (val currentReport: BenchmarksReport,
|
|||||||
|
|
||||||
// Analyze and collect changes in performance between same becnhmarks.
|
// Analyze and collect changes in performance between same becnhmarks.
|
||||||
private fun analyzePerformanceChanges() {
|
private fun analyzePerformanceChanges() {
|
||||||
val performanceChanges = mergedReport.asSequence().map {(name, element) ->
|
val performanceChanges = mergedReport.asSequence().map { (name, element) ->
|
||||||
getBenchmarkPerfomanceChange(name, element)
|
getBenchmarkPerfomanceChange(name, element)
|
||||||
}.filterNotNull().groupBy {
|
}.filterNotNull().groupBy {
|
||||||
if (it.second.first.mean > 0) "regressions" else "improvements"
|
if (it.second.first.mean > 0) "regressions" else "improvements"
|
||||||
@@ -277,8 +267,8 @@ class SummaryBenchmarksReport (val currentReport: BenchmarksReport,
|
|||||||
|
|
||||||
// Calculate change for geometric mean.
|
// Calculate change for geometric mean.
|
||||||
val (current, previous) = geoMeanBenchmark
|
val (current, previous) = geoMeanBenchmark
|
||||||
geoMeanScoreChange = current?. let {
|
geoMeanScoreChange = current?.let {
|
||||||
previous?. let {
|
previous?.let {
|
||||||
Pair(current.calcPercentageDiff(previous), current.calcRatio(previous))
|
Pair(current.calcPercentageDiff(previous), current.calcRatio(previous))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 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
|
||||||
|
|
||||||
|
expect fun readFile(fileName: String): String
|
||||||
|
expect fun Double.format(decimalNumber: Int = 4): String
|
||||||
|
expect fun writeToFile(fileName: String, text: String)
|
||||||
|
expect fun assert(value: Boolean, lazyMessage: () -> Any)
|
||||||
|
expect fun sendGetRequest(url: String, user: String? = null, password: String? = null,
|
||||||
|
followLocation: Boolean = false) : String
|
||||||
+110
-78
@@ -1,26 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
* Copyright 2010-2019 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.jetbrains.report
|
package org.jetbrains.report
|
||||||
|
|
||||||
import org.jetbrains.report.json.*
|
import org.jetbrains.report.json.*
|
||||||
|
|
||||||
interface JsonSerializable {
|
interface JsonSerializable {
|
||||||
fun toJson(): String
|
fun serializeFields(): String
|
||||||
|
|
||||||
|
fun toJson(): String {
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
${serializeFields()}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
// Convert iterable objects arrays, lists to json.
|
// Convert iterable objects arrays, lists to json.
|
||||||
fun <T> arrayToJson(data: Iterable<T>): String {
|
fun <T> arrayToJson(data: Iterable<T>): String {
|
||||||
@@ -30,51 +26,63 @@ interface JsonSerializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EntityFromJsonFactory<T>: ConvertedFromJson {
|
interface EntityFromJsonFactory<T> : ConvertedFromJson {
|
||||||
fun create(data: JsonElement): T
|
fun create(data: JsonElement): T
|
||||||
}
|
}
|
||||||
|
|
||||||
// Class for benchcmarks report with all information of run.
|
// Parse array with benchmarks to list
|
||||||
class BenchmarksReport(val env: Environment, benchmarksList: List<BenchmarkResult>, val compiler: Compiler):
|
fun parseBenchmarksArray(data: JsonElement): List<BenchmarkResult> {
|
||||||
|
if (data is JsonArray) {
|
||||||
|
return data.jsonArray.map {
|
||||||
|
if (MeanVarianceBenchmark.isMeanVarianceBenchmark(it))
|
||||||
|
MeanVarianceBenchmark.create(it as JsonObject)
|
||||||
|
else BenchmarkResult.create(it as JsonObject)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error("Benchmarks field is expected to be an array. Please, check origin files.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Class for benchmarks report with all information of run.
|
||||||
|
open class BenchmarksReport(val env: Environment, benchmarksList: List<BenchmarkResult>, val compiler: Compiler) :
|
||||||
JsonSerializable {
|
JsonSerializable {
|
||||||
|
|
||||||
companion object: EntityFromJsonFactory<BenchmarksReport> {
|
companion object : EntityFromJsonFactory<BenchmarksReport> {
|
||||||
override fun create(data: JsonElement): BenchmarksReport {
|
override fun create(data: JsonElement): BenchmarksReport {
|
||||||
if (data is JsonObject) {
|
if (data is JsonObject) {
|
||||||
val env = Environment.create(data.getRequiredField("env"))
|
val env = Environment.create(data.getRequiredField("env"))
|
||||||
val benchmarksObj = data.getRequiredField("benchmarks")
|
val benchmarksObj = data.getRequiredField("benchmarks")
|
||||||
val compiler = Compiler.create(data.getRequiredField("kotlin"))
|
val compiler = Compiler.create(data.getRequiredField("kotlin"))
|
||||||
|
val buildNumberField = data.getOptionalField("buildNumber")
|
||||||
val benchmarksList = parseBenchmarksArray(benchmarksObj)
|
val benchmarksList = parseBenchmarksArray(benchmarksObj)
|
||||||
return BenchmarksReport(env, benchmarksList, compiler)
|
val report = BenchmarksReport(env, benchmarksList, compiler)
|
||||||
|
buildNumberField?.let { report.buildNumber = (it as JsonLiteral).unquoted() }
|
||||||
|
return report
|
||||||
} else {
|
} else {
|
||||||
error("Top level entity is expected to be an object. Please, check origin files.")
|
error("Top level entity is expected to be an object. Please, check origin files.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse array with benchmarks to list
|
|
||||||
fun parseBenchmarksArray(data: JsonElement): List<BenchmarkResult> {
|
|
||||||
if (data is JsonArray) {
|
|
||||||
return data.jsonArray.map { BenchmarkResult.create(it as JsonObject) }
|
|
||||||
} else {
|
|
||||||
error("Benchmarks field is expected to be an array. Please, check origin files.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Made a map of becnhmarks with name as key from list.
|
// Made a map of becnhmarks with name as key from list.
|
||||||
private fun structBenchmarks(benchmarksList: List<BenchmarkResult>) =
|
private fun structBenchmarks(benchmarksList: List<BenchmarkResult>) =
|
||||||
benchmarksList.groupBy{ it.name }
|
benchmarksList.groupBy { it.name }
|
||||||
}
|
}
|
||||||
|
|
||||||
val benchmarks: Map<String, List<BenchmarkResult>> = structBenchmarks(benchmarksList)
|
val benchmarks = structBenchmarks(benchmarksList)
|
||||||
|
|
||||||
override fun toJson(): String {
|
var buildNumber: String? = null
|
||||||
|
|
||||||
|
override fun serializeFields(): String {
|
||||||
|
val buildNumberField = buildNumber?.let {
|
||||||
|
""",
|
||||||
|
"buildNumber": "$buildNumber"
|
||||||
|
"""
|
||||||
|
} ?: ""
|
||||||
return """
|
return """
|
||||||
{
|
|
||||||
"env": ${env.toJson()},
|
"env": ${env.toJson()},
|
||||||
"kotlin": ${compiler.toJson()},
|
"kotlin": ${compiler.toJson()},
|
||||||
"benchmarks": ${arrayToJson(benchmarks.flatMap{it.value})}
|
"benchmarks": ${arrayToJson(benchmarks.flatMap { it.value })}$buildNumberField
|
||||||
}
|
""".trimIndent()
|
||||||
"""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun merge(other: BenchmarksReport): BenchmarksReport {
|
fun merge(other: BenchmarksReport): BenchmarksReport {
|
||||||
@@ -85,27 +93,27 @@ class BenchmarksReport(val env: Environment, benchmarksList: List<BenchmarkResul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
mergedBenchmarks.putAll(other.benchmarks)
|
mergedBenchmarks.putAll(other.benchmarks)
|
||||||
return BenchmarksReport(env, mergedBenchmarks.flatMap{it.value}, compiler)
|
return BenchmarksReport(env, mergedBenchmarks.flatMap { it.value }, compiler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concatenate benchmarks report if they have same environment and compiler.
|
// Concatenate benchmarks report if they have same environment and compiler.
|
||||||
operator fun plus(other: BenchmarksReport): BenchmarksReport {
|
operator fun plus(other: BenchmarksReport): BenchmarksReport {
|
||||||
if (compiler != other.compiler || env != other.env) {
|
if (compiler != other.compiler || env != other.env) {
|
||||||
error ("It's impossible to concat reports from different machines!")
|
error("It's impossible to concat reports from different machines!")
|
||||||
}
|
}
|
||||||
return merge(other)
|
return merge(other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Class for kotlin compiler
|
// Class for kotlin compiler
|
||||||
data class Compiler(val backend: Backend, val kotlinVersion: String): JsonSerializable {
|
data class Compiler(val backend: Backend, val kotlinVersion: String) : JsonSerializable {
|
||||||
|
|
||||||
enum class BackendType(val type: String) {
|
enum class BackendType(val type: String) {
|
||||||
JVM("jvm"),
|
JVM("jvm"),
|
||||||
NATIVE("native")
|
NATIVE("native")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object: EntityFromJsonFactory<Compiler> {
|
companion object : EntityFromJsonFactory<Compiler> {
|
||||||
override fun create(data: JsonElement): Compiler {
|
override fun create(data: JsonElement): Compiler {
|
||||||
if (data is JsonObject) {
|
if (data is JsonObject) {
|
||||||
val backend = Backend.create(data.getRequiredField("backend"))
|
val backend = Backend.create(data.getRequiredField("backend"))
|
||||||
@@ -121,13 +129,14 @@ data class Compiler(val backend: Backend, val kotlinVersion: String): JsonSerial
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Class for compiler backend
|
// Class for compiler backend
|
||||||
data class Backend(val type: BackendType, val version: String, val flags: List<String>): JsonSerializable {
|
data class Backend(val type: BackendType, val version: String, val flags: List<String>) : JsonSerializable {
|
||||||
companion object: EntityFromJsonFactory<Backend> {
|
companion object : EntityFromJsonFactory<Backend> {
|
||||||
override fun create(data: JsonElement): Backend {
|
override fun create(data: JsonElement): Backend {
|
||||||
if (data is JsonObject) {
|
if (data is JsonObject) {
|
||||||
val typeElement = data.getRequiredField("type")
|
val typeElement = data.getRequiredField("type")
|
||||||
if (typeElement is JsonLiteral) {
|
if (typeElement is JsonLiteral) {
|
||||||
val type = backendTypeFromString(typeElement.unquoted()) ?: error("Backend type should be 'jvm' or 'native'")
|
val type = backendTypeFromString(typeElement.unquoted())
|
||||||
|
?: error("Backend type should be 'jvm' or 'native'")
|
||||||
val version = elementToString(data.getRequiredField("version"), "version")
|
val version = elementToString(data.getRequiredField("version"), "version")
|
||||||
val flagsArray = data.getOptionalField("flags")
|
val flagsArray = data.getOptionalField("flags")
|
||||||
var flags: List<String> = emptyList()
|
var flags: List<String> = emptyList()
|
||||||
@@ -144,40 +153,35 @@ data class Compiler(val backend: Backend, val kotlinVersion: String): JsonSerial
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toJson(): String {
|
override fun serializeFields(): String {
|
||||||
val result = """
|
val result = """
|
||||||
{
|
|
||||||
"type": "${type.type}",
|
"type": "${type.type}",
|
||||||
"version": "${version}""""
|
"version": "${version}""""
|
||||||
// Don't print flags field if there is no one.
|
// Don't print flags field if there is no one.
|
||||||
if (flags.isEmpty()) {
|
if (flags.isEmpty()) {
|
||||||
return """$result
|
return """$result
|
||||||
}"""
|
"""
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return """
|
return """
|
||||||
$result,
|
$result,
|
||||||
"flags": ${arrayToJson(flags)}
|
"flags": ${arrayToJson(flags.map { if (it.startsWith("\"")) it else "\"$it\"" })}
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toJson(): String {
|
override fun serializeFields(): String {
|
||||||
return """
|
return """
|
||||||
{
|
|
||||||
"backend": ${backend.toJson()},
|
"backend": ${backend.toJson()},
|
||||||
"kotlinVersion": "${kotlinVersion}"
|
"kotlinVersion": "${kotlinVersion}"
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Class for description of environment of benchmarks run
|
// Class for description of environment of benchmarks run
|
||||||
data class Environment(val machine: Machine, val jdk: JDKInstance): JsonSerializable {
|
data class Environment(val machine: Machine, val jdk: JDKInstance) : JsonSerializable {
|
||||||
|
|
||||||
companion object: EntityFromJsonFactory<Environment> {
|
companion object : EntityFromJsonFactory<Environment> {
|
||||||
override fun create(data: JsonElement): Environment {
|
override fun create(data: JsonElement): Environment {
|
||||||
if (data is JsonObject) {
|
if (data is JsonObject) {
|
||||||
val machine = Machine.create(data.getRequiredField("machine"))
|
val machine = Machine.create(data.getRequiredField("machine"))
|
||||||
@@ -191,8 +195,8 @@ data class Environment(val machine: Machine, val jdk: JDKInstance): JsonSerializ
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Class for description of machine used for benchmarks run.
|
// Class for description of machine used for benchmarks run.
|
||||||
data class Machine(val cpu: String, val os: String): JsonSerializable {
|
data class Machine(val cpu: String, val os: String) : JsonSerializable {
|
||||||
companion object: EntityFromJsonFactory<Machine> {
|
companion object : EntityFromJsonFactory<Machine> {
|
||||||
override fun create(data: JsonElement): Machine {
|
override fun create(data: JsonElement): Machine {
|
||||||
if (data is JsonObject) {
|
if (data is JsonObject) {
|
||||||
val cpu = elementToString(data.getRequiredField("cpu"), "cpu")
|
val cpu = elementToString(data.getRequiredField("cpu"), "cpu")
|
||||||
@@ -205,19 +209,17 @@ data class Environment(val machine: Machine, val jdk: JDKInstance): JsonSerializ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toJson(): String {
|
override fun serializeFields(): String {
|
||||||
return """
|
return """
|
||||||
{
|
|
||||||
"cpu": "$cpu",
|
"cpu": "$cpu",
|
||||||
"os": "$os"
|
"os": "$os"
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Class for description of jdk used for benchmarks run.
|
// Class for description of jdk used for benchmarks run.
|
||||||
data class JDKInstance(val version: String, val vendor: String): JsonSerializable {
|
data class JDKInstance(val version: String, val vendor: String) : JsonSerializable {
|
||||||
companion object: EntityFromJsonFactory<JDKInstance> {
|
companion object : EntityFromJsonFactory<JDKInstance> {
|
||||||
override fun create(data: JsonElement): JDKInstance {
|
override fun create(data: JsonElement): JDKInstance {
|
||||||
if (data is JsonObject) {
|
if (data is JsonObject) {
|
||||||
val version = elementToString(data.getRequiredField("version"), "version")
|
val version = elementToString(data.getRequiredField("version"), "version")
|
||||||
@@ -230,47 +232,44 @@ data class Environment(val machine: Machine, val jdk: JDKInstance): JsonSerializ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toJson(): String {
|
override fun serializeFields(): String {
|
||||||
return """
|
return """
|
||||||
{
|
|
||||||
"version": "$version",
|
"version": "$version",
|
||||||
"vendor": "$vendor"
|
"vendor": "$vendor"
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toJson(): String {
|
override fun serializeFields(): String {
|
||||||
return """
|
return """
|
||||||
{
|
|
||||||
"machine": ${machine.toJson()},
|
"machine": ${machine.toJson()},
|
||||||
"jdk": ${jdk.toJson()}
|
"jdk": ${jdk.toJson()}
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BenchmarkResult(val name: String, val status: Status,
|
open class BenchmarkResult(val name: String, val status: Status,
|
||||||
val score: Double, val metric: Metric, val runtimeInUs: Double,
|
val score: Double, val metric: Metric, val runtimeInUs: Double,
|
||||||
val repeat: Int, val warmup: Int): JsonSerializable {
|
val repeat: Int, val warmup: Int) : JsonSerializable {
|
||||||
|
|
||||||
enum class Metric(val suffix: String, val value: String) {
|
enum class Metric(val suffix: String, val value: String) {
|
||||||
EXECUTION_TIME("", "EXECUTION_TIME"),
|
EXECUTION_TIME("", "EXECUTION_TIME"),
|
||||||
CODE_SIZE(".codeSize", "CODE_SIZE"),
|
CODE_SIZE(".codeSize", "CODE_SIZE"),
|
||||||
COMPILE_TIME(".compileTime", "COMPILE_TIME")
|
COMPILE_TIME(".compileTime", "COMPILE_TIME"),
|
||||||
|
BUNDLE_SIZE(".bundleSize", "BUNDLE_SIZE")
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(name: String, score: Double) : this(name, Status.PASSED, score, Metric.EXECUTION_TIME, 0.0, 0, 0)
|
constructor(name: String, score: Double) : this(name, Status.PASSED, score, Metric.EXECUTION_TIME, 0.0, 0, 0)
|
||||||
|
|
||||||
companion object: EntityFromJsonFactory<BenchmarkResult> {
|
companion object : EntityFromJsonFactory<BenchmarkResult> {
|
||||||
|
|
||||||
override fun create(data: JsonElement): BenchmarkResult {
|
override fun create(data: JsonElement): BenchmarkResult {
|
||||||
if (data is JsonObject) {
|
if (data is JsonObject) {
|
||||||
var name = elementToString(data.getRequiredField("name"), "name")
|
var name = elementToString(data.getRequiredField("name"), "name")
|
||||||
val metricElement = data.getOptionalField("metric")
|
val metricElement = data.getOptionalField("metric")
|
||||||
val metric = if (metricElement != null && metricElement is JsonLiteral)
|
val metric = if (metricElement != null && metricElement is JsonLiteral)
|
||||||
metricFromString(metricElement.unquoted()) ?: Metric.EXECUTION_TIME
|
metricFromString(metricElement.unquoted()) ?: Metric.EXECUTION_TIME
|
||||||
else Metric.EXECUTION_TIME
|
else Metric.EXECUTION_TIME
|
||||||
name += metric.suffix
|
name += metric.suffix
|
||||||
val statusElement = data.getRequiredField("status")
|
val statusElement = data.getRequiredField("status")
|
||||||
if (statusElement is JsonLiteral) {
|
if (statusElement is JsonLiteral) {
|
||||||
@@ -300,9 +299,8 @@ class BenchmarkResult(val name: String, val status: Status,
|
|||||||
FAILED("FAILED")
|
FAILED("FAILED")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toJson(): String {
|
override fun serializeFields(): String {
|
||||||
return """
|
return """
|
||||||
{
|
|
||||||
"name": "${name.removeSuffix(metric.suffix)}",
|
"name": "${name.removeSuffix(metric.suffix)}",
|
||||||
"status": "${status.value}",
|
"status": "${status.value}",
|
||||||
"score": ${score},
|
"score": ${score},
|
||||||
@@ -310,10 +308,44 @@ class BenchmarkResult(val name: String, val status: Status,
|
|||||||
"runtimeInUs": ${runtimeInUs},
|
"runtimeInUs": ${runtimeInUs},
|
||||||
"repeat": ${repeat},
|
"repeat": ${repeat},
|
||||||
"warmup": ${warmup}
|
"warmup": ${warmup}
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
val shortName: String
|
val shortName: String
|
||||||
get() = name.removeSuffix(metric.suffix)
|
get() = name.removeSuffix(metric.suffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Entity to describe avarage values which conssists of mean and variance values.
|
||||||
|
data class MeanVariance(val mean: Double, val variance: Double)
|
||||||
|
|
||||||
|
// Processed benchmark result with calculated mean and variance value.
|
||||||
|
open class MeanVarianceBenchmark(name: String, status: BenchmarkResult.Status, score: Double, metric: BenchmarkResult.Metric,
|
||||||
|
runtimeInUs: Double, repeat: Int, warmup: Int, val variance: Double) :
|
||||||
|
BenchmarkResult(name, status, score, metric, runtimeInUs, repeat, warmup) {
|
||||||
|
|
||||||
|
constructor(name: String, score: Double, variance: Double) : this(name, BenchmarkResult.Status.PASSED, score,
|
||||||
|
BenchmarkResult.Metric.EXECUTION_TIME, 0.0, 0, 0, variance)
|
||||||
|
|
||||||
|
companion object : EntityFromJsonFactory<MeanVarianceBenchmark> {
|
||||||
|
|
||||||
|
fun isMeanVarianceBenchmark(data: JsonElement) = data is JsonObject && data.getOptionalField("variance") != null
|
||||||
|
|
||||||
|
override fun create(data: JsonElement): MeanVarianceBenchmark {
|
||||||
|
if (data is JsonObject) {
|
||||||
|
val baseBenchmark = BenchmarkResult.create(data)
|
||||||
|
val variance = elementToDouble(data.getRequiredField("variance"), "variance")
|
||||||
|
return MeanVarianceBenchmark(baseBenchmark.name, baseBenchmark.status, baseBenchmark.score, baseBenchmark.metric,
|
||||||
|
baseBenchmark.runtimeInUs, baseBenchmark.repeat, baseBenchmark.warmup, variance)
|
||||||
|
} else {
|
||||||
|
error("Benchmark entity is expected to be an object. Please, check origin files.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serializeFields(): String {
|
||||||
|
return """
|
||||||
|
${super.serializeFields()},
|
||||||
|
"variance": $variance
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -121,10 +121,12 @@ internal class Parser(val source: String) {
|
|||||||
TC_BEGIN_LIST, TC_BEGIN_OBJ, TC_OTHER, TC_STRING, TC_NULL -> true
|
TC_BEGIN_LIST, TC_BEGIN_OBJ, TC_OTHER, TC_STRING, TC_NULL -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
fun takeStr(): String {
|
fun takeStr(): String {
|
||||||
if (tc != TC_OTHER && tc != TC_STRING) fail(tokenPos, "Expected string or non-null literal")
|
if (tc != TC_OTHER && tc != TC_STRING) fail(tokenPos, "Expected string or non-null literal")
|
||||||
val prevStr = if (offset < 0)
|
val prevStr = if (offset < 0)
|
||||||
String(buf, 0, length) else
|
buf.concatToString(0, length) else
|
||||||
source.substring(offset, offset + length)
|
source.substring(offset, offset + length)
|
||||||
nextToken()
|
nextToken()
|
||||||
return prevStr
|
return prevStr
|
||||||
@@ -1,17 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.analyzer
|
package org.jetbrains.analyzer
|
||||||
|
|||||||
@@ -1,17 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.analyzer
|
package org.jetbrains.analyzer
|
||||||
@@ -29,7 +18,7 @@ actual fun readFile(fileName: String): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
actual fun Double.format(decimalNumber: Int): String =
|
actual fun Double.format(decimalNumber: Int): String =
|
||||||
"%.${decimalNumber}f".format(this)
|
"%.${decimalNumber}f".format(this)
|
||||||
|
|
||||||
actual fun writeToFile(fileName: String, text: String) {
|
actual fun writeToFile(fileName: String, text: String) {
|
||||||
File(fileName).printWriter().use { out ->
|
File(fileName).printWriter().use { out ->
|
||||||
|
|||||||
@@ -1,17 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2017 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.analyzer
|
package org.jetbrains.analyzer
|
||||||
|
|||||||
@@ -1,23 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
@file:OptIn(ExperimentalCli::class)
|
|
||||||
|
import kotlinx.cli.*
|
||||||
import org.jetbrains.analyzer.sendGetRequest
|
import org.jetbrains.analyzer.sendGetRequest
|
||||||
import org.jetbrains.analyzer.readFile
|
import org.jetbrains.analyzer.readFile
|
||||||
import org.jetbrains.analyzer.SummaryBenchmarksReport
|
import org.jetbrains.analyzer.SummaryBenchmarksReport
|
||||||
import kotlinx.cli.*
|
|
||||||
import org.jetbrains.renders.*
|
import org.jetbrains.renders.*
|
||||||
import org.jetbrains.report.*
|
import org.jetbrains.report.*
|
||||||
import org.jetbrains.report.json.*
|
import org.jetbrains.report.json.*
|
||||||
@@ -56,7 +45,7 @@ object ArtifactoryConnector : Connector() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object TeamCityConnector: Connector() {
|
object TeamCityConnector : Connector() {
|
||||||
override val connectorPrefix = "teamcity:"
|
override val connectorPrefix = "teamcity:"
|
||||||
val teamCityUrl = "http://buildserver.labs.intellij.net"
|
val teamCityUrl = "http://buildserver.labs.intellij.net"
|
||||||
|
|
||||||
@@ -76,11 +65,28 @@ object TeamCityConnector: Connector() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
fun getFileContent(fileName: String, user: String? = null): String {
|
||||||
return when {
|
return when {
|
||||||
ArtifactoryConnector.isCompatible(fileName) -> ArtifactoryConnector.getFileContent(fileName, user)
|
ArtifactoryConnector.isCompatible(fileName) -> ArtifactoryConnector.getFileContent(fileName, user)
|
||||||
TeamCityConnector.isCompatible(fileName) -> TeamCityConnector.getFileContent(fileName, user)
|
TeamCityConnector.isCompatible(fileName) -> TeamCityConnector.getFileContent(fileName, user)
|
||||||
else -> readFile(fileName)
|
fileName.endsWith(".json") -> readFile(fileName)
|
||||||
|
else -> DBServerConnector.getFileContent(fileName, user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,12 +114,16 @@ fun parseNormalizeResults(results: String): Map<String, Map<String, Double>> {
|
|||||||
return parsedNormalizeResults
|
return parsedNormalizeResults
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mergeCompilerFlags(reports: List<BenchmarksReport>) =
|
fun mergeCompilerFlags(reports: List<BenchmarksReport>): List<String> {
|
||||||
reports.map {
|
val flagsMap = mutableMapOf<String, MutableList<String>>()
|
||||||
|
reports.forEach {
|
||||||
val benchmarks = it.benchmarks.values.flatten().asSequence().filter { it.metric == BenchmarkResult.Metric.COMPILE_TIME }
|
val benchmarks = it.benchmarks.values.flatten().asSequence().filter { it.metric == BenchmarkResult.Metric.COMPILE_TIME }
|
||||||
.map { it.shortName }.distinct().sorted().joinToString()
|
.map { it.shortName }.toList()
|
||||||
"${it.compiler.backend.flags.joinToString()} for [$benchmarks]"
|
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>) =
|
fun mergeReportsWithDetailedFlags(reports: List<BenchmarksReport>) =
|
||||||
if (reports.size > 1) {
|
if (reports.size > 1) {
|
||||||
@@ -129,75 +139,9 @@ fun mergeReportsWithDetailedFlags(reports: List<BenchmarksReport>) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
class Summary: Subcommand("summary", "Output summary information") {
|
|
||||||
val exec by option(ArgType.Choice(listOf("samples", "geomean")),
|
|
||||||
description = "Execution time way of calculation").default("geomean")
|
|
||||||
val execSamples by option(ArgType.String, "exec-samples",
|
|
||||||
description = "Samples used for execution time metric (value 'all' allows use all samples)")
|
|
||||||
.delimiter(",")
|
|
||||||
val execNormalize by option(ArgType.String, "exec-normalize",
|
|
||||||
description = "File with golden results which should be used for normalization")
|
|
||||||
val compile by option(ArgType.Choice(listOf("samples", "geomean")),
|
|
||||||
description = "Compile time way of calculation").default("geomean")
|
|
||||||
val compileSamples by option(ArgType.String, "compile-samples",
|
|
||||||
description = "Samples used for compile time metric (value 'all' allows use all samples)")
|
|
||||||
.delimiter(",")
|
|
||||||
val compileNormalize by option(ArgType.String, "compile-normalize",
|
|
||||||
description = "File with golden results which should be used for normalization")
|
|
||||||
val codesize by option(ArgType.Choice(listOf("samples", "geomean")),
|
|
||||||
description = "Code size way of calculation").default("geomean")
|
|
||||||
val codesizeSamples by option(ArgType.String, "codesize-samples",
|
|
||||||
description = "Samples used for code size metric (value 'all' allows use all samples)").delimiter(",")
|
|
||||||
val codesizeNormalize by option(ArgType.String, "codesize-normalize",
|
|
||||||
description = "File with golden results which should be used for normalization")
|
|
||||||
val user by option(ArgType.String, shortName = "u", description = "User access information for authorization")
|
|
||||||
val mainReport by argument(ArgType.String, description = "Main report for analysis")
|
|
||||||
|
|
||||||
override fun execute() {
|
|
||||||
val reportsList = getBenchmarkReport(mainReport, user)
|
|
||||||
val report = reportsList.reduce { result, it ->
|
|
||||||
result.merge(it)
|
|
||||||
}
|
|
||||||
val benchsReport = SummaryBenchmarksReport(report)
|
|
||||||
val results = mutableListOf<String>()
|
|
||||||
val executionNormalize = execNormalize?.let {
|
|
||||||
parseNormalizeResults(getFileContent(it))
|
|
||||||
}
|
|
||||||
val compileNormalize = compileNormalize?.let {
|
|
||||||
parseNormalizeResults(getFileContent(it))
|
|
||||||
}
|
|
||||||
val codesizeNormalize = codesizeNormalize?.let {
|
|
||||||
parseNormalizeResults(getFileContent(it))
|
|
||||||
}
|
|
||||||
|
|
||||||
results.apply {
|
|
||||||
add(benchsReport.failedBenchmarks.size.toString())
|
|
||||||
if (!execSamples.isEmpty()) {
|
|
||||||
val filterExec = if (execSamples.first() == "all") null else execSamples
|
|
||||||
add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.EXECUTION_TIME,
|
|
||||||
exec == "geomean", filterExec, executionNormalize).joinToString(";"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!compileSamples.isEmpty()) {
|
|
||||||
val filterCompile = if (compileSamples.first() == "all") null else compileSamples
|
|
||||||
add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.COMPILE_TIME,
|
|
||||||
compile == "geomean", filterCompile, compileNormalize).joinToString(";"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!codesizeSamples.isEmpty()) {
|
|
||||||
val filterCodesize = if (codesizeSamples.first() == "all") null else codesizeSamples
|
|
||||||
add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.CODE_SIZE,
|
|
||||||
codesize == "geomean", filterCodesize, codesizeNormalize).joinToString(";"))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
println(results.joinToString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val action = Summary()
|
|
||||||
// Parse args.
|
// Parse args.
|
||||||
val argParser = ArgParser("benchmarksAnalyzer")
|
val argParser = ArgParser("benchmarksAnalyzer")
|
||||||
argParser.subcommands(action)
|
|
||||||
val mainReport by argParser.argument(ArgType.String, description = "Main report for analysis")
|
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 compareToReport by argParser.argument(ArgType.String, description = "Report to compare to").optional()
|
||||||
|
|
||||||
@@ -207,26 +151,25 @@ fun main(args: Array<String>) {
|
|||||||
val useShortForm by argParser.option(ArgType.Boolean, "short", "s",
|
val useShortForm by argParser.option(ArgType.Boolean, "short", "s",
|
||||||
"Show short version of report").default(false)
|
"Show short version of report").default(false)
|
||||||
val renders by argParser.option(ArgType.Choice(listOf("text", "html", "teamcity", "statistics", "metrics")),
|
val renders by argParser.option(ArgType.Choice(listOf("text", "html", "teamcity", "statistics", "metrics")),
|
||||||
shortName = "r", description = "Renders for showing information").multiple().default(listOf("text"))
|
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")
|
val user by argParser.option(ArgType.String, shortName = "u", description = "User access information for authorization")
|
||||||
|
|
||||||
if (argParser.parse(args).commandName == "benchmarksAnalyzer") {
|
argParser.parse(args)
|
||||||
// Read contents of file.
|
// Read contents of file.
|
||||||
val mainBenchsReport = mergeReportsWithDetailedFlags(getBenchmarkReport(mainReport, user))
|
val mainBenchsReport = mergeReportsWithDetailedFlags(getBenchmarkReport(mainReport, user))
|
||||||
|
|
||||||
var compareToBenchsReport = compareToReport?.let {
|
var compareToBenchsReport = compareToReport?.let {
|
||||||
mergeReportsWithDetailedFlags(getBenchmarkReport(it, user))
|
mergeReportsWithDetailedFlags(getBenchmarkReport(it, user))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate comparasion report.
|
// Generate comparasion report.
|
||||||
val summaryReport = SummaryBenchmarksReport(mainBenchsReport,
|
val summaryReport = SummaryBenchmarksReport(mainBenchsReport,
|
||||||
compareToBenchsReport,
|
compareToBenchsReport,
|
||||||
epsValue)
|
epsValue)
|
||||||
|
|
||||||
var outputFile = output
|
var outputFile = output
|
||||||
renders.forEach {
|
renders.forEach {
|
||||||
Render.getRenderByName(it).print(summaryReport, useShortForm, outputFile)
|
Render.getRenderByName(it).print(summaryReport, useShortForm, outputFile)
|
||||||
outputFile = null
|
outputFile = null
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package org.jetbrains.analyzer
|
|
||||||
import org.jetbrains.report.BenchmarkResult
|
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
|
||||||
import kotlin.math.pow
|
|
||||||
import kotlin.math.sqrt
|
|
||||||
|
|
||||||
// Entity to describe avarage values which conssists of mean and variance values.
|
|
||||||
data class MeanVariance(val mean: Double, val variance: Double) {
|
|
||||||
override fun toString(): String {
|
|
||||||
val format = { number: Double -> number.format(2)}
|
|
||||||
return "${format(mean)} ± ${format(variance)}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Composite benchmark which descibes avarage result for several runs and contains mean and variance value.
|
|
||||||
data class MeanVarianceBenchmark(val meanBenchmark: BenchmarkResult, val varianceBenchmark: BenchmarkResult) {
|
|
||||||
|
|
||||||
// Calculate difference in percentage compare to another.
|
|
||||||
fun calcPercentageDiff(other: MeanVarianceBenchmark): MeanVariance {
|
|
||||||
assert(other.meanBenchmark.score >= 0 &&
|
|
||||||
other.varianceBenchmark.score >= 0 &&
|
|
||||||
other.meanBenchmark.score - other.varianceBenchmark.score != 0.0,
|
|
||||||
{ "Mean and variance should be positive and not equal!" })
|
|
||||||
val exactMean = (meanBenchmark.score - other.meanBenchmark.score) / other.meanBenchmark.score
|
|
||||||
// Analyze intervals. Calculate difference between border points.
|
|
||||||
val (bigValue, smallValue) = if (meanBenchmark.score > other.meanBenchmark.score) Pair(this, other) else Pair(other, this)
|
|
||||||
val bigValueIntervalStart = bigValue.meanBenchmark.score - bigValue.varianceBenchmark.score
|
|
||||||
val bigValueIntervalEnd = bigValue.meanBenchmark.score + bigValue.varianceBenchmark.score
|
|
||||||
val smallValueIntervalStart = smallValue.meanBenchmark.score - smallValue.varianceBenchmark.score
|
|
||||||
val smallValueIntervalEnd = smallValue.meanBenchmark.score + smallValue.varianceBenchmark.score
|
|
||||||
if (smallValueIntervalEnd > bigValueIntervalStart) {
|
|
||||||
// Interval intersect.
|
|
||||||
return MeanVariance(0.0, 0.0)
|
|
||||||
}
|
|
||||||
val mean = ((smallValueIntervalEnd - bigValueIntervalStart) / bigValueIntervalStart) *
|
|
||||||
(if (meanBenchmark.score > other.meanBenchmark.score) -1 else 1)
|
|
||||||
|
|
||||||
val maxValueChange = ((bigValueIntervalEnd - smallValueIntervalEnd) / bigValueIntervalEnd)
|
|
||||||
val minValueChange = ((bigValueIntervalStart - smallValueIntervalStart) / bigValueIntervalStart)
|
|
||||||
val variance = abs(abs(mean) - max(minValueChange, maxValueChange))
|
|
||||||
return MeanVariance(mean * 100, variance * 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate ratio value compare to another.
|
|
||||||
fun calcRatio(other: MeanVarianceBenchmark): MeanVariance {
|
|
||||||
assert(other.meanBenchmark.score >= 0 &&
|
|
||||||
other.varianceBenchmark.score >= 0 &&
|
|
||||||
other.meanBenchmark.score - other.varianceBenchmark.score != 0.0,
|
|
||||||
{ "Mean and variance should be positive and not equal!" })
|
|
||||||
val mean = meanBenchmark.score / other.meanBenchmark.score
|
|
||||||
val minRatio = (meanBenchmark.score - varianceBenchmark.score) / (other.meanBenchmark.score + other.varianceBenchmark.score)
|
|
||||||
val maxRatio = (meanBenchmark.score + varianceBenchmark.score) / (other.meanBenchmark.score - other.varianceBenchmark.score)
|
|
||||||
val ratioConfInt = min(abs(minRatio - mean), abs(maxRatio - mean))
|
|
||||||
return MeanVariance(mean, ratioConfInt)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String =
|
|
||||||
"${meanBenchmark.score.format()} ± ${varianceBenchmark.score.format()}"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun geometricMean(values: Collection<Double>, totalNumber: Int = values.size) =
|
|
||||||
with(values.asSequence().filter { it != 0.0 }) {
|
|
||||||
if (count() == 0) {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
map { it.pow(1.0 / totalNumber) }.reduce { a, b -> a * b }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun computeMeanVariance(samples: List<Double>): MeanVariance {
|
|
||||||
val zStar = 1.67 // Critical point for 90% confidence of normal distribution.
|
|
||||||
val mean = samples.sum() / samples.size
|
|
||||||
val variance = samples.indices.sumByDouble { (samples[it] - mean) * (samples[it] - mean) } / samples.size
|
|
||||||
val confidenceInterval = sqrt(variance / samples.size) * zStar
|
|
||||||
return MeanVariance(mean, confidenceInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate avarage results for bencmarks (each becnhmark can be run several times).
|
|
||||||
fun collectMeanResults(benchmarks: Map<String, List<BenchmarkResult>>): BenchmarksTable {
|
|
||||||
return benchmarks.map {(name, resultsSet) ->
|
|
||||||
val repeatedSequence = IntArray(resultsSet.size)
|
|
||||||
var metric = BenchmarkResult.Metric.EXECUTION_TIME
|
|
||||||
var currentStatus = BenchmarkResult.Status.PASSED
|
|
||||||
var currentWarmup = -1
|
|
||||||
|
|
||||||
// Collect common becnhmark values and check them.
|
|
||||||
resultsSet.forEachIndexed { index, result ->
|
|
||||||
// If there was at least one failure, summary is marked as failure.
|
|
||||||
if (result.status == BenchmarkResult.Status.FAILED) {
|
|
||||||
currentStatus = result.status
|
|
||||||
}
|
|
||||||
repeatedSequence[index] = result.repeat
|
|
||||||
if (currentWarmup != -1)
|
|
||||||
if (result.warmup != currentWarmup)
|
|
||||||
println("Check data consistency. Warmup value for benchmark '${result.name}' differs.")
|
|
||||||
currentWarmup = result.warmup
|
|
||||||
metric = result.metric
|
|
||||||
}
|
|
||||||
|
|
||||||
repeatedSequence.sort()
|
|
||||||
// Check if there are missed loop during running benchmarks.
|
|
||||||
repeatedSequence.forEachIndexed { index, element ->
|
|
||||||
if (index != 0)
|
|
||||||
if ((element - repeatedSequence[index - 1]) != 1)
|
|
||||||
println("Check data consistency. For benchmark '$name' there is no run" +
|
|
||||||
" between ${repeatedSequence[index - 1]} and $element.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mean and variance benchmarks result.
|
|
||||||
val scoreMeanVariance = computeMeanVariance(resultsSet.map { it.score })
|
|
||||||
val runtimeInUsMeanVariance = computeMeanVariance(resultsSet.map { it.runtimeInUs })
|
|
||||||
val meanBenchmark = BenchmarkResult(name, currentStatus, scoreMeanVariance.mean, metric,
|
|
||||||
runtimeInUsMeanVariance.mean, repeatedSequence[resultsSet.size - 1],
|
|
||||||
currentWarmup)
|
|
||||||
val varianceBenchmark = BenchmarkResult(name, currentStatus, scoreMeanVariance.variance, metric,
|
|
||||||
runtimeInUsMeanVariance.variance, repeatedSequence[resultsSet.size - 1],
|
|
||||||
currentWarmup)
|
|
||||||
name to MeanVarianceBenchmark(meanBenchmark, varianceBenchmark)
|
|
||||||
}.toMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun collectBenchmarksDurations(benchmarks: Map<String, List<BenchmarkResult>>): Map<String, Double> =
|
|
||||||
benchmarks.map { (name, resultsSet) ->
|
|
||||||
name to resultsSet.sumByDouble { it.runtimeInUs }
|
|
||||||
}.toMap()
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.analyzer
|
|
||||||
|
|
||||||
expect fun readFile(fileName: String): String
|
|
||||||
expect fun Double.format(decimalNumber: Int = 4): String
|
|
||||||
expect fun writeToFile(fileName: String, text: String)
|
|
||||||
expect fun assert(value: Boolean, lazyMessage: () -> Any)
|
|
||||||
expect fun sendGetRequest(url: String, user: String? = null, password: String? = null,
|
|
||||||
followLocation: Boolean = false) : String
|
|
||||||
@@ -1,20 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.jetbrains.renders
|
package org.jetbrains.renders
|
||||||
|
|
||||||
import org.jetbrains.analyzer.*
|
import org.jetbrains.analyzer.*
|
||||||
@@ -556,7 +544,7 @@ class HTMLRender: Render() {
|
|||||||
bucket: Map<String, ScoreChange>? = null, rowStyle: String? = null) {
|
bucket: Map<String, ScoreChange>? = null, rowStyle: String? = null) {
|
||||||
if (bucket != null && !bucket.isEmpty()) {
|
if (bucket != null && !bucket.isEmpty()) {
|
||||||
// Find max ratio.
|
// Find max ratio.
|
||||||
val maxRatio = bucket.values.map { it.second.mean }.max()!!
|
val maxRatio = bucket.values.map { it.second.mean }.maxOrNull()!!
|
||||||
// There are changes in performance.
|
// There are changes in performance.
|
||||||
// Output changed benchmarks.
|
// Output changed benchmarks.
|
||||||
for ((name, change) in bucket) {
|
for ((name, change) in bucket) {
|
||||||
@@ -565,19 +553,19 @@ class HTMLRender: Render() {
|
|||||||
attributes["style"] = rowStyle
|
attributes["style"] = rowStyle
|
||||||
}
|
}
|
||||||
th { +name }
|
th { +name }
|
||||||
td { +"${fullSet.getValue(name).first}" }
|
td { +"${fullSet.getValue(name).first?.description}" }
|
||||||
td { +"${fullSet.getValue(name).second}" }
|
td { +"${fullSet.getValue(name).second?.description}" }
|
||||||
td {
|
td {
|
||||||
attributes["bgcolor"] = ColoredCell(if (bucket.values.first().first.mean == 0.0) null
|
attributes["bgcolor"] = ColoredCell(if (bucket.values.first().first.mean == 0.0) null
|
||||||
else change.first.mean / abs(bucket.values.first().first.mean))
|
else change.first.mean / abs(bucket.values.first().first.mean))
|
||||||
.backgroundStyle
|
.backgroundStyle
|
||||||
+"${change.first.toString() + " %"}"
|
+"${change.first.description + " %"}"
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
val scaledRatio = if (maxRatio == 0.0) null else change.second.mean / maxRatio
|
val scaledRatio = if (maxRatio == 0.0) null else change.second.mean / maxRatio
|
||||||
attributes["bgcolor"] = ColoredCell(scaledRatio,
|
attributes["bgcolor"] = ColoredCell(scaledRatio,
|
||||||
borderPositive = { cellValue -> cellValue > 1.0 / maxRatio }).backgroundStyle
|
borderPositive = { cellValue -> cellValue > 1.0 / maxRatio }).backgroundStyle
|
||||||
+"${change.second}"
|
+"${change.second.description}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -587,8 +575,8 @@ class HTMLRender: Render() {
|
|||||||
for ((name, value) in fullSet) {
|
for ((name, value) in fullSet) {
|
||||||
tr {
|
tr {
|
||||||
th { +name }
|
th { +name }
|
||||||
td { +"${value.first?.toString() ?: placeholder}" }
|
td { +"${value.first?.description ?: placeholder}" }
|
||||||
td { +"${value.second?.toString() ?: placeholder}" }
|
td { +"${value.second?.description ?: placeholder}" }
|
||||||
td { +placeholder }
|
td { +placeholder }
|
||||||
td { +placeholder }
|
td { +placeholder }
|
||||||
}
|
}
|
||||||
@@ -619,11 +607,11 @@ class HTMLRender: Render() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val geoMeanChangeMap = report.geoMeanScoreChange?.
|
val geoMeanChangeMap = report.geoMeanScoreChange?.
|
||||||
let { mapOf(report.geoMeanBenchmark.first!!.meanBenchmark.name to report.geoMeanScoreChange!!) }
|
let { mapOf(report.geoMeanBenchmark.first!!.name to report.geoMeanScoreChange!!) }
|
||||||
|
|
||||||
tbody {
|
tbody {
|
||||||
renderBenchmarksDetails(
|
renderBenchmarksDetails(
|
||||||
mutableMapOf(report.geoMeanBenchmark.first!!.meanBenchmark.name to report.geoMeanBenchmark),
|
mutableMapOf(report.geoMeanBenchmark.first!!.name to report.geoMeanBenchmark),
|
||||||
geoMeanChangeMap, "border-bottom: 2.3pt solid black; border-top: 2.3pt solid black")
|
geoMeanChangeMap, "border-bottom: 2.3pt solid black; border-top: 2.3pt solid black")
|
||||||
renderBenchmarksDetails(report.mergedReport, report.regressions)
|
renderBenchmarksDetails(report.mergedReport, report.regressions)
|
||||||
renderBenchmarksDetails(report.mergedReport, report.improvements)
|
renderBenchmarksDetails(report.mergedReport, report.improvements)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
+4
-16
@@ -1,20 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.jetbrains.renders
|
package org.jetbrains.renders
|
||||||
|
|
||||||
import org.jetbrains.analyzer.*
|
import org.jetbrains.analyzer.*
|
||||||
@@ -28,10 +16,10 @@ class MetricResultsRender: Render() {
|
|||||||
override fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean): String {
|
override fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean): String {
|
||||||
val results = report.mergedReport.map { entry ->
|
val results = report.mergedReport.map { entry ->
|
||||||
buildString {
|
buildString {
|
||||||
val metric = entry.value.first!!.meanBenchmark.metric
|
val metric = entry.value.first!!.metric
|
||||||
append("{ \"benchmarkName\": \"${entry.key.removeSuffix(metric.suffix)}\",")
|
append("{ \"benchmarkName\": \"${entry.key.removeSuffix(metric.suffix)}\",")
|
||||||
append("\"metric\": \"${metric}\",")
|
append("\"metric\": \"${metric}\",")
|
||||||
append("\"value\": \"${entry.value.first!!.meanBenchmark.score}\" }")
|
append("\"value\": \"${entry.value.first!!.score}\" }")
|
||||||
}
|
}
|
||||||
}.joinToString(", ")
|
}.joinToString(", ")
|
||||||
return "[ $results ]"
|
return "[ $results ]"
|
||||||
|
|||||||
@@ -1,24 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.jetbrains.renders
|
package org.jetbrains.renders
|
||||||
|
|
||||||
import org.jetbrains.analyzer.*
|
import org.jetbrains.analyzer.*
|
||||||
import org.jetbrains.report.BenchmarkResult
|
import org.jetbrains.report.*
|
||||||
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
@@ -150,23 +138,23 @@ class TextRender: Render() {
|
|||||||
|
|
||||||
private fun printBenchmarksDetails(fullSet: Map<String, SummaryBenchmark>,
|
private fun printBenchmarksDetails(fullSet: Map<String, SummaryBenchmark>,
|
||||||
bucket: Map<String, ScoreChange>? = null) {
|
bucket: Map<String, ScoreChange>? = null) {
|
||||||
|
val placeholder = "-"
|
||||||
if (bucket != null) {
|
if (bucket != null) {
|
||||||
// There are changes in performance.
|
// There are changes in performance.
|
||||||
// Output changed benchmarks.
|
// Output changed benchmarks.
|
||||||
for ((name, change) in bucket) {
|
for ((name, change) in bucket) {
|
||||||
append(formatColumn(name, true) +
|
append(formatColumn(name, true) +
|
||||||
formatColumn(fullSet.getValue(name).first.toString()) +
|
formatColumn(fullSet.getValue(name).first?.description ?: placeholder) +
|
||||||
formatColumn(fullSet.getValue(name).second.toString()) +
|
formatColumn(fullSet.getValue(name).second?.description ?: placeholder) +
|
||||||
formatColumn(change.first.toString() + " %") +
|
formatColumn(change.first.description + " %") +
|
||||||
formatColumn(change.second.toString()))
|
formatColumn(change.second.description))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Output all values without performance changes.
|
// Output all values without performance changes.
|
||||||
val placeholder = "-"
|
|
||||||
for ((name, value) in fullSet) {
|
for ((name, value) in fullSet) {
|
||||||
append(formatColumn(name, true) +
|
append(formatColumn(name, true) +
|
||||||
formatColumn(value.first?.toString() ?: placeholder) +
|
formatColumn(value.first?.description ?: placeholder) +
|
||||||
formatColumn(value.second?.toString() ?: placeholder) +
|
formatColumn(value.second?.description ?: placeholder) +
|
||||||
formatColumn(placeholder) +
|
formatColumn(placeholder) +
|
||||||
formatColumn(placeholder))
|
formatColumn(placeholder))
|
||||||
}
|
}
|
||||||
@@ -201,9 +189,9 @@ class TextRender: Render() {
|
|||||||
val tableWidth = printPerformanceTableHeader()
|
val tableWidth = printPerformanceTableHeader()
|
||||||
// Print geometric mean.
|
// Print geometric mean.
|
||||||
val geoMeanChangeMap = report.geoMeanScoreChange?.
|
val geoMeanChangeMap = report.geoMeanScoreChange?.
|
||||||
let { mapOf(report.geoMeanBenchmark.first!!.meanBenchmark.name to report.geoMeanScoreChange!!) }
|
let { mapOf(report.geoMeanBenchmark.first!!.name to report.geoMeanScoreChange!!) }
|
||||||
printBenchmarksDetails(
|
printBenchmarksDetails(
|
||||||
mutableMapOf(report.geoMeanBenchmark.first!!.meanBenchmark.name to report.geoMeanBenchmark),
|
mutableMapOf(report.geoMeanBenchmark.first!!.name to report.geoMeanBenchmark),
|
||||||
geoMeanChangeMap)
|
geoMeanChangeMap)
|
||||||
printTableLineSeparator(tableWidth)
|
printTableLineSeparator(tableWidth)
|
||||||
printBenchmarksDetails(report.mergedReport, report.regressions)
|
printBenchmarksDetails(report.mergedReport, report.regressions)
|
||||||
|
|||||||
@@ -1,20 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.jetbrains.renders
|
package org.jetbrains.renders
|
||||||
|
|
||||||
import org.jetbrains.analyzer.*
|
import org.jetbrains.analyzer.*
|
||||||
|
|||||||
+13
-22
@@ -1,24 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.jetbrains.renders
|
package org.jetbrains.renders
|
||||||
|
|
||||||
import org.jetbrains.analyzer.*
|
import org.jetbrains.analyzer.*
|
||||||
import org.jetbrains.report.BenchmarkResult
|
import org.jetbrains.report.BenchmarkResult
|
||||||
|
import org.jetbrains.report.MeanVarianceBenchmark
|
||||||
|
|
||||||
// Report render to text format.
|
// Report render to text format.
|
||||||
class TeamCityStatisticsRender: Render() {
|
class TeamCityStatisticsRender: Render() {
|
||||||
@@ -34,9 +23,8 @@ class TeamCityStatisticsRender: Render() {
|
|||||||
|
|
||||||
// For current benchmarks print score as TeamCity Test Metadata
|
// For current benchmarks print score as TeamCity Test Metadata
|
||||||
report.currentMeanVarianceBenchmarks.forEach { benchmark ->
|
report.currentMeanVarianceBenchmarks.forEach { benchmark ->
|
||||||
renderBenchmark(benchmark.meanBenchmark, currentDurations[benchmark.meanBenchmark.name]!!)
|
renderBenchmark(benchmark, currentDurations[benchmark.name]!!)
|
||||||
renderSummaryBecnhmarkValue(benchmark.meanBenchmark, "Mean")
|
renderSummaryBecnhmarkValue(benchmark)
|
||||||
renderSummaryBecnhmarkValue(benchmark.varianceBenchmark, "Variance")
|
|
||||||
}
|
}
|
||||||
content.append("##teamcity[testSuiteFinished name='Benchmarks']\n")
|
content.append("##teamcity[testSuiteFinished name='Benchmarks']\n")
|
||||||
|
|
||||||
@@ -46,9 +34,12 @@ class TeamCityStatisticsRender: Render() {
|
|||||||
return content.toString()
|
return content.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderSummaryBecnhmarkValue(benchmark: BenchmarkResult, metric: String) =
|
private fun renderSummaryBecnhmarkValue(benchmark: MeanVarianceBenchmark) {
|
||||||
content.append("##teamcity[testMetadata testName='${benchmark.name}' name='$metric'" +
|
content.append("##teamcity[testMetadata testName='${benchmark.name}' name='Mean'" +
|
||||||
" type='number' value='${benchmark.score}']\n")
|
" 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
|
// Produce benchmark as test in TeamCity
|
||||||
private fun renderBenchmark(benchmark: BenchmarkResult , duration: Double) {
|
private fun renderBenchmark(benchmark: BenchmarkResult , duration: Double) {
|
||||||
@@ -61,7 +52,7 @@ class TeamCityStatisticsRender: Render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun renderGeometricMean(geoMeanBenchmark: MeanVarianceBenchmark) {
|
private fun renderGeometricMean(geoMeanBenchmark: MeanVarianceBenchmark) {
|
||||||
content.append("##teamcity[buildStatisticValue key='Geometric mean' value='${geoMeanBenchmark.meanBenchmark.score}']\n")
|
content.append("##teamcity[buildStatisticValue key='Geometric mean' value='${geoMeanBenchmark.score}']\n")
|
||||||
content.append("##teamcity[buildStatisticValue key='Geometric mean variance' value='${geoMeanBenchmark.varianceBenchmark.score}']\n")
|
content.append("##teamcity[buildStatisticValue key='Geometric mean variance' value='${geoMeanBenchmark.variance}']\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,37 +1,21 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2018 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.jetbrains.analyzer
|
package org.jetbrains.analyzer
|
||||||
|
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import org.jetbrains.report.BenchmarkResult
|
import org.jetbrains.report.BenchmarkResult
|
||||||
|
import org.jetbrains.report.MeanVarianceBenchmark
|
||||||
|
|
||||||
class AnalyzerTests {
|
class AnalyzerTests {
|
||||||
private val eps = 0.000001
|
private val eps = 0.000001
|
||||||
|
|
||||||
private fun createMeanVarianceBenchmarks(): Pair<MeanVarianceBenchmark, MeanVarianceBenchmark> {
|
private fun createMeanVarianceBenchmarks(): Pair<MeanVarianceBenchmark, MeanVarianceBenchmark> {
|
||||||
val firstMean = BenchmarkResult("testBenchmark", BenchmarkResult.Status.PASSED, 9.0, BenchmarkResult.Metric.EXECUTION_TIME, 9.0, 10, 10)
|
val first = MeanVarianceBenchmark("testBenchmark", BenchmarkResult.Status.PASSED, 9.0, BenchmarkResult.Metric.EXECUTION_TIME, 9.0, 10, 10, 0.0001)
|
||||||
val firstVariance = BenchmarkResult("testBenchmark", BenchmarkResult.Status.PASSED, 0.0001, BenchmarkResult.Metric.EXECUTION_TIME, 0.0001, 10, 10)
|
val second = MeanVarianceBenchmark("testBenchmark", BenchmarkResult.Status.PASSED, 10.0, BenchmarkResult.Metric.EXECUTION_TIME, 10.0, 10, 10, 0.0001)
|
||||||
val first = MeanVarianceBenchmark(firstMean, firstVariance)
|
|
||||||
|
|
||||||
val secondMean = BenchmarkResult("testBenchmark", BenchmarkResult.Status.PASSED, 10.0, BenchmarkResult.Metric.EXECUTION_TIME, 10.0, 10, 10)
|
|
||||||
val secondVariance = BenchmarkResult("testBenchmark", BenchmarkResult.Status.PASSED, 0.0001, BenchmarkResult.Metric.EXECUTION_TIME, 0.0001, 10, 10)
|
|
||||||
val second = MeanVarianceBenchmark(secondMean, secondVariance)
|
|
||||||
|
|
||||||
return Pair(first, second)
|
return Pair(first, second)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ buildscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply plugin: 'kotlin-multiplatform'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||||
@@ -30,22 +32,33 @@ repositories {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'kotlin2js'
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion"
|
||||||
|
}
|
||||||
|
kotlin.srcDir '../benchmarks/shared/src'
|
||||||
|
}
|
||||||
|
jsMain {
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
|
||||||
|
implementation(npm("aws-sdk", "~2.670.0"))
|
||||||
|
}
|
||||||
|
kotlin.srcDir 'src/main/kotlin'
|
||||||
|
kotlin.srcDir 'src/main/kotlin-js'
|
||||||
|
kotlin.srcDir 'shared/src/main/kotlin'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
targets {
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
|
fromPreset(presets.js, 'js') {
|
||||||
}
|
nodejs()
|
||||||
|
compilations.main.kotlinOptions {
|
||||||
|
outputFile = "${projectDir}/server/app.js"
|
||||||
sourceSets {
|
moduleKind = "commonjs"
|
||||||
main.resources.srcDirs += "resources"
|
sourceMap = true
|
||||||
main.kotlin.srcDirs += "src"
|
}
|
||||||
main.kotlin.srcDirs += "../benchmarks/shared/src"
|
}
|
||||||
main.kotlin.srcDirs += "shared/src"
|
}
|
||||||
}
|
|
||||||
|
|
||||||
compileKotlin2Js {
|
|
||||||
kotlinOptions.outputFile = "${projectDir}/server/app.js"
|
|
||||||
kotlinOptions.moduleKind = "commonjs"
|
|
||||||
kotlinOptions.sourceMap = true
|
|
||||||
}
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
+183
@@ -0,0 +1,183 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
Vendored
+100
@@ -0,0 +1,100 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
+93
-203
@@ -4,32 +4,6 @@
|
|||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/concat-stream": {
|
|
||||||
"version": "1.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.0.tgz",
|
|
||||||
"integrity": "sha1-OU2+C7X+5Gs42JZzXoto7yOQ0A0=",
|
|
||||||
"requires": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/form-data": {
|
|
||||||
"version": "0.0.33",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz",
|
|
||||||
"integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=",
|
|
||||||
"requires": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/node": {
|
|
||||||
"version": "9.6.44",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.44.tgz",
|
|
||||||
"integrity": "sha512-hR+jK4kSOX+u5aQ6Zj6az5N4l1AeXH37/sOenbgxJvkO450C5qQL4/1twdufIMLC3o3hNfJNrPfQn0ivMdKfPg=="
|
|
||||||
},
|
|
||||||
"@types/qs": {
|
|
||||||
"version": "6.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.5.2.tgz",
|
|
||||||
"integrity": "sha512-47kAAs3yV/hROraCTQYDMh4p/6zI9+gtssjD0kq9OWsGdLcBge59rl49FnCuJ+iWxEKiqFz6KXzeGH5DRVjNJA=="
|
|
||||||
},
|
|
||||||
"accepts": {
|
"accepts": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
|
||||||
@@ -44,15 +18,26 @@
|
|||||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||||
},
|
},
|
||||||
"asap": {
|
"aws-sdk": {
|
||||||
"version": "2.0.6",
|
"version": "2.670.0",
|
||||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.670.0.tgz",
|
||||||
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
|
"integrity": "sha512-hGRnZtp1wDUh6hZRBHO0Ki7thx/xbRlIEiTKlWes+f/0E1Nhm3KpelsBZ3L/Q6y1ragwkQd4Q720AmWEqemLyA==",
|
||||||
|
"requires": {
|
||||||
|
"buffer": "4.9.1",
|
||||||
|
"events": "1.1.1",
|
||||||
|
"ieee754": "1.1.13",
|
||||||
|
"jmespath": "0.15.0",
|
||||||
|
"querystring": "0.2.0",
|
||||||
|
"sax": "1.2.1",
|
||||||
|
"url": "0.10.3",
|
||||||
|
"uuid": "3.3.2",
|
||||||
|
"xml2js": "0.4.19"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"asynckit": {
|
"base64-js": {
|
||||||
"version": "0.4.0",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
||||||
},
|
},
|
||||||
"body-parser": {
|
"body-parser": {
|
||||||
"version": "1.18.3",
|
"version": "1.18.3",
|
||||||
@@ -81,40 +66,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"buffer-from": {
|
"buffer": {
|
||||||
"version": "1.1.1",
|
"version": "4.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
|
||||||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
|
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
|
||||||
|
"requires": {
|
||||||
|
"base64-js": "^1.0.2",
|
||||||
|
"ieee754": "^1.1.4",
|
||||||
|
"isarray": "^1.0.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"bytes": {
|
"bytes": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
||||||
},
|
},
|
||||||
"caseless": {
|
|
||||||
"version": "0.12.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
|
||||||
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
|
|
||||||
},
|
|
||||||
"combined-stream": {
|
|
||||||
"version": "1.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
|
|
||||||
"integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
|
|
||||||
"requires": {
|
|
||||||
"delayed-stream": "~1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"concat-stream": {
|
|
||||||
"version": "1.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
|
|
||||||
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
|
|
||||||
"requires": {
|
|
||||||
"buffer-from": "^1.0.0",
|
|
||||||
"inherits": "^2.0.3",
|
|
||||||
"readable-stream": "^2.2.2",
|
|
||||||
"typedarray": "^0.0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"content-disposition": {
|
"content-disposition": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
|
||||||
@@ -135,11 +101,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
|
||||||
},
|
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
@@ -155,11 +116,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"delayed-stream": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
|
||||||
},
|
|
||||||
"depd": {
|
"depd": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||||
@@ -195,6 +151,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||||
},
|
},
|
||||||
|
"events": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
|
||||||
|
},
|
||||||
"express": {
|
"express": {
|
||||||
"version": "4.16.4",
|
"version": "4.16.4",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
|
||||||
@@ -276,16 +237,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"form-data": {
|
|
||||||
"version": "2.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
|
||||||
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
|
||||||
"requires": {
|
|
||||||
"asynckit": "^0.4.0",
|
|
||||||
"combined-stream": "^1.0.6",
|
|
||||||
"mime-types": "^2.1.12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"forwarded": {
|
"forwarded": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||||
@@ -296,24 +247,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||||
},
|
},
|
||||||
"get-port": {
|
|
||||||
"version": "3.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
|
|
||||||
"integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw="
|
|
||||||
},
|
|
||||||
"http-basic": {
|
|
||||||
"version": "7.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-basic/-/http-basic-7.0.0.tgz",
|
|
||||||
"integrity": "sha1-gvClBr6UJzLsje6+6A50bvVzbbo=",
|
|
||||||
"requires": {
|
|
||||||
"@types/concat-stream": "^1.6.0",
|
|
||||||
"@types/node": "^9.4.1",
|
|
||||||
"caseless": "~0.12.0",
|
|
||||||
"concat-stream": "^1.4.6",
|
|
||||||
"http-response-object": "^3.0.1",
|
|
||||||
"parse-cache-control": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"http-errors": {
|
"http-errors": {
|
||||||
"version": "1.6.3",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
||||||
@@ -325,14 +258,6 @@
|
|||||||
"statuses": ">= 1.4.0 < 2"
|
"statuses": ">= 1.4.0 < 2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"http-response-object": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-6L0Fkd6TozA8kFSfh9Widst0wfza3U1Ex2RjJ6zNDK0vR1U1auUR6jY4Nn2Xl7CCy0ikFmxW1XcspVpb9RvwTg==",
|
|
||||||
"requires": {
|
|
||||||
"@types/node": "^9.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
"version": "0.4.23",
|
"version": "0.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
||||||
@@ -341,6 +266,11 @@
|
|||||||
"safer-buffer": ">= 2.1.2 < 3"
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ieee754": {
|
||||||
|
"version": "1.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||||
|
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
|
||||||
|
},
|
||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||||
@@ -356,10 +286,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||||
},
|
},
|
||||||
|
"jmespath": {
|
||||||
|
"version": "0.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
|
||||||
|
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
|
||||||
|
},
|
||||||
"kotlin": {
|
"kotlin": {
|
||||||
"version": "1.3.21",
|
"version": "1.4.0-M3",
|
||||||
"resolved": "https://registry.npmjs.org/kotlin/-/kotlin-1.3.21.tgz",
|
"resolved": "https://registry.npmjs.org/kotlin/-/kotlin-1.4.0-M3.tgz",
|
||||||
"integrity": "sha512-JqxY9quNzwWl8iYvNj/ycfNBEbSFDKvmeK2rDVU89q0/X8BRjVzibJ5RFOq/9efvvvi+VBnIRhL7GSOGzmP2nQ=="
|
"integrity": "sha512-y6dl1t36tI1hAJlJTxlOB/fj/0iOga+XC8+eg9hUSTu5BDJ9MIUR7XnA8Ld+tQA9IakAPaUmgZZfX2lsHaAdIw=="
|
||||||
},
|
},
|
||||||
"media-typer": {
|
"media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
@@ -404,6 +339,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
|
||||||
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
|
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
|
||||||
},
|
},
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
|
||||||
|
},
|
||||||
"on-finished": {
|
"on-finished": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||||
@@ -412,11 +352,6 @@
|
|||||||
"ee-first": "1.1.1"
|
"ee-first": "1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parse-cache-control": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-juqz5U+laSD+Fro493+iGqzC104="
|
|
||||||
},
|
|
||||||
"parseurl": {
|
"parseurl": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
|
||||||
@@ -427,19 +362,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||||
},
|
},
|
||||||
"process-nextick-args": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
|
|
||||||
},
|
|
||||||
"promise": {
|
|
||||||
"version": "8.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/promise/-/promise-8.0.2.tgz",
|
|
||||||
"integrity": "sha512-EIyzM39FpVOMbqgzEHhxdrEhtOSDOtjMZQ0M6iVfCE+kWNgCkAyOdnuCWqfmflylftfadU6FkiMgHZA2kUzwRw==",
|
|
||||||
"requires": {
|
|
||||||
"asap": "~2.0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"proxy-addr": {
|
"proxy-addr": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
|
||||||
@@ -449,11 +371,21 @@
|
|||||||
"ipaddr.js": "1.8.0"
|
"ipaddr.js": "1.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"punycode": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
|
||||||
|
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
|
||||||
|
},
|
||||||
"qs": {
|
"qs": {
|
||||||
"version": "6.5.2",
|
"version": "6.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
||||||
},
|
},
|
||||||
|
"querystring": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||||
|
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
|
||||||
|
},
|
||||||
"range-parser": {
|
"range-parser": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
|
||||||
@@ -470,20 +402,6 @@
|
|||||||
"unpipe": "1.0.0"
|
"unpipe": "1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
|
||||||
"version": "2.3.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
|
||||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
|
||||||
"requires": {
|
|
||||||
"core-util-is": "~1.0.0",
|
|
||||||
"inherits": "~2.0.3",
|
|
||||||
"isarray": "~1.0.0",
|
|
||||||
"process-nextick-args": "~2.0.0",
|
|
||||||
"safe-buffer": "~5.1.1",
|
|
||||||
"string_decoder": "~1.1.1",
|
|
||||||
"util-deprecate": "~1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
@@ -494,6 +412,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
},
|
},
|
||||||
|
"sax": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
|
||||||
|
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
|
||||||
|
},
|
||||||
"send": {
|
"send": {
|
||||||
"version": "0.16.2",
|
"version": "0.16.2",
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
|
"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
|
||||||
@@ -550,57 +473,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
|
||||||
"requires": {
|
|
||||||
"safe-buffer": "~5.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sync-request": {
|
|
||||||
"version": "6.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.0.0.tgz",
|
|
||||||
"integrity": "sha512-jGNIAlCi9iU4X3Dm4oQnNQshDD3h0/1A7r79LyqjbjUnj69sX6mShAXlhRXgImsfVKtTcnra1jfzabdZvp+Lmw==",
|
|
||||||
"requires": {
|
|
||||||
"http-response-object": "^3.0.1",
|
|
||||||
"sync-rpc": "^1.2.1",
|
|
||||||
"then-request": "^6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sync-rpc": {
|
|
||||||
"version": "1.3.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.4.tgz",
|
|
||||||
"integrity": "sha512-Iug+t1ICVFenUcTnDu8WXFnT+k8IVoLKGh8VA3eXUtl2Rt9SjKX3YEv33OenABqpTPL9QEaHv1+CNn2LK8vMow==",
|
|
||||||
"requires": {
|
|
||||||
"get-port": "^3.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"then-request": {
|
|
||||||
"version": "6.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.0.tgz",
|
|
||||||
"integrity": "sha512-xA+7uEMc+jsQIoyySJ93Ad08Kuqnik7u6jLS5hR91Z3smAoCfL3M8/MqMlobAa9gzBfO9pA88A/AntfepkkMJQ==",
|
|
||||||
"requires": {
|
|
||||||
"@types/concat-stream": "^1.6.0",
|
|
||||||
"@types/form-data": "0.0.33",
|
|
||||||
"@types/node": "^8.0.0",
|
|
||||||
"@types/qs": "^6.2.31",
|
|
||||||
"caseless": "~0.12.0",
|
|
||||||
"concat-stream": "^1.6.0",
|
|
||||||
"form-data": "^2.2.0",
|
|
||||||
"http-basic": "^7.0.0",
|
|
||||||
"http-response-object": "^3.0.1",
|
|
||||||
"promise": "^8.0.0",
|
|
||||||
"qs": "^6.4.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "8.10.42",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.42.tgz",
|
|
||||||
"integrity": "sha512-8LCqostMfYwQs9by1k21/P4KZp9uFQk3Q528y3qtPKQnCJmKz0Em3YzgeNjTNV1FVrG/7n/6j12d4UKg9zSgDw=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type-is": {
|
"type-is": {
|
||||||
"version": "1.6.16",
|
"version": "1.6.16",
|
||||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
|
||||||
@@ -610,30 +482,48 @@
|
|||||||
"mime-types": "~2.1.18"
|
"mime-types": "~2.1.18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typedarray": {
|
|
||||||
"version": "0.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
|
||||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
|
||||||
},
|
|
||||||
"unpipe": {
|
"unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||||
},
|
},
|
||||||
"util-deprecate": {
|
"url": {
|
||||||
"version": "1.0.2",
|
"version": "0.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
|
||||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
"integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=",
|
||||||
|
"requires": {
|
||||||
|
"punycode": "1.3.2",
|
||||||
|
"querystring": "0.2.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"utils-merge": {
|
"utils-merge": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||||
},
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
|
||||||
|
},
|
||||||
"vary": {
|
"vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||||
|
},
|
||||||
|
"xml2js": {
|
||||||
|
"version": "0.4.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
|
||||||
|
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
|
||||||
|
"requires": {
|
||||||
|
"sax": ">=0.6.0",
|
||||||
|
"xmlbuilder": "~9.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xmlbuilder": {
|
||||||
|
"version": "9.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
|
||||||
|
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,12 @@
|
|||||||
"start": "node server/app.js"
|
"start": "node server/app.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"aws-sdk": "~2.670.0",
|
||||||
"body-parser": "~1.18.3",
|
"body-parser": "~1.18.3",
|
||||||
"debug": "~4.1.1",
|
"debug": "~4.1.1",
|
||||||
|
"ejs": "~2.6.1",
|
||||||
"express": "~4.16.4",
|
"express": "~4.16.4",
|
||||||
"kotlin": "^1.3.21",
|
"kotlin": "~1.4.0-M3",
|
||||||
"sync-request": "~6.0.0",
|
"node-fetch": "~2.6.0"
|
||||||
"ejs": "~2.6.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-29
@@ -1,45 +1,26 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2019 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.buildInfo
|
||||||
package org.jetbrains.build
|
|
||||||
|
|
||||||
import org.jetbrains.report.*
|
import org.jetbrains.report.*
|
||||||
import org.jetbrains.report.json.*
|
import org.jetbrains.report.json.*
|
||||||
|
|
||||||
data class Build(val buildNumber: String, val startTime: String, val finishTime: String, val branch: String,
|
data class Build(val buildNumber: String, val startTime: String, val finishTime: String, val branch: String,
|
||||||
val commits: String, val buildType: String, val failuresNumber: Int, val executionTime: String,
|
val commits: String, val failuresNumber: Int) {
|
||||||
val compileTime: String, val codeSize: String, val bundleSize: String?) {
|
|
||||||
|
|
||||||
companion object: EntityFromJsonFactory<Build> {
|
companion object : EntityFromJsonFactory<Build> {
|
||||||
override fun create(data: JsonElement): Build {
|
override fun create(data: JsonElement): Build {
|
||||||
if (data is JsonObject) {
|
if (data is JsonObject) {
|
||||||
val buildNumber = elementToString(data.getRequiredField("buildNumber"), "buildNumber")
|
val buildNumber = elementToString(data.getRequiredField("buildNumber"), "buildNumber").replace("\"", "")
|
||||||
val startTime = elementToString(data.getRequiredField("startTime"), "startTime")
|
val startTime = elementToString(data.getRequiredField("startTime"), "startTime").replace("\"", "")
|
||||||
val finishTime = elementToString(data.getRequiredField("finishTime"), "finishTime")
|
val finishTime = elementToString(data.getRequiredField("finishTime"), "finishTime").replace("\"", "")
|
||||||
val branch = elementToString(data.getRequiredField("branch"), "branch")
|
val branch = elementToString(data.getRequiredField("branch"), "branch").replace("\"", "")
|
||||||
val commits = elementToString(data.getRequiredField("commits"), "commits")
|
val commits = elementToString(data.getRequiredField("commits"), "commits")
|
||||||
val buildType = elementToString(data.getRequiredField("buildType"), "buildType")
|
|
||||||
val failuresNumber = elementToInt(data.getRequiredField("failuresNumber"), "failuresNumber")
|
val failuresNumber = elementToInt(data.getRequiredField("failuresNumber"), "failuresNumber")
|
||||||
val executionTime = elementToString(data.getRequiredField("executionTime"), "executionTime")
|
return Build(buildNumber, startTime, finishTime, branch, commits, failuresNumber)
|
||||||
val compileTime = elementToString(data.getRequiredField("compileTime"), "compileTime")
|
|
||||||
val codeSize = elementToString(data.getRequiredField("codeSize"), "codeSize")
|
|
||||||
val bundleSize = elementToStringOrNull(data.getRequiredField("bundleSize"), "bundleSize")
|
|
||||||
return Build(buildNumber, startTime, finishTime, branch, commits, buildType, failuresNumber, executionTime,
|
|
||||||
compileTime, codeSize, bundleSize)
|
|
||||||
} else {
|
} else {
|
||||||
error("Top level entity is expected to be an object. Please, check origin files.")
|
error("Top level entity is expected to be an object. Please, check origin files.")
|
||||||
}
|
}
|
||||||
@@ -58,6 +39,7 @@ data class Build(val buildNumber: String, val startTime: String, val finishTime:
|
|||||||
return "${if (hours < 10) "0$hours" else "$hours"}:${matchResult[2]}"
|
return "${if (hours < 10) "0$hours" else "$hours"}:${matchResult[2]}"
|
||||||
} ?: error { "Wrong format of time $startTime" }
|
} ?: error { "Wrong format of time $startTime" }
|
||||||
}
|
}
|
||||||
|
|
||||||
val date: String by lazy {
|
val date: String by lazy {
|
||||||
val matchResult = "^(\\d{4})(\\d{2})(\\d{2})".toRegex().find(startTime)?.groupValues
|
val matchResult = "^(\\d{4})(\\d{2})(\\d{2})".toRegex().find(startTime)?.groupValues
|
||||||
matchResult?.let { "${matchResult[3]}/${matchResult[2]}/${matchResult[1]}" }
|
matchResult?.let { "${matchResult[3]}/${matchResult[2]}/${matchResult[1]}" }
|
||||||
+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.elastic
|
||||||
|
|
||||||
|
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||||
|
import org.jetbrains.report.json.*
|
||||||
|
import org.jetbrains.network.*
|
||||||
|
|
||||||
|
// Connector with InfluxDB.
|
||||||
|
class ElasticSearchConnector(private val connector: NetworkConnector,
|
||||||
|
private val user: String? = null, private val password: String? = null) {
|
||||||
|
// Execute ElasticSearch request.
|
||||||
|
fun request(method: RequestMethod, path: String, acceptJsonContentType: Boolean = true, body: String? = null) =
|
||||||
|
connector.sendRequest(method, path, user, password, acceptJsonContentType, body)
|
||||||
|
}
|
||||||
+224
@@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* 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.elastic
|
||||||
|
|
||||||
|
import org.jetbrains.report.*
|
||||||
|
import org.jetbrains.report.json.*
|
||||||
|
import org.jetbrains.report.MeanVarianceBenchmark
|
||||||
|
import org.jetbrains.network.*
|
||||||
|
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||||
|
|
||||||
|
data class Commit(val revision: String, val developer: String) : JsonSerializable {
|
||||||
|
override fun toString() = "$revision by $developer"
|
||||||
|
|
||||||
|
override fun serializeFields() = """
|
||||||
|
"revision": "$revision",
|
||||||
|
"developer": "$developer"
|
||||||
|
"""
|
||||||
|
|
||||||
|
companion object : EntityFromJsonFactory<Commit> {
|
||||||
|
fun parse(description: String) = if (description != "...") {
|
||||||
|
description.split(" by ").let {
|
||||||
|
val (currentRevision, currentDeveloper) = it
|
||||||
|
Commit(currentRevision, currentDeveloper)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Commit("unknown", "unknown")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(data: JsonElement): Commit {
|
||||||
|
if (data is JsonObject) {
|
||||||
|
val revision = elementToString(data.getRequiredField("revision"), "revision")
|
||||||
|
val developer = elementToString(data.getRequiredField("developer"), "developer")
|
||||||
|
return Commit(revision, developer)
|
||||||
|
} else {
|
||||||
|
error("Top level entity is expected to be an object. Please, check origin files.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of commits.
|
||||||
|
class CommitsList : ConvertedFromJson, JsonSerializable {
|
||||||
|
|
||||||
|
val commits: List<Commit>
|
||||||
|
|
||||||
|
constructor(data: JsonElement) {
|
||||||
|
if (data !is JsonObject) {
|
||||||
|
error("Commits description is expected to be a JSON object!")
|
||||||
|
}
|
||||||
|
val changesElement = data.getOptionalField("change")
|
||||||
|
commits = changesElement?.let {
|
||||||
|
if (changesElement !is JsonArray) {
|
||||||
|
error("Change field is expected to be an array. Please, check source.")
|
||||||
|
}
|
||||||
|
changesElement.jsonArray.map {
|
||||||
|
with(it as JsonObject) {
|
||||||
|
Commit(elementToString(getRequiredField("version"), "version"),
|
||||||
|
elementToString(getRequiredField("username"), "username")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: listOf<Commit>()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(_commits: List<Commit>) {
|
||||||
|
commits = _commits
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String =
|
||||||
|
commits.toString()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun parse(description: String) = CommitsList(description.split(";").filter { it.isNotEmpty() }.map {
|
||||||
|
Commit.parse(it)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serializeFields() = """
|
||||||
|
"commits": ${arrayToJson(commits)}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
data class BuildInfo(val buildNumber: String, val startTime: String, val endTime: String, val commitsList: CommitsList,
|
||||||
|
val branch: String,
|
||||||
|
val agentInfo: String /* Important agent information often used in requests.*/) : JsonSerializable {
|
||||||
|
override fun serializeFields() = """
|
||||||
|
"buildNumber": "$buildNumber",
|
||||||
|
"startTime": "$startTime",
|
||||||
|
"endTime": "$endTime",
|
||||||
|
${commitsList.serializeFields()},
|
||||||
|
"branch": "$branch",
|
||||||
|
"agentInfo": "$agentInfo"
|
||||||
|
"""
|
||||||
|
|
||||||
|
companion object : EntityFromJsonFactory<BuildInfo> {
|
||||||
|
override fun create(data: JsonElement): BuildInfo {
|
||||||
|
if (data is JsonObject) {
|
||||||
|
val buildNumber = elementToString(data.getRequiredField("buildNumber"), "buildNumber")
|
||||||
|
val startTime = elementToString(data.getRequiredField("startTime"), "startTime")
|
||||||
|
val endTime = elementToString(data.getRequiredField("endTime"), "endTime")
|
||||||
|
val branch = elementToString(data.getRequiredField("branch"), "branch")
|
||||||
|
val commitsList = data.getRequiredField("commits")
|
||||||
|
val commits = if (commitsList is JsonArray) {
|
||||||
|
commitsList.jsonArray.map { Commit.create(it as JsonObject) }
|
||||||
|
} else {
|
||||||
|
error("benchmarksSets field is expected to be an array. Please, check origin files.")
|
||||||
|
}
|
||||||
|
val agentInfoElement = data.getOptionalField("agentInfo")
|
||||||
|
val agentInfo = agentInfoElement?.let {
|
||||||
|
elementToString(agentInfoElement, "agentInfo")
|
||||||
|
} ?: ""
|
||||||
|
return BuildInfo(buildNumber, startTime, endTime, CommitsList(commits), branch, agentInfo)
|
||||||
|
} else {
|
||||||
|
error("Top level entity is expected to be an object. Please, check origin files.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ElasticSearchType {
|
||||||
|
TEXT, KEYWORD, DATE, LONG, DOUBLE, BOOLEAN, OBJECT, NESTED
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ElasticSearchIndex(val indexName: String, val connector: ElasticSearchConnector) {
|
||||||
|
var nextId = 0L
|
||||||
|
|
||||||
|
// Insert data.
|
||||||
|
fun insert(data: JsonSerializable): Promise<String> {
|
||||||
|
val description = data.toJson()
|
||||||
|
val writePath = "$indexName/_doc/$nextId?pretty"
|
||||||
|
nextId++
|
||||||
|
return connector.request(RequestMethod.PUT, writePath, body = description)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete data.
|
||||||
|
fun delete(data: String): Promise<String> {
|
||||||
|
val writePath = "$indexName/_delete_by_query"
|
||||||
|
return connector.request(RequestMethod.POST, writePath, body = data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make search request.
|
||||||
|
fun search(requestJson: String, filterPathes: List<String> = emptyList()): Promise<String> {
|
||||||
|
val path = "$indexName/_search?pretty${if (filterPathes.isNotEmpty())
|
||||||
|
"&filter_path=" + filterPathes.joinToString(",") else ""}"
|
||||||
|
return connector.request(RequestMethod.POST, path, body = requestJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Get latest id for index.
|
||||||
|
val queryBody = """{
|
||||||
|
"_source": ["_id"],
|
||||||
|
"size": 1,
|
||||||
|
"query": {
|
||||||
|
"match_all": {}
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
search(queryBody, listOf("hits.total.value")).then { responseString ->
|
||||||
|
val response = JsonTreeParser.parse(responseString).jsonObject
|
||||||
|
val value = response.getObjectOrNull("hits")?.getObjectOrNull("total")?.getPrimitiveOrNull("value")?.content
|
||||||
|
?: error("Error response from ElasticSearch:\n$responseString")
|
||||||
|
nextId = value.toLong()
|
||||||
|
}.catch { errorMessage ->
|
||||||
|
error(errorMessage.message ?: "Failed getting next id for index $indexName")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract val mapping: Map<String, ElasticSearchType>
|
||||||
|
|
||||||
|
val mappingDescription: String
|
||||||
|
get() = """
|
||||||
|
{
|
||||||
|
"mappings": {
|
||||||
|
"properties": {
|
||||||
|
${mapping.map { (property, type) ->
|
||||||
|
"\"${property}\": { \"type\": \"${type.name.toLowerCase()}\"${if (type == ElasticSearchType.DATE) "," +
|
||||||
|
"\"format\": \"basic_date_time_no_millis\"" else ""} }"
|
||||||
|
}.joinToString()}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
fun createMapping() =
|
||||||
|
connector.request(RequestMethod.PUT, indexName, body = mappingDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
class BenchmarksIndex(name: String, connector: ElasticSearchConnector) : ElasticSearchIndex(name, connector) {
|
||||||
|
override val mapping: Map<String, ElasticSearchType>
|
||||||
|
get() = mapOf("buildNumber" to ElasticSearchType.KEYWORD,
|
||||||
|
"benchmarks" to ElasticSearchType.NESTED,
|
||||||
|
"env" to ElasticSearchType.NESTED,
|
||||||
|
"kotlin" to ElasticSearchType.NESTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
class GoldenResultsIndex(connector: ElasticSearchConnector) : ElasticSearchIndex("golden", connector) {
|
||||||
|
override val mapping: Map<String, ElasticSearchType>
|
||||||
|
get() = mapOf("buildNumber" to ElasticSearchType.KEYWORD,
|
||||||
|
"benchmarks" to ElasticSearchType.NESTED,
|
||||||
|
"env" to ElasticSearchType.NESTED,
|
||||||
|
"kotlin" to ElasticSearchType.NESTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
class BuildInfoIndex(connector: ElasticSearchConnector) : ElasticSearchIndex("builds", connector) {
|
||||||
|
override val mapping: Map<String, ElasticSearchType>
|
||||||
|
get() = mapOf("buildNumber" to ElasticSearchType.KEYWORD,
|
||||||
|
"startTime" to ElasticSearchType.DATE,
|
||||||
|
"endTime" to ElasticSearchType.DATE,
|
||||||
|
"commits" to ElasticSearchType.NESTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processed benchmark result with calculated mean, variance and normalized reult.
|
||||||
|
class NormalizedMeanVarianceBenchmark(name: String, status: BenchmarkResult.Status, score: Double, metric: BenchmarkResult.Metric,
|
||||||
|
runtimeInUs: Double, repeat: Int, warmup: Int, variance: Double, val normalizedScore: Double) :
|
||||||
|
MeanVarianceBenchmark(name, status, score, metric, runtimeInUs, repeat, warmup, variance) {
|
||||||
|
|
||||||
|
override fun serializeFields(): String {
|
||||||
|
return """
|
||||||
|
${super.serializeFields()},
|
||||||
|
"normalizedScore": $normalizedScore
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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.network
|
||||||
|
|
||||||
|
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||||
|
import kotlin.js.json // TODO - migrate to multiplatform.
|
||||||
|
|
||||||
|
// Now implemenation for network connection only for Node.js. TODO - multiplatform.
|
||||||
|
external fun require(module: String): dynamic
|
||||||
|
|
||||||
|
enum class RequestMethod {
|
||||||
|
POST, GET, PUT
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abstract class for working with network.
|
||||||
|
abstract class NetworkConnector {
|
||||||
|
fun getAuth(user: String, password: String): String {
|
||||||
|
val buffer = js("Buffer").from(user + ":" + password)
|
||||||
|
val based64String = buffer.toString("base64")
|
||||||
|
return "Basic " + based64String
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun <T : String?> sendBaseRequest(method: RequestMethod, path: String, user: String? = null,
|
||||||
|
password: String? = null, acceptJsonContentType: Boolean = true,
|
||||||
|
body: String? = null,
|
||||||
|
errorHandler: (url: String, response: dynamic) -> Nothing?): Promise<T>
|
||||||
|
|
||||||
|
open fun sendRequest(method: RequestMethod, path: String, user: String? = null, password: String? = null,
|
||||||
|
acceptJsonContentType: Boolean = true, body: String? = null): Promise<String> =
|
||||||
|
sendBaseRequest<String>(method, path, user, password, acceptJsonContentType, body) { url, response ->
|
||||||
|
error("Error during getting response from $url\n$response")
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun sendOptionalRequest(method: RequestMethod, path: String, user: String? = null, password: String? = null,
|
||||||
|
acceptJsonContentType: Boolean = true, body: String? = null): Promise<String?> =
|
||||||
|
sendBaseRequest<String?>(method, path, user, password, acceptJsonContentType, body) { url, response ->
|
||||||
|
println("Error during getting response from $url\n$response")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.network
|
||||||
|
|
||||||
|
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||||
|
import kotlin.js.json // TODO - migrate to multiplatform.
|
||||||
|
|
||||||
|
// Network connector to work with basic url requests.
|
||||||
|
class UrlNetworkConnector(private val host: String, private val port: Int? = null) : NetworkConnector() {
|
||||||
|
|
||||||
|
private val url = "$host${port?.let { ":$port" } ?: ""}"
|
||||||
|
|
||||||
|
override fun <T : String?> sendBaseRequest(method: RequestMethod, path: String, user: String?, password: String?,
|
||||||
|
acceptJsonContentType: Boolean, body: String?,
|
||||||
|
errorHandler: (url: String, response: dynamic) -> Nothing?): Promise<T> {
|
||||||
|
val fullUrl = "$url/$path"
|
||||||
|
val request = require("node-fetch")
|
||||||
|
val headers = mutableListOf<Pair<String, String>>()
|
||||||
|
if (user != null && password != null) {
|
||||||
|
headers.add("Authorization" to getAuth(user, password))
|
||||||
|
}
|
||||||
|
if (acceptJsonContentType) {
|
||||||
|
headers.add("Accept" to "application/json")
|
||||||
|
headers.add("Content-Type" to "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
return request(fullUrl,
|
||||||
|
json(
|
||||||
|
"method" to method.toString(),
|
||||||
|
"headers" to json(*(headers.toTypedArray())),
|
||||||
|
"body" to body
|
||||||
|
)
|
||||||
|
).then { response ->
|
||||||
|
if (!response.ok) {
|
||||||
|
errorHandler(fullUrl, response)
|
||||||
|
} else {
|
||||||
|
response.text()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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 writeToFile(fileName: String, text: String) {
|
||||||
|
error("Writing to local file for JS isn't supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun Double.format(decimalNumber: Int): String =
|
||||||
|
this.asDynamic().toFixed(decimalNumber)
|
||||||
|
|
||||||
|
actual fun assert(value: Boolean, lazyMessage: () -> Any) {
|
||||||
|
if (!value) error(lazyMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun sendGetRequest(url: String, user: String?, password: String?, followLocation: Boolean) : String {
|
||||||
|
error("Unsupported")
|
||||||
|
}
|
||||||
@@ -0,0 +1,365 @@
|
|||||||
|
/*
|
||||||
|
* 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.database
|
||||||
|
|
||||||
|
import kotlin.js.Promise
|
||||||
|
import org.jetbrains.elastic.*
|
||||||
|
import org.jetbrains.utils.*
|
||||||
|
import org.jetbrains.report.json.*
|
||||||
|
import org.jetbrains.report.*
|
||||||
|
|
||||||
|
fun <T> Iterable<T>.isEmpty() = count() == 0
|
||||||
|
fun <T> Iterable<T>.isNotEmpty() = !isEmpty()
|
||||||
|
|
||||||
|
inline fun <T: Any> T?.str(block: (T) -> String): String =
|
||||||
|
if (this != null) block(this)
|
||||||
|
else ""
|
||||||
|
|
||||||
|
// Dispatcher to create and control benchmarks indexes separated by some feature.
|
||||||
|
// Feature can be choosen as often used as filtering entity in case there is no need in separate indexes.
|
||||||
|
// Default behaviour of dispatcher is working with one index (case when separating isn't needed).
|
||||||
|
class BenchmarksIndexesDispatcher(connector: ElasticSearchConnector, val feature: String,
|
||||||
|
featureValues: Iterable<String> = emptyList()) {
|
||||||
|
// Becnhmarks indexes to work with in case of existing feature values.
|
||||||
|
private val benchmarksIndexes =
|
||||||
|
if (featureValues.isNotEmpty())
|
||||||
|
featureValues.map { it to BenchmarksIndex("benchmarks_${it.replace(" ", "_").toLowerCase()}", connector) }
|
||||||
|
.toMap()
|
||||||
|
else emptyMap()
|
||||||
|
|
||||||
|
// Single benchmark index.
|
||||||
|
private val benchmarksSingleInstance =
|
||||||
|
if (featureValues.isEmpty()) BenchmarksIndex("benchmarks", connector) else null
|
||||||
|
|
||||||
|
// Get right index in ES.
|
||||||
|
private fun getIndex(featureValue: String = "") =
|
||||||
|
benchmarksSingleInstance ?: benchmarksIndexes[featureValue]
|
||||||
|
?: error("Used wrong feature value $featureValue. Indexes are separated using next values: ${benchmarksIndexes.keys}")
|
||||||
|
|
||||||
|
// Used filter to get data with needed feature value.
|
||||||
|
var featureFilter: ((String) -> String)? = null
|
||||||
|
|
||||||
|
// Get benchmark reports corresponding to needed build number.
|
||||||
|
fun getBenchmarksReports(buildNumber: String, featureValue: String): Promise<List<String>> {
|
||||||
|
val queryDescription = """
|
||||||
|
{
|
||||||
|
"size": 1000,
|
||||||
|
"query": {
|
||||||
|
"bool": {
|
||||||
|
"must": [
|
||||||
|
{ "match": { "buildNumber": "$buildNumber" } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
return getIndex(featureValue).search(queryDescription, listOf("hits.hits._source")).then { responseString ->
|
||||||
|
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||||
|
dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits")?.let { results ->
|
||||||
|
results.map {
|
||||||
|
val element = it as JsonObject
|
||||||
|
element.getObject("_source").toString()
|
||||||
|
}
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get benchmarkes names corresponding to needed build number.
|
||||||
|
fun getBenchmarksList(buildNumber: String, featureValue: String): Promise<List<String>> {
|
||||||
|
return getBenchmarksReports(buildNumber, featureValue).then { reports ->
|
||||||
|
reports.map {
|
||||||
|
val dbResponse = JsonTreeParser.parse(it).jsonObject
|
||||||
|
parseBenchmarksArray(dbResponse.getArray("benchmarks"))
|
||||||
|
.map { it.name }
|
||||||
|
}.flatten()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete benchmarks from database.
|
||||||
|
fun deleteBenchmarks(featureValue: String, buildNumber: String? = null): Promise<String> {
|
||||||
|
// Delete all or for choosen build number.
|
||||||
|
val matchQuery = buildNumber?.let {
|
||||||
|
""""match": { "buildNumber": "$it" }"""
|
||||||
|
} ?: """"match_all": {}"""
|
||||||
|
|
||||||
|
val queryDescription = """
|
||||||
|
{
|
||||||
|
"query": {
|
||||||
|
$matchQuery
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
return getIndex(featureValue).delete(queryDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get benchmarks values of needed metric for choosen build number.
|
||||||
|
fun getSamples(metricName: String, featureValue: String = "", samples: List<String>,
|
||||||
|
buildNumbers: Iterable<String>? = null,
|
||||||
|
normalize: Boolean = false): Promise<List<Pair<String, Array<Double?>>>> {
|
||||||
|
val queryDescription = """
|
||||||
|
{
|
||||||
|
"_source": ["buildNumber"],
|
||||||
|
"size": 1000,
|
||||||
|
"query": {
|
||||||
|
"bool": {
|
||||||
|
"must": [
|
||||||
|
${buildNumbers.str { builds ->
|
||||||
|
"""
|
||||||
|
{ "terms" : { "buildNumber" : [${builds.map { "\"$it\"" }.joinToString()}] } },""" }
|
||||||
|
}
|
||||||
|
${featureFilter.str { "${it(featureValue)}," } }
|
||||||
|
{"nested" : {
|
||||||
|
"path" : "benchmarks",
|
||||||
|
"query" : {
|
||||||
|
"bool": {
|
||||||
|
"must": [
|
||||||
|
{ "match": { "benchmarks.metric": "$metricName" } },
|
||||||
|
{ "terms": { "benchmarks.name": [${samples.map { "\"${it.toLowerCase()}\"" }.joinToString()}] }}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}, "inner_hits": {
|
||||||
|
"size": ${samples.size},
|
||||||
|
"_source": ["benchmarks.name",
|
||||||
|
"benchmarks.${if (normalize) "normalizedScore" else "score"}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
|
||||||
|
return getIndex(featureValue).search(queryDescription, listOf("hits.hits._source", "hits.hits.inner_hits"))
|
||||||
|
.then { responseString ->
|
||||||
|
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||||
|
val results = dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits")
|
||||||
|
?: error("Wrong response:\n$responseString")
|
||||||
|
// Get indexes for provided samples.
|
||||||
|
val indexesMap = samples.mapIndexed { index, it -> it to index }.toMap()
|
||||||
|
val valuesMap = buildNumbers?.map {
|
||||||
|
it to arrayOfNulls<Double?>(samples.size)
|
||||||
|
}?.toMap()?.toMutableMap() ?: mutableMapOf<String, Array<Double?>>()
|
||||||
|
// Parse and save values in requested order.
|
||||||
|
results.forEach {
|
||||||
|
val element = it as JsonObject
|
||||||
|
val build = element.getObject("_source").getPrimitive("buildNumber").content
|
||||||
|
buildNumbers?.let { valuesMap.getOrPut(build) { arrayOfNulls<Double?>(samples.size) } }
|
||||||
|
element
|
||||||
|
.getObject("inner_hits")
|
||||||
|
.getObject("benchmarks")
|
||||||
|
.getObject("hits")
|
||||||
|
.getArray("hits").forEach {
|
||||||
|
val source = (it as JsonObject).getObject("_source")
|
||||||
|
valuesMap[build]!![indexesMap[source.getPrimitive("name").content]!!] =
|
||||||
|
source.getPrimitive(if (normalize) "normalizedScore" else "score").double
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
valuesMap.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insert(data: JsonSerializable, featureValue: String = "") =
|
||||||
|
getIndex(featureValue).insert(data)
|
||||||
|
|
||||||
|
fun delete(data: String, featureValue: String = "") =
|
||||||
|
getIndex(featureValue).delete(data)
|
||||||
|
|
||||||
|
// Get failures number happned during build.
|
||||||
|
fun getFailuresNumber(featureValue: String = "", buildNumbers: Iterable<String>? = null): Promise<Map<String, Int>> {
|
||||||
|
val queryDescription = """
|
||||||
|
{
|
||||||
|
"_source": false,
|
||||||
|
${featureFilter.str {
|
||||||
|
"""
|
||||||
|
"query": {
|
||||||
|
"bool": {
|
||||||
|
"must": [ ${it(featureValue)} ]
|
||||||
|
}
|
||||||
|
}, """
|
||||||
|
} }
|
||||||
|
${buildNumbers.str { builds ->
|
||||||
|
"""
|
||||||
|
"aggs" : {
|
||||||
|
"builds": {
|
||||||
|
"filters" : {
|
||||||
|
"filters": {
|
||||||
|
${builds.map { "\"$it\": { \"match\" : { \"buildNumber\" : \"$it\" }}" }
|
||||||
|
.joinToString(",\n")}
|
||||||
|
}
|
||||||
|
},"""
|
||||||
|
} }
|
||||||
|
"aggs" : {
|
||||||
|
"metric_build" : {
|
||||||
|
"nested" : {
|
||||||
|
"path" : "benchmarks"
|
||||||
|
},
|
||||||
|
"aggs" : {
|
||||||
|
"metric_samples": {
|
||||||
|
"filters" : {
|
||||||
|
"filters": { "samples": { "match": { "benchmarks.status": "FAILED" } } }
|
||||||
|
},
|
||||||
|
"aggs" : {
|
||||||
|
"failed_count": {
|
||||||
|
"value_count": {
|
||||||
|
"field" : "benchmarks.score"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${buildNumbers.str {
|
||||||
|
""" }
|
||||||
|
}"""
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
return getIndex(featureValue).search(queryDescription, listOf("aggregations")).then { responseString ->
|
||||||
|
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||||
|
val aggregations = dbResponse.getObjectOrNull("aggregations") ?: error("Wrong response:\n$responseString")
|
||||||
|
buildNumbers?.let {
|
||||||
|
// Get failed number for each provided build.
|
||||||
|
val buckets = aggregations
|
||||||
|
.getObjectOrNull("builds")
|
||||||
|
?.getObjectOrNull("buckets")
|
||||||
|
?: error("Wrong response:\n$responseString")
|
||||||
|
buildNumbers.map {
|
||||||
|
it to buckets
|
||||||
|
.getObject(it)
|
||||||
|
.getObject("metric_build")
|
||||||
|
.getObject("metric_samples")
|
||||||
|
.getObject("buckets")
|
||||||
|
.getObject("samples")
|
||||||
|
.getObject("failed_count")
|
||||||
|
.getPrimitive("value")
|
||||||
|
.int
|
||||||
|
}.toMap()
|
||||||
|
} ?: listOf("golden" to aggregations
|
||||||
|
.getObject("metric_build")
|
||||||
|
.getObject("metric_samples")
|
||||||
|
.getObject("buckets")
|
||||||
|
.getObject("samples")
|
||||||
|
.getObject("failed_count")
|
||||||
|
.getPrimitive("value")
|
||||||
|
.int
|
||||||
|
).toMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get geometric mean for benchmarks values of needed metric.
|
||||||
|
fun getGeometricMean(metricName: String, featureValue: String = "",
|
||||||
|
buildNumbers: Iterable<String>? = null, normalize: Boolean = false,
|
||||||
|
excludeNames: List<String> = emptyList()): Promise<List<Pair<String, List<Double>>>> {
|
||||||
|
// Filter only with metric or also with names.
|
||||||
|
val filterBenchmarks = if (excludeNames.isEmpty())
|
||||||
|
"""
|
||||||
|
"match": { "benchmarks.metric": "$metricName" }
|
||||||
|
"""
|
||||||
|
else """
|
||||||
|
"bool": {
|
||||||
|
"must": { "match": { "benchmarks.metric": "$metricName" } },
|
||||||
|
"must_not": { "terms" : { "benchmarks.name" : [${excludeNames.map { "\"$it\"" }.joinToString()}] } }
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
val queryDescription = """
|
||||||
|
{
|
||||||
|
"_source": false,
|
||||||
|
${featureFilter.str {
|
||||||
|
"""
|
||||||
|
"query": {
|
||||||
|
"bool": {
|
||||||
|
"must": [ ${it(featureValue)} ]
|
||||||
|
}
|
||||||
|
}, """
|
||||||
|
} }
|
||||||
|
${buildNumbers.str { builds ->
|
||||||
|
"""
|
||||||
|
"aggs" : {
|
||||||
|
"builds": {
|
||||||
|
"filters" : {
|
||||||
|
"filters": {
|
||||||
|
${builds.map { "\"$it\": { \"match\" : { \"buildNumber\" : \"$it\" }}" }
|
||||||
|
.joinToString(",\n")}
|
||||||
|
}
|
||||||
|
},"""
|
||||||
|
} }
|
||||||
|
"aggs" : {
|
||||||
|
"metric_build" : {
|
||||||
|
"nested" : {
|
||||||
|
"path" : "benchmarks"
|
||||||
|
},
|
||||||
|
"aggs" : {
|
||||||
|
"metric_samples": {
|
||||||
|
"filters" : {
|
||||||
|
"filters": { "samples": { $filterBenchmarks } }
|
||||||
|
},
|
||||||
|
"aggs" : {
|
||||||
|
"sum_log_x": {
|
||||||
|
"sum": {
|
||||||
|
"field" : "benchmarks.${if (normalize) "normalizedScore" else "score"}",
|
||||||
|
"script" : {
|
||||||
|
"source": "if (_value == 0) { 0.0 } else { Math.log(_value) }"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"geom_mean": {
|
||||||
|
"bucket_script": {
|
||||||
|
"buckets_path": {
|
||||||
|
"sum_log_x": "sum_log_x",
|
||||||
|
"x_cnt": "_count"
|
||||||
|
},
|
||||||
|
"script": "Math.exp(params.sum_log_x/params.x_cnt)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${buildNumbers.str {
|
||||||
|
""" }
|
||||||
|
}"""
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
return getIndex(featureValue).search(queryDescription, listOf("aggregations")).then { responseString ->
|
||||||
|
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||||
|
val aggregations = dbResponse.getObjectOrNull("aggregations") ?: error("Wrong response:\n$responseString")
|
||||||
|
buildNumbers?.let {
|
||||||
|
val buckets = aggregations
|
||||||
|
.getObjectOrNull("builds")
|
||||||
|
?.getObjectOrNull("buckets")
|
||||||
|
?: error("Wrong response:\n$responseString")
|
||||||
|
buildNumbers.map {
|
||||||
|
it to listOf(buckets
|
||||||
|
.getObject(it)
|
||||||
|
.getObject("metric_build")
|
||||||
|
.getObject("metric_samples")
|
||||||
|
.getObject("buckets")
|
||||||
|
.getObject("samples")
|
||||||
|
.getObject("geom_mean")
|
||||||
|
.getPrimitive("value")
|
||||||
|
.double
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} ?: listOf("golden" to listOf(aggregations
|
||||||
|
.getObject("metric_build")
|
||||||
|
.getObject("metric_samples")
|
||||||
|
.getObject("buckets")
|
||||||
|
.getObject("samples")
|
||||||
|
.getObject("geom_mean")
|
||||||
|
.getPrimitive("value")
|
||||||
|
.double
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* 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.database
|
||||||
|
|
||||||
|
import kotlin.js.Promise
|
||||||
|
import org.jetbrains.elastic.*
|
||||||
|
import org.jetbrains.utils.*
|
||||||
|
import org.jetbrains.report.json.*
|
||||||
|
import org.jetbrains.report.*
|
||||||
|
|
||||||
|
// Delete build information from ES index.
|
||||||
|
internal fun deleteBuildInfo(agentInfo: String, buildInfoIndex: ElasticSearchIndex,
|
||||||
|
buildNumber: String? = null): Promise<String> {
|
||||||
|
val queryDescription = """
|
||||||
|
{
|
||||||
|
"query": {
|
||||||
|
"bool": {
|
||||||
|
"must": [
|
||||||
|
{ "match": { "agentInfo": "$agentInfo" } }
|
||||||
|
${buildNumber?.let {
|
||||||
|
""",
|
||||||
|
{"match": { "buildNumber": "$it" }}
|
||||||
|
"""
|
||||||
|
} ?: ""}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
return buildInfoIndex.delete(queryDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get infromation about builds details from database.
|
||||||
|
internal fun getBuildsDescription(type: String?, branch: String?, agentInfo: String, buildInfoIndex: ElasticSearchIndex,
|
||||||
|
onlyNumbers: Boolean = false): Promise<JsonArray> {
|
||||||
|
val queryDescription = """
|
||||||
|
{ "size": 10000,
|
||||||
|
${if (onlyNumbers) """"_source": ["buildNumber"],""" else ""}
|
||||||
|
"sort": {"_id": "desc" },
|
||||||
|
"query": {
|
||||||
|
"bool": {
|
||||||
|
"must": [
|
||||||
|
{ "match": { "agentInfo": "$agentInfo" } }
|
||||||
|
${type?.let {
|
||||||
|
""",
|
||||||
|
{ "regexp": { "buildNumber": { "value": "${if (it == "release")
|
||||||
|
".*eap.*|.*release.*|.*rc.*" else ".*dev.*"}" } }
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
} ?: ""}
|
||||||
|
${branch?.let {
|
||||||
|
""",
|
||||||
|
{"match": { "branch": "$it" }}
|
||||||
|
"""
|
||||||
|
} ?: ""}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
return buildInfoIndex.search(queryDescription, listOf("hits.hits._source")).then { responseString ->
|
||||||
|
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||||
|
dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits") ?: error("Wrong response:\n$responseString")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if current build already exists.
|
||||||
|
suspend fun buildExists(buildInfo: BuildInfo, buildInfoIndex: ElasticSearchIndex): Boolean {
|
||||||
|
val queryDescription = """
|
||||||
|
{ "size": 1,
|
||||||
|
"_source": ["buildNumber"],
|
||||||
|
"query": {
|
||||||
|
"bool": {
|
||||||
|
"must": [
|
||||||
|
{ "match": { "buildNumber": "${buildInfo.buildNumber}" } },
|
||||||
|
{ "match": { "agentInfo": "${buildInfo.agentInfo}" } },
|
||||||
|
{ "match": { "branch": "${buildInfo.branch}" } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
return buildInfoIndex.search(queryDescription, listOf("hits.total.value")).then { responseString ->
|
||||||
|
val response = JsonTreeParser.parse(responseString).jsonObject
|
||||||
|
val value = response.getObjectOrNull("hits")?.getObjectOrNull("total")?.getPrimitiveOrNull("value")?.content
|
||||||
|
?: error("Error response from ElasticSearch:\n$responseString")
|
||||||
|
value.toInt() > 0
|
||||||
|
}.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get builds numbers corresponding to machine and branch.
|
||||||
|
fun getBuildsNumbers(type: String?, branch: String?, agentInfo: String, buildInfoIndex: ElasticSearchIndex) =
|
||||||
|
getBuildsDescription(type, branch, agentInfo, buildInfoIndex, true).then { responseArray ->
|
||||||
|
responseArray.map { (it as JsonObject).getObject("_source").getPrimitive("buildNumber").content }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get full builds information corresponding to machine and branch.
|
||||||
|
fun getBuildsInfo(type: String?, branch: String?, agentInfo: String, buildInfoIndex: ElasticSearchIndex) =
|
||||||
|
getBuildsDescription(type, branch, agentInfo, buildInfoIndex).then { responseArray ->
|
||||||
|
responseArray.map { BuildInfo.create((it as JsonObject).getObject("_source")) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get golden results from database.
|
||||||
|
fun getGoldenResults(goldenResultsIndex: GoldenResultsIndex): Promise<Map<String, List<BenchmarkResult>>> {
|
||||||
|
return goldenResultsIndex.search("", listOf("hits.hits._source")).then { responseString ->
|
||||||
|
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||||
|
dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits")?.map {
|
||||||
|
val reportDescription = (it as JsonObject).getObject("_source")
|
||||||
|
BenchmarksReport.create(reportDescription).benchmarks
|
||||||
|
}?.reduce { acc, it -> acc + it } ?: error("Wrong format of response:\n $responseString")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get distinct values for needed field from database.
|
||||||
|
fun distinctValues(field: String, index: ElasticSearchIndex): Promise<List<String>> {
|
||||||
|
val queryDescription = """
|
||||||
|
{
|
||||||
|
"aggs": {
|
||||||
|
"unique": {"terms": {"field": "$field", "size": 1000}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
return index.search(queryDescription, listOf("aggregations.unique.buckets")).then { responseString ->
|
||||||
|
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||||
|
dbResponse.getObjectOrNull("aggregations")?.getObjectOrNull("unique")?.getArrayOrNull("buckets")
|
||||||
|
?.map { (it as JsonObject).getPrimitiveOrNull("key")?.content }?.filterNotNull()
|
||||||
|
?: error("Wrong response:\n$responseString")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
@file:OptIn(ExperimentalTime::class)
|
||||||
|
|
||||||
|
package org.jetbrains.network
|
||||||
|
|
||||||
|
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||||
|
import kotlin.time.*
|
||||||
|
|
||||||
|
// Response saved in cache.
|
||||||
|
data class CachedResponse(val cachedResult: Any, val time: TimeMark)
|
||||||
|
|
||||||
|
// Dispatcher for work with cachable responses.
|
||||||
|
object CachableResponseDispatcher {
|
||||||
|
// Storage of cached responses.
|
||||||
|
private val cachedResponses = mutableMapOf<String, CachedResponse>()
|
||||||
|
|
||||||
|
// Get response. If response isn't cached, use provided action to get response.
|
||||||
|
fun getResponse(request: dynamic, response: dynamic,
|
||||||
|
action: (success: (result: Any) -> Unit, reject: () -> Unit) -> Unit) {
|
||||||
|
cachedResponses[request.url]?.let {
|
||||||
|
// Update cache value if needed. Update only if last result was get later than 2 minutes.
|
||||||
|
if (it.time.elapsedNow().inMinutes > 2.0) {
|
||||||
|
println("Cache update for ${request.url}...")
|
||||||
|
action({ result: Any ->
|
||||||
|
cachedResponses[request.url] = CachedResponse(result, TimeSource.Monotonic.markNow())
|
||||||
|
}, { println("Cache update for ${request.url} failed!") })
|
||||||
|
}
|
||||||
|
response.json(it.cachedResult)
|
||||||
|
} ?: run {
|
||||||
|
action({ result: Any ->
|
||||||
|
cachedResponses[request.url] = CachedResponse(result, TimeSource.Monotonic.markNow())
|
||||||
|
response.json(result)
|
||||||
|
}, { response.sendStatus(400) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear(): Unit {
|
||||||
|
cachedResponses.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* 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.network
|
||||||
|
|
||||||
|
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||||
|
import kotlin.js.json // TODO - migrate to multiplatform.
|
||||||
|
import kotlin.js.Date
|
||||||
|
import org.jetbrains.report.json.*
|
||||||
|
|
||||||
|
// Placeholder as analog for indexable type in TS.
|
||||||
|
external interface `T$0` {
|
||||||
|
@nativeGetter
|
||||||
|
operator fun get(key: String): String?
|
||||||
|
|
||||||
|
@nativeSetter
|
||||||
|
operator fun set(key: String, value: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsModule("aws-sdk")
|
||||||
|
@JsNonModule
|
||||||
|
external object AWSInstance {
|
||||||
|
|
||||||
|
// Replace dynamic with some real type
|
||||||
|
class Endpoint(domain: String)
|
||||||
|
open class HttpRequest(endpoint: Endpoint, region: String) {
|
||||||
|
open fun pathname(): String
|
||||||
|
open var search: String
|
||||||
|
open var body: String?
|
||||||
|
open var endpoint: Endpoint
|
||||||
|
open var headers: `T$0`
|
||||||
|
open var method: String
|
||||||
|
open var path: String
|
||||||
|
}
|
||||||
|
|
||||||
|
class HttpClient() {
|
||||||
|
val handleRequest: dynamic
|
||||||
|
}
|
||||||
|
|
||||||
|
class SharedIniFileCredentials(options: Map<String, String>) {
|
||||||
|
val accessKeyId: String
|
||||||
|
}
|
||||||
|
|
||||||
|
class Signers() {
|
||||||
|
class V4(request: HttpRequest, subsystem: String) {
|
||||||
|
fun addAuthorization(credentials: SharedIniFileCredentials, date: Date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network connector to work with AWS resources.
|
||||||
|
class AWSNetworkConnector : NetworkConnector() {
|
||||||
|
val AWSDomain = "vpc-kotlin-perf-service-5e6ldakkdv526ii5hbclzcmpny.eu-west-1.es.amazonaws.com"
|
||||||
|
val AWSRegion = "eu-west-1"
|
||||||
|
|
||||||
|
override fun <T : String?> sendBaseRequest(method: RequestMethod, path: String, user: String?, password: String?,
|
||||||
|
acceptJsonContentType: Boolean, body: String?,
|
||||||
|
errorHandler: (url: String, response: dynamic) -> Nothing?): Promise<T> {
|
||||||
|
val AWSEndpoint = AWSInstance.Endpoint(AWSDomain)
|
||||||
|
var request = AWSInstance.HttpRequest(AWSEndpoint, AWSRegion)
|
||||||
|
request.method = method.toString()
|
||||||
|
request.path += path
|
||||||
|
request.body = body
|
||||||
|
|
||||||
|
request.headers["host"] = this.AWSDomain
|
||||||
|
|
||||||
|
if (acceptJsonContentType) {
|
||||||
|
request.headers["Content-Type"] = "application/json"
|
||||||
|
request.headers["Content-Length"] = js("Buffer").byteLength(request.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
val credentials = AWSInstance.SharedIniFileCredentials(mapOf<String, String>())
|
||||||
|
val signer = AWSInstance.Signers.V4(request, "es")
|
||||||
|
signer.addAuthorization(credentials, Date())
|
||||||
|
|
||||||
|
val client = AWSInstance.HttpClient()
|
||||||
|
return Promise { resolve, reject ->
|
||||||
|
client.handleRequest(request, null, { response ->
|
||||||
|
var responseBody = ""
|
||||||
|
response.on("data") { chunk ->
|
||||||
|
responseBody += chunk
|
||||||
|
chunk
|
||||||
|
}
|
||||||
|
response.on("end") { _ ->
|
||||||
|
val dbResponse = JsonTreeParser.parse(responseBody).jsonObject
|
||||||
|
// Response can fail and return 400 error for ES.
|
||||||
|
if (dbResponse.getPrimitiveOrNull("status")?.let { it.content != "200" } ?: false) {
|
||||||
|
println(dbResponse)
|
||||||
|
val errorMessage = dbResponse.getObject("error").toString()
|
||||||
|
reject(Throwable(errorMessage))
|
||||||
|
}
|
||||||
|
resolve(responseBody as T)
|
||||||
|
}
|
||||||
|
}, { error ->
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,330 +1,551 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2019 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import org.w3c.xhr.*
|
import org.w3c.xhr.*
|
||||||
import kotlin.js.json
|
import kotlin.js.json
|
||||||
import kotlin.js.Date
|
import kotlin.js.Date
|
||||||
|
import kotlin.js.Promise
|
||||||
|
import org.jetbrains.database.*
|
||||||
import org.jetbrains.report.json.*
|
import org.jetbrains.report.json.*
|
||||||
import org.jetbrains.build.Build
|
import org.jetbrains.elastic.*
|
||||||
|
import org.jetbrains.network.*
|
||||||
|
import org.jetbrains.buildInfo.Build
|
||||||
|
import org.jetbrains.analyzer.*
|
||||||
|
import org.jetbrains.report.*
|
||||||
|
import org.jetbrains.utils.*
|
||||||
|
|
||||||
|
// TODO - create DSL for ES requests?
|
||||||
|
|
||||||
const val teamCityUrl = "https://buildserver.labs.intellij.net/app/rest"
|
const val teamCityUrl = "https://buildserver.labs.intellij.net/app/rest"
|
||||||
const val artifactoryUrl = "https://repo.labs.intellij.net/kotlin-native-benchmarks"
|
const val artifactoryUrl = "https://repo.labs.intellij.net/kotlin-native-benchmarks"
|
||||||
const val buildsFileName = "buildsSummary.csv"
|
|
||||||
const val goldenResultsFileName = "goldenResults.csv"
|
|
||||||
const val artifactoryBuildsDirectory = "builds"
|
|
||||||
const val buildsInfoPartsNumber = 11
|
|
||||||
|
|
||||||
operator fun <K, V> Map<K, V>?.get(key: K) = this?.get(key)
|
operator fun <K, V> Map<K, V>?.get(key: K) = this?.get(key)
|
||||||
|
|
||||||
fun getArtifactoryHeader(artifactoryApiKey: String) = Pair("X-JFrog-Art-Api", artifactoryApiKey)
|
fun getArtifactoryHeader(artifactoryApiKey: String) = Pair("X-JFrog-Art-Api", artifactoryApiKey)
|
||||||
|
|
||||||
// Local cache for saving information about builds got from Artifactory.
|
// Convert saved old report to expected new format.
|
||||||
object LocalCache {
|
internal fun convertToNewFormat(data: JsonObject): List<Any> {
|
||||||
private val knownTargets = listOf("Linux", "MacOSX", "Windows10")
|
val env = Environment.create(data.getRequiredField("env"))
|
||||||
private val buildsInfo = mutableMapOf<String, MutableMap<String, String>>()
|
val benchmarksObj = data.getRequiredField("benchmarks")
|
||||||
|
val compilerDescription = data.getRequiredField("kotlin")
|
||||||
fun clean(onlyTarget: String? = null) {
|
val compiler = Compiler.create(compilerDescription)
|
||||||
onlyTarget?.let {
|
val backend = (compilerDescription as JsonObject).getRequiredField("backend")
|
||||||
buildsInfo[onlyTarget]?.clear()
|
val flagsArray = (backend as JsonObject).getOptionalField("flags")
|
||||||
} ?: buildsInfo.clear()
|
var flags: List<String> = emptyList()
|
||||||
|
if (flagsArray != null && flagsArray is JsonArray) {
|
||||||
|
flags = flagsArray.jsonArray.map { (it as JsonLiteral).unquoted() }
|
||||||
}
|
}
|
||||||
|
val benchmarksList = parseBenchmarksArray(benchmarksObj)
|
||||||
|
|
||||||
fun fill(onlyTarget: String? = null) {
|
return listOf(env, compiler, benchmarksList, flags)
|
||||||
onlyTarget?.let {
|
|
||||||
val buildsDescription = getBuildsInfoFromArtifactory(onlyTarget).lines().drop(1)
|
|
||||||
buildsInfo[onlyTarget] = mutableMapOf<String, String>()
|
|
||||||
buildsDescription.forEach {
|
|
||||||
if (!it.isEmpty()) {
|
|
||||||
val buildNumber = it.substringBefore(',')
|
|
||||||
if (!"\\d+(\\.\\d+)+(-M\\d)?-\\w+-\\d+".toRegex().matches(buildNumber)) {
|
|
||||||
error("Build number $buildNumber differs from expected format. File with data for " +
|
|
||||||
"target $onlyTarget could be corrupted.")
|
|
||||||
}
|
|
||||||
buildsInfo[onlyTarget]!![buildNumber] = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ?: run {
|
|
||||||
knownTargets.forEach {
|
|
||||||
fill(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun buildExists(target: String, buildNumber: String) =
|
|
||||||
buildsInfo[target][buildNumber]?.let { true } ?: false
|
|
||||||
|
|
||||||
fun delete(target: String, builds: Iterable<String>, apiKey: String): Boolean {
|
|
||||||
// Delete from Artifactory.
|
|
||||||
val buildsDescription = getBuildsInfoFromArtifactory(target).lines()
|
|
||||||
|
|
||||||
val newBuildsDescription = buildsDescription.filter {
|
|
||||||
val buildNumber = it.substringBefore(',')
|
|
||||||
buildNumber !in builds
|
|
||||||
}
|
|
||||||
if (newBuildsDescription.size < buildsDescription.size) {
|
|
||||||
// Upload new version of file.
|
|
||||||
val uploadUrl = "$artifactoryUrl/$artifactoryBuildsDirectory/$target/$buildsFileName"
|
|
||||||
sendUploadRequest(uploadUrl, newBuildsDescription.joinToString("\n"),
|
|
||||||
extraHeaders = listOf(getArtifactoryHeader(apiKey)))
|
|
||||||
|
|
||||||
// Reload values.
|
|
||||||
clean(target)
|
|
||||||
fill(target)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getBuilds(target: String, buildNumber: String? = null) =
|
|
||||||
buildsInfo[target]?.let { buildsList ->
|
|
||||||
buildNumber?.let {
|
|
||||||
// Check if interesting build id is in cache.
|
|
||||||
buildsList[it]?.let { buildsList.values }
|
|
||||||
} ?: buildsList.values
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun get(target: String, buildId: String? = null): Collection<String> {
|
|
||||||
val builds = getBuilds(target, buildId)
|
|
||||||
|
|
||||||
if (builds.isNullOrEmpty()) {
|
|
||||||
// No suitable builds were found.
|
|
||||||
// Refill cache.
|
|
||||||
clean(target)
|
|
||||||
fill(target)
|
|
||||||
return getBuilds(target, buildId) ?: listOf<String>()
|
|
||||||
}
|
|
||||||
|
|
||||||
return builds
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert data results to expected format.
|
||||||
|
internal fun convert(json: String, buildNumber: String, target: String): List<BenchmarksReport> {
|
||||||
|
val data = JsonTreeParser.parse(json)
|
||||||
|
val reports = if (data is JsonArray) {
|
||||||
|
data.map { convertToNewFormat(it as JsonObject) }
|
||||||
|
} else listOf(convertToNewFormat(data as JsonObject))
|
||||||
|
|
||||||
|
// Restored flags for old reports.
|
||||||
|
val knownFlags = mapOf(
|
||||||
|
"Cinterop" to listOf("-opt"),
|
||||||
|
"FrameworkBenchmarksAnalyzer" to listOf("-g"),
|
||||||
|
"HelloWorld" to if (target == "Mac OS X")
|
||||||
|
listOf("-Xcache-directory=/Users/teamcity/buildAgent/work/c104dee5223a31c5/test_dist/klib/cache/macos_x64-gSTATIC", "-g")
|
||||||
|
else listOf("-g"),
|
||||||
|
"Numerical" to listOf("-opt"),
|
||||||
|
"ObjCInterop" to listOf("-opt"),
|
||||||
|
"Ring" to listOf("-opt"),
|
||||||
|
"Startup" to listOf("-opt"),
|
||||||
|
"swiftInterop" to listOf("-opt"),
|
||||||
|
"Videoplayer" to if (target == "Mac OS X")
|
||||||
|
listOf("-Xcache-directory=/Users/teamcity/buildAgent/work/c104dee5223a31c5/test_dist/klib/cache/macos_x64-gSTATIC", "-g")
|
||||||
|
else listOf("-g")
|
||||||
|
)
|
||||||
|
|
||||||
|
return reports.map { elements ->
|
||||||
|
val benchmarks = (elements[2] as List<BenchmarkResult>).groupBy { it.name.substringBefore('.').substringBefore(':') }
|
||||||
|
val parsedFlags = elements[3] as List<String>
|
||||||
|
benchmarks.map { (setName, results) ->
|
||||||
|
val flags = if (parsedFlags.isNotEmpty() && parsedFlags[0] == "-opt") knownFlags[setName]!! else parsedFlags
|
||||||
|
val savedCompiler = elements[1] as Compiler
|
||||||
|
val compiler = Compiler(Compiler.Backend(savedCompiler.backend.type, savedCompiler.backend.version, flags),
|
||||||
|
savedCompiler.kotlinVersion)
|
||||||
|
val newReport = BenchmarksReport(elements[0] as Environment, results, compiler)
|
||||||
|
newReport.buildNumber = buildNumber
|
||||||
|
newReport
|
||||||
|
}
|
||||||
|
}.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Golden result value used to get normalized results.
|
||||||
data class GoldenResult(val benchmarkName: String, val metric: String, val value: Double)
|
data class GoldenResult(val benchmarkName: String, val metric: String, val value: Double)
|
||||||
data class GoldenResultsInfo(val apiKey: String, val goldenResults: Array<GoldenResult>)
|
data class GoldenResultsInfo(val goldenResults: Array<GoldenResult>)
|
||||||
|
|
||||||
|
// Convert information about golden results to benchmarks report format.
|
||||||
|
fun GoldenResultsInfo.toBenchmarksReport(): BenchmarksReport {
|
||||||
|
val benchmarksSamples = goldenResults.map {
|
||||||
|
BenchmarkResult(it.benchmarkName, BenchmarkResult.Status.PASSED,
|
||||||
|
it.value, BenchmarkResult.metricFromString(it.metric)!!, it.value, 1, 0)
|
||||||
|
}
|
||||||
|
val compiler = Compiler(Compiler.Backend(Compiler.BackendType.NATIVE, "golden", emptyList()), "golden")
|
||||||
|
val environment = Environment(Environment.Machine("golden", "golden"), Environment.JDKInstance("golden", "golden"))
|
||||||
|
return BenchmarksReport(environment,
|
||||||
|
benchmarksSamples, compiler)
|
||||||
|
}
|
||||||
|
|
||||||
// Build information provided from request.
|
// Build information provided from request.
|
||||||
data class BuildInfo(val buildNumber: String, val branch: String, val startTime: String,
|
data class TCBuildInfo(val buildNumber: String, val branch: String, val startTime: String,
|
||||||
val finishTime: String)
|
val finishTime: String)
|
||||||
|
|
||||||
data class BuildRegister(val buildId: String, val teamCityUser: String, val teamCityPassword: String,
|
data class BuildRegister(val buildId: String, val teamCityUser: String, val teamCityPassword: String,
|
||||||
val artifactoryApiKey: String, val target: String, val buildType: String, val failuresNumber: Int,
|
val bundleSize: String?, val fileWithResult: String) {
|
||||||
val executionTime: String, val compileTime: String, val codeSize: String,
|
|
||||||
val bundleSize: String?) {
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create(json: String): BuildRegister {
|
fun create(json: String): BuildRegister {
|
||||||
val requestDetails = JSON.parse<BuildRegister>(json)
|
val requestDetails = JSON.parse<BuildRegister>(json)
|
||||||
// Parse method doesn't create real instance with all methods. So create it by hands.
|
// Parse method doesn't create real instance with all methods. So create it by hands.
|
||||||
return BuildRegister(requestDetails.buildId, requestDetails.teamCityUser, requestDetails.teamCityPassword,
|
return BuildRegister(requestDetails.buildId, requestDetails.teamCityUser, requestDetails.teamCityPassword,
|
||||||
requestDetails.artifactoryApiKey, requestDetails.target, requestDetails.buildType,
|
requestDetails.bundleSize, requestDetails.fileWithResult)
|
||||||
requestDetails.failuresNumber, requestDetails.executionTime, requestDetails.compileTime,
|
|
||||||
requestDetails.codeSize, requestDetails.bundleSize)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val teamCityBuildUrl: String by lazy { "$teamCityUrl/builds/id:$buildId" }
|
private val teamCityBuildUrl: String by lazy { "builds/id:$buildId" }
|
||||||
|
|
||||||
val changesListUrl: String by lazy {
|
val changesListUrl: String by lazy {
|
||||||
"$teamCityUrl/changes/?locator=build:id:$buildId"
|
"changes/?locator=build:id:$buildId"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendTeamCityRequest(url: String) = sendGetRequest(url, teamCityUser, teamCityPassword)
|
val teamCityArtifactsUrl: String by lazy { "builds/id:$buildId/artifacts/content/$fileWithResult" }
|
||||||
|
|
||||||
|
fun sendTeamCityRequest(url: String, json: Boolean = false) =
|
||||||
|
UrlNetworkConnector(teamCityUrl).sendRequest(RequestMethod.GET, url, teamCityUser, teamCityPassword, json)
|
||||||
|
|
||||||
private fun format(timeValue: Int): String =
|
private fun format(timeValue: Int): String =
|
||||||
if (timeValue < 10) "0$timeValue" else "$timeValue"
|
if (timeValue < 10) "0$timeValue" else "$timeValue"
|
||||||
|
|
||||||
fun getBuildInformation(): BuildInfo {
|
fun getBuildInformation(): Promise<TCBuildInfo> {
|
||||||
val buildNumber = sendTeamCityRequest("$teamCityBuildUrl/number")
|
return Promise.all(arrayOf(sendTeamCityRequest("$teamCityBuildUrl/number"),
|
||||||
val branch = sendTeamCityRequest("$teamCityBuildUrl/branchName")
|
sendTeamCityRequest("$teamCityBuildUrl/branchName"),
|
||||||
val startTime = sendTeamCityRequest("$teamCityBuildUrl/startDate")
|
sendTeamCityRequest("$teamCityBuildUrl/startDate"))).then { results ->
|
||||||
val currentTime = Date()
|
val (buildNumber, branch, startTime) = results
|
||||||
val timeZone = currentTime.getTimezoneOffset() / -60 // Convert to hours.
|
val currentTime = Date()
|
||||||
// Get finish time as current time, because buid on TeamCity isn't finished.
|
val timeZone = currentTime.getTimezoneOffset() / -60 // Convert to hours.
|
||||||
val finishTime = "${format(currentTime.getUTCFullYear())}" +
|
// Get finish time as current time, because buid on TeamCity isn't finished.
|
||||||
"${format(currentTime.getUTCMonth() + 1)}" +
|
val finishTime = "${format(currentTime.getUTCFullYear())}" +
|
||||||
"${format(currentTime.getUTCDate())}" +
|
"${format(currentTime.getUTCMonth() + 1)}" +
|
||||||
"T${format(currentTime.getUTCHours())}" +
|
"${format(currentTime.getUTCDate())}" +
|
||||||
"${format(currentTime.getUTCMinutes())}" +
|
"T${format(currentTime.getUTCHours())}" +
|
||||||
"${format(currentTime.getUTCSeconds())}" +
|
"${format(currentTime.getUTCMinutes())}" +
|
||||||
"${if (timeZone > 0) "+" else "-"}${format(timeZone)}${format(0)}"
|
"${format(currentTime.getUTCSeconds())}" +
|
||||||
return BuildInfo(buildNumber, branch, startTime, finishTime)
|
"${if (timeZone > 0) "+" else "-"}${format(timeZone)}${format(0)}"
|
||||||
}
|
TCBuildInfo(buildNumber, branch, startTime, finishTime)
|
||||||
}
|
|
||||||
|
|
||||||
data class Commit(val revision: String, val developer: String)
|
|
||||||
|
|
||||||
// List of commits.
|
|
||||||
class CommitsList(data: JsonElement): ConvertedFromJson {
|
|
||||||
|
|
||||||
val commits: List<Commit>
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (data !is JsonObject) {
|
|
||||||
error("Commits description is expected to be a json object!")
|
|
||||||
}
|
|
||||||
val changesElement = data.getOptionalField("change")
|
|
||||||
commits = changesElement?.let {
|
|
||||||
if (changesElement !is JsonArray) {
|
|
||||||
error("Change field is expected to be an array. Please, check source.")
|
|
||||||
}
|
|
||||||
changesElement.jsonArray.map {
|
|
||||||
with(it as JsonObject) {
|
|
||||||
Commit(elementToString(getRequiredField("version"), "version"),
|
|
||||||
elementToString(getRequiredField("username"), "username")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ?: listOf<Commit>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getBuildsInfoFromArtifactory(target: String) =
|
|
||||||
sendGetRequest("$artifactoryUrl/$artifactoryBuildsDirectory/$target/$buildsFileName")
|
|
||||||
|
|
||||||
fun checkBuildType(currentType: String, targetType: String): Boolean {
|
|
||||||
val releasesBuildTypes = listOf("release", "eap", "rc1", "rc2")
|
|
||||||
return if (targetType == "release") currentType in releasesBuildTypes else currentType == targetType
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse and postprocess result of response with build description.
|
|
||||||
fun prepareBuildsResponse(builds: Collection<String>, type: String, branch: String, buildNumber: String? = null): List<Build> {
|
|
||||||
val buildsObjects = mutableListOf<Build>()
|
|
||||||
builds.forEach {
|
|
||||||
val tokens = buildDescriptionToTokens(it)
|
|
||||||
if ((checkBuildType(tokens[5], type) || type == "day") && (branch == tokens[3] || branch == "all")
|
|
||||||
|| tokens[0] == buildNumber) {
|
|
||||||
buildsObjects.add(Build(tokens[0], tokens[1], tokens[2], tokens[3],
|
|
||||||
tokens[4], tokens[5], tokens[6].toInt(), tokens[7], tokens[8], tokens[9],
|
|
||||||
if (tokens[10] == "-") null else tokens[10]))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buildsObjects
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildDescriptionToTokens(buildDescription: String): List<String> {
|
// Get builds numbers in right order.
|
||||||
val tokens = buildDescription.split(",").map { it.trim() }
|
internal fun <T> orderedValues(values: List<T>, buildElement: (T) -> String = { it -> it.toString() },
|
||||||
if (tokens.size != buildsInfoPartsNumber) {
|
skipMilestones: Boolean = false) =
|
||||||
error("Build description $buildDescription doesn't contain all necessary information. " +
|
values.sortedWith(
|
||||||
"File with data could be corrupted.")
|
compareBy({ buildElement(it).substringBefore(".").toInt() },
|
||||||
}
|
{ buildElement(it).substringAfter(".").substringBefore("-").toDouble() },
|
||||||
return tokens
|
{
|
||||||
}
|
if (skipMilestones) 0
|
||||||
|
else if (buildElement(it).substringAfter("-").startsWith("M"))
|
||||||
|
buildElement(it).substringAfter("M").substringBefore("-").toInt()
|
||||||
|
else
|
||||||
|
Int.MAX_VALUE
|
||||||
|
},
|
||||||
|
{ buildElement(it).substringAfterLast("-").toDouble() }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ElasticSearch connector for work with custom instance.
|
||||||
|
internal val localHostElasticConnector = UrlNetworkConnector("http://localhost", 9200)
|
||||||
|
// ElasticSearch connector for work with AWS instance.
|
||||||
|
internal val awsElasticConnector = AWSNetworkConnector()
|
||||||
|
internal val networkConnector = awsElasticConnector
|
||||||
|
|
||||||
|
fun urlParameterToBaseFormat(value: dynamic) =
|
||||||
|
value.toString().replace("_", " ")
|
||||||
|
|
||||||
// Routing of requests to current server.
|
// Routing of requests to current server.
|
||||||
fun router() {
|
fun router() {
|
||||||
val express = require("express")
|
val express = require("express")
|
||||||
val router = express.Router()
|
val router = express.Router()
|
||||||
|
val connector = ElasticSearchConnector(networkConnector)
|
||||||
|
val benchmarksDispatcher = BenchmarksIndexesDispatcher(connector, "env.machine.os",
|
||||||
|
listOf("Linux", "Mac OS X", "Windows 10")
|
||||||
|
)
|
||||||
|
val goldenIndex = GoldenResultsIndex(connector)
|
||||||
|
val buildInfoIndex = BuildInfoIndex(connector)
|
||||||
|
|
||||||
|
router.get("/createMapping") { request, response ->
|
||||||
|
buildInfoIndex.createMapping().then { _ ->
|
||||||
|
response.sendStatus(200)
|
||||||
|
}.catch { _ ->
|
||||||
|
response.sendStatus(400)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get consistent build information in cases of rerunning the same build.
|
||||||
|
suspend fun getConsistentBuildInfo(buildInfoInstance: BuildInfo, reports: List<BenchmarksReport>,
|
||||||
|
rerunNumber: Int = 1): BuildInfo {
|
||||||
|
var currentBuildInfo = buildInfoInstance
|
||||||
|
if (buildExists(currentBuildInfo, buildInfoIndex)) {
|
||||||
|
// Check if benchmarks aren't repeated.
|
||||||
|
val existingBecnhmarks = benchmarksDispatcher.getBenchmarksList(currentBuildInfo.buildNumber,
|
||||||
|
currentBuildInfo.agentInfo).await()
|
||||||
|
val benchmarksToRegister = reports.map { it.benchmarks.keys }.flatten()
|
||||||
|
if (existingBecnhmarks.toTypedArray().intersect(benchmarksToRegister).isNotEmpty()) {
|
||||||
|
// Build was rerun.
|
||||||
|
val buildNumber = "${currentBuildInfo.buildNumber}.$rerunNumber"
|
||||||
|
currentBuildInfo = BuildInfo(buildNumber, currentBuildInfo.startTime, currentBuildInfo.endTime,
|
||||||
|
currentBuildInfo.commitsList, currentBuildInfo.branch, currentBuildInfo.agentInfo)
|
||||||
|
return getConsistentBuildInfo(currentBuildInfo, reports, rerunNumber + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentBuildInfo
|
||||||
|
}
|
||||||
|
|
||||||
// Register build on Artifactory.
|
// Register build on Artifactory.
|
||||||
router.post("/register", { request, response ->
|
router.post("/register") { request, response ->
|
||||||
val maxCommitsNumber = 5
|
|
||||||
val register = BuildRegister.create(JSON.stringify(request.body))
|
val register = BuildRegister.create(JSON.stringify(request.body))
|
||||||
|
|
||||||
// Get information from TeamCity.
|
// Get information from TeamCity.
|
||||||
val buildInfo = register.getBuildInformation()
|
register.getBuildInformation().then { buildInfo ->
|
||||||
val changes = sendGetRequest(register.changesListUrl, register.teamCityUser,
|
register.sendTeamCityRequest(register.changesListUrl, true).then { changes ->
|
||||||
register.teamCityPassword, true)
|
val commitsList = CommitsList(JsonTreeParser.parse(changes))
|
||||||
val commitsList = CommitsList(JsonTreeParser.parse(changes))
|
// Get artifact.
|
||||||
val commitsDescription = buildString {
|
register.sendTeamCityRequest(register.teamCityArtifactsUrl).then { resultsContent ->
|
||||||
if (commitsList.commits.size > maxCommitsNumber) {
|
launch {
|
||||||
append("${commitsList.commits.get(0).revision} by ${commitsList.commits.get(0).developer};")
|
val reportData = JsonTreeParser.parse(resultsContent)
|
||||||
append("${commitsList.commits.get(1).revision} by ${commitsList.commits.get(1).developer};")
|
val reports = if (reportData is JsonArray) {
|
||||||
append("...;")
|
reportData.map { BenchmarksReport.create(it as JsonObject) }
|
||||||
val beforeLast = commitsList.commits.lastIndex - 1
|
} else listOf(BenchmarksReport.create(reportData as JsonObject))
|
||||||
append("${commitsList.commits.get(beforeLast).revision} by ${commitsList.commits.get(beforeLast).developer};")
|
val goldenResultPromise = getGoldenResults(goldenIndex)
|
||||||
append("${commitsList.commits.last().revision} by ${commitsList.commits.last().developer};")
|
val goldenResults = goldenResultPromise.await()
|
||||||
} else {
|
// Register build information.
|
||||||
commitsList.commits.forEach {
|
var buildInfoInstance = getConsistentBuildInfo(
|
||||||
append("${it.revision} by ${it.developer};")
|
BuildInfo(buildInfo.buildNumber, buildInfo.startTime, buildInfo.finishTime,
|
||||||
|
commitsList, buildInfo.branch, reports[0].env.machine.os),
|
||||||
|
reports
|
||||||
|
)
|
||||||
|
if (register.bundleSize != null) {
|
||||||
|
// Add bundle size.
|
||||||
|
val bundleSizeBenchmark = BenchmarkResult("KotlinNative",
|
||||||
|
BenchmarkResult.Status.PASSED, register.bundleSize.toDouble(),
|
||||||
|
BenchmarkResult.Metric.BUNDLE_SIZE, 0.0, 1, 0)
|
||||||
|
val bundleSizeReport = BenchmarksReport(reports[0].env,
|
||||||
|
listOf(bundleSizeBenchmark), reports[0].compiler)
|
||||||
|
bundleSizeReport.buildNumber = buildInfoInstance.buildNumber
|
||||||
|
benchmarksDispatcher.insert(bundleSizeReport, reports[0].env.machine.os).then { _ ->
|
||||||
|
println("[BUNDLE] Success insert ${buildInfoInstance.buildNumber}")
|
||||||
|
}.catch { errorResponse ->
|
||||||
|
println("Failed to insert data for build")
|
||||||
|
println(errorResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val insertResults = reports.map {
|
||||||
|
val benchmarksReport = SummaryBenchmarksReport(it).getBenchmarksReport()
|
||||||
|
.normalizeBenchmarksSet(goldenResults)
|
||||||
|
benchmarksReport.buildNumber = buildInfoInstance.buildNumber
|
||||||
|
// Save results in database.
|
||||||
|
benchmarksDispatcher.insert(benchmarksReport, benchmarksReport.env.machine.os)
|
||||||
|
}
|
||||||
|
if (!buildExists(buildInfoInstance, buildInfoIndex)) {
|
||||||
|
buildInfoIndex.insert(buildInfoInstance).then { _ ->
|
||||||
|
println("Success insert build information for ${buildInfoInstance.buildNumber}")
|
||||||
|
}.catch {
|
||||||
|
response.sendStatus(400)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Promise.all(insertResults.toTypedArray()).then { _ ->
|
||||||
|
response.sendStatus(200)
|
||||||
|
}.catch {
|
||||||
|
response.sendStatus(400)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Get summary file from Artifactory.
|
|
||||||
var buildsDescription = getBuildsInfoFromArtifactory(register.target)
|
|
||||||
// Add information about new build.
|
|
||||||
//var buildsDescription = "build, start time, finish time, branch, commits, type, failuresNumber, execution time, compile time, code size, bundle size\n"
|
|
||||||
buildsDescription += "${buildInfo.buildNumber}, ${buildInfo.startTime}, ${buildInfo.finishTime}, " +
|
|
||||||
"${buildInfo.branch}, $commitsDescription, ${register.buildType}, ${register.failuresNumber}, " +
|
|
||||||
"${register.executionTime}, ${register.compileTime}, ${register.codeSize}, " +
|
|
||||||
"${register.bundleSize ?: "-"}\n"
|
|
||||||
|
|
||||||
// Upload new version of file.
|
|
||||||
val uploadUrl = "$artifactoryUrl/$artifactoryBuildsDirectory/${register.target}/${buildsFileName}"
|
|
||||||
sendUploadRequest(uploadUrl, buildsDescription, extraHeaders = listOf(getArtifactoryHeader(register.artifactoryApiKey)))
|
|
||||||
|
|
||||||
LocalCache.clean(register.target)
|
|
||||||
LocalCache.fill(register.target)
|
|
||||||
|
|
||||||
// Send response.
|
|
||||||
response.sendStatus(200)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Register golden results to normalize on Artifactory.
|
// Register golden results to normalize on Artifactory.
|
||||||
router.post("/registerGolden", { request, response ->
|
router.post("/registerGolden", { request, response ->
|
||||||
val goldenResultsInfo = JSON.parse<GoldenResultsInfo>(JSON.stringify(request.body))
|
val goldenResultsInfo: GoldenResultsInfo = JSON.parse<GoldenResultsInfo>(JSON.stringify(request.body))
|
||||||
val buildsDescription = StringBuilder(sendGetRequest("$artifactoryUrl/$artifactoryBuildsDirectory/$goldenResultsFileName"))
|
val goldenReport = goldenResultsInfo.toBenchmarksReport()
|
||||||
goldenResultsInfo.goldenResults.forEach {
|
goldenIndex.insert(goldenReport).then { _ ->
|
||||||
buildsDescription.append("${it.benchmarkName}, ${it.metric}, ${it.value}\n")
|
response.sendStatus(200)
|
||||||
|
}.catch {
|
||||||
|
response.sendStatus(400)
|
||||||
}
|
}
|
||||||
// Upload new version of file.
|
|
||||||
val uploadUrl = "$artifactoryUrl/$artifactoryBuildsDirectory/$goldenResultsFileName"
|
|
||||||
sendUploadRequest(uploadUrl, buildsDescription.toString(),
|
|
||||||
extraHeaders = listOf(getArtifactoryHeader(goldenResultsInfo.apiKey)))
|
|
||||||
// Send response.
|
|
||||||
response.sendStatus(200)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get list of builds.
|
// Get builds description with additional information.
|
||||||
router.get("/builds/:target/:type/:branch/:id", { request, response ->
|
router.get("/buildsDesc/:target", { request, response ->
|
||||||
val builds = LocalCache[request.params.target, request.params.id]
|
CachableResponseDispatcher.getResponse(request, response) { success, reject ->
|
||||||
response.json(prepareBuildsResponse(builds, request.params.type, request.params.branch, request.params.id))
|
val target = request.params.target.toString().replace('_', ' ')
|
||||||
|
|
||||||
|
var branch: String? = null
|
||||||
|
var type: String? = null
|
||||||
|
if (request.query != undefined) {
|
||||||
|
if (request.query.branch != undefined) {
|
||||||
|
branch = request.query.branch
|
||||||
|
}
|
||||||
|
if (request.query.type != undefined) {
|
||||||
|
type = request.query.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getBuildsInfo(type, branch, target, buildInfoIndex).then { buildsInfo ->
|
||||||
|
val buildNumbers = buildsInfo.map { it.buildNumber }
|
||||||
|
// Get number of failed benchmarks for each build.
|
||||||
|
benchmarksDispatcher.getFailuresNumber(target, buildNumbers).then { failures ->
|
||||||
|
success(orderedValues(buildsInfo, { it -> it.buildNumber }, branch == "master").map {
|
||||||
|
Build(it.buildNumber, it.startTime, it.endTime, it.branch,
|
||||||
|
it.commitsList.serializeFields(), failures[it.buildNumber] ?: 0)
|
||||||
|
})
|
||||||
|
}.catch { errorResponse ->
|
||||||
|
println("Error during getting failures numbers")
|
||||||
|
println(errorResponse)
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
}.catch {
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get("/builds/:target/:type/:branch", { request, response ->
|
// Get values of current metric.
|
||||||
val builds = LocalCache[request.params.target]
|
router.get("/metricValue/:target/:metric", { request, response ->
|
||||||
response.json(prepareBuildsResponse(builds, request.params.type, request.params.branch))
|
CachableResponseDispatcher.getResponse(request, response) { success, reject ->
|
||||||
|
val metric = request.params.metric
|
||||||
|
val target = request.params.target.toString().replace('_', ' ')
|
||||||
|
var samples: List<String> = emptyList()
|
||||||
|
var aggregation = "geomean"
|
||||||
|
var normalize = false
|
||||||
|
var branch: String? = null
|
||||||
|
var type: String? = null
|
||||||
|
var excludeNames: List<String> = emptyList()
|
||||||
|
|
||||||
|
// Parse parameters from request if it exists.
|
||||||
|
if (request.query != undefined) {
|
||||||
|
if (request.query.samples != undefined) {
|
||||||
|
samples = request.query.samples.toString().split(",").map { it.trim() }
|
||||||
|
}
|
||||||
|
if (request.query.agr != undefined) {
|
||||||
|
aggregation = request.query.agr.toString()
|
||||||
|
}
|
||||||
|
if (request.query.normalize != undefined) {
|
||||||
|
normalize = true
|
||||||
|
}
|
||||||
|
if (request.query.branch != undefined) {
|
||||||
|
branch = request.query.branch
|
||||||
|
}
|
||||||
|
if (request.query.type != undefined) {
|
||||||
|
type = request.query.type
|
||||||
|
}
|
||||||
|
if (request.query.exclude != undefined) {
|
||||||
|
excludeNames = request.query.exclude.toString().split(",").map { it.trim() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getBuildsNumbers(type, branch, target, buildInfoIndex).then { buildNumbers ->
|
||||||
|
if (aggregation == "geomean") {
|
||||||
|
// Get geometric mean for samples.
|
||||||
|
benchmarksDispatcher.getGeometricMean(metric, target, buildNumbers, normalize,
|
||||||
|
excludeNames).then { geoMeansValues ->
|
||||||
|
success(orderedValues(geoMeansValues, { it -> it.first }, branch == "master"))
|
||||||
|
}.catch { errorResponse ->
|
||||||
|
println("Error during getting geometric mean")
|
||||||
|
println(errorResponse)
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
benchmarksDispatcher.getSamples(metric, target, samples, buildNumbers, normalize).then { geoMeansValues ->
|
||||||
|
success(orderedValues(geoMeansValues, { it -> it.first }, branch == "master"))
|
||||||
|
}.catch {
|
||||||
|
println("Error during getting samples")
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.catch {
|
||||||
|
println("Error during getting builds information")
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get("/branches/:target", { request, response ->
|
// Get branches for [target].
|
||||||
val builds = LocalCache[request.params.target]
|
router.get("/branches", { request, response ->
|
||||||
response.json(builds.map { buildDescriptionToTokens(it)[3] }.distinct())
|
CachableResponseDispatcher.getResponse(request, response) { success, reject ->
|
||||||
|
distinctValues("branch", buildInfoIndex).then { results ->
|
||||||
|
success(results)
|
||||||
|
}.catch { errorMessage ->
|
||||||
|
error(errorMessage.message ?: "Failed getting branches list.")
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Get build numbers for [target].
|
||||||
router.get("/buildsNumbers/:target", { request, response ->
|
router.get("/buildsNumbers/:target", { request, response ->
|
||||||
val builds = LocalCache[request.params.target]
|
CachableResponseDispatcher.getResponse(request, response) { success, reject ->
|
||||||
response.json(builds.map { buildDescriptionToTokens(it)[0] }.distinct())
|
distinctValues("buildNumber", buildInfoIndex).then { results ->
|
||||||
|
success(results)
|
||||||
|
}.catch { errorMessage ->
|
||||||
|
println(errorMessage.message ?: "Failed getting branches list.")
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get("/clean", { _, response ->
|
// Conert data and migrate it from Artifactory to DB.
|
||||||
LocalCache.clean()
|
router.get("/migrate/:target", { request, response ->
|
||||||
response.sendStatus(200)
|
val target = urlParameterToBaseFormat(request.params.target)
|
||||||
})
|
val targetPathName = target.replace(" ", "")
|
||||||
|
var buildNumber: String? = null
|
||||||
|
if (request.query != undefined) {
|
||||||
|
if (request.query.buildNumber != undefined) {
|
||||||
|
buildNumber = request.query.buildNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getBuildsInfoFromArtifactory(targetPathName).then { buildInfo ->
|
||||||
|
launch {
|
||||||
|
val buildsDescription = buildInfo.lines().drop(1)
|
||||||
|
var shouldConvert = buildNumber?.let { false } ?: true
|
||||||
|
val goldenResultPromise = getGoldenResults(goldenIndex)
|
||||||
|
val goldenResults = goldenResultPromise.await()
|
||||||
|
val buildsSet = mutableSetOf<String>()
|
||||||
|
buildsDescription.forEach {
|
||||||
|
if (!it.isEmpty()) {
|
||||||
|
val currentBuildNumber = it.substringBefore(',')
|
||||||
|
if (!"\\d+(\\.\\d+)+(-M\\d)?-\\w+-\\d+(\\.\\d+)?".toRegex().matches(currentBuildNumber)) {
|
||||||
|
error("Build number $currentBuildNumber differs from expected format. File with data for " +
|
||||||
|
"target $target could be corrupted.")
|
||||||
|
}
|
||||||
|
if (!shouldConvert && buildNumber != null && buildNumber == currentBuildNumber) {
|
||||||
|
shouldConvert = true
|
||||||
|
}
|
||||||
|
if (shouldConvert) {
|
||||||
|
// Save data from Artifactory into database.
|
||||||
|
val artifactoryUrlConnector = UrlNetworkConnector(artifactoryUrl)
|
||||||
|
val fileName = "nativeReport.json"
|
||||||
|
val accessFileUrl = "$targetPathName/$currentBuildNumber/$fileName"
|
||||||
|
val extrenalFileName = if (target == "Linux") "externalReport.json" else "spaceFrameworkReport.json"
|
||||||
|
val accessExternalFileUrl = "$targetPathName/$currentBuildNumber/$extrenalFileName"
|
||||||
|
val infoParts = it.split(", ")
|
||||||
|
if ((infoParts[3] == "master" || "eap" in currentBuildNumber || "release" in currentBuildNumber) &&
|
||||||
|
currentBuildNumber !in buildsSet) {
|
||||||
|
try {
|
||||||
|
buildsSet.add(currentBuildNumber)
|
||||||
|
val jsonReport = artifactoryUrlConnector.sendRequest(RequestMethod.GET, accessFileUrl).await()
|
||||||
|
var reports = convert(jsonReport, currentBuildNumber, target)
|
||||||
|
val buildInfoRecord = BuildInfo(currentBuildNumber, infoParts[1], infoParts[2],
|
||||||
|
CommitsList.parse(infoParts[4]), infoParts[3], target)
|
||||||
|
|
||||||
router.get("/fill", { _, response ->
|
val externalJsonReport = artifactoryUrlConnector.sendOptionalRequest(RequestMethod.GET, accessExternalFileUrl).await()
|
||||||
LocalCache.fill()
|
buildInfoIndex.insert(buildInfoRecord).then { _ ->
|
||||||
response.sendStatus(200)
|
externalJsonReport?.let {
|
||||||
|
var externalReports = convert(externalJsonReport, currentBuildNumber, target)
|
||||||
|
externalReports.forEach { externalReport ->
|
||||||
|
val extrenalAdditionalReport = SummaryBenchmarksReport(externalReport)
|
||||||
|
.getBenchmarksReport().normalizeBenchmarksSet(goldenResults)
|
||||||
|
extrenalAdditionalReport.buildNumber = currentBuildNumber
|
||||||
|
benchmarksDispatcher.insert(extrenalAdditionalReport, target).then { _ ->
|
||||||
|
println("[External] Success insert ${buildInfoRecord.buildNumber}")
|
||||||
|
}.catch { errorResponse ->
|
||||||
|
println("Failed to insert data for build")
|
||||||
|
println(errorResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val bundleSize = if (infoParts[10] != "-") infoParts[10] else null
|
||||||
|
if (bundleSize != null) {
|
||||||
|
// Add bundle size.
|
||||||
|
val bundleSizeBenchmark = BenchmarkResult("KotlinNative",
|
||||||
|
BenchmarkResult.Status.PASSED, bundleSize.toDouble(),
|
||||||
|
BenchmarkResult.Metric.BUNDLE_SIZE, 0.0, 1, 0)
|
||||||
|
val bundleSizeReport = BenchmarksReport(reports[0].env,
|
||||||
|
listOf(bundleSizeBenchmark), reports[0].compiler)
|
||||||
|
bundleSizeReport.buildNumber = currentBuildNumber
|
||||||
|
benchmarksDispatcher.insert(bundleSizeReport, target).then { _ ->
|
||||||
|
println("[BUNDLE] Success insert ${buildInfoRecord.buildNumber}")
|
||||||
|
}.catch { errorResponse ->
|
||||||
|
println("Failed to insert data for build")
|
||||||
|
println(errorResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reports.forEach { report ->
|
||||||
|
val summaryReport = SummaryBenchmarksReport(report).getBenchmarksReport()
|
||||||
|
.normalizeBenchmarksSet(goldenResults)
|
||||||
|
summaryReport.buildNumber = currentBuildNumber
|
||||||
|
// Save results in database.
|
||||||
|
benchmarksDispatcher.insert(summaryReport, target).then { _ ->
|
||||||
|
println("Success insert ${buildInfoRecord.buildNumber}")
|
||||||
|
}.catch { errorResponse ->
|
||||||
|
println("Failed to insert data for build")
|
||||||
|
println(errorResponse.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}.catch { errorResponse ->
|
||||||
|
println("Failed to insert data for build")
|
||||||
|
println(errorResponse)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response.sendStatus(200)
|
||||||
|
}.catch {
|
||||||
|
response.sendStatus(400)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get("/delete/:target", { request, response ->
|
router.get("/delete/:target", { request, response ->
|
||||||
val buildsToDelete: List<String> = request.query.builds.toString().split(",").map { it.trim() }
|
val target = urlParameterToBaseFormat(request.params.target)
|
||||||
val result = LocalCache.delete(request.params.target, buildsToDelete, request.query.key)
|
var buildNumber: String? = null
|
||||||
if (result) {
|
if (request.query != undefined) {
|
||||||
response.sendStatus(200)
|
if (request.query.buildNumber != undefined) {
|
||||||
} else {
|
buildNumber = request.query.buildNumber
|
||||||
response.sendStatus(404)
|
}
|
||||||
}
|
}
|
||||||
|
benchmarksDispatcher.deleteBenchmarks(target, buildNumber).then {
|
||||||
|
deleteBuildInfo(target, buildInfoIndex, buildNumber).then {
|
||||||
|
response.sendStatus(200)
|
||||||
|
}.catch {
|
||||||
|
response.sendStatus(400)
|
||||||
|
}
|
||||||
|
}.catch {
|
||||||
|
response.sendStatus(400)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get("/report/:target/:buildNumber", { request, response ->
|
||||||
|
val target = urlParameterToBaseFormat(request.params.target)
|
||||||
|
val buildNumber = request.params.buildNumber.toString()
|
||||||
|
benchmarksDispatcher.getBenchmarksReports(buildNumber, target).then { reports ->
|
||||||
|
response.send(reports.joinToString(", ", "[", "]"))
|
||||||
|
}.catch {
|
||||||
|
response.sendStatus(400)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get("/clear", { _, response ->
|
||||||
|
CachableResponseDispatcher.clear()
|
||||||
|
response.sendStatus(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Main page.
|
// Main page.
|
||||||
@@ -335,52 +556,20 @@ fun router() {
|
|||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAuth(user: String, password: String): String {
|
fun getBuildsInfoFromArtifactory(target: String): Promise<String> {
|
||||||
val buffer = js("Buffer").from(user + ":" + password)
|
val buildsFileName = "buildsSummary.csv"
|
||||||
val based64String = buffer.toString("base64")
|
val artifactoryBuildsDirectory = "builds"
|
||||||
return "Basic " + based64String
|
return UrlNetworkConnector(artifactoryUrl).sendRequest(RequestMethod.GET,
|
||||||
|
"$artifactoryBuildsDirectory/$target/$buildsFileName")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendGetRequest(url: String, user: String? = null, password: String? = null, jsonContentType: Boolean = false) : String {
|
fun BenchmarksReport.normalizeBenchmarksSet(dataForNormalization: Map<String, List<BenchmarkResult>>): BenchmarksReport {
|
||||||
val request = require("sync-request")
|
val resultBenchmarksList = benchmarks.map { benchmarksList ->
|
||||||
val headers = mutableListOf<Pair<String, String>>()
|
benchmarksList.value.map {
|
||||||
if (user != null && password != null) {
|
NormalizedMeanVarianceBenchmark(it.name, it.status, it.score, it.metric,
|
||||||
headers.add("Authorization" to getAuth(user, password))
|
it.runtimeInUs, it.repeat, it.warmup, (it as MeanVarianceBenchmark).variance,
|
||||||
}
|
dataForNormalization[benchmarksList.key]?.get(0)?.score?.let { golden -> it.score / golden } ?: 0.0)
|
||||||
if (jsonContentType) {
|
}
|
||||||
headers.add("Accept" to "application/json")
|
}.flatten()
|
||||||
}
|
return BenchmarksReport(env, resultBenchmarksList, compiler)
|
||||||
val response = request("GET", url,
|
|
||||||
json(
|
|
||||||
"headers" to json(*(headers.toTypedArray()))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
error("Error during getting response from $url\n" +
|
|
||||||
"${response.getBody()}")
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.getBody().toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendUploadRequest(url: String, fileContent: String, user: String? = null, password: String? = null,
|
|
||||||
extraHeaders: List<Pair<String, String>> = emptyList()) {
|
|
||||||
val request = require("sync-request")
|
|
||||||
val headers = mutableListOf<Pair<String, String>>("Content-type" to "text/plain")
|
|
||||||
if (user != null && password != null) {
|
|
||||||
headers.add("Authorization" to getAuth(user, password))
|
|
||||||
}
|
|
||||||
extraHeaders.forEach {
|
|
||||||
headers.add(it.first to it.second)
|
|
||||||
}
|
|
||||||
val response = request("PUT", url,
|
|
||||||
json(
|
|
||||||
"headers" to json(*(headers.toTypedArray())),
|
|
||||||
"body" to fileContent
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (response.statusCode != 201) {
|
|
||||||
error("Error during uploading to $url\n" +
|
|
||||||
"${response}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* 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.utils
|
||||||
|
|
||||||
|
import kotlin.coroutines.*
|
||||||
|
import kotlin.js.Promise
|
||||||
|
|
||||||
|
suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont ->
|
||||||
|
then({ cont.resume(it) }, { cont.resumeWithException(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun launch(context: CoroutineContext = EmptyCoroutineContext, block: suspend () -> Unit) =
|
||||||
|
block.startCoroutine(Continuation(context) { result ->
|
||||||
|
result.onFailure { exception ->
|
||||||
|
throw exception
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.rootBuildDirectory = file('../../..')
|
ext.rootBuildDirectory = file('../../..')
|
||||||
|
|
||||||
@@ -11,6 +13,12 @@ buildscript {
|
|||||||
maven {
|
maven {
|
||||||
url kotlinCompilerRepo
|
url kotlinCompilerRepo
|
||||||
}
|
}
|
||||||
|
maven {
|
||||||
|
url "http://dl.bintray.com/kotlin/kotlin-eap"
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
url "http://dl.bintray.com/kotlin/kotlin-dev"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -28,42 +36,40 @@ repositories {
|
|||||||
maven {
|
maven {
|
||||||
url buildKotlinCompilerRepo
|
url buildKotlinCompilerRepo
|
||||||
}
|
}
|
||||||
|
maven {
|
||||||
|
url "http://dl.bintray.com/kotlin/kotlin-eap"
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
url "http://dl.bintray.com/kotlin/kotlin-dev"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'kotlin2js'
|
apply plugin: 'kotlin-multiplatform'
|
||||||
|
|
||||||
dependencies {
|
kotlin {
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
|
js {
|
||||||
}
|
browser {
|
||||||
|
binaries.executable()
|
||||||
compileKotlin2Js {
|
distribution {
|
||||||
kotlinOptions.outputFile = "${projectDir}/js/main.js"
|
directory = new File("$projectDir/js/")
|
||||||
kotlinOptions.main = "call"
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
main.kotlin.srcDirs += "src"
|
|
||||||
main.kotlin.srcDirs += "../../benchmarks/shared/src"
|
|
||||||
main.kotlin.srcDirs += "../shared/src"
|
|
||||||
main.output.resourcesDir = "build/js/resources"
|
|
||||||
}
|
|
||||||
|
|
||||||
task copyResources(type: Copy) {
|
|
||||||
from sourceSets.main.resources.srcDirs
|
|
||||||
into file(buildDir.path + "/js")
|
|
||||||
}
|
|
||||||
|
|
||||||
build.doLast {
|
|
||||||
configurations.compile.each { File file ->
|
|
||||||
copy {
|
|
||||||
includeEmptyDirs = false
|
|
||||||
|
|
||||||
from zipTree(file.absolutePath)
|
|
||||||
into "${projectDir}/lib/kotlin"
|
|
||||||
include { fileTreeElement ->
|
|
||||||
def path = fileTreeElement.path
|
|
||||||
path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion"
|
||||||
|
}
|
||||||
|
kotlin.srcDir '../../benchmarks/shared/src'
|
||||||
|
}
|
||||||
|
jsMain {
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
|
||||||
|
}
|
||||||
|
kotlin.srcDir 'src/main/kotlin'
|
||||||
|
kotlin.srcDir '../shared/src/main/kotlin'
|
||||||
|
kotlin.srcDir '../src/main/kotlin-js'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -68,11 +68,6 @@
|
|||||||
border-color: darkcyan;
|
border-color: darkcyan;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ct-legend .ct-series-5:before {
|
|
||||||
background-color: #453d3f;
|
|
||||||
border-color: #453d3f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ct-series-b .ct-line,
|
.ct-series-b .ct-line,
|
||||||
.ct-series-b .ct-point {
|
.ct-series-b .ct-point {
|
||||||
/* Set the colour of this series line */
|
/* Set the colour of this series line */
|
||||||
|
|||||||
@@ -33,8 +33,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<select class="custom-select" id="inputGroupTarget">
|
<select class="custom-select" id="inputGroupTarget">
|
||||||
<option value="Linux">Linux</option>
|
<option value="Linux">Linux</option>
|
||||||
<option value="MacOSX">Mac OS X</option>
|
<option value="Mac_OS_X">Mac OS X</option>
|
||||||
<option value="Windows10">Windows 10</option>
|
<option value="Windows_10">Windows 10</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,7 +110,6 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartist-plugin-legend/0.6.2/chartist-plugin-legend.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartist-plugin-legend/0.6.2/chartist-plugin-legend.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.devbridge-autocomplete/1.4.9/jquery.autocomplete.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.devbridge-autocomplete/1.4.9/jquery.autocomplete.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chartist-plugin-axistitle@0.0.4/dist/chartist-plugin-axistitle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chartist-plugin-axistitle@0.0.4/dist/chartist-plugin-axistitle.min.js"></script>
|
||||||
<script src="lib/kotlin/kotlin.js"></script>
|
<script src="js/ui.js"></script>
|
||||||
<script src="js/main.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,23 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2019 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import kotlin.browser.*
|
import kotlin.browser.*
|
||||||
import org.w3c.xhr.*
|
import org.w3c.fetch.*
|
||||||
import org.jetbrains.report.json.*
|
import org.jetbrains.report.json.*
|
||||||
import org.jetbrains.build.Build
|
import org.jetbrains.buildInfo.Build
|
||||||
import kotlin.js.*
|
import kotlin.js.*
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import org.w3c.dom.*
|
import org.w3c.dom.*
|
||||||
@@ -30,63 +19,40 @@ external class ChartistPlugins {
|
|||||||
|
|
||||||
external object Chartist {
|
external object Chartist {
|
||||||
class Svg(form: String, parameters: dynamic, chartArea: String)
|
class Svg(form: String, parameters: dynamic, chartArea: String)
|
||||||
|
|
||||||
val plugins: ChartistPlugins
|
val plugins: ChartistPlugins
|
||||||
val Interpolation: dynamic
|
val Interpolation: dynamic
|
||||||
fun Line(query: String, data: dynamic, options: dynamic): dynamic
|
fun Line(query: String, data: dynamic, options: dynamic): dynamic
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendGetRequest(url: String) : String {
|
data class Commit(val revision: String, val developer: String)
|
||||||
val request = XMLHttpRequest()
|
|
||||||
|
|
||||||
request.open("GET", url, false)
|
fun sendGetRequest(url: String) = window.fetch(url, RequestInit("GET")).then { response ->
|
||||||
request.send()
|
if (!response.ok)
|
||||||
if (request.status == 200.toShort()) {
|
error("Error during getting response from $url\n" +
|
||||||
return request.responseText
|
"${response}")
|
||||||
|
else
|
||||||
|
response.text()
|
||||||
|
}.then { text -> text }
|
||||||
|
|
||||||
|
// Get groups of builds for different zoom values.
|
||||||
|
fun getBuildsGroup(builds: List<Build?>) = buildsNumberToShow?.let {
|
||||||
|
val buildsGroups = builds.chunked(buildsNumberToShow!!)
|
||||||
|
val expectedGroup = buildsGroups.size - 1 + stageToShow
|
||||||
|
val index = when {
|
||||||
|
expectedGroup < 0 -> 0
|
||||||
|
expectedGroup >= buildsGroups.size -> buildsGroups.size - 1
|
||||||
|
else -> expectedGroup
|
||||||
}
|
}
|
||||||
error("Request to $url has status ${request.status}")
|
buildsGroups[index]
|
||||||
}
|
} ?: builds
|
||||||
|
|
||||||
// Parse description with values for metrics.
|
|
||||||
fun <T : Any> separateValues(values: String, valuesContainer: MutableMap<String, MutableList<T?>>, convert: (String) -> T = { it as T }) {
|
|
||||||
val existingSamples = mutableListOf<String>()
|
|
||||||
val splittedValues = values.split(";")
|
|
||||||
val insertedList = valuesContainer.values.firstOrNull()?.let { MutableList<T?>(it.size) { null } } ?: mutableListOf<T?>()
|
|
||||||
splittedValues.forEach { it ->
|
|
||||||
val valueParts = it.split("-", limit = 2)
|
|
||||||
if (valueParts.size != 2) {
|
|
||||||
error("Wrong format of value $it.")
|
|
||||||
}
|
|
||||||
val (sampleName, value) = valueParts
|
|
||||||
existingSamples.add(sampleName)
|
|
||||||
val currentList = mutableListOf<T?>()
|
|
||||||
currentList.addAll(insertedList)
|
|
||||||
valuesContainer.getOrPut(sampleName) { currentList }.add(convert(value))
|
|
||||||
}
|
|
||||||
// Check if there are other keys that are absent in current record.
|
|
||||||
val missedSamples = valuesContainer.keys - existingSamples
|
|
||||||
missedSamples.forEach {
|
|
||||||
valuesContainer[it]!!.add(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getBuildsGroup(builds: List<Build>) = buildsNumberToShow?.let {
|
|
||||||
val buildsGroups = builds.chunked(buildsNumberToShow!!)
|
|
||||||
val expectedGroup = buildsGroups.size - 1 + stageToShow
|
|
||||||
val index = when {
|
|
||||||
expectedGroup < 0 -> 0
|
|
||||||
expectedGroup >= buildsGroups.size -> buildsGroups.size - 1
|
|
||||||
else -> expectedGroup
|
|
||||||
}
|
|
||||||
buildsGroups[index]
|
|
||||||
} ?: builds
|
|
||||||
|
|
||||||
|
|
||||||
|
// Get data for chart in needed format.
|
||||||
fun getChartData(labels: List<String>, valuesList: Collection<List<*>>, stageToShow: Int = 0,
|
fun getChartData(labels: List<String>, valuesList: Collection<List<*>>, stageToShow: Int = 0,
|
||||||
buildsNumber: Int? = null, classNames: Array<String>? = null): dynamic {
|
buildsNumber: Int? = null, classNames: Array<String>? = null): dynamic {
|
||||||
val chartData: dynamic = object{}
|
val chartData: dynamic = object {}
|
||||||
// Show only some part of data.
|
// Show only some part of data.
|
||||||
val (labelsData, valuesData) = buildsNumber?.let {
|
val (labelsData, valuesData) = buildsNumber?.let {
|
||||||
println("Divide on ${labels.size / buildsNumber}")
|
|
||||||
val labelsGroups = labels.chunked(buildsNumber)
|
val labelsGroups = labels.chunked(buildsNumber)
|
||||||
val valuesListGroups = valuesList.map { it.chunked(buildsNumber) }
|
val valuesListGroups = valuesList.map { it.chunked(buildsNumber) }
|
||||||
val expectedGroup = labelsGroups.size - 1 + stageToShow
|
val expectedGroup = labelsGroups.size - 1 + stageToShow
|
||||||
@@ -95,11 +61,11 @@ fun getChartData(labels: List<String>, valuesList: Collection<List<*>>, stageToS
|
|||||||
expectedGroup >= labelsGroups.size -> labelsGroups.size - 1
|
expectedGroup >= labelsGroups.size -> labelsGroups.size - 1
|
||||||
else -> expectedGroup
|
else -> expectedGroup
|
||||||
}
|
}
|
||||||
Pair(labelsGroups[index], valuesListGroups.map {it[index]})
|
Pair(labelsGroups[index], valuesListGroups.map { it[index] })
|
||||||
} ?: Pair(labels, valuesList)
|
} ?: Pair(labels, valuesList)
|
||||||
chartData["labels"] = labelsData.toTypedArray()
|
chartData["labels"] = labelsData.toTypedArray()
|
||||||
chartData["series"] = valuesData.mapIndexed { index, it ->
|
chartData["series"] = valuesData.mapIndexed { index, it ->
|
||||||
val series: dynamic = object{}
|
val series: dynamic = object {}
|
||||||
series["data"] = it.toTypedArray()
|
series["data"] = it.toTypedArray()
|
||||||
classNames?.let { series["className"] = classNames[index] }
|
classNames?.let { series["className"] = classNames[index] }
|
||||||
series
|
series
|
||||||
@@ -107,32 +73,33 @@ fun getChartData(labels: List<String>, valuesList: Collection<List<*>>, stageToS
|
|||||||
return chartData
|
return chartData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create object with options of chart.
|
||||||
fun getChartOptions(samples: Array<String>, yTitle: String, classNames: Array<String>? = null): dynamic {
|
fun getChartOptions(samples: Array<String>, yTitle: String, classNames: Array<String>? = null): dynamic {
|
||||||
val chartOptions: dynamic = object{}
|
val chartOptions: dynamic = object {}
|
||||||
chartOptions["fullWidth"] = true
|
chartOptions["fullWidth"] = true
|
||||||
val paddingObject: dynamic = object{}
|
val paddingObject: dynamic = object {}
|
||||||
paddingObject["right"] = 40
|
paddingObject["right"] = 40
|
||||||
chartOptions["chartPadding"] = paddingObject
|
chartOptions["chartPadding"] = paddingObject
|
||||||
val axisXObject: dynamic = object{}
|
val axisXObject: dynamic = object {}
|
||||||
axisXObject["offset"] = 40
|
axisXObject["offset"] = 40
|
||||||
axisXObject["labelInterpolationFnc"] = { value, index, labels ->
|
axisXObject["labelInterpolationFnc"] = { value, index, labels ->
|
||||||
val labelsCount = 30
|
val labelsCount = 30
|
||||||
val skipNumber = ceil((labels.length as Int).toDouble() / labelsCount).toInt()
|
val skipNumber = ceil((labels.length as Int).toDouble() / labelsCount).toInt()
|
||||||
if (skipNumber > 1) {
|
if (skipNumber > 1) {
|
||||||
if (index % skipNumber == 0) value else null
|
if (index % skipNumber == 0) value else null
|
||||||
} else {
|
} else {
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chartOptions["axisX"] = axisXObject
|
chartOptions["axisX"] = axisXObject
|
||||||
val axisYObject: dynamic = object{}
|
val axisYObject: dynamic = object {}
|
||||||
axisYObject["offset"] = 90
|
axisYObject["offset"] = 90
|
||||||
chartOptions["axisY"] = axisYObject
|
chartOptions["axisY"] = axisYObject
|
||||||
val legendObject: dynamic = object{}
|
val legendObject: dynamic = object {}
|
||||||
legendObject["legendNames"] = samples
|
legendObject["legendNames"] = samples
|
||||||
classNames?.let { legendObject["classNames"] = classNames }
|
classNames?.let { legendObject["classNames"] = classNames.sliceArray(0 until samples.size) }
|
||||||
val titleObject: dynamic = object{}
|
val titleObject: dynamic = object {}
|
||||||
val axisYTitle: dynamic = object{}
|
val axisYTitle: dynamic = object {}
|
||||||
axisYTitle["axisTitle"] = yTitle
|
axisYTitle["axisTitle"] = yTitle
|
||||||
axisYTitle["axisClass"] = "ct-axis-title"
|
axisYTitle["axisClass"] = "ct-axis-title"
|
||||||
val titleOffset: dynamic = {}
|
val titleOffset: dynamic = {}
|
||||||
@@ -153,7 +120,8 @@ fun redirect(url: String) {
|
|||||||
window.location.href = url
|
window.location.href = url
|
||||||
}
|
}
|
||||||
|
|
||||||
fun customizeChart(chart: dynamic, chartContainer: String, jquerySelector: dynamic, builds: List<Build>,
|
// Set customizations rules for chart.
|
||||||
|
fun customizeChart(chart: dynamic, chartContainer: String, jquerySelector: dynamic, builds: List<Build?>,
|
||||||
parameters: Map<String, String>) {
|
parameters: Map<String, String>) {
|
||||||
chart.on("draw", { data ->
|
chart.on("draw", { data ->
|
||||||
var element = data.element
|
var element = data.element
|
||||||
@@ -161,59 +129,74 @@ fun customizeChart(chart: dynamic, chartContainer: String, jquerySelector: dynam
|
|||||||
val buildsGroup = getBuildsGroup(builds)
|
val buildsGroup = getBuildsGroup(builds)
|
||||||
val pointSize = 12
|
val pointSize = 12
|
||||||
val currentBuild = buildsGroup.get(data.index)
|
val currentBuild = buildsGroup.get(data.index)
|
||||||
// Higlight builds with failures.
|
currentBuild?.let { currentBuild ->
|
||||||
if (currentBuild.failuresNumber > 0) {
|
// Higlight builds with failures.
|
||||||
val svgParameters: dynamic = object{}
|
|
||||||
svgParameters["d"] = arrayOf("M", data.x, data.y - pointSize,
|
|
||||||
"L", data.x - pointSize, data.y + pointSize/2,
|
|
||||||
"L", data.x + pointSize, data.y + pointSize/2, "z").joinToString(" ")
|
|
||||||
svgParameters["style"] = "fill:rgb(255,0,0);stroke-width:0"
|
|
||||||
val triangle = Chartist.Svg("path", svgParameters, chartContainer)
|
|
||||||
element = data.element.replace(triangle)
|
|
||||||
} else if (currentBuild.buildNumber == parameters["build"]) {
|
|
||||||
// Higlight choosen build.
|
|
||||||
val svgParameters: dynamic = object{}
|
|
||||||
svgParameters["x"] = data.x - pointSize/2
|
|
||||||
svgParameters["y"] = data.y - pointSize/2
|
|
||||||
svgParameters["height"] = pointSize
|
|
||||||
svgParameters["width"] = pointSize
|
|
||||||
svgParameters["style"] = "fill:rgb(0,0,255);stroke-width:0"
|
|
||||||
val rectangle = Chartist.Svg("rect", svgParameters, "ct-point")
|
|
||||||
element = data.element.replace(rectangle)
|
|
||||||
}
|
|
||||||
// Add tooltips.
|
|
||||||
val linkToDetailedInfo = "https://kotlin-native-performance.labs.jb.gg/?report=artifactory:" +
|
|
||||||
"${currentBuild.buildNumber}:${parameters["target"]}:nativeReport.json" +
|
|
||||||
"${if (data.index - 1 >= 0)
|
|
||||||
"&compareTo=artifactory:${buildsGroup.get(data.index - 1).buildNumber}:${parameters["target"]}:nativeReport.json"
|
|
||||||
else ""}"
|
|
||||||
val information = buildString {
|
|
||||||
append("<a href=\"$linkToDetailedInfo\">${currentBuild.buildNumber}</a><br>")
|
|
||||||
append("Value: ${data.value.y.toFixed(4)}<br>")
|
|
||||||
if (currentBuild.failuresNumber > 0) {
|
if (currentBuild.failuresNumber > 0) {
|
||||||
append("failures: ${currentBuild.failuresNumber}<br>")
|
val svgParameters: dynamic = object {}
|
||||||
|
svgParameters["d"] = arrayOf("M", data.x, data.y - pointSize,
|
||||||
|
"L", data.x - pointSize, data.y + pointSize / 2,
|
||||||
|
"L", data.x + pointSize, data.y + pointSize / 2, "z").joinToString(" ")
|
||||||
|
svgParameters["style"] = "fill:rgb(255,0,0);stroke-width:0"
|
||||||
|
val triangle = Chartist.Svg("path", svgParameters, chartContainer)
|
||||||
|
element = data.element.replace(triangle)
|
||||||
|
} else if (currentBuild.buildNumber == parameters["build"]) {
|
||||||
|
// Higlight choosen build.
|
||||||
|
val svgParameters: dynamic = object {}
|
||||||
|
svgParameters["x"] = data.x - pointSize / 2
|
||||||
|
svgParameters["y"] = data.y - pointSize / 2
|
||||||
|
svgParameters["height"] = pointSize
|
||||||
|
svgParameters["width"] = pointSize
|
||||||
|
svgParameters["style"] = "fill:rgb(0,0,255);stroke-width:0"
|
||||||
|
val rectangle = Chartist.Svg("rect", svgParameters, "ct-point")
|
||||||
|
element = data.element.replace(rectangle)
|
||||||
}
|
}
|
||||||
append("branch: ${currentBuild.branch}<br>")
|
// Add tooltips.
|
||||||
append("date: ${currentBuild.date}<br>")
|
var shift = 1
|
||||||
append("time: ${currentBuild.formattedStartTime}-${currentBuild.formattedFinishTime}<br>")
|
var previousBuild: Build? = null
|
||||||
append("Commits:<br>")
|
while (previousBuild == null && data.index - shift >= 0) {
|
||||||
val commitsList = currentBuild.commits.split(";")
|
previousBuild = buildsGroup.get(data.index - shift)
|
||||||
commitsList.forEach {
|
shift++
|
||||||
if (!it.isEmpty())
|
|
||||||
append("${it.substringBefore("by").substring(0, 7)} by ${it.substringAfter("by ")}<br>")
|
|
||||||
}
|
}
|
||||||
|
val linkToDetailedInfo = "https://kotlin-native-performance.labs.jb.gg/?report=" +
|
||||||
|
"${currentBuild.buildNumber}:${parameters["target"]}" +
|
||||||
|
"${previousBuild?.let {
|
||||||
|
"&compareTo=${previousBuild.buildNumber}:${parameters["target"]}"
|
||||||
|
} ?: ""}"
|
||||||
|
val information = buildString {
|
||||||
|
append("<a href=\"$linkToDetailedInfo\">${currentBuild.buildNumber}</a><br>")
|
||||||
|
append("Value: ${data.value.y.toFixed(4)}<br>")
|
||||||
|
if (currentBuild.failuresNumber > 0) {
|
||||||
|
append("failures: ${currentBuild.failuresNumber}<br>")
|
||||||
|
}
|
||||||
|
append("branch: ${currentBuild.branch}<br>")
|
||||||
|
append("date: ${currentBuild.date}<br>")
|
||||||
|
append("time: ${currentBuild.formattedStartTime}-${currentBuild.formattedFinishTime}<br>")
|
||||||
|
append("Commits:<br>")
|
||||||
|
val commitsList = (JsonTreeParser.parse("{${currentBuild.commits}}") as JsonObject).getArray("commits").map {
|
||||||
|
Commit(
|
||||||
|
(it as JsonObject).getPrimitive("revision").content,
|
||||||
|
(it as JsonObject).getPrimitive("developer").content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val commits = if (commitsList.size > 3) commitsList.slice(0..2) else commitsList
|
||||||
|
commits.forEach {
|
||||||
|
append("${it.revision.substring(0, 7)} by ${it.developer}<br>")
|
||||||
|
}
|
||||||
|
if (commitsList.size > 3) {
|
||||||
|
append("...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element._node.setAttribute("title", information)
|
||||||
|
element._node.setAttribute("data-chart-tooltip", chartContainer)
|
||||||
|
element._node.addEventListener("click", {
|
||||||
|
redirect(linkToDetailedInfo)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
element._node.setAttribute("title", information)
|
|
||||||
element._node.setAttribute("data-chart-tooltip", chartContainer)
|
|
||||||
element._node.addEventListener("click", {
|
|
||||||
redirect(linkToDetailedInfo)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
chart.on("created", {
|
chart.on("created", {
|
||||||
val currentChart = jquerySelector
|
val currentChart = jquerySelector
|
||||||
val parameters: dynamic = object{}
|
val parameters: dynamic = object {}
|
||||||
parameters["selector"] = "[data-chart-tooltip=\"$chartContainer\"]"
|
parameters["selector"] = "[data-chart-tooltip=\"$chartContainer\"]"
|
||||||
parameters["container"] = "#$chartContainer"
|
parameters["container"] = "#$chartContainer"
|
||||||
parameters["html"] = true
|
parameters["html"] = true
|
||||||
@@ -225,6 +208,12 @@ var stageToShow = 0
|
|||||||
|
|
||||||
var buildsNumberToShow: Int? = null
|
var buildsNumberToShow: Int? = null
|
||||||
|
|
||||||
|
fun waitForElements(elements: Iterable<Any?>, action: () -> Unit) {
|
||||||
|
if (elements.filterNotNull().size == elements.count())
|
||||||
|
action()
|
||||||
|
else window.setTimeout(waitForElements(elements, action), 500)
|
||||||
|
}
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
val serverUrl = "https://kotlin-native-perf-summary.labs.jb.gg"
|
val serverUrl = "https://kotlin-native-perf-summary.labs.jb.gg"
|
||||||
buildsNumberToShow = null
|
buildsNumberToShow = null
|
||||||
@@ -243,45 +232,52 @@ fun main(args: Array<String>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get builds.
|
// Get branches.
|
||||||
val buildsUrl = buildString {
|
val branchesUrl = "$serverUrl/branches"
|
||||||
append("$serverUrl/builds")
|
sendGetRequest(branchesUrl).then { response ->
|
||||||
append("/${parameters["target"]}")
|
val branches: Array<String> = JSON.parse(response)
|
||||||
append("/${parameters["type"]}")
|
// Add release branches to selector.
|
||||||
append("/${parameters["branch"]}")
|
branches.filter { it != "master" }.forEach {
|
||||||
append("/${parameters["build"]}")
|
if ("v(\\d|\\.)+(-M\\d)?-fixes".toRegex().matches(it)) {
|
||||||
|
val option = Option(it, it)
|
||||||
|
js("$('#inputGroupBranch')").append(js("$(option)"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.querySelector("#inputGroupBranch [value=\"${parameters["branch"]}\"]")?.setAttribute("selected", "true")
|
||||||
}
|
}
|
||||||
val response = sendGetRequest(buildsUrl)
|
|
||||||
|
|
||||||
val data = JsonTreeParser.parse(response)
|
|
||||||
if (data !is JsonArray) {
|
|
||||||
error("Response is expected to be an array.")
|
|
||||||
}
|
|
||||||
val builds = data.jsonArray.map { Build.create(it as JsonObject) }
|
|
||||||
.sortedWith(compareBy ( { it.buildNumber.substringBefore(".").toInt() }, { it.buildNumber.substringAfter(".").substringBefore("-").toDouble() }, { it.buildNumber.substringAfterLast("-").toInt() }))
|
|
||||||
|
|
||||||
val branchesUrl = "$serverUrl/branches/${parameters["target"]}"
|
// Fill autocomplete list with build numbers.
|
||||||
|
|
||||||
val branches: Array<String> = JSON.parse(sendGetRequest(branchesUrl))
|
|
||||||
val releaseBranches = branches.filter { "^v\\d+(\\.\\d+)+(-M\\d)?-fixes$".toRegex().find(it) != null }
|
|
||||||
|
|
||||||
// Fill autocomplete list.
|
|
||||||
val buildsNumbersUrl = "$serverUrl/buildsNumbers/${parameters["target"]}"
|
val buildsNumbersUrl = "$serverUrl/buildsNumbers/${parameters["target"]}"
|
||||||
val buildsNumbers: Array<String> = JSON.parse(sendGetRequest(buildsNumbersUrl))
|
sendGetRequest(buildsNumbersUrl).then { response ->
|
||||||
|
val buildsNumbers: Array<String> = JSON.parse(response)
|
||||||
// Add release branches to selector.
|
val autocompleteParameters: dynamic = object {}
|
||||||
releaseBranches.forEach {
|
autocompleteParameters["lookup"] = buildsNumbers
|
||||||
val option = Option(it, it)
|
autocompleteParameters["onSelect"] = { suggestion ->
|
||||||
js("$('#inputGroupBranch')").append(js("$(option)"))
|
if (suggestion.value != parameters["build"]) {
|
||||||
|
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}" +
|
||||||
|
"${if ((suggestion.value as String).isEmpty()) "" else "&build=${suggestion.value}"}"
|
||||||
|
window.location.href = newLink
|
||||||
|
}
|
||||||
|
}
|
||||||
|
js("$( \"#highligted_build\" )").autocomplete(autocompleteParameters)
|
||||||
|
js("$('#highligted_build')").change({ value ->
|
||||||
|
val newValue = js("$(this).val()").toString()
|
||||||
|
if (newValue.isEmpty() || newValue in buildsNumbers) {
|
||||||
|
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}" +
|
||||||
|
"${if (newValue.isEmpty()) "" else "&build=$newValue"}"
|
||||||
|
window.location.href = newLink
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change inputs values connected with parameters and add events listeners.
|
// Change inputs values connected with parameters and add events listeners.
|
||||||
document.querySelector("#inputGroupTarget [value=\"${parameters["target"]}\"]")?.setAttribute("selected", "true")
|
document.querySelector("#inputGroupTarget [value=\"${parameters["target"]}\"]")?.setAttribute("selected", "true")
|
||||||
document.querySelector("#inputGroupBuildType [value=\"${parameters["type"]}\"]")?.setAttribute("selected", "true")
|
document.querySelector("#inputGroupBuildType [value=\"${parameters["type"]}\"]")?.setAttribute("selected", "true")
|
||||||
document.querySelector("#inputGroupBranch [value=\"${parameters["branch"]}\"]")?.setAttribute("selected", "true")
|
|
||||||
(document.getElementById("highligted_build") as HTMLInputElement).value = parameters["build"]!!
|
(document.getElementById("highligted_build") as HTMLInputElement).value = parameters["build"]!!
|
||||||
|
|
||||||
// Add onChange events for fields.
|
// Add onChange events for fields.
|
||||||
|
// Don't use AJAX to have opportunity to share results with simple links.
|
||||||
js("$('#inputGroupTarget')").change({
|
js("$('#inputGroupTarget')").change({
|
||||||
val newValue = js("$(this).val()")
|
val newValue = js("$(this).val()")
|
||||||
if (newValue != parameters["target"]) {
|
if (newValue != parameters["target"]) {
|
||||||
@@ -307,69 +303,160 @@ fun main(args: Array<String>) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
val autocompleteParameters: dynamic = object{}
|
val platformSpecificBenchs = if (parameters["target"] == "Mac_OS_X") ",FrameworkBenchmarksAnalyzer,circlet_iosX64" else
|
||||||
autocompleteParameters["lookup"] = buildsNumbers
|
if (parameters["target"] == "Linux") ",kotlinx.coroutines" else ""
|
||||||
autocompleteParameters["onSelect"] = { suggestion ->
|
|
||||||
if (suggestion.value != parameters["build"]) {
|
|
||||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}" +
|
|
||||||
"${if ((suggestion.value as String).isEmpty()) "" else "&build=${suggestion.value}"}"
|
|
||||||
window.location.href = newLink
|
|
||||||
}
|
|
||||||
}
|
|
||||||
js("$( \"#highligted_build\" )").autocomplete(autocompleteParameters)
|
|
||||||
js("$('#highligted_build')").change({ value ->
|
|
||||||
val newValue = js("$(this).val()").toString()
|
|
||||||
if (newValue.isEmpty() || newValue in builds.map {it.buildNumber}) {
|
|
||||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}" +
|
|
||||||
"${if (newValue.isEmpty()) "" else "&build=$newValue"}"
|
|
||||||
window.location.href = newLink
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// Collect information for charts library.
|
// Collect information for charts library.
|
||||||
val labels = mutableListOf<String>()
|
val valuesToShow = mapOf("EXECUTION_TIME" to arrayOf(mapOf(
|
||||||
val executionTime = mutableMapOf<String, MutableList<Double?>>()
|
"normalize" to "true"
|
||||||
val compileTime = mutableMapOf<String, MutableList<Double?>>()
|
)),
|
||||||
val codeSize = mutableMapOf<String, MutableList<Double?>>()
|
"COMPILE_TIME" to arrayOf(mapOf(
|
||||||
val bundleSize = mutableListOf<Int?>()
|
"samples" to "HelloWorld,Videoplayer$platformSpecificBenchs",
|
||||||
|
"agr" to "samples"
|
||||||
|
)),
|
||||||
|
"CODE_SIZE" to arrayOf(mapOf(
|
||||||
|
"normalize" to "true",
|
||||||
|
"exclude" to if (parameters["target"] == "Linux")
|
||||||
|
"kotlinx.coroutines"
|
||||||
|
else if (parameters["target"] == "Mac_OS_X")
|
||||||
|
"circlet_iosX64"
|
||||||
|
else ""
|
||||||
|
), mapOf(
|
||||||
|
"normalize" to "true",
|
||||||
|
"agr" to "samples",
|
||||||
|
"samples" to platformSpecificBenchs.removePrefix(",")
|
||||||
|
)),
|
||||||
|
"BUNDLE_SIZE" to arrayOf(mapOf("samples" to "KotlinNative",
|
||||||
|
"agr" to "samples"))
|
||||||
|
)
|
||||||
|
|
||||||
builds.forEach {
|
var execData = listOf<String>() to listOf<List<Double?>>()
|
||||||
// Choose labels on x axis.
|
var compileData = listOf<String>() to listOf<List<Double?>>()
|
||||||
if (parameters["type"] == "day") {
|
var codeSizeData = listOf<String>() to listOf<List<Double?>>()
|
||||||
labels.add(it.date)
|
var bundleSizeData = listOf<String>() to listOf<List<Int?>>()
|
||||||
} else {
|
|
||||||
labels.add(it.buildNumber)
|
|
||||||
}
|
|
||||||
separateValues(it.executionTime, executionTime) { value -> value.toDouble() }
|
|
||||||
separateValues(it.compileTime, compileTime) { value -> value.toDouble() / 1000 }
|
|
||||||
separateValues(it.codeSize, codeSize) { value -> value.toDouble() }
|
|
||||||
bundleSize.add(it.bundleSize?.toInt()?. let { it / 1024 / 1024 })
|
|
||||||
}
|
|
||||||
|
|
||||||
val sizeClassNames = arrayOf("ct-series-e", "ct-series-f", "ct-series-g")
|
val sizeClassNames = arrayOf("ct-series-e", "ct-series-f", "ct-series-g")
|
||||||
|
|
||||||
// Draw charts.
|
// Draw charts.
|
||||||
val execChart = Chartist.Line("#exec_chart", getChartData(labels, executionTime.values, stageToShow, buildsNumberToShow),
|
var execChart: dynamic = null
|
||||||
getChartOptions(executionTime.keys.toTypedArray(), "Normalized time"))
|
var compileChart: dynamic = null
|
||||||
val compileChart = Chartist.Line("#compile_chart", getChartData(labels, compileTime.values, stageToShow, buildsNumberToShow),
|
var codeSizeChart: dynamic = null
|
||||||
getChartOptions(compileTime.keys.toTypedArray(), "Time, milliseconds"))
|
var bundleSizeChart: dynamic = null
|
||||||
val codeSizeChart = Chartist.Line("#codesize_chart", getChartData(labels, codeSize.values, stageToShow, buildsNumberToShow, sizeClassNames),
|
|
||||||
getChartOptions(codeSize.keys.toTypedArray(), "Normalized size", arrayOf("ct-series-4", "ct-series-5", "ct-series-6")))
|
|
||||||
val bundleSizeChart = Chartist.Line("#bundlesize_chart", getChartData(labels, listOf(bundleSize), stageToShow, buildsNumberToShow, sizeClassNames),
|
|
||||||
getChartOptions(arrayOf("Bundle size"), "Size, MB", arrayOf("ct-series-4")))
|
|
||||||
|
|
||||||
// Tooltips and higlights.
|
val descriptionUrl = "$serverUrl/buildsDesc/${parameters["target"]}?type=${parameters["type"]}" +
|
||||||
customizeChart(execChart, "exec_chart", js("$(\"#exec_chart\")"), builds, parameters)
|
"${if (parameters["branch"] != "all") "&branch=${parameters["branch"]}" else ""}"
|
||||||
customizeChart(compileChart, "compile_chart", js("$(\"#compile_chart\")"), builds, parameters)
|
|
||||||
customizeChart(codeSizeChart, "codesize_chart", js("$(\"#codesize_chart\")"), builds, parameters)
|
|
||||||
customizeChart(bundleSizeChart, "bundlesize_chart", js("$(\"#bundlesize_chart\")"), builds, parameters)
|
|
||||||
|
|
||||||
|
val metricUrl = "$serverUrl/metricValue/${parameters["target"]}/"
|
||||||
|
|
||||||
|
// Send requests to get all needed metric values.
|
||||||
|
valuesToShow.map { (metric, arrayOfSettings) ->
|
||||||
|
val resultValues = arrayOfSettings.map { settings ->
|
||||||
|
val getParameters = with(StringBuilder()) {
|
||||||
|
if (settings.isNotEmpty()) {
|
||||||
|
append("?")
|
||||||
|
}
|
||||||
|
var prefix = ""
|
||||||
|
settings.forEach { (key, value) ->
|
||||||
|
if (value.isNotEmpty()) {
|
||||||
|
append("$prefix$key=$value")
|
||||||
|
prefix = "&"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toString()
|
||||||
|
}
|
||||||
|
val branchParameter = if (parameters["branch"] != "all")
|
||||||
|
(if (getParameters.isEmpty()) "?" else "&") + "branch=${parameters["branch"]}"
|
||||||
|
else ""
|
||||||
|
|
||||||
|
val url = "$metricUrl$metric$getParameters$branchParameter${
|
||||||
|
if (parameters["type"] != "all")
|
||||||
|
(if (getParameters.isEmpty() && branchParameter.isEmpty()) "?" else "&") + "type=${parameters["type"]}"
|
||||||
|
else ""
|
||||||
|
}"
|
||||||
|
sendGetRequest(url)
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
// Get metrics values for charts.
|
||||||
|
Promise.all(resultValues).then { responses ->
|
||||||
|
val valuesList = responses.map { response ->
|
||||||
|
val results = (JsonTreeParser.parse(response) as JsonArray).map {
|
||||||
|
(it as JsonObject).getPrimitive("first").content to
|
||||||
|
it.getArray("second").map { (it as JsonPrimitive).doubleOrNull }
|
||||||
|
}
|
||||||
|
|
||||||
|
val labels = results.map { it.first }
|
||||||
|
val values = results[0]?.second?.size?.let { (0..it - 1).map { i -> results.map { it.second[i] } } }
|
||||||
|
?: emptyList()
|
||||||
|
labels to values
|
||||||
|
}
|
||||||
|
val labels = valuesList[0].first
|
||||||
|
val values = valuesList.map { it.second }.reduce { acc, valuesPart -> acc + valuesPart }
|
||||||
|
|
||||||
|
when (metric) {
|
||||||
|
// Update chart with gotten data.
|
||||||
|
"COMPILE_TIME" -> {
|
||||||
|
compileData = labels to values.map { it.map { it?.let { it / 1000 } } }
|
||||||
|
compileChart = Chartist.Line("#compile_chart",
|
||||||
|
getChartData(labels, compileData.second, stageToShow, buildsNumberToShow),
|
||||||
|
getChartOptions(valuesToShow["COMPILE_TIME"]!![0]!!["samples"]!!.split(',').toTypedArray(),
|
||||||
|
"Time, milliseconds"))
|
||||||
|
}
|
||||||
|
"EXECUTION_TIME" -> {
|
||||||
|
execData = labels to values
|
||||||
|
execChart = Chartist.Line("#exec_chart",
|
||||||
|
getChartData(labels, execData.second, stageToShow, buildsNumberToShow),
|
||||||
|
getChartOptions(arrayOf("Geometric Mean"), "Normalized time"))
|
||||||
|
}
|
||||||
|
"CODE_SIZE" -> {
|
||||||
|
codeSizeData = labels to values
|
||||||
|
codeSizeChart = Chartist.Line("#codesize_chart",
|
||||||
|
getChartData(labels, codeSizeData.second,
|
||||||
|
stageToShow, buildsNumberToShow, sizeClassNames),
|
||||||
|
getChartOptions(arrayOf("Geometric Mean") + platformSpecificBenchs.split(',')
|
||||||
|
.filter { it.isNotEmpty() },
|
||||||
|
"Normalized size",
|
||||||
|
arrayOf("ct-series-4", "ct-series-5", "ct-series-6")))
|
||||||
|
}
|
||||||
|
"BUNDLE_SIZE" -> {
|
||||||
|
bundleSizeData = labels to values.map { it.map { it?.let { it.toInt() / 1024 / 1024 } } }
|
||||||
|
bundleSizeChart = Chartist.Line("#bundlesize_chart",
|
||||||
|
getChartData(labels,
|
||||||
|
bundleSizeData.second, stageToShow,
|
||||||
|
buildsNumberToShow, sizeClassNames),
|
||||||
|
getChartOptions(arrayOf("Bundle size"), "Size, MB", arrayOf("ct-series-4")))
|
||||||
|
}
|
||||||
|
else -> error("No chart for metric $metric")
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all charts with using same data.
|
||||||
val updateAllCharts: () -> Unit = {
|
val updateAllCharts: () -> Unit = {
|
||||||
execChart.update(getChartData(labels, executionTime.values, stageToShow, buildsNumberToShow))
|
execChart.update(getChartData(execData.first, execData.second, stageToShow, buildsNumberToShow))
|
||||||
compileChart.update(getChartData(labels, compileTime.values, stageToShow, buildsNumberToShow))
|
compileChart.update(getChartData(compileData.first, compileData.second, stageToShow, buildsNumberToShow))
|
||||||
codeSizeChart.update(getChartData(labels, codeSize.values, stageToShow, buildsNumberToShow, sizeClassNames))
|
codeSizeChart.update(getChartData(codeSizeData.first, codeSizeData.second, stageToShow, buildsNumberToShow, sizeClassNames))
|
||||||
bundleSizeChart.update(getChartData(labels, listOf(bundleSize), stageToShow, buildsNumberToShow, sizeClassNames))
|
bundleSizeChart.update(getChartData(bundleSizeData.first, bundleSizeData.second, stageToShow, buildsNumberToShow, sizeClassNames))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get builds description.
|
||||||
|
sendGetRequest(descriptionUrl).then { response ->
|
||||||
|
val buildsInfo = response as String
|
||||||
|
val data = JsonTreeParser.parse(buildsInfo)
|
||||||
|
if (data !is JsonArray) {
|
||||||
|
error("Response is expected to be an array.")
|
||||||
|
}
|
||||||
|
val builds = data.jsonArray.map {
|
||||||
|
val element = it as JsonElement
|
||||||
|
if (element.isNull) null else Build.create(element as JsonObject)
|
||||||
|
}
|
||||||
|
waitForElements(listOf(execChart, compileChart, codeSizeChart, bundleSizeChart)) {
|
||||||
|
customizeChart(execChart, "exec_chart", js("$(\"#exec_chart\")"), builds, parameters)
|
||||||
|
customizeChart(compileChart, "compile_chart", js("$(\"#compile_chart\")"), builds, parameters)
|
||||||
|
customizeChart(codeSizeChart, "codesize_chart", js("$(\"#codesize_chart\")"), builds, parameters)
|
||||||
|
customizeChart(bundleSizeChart, "bundlesize_chart", js("$(\"#bundlesize_chart\")"), builds, parameters)
|
||||||
|
updateAllCharts()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
js("$('#plusBtn')").click({
|
js("$('#plusBtn')").click({
|
||||||
@@ -379,14 +466,13 @@ fun main(args: Array<String>) {
|
|||||||
} else {
|
} else {
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
} ?: labels.size / zoomRatio
|
} ?: execData.first.size / zoomRatio
|
||||||
println(buildsNumberToShow)
|
|
||||||
updateAllCharts()
|
updateAllCharts()
|
||||||
})
|
})
|
||||||
|
|
||||||
js("$('#minusBtn')").click({
|
js("$('#minusBtn')").click({
|
||||||
buildsNumberToShow = buildsNumberToShow?.let {
|
buildsNumberToShow = buildsNumberToShow?.let {
|
||||||
if (it * zoomRatio <= labels.size) {
|
if (it * zoomRatio <= execData.first.size) {
|
||||||
it * zoomRatio
|
it * zoomRatio
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
@@ -397,11 +483,11 @@ fun main(args: Array<String>) {
|
|||||||
|
|
||||||
js("$('#prevBtn')").click({
|
js("$('#prevBtn')").click({
|
||||||
buildsNumberToShow?.let {
|
buildsNumberToShow?.let {
|
||||||
val bottomBorder = -labels.size / (buildsNumberToShow as Int)
|
val bottomBorder = -execData.first.size / (buildsNumberToShow as Int)
|
||||||
if (stageToShow - 1 > bottomBorder) {
|
if (stageToShow - 1 > bottomBorder) {
|
||||||
stageToShow--
|
stageToShow--
|
||||||
}
|
}
|
||||||
} ?: run { stageToShow = 0}
|
} ?: run { stageToShow = 0 }
|
||||||
updateAllCharts()
|
updateAllCharts()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user