Server and client to show summary performance information (#2765)
This commit is contained in:
@@ -59,3 +59,6 @@ samples/androidNativeActivity/Polyhedron
|
||||
|
||||
# CMake
|
||||
llvmCoverageMappingC/CMakeLists.txt
|
||||
tools/performance-server/node_modules
|
||||
tools/performance-server/server
|
||||
tools/performance-server/ui/js
|
||||
|
||||
@@ -86,9 +86,9 @@ fun getFileSize(filePath: String): Long? {
|
||||
|
||||
fun getCodeSizeBenchmark(programName: String, filePath: String): BenchmarkResult {
|
||||
val codeSize = getFileSize(filePath)
|
||||
return BenchmarkResult("$programName.codeSize",
|
||||
return BenchmarkResult("$programName",
|
||||
codeSize?. let { BenchmarkResult.Status.PASSED } ?: run { BenchmarkResult.Status.FAILED },
|
||||
codeSize?.toDouble() ?: 0.0, codeSize?.toDouble() ?: 0.0, 1, 0)
|
||||
codeSize?.toDouble() ?: 0.0, BenchmarkResult.Metric.CODE_SIZE, codeSize?.toDouble() ?: 0.0, 1, 0)
|
||||
}
|
||||
|
||||
// Create benchmarks json report based on information get from gradle project
|
||||
@@ -184,7 +184,7 @@ fun getCompileBenchmarkTime(programName: String, tasksNames: Iterable<String>, r
|
||||
status = if (exitCodes["$it$number"] != 0) BenchmarkResult.Status.FAILED else status
|
||||
}
|
||||
|
||||
BenchmarkResult("$programName.compileTime", status, time, time, number, 0)
|
||||
BenchmarkResult("$programName", status, time, BenchmarkResult.Metric.COMPILE_TIME, time, number, 0)
|
||||
}.toList()
|
||||
|
||||
|
||||
@@ -197,9 +197,9 @@ class TaskTimerListener: TaskExecutionListener {
|
||||
val time = tasksNames.map { tasksTimes[it] ?: 0.0 }.sum()
|
||||
// TODO get this info from gradle plugin with exit code end stacktrace.
|
||||
val status = tasksNames.map { tasksTimes.containsKey(it) }.reduce { a, b -> a && b }
|
||||
return BenchmarkResult("$programName.compileTime",
|
||||
return BenchmarkResult("$programName",
|
||||
if (status) BenchmarkResult.Status.PASSED else BenchmarkResult.Status.FAILED,
|
||||
time, time, 1, 0)
|
||||
time, BenchmarkResult.Metric.COMPILE_TIME, time, 1, 0)
|
||||
}
|
||||
|
||||
fun getTime(taskName: String) = tasksTimes[taskName] ?: 0.0
|
||||
|
||||
@@ -222,11 +222,14 @@ open class RegressionsReporter : DefaultTask() {
|
||||
arrayOf("$analyzer", "-r", "statistics", "$currentBenchmarksReportFile", "bintray:$compareToBuildNumber:$target:$bintrayFileName", "-o", "$summaryFile")
|
||||
.runCommand()
|
||||
|
||||
val reportLink = "http://kotlin-native-performance.labs.jb.gg/?" +
|
||||
val reportLink = "https://kotlin-native-perf-summary.labs.jb.gg/?target=$target&build=$buildNumber"
|
||||
val detailedReportLink = "https://kotlin-native-performance.labs.jb.gg/?" +
|
||||
"report=bintray:$buildNumber:$target:$bintrayFileName&" +
|
||||
"compareTo=bintray:$compareToBuildNumber:$target:$bintrayFileName"
|
||||
|
||||
val title = "\n*Performance report for target $target (build $buildNumber)* - $reportLink\n"
|
||||
val title = "\n*Performance report for target $target (build $buildNumber)* - $reportLink\n" +
|
||||
"*Detailed info - * $detailedReportLink"
|
||||
|
||||
val header = "$title\n$changesInfo\n\nCompare to build $compareToBuildNumber: $compareToBuildLink\n\n"
|
||||
val footer = "*Benchmarks statistics:* $testReportUrl"
|
||||
val message = "$header\n$footer\n"
|
||||
|
||||
@@ -29,6 +29,8 @@ open class RegressionsSummaryReporter : DefaultTask() {
|
||||
@Input
|
||||
lateinit var targetsResultFiles: Map<String, String>
|
||||
|
||||
const val performanceServer = "https://kotlin-native-perf-summary.labs.jb.gg/"
|
||||
|
||||
@TaskAction
|
||||
fun run() {
|
||||
// Get TeamCity properties.
|
||||
@@ -59,6 +61,7 @@ open class RegressionsSummaryReporter : DefaultTask() {
|
||||
}
|
||||
|
||||
val message = buildString {
|
||||
append("*Performance summary on charts* - $performanceServer\n")
|
||||
results.forEach { (property, targets) ->
|
||||
append("$property: ${targets.map {(target, value) -> "$value ($target)"}.joinToString(" | ")}\n")
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ abstract class Launcher(val numWarmIterations: Int, val numberOfAttempts: Int, v
|
||||
samples[k] = scaledTime
|
||||
// Save benchmark object
|
||||
benchmarkResults.add(BenchmarkResult("$prefix$it", BenchmarkResult.Status.PASSED,
|
||||
scaledTime / 1000, scaledTime / 1000,
|
||||
scaledTime / 1000, BenchmarkResult.Metric.EXECUTION_TIME, scaledTime / 1000,
|
||||
k + 1, numWarmIterations))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,26 +242,38 @@ data class Environment(val machine: Machine, val jdk: JDKInstance): JsonSerializ
|
||||
}
|
||||
|
||||
class BenchmarkResult(val name: String, val status: Status,
|
||||
val score: Double, val runtimeInUs: Double,
|
||||
val score: Double, val metric: Metric, val runtimeInUs: Double,
|
||||
val repeat: Int, val warmup: Int): JsonSerializable {
|
||||
|
||||
constructor(name: String, score: Double) : this(name, Status.PASSED, score, 0.0, 0, 0)
|
||||
enum class Metric(val suffix: String, val value: String) {
|
||||
EXECUTION_TIME("", "EXECUTION_TIME"),
|
||||
CODE_SIZE(".codeSize", "CODE_SIZE"),
|
||||
COMPILE_TIME(".compileTime", "COMPILE_TIME")
|
||||
}
|
||||
|
||||
constructor(name: String, score: Double) : this(name, Status.PASSED, score, Metric.EXECUTION_TIME, 0.0, 0, 0)
|
||||
|
||||
companion object: EntityFromJsonFactory<BenchmarkResult> {
|
||||
|
||||
override fun create(data: JsonElement): BenchmarkResult {
|
||||
if (data is JsonObject) {
|
||||
val name = elementToString(data.getRequiredField("name"), "name")
|
||||
var name = elementToString(data.getRequiredField("name"), "name")
|
||||
val metricElement = data.getOptionalField("metric")
|
||||
val metric = if (metricElement != null && metricElement is JsonLiteral)
|
||||
metricFromString(metricElement.unquoted()) ?: Metric.EXECUTION_TIME
|
||||
else Metric.EXECUTION_TIME
|
||||
name += metric.suffix
|
||||
val statusElement = data.getRequiredField("status")
|
||||
if (statusElement is JsonLiteral) {
|
||||
val status = statusFromString(statusElement.unquoted())
|
||||
?: error("Status should be PASSED or FAILED")
|
||||
|
||||
val score = elementToDouble(data.getRequiredField("score"), "score")
|
||||
val runtimeInUs = elementToDouble(data.getRequiredField("runtimeInUs"), "runtimeInUs")
|
||||
val repeat = elementToInt(data.getRequiredField("repeat"), "repeat")
|
||||
val warmup = elementToInt(data.getRequiredField("warmup"), "warmup")
|
||||
|
||||
return BenchmarkResult(name, status, score, runtimeInUs, repeat, warmup)
|
||||
return BenchmarkResult(name, status, score, metric, runtimeInUs, repeat, warmup)
|
||||
} else {
|
||||
error("Status should be string literal.")
|
||||
}
|
||||
@@ -271,6 +283,7 @@ class BenchmarkResult(val name: String, val status: Status,
|
||||
}
|
||||
|
||||
fun statusFromString(s: String): Status? = Status.values().find { it.value == s }
|
||||
fun metricFromString(s: String): Metric? = Metric.values().find { it.value == s }
|
||||
}
|
||||
|
||||
enum class Status(val value: String) {
|
||||
@@ -281,9 +294,10 @@ class BenchmarkResult(val name: String, val status: Status,
|
||||
override fun toJson(): String {
|
||||
return """
|
||||
{
|
||||
"name": "$name",
|
||||
"name": "${name.removeSuffix(metric.suffix)}",
|
||||
"status": "${status.value}",
|
||||
"score": ${score},
|
||||
"metric": "${metric.value}",
|
||||
"runtimeInUs": ${runtimeInUs},
|
||||
"repeat": ${repeat},
|
||||
"warmup": ${warmup}
|
||||
|
||||
@@ -36,6 +36,13 @@ interface ConvertedFromJson {
|
||||
element.unquoted()
|
||||
else
|
||||
error("Field '$name' in '$element' is expected to be a string. Please, check origin files.")
|
||||
|
||||
fun elementToStringOrNull(element: JsonElement, name:String): String? =
|
||||
when (element) {
|
||||
is JsonLiteral -> element.unquoted()
|
||||
is JsonNull -> null
|
||||
else -> error("Field '$name' in '$element' is expected to be a string. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
|
||||
fun JsonObject.getRequiredField(fieldName: String): JsonElement {
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.jetbrains.analyzer.SummaryBenchmarksReport
|
||||
import org.jetbrains.kliopt.*
|
||||
import org.jetbrains.renders.*
|
||||
import org.jetbrains.report.BenchmarksReport
|
||||
import org.jetbrains.report.BenchmarkResult
|
||||
import org.jetbrains.report.json.JsonTreeParser
|
||||
|
||||
abstract class Connector {
|
||||
@@ -77,8 +78,61 @@ fun getFileContent(fileName: String, user: String? = null): String {
|
||||
}
|
||||
}
|
||||
|
||||
fun getBenchmarkReport(fileName: String, user: String? = null) =
|
||||
BenchmarksReport.create(JsonTreeParser.parse(getFileContent(fileName, user)))
|
||||
|
||||
// Prints text summary by users request.
|
||||
fun summaryAction(argParser: ArgParser) {
|
||||
val benchsReport = SummaryBenchmarksReport(getBenchmarkReport(argParser.get<String>("mainReport")!!, argParser.get<String>("user")))
|
||||
val results = mutableListOf<String>()
|
||||
results.apply {
|
||||
add(benchsReport.failedBenchmarks.size.toString())
|
||||
argParser.getAll<String>("exec-samples")?. let {
|
||||
val filter = if (it.first() == "all") null else it
|
||||
add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.EXECUTION_TIME,
|
||||
argParser.get<String>("exec")!! == "geomean", filter).joinToString(";"))
|
||||
}
|
||||
argParser.getAll<String>("compile-samples")?. let {
|
||||
val filter = if (it.first() == "all") null else it
|
||||
add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.COMPILE_TIME,
|
||||
argParser.get<String>("compile")!! == "geomean", filter).joinToString(";"))
|
||||
}
|
||||
argParser.getAll<String>("codesize-samples")?. let {
|
||||
val filter = if (it.first() == "all") null else it
|
||||
add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.CODE_SIZE,
|
||||
argParser.get<String>("codesize")!! == "geomean", filter).joinToString(";"))
|
||||
}
|
||||
}
|
||||
println(results.joinToString())
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
|
||||
val actions = mapOf( "summary" to Action(
|
||||
::summaryAction,
|
||||
ArgParser(
|
||||
listOf(
|
||||
OptionDescriptor(ArgType.Choice(listOf("samples", "geomean")), "exec",
|
||||
description = "Execution time way of calculation", defaultValue = "geomean"),
|
||||
OptionDescriptor(ArgType.String(), "exec-samples",
|
||||
description = "Samples used for execution time metric (value 'all' allows use all samples)",
|
||||
delimiter = ","),
|
||||
OptionDescriptor(ArgType.Choice(listOf("samples", "geomean")), "compile",
|
||||
description = "Compile time way of calculation", defaultValue = "geomean"),
|
||||
OptionDescriptor(ArgType.String(), "compile-samples",
|
||||
description = "Samples used for compile time metric (value 'all' allows use all samples)",
|
||||
delimiter = ","),
|
||||
OptionDescriptor(ArgType.Choice(listOf("samples", "geomean")), "codesize",
|
||||
description = "Code size way of calculation", defaultValue = "geomean"),
|
||||
OptionDescriptor(ArgType.String(), "codesize-samples",
|
||||
description = "Samples used for code size metric (value 'all' allows use all samples)",
|
||||
delimiter = ","),
|
||||
OptionDescriptor(ArgType.String(), "user", "u", "User access information for authorization")
|
||||
), listOf(ArgDescriptor(ArgType.String(), "mainReport", "Main report for analysis"))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val options = listOf(
|
||||
OptionDescriptor(ArgType.String(), "output", "o", "Output file"),
|
||||
OptionDescriptor(ArgType.Double(), "eps", "e", "Meaningful performance changes", "1.0"),
|
||||
@@ -94,16 +148,12 @@ fun main(args: Array<String>) {
|
||||
)
|
||||
|
||||
// Parse args.
|
||||
val argParser = ArgParser(options, arguments)
|
||||
val argParser = ArgParser(options, arguments, actions)
|
||||
if (argParser.parse(args)) {
|
||||
// Read contents of file.
|
||||
val mainBenchsResults = getFileContent(argParser.get<String>("mainReport")!!, argParser.get<String>("user"))
|
||||
val mainReportElement = JsonTreeParser.parse(mainBenchsResults)
|
||||
val mainBenchsReport = BenchmarksReport.create(mainReportElement)
|
||||
val mainBenchsReport = getBenchmarkReport(argParser.get<String>("mainReport")!!, argParser.get<String>("user"))
|
||||
var compareToBenchsReport = argParser.get<String>("compareToReport")?.let {
|
||||
val compareToResults = getFileContent(it, argParser.get<String>("user"))
|
||||
val compareToReportElement = JsonTreeParser.parse(compareToResults)
|
||||
BenchmarksReport.create(compareToReportElement)
|
||||
getBenchmarkReport(it, argParser.get<String>("user"))
|
||||
}
|
||||
|
||||
val renders = argParser.getAll<String>("renders")
|
||||
|
||||
@@ -82,7 +82,7 @@ data class MeanVarianceBenchmark(val meanBenchmark: BenchmarkResult, val varianc
|
||||
|
||||
}
|
||||
|
||||
fun geometricMean(values: List<Double>, totalNumber: Int = values.size) =
|
||||
fun geometricMean(values: Collection<Double>, totalNumber: Int = values.size) =
|
||||
values.asSequence().filter{ it != 0.0 }.map { it.pow(1.0 / totalNumber) }.reduce { a, b -> a * b }
|
||||
|
||||
fun computeMeanVariance(samples: List<Double>): MeanVariance {
|
||||
@@ -97,6 +97,7 @@ fun computeMeanVariance(samples: List<Double>): MeanVariance {
|
||||
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
|
||||
|
||||
@@ -111,6 +112,7 @@ fun collectMeanResults(benchmarks: Map<String, List<BenchmarkResult>>): Benchmar
|
||||
if (result.warmup != currentWarmup)
|
||||
println("Check data consistency. Warmup value for benchmark '${result.name}' differs.")
|
||||
currentWarmup = result.warmup
|
||||
metric = result.metric
|
||||
}
|
||||
|
||||
repeatedSequence.sort()
|
||||
@@ -125,10 +127,10 @@ fun collectMeanResults(benchmarks: Map<String, List<BenchmarkResult>>): Benchmar
|
||||
// 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,
|
||||
val meanBenchmark = BenchmarkResult(name, currentStatus, scoreMeanVariance.mean, metric,
|
||||
runtimeInUsMeanVariance.mean, repeatedSequence[resultsSet.size - 1],
|
||||
currentWarmup)
|
||||
val varianceBenchmark = BenchmarkResult(name, currentStatus, scoreMeanVariance.variance,
|
||||
val varianceBenchmark = BenchmarkResult(name, currentStatus, scoreMeanVariance.variance, metric,
|
||||
runtimeInUsMeanVariance.variance, repeatedSequence[resultsSet.size - 1],
|
||||
currentWarmup)
|
||||
name to MeanVarianceBenchmark(meanBenchmark, varianceBenchmark)
|
||||
|
||||
+22
@@ -133,6 +133,28 @@ class SummaryBenchmarksReport (val currentReport: BenchmarksReport,
|
||||
}
|
||||
}
|
||||
|
||||
fun getResultsByMetric(metric: BenchmarkResult.Metric, getGeoMean: Boolean = true, filter: List<String>? = null): List<Double> {
|
||||
val benchmarks = filter?.let {
|
||||
mergedReport.filter { entry ->
|
||||
filter.find {
|
||||
entry.key.startsWith(it)
|
||||
} != null
|
||||
}
|
||||
} ?: mergedReport
|
||||
if (benchmarks.isEmpty()) {
|
||||
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 -> entry.key.removeSuffix(metric.suffix) to entry.value.first!!.meanBenchmark.score }.toMap()
|
||||
if (getGeoMean) {
|
||||
return listOf(geometricMean(results.values))
|
||||
}
|
||||
return filter?.let { it.map { results[it] ?: error("Benchmark $it for metric $metric doesn't exist.") }.toList() } ?: results.values.toList()
|
||||
}
|
||||
|
||||
private fun getMaximumChange(bucket: Map<String, ScoreChange>): Double =
|
||||
// Maps of regressions and improvements are sorted.
|
||||
if (bucket.isEmpty()) 0.0 else bucket.values.map { it.first.mean }.first()
|
||||
|
||||
@@ -25,12 +25,12 @@ class AnalyzerTests {
|
||||
private val eps = 0.000001
|
||||
|
||||
private fun createMeanVarianceBenchmarks(): Pair<MeanVarianceBenchmark, MeanVarianceBenchmark> {
|
||||
val firstMean = BenchmarkResult("testBenchmark", BenchmarkResult.Status.PASSED, 9.0, 9.0, 10, 10)
|
||||
val firstVariance = BenchmarkResult("testBenchmark", BenchmarkResult.Status.PASSED, 0.0001, 0.0001, 10, 10)
|
||||
val firstMean = BenchmarkResult("testBenchmark", BenchmarkResult.Status.PASSED, 9.0, BenchmarkResult.Metric.EXECUTION_TIME, 9.0, 10, 10)
|
||||
val firstVariance = BenchmarkResult("testBenchmark", BenchmarkResult.Status.PASSED, 0.0001, BenchmarkResult.Metric.EXECUTION_TIME, 0.0001, 10, 10)
|
||||
val first = MeanVarianceBenchmark(firstMean, firstVariance)
|
||||
|
||||
val secondMean = BenchmarkResult("testBenchmark", BenchmarkResult.Status.PASSED, 10.0, 10.0, 10, 10)
|
||||
val secondVariance = BenchmarkResult("testBenchmark", BenchmarkResult.Status.PASSED, 0.0001, 0.0001, 10, 10)
|
||||
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)
|
||||
|
||||
@@ -55,6 +55,8 @@ sealed class ArgType(val hasParameter: kotlin.Boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
data class Action(val callback: (parser: ArgParser) -> Unit, val parser: ArgParser)
|
||||
|
||||
// Common descriptor both for options and positional arguments.
|
||||
abstract class Descriptor(val type: ArgType,
|
||||
val longName: String,
|
||||
@@ -120,7 +122,7 @@ class ArgDescriptor(
|
||||
|
||||
// Arguments parser.
|
||||
class ArgParser(optionsList: List<OptionDescriptor>, argsList: List<ArgDescriptor> = listOf<ArgDescriptor>(),
|
||||
useDefaultHelpShortName: Boolean = true) {
|
||||
val actions: Map<String, Action> = emptyMap(), useDefaultHelpShortName: Boolean = true) {
|
||||
private val options = optionsList.union(if (useDefaultHelpShortName)
|
||||
listOf(OptionDescriptor(ArgType.Boolean(), "help", "h", "Usage info"))
|
||||
else
|
||||
@@ -212,6 +214,16 @@ class ArgParser(optionsList: List<OptionDescriptor>, argsList: List<ArgDescripto
|
||||
valuesOrigin = descriptorsKeys.map { it to ValueOrigin.UNSET }.toMap().toMutableMap()
|
||||
while (index < args.size) {
|
||||
val arg = args[index]
|
||||
// Check for actions.
|
||||
actions.forEach { (name, action) ->
|
||||
if (arg == name) {
|
||||
// Use parser for this action.
|
||||
val parseResult = action.parser.parse(args.slice(index + 1..args.size - 1).toTypedArray())
|
||||
if (parseResult)
|
||||
action.callback(action.parser)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (arg.startsWith('-')) {
|
||||
// Option is found.
|
||||
val name = shortNames[arg.substring(1)] ?: arg.substring(1)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM node:8
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
# A wildcard is used to ensure both package.json AND package-lock.json are copied
|
||||
# where available (npm@5+)
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
# If you are building your code for production
|
||||
# RUN npm install --only=production
|
||||
|
||||
# Bundle app source
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "npm", "start" ]
|
||||
@@ -0,0 +1,51 @@
|
||||
buildscript {
|
||||
ext.rootBuildDirectory = file('../..')
|
||||
|
||||
apply from: "$rootBuildDirectory/gradle/loadRootProperties.gradle"
|
||||
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url buildKotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin2js'
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
|
||||
}
|
||||
|
||||
|
||||
sourceSets {
|
||||
main.resources.srcDirs += "resources"
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.native.home=../../dist
|
||||
+644
@@ -0,0 +1,644 @@
|
||||
{
|
||||
"name": "performance-server",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"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": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
|
||||
"integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.18",
|
||||
"negotiator": "0.6.1"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.18.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
|
||||
"integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
|
||||
"requires": {
|
||||
"bytes": "3.0.0",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "~1.6.3",
|
||||
"iconv-lite": "0.4.23",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.5.2",
|
||||
"raw-body": "2.3.3",
|
||||
"type-is": "~1.6.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bower": {
|
||||
"version": "1.8.8",
|
||||
"resolved": "https://registry.npmjs.org/bower/-/bower-1.8.8.tgz",
|
||||
"integrity": "sha512-1SrJnXnkP9soITHptSO+ahx3QKp3cVzn8poI6ujqc5SeOkg5iqM1pK9H+DSc2OQ8SnO0jC/NG4Ur/UIwy7574A=="
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||
"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": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
|
||||
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
||||
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"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": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"ejs": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz",
|
||||
"integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
},
|
||||
"express": {
|
||||
"version": "4.16.4",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
|
||||
"integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.5",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.18.3",
|
||||
"content-disposition": "0.5.2",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.3.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.1.1",
|
||||
"fresh": "0.5.2",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.4",
|
||||
"qs": "6.5.2",
|
||||
"range-parser": "~1.2.0",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.16.2",
|
||||
"serve-static": "1.13.2",
|
||||
"setprototypeof": "1.1.0",
|
||||
"statuses": "~1.4.0",
|
||||
"type-is": "~1.6.16",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
|
||||
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"statuses": "~1.4.0",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"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": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
||||
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.0",
|
||||
"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": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
||||
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
|
||||
"integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"kotlin": {
|
||||
"version": "1.3.21",
|
||||
"resolved": "https://registry.npmjs.org/kotlin/-/kotlin-1.3.21.tgz",
|
||||
"integrity": "sha512-JqxY9quNzwWl8iYvNj/ycfNBEbSFDKvmeK2rDVU89q0/X8BRjVzibJ5RFOq/9efvvvi+VBnIRhL7GSOGzmP2nQ=="
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
|
||||
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.38.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz",
|
||||
"integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.22",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz",
|
||||
"integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==",
|
||||
"requires": {
|
||||
"mime-db": "~1.38.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
|
||||
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||
"requires": {
|
||||
"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": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
|
||||
"integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"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": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
|
||||
"integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==",
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"ipaddr.js": "1.8.0"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
|
||||
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
|
||||
"integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
|
||||
"requires": {
|
||||
"bytes": "3.0.0",
|
||||
"http-errors": "1.6.3",
|
||||
"iconv-lite": "0.4.23",
|
||||
"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": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
|
||||
"integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.6.2",
|
||||
"mime": "1.4.1",
|
||||
"ms": "2.0.0",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.0",
|
||||
"statuses": "~1.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
|
||||
"integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.2",
|
||||
"send": "0.16.2"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
|
||||
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"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": {
|
||||
"version": "1.6.16",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
|
||||
"integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "performance-server",
|
||||
"version": "1.0.0",
|
||||
"main": "server/app.js",
|
||||
"scripts": {
|
||||
"start": "node server/app.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "~1.18.3",
|
||||
"debug": "~4.1.1",
|
||||
"express": "~4.16.4",
|
||||
"kotlin": "^1.3.21",
|
||||
"sync-request": "~6.0.0",
|
||||
"ejs": "~2.6.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.
|
||||
*/
|
||||
|
||||
external fun require(module: String): dynamic
|
||||
|
||||
external val process: dynamic
|
||||
external val __dirname: dynamic
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
println("Server Starting!")
|
||||
|
||||
val express = require("express")
|
||||
val app = express()
|
||||
val path = require("path")
|
||||
val bodyParser = require("body-parser")
|
||||
val http = require("http")
|
||||
// Get port from environment and store in Express.
|
||||
val port = normalizePort(process.env.PORT)
|
||||
app.use(bodyParser.json())
|
||||
app.set("port", port)
|
||||
|
||||
// View engine setup.
|
||||
app.set("views", path.join(__dirname, "../ui"))
|
||||
app.set("view engine", "ejs")
|
||||
app.use(express.static("ui"))
|
||||
|
||||
val server = http.createServer(app)
|
||||
app.listen(port, {
|
||||
println("App listening on port " + port + "!")
|
||||
})
|
||||
|
||||
app.use("/", router())
|
||||
}
|
||||
|
||||
fun normalizePort(port: Int) =
|
||||
if (port >= 0) port else 3000
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.
|
||||
*/
|
||||
|
||||
import org.w3c.xhr.*
|
||||
import kotlin.js.json
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.build.Build
|
||||
|
||||
const val teamCityUrl = "https://buildserver.labs.intellij.net/app/rest"
|
||||
const val downloadBintrayUrl = "https://dl.bintray.com/content/lepilkinaelena/KotlinNativePerformance"
|
||||
const val uploadBintrayUrl = "https://api.bintray.com/content/lepilkinaelena/KotlinNativePerformance"
|
||||
const val buildsFileName = "buildsSummary.csv"
|
||||
const val bintrayPackage = "builds"
|
||||
const val buildsInfoPartsNumber = 11
|
||||
|
||||
operator fun <K, V> Map<K, V>?.get(key: K) = this?.get(key)
|
||||
|
||||
// Local cache for saving information about builds got from Bintray.
|
||||
object LocalCache {
|
||||
private val knownTargets = listOf("Linux", "MacOSX", "Windows10")
|
||||
private val buildsInfo = mutableMapOf<String, MutableMap<String, String>>()
|
||||
|
||||
fun clean(onlyTarget: String? = null) {
|
||||
onlyTarget?.let {
|
||||
buildsInfo[onlyTarget]?.clear()
|
||||
} ?: buildsInfo.clear()
|
||||
}
|
||||
|
||||
fun fill(onlyTarget: String? = null) {
|
||||
onlyTarget?.let {
|
||||
val buildsDescription = getBuildsInfoFromBintray(onlyTarget).lines().drop(1)
|
||||
buildsInfo[onlyTarget] = mutableMapOf<String, String>()
|
||||
buildsDescription.forEach {
|
||||
if (!it.isEmpty()) {
|
||||
val buildNumber = it.substringBefore(',')
|
||||
if (!"\\d+(\\.\\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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Build information provided from request.
|
||||
data class BuildInfo(val buildNumber: String, val branch: String, val startTime: String,
|
||||
val finishTime: String)
|
||||
|
||||
data class BuildRegister(val buildId: String, val teamCityUser: String, val teamCityPassword: String,
|
||||
val bintrayUser: String, val bintrayPassword: String,
|
||||
val target: String, val buildType: String, val failuresNumber: Int,
|
||||
val executionTime: String, val compileTime: String, val codeSize: String,
|
||||
val bundleSize: String?) {
|
||||
companion object {
|
||||
fun create(json: String): BuildRegister {
|
||||
val requestDetails = JSON.parse<BuildRegister>(json)
|
||||
// Parse method doesn't create real instance with all methods. So create it by hands.
|
||||
return BuildRegister(requestDetails.buildId, requestDetails.teamCityUser, requestDetails.teamCityPassword,
|
||||
requestDetails.bintrayUser, requestDetails.bintrayPassword, requestDetails.target,
|
||||
requestDetails.buildType, requestDetails.failuresNumber, requestDetails.executionTime, requestDetails.compileTime,
|
||||
requestDetails.codeSize, requestDetails.bundleSize)
|
||||
}
|
||||
}
|
||||
|
||||
private val teamCityBuildUrl: String by lazy { "$teamCityUrl/builds/id:$buildId" }
|
||||
|
||||
val changesListUrl: String by lazy {
|
||||
"$teamCityUrl/changes/?locator=build:id:$buildId"
|
||||
}
|
||||
|
||||
private fun sendTeamCityRequest(url: String) = sendGetRequest(url, teamCityUser, teamCityPassword)
|
||||
|
||||
fun getBuildInformation(): BuildInfo {
|
||||
val buildNumber = sendTeamCityRequest("$teamCityBuildUrl/number")
|
||||
val branch = sendTeamCityRequest("$teamCityBuildUrl/branchName")
|
||||
val startTime = sendTeamCityRequest("$teamCityBuildUrl/startDate")
|
||||
val finishTime = sendTeamCityRequest("$teamCityBuildUrl/finishDate")
|
||||
return BuildInfo(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 getBuildsInfoFromBintray(target: String) =
|
||||
sendGetRequest("$downloadBintrayUrl/$target/$buildsFileName")
|
||||
|
||||
// Parse and postprocess result of response with build description.
|
||||
fun prepareBuildsResponse(builds: Collection<String>, type: String, buildNumber: String? = null): List<Build> {
|
||||
val buildsObjects = mutableListOf<Build>()
|
||||
builds.forEach {
|
||||
val tokens = it.split(",").map { it.trim() }
|
||||
if (tokens.size != buildsInfoPartsNumber) {
|
||||
error("Build description $it doesn't contain all necessary information. " +
|
||||
"File with data could be corrupted.")
|
||||
}
|
||||
if (tokens[5] == type || type == "day" || 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
|
||||
}
|
||||
|
||||
// Routing of requests to current server.
|
||||
fun router() {
|
||||
val express = require("express")
|
||||
val router = express.Router()
|
||||
|
||||
// Register build on Bintray.
|
||||
router.post("/register", { request, response ->
|
||||
val register = BuildRegister.create(JSON.stringify(request.body))
|
||||
|
||||
// Get information from TeamCity.
|
||||
val buildInfo = register.getBuildInformation()
|
||||
val changes = sendGetRequest(register.changesListUrl, register.teamCityUser,
|
||||
register.teamCityPassword, true)
|
||||
val commitsList = CommitsList(JsonTreeParser.parse(changes))
|
||||
val commitsDescription = buildString {
|
||||
commitsList.commits.forEach {
|
||||
append("${it.revision} by ${it.developer};")
|
||||
}
|
||||
}
|
||||
|
||||
// Get summary file from Bintray.
|
||||
var buildsDescription = getBuildsInfoFromBintray(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 = "$uploadBintrayUrl/$bintrayPackage/latest/${register.target}/${buildsFileName}?publish=1&override=1"
|
||||
sendUploadRequest(uploadUrl, buildsDescription, register.bintrayUser, register.bintrayPassword)
|
||||
|
||||
// Send response.
|
||||
response.sendStatus(200)
|
||||
})
|
||||
|
||||
// Get list of builds.
|
||||
router.get("/builds/:target/:type/:id", { request, response ->
|
||||
val builds = LocalCache[request.params.target, request.params.id]
|
||||
response.json(prepareBuildsResponse(builds, request.params.type, request.params.id))
|
||||
})
|
||||
|
||||
router.get("/builds/:target/:type", { request, response ->
|
||||
val builds = LocalCache[request.params.target]
|
||||
response.json(prepareBuildsResponse(builds, request.params.type))
|
||||
})
|
||||
|
||||
router.get("/clean", { request, response ->
|
||||
LocalCache.clean()
|
||||
response.sendStatus(200)
|
||||
})
|
||||
|
||||
router.get("/fill", { request, response ->
|
||||
LocalCache.fill()
|
||||
response.sendStatus(200)
|
||||
})
|
||||
|
||||
// Main page.
|
||||
router.get("/", { request, response ->
|
||||
response.render("index")
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
fun getAuth(user: String, password: String): String {
|
||||
val buffer = js("Buffer").from(user + ":" + password)
|
||||
val based64String = buffer.toString("base64")
|
||||
return "Basic " + based64String
|
||||
}
|
||||
|
||||
fun sendGetRequest(url: String, user: String? = null, password: String? = null, jsonContentType: Boolean = false) : String {
|
||||
val request = require("sync-request")
|
||||
val headers = mutableListOf<Pair<String, String>>()
|
||||
if (user != null && password != null) {
|
||||
headers.add("Authorization" to getAuth(user, password))
|
||||
}
|
||||
if (jsonContentType) {
|
||||
headers.add("Accept" to "application/json")
|
||||
}
|
||||
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) {
|
||||
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))
|
||||
}
|
||||
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,69 @@
|
||||
buildscript {
|
||||
ext.rootBuildDirectory = file('../../..')
|
||||
|
||||
apply from: "$rootBuildDirectory/gradle/loadRootProperties.gradle"
|
||||
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url buildKotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin2js'
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
|
||||
}
|
||||
|
||||
compileKotlin2Js {
|
||||
kotlinOptions.outputFile = "${projectDir}/js/main.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/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
.chart {
|
||||
height: 400px;
|
||||
}
|
||||
.ct-legend {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
list-style: none;
|
||||
text-align: center;
|
||||
}
|
||||
.ct-legend li {
|
||||
position: relative;
|
||||
padding-left: 23px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 3px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
.ct-legend li:before {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
content: '';
|
||||
border: 3px solid transparent;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.ct-legend li.inactive:before {
|
||||
background: transparent;
|
||||
}
|
||||
.ct-legend.ct-legend-inside {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
.ct-legend.ct-legend-inside li{
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
.ct-legend .ct-series-0:before {
|
||||
background-color: #d70206;
|
||||
border-color: #d70206;
|
||||
}
|
||||
.ct-legend .ct-series-1:before {
|
||||
background-color: #f05b4f;
|
||||
border-color: #f05b4f;
|
||||
}
|
||||
.ct-legend .ct-series-2:before {
|
||||
background-color: #f4c63d;
|
||||
border-color: #f4c63d;
|
||||
}
|
||||
.ct-legend .ct-series-3:before {
|
||||
background-color: #d17905;
|
||||
border-color: #d17905;
|
||||
}
|
||||
.ct-legend .ct-series-4:before {
|
||||
background-color: #453d3f;
|
||||
border-color: #453d3f;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Benchmarks report
|
||||
</title>
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/chartist/0.11.0/chartist.min.css">
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.min.js">
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js">
|
||||
</script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="navbar navbar-expand navbar-dark flex-column flex-md-row bd-navbar" style="background-color:#161616;">
|
||||
<img src="https://dashboard.snapcraft.io/site_media/appmedia/2018/04/256px-kotlin-logo-svg.png" style="width:60px;height:60px;">
|
||||
|
||||
<span class="navbar-brand mb-0 h1">
|
||||
Benchmarks report
|
||||
</span>
|
||||
</header>
|
||||
<div class="container-fluid">
|
||||
<p>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<label class="input-group-text" for="inputGroupTarget">Target</label>
|
||||
</div>
|
||||
<select class="custom-select" id="inputGroupTarget">
|
||||
<option value="Linux">Linux</option>
|
||||
<option value="MacOSX">Mac OS X</option>
|
||||
<option value="Windows10">Windows 10</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<label class="input-group-text" for="inputGroupBuildType">Build type</label>
|
||||
</div>
|
||||
<select class="custom-select" id="inputGroupBuildType">
|
||||
<option value="dev">Dev builds</option>
|
||||
<option value="day">Day</option>
|
||||
<option value="release">Releases</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="basic-addon1">Highlighted build</span>
|
||||
</div>
|
||||
<input id="highligted_build" type="text" class="form-control" placeholder="" aria-label="build" aria-describedby="basic-addon1">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>Execution time</h4>
|
||||
<div id="exec_chart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 40px">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>Compile time</h4>
|
||||
<div id="compile_chart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 40px">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>Code size</h4>
|
||||
<div id="codesize_chart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 40px">
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>Bundle size</h4>
|
||||
<div id="bundlesize_chart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 40px">
|
||||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartist/0.11.0/chartist.min.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="lib/kotlin/kotlin.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.
|
||||
*/
|
||||
|
||||
import kotlin.browser.*
|
||||
import org.w3c.xhr.*
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.build.Build
|
||||
import kotlin.js.*
|
||||
import org.w3c.dom.*
|
||||
|
||||
// API for interop with JS library Chartist.
|
||||
external class ChartistPlugins {
|
||||
fun legend(data: dynamic): dynamic
|
||||
}
|
||||
|
||||
external object Chartist {
|
||||
class Svg(form: String, parameters: dynamic, chartArea: String)
|
||||
val plugins: ChartistPlugins
|
||||
fun Line(query: String, data: dynamic, options: dynamic): dynamic
|
||||
}
|
||||
|
||||
fun sendGetRequest(url: String) : String {
|
||||
val request = XMLHttpRequest()
|
||||
|
||||
request.open("GET", url, false)
|
||||
request.send()
|
||||
if (request.status == 200.toShort()) {
|
||||
return request.responseText
|
||||
}
|
||||
error("Request to $url has status ${request.status}")
|
||||
}
|
||||
|
||||
// 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 getChartData(labels: List<String>, valuesList: Collection<List<*>>): dynamic {
|
||||
val chartData: dynamic = object{}
|
||||
chartData["labels"] = labels.toTypedArray()
|
||||
chartData["series"] = valuesList.map { it.toTypedArray() }.toTypedArray()
|
||||
return chartData
|
||||
}
|
||||
|
||||
fun getChartOptions(samples: Array<String>): dynamic {
|
||||
val chartOptions: dynamic = object{}
|
||||
chartOptions["fullWidth"] = true
|
||||
val paddingObject: dynamic = object{}
|
||||
paddingObject["right"] = 40
|
||||
chartOptions["chartPadding"] = paddingObject
|
||||
val axisXObject: dynamic = object{}
|
||||
axisXObject["offset"] = 40
|
||||
chartOptions["axisX"] = axisXObject
|
||||
val legendObject: dynamic = object{}
|
||||
legendObject["legendNames"] = samples
|
||||
chartOptions["plugins"] = arrayOf(Chartist.plugins.legend(legendObject))
|
||||
return chartOptions
|
||||
}
|
||||
|
||||
fun customizeChart(chart: dynamic, chartContainer: String, jquerySelector: dynamic, builds: List<Build>,
|
||||
parameters: Map<String, String>) {
|
||||
chart.on("draw", { data ->
|
||||
var element = data.element
|
||||
if (data.type == "point") {
|
||||
val pointSize = 12
|
||||
val currentBuild = builds.get(data.index)
|
||||
// Higlight builds with failures.
|
||||
if (currentBuild.failuresNumber > 0) {
|
||||
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._node.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=bintray:" +
|
||||
"${currentBuild.buildNumber}:${parameters["target"]}:nativeReport.json" +
|
||||
"${if (data.index - 1 >= 0)
|
||||
"&compareTo=bintray:${builds.get(data.index - 1).buildNumber}:${parameters["target"]}:nativeReport.json"
|
||||
else ""}"
|
||||
val information = buildString {
|
||||
append("<a href=\"$linkToDetailedInfo\">${currentBuild.buildNumber}</a><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 = currentBuild.commits.split(";")
|
||||
commitsList.forEach {
|
||||
if (!it.isEmpty())
|
||||
append("${it.substringBefore("by").substring(0, 7)} by ${it.substringAfter("by ")}<br>")
|
||||
}
|
||||
|
||||
}
|
||||
element._node.setAttribute("title", information)
|
||||
element._node.setAttribute("data-chart-tooltip", chartContainer)
|
||||
element._node.addEventListener("click", {
|
||||
window.location.replace(linkToDetailedInfo)
|
||||
})
|
||||
}
|
||||
})
|
||||
chart.on("created", {
|
||||
val currentChart = jquerySelector
|
||||
val parameters: dynamic = object{}
|
||||
parameters["selector"] = "[data-chart-tooltip=\"$chartContainer\"]"
|
||||
parameters["container"] = "#$chartContainer"
|
||||
parameters["html"] = true
|
||||
currentChart.tooltip(parameters)
|
||||
})
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val serverUrl = "https://kotlin-native-perf-summary.labs.jb.gg/builds"
|
||||
|
||||
// Get parameters from request.
|
||||
val url = window.location.href
|
||||
val parametersPart = url.substringAfter("?").split('&')
|
||||
val parameters = mutableMapOf("target" to "Linux", "type" to "dev", "build" to "")
|
||||
parametersPart.forEach {
|
||||
val parsedParameter = it.split("=", limit = 2)
|
||||
if (parsedParameter.size == 2) {
|
||||
val (key, value) = parsedParameter
|
||||
if (parameters.containsKey(key)) {
|
||||
parameters[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get builds.
|
||||
val buildsUrl = buildString {
|
||||
append("$serverUrl")
|
||||
append("/${parameters["target"]}")
|
||||
append("/${parameters["type"]}")
|
||||
append("/${parameters["build"]}")
|
||||
}
|
||||
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) }
|
||||
|
||||
// Fill autocomplete list.
|
||||
val buildsNumbers = builds.map { json("value" to it.buildNumber, "data" to it.buildNumber) }.toTypedArray()
|
||||
|
||||
// Change inputs values connected with parameters and add events listeners.
|
||||
document.querySelector("#inputGroupTarget [value=\"${parameters["target"]}\"]")?.setAttribute("selected", "true")
|
||||
document.querySelector("#inputGroupBuildType [value=\"${parameters["type"]}\"]")?.setAttribute("selected", "true")
|
||||
(document.getElementById("highligted_build") as HTMLInputElement).value = parameters["build"]!!
|
||||
|
||||
// Add onChange events for fields.
|
||||
js("$('#inputGroupTarget')").change({
|
||||
val newValue = js("$(this).val()")
|
||||
if (newValue != parameters["target"]) {
|
||||
val newLink = "http://${window.location.host}/?target=$newValue&type=${parameters["type"]}" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}"
|
||||
window.location.replace(newLink)
|
||||
}
|
||||
})
|
||||
js("$('#inputGroupBuildType')").change({
|
||||
val newValue = js("$(this).val()")
|
||||
if (newValue != parameters["type"]) {
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=$newValue" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}"
|
||||
window.location.replace(newLink)
|
||||
}
|
||||
})
|
||||
|
||||
val autocompleteParameters: dynamic = object{}
|
||||
autocompleteParameters["lookup"] = buildsNumbers
|
||||
autocompleteParameters["onSelect"] = { suggestion ->
|
||||
if (suggestion.value != parameters["build"]) {
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}" +
|
||||
"${if (suggestion.value.isEmpty()) "" else "&build=${suggestion.value}"}"
|
||||
window.location.replace(newLink)
|
||||
}
|
||||
}
|
||||
js("$( \"#highligted_build\" )").autocomplete(autocompleteParameters)
|
||||
js("$('#highligted_build')").change({
|
||||
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.replace(newLink)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// Collect information for charts library.
|
||||
val labels = mutableListOf<String>()
|
||||
val executionTime = mutableMapOf<String, MutableList<Double?>>()
|
||||
val compileTime = mutableMapOf<String, MutableList<Double?>>()
|
||||
val codeSize = mutableMapOf<String, MutableList<Int?>>()
|
||||
val bundleSize = mutableListOf<Int?>()
|
||||
|
||||
builds.forEach {
|
||||
// Choose labels on x axis.
|
||||
if (parameters["type"] == "day") {
|
||||
labels.add(it.date)
|
||||
} else {
|
||||
labels.add(it.buildNumber)
|
||||
}
|
||||
separateValues(it.executionTime, executionTime) { value -> value.toDouble() }
|
||||
separateValues(it.compileTime, compileTime) { value -> value.toDouble() }
|
||||
separateValues(it.codeSize, codeSize) { value -> value.toInt() }
|
||||
bundleSize.add(it.bundleSize?.toInt())
|
||||
}
|
||||
|
||||
// Draw charts.
|
||||
val execChart = Chartist.Line("#exec_chart", getChartData(labels, executionTime.values),
|
||||
getChartOptions(executionTime.keys.toTypedArray()))
|
||||
val compileChart = Chartist.Line("#compile_chart", getChartData(labels, compileTime.values),
|
||||
getChartOptions(compileTime.keys.toTypedArray()))
|
||||
val codeSizeChart = Chartist.Line("#codesize_chart", getChartData(labels, codeSize.values),
|
||||
getChartOptions(codeSize.keys.toTypedArray()))
|
||||
val bundleSizeChart = Chartist.Line("#bundlesize_chart", getChartData(labels, listOf(bundleSize)),
|
||||
getChartOptions(arrayOf("Bundle size")))
|
||||
|
||||
// Tooltips and higlights.
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user