Change axises and delete action in API (#2792)

This commit is contained in:
LepilkinaElena
2019-03-20 19:31:01 +03:00
committed by GitHub
parent 817732bb69
commit 553e2fdda7
7 changed files with 141 additions and 79 deletions
+1 -1
View File
@@ -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) {
@@ -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",
@@ -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<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"),
elementToString(getRequiredField("webUrl"), "webUrl")
)
}
}
} ?: listOf<Commit>()
}
}
/**
* 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)
+64 -1
View File
@@ -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<String>.runCommand(workingDir: File = File("."),
@@ -23,4 +27,63 @@ fun Array<String>.runCommand(workingDir: File = File("."),
} catch (e: IOException) {
error("Couldn't run command $this")
}
}
}
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<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"),
elementToString(getRequiredField("webUrl"), "webUrl")
)
}
}
} ?: listOf<Commit>()
}
}
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()
}
@@ -64,6 +64,28 @@ object LocalCache {
fun buildExists(target: String, buildNumber: String) =
buildsInfo[target][buildNumber]?.let { true } ?: false
fun delete(target: String, builds: Iterable<String>, 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<String> = 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")
+1
View File
@@ -94,6 +94,7 @@
<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="https://cdn.jsdelivr.net/npm/chartist-plugin-axistitle@0.0.4/dist/chartist-plugin-axistitle.min.js"></script>
<script src="lib/kotlin/kotlin.js"></script>
<script src="js/main.js"></script>
</body>
@@ -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<String>, valuesList: Collection<List<*>>): dynamic
return chartData
}
fun getChartOptions(samples: Array<String>): dynamic {
fun getChartOptions(samples: Array<String>, yTitle: String): dynamic {
val chartOptions: dynamic = object{}
chartOptions["fullWidth"] = true
val paddingObject: dynamic = object{}
@@ -83,11 +84,22 @@ fun getChartOptions(samples: Array<String>): 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<String>) {
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<String>) {
}
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)
}
}