Change axises and delete action in API (#2792)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user