Deployment fixes for performance monitoring system (#4356)

This commit is contained in:
LepilkinaElena
2020-09-07 14:16:34 +03:00
committed by GitHub
parent 257590971c
commit 9b727bdd9d
13 changed files with 244 additions and 172 deletions
@@ -159,7 +159,9 @@ fun Array<String>.runCommand(workingDir: File = File("."),
waitFor(timeoutAmount, timeoutUnit) waitFor(timeoutAmount, timeoutUnit)
}.inputStream.bufferedReader().readText() }.inputStream.bufferedReader().readText()
} catch (e: Exception) { } catch (e: Exception) {
error("Couldn't run command $this") println("Couldn't run command ${this.joinToString(" ")}")
println(e.stackTrace.joinToString("\n"))
error(e.message!!)
} }
} }
+27 -4
View File
@@ -81,16 +81,27 @@ task slackSummary(type: RegressionsSummaryReporter) {
'Windows': "${rootBuildDirectory}/targetsResults/Windows 10.txt".toString()] 'Windows': "${rootBuildDirectory}/targetsResults/Windows 10.txt".toString()]
} }
private def uploadBenchmarkResultToArtifactory(String fileName) { private String getUploadedFile(String fileName) {
def teamcityConfig = System.getenv("TEAMCITY_BUILD_PROPERTIES_FILE") def teamcityConfig = System.getenv("TEAMCITY_BUILD_PROPERTIES_FILE")
if (teamcityConfig) { if (teamcityConfig) {
def buildProperties = new Properties() def buildProperties = new Properties()
buildProperties.load(new FileInputStream(teamcityConfig)) buildProperties.load(new FileInputStream(teamcityConfig))
def password = buildProperties.getProperty("artifactory.apikey")
def buildNumber = buildProperties.getProperty("build.number") def buildNumber = buildProperties.getProperty("build.number")
def target = System.getProperty("os.name").replaceAll("\\s", "") def target = System.getProperty("os.name").replaceAll("\\s", "")
return "${target}/${buildNumber}/${fileName}"
}
return null
}
private def uploadBenchmarkResultToArtifactory(String fileName) {
def teamcityConfig = System.getenv("TEAMCITY_BUILD_PROPERTIES_FILE")
if (teamcityConfig) {
def uploadedFile = getUploadedFile(fileName)
def buildProperties = new Properties()
buildProperties.load(new FileInputStream(teamcityConfig))
def password = buildProperties.getProperty("artifactory.apikey")
MPPTools.uploadFileToArtifactory("${artifactoryUrl}", "${artifactoryRepo}", MPPTools.uploadFileToArtifactory("${artifactoryUrl}", "${artifactoryRepo}",
"${target}/${buildNumber}/${fileName}", "${buildDir.absolutePath}/${fileName}", password) uploadedFile, "${buildDir.absolutePath}/${fileName}", password)
} }
} }
@@ -161,6 +172,11 @@ task registerExternalBenchmarks {
task registerBuild(type: BuildRegister) { task registerBuild(type: BuildRegister) {
onlyBranch = project.findProperty('kotlin.register.branch') onlyBranch = project.findProperty('kotlin.register.branch')
def uploadedFile = getUploadedFile(nativeJson)
if (uploadedFile != null) {
println("Use file from Artifacrory $uploadedFile")
fileWithResult = uploadedFile
}
// Get bundle size. // Get bundle size.
bundleSize = null bundleSize = null
if (project.findProperty('kotlin.bundleBuild') != null) { if (project.findProperty('kotlin.bundleBuild') != null) {
@@ -172,7 +188,14 @@ task registerBuild(type: BuildRegister) {
task registerExternalBuild(type: BuildRegister) { task registerExternalBuild(type: BuildRegister) {
onlyBranch = project.findProperty('kotlin.register.branch') onlyBranch = project.findProperty('kotlin.register.branch')
fileWithResult = externalBenchmarksReport def uploadedFile = getUploadedFile(externalBenchmarksReport)
if (uploadedFile != null) {
println("Use file from Artifacrory $uploadedFile")
fileWithResult = uploadedFile
} else {
fileWithResult = externalBenchmarksReport
}
} }
registerExternalBenchmarks.finalizedBy registerExternalBuild registerExternalBenchmarks.finalizedBy registerExternalBuild
+11
View File
@@ -154,6 +154,17 @@ kotlin {
} }
} }
} }
js {
browser {
distribution {
directory = new File("$projectDir/web/")
}
dceTask {
keep 'benchmarksAnalyzer.main_kand9s$'
}
}
}
} }
def getMingwPath() { def getMingwPath() {
+3 -3
View File
@@ -292,9 +292,9 @@
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
}, },
"kotlin": { "kotlin": {
"version": "1.4.0-M3", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/kotlin/-/kotlin-1.4.0-M3.tgz", "resolved": "https://registry.npmjs.org/kotlin/-/kotlin-1.4.0.tgz",
"integrity": "sha512-y6dl1t36tI1hAJlJTxlOB/fj/0iOga+XC8+eg9hUSTu5BDJ9MIUR7XnA8Ld+tQA9IakAPaUmgZZfX2lsHaAdIw==" "integrity": "sha512-q+Ts9Xr72eT3QkmLjHDObPAHbsKbZ/Vn0ozqrNNr7UbtpLxa26+Hn8ILORPLz1UIUTespZyfXztXJ7AO5Xl/Gg=="
}, },
"media-typer": { "media-typer": {
"version": "0.3.0", "version": "0.3.0",
+1 -1
View File
@@ -11,7 +11,7 @@
"debug": "~4.1.1", "debug": "~4.1.1",
"ejs": "~2.6.1", "ejs": "~2.6.1",
"express": "~4.16.4", "express": "~4.16.4",
"kotlin": "~1.4.0-M3", "kotlin": "~1.4.0",
"node-fetch": "~2.6.0" "node-fetch": "~2.6.0"
} }
} }
@@ -125,14 +125,11 @@ enum class ElasticSearchType {
} }
abstract class ElasticSearchIndex(val indexName: String, val connector: ElasticSearchConnector) { abstract class ElasticSearchIndex(val indexName: String, val connector: ElasticSearchConnector) {
var nextId = 0L
// Insert data. // Insert data.
fun insert(data: JsonSerializable): Promise<String> { fun insert(data: JsonSerializable): Promise<String> {
val description = data.toJson() val description = data.toJson()
val writePath = "$indexName/_doc/$nextId?pretty" val writePath = "$indexName/_doc/"
nextId++ return connector.request(RequestMethod.POST, writePath, body = description)
return connector.request(RequestMethod.PUT, writePath, body = description)
} }
// Delete data. // Delete data.
@@ -148,25 +145,6 @@ abstract class ElasticSearchIndex(val indexName: String, val connector: ElasticS
return connector.request(RequestMethod.POST, path, body = requestJson) return connector.request(RequestMethod.POST, path, body = requestJson)
} }
init {
// Get latest id for index.
val queryBody = """{
"_source": ["_id"],
"size": 1,
"query": {
"match_all": {}
}
}"""
search(queryBody, listOf("hits.total.value")).then { responseString ->
val response = JsonTreeParser.parse(responseString).jsonObject
val value = response.getObjectOrNull("hits")?.getObjectOrNull("total")?.getPrimitiveOrNull("value")?.content
?: error("Error response from ElasticSearch:\n$responseString")
nextId = value.toLong()
}.catch { errorMessage ->
error(errorMessage.message ?: "Failed getting next id for index $indexName")
}
}
abstract val mapping: Map<String, ElasticSearchType> abstract val mapping: Map<String, ElasticSearchType>
val mappingDescription: String val mappingDescription: String
@@ -35,6 +35,7 @@ class UrlNetworkConnector(private val host: String, private val port: Int? = nul
) )
).then { response -> ).then { response ->
if (!response.ok) { if (!response.ok) {
println(JSON.stringify(response))
errorHandler(fullUrl, response) errorHandler(fullUrl, response)
} else { } else {
response.text() response.text()
@@ -97,13 +97,13 @@ class BenchmarksIndexesDispatcher(connector: ElasticSearchConnector, val feature
} }
// Get benchmarks values of needed metric for choosen build number. // Get benchmarks values of needed metric for choosen build number.
fun getSamples(metricName: String, featureValue: String = "", samples: List<String>, fun getSamples(metricName: String, featureValue: String = "", samples: List<String>, buildsCountToShow: Int,
buildNumbers: Iterable<String>? = null, buildNumbers: Iterable<String>? = null,
normalize: Boolean = false): Promise<List<Pair<String, Array<Double?>>>> { normalize: Boolean = false): Promise<List<Pair<String, Array<Double?>>>> {
val queryDescription = """ val queryDescription = """
{ {
"_source": ["buildNumber"], "_source": ["buildNumber"],
"size": 1000, "size": ${samples.size * buildsCountToShow},
"query": { "query": {
"bool": { "bool": {
"must": [ "must": [
@@ -255,7 +255,7 @@ class BenchmarksIndexesDispatcher(connector: ElasticSearchConnector, val feature
// Get geometric mean for benchmarks values of needed metric. // Get geometric mean for benchmarks values of needed metric.
fun getGeometricMean(metricName: String, featureValue: String = "", fun getGeometricMean(metricName: String, featureValue: String = "",
buildNumbers: Iterable<String>? = null, normalize: Boolean = false, buildNumbers: Iterable<String>? = null, normalize: Boolean = false,
excludeNames: List<String> = emptyList()): Promise<List<Pair<String, List<Double>>>> { excludeNames: List<String> = emptyList()): Promise<List<Pair<String, List<Double?>>>> {
// Filter only with metric or also with names. // Filter only with metric or also with names.
val filterBenchmarks = if (excludeNames.isEmpty()) val filterBenchmarks = if (excludeNames.isEmpty())
""" """
@@ -345,9 +345,9 @@ class BenchmarksIndexesDispatcher(connector: ElasticSearchConnector, val feature
.getObject("metric_samples") .getObject("metric_samples")
.getObject("buckets") .getObject("buckets")
.getObject("samples") .getObject("samples")
.getObject("geom_mean") .getObjectOrNull("geom_mean")
.getPrimitive("value") ?.getPrimitive("value")
.double ?.double
) )
} }
} ?: listOf("golden" to listOf(aggregations } ?: listOf("golden" to listOf(aggregations
@@ -355,9 +355,9 @@ class BenchmarksIndexesDispatcher(connector: ElasticSearchConnector, val feature
.getObject("metric_samples") .getObject("metric_samples")
.getObject("buckets") .getObject("buckets")
.getObject("samples") .getObject("samples")
.getObject("geom_mean") .getObjectOrNull("geom_mean")
.getPrimitive("value") ?.getPrimitive("value")
.double ?.double
) )
) )
} }
@@ -35,11 +35,12 @@ internal fun deleteBuildInfo(agentInfo: String, buildInfoIndex: ElasticSearchInd
// Get infromation about builds details from database. // Get infromation about builds details from database.
internal fun getBuildsDescription(type: String?, branch: String?, agentInfo: String, buildInfoIndex: ElasticSearchIndex, internal fun getBuildsDescription(type: String?, branch: String?, agentInfo: String, buildInfoIndex: ElasticSearchIndex,
buildsCountToShow: Int, beforeDate: String?, afterDate: String?,
onlyNumbers: Boolean = false): Promise<JsonArray> { onlyNumbers: Boolean = false): Promise<JsonArray> {
val queryDescription = """ val queryDescription = """
{ "size": 10000, { "size": $buildsCountToShow,
${if (onlyNumbers) """"_source": ["buildNumber"],""" else ""} ${if (onlyNumbers) """"_source": ["buildNumber"],""" else ""}
"sort": {"_id": "desc" }, "sort": {"startTime": "desc" },
"query": { "query": {
"bool": { "bool": {
"must": [ "must": [
@@ -51,6 +52,16 @@ internal fun getBuildsDescription(type: String?, branch: String?, agentInfo: Str
} }
""" """
} ?: ""} } ?: ""}
${beforeDate?.let {
""",
{ "range": { "startTime": { "lt": "$it" } } }
"""
} ?: ""}
${afterDate?.let {
""",
{ "range": { "startTime": { "gt": "$it" } } }
"""
} ?: ""}
${branch?.let { ${branch?.let {
""", """,
{"match": { "branch": "$it" }} {"match": { "branch": "$it" }}
@@ -61,6 +72,7 @@ internal fun getBuildsDescription(type: String?, branch: String?, agentInfo: Str
} }
} }
""".trimIndent() """.trimIndent()
return buildInfoIndex.search(queryDescription, listOf("hits.hits._source")).then { responseString -> return buildInfoIndex.search(queryDescription, listOf("hits.hits._source")).then { responseString ->
val dbResponse = JsonTreeParser.parse(responseString).jsonObject val dbResponse = JsonTreeParser.parse(responseString).jsonObject
dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits") ?: error("Wrong response:\n$responseString") dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits") ?: error("Wrong response:\n$responseString")
@@ -93,14 +105,18 @@ suspend fun buildExists(buildInfo: BuildInfo, buildInfoIndex: ElasticSearchIndex
} }
// Get builds numbers corresponding to machine and branch. // Get builds numbers corresponding to machine and branch.
fun getBuildsNumbers(type: String?, branch: String?, agentInfo: String, buildInfoIndex: ElasticSearchIndex) = fun getBuildsNumbers(type: String?, branch: String?, agentInfo: String, buildsCountToShow: Int,
getBuildsDescription(type, branch, agentInfo, buildInfoIndex, true).then { responseArray -> buildInfoIndex: ElasticSearchIndex, beforeDate: String? = null, afterDate: String? = null) =
getBuildsDescription(type, branch, agentInfo, buildInfoIndex, buildsCountToShow, beforeDate, afterDate, true)
.then { responseArray ->
responseArray.map { (it as JsonObject).getObject("_source").getPrimitive("buildNumber").content } responseArray.map { (it as JsonObject).getObject("_source").getPrimitive("buildNumber").content }
} }
// Get full builds information corresponding to machine and branch. // Get full builds information corresponding to machine and branch.
fun getBuildsInfo(type: String?, branch: String?, agentInfo: String, buildInfoIndex: ElasticSearchIndex) = fun getBuildsInfo(type: String?, branch: String?, agentInfo: String, buildsCountToShow: Int,
getBuildsDescription(type, branch, agentInfo, buildInfoIndex).then { responseArray -> buildInfoIndex: ElasticSearchIndex, beforeDate: String? = null,
afterDate: String? = null) =
getBuildsDescription(type, branch, agentInfo, buildInfoIndex, buildsCountToShow, beforeDate, afterDate).then { responseArray ->
responseArray.map { BuildInfo.create((it as JsonObject).getObject("_source")) } responseArray.map { BuildInfo.create((it as JsonObject).getObject("_source")) }
} }
@@ -17,6 +17,8 @@ object CachableResponseDispatcher {
// Storage of cached responses. // Storage of cached responses.
private val cachedResponses = mutableMapOf<String, CachedResponse>() private val cachedResponses = mutableMapOf<String, CachedResponse>()
private val cacheMaxSize = 200
// Get response. If response isn't cached, use provided action to get response. // Get response. If response isn't cached, use provided action to get response.
fun getResponse(request: dynamic, response: dynamic, fun getResponse(request: dynamic, response: dynamic,
action: (success: (result: Any) -> Unit, reject: () -> Unit) -> Unit) { action: (success: (result: Any) -> Unit, reject: () -> Unit) -> Unit) {
@@ -31,7 +33,9 @@ object CachableResponseDispatcher {
response.json(it.cachedResult) response.json(it.cachedResult)
} ?: run { } ?: run {
action({ result: Any -> action({ result: Any ->
cachedResponses[request.url] = CachedResponse(result, TimeSource.Monotonic.markNow()) if (cachedResponses.size >= cacheMaxSize) {
cachedResponses[request.url] = CachedResponse(result, TimeSource.Monotonic.markNow())
}
response.json(result) response.json(result)
}, { response.sendStatus(400) }) }, { response.sendStatus(400) })
} }
@@ -39,13 +39,19 @@ external object AWSInstance {
val handleRequest: dynamic val handleRequest: dynamic
} }
class SharedIniFileCredentials(options: Map<String, String>) { interface Credentials
class SharedIniFileCredentials(options: Map<String, String>): Credentials {
val accessKeyId: String
}
class EnvironmentCredentials(envPrefix: String): Credentials {
val accessKeyId: String val accessKeyId: String
} }
class Signers() { class Signers() {
class V4(request: HttpRequest, subsystem: String) { class V4(request: HttpRequest, subsystem: String) {
fun addAuthorization(credentials: SharedIniFileCredentials, date: Date) fun addAuthorization(credentials: Credentials, date: Date)
} }
} }
} }
@@ -58,6 +64,7 @@ class AWSNetworkConnector : NetworkConnector() {
override fun <T : String?> sendBaseRequest(method: RequestMethod, path: String, user: String?, password: String?, override fun <T : String?> sendBaseRequest(method: RequestMethod, path: String, user: String?, password: String?,
acceptJsonContentType: Boolean, body: String?, acceptJsonContentType: Boolean, body: String?,
errorHandler: (url: String, response: dynamic) -> Nothing?): Promise<T> { errorHandler: (url: String, response: dynamic) -> Nothing?): Promise<T> {
val useEnvironmentCredentials = true // For easy test on localhost change to false.
val AWSEndpoint = AWSInstance.Endpoint(AWSDomain) val AWSEndpoint = AWSInstance.Endpoint(AWSDomain)
var request = AWSInstance.HttpRequest(AWSEndpoint, AWSRegion) var request = AWSInstance.HttpRequest(AWSEndpoint, AWSRegion)
request.method = method.toString() request.method = method.toString()
@@ -71,7 +78,8 @@ class AWSNetworkConnector : NetworkConnector() {
request.headers["Content-Length"] = js("Buffer").byteLength(request.body) request.headers["Content-Length"] = js("Buffer").byteLength(request.body)
} }
val credentials = AWSInstance.SharedIniFileCredentials(mapOf<String, String>()) val credentials = if (useEnvironmentCredentials) AWSInstance.EnvironmentCredentials("AWS")
else AWSInstance.SharedIniFileCredentials(mapOf<String, String>())
val signer = AWSInstance.Signers.V4(request, "es") val signer = AWSInstance.Signers.V4(request, "es")
signer.addAuthorization(credentials, Date()) signer.addAuthorization(credentials, Date())
@@ -25,6 +25,8 @@ operator fun <K, V> Map<K, V>?.get(key: K) = this?.get(key)
fun getArtifactoryHeader(artifactoryApiKey: String) = Pair("X-JFrog-Art-Api", artifactoryApiKey) fun getArtifactoryHeader(artifactoryApiKey: String) = Pair("X-JFrog-Art-Api", artifactoryApiKey)
external fun decodeURIComponent(url: String): String
// Convert saved old report to expected new format. // Convert saved old report to expected new format.
internal fun convertToNewFormat(data: JsonObject): List<Any> { internal fun convertToNewFormat(data: JsonObject): List<Any> {
val env = Environment.create(data.getRequiredField("env")) val env = Environment.create(data.getRequiredField("env"))
@@ -183,7 +185,7 @@ fun router() {
val goldenIndex = GoldenResultsIndex(connector) val goldenIndex = GoldenResultsIndex(connector)
val buildInfoIndex = BuildInfoIndex(connector) val buildInfoIndex = BuildInfoIndex(connector)
router.get("/createMapping") { request, response -> router.get("/createMapping") { _, response ->
buildInfoIndex.createMapping().then { _ -> buildInfoIndex.createMapping().then { _ ->
response.sendStatus(200) response.sendStatus(200)
}.catch { _ -> }.catch { _ ->
@@ -220,7 +222,10 @@ fun router() {
register.sendTeamCityRequest(register.changesListUrl, true).then { changes -> register.sendTeamCityRequest(register.changesListUrl, true).then { changes ->
val commitsList = CommitsList(JsonTreeParser.parse(changes)) val commitsList = CommitsList(JsonTreeParser.parse(changes))
// Get artifact. // Get artifact.
register.sendTeamCityRequest(register.teamCityArtifactsUrl).then { resultsContent -> val content = if(register.fileWithResult.contains("/"))
UrlNetworkConnector(artifactoryUrl).sendRequest(RequestMethod.GET, register.fileWithResult)
else register.sendTeamCityRequest(register.teamCityArtifactsUrl)
content.then { resultsContent ->
launch { launch {
val reportData = JsonTreeParser.parse(resultsContent) val reportData = JsonTreeParser.parse(resultsContent)
val reports = if (reportData is JsonArray) { val reports = if (reportData is JsonArray) {
@@ -292,6 +297,9 @@ fun router() {
var branch: String? = null var branch: String? = null
var type: String? = null var type: String? = null
var buildsCountToShow = 200
var beforeDate: String? = null
var afterDate: String? = null
if (request.query != undefined) { if (request.query != undefined) {
if (request.query.branch != undefined) { if (request.query.branch != undefined) {
branch = request.query.branch branch = request.query.branch
@@ -299,9 +307,19 @@ fun router() {
if (request.query.type != undefined) { if (request.query.type != undefined) {
type = request.query.type type = request.query.type
} }
if (request.query.count != undefined) {
buildsCountToShow = request.query.count.toString().toInt()
}
if (request.query.before != undefined) {
beforeDate = decodeURIComponent(request.query.before)
}
if (request.query.after != undefined) {
afterDate = decodeURIComponent(request.query.after)
}
} }
getBuildsInfo(type, branch, target, buildInfoIndex).then { buildsInfo -> getBuildsInfo(type, branch, target, buildsCountToShow, buildInfoIndex, beforeDate, afterDate)
.then { buildsInfo ->
val buildNumbers = buildsInfo.map { it.buildNumber } val buildNumbers = buildsInfo.map { it.buildNumber }
// Get number of failed benchmarks for each build. // Get number of failed benchmarks for each build.
benchmarksDispatcher.getFailuresNumber(target, buildNumbers).then { failures -> benchmarksDispatcher.getFailuresNumber(target, buildNumbers).then { failures ->
@@ -331,6 +349,9 @@ fun router() {
var branch: String? = null var branch: String? = null
var type: String? = null var type: String? = null
var excludeNames: List<String> = emptyList() var excludeNames: List<String> = emptyList()
var buildsCountToShow = 200
var beforeDate: String? = null
var afterDate: String? = null
// Parse parameters from request if it exists. // Parse parameters from request if it exists.
if (request.query != undefined) { if (request.query != undefined) {
@@ -352,9 +373,18 @@ fun router() {
if (request.query.exclude != undefined) { if (request.query.exclude != undefined) {
excludeNames = request.query.exclude.toString().split(",").map { it.trim() } excludeNames = request.query.exclude.toString().split(",").map { it.trim() }
} }
if (request.query.count != undefined) {
buildsCountToShow = request.query.count.toString().toInt()
}
if (request.query.before != undefined) {
beforeDate = decodeURIComponent(request.query.before)
}
if (request.query.after != undefined) {
afterDate = decodeURIComponent(request.query.after)
}
} }
getBuildsNumbers(type, branch, target, buildInfoIndex).then { buildNumbers -> getBuildsNumbers(type, branch, target, buildsCountToShow, buildInfoIndex, beforeDate, afterDate).then { buildNumbers ->
if (aggregation == "geomean") { if (aggregation == "geomean") {
// Get geometric mean for samples. // Get geometric mean for samples.
benchmarksDispatcher.getGeometricMean(metric, target, buildNumbers, normalize, benchmarksDispatcher.getGeometricMean(metric, target, buildNumbers, normalize,
@@ -366,7 +396,8 @@ fun router() {
reject() reject()
} }
} else { } else {
benchmarksDispatcher.getSamples(metric, target, samples, buildNumbers, normalize).then { geoMeansValues -> benchmarksDispatcher.getSamples(metric, target, samples, buildsCountToShow, buildNumbers, normalize)
.then { geoMeansValues ->
success(orderedValues(geoMeansValues, { it -> it.first }, branch == "master")) success(orderedValues(geoMeansValues, { it -> it.first }, branch == "master"))
}.catch { }.catch {
println("Error during getting samples") println("Error during getting samples")
@@ -412,6 +443,7 @@ fun router() {
if (request.query != undefined) { if (request.query != undefined) {
if (request.query.buildNumber != undefined) { if (request.query.buildNumber != undefined) {
buildNumber = request.query.buildNumber buildNumber = request.query.buildNumber
buildNumber = request.query.buildNumber
} }
} }
getBuildsInfoFromArtifactory(targetPathName).then { buildInfo -> getBuildsInfoFromArtifactory(targetPathName).then { buildInfo ->
@@ -448,10 +480,13 @@ fun router() {
val buildInfoRecord = BuildInfo(currentBuildNumber, infoParts[1], infoParts[2], val buildInfoRecord = BuildInfo(currentBuildNumber, infoParts[1], infoParts[2],
CommitsList.parse(infoParts[4]), infoParts[3], target) CommitsList.parse(infoParts[4]), infoParts[3], target)
val externalJsonReport = artifactoryUrlConnector.sendOptionalRequest(RequestMethod.GET, accessExternalFileUrl).await() val externalJsonReport = artifactoryUrlConnector.sendOptionalRequest(RequestMethod.GET, accessExternalFileUrl)
.await()
buildInfoIndex.insert(buildInfoRecord).then { _ -> buildInfoIndex.insert(buildInfoRecord).then { _ ->
println("[BUILD INFO] Success insert build number ${buildInfoRecord.buildNumber}")
externalJsonReport?.let { externalJsonReport?.let {
var externalReports = convert(externalJsonReport, currentBuildNumber, target) var externalReports = convert(externalJsonReport.replace("circlet_iosX64", "SpaceFramework_iosX64"),
currentBuildNumber, target)
externalReports.forEach { externalReport -> externalReports.forEach { externalReport ->
val extrenalAdditionalReport = SummaryBenchmarksReport(externalReport) val extrenalAdditionalReport = SummaryBenchmarksReport(externalReport)
.getBenchmarksReport().normalizeBenchmarksSet(goldenResults) .getBenchmarksReport().normalizeBenchmarksSet(goldenResults)
@@ -35,36 +35,12 @@ fun sendGetRequest(url: String) = window.fetch(url, RequestInit("GET")).then { r
response.text() response.text()
}.then { text -> text } }.then { text -> text }
// Get groups of builds for different zoom values.
fun getBuildsGroup(builds: List<Build?>) = buildsNumberToShow?.let {
val buildsGroups = builds.chunked(buildsNumberToShow!!)
val expectedGroup = buildsGroups.size - 1 + stageToShow
val index = when {
expectedGroup < 0 -> 0
expectedGroup >= buildsGroups.size -> buildsGroups.size - 1
else -> expectedGroup
}
buildsGroups[index]
} ?: builds
// Get data for chart in needed format. // Get data for chart in needed format.
fun getChartData(labels: List<String>, valuesList: Collection<List<*>>, stageToShow: Int = 0, fun getChartData(labels: List<String>, valuesList: Collection<List<*>>,
buildsNumber: Int? = null, classNames: Array<String>? = null): dynamic { classNames: Array<String>? = null): dynamic {
val chartData: dynamic = object {} val chartData: dynamic = object {}
// Show only some part of data. chartData["labels"] = labels.toTypedArray()
val (labelsData, valuesData) = buildsNumber?.let { chartData["series"] = valuesList.mapIndexed { index, it ->
val labelsGroups = labels.chunked(buildsNumber)
val valuesListGroups = valuesList.map { it.chunked(buildsNumber) }
val expectedGroup = labelsGroups.size - 1 + stageToShow
val index = when {
expectedGroup < 0 -> 0
expectedGroup >= labelsGroups.size -> labelsGroups.size - 1
else -> expectedGroup
}
Pair(labelsGroups[index], valuesListGroups.map { it[index] })
} ?: Pair(labels, valuesList)
chartData["labels"] = labelsData.toTypedArray()
chartData["series"] = valuesData.mapIndexed { index, it ->
val series: dynamic = object {} val series: dynamic = object {}
series["data"] = it.toTypedArray() series["data"] = it.toTypedArray()
classNames?.let { series["className"] = classNames[index] } classNames?.let { series["className"] = classNames[index] }
@@ -83,7 +59,7 @@ fun getChartOptions(samples: Array<String>, yTitle: String, classNames: Array<St
val axisXObject: dynamic = object {} val axisXObject: dynamic = object {}
axisXObject["offset"] = 40 axisXObject["offset"] = 40
axisXObject["labelInterpolationFnc"] = { value, index, labels -> axisXObject["labelInterpolationFnc"] = { value, index, labels ->
val labelsCount = 30 val labelsCount = 20
val skipNumber = ceil((labels.length as Int).toDouble() / labelsCount).toInt() val skipNumber = ceil((labels.length as Int).toDouble() / labelsCount).toInt()
if (skipNumber > 1) { if (skipNumber > 1) {
if (index % skipNumber == 0) value else null if (index % skipNumber == 0) value else null
@@ -126,9 +102,8 @@ fun customizeChart(chart: dynamic, chartContainer: String, jquerySelector: dynam
chart.on("draw", { data -> chart.on("draw", { data ->
var element = data.element var element = data.element
if (data.type == "point") { if (data.type == "point") {
val buildsGroup = getBuildsGroup(builds)
val pointSize = 12 val pointSize = 12
val currentBuild = buildsGroup.get(data.index) val currentBuild = builds.get(data.index)
currentBuild?.let { currentBuild -> currentBuild?.let { currentBuild ->
// Higlight builds with failures. // Higlight builds with failures.
if (currentBuild.failuresNumber > 0) { if (currentBuild.failuresNumber > 0) {
@@ -154,7 +129,7 @@ fun customizeChart(chart: dynamic, chartContainer: String, jquerySelector: dynam
var shift = 1 var shift = 1
var previousBuild: Build? = null var previousBuild: Build? = null
while (previousBuild == null && data.index - shift >= 0) { while (previousBuild == null && data.index - shift >= 0) {
previousBuild = buildsGroup.get(data.index - shift) previousBuild = builds.get(data.index - shift)
shift++ shift++
} }
val linkToDetailedInfo = "https://kotlin-native-performance.labs.jb.gg/?report=" + val linkToDetailedInfo = "https://kotlin-native-performance.labs.jb.gg/?report=" +
@@ -204,21 +179,19 @@ fun customizeChart(chart: dynamic, chartContainer: String, jquerySelector: dynam
}) })
} }
var stageToShow = 0 var buildsNumberToShow: Int = 200
var beforeDate: String? = null
var afterDate: String? = null
var buildsNumberToShow: Int? = null external fun decodeURIComponent(url: String): String
external fun encodeURIComponent(url: String): String
fun waitForElements(elements: Iterable<Any?>, action: () -> Unit) { fun getDatesComponents() = "${beforeDate?.let {"&before=${encodeURIComponent(it)}"} ?: ""}" +
if (elements.filterNotNull().size == elements.count()) "${afterDate?.let {"&after=${encodeURIComponent(it)}"} ?: ""}"
action()
else window.setTimeout(waitForElements(elements, action), 500)
}
fun main(args: Array<String>) { fun main(args: Array<String>) {
val serverUrl = "https://kotlin-native-perf-summary.labs.jb.gg" val serverUrl = "https://kotlin-native-perf-summary.labs.jb.gg"
buildsNumberToShow = null val zoomRatio = 2
stageToShow = 0
val zoomRatio = 3
// Get parameters from request. // Get parameters from request.
val url = window.location.href val url = window.location.href
@@ -232,6 +205,10 @@ fun main(args: Array<String>) {
} }
} }
buildsNumberToShow = parameters["count"]?.toInt() ?: buildsNumberToShow
beforeDate = parameters["before"]?.let{ decodeURIComponent(it)}
afterDate = parameters["after"]?.let{ decodeURIComponent(it)}
// Get branches. // Get branches.
val branchesUrl = "$serverUrl/branches" val branchesUrl = "$serverUrl/branches"
sendGetRequest(branchesUrl).then { response -> sendGetRequest(branchesUrl).then { response ->
@@ -256,7 +233,8 @@ fun main(args: Array<String>) {
autocompleteParameters["onSelect"] = { suggestion -> autocompleteParameters["onSelect"] = { suggestion ->
if (suggestion.value != parameters["build"]) { if (suggestion.value != parameters["build"]) {
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}" + val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}" +
"${if ((suggestion.value as String).isEmpty()) "" else "&build=${suggestion.value}"}" "${if ((suggestion.value as String).isEmpty()) "" else "&build=${suggestion.value}"}&count=$buildsNumberToShow" +
getDatesComponents()
window.location.href = newLink window.location.href = newLink
} }
} }
@@ -265,7 +243,8 @@ fun main(args: Array<String>) {
val newValue = js("$(this).val()").toString() val newValue = js("$(this).val()").toString()
if (newValue.isEmpty() || newValue in buildsNumbers) { if (newValue.isEmpty() || newValue in buildsNumbers) {
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}" + val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}" +
"${if (newValue.isEmpty()) "" else "&build=$newValue"}" "${if (newValue.isEmpty()) "" else "&build=$newValue"}&count=$buildsNumberToShow" +
getDatesComponents()
window.location.href = newLink window.location.href = newLink
} }
}) })
@@ -282,7 +261,7 @@ fun main(args: Array<String>) {
val newValue = js("$(this).val()") val newValue = js("$(this).val()")
if (newValue != parameters["target"]) { if (newValue != parameters["target"]) {
val newLink = "http://${window.location.host}/?target=$newValue&type=${parameters["type"]}&branch=${parameters["branch"]}" + val newLink = "http://${window.location.host}/?target=$newValue&type=${parameters["type"]}&branch=${parameters["branch"]}" +
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}" "${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow"
window.location.href = newLink window.location.href = newLink
} }
}) })
@@ -290,7 +269,7 @@ fun main(args: Array<String>) {
val newValue = js("$(this).val()") val newValue = js("$(this).val()")
if (newValue != parameters["type"]) { if (newValue != parameters["type"]) {
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=$newValue&branch=${parameters["branch"]}" + val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=$newValue&branch=${parameters["branch"]}" +
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}" "${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow"
window.location.href = newLink window.location.href = newLink
} }
}) })
@@ -298,35 +277,35 @@ fun main(args: Array<String>) {
val newValue = js("$(this).val()") val newValue = js("$(this).val()")
if (newValue != parameters["branch"]) { if (newValue != parameters["branch"]) {
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=$newValue" + val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=$newValue" +
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}" "${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow"
window.location.href = newLink window.location.href = newLink
} }
}) })
val platformSpecificBenchs = if (parameters["target"] == "Mac_OS_X") ",FrameworkBenchmarksAnalyzer,circlet_iosX64" else val platformSpecificBenchs = if (parameters["target"] == "Mac_OS_X") ",FrameworkBenchmarksAnalyzer,SpaceFramework_iosX64" else
if (parameters["target"] == "Linux") ",kotlinx.coroutines" else "" if (parameters["target"] == "Linux") ",kotlinx.coroutines" else ""
// Collect information for charts library. // Collect information for charts library.
val valuesToShow = mapOf("EXECUTION_TIME" to arrayOf(mapOf( val valuesToShow = mapOf("EXECUTION_TIME" to listOf(mapOf(
"normalize" to "true" "normalize" to "true"
)), )),
"COMPILE_TIME" to arrayOf(mapOf( "COMPILE_TIME" to listOf(mapOf(
"samples" to "HelloWorld,Videoplayer$platformSpecificBenchs", "samples" to "HelloWorld,Videoplayer$platformSpecificBenchs",
"agr" to "samples" "agr" to "samples"
)), )),
"CODE_SIZE" to arrayOf(mapOf( "CODE_SIZE" to listOf(mapOf(
"normalize" to "true", "normalize" to "true",
"exclude" to if (parameters["target"] == "Linux") "exclude" to if (parameters["target"] == "Linux")
"kotlinx.coroutines" "kotlinx.coroutines"
else if (parameters["target"] == "Mac_OS_X") else if (parameters["target"] == "Mac_OS_X")
"circlet_iosX64" "SpaceFramework_iosX64"
else "" else ""
), mapOf( ), if (platformSpecificBenchs.isNotEmpty()) mapOf(
"normalize" to "true", "normalize" to "true",
"agr" to "samples", "agr" to "samples",
"samples" to platformSpecificBenchs.removePrefix(",") "samples" to platformSpecificBenchs.removePrefix(",")
)), ) else null).filterNotNull(),
"BUNDLE_SIZE" to arrayOf(mapOf("samples" to "KotlinNative", "BUNDLE_SIZE" to listOf(mapOf("samples" to "KotlinNative",
"agr" to "samples")) "agr" to "samples"))
) )
@@ -344,13 +323,27 @@ fun main(args: Array<String>) {
var bundleSizeChart: dynamic = null var bundleSizeChart: dynamic = null
val descriptionUrl = "$serverUrl/buildsDesc/${parameters["target"]}?type=${parameters["type"]}" + val descriptionUrl = "$serverUrl/buildsDesc/${parameters["target"]}?type=${parameters["type"]}" +
"${if (parameters["branch"] != "all") "&branch=${parameters["branch"]}" else ""}" "${if (parameters["branch"] != "all") "&branch=${parameters["branch"]}" else ""}&count=$buildsNumberToShow" +
getDatesComponents()
val metricUrl = "$serverUrl/metricValue/${parameters["target"]}/" val metricUrl = "$serverUrl/metricValue/${parameters["target"]}/"
// Get builds description.
val buildsInfoPromise = sendGetRequest(descriptionUrl).then { response ->
val buildsInfo = response as String
val data = JsonTreeParser.parse(buildsInfo)
if (data !is JsonArray) {
error("Response is expected to be an array.")
}
data.jsonArray.map {
val element = it as JsonElement
if (element.isNull) null else Build.create(element as JsonObject)
}
}
// Send requests to get all needed metric values. // Send requests to get all needed metric values.
valuesToShow.map { (metric, arrayOfSettings) -> valuesToShow.map { (metric, listOfSettings) ->
val resultValues = arrayOfSettings.map { settings -> val resultValues = listOfSettings.map { settings ->
val getParameters = with(StringBuilder()) { val getParameters = with(StringBuilder()) {
if (settings.isNotEmpty()) { if (settings.isNotEmpty()) {
append("?") append("?")
@@ -372,7 +365,7 @@ fun main(args: Array<String>) {
if (parameters["type"] != "all") if (parameters["type"] != "all")
(if (getParameters.isEmpty() && branchParameter.isEmpty()) "?" else "&") + "type=${parameters["type"]}" (if (getParameters.isEmpty() && branchParameter.isEmpty()) "?" else "&") + "type=${parameters["type"]}"
else "" else ""
}" }&count=$buildsNumberToShow${getDatesComponents()}"
sendGetRequest(url) sendGetRequest(url)
}.toTypedArray() }.toTypedArray()
@@ -397,33 +390,47 @@ fun main(args: Array<String>) {
"COMPILE_TIME" -> { "COMPILE_TIME" -> {
compileData = labels to values.map { it.map { it?.let { it / 1000 } } } compileData = labels to values.map { it.map { it?.let { it / 1000 } } }
compileChart = Chartist.Line("#compile_chart", compileChart = Chartist.Line("#compile_chart",
getChartData(labels, compileData.second, stageToShow, buildsNumberToShow), getChartData(labels, compileData.second),
getChartOptions(valuesToShow["COMPILE_TIME"]!![0]!!["samples"]!!.split(',').toTypedArray(), getChartOptions(valuesToShow["COMPILE_TIME"]!![0]!!["samples"]!!.split(',').toTypedArray(),
"Time, milliseconds")) "Time, milliseconds"))
buildsInfoPromise.then { builds ->
customizeChart(compileChart, "compile_chart", js("$(\"#compile_chart\")"), builds, parameters)
compileChart.update(getChartData(compileData.first, compileData.second))
}
} }
"EXECUTION_TIME" -> { "EXECUTION_TIME" -> {
execData = labels to values execData = labels to values
execChart = Chartist.Line("#exec_chart", execChart = Chartist.Line("#exec_chart",
getChartData(labels, execData.second, stageToShow, buildsNumberToShow), getChartData(labels, execData.second),
getChartOptions(arrayOf("Geometric Mean"), "Normalized time")) getChartOptions(arrayOf("Geometric Mean"), "Normalized time"))
buildsInfoPromise.then { builds ->
customizeChart(execChart, "exec_chart", js("$(\"#exec_chart\")"), builds, parameters)
execChart.update(getChartData(execData.first, execData.second))
}
} }
"CODE_SIZE" -> { "CODE_SIZE" -> {
codeSizeData = labels to values codeSizeData = labels to values
codeSizeChart = Chartist.Line("#codesize_chart", codeSizeChart = Chartist.Line("#codesize_chart",
getChartData(labels, codeSizeData.second, getChartData(labels, codeSizeData.second),
stageToShow, buildsNumberToShow, sizeClassNames),
getChartOptions(arrayOf("Geometric Mean") + platformSpecificBenchs.split(',') getChartOptions(arrayOf("Geometric Mean") + platformSpecificBenchs.split(',')
.filter { it.isNotEmpty() }, .filter { it.isNotEmpty() },
"Normalized size", "Normalized size",
arrayOf("ct-series-4", "ct-series-5", "ct-series-6"))) arrayOf("ct-series-4", "ct-series-5", "ct-series-6")))
buildsInfoPromise.then { builds ->
customizeChart(codeSizeChart, "codesize_chart", js("$(\"#codesize_chart\")"), builds, parameters)
codeSizeChart.update(getChartData(codeSizeData.first, codeSizeData.second, sizeClassNames))
}
} }
"BUNDLE_SIZE" -> { "BUNDLE_SIZE" -> {
bundleSizeData = labels to values.map { it.map { it?.let { it.toInt() / 1024 / 1024 } } } bundleSizeData = labels to values.map { it.map { it?.let { it.toInt() / 1024 / 1024 } } }
bundleSizeChart = Chartist.Line("#bundlesize_chart", bundleSizeChart = Chartist.Line("#bundlesize_chart",
getChartData(labels, getChartData(labels,
bundleSizeData.second, stageToShow, bundleSizeData.second, sizeClassNames),
buildsNumberToShow, sizeClassNames),
getChartOptions(arrayOf("Bundle size"), "Size, MB", arrayOf("ct-series-4"))) getChartOptions(arrayOf("Bundle size"), "Size, MB", arrayOf("ct-series-4")))
buildsInfoPromise.then { builds ->
customizeChart(bundleSizeChart, "bundlesize_chart", js("$(\"#bundlesize_chart\")"), builds, parameters)
bundleSizeChart.update(getChartData(bundleSizeData.first, bundleSizeData.second, sizeClassNames))
}
} }
else -> error("No chart for metric $metric") else -> error("No chart for metric $metric")
} }
@@ -433,69 +440,56 @@ fun main(args: Array<String>) {
// Update all charts with using same data. // Update all charts with using same data.
val updateAllCharts: () -> Unit = { val updateAllCharts: () -> Unit = {
execChart.update(getChartData(execData.first, execData.second, stageToShow, buildsNumberToShow)) execChart.update(getChartData(execData.first, execData.second))
compileChart.update(getChartData(compileData.first, compileData.second, stageToShow, buildsNumberToShow)) compileChart.update(getChartData(compileData.first, compileData.second))
codeSizeChart.update(getChartData(codeSizeData.first, codeSizeData.second, stageToShow, buildsNumberToShow, sizeClassNames)) codeSizeChart.update(getChartData(codeSizeData.first, codeSizeData.second, sizeClassNames))
bundleSizeChart.update(getChartData(bundleSizeData.first, bundleSizeData.second, stageToShow, buildsNumberToShow, sizeClassNames)) bundleSizeChart.update(getChartData(bundleSizeData.first, bundleSizeData.second, sizeClassNames))
}
// Get builds description.
sendGetRequest(descriptionUrl).then { response ->
val buildsInfo = response as String
val data = JsonTreeParser.parse(buildsInfo)
if (data !is JsonArray) {
error("Response is expected to be an array.")
}
val builds = data.jsonArray.map {
val element = it as JsonElement
if (element.isNull) null else Build.create(element as JsonObject)
}
waitForElements(listOf(execChart, compileChart, codeSizeChart, bundleSizeChart)) {
customizeChart(execChart, "exec_chart", js("$(\"#exec_chart\")"), builds, parameters)
customizeChart(compileChart, "compile_chart", js("$(\"#compile_chart\")"), builds, parameters)
customizeChart(codeSizeChart, "codesize_chart", js("$(\"#codesize_chart\")"), builds, parameters)
customizeChart(bundleSizeChart, "bundlesize_chart", js("$(\"#bundlesize_chart\")"), builds, parameters)
updateAllCharts()
}
} }
js("$('#plusBtn')").click({ js("$('#plusBtn')").click({
buildsNumberToShow = buildsNumberToShow?.let { buildsNumberToShow =
if (it / zoomRatio > zoomRatio) { if (buildsNumberToShow / zoomRatio > zoomRatio) {
it / zoomRatio buildsNumberToShow / zoomRatio
} else { } else {
it buildsNumberToShow
} }
} ?: execData.first.size / zoomRatio
updateAllCharts() val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=${parameters["branch"]}" +
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow" +
getDatesComponents()
window.location.href = newLink
Unit
}) })
js("$('#minusBtn')").click({ js("$('#minusBtn')").click({
buildsNumberToShow = buildsNumberToShow?.let { buildsNumberToShow = buildsNumberToShow * zoomRatio
if (it * zoomRatio <= execData.first.size) { val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=${parameters["branch"]}" +
it * zoomRatio "${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow" +
} else { getDatesComponents()
null window.location.href = newLink
} Unit
}
updateAllCharts()
}) })
js("$('#prevBtn')").click({ js("$('#prevBtn')").click({
buildsNumberToShow?.let { buildsInfoPromise.then { builds ->
val bottomBorder = -execData.first.size / (buildsNumberToShow as Int) beforeDate = builds.firstOrNull()?.startTime
if (stageToShow - 1 > bottomBorder) { afterDate = null
stageToShow-- val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=${parameters["branch"]}" +
} "${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow" +
} ?: run { stageToShow = 0 } "${beforeDate?.let {"&before=${encodeURIComponent(it)}"} ?: ""}"
updateAllCharts() window.location.href = newLink
}
}) })
js("$('#nextBtn')").click({ js("$('#nextBtn')").click({
if (stageToShow + 1 <= 0) { buildsInfoPromise.then { builds ->
stageToShow++ beforeDate = null
afterDate = builds.lastOrNull()?.startTime
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=${parameters["branch"]}" +
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow" +
"${afterDate?.let {"&after=${encodeURIComponent(it)}"} ?: ""}"
window.location.href = newLink
} }
updateAllCharts()
}) })
// Auto reload. // Auto reload.