From 553e2fdda7dc463378e677400107905a32a5f1f9 Mon Sep 17 00:00:00 2001 From: LepilkinaElena Date: Wed, 20 Mar 2019 19:31:01 +0300 Subject: [PATCH] Change axises and delete action in API (#2792) --- performance/build.gradle | 2 +- .../buildSrc/src/main/kotlin/BuildRegister.kt | 5 +- .../src/main/kotlin/RegressionsReporter.kt | 59 +---------------- performance/buildSrc/src/main/kotlin/Utils.kt | 65 ++++++++++++++++++- .../src/main/kotlin/routes/route.kt | 46 +++++++++++-- tools/performance-server/ui/index.ejs | 1 + .../ui/src/main/kotlin/main.kt | 42 ++++++++---- 7 files changed, 141 insertions(+), 79 deletions(-) diff --git a/performance/build.gradle b/performance/build.gradle index 36d1824f7a4..e2d6ee5e27a 100644 --- a/performance/build.gradle +++ b/performance/build.gradle @@ -82,7 +82,7 @@ private def uploadBenchmarkResultToBintray(String fileName) { } task registerBuild(type: BuildRegister) { - onlyBranch = project.findProperty('kotlin.register.only.branch') + onlyBranch = project.findProperty('kotlin.register.branch') // Get bundle size. bundleSize = null if (project.findProperty('kotlin.bundleBuild') != null) { diff --git a/performance/buildSrc/src/main/kotlin/BuildRegister.kt b/performance/buildSrc/src/main/kotlin/BuildRegister.kt index b1408b4ad99..f293732d3d5 100644 --- a/performance/buildSrc/src/main/kotlin/BuildRegister.kt +++ b/performance/buildSrc/src/main/kotlin/BuildRegister.kt @@ -91,7 +91,10 @@ open class BuildRegister : DefaultTask() { val teamCityUser = buildProperties.getProperty("teamcity.auth.userId") val teamCityPassword = buildProperties.getProperty("teamcity.auth.password") val buildNumber = buildProperties.getProperty("build.number") - val branch = buildProperties.getProperty("teamcity.build.branch") + + // Get branch. + val currentBuild = getBuild("id:$buildId", teamCityUser, teamCityPassword) + val branch = getBuildProperty(currentBuild,"branchName") // Get summary information. val output = arrayOf("$analyzer", "summary", "-exec-samples", "all", "-compile", "samples", diff --git a/performance/buildSrc/src/main/kotlin/RegressionsReporter.kt b/performance/buildSrc/src/main/kotlin/RegressionsReporter.kt index 4332c0af4a3..911ca82fcf9 100644 --- a/performance/buildSrc/src/main/kotlin/RegressionsReporter.kt +++ b/performance/buildSrc/src/main/kotlin/RegressionsReporter.kt @@ -24,34 +24,6 @@ import java.net.URL import java.util.Base64 import java.util.Properties -data class Commit(val revision: String, val developer: String, val webUrlWithDescription: String) - -// List of commits. -class CommitsList(data: JsonElement): ConvertedFromJson { - - val commits: List - - 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"), - elementToString(getRequiredField("webUrl"), "webUrl") - ) - } - } - } ?: listOf() - } -} - /** * Task to produce regressions report and send it to slack. Requires a report with current benchmarks result * and path to analyzer tool @@ -65,7 +37,7 @@ class CommitsList(data: JsonElement): ConvertedFromJson { */ open class RegressionsReporter : DefaultTask() { - val teamCityUrl = "http://buildserver.labs.intellij.net" + val slackUsers = mapOf( "olonho" to "nikolay.igotti", "nikolay.igotti" to "nikolay.igotti", @@ -107,41 +79,12 @@ open class RegressionsReporter : DefaultTask() { private fun testReportUrl(buildId: String, buildTypeId: String) = tabUrl(buildId, buildTypeId, "testsInfo") - private fun buildsUrl(buildLocator: String) = - "$teamCityUrl/app/rest/builds/?locator=$buildLocator" - private fun previousBuildLocator(buildTypeId: String, branchName: String) = "buildType:id:$buildTypeId,branch:name:$branchName,status:SUCCESS,state:finished,count:1" private fun changesListUrl(buildLocator: String) = "$teamCityUrl/app/rest/changes/?locator=build:$buildLocator" - private fun sendGetRequest(url: String, username: String? = null, password: String? = null) : String { - val connection = URL(url).openConnection() as HttpURLConnection - if (username != null && password != null) { - val auth = Base64.getEncoder().encode((username + ":" + password).toByteArray()).toString(Charsets.UTF_8) - connection.addRequestProperty("Authorization", "Basic $auth") - } - connection.setRequestProperty("Accept", "application/json"); - connection.connect() - return connection.inputStream.use { it.reader().use { reader -> reader.readText() } } - } - - private fun getBuild(buildLocator: String, user: String, password: String) = - try { - sendGetRequest(buildsUrl(buildLocator), user, password) - } catch (t: Throwable) { - error("Try to get build! TeamCity is unreachable!") - } - - private fun getBuildProperty(buildJsonDescription: String, property: String) = - with(JsonTreeParser.parse(buildJsonDescription) as JsonObject) { - if (getPrimitive("count").int == 0) { - error("No build information on TeamCity for $buildJsonDescription!") - } - (getArray("build").getObject(0).getPrimitive(property) as JsonLiteral).unquoted() - } - private fun getCommits(buildLocator: String, user: String, password: String): CommitsList { val changes = try { sendGetRequest(changesListUrl(buildLocator), user, password) diff --git a/performance/buildSrc/src/main/kotlin/Utils.kt b/performance/buildSrc/src/main/kotlin/Utils.kt index 45604c3b73d..94a3250518b 100644 --- a/performance/buildSrc/src/main/kotlin/Utils.kt +++ b/performance/buildSrc/src/main/kotlin/Utils.kt @@ -7,6 +7,10 @@ import java.io.FileInputStream import java.io.IOException import java.io.File import java.util.concurrent.TimeUnit +import java.net.HttpURLConnection +import java.net.URL +import java.util.Base64 +import org.jetbrains.report.json.* // Run command line from string. fun Array.runCommand(workingDir: File = File("."), @@ -23,4 +27,63 @@ fun Array.runCommand(workingDir: File = File("."), } catch (e: IOException) { error("Couldn't run command $this") } -} \ No newline at end of file +} + +data class Commit(val revision: String, val developer: String, val webUrlWithDescription: String) + +val teamCityUrl = "http://buildserver.labs.intellij.net" + +// List of commits. +class CommitsList(data: JsonElement): ConvertedFromJson { + + val commits: List + + 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"), + elementToString(getRequiredField("webUrl"), "webUrl") + ) + } + } + } ?: listOf() + } +} + +fun buildsUrl(buildLocator: String) = + "$teamCityUrl/app/rest/builds/?locator=$buildLocator" + +fun getBuild(buildLocator: String, user: String, password: String) = + try { + sendGetRequest(buildsUrl(buildLocator), user, password) + } catch (t: Throwable) { + error("Try to get build! TeamCity is unreachable!") + } + +fun sendGetRequest(url: String, username: String? = null, password: String? = null) : String { + val connection = URL(url).openConnection() as HttpURLConnection + if (username != null && password != null) { + val auth = Base64.getEncoder().encode((username + ":" + password).toByteArray()).toString(Charsets.UTF_8) + connection.addRequestProperty("Authorization", "Basic $auth") + } + connection.setRequestProperty("Accept", "application/json"); + connection.connect() + return connection.inputStream.use { it.reader().use { reader -> reader.readText() } } +} + +fun getBuildProperty(buildJsonDescription: String, property: String) = + with(JsonTreeParser.parse(buildJsonDescription) as JsonObject) { + if (getPrimitive("count").int == 0) { + error("No build information on TeamCity for $buildJsonDescription!") + } + (getArray("build").getObject(0).getPrimitive(property) as JsonLiteral).unquoted() + } diff --git a/tools/performance-server/src/main/kotlin/routes/route.kt b/tools/performance-server/src/main/kotlin/routes/route.kt index 4f97d868ea8..aafb86f569a 100644 --- a/tools/performance-server/src/main/kotlin/routes/route.kt +++ b/tools/performance-server/src/main/kotlin/routes/route.kt @@ -64,6 +64,28 @@ object LocalCache { fun buildExists(target: String, buildNumber: String) = buildsInfo[target][buildNumber]?.let { true } ?: false + fun delete(target: String, builds: Iterable, bintrayUser: String, bintrayPassword: String): Boolean { + // Delete from bintray. + val buildsDescription = getBuildsInfoFromBintray(target).lines() + val initialBuildsNumber = buildsDescription.size + buildsDescription.filter { + val buildNumber = it.substringBefore(',') + buildNumber !in builds + } + + if (buildsDescription.size < initialBuildsNumber) { + // Upload new version of file. + val uploadUrl = "$uploadBintrayUrl/$bintrayPackage/latest/$target/$buildsFileName?publish=1&override=1" + sendUploadRequest(uploadUrl, buildsDescription.joinToString("\n"), bintrayUser, bintrayPassword) + + // Reload values. + clean(target) + fill(target) + return true + } + return false + } + private fun getBuilds(target: String, buildNumber: String? = null) = buildsInfo[target]?.let { buildsList -> buildNumber?.let { @@ -125,12 +147,12 @@ data class BuildRegister(val buildId: String, val teamCityUser: String, val team val currentTime = Date() val timeZone = currentTime.getTimezoneOffset() / -60 // Convert to hours. // Get finish time as current time, because buid on TeamCity isn't finished. - val finishTime = "${format(currentTime.getFullYear())}" + - "${format(currentTime.getMonth() + 1)}" + - "${format(currentTime.getDate())}" + - "T${format(currentTime.getHours())}" + - "${format(currentTime.getMinutes())}" + - "${format(currentTime.getSeconds())}" + + val finishTime = "${format(currentTime.getUTCFullYear())}" + + "${format(currentTime.getUTCMonth() + 1)}" + + "${format(currentTime.getUTCDate())}" + + "T${format(currentTime.getUTCHours())}" + + "${format(currentTime.getUTCMinutes())}" + + "${format(currentTime.getUTCSeconds())}" + "${if (timeZone > 0) "+" else "-"}${format(timeZone)}${format(0)}" return BuildInfo(buildNumber, branch, startTime, finishTime) } @@ -217,6 +239,9 @@ fun router() { val uploadUrl = "$uploadBintrayUrl/$bintrayPackage/latest/${register.target}/${buildsFileName}?publish=1&override=1" sendUploadRequest(uploadUrl, buildsDescription, register.bintrayUser, register.bintrayPassword) + LocalCache.clean(register.target) + LocalCache.fill(register.target) + // Send response. response.sendStatus(200) }) @@ -242,6 +267,15 @@ fun router() { response.sendStatus(200) }) + router.get("/delete/:target", { request, response -> + val buildsToDelete: List = request.query.builds.toString().split(",").map { it.trim() } + val result = LocalCache.delete(request.params.target, buildsToDelete, request.query.user, request.query.key) + if (result) { + response.sendStatus(200) + } + response.sendStatus(404) + }) + // Main page. router.get("/", { request, response -> response.render("index") diff --git a/tools/performance-server/ui/index.ejs b/tools/performance-server/ui/index.ejs index 2fcceefa863..edbe3f56b4f 100644 --- a/tools/performance-server/ui/index.ejs +++ b/tools/performance-server/ui/index.ejs @@ -94,6 +94,7 @@ + diff --git a/tools/performance-server/ui/src/main/kotlin/main.kt b/tools/performance-server/ui/src/main/kotlin/main.kt index 084c0bea4d3..84408936c72 100644 --- a/tools/performance-server/ui/src/main/kotlin/main.kt +++ b/tools/performance-server/ui/src/main/kotlin/main.kt @@ -24,6 +24,7 @@ import org.w3c.dom.* // API for interop with JS library Chartist. external class ChartistPlugins { fun legend(data: dynamic): dynamic + fun ctAxisTitle(data: dynamic): dynamic } external object Chartist { @@ -73,7 +74,7 @@ fun getChartData(labels: List, valuesList: Collection>): dynamic return chartData } -fun getChartOptions(samples: Array): dynamic { +fun getChartOptions(samples: Array, yTitle: String): dynamic { val chartOptions: dynamic = object{} chartOptions["fullWidth"] = true val paddingObject: dynamic = object{} @@ -83,11 +84,22 @@ fun getChartOptions(samples: Array): dynamic { axisXObject["offset"] = 40 chartOptions["axisX"] = axisXObject val axisYObject: dynamic = object{} - axisYObject["offset"] = 70 + axisYObject["offset"] = 90 chartOptions["axisY"] = axisYObject val legendObject: dynamic = object{} legendObject["legendNames"] = samples - chartOptions["plugins"] = arrayOf(Chartist.plugins.legend(legendObject)) + val titleObject: dynamic = object{} + val axisYTitle: dynamic = object{} + axisYTitle["axisTitle"] = yTitle + axisYTitle["axisClass"] = "ct-axis-title" + val titleOffset: dynamic = {} + titleOffset["x"] = 15 + titleOffset["y"] = 15 + axisYTitle["offset"] = titleOffset + axisYTitle["textAnchor"] = "middle" + axisYTitle["flipTitle"] = true + titleObject["axisY"] = axisYTitle + chartOptions["plugins"] = arrayOf(Chartist.plugins.legend(legendObject), Chartist.plugins.ctAxisTitle(titleObject)) return chartOptions } @@ -168,9 +180,7 @@ fun main(args: Array) { val parsedParameter = it.split("=", limit = 2) if (parsedParameter.size == 2) { val (key, value) = parsedParameter - if (parameters.containsKey(key)) { - parameters[key] = value - } + parameters[key] = value } } @@ -251,23 +261,31 @@ fun main(args: Array) { } separateValues(it.executionTime, executionTime) { value -> value.toDouble() } separateValues(it.compileTime, compileTime) { value -> value.toDouble() } - separateValues(it.codeSize, codeSize) { value -> value.toDouble() } - bundleSize.add(it.bundleSize?.toInt()) + separateValues(it.codeSize, codeSize) { value -> value.toDouble() / 1024.0 } + bundleSize.add(it.bundleSize?.toInt()?. let { it / 1024 / 1024 }) } // Draw charts. val execChart = Chartist.Line("#exec_chart", getChartData(labels, executionTime.values), - getChartOptions(executionTime.keys.toTypedArray())) + getChartOptions(executionTime.keys.toTypedArray(), "Time, microseconds")) val compileChart = Chartist.Line("#compile_chart", getChartData(labels, compileTime.values), - getChartOptions(compileTime.keys.toTypedArray())) + getChartOptions(compileTime.keys.toTypedArray(), "Time, microseconds")) val codeSizeChart = Chartist.Line("#codesize_chart", getChartData(labels, codeSize.values), - getChartOptions(codeSize.keys.toTypedArray())) + getChartOptions(codeSize.keys.toTypedArray(), "Size, KB")) val bundleSizeChart = Chartist.Line("#bundlesize_chart", getChartData(labels, listOf(bundleSize)), - getChartOptions(arrayOf("Bundle size"))) + getChartOptions(arrayOf("Bundle size"), "Size, MB")) // 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) + + // Auto reload. + parameters["refresh"]?.let { + // Set event. + window.setInterval({ + window.location.reload() + }, it.toInt() * 1000) + } } \ No newline at end of file