Deployment fixes for performance monitoring system (#4356)
This commit is contained in:
@@ -159,7 +159,9 @@ fun Array<String>.runCommand(workingDir: File = File("."),
|
||||
waitFor(timeoutAmount, timeoutUnit)
|
||||
}.inputStream.bufferedReader().readText()
|
||||
} catch (e: Exception) {
|
||||
error("Couldn't run command $this")
|
||||
println("Couldn't run command ${this.joinToString(" ")}")
|
||||
println(e.stackTrace.joinToString("\n"))
|
||||
error(e.message!!)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,16 +81,27 @@ task slackSummary(type: RegressionsSummaryReporter) {
|
||||
'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")
|
||||
if (teamcityConfig) {
|
||||
def buildProperties = new Properties()
|
||||
buildProperties.load(new FileInputStream(teamcityConfig))
|
||||
def password = buildProperties.getProperty("artifactory.apikey")
|
||||
def buildNumber = buildProperties.getProperty("build.number")
|
||||
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}",
|
||||
"${target}/${buildNumber}/${fileName}", "${buildDir.absolutePath}/${fileName}", password)
|
||||
uploadedFile, "${buildDir.absolutePath}/${fileName}", password)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +172,11 @@ task registerExternalBenchmarks {
|
||||
|
||||
task registerBuild(type: BuildRegister) {
|
||||
onlyBranch = project.findProperty('kotlin.register.branch')
|
||||
def uploadedFile = getUploadedFile(nativeJson)
|
||||
if (uploadedFile != null) {
|
||||
println("Use file from Artifacrory $uploadedFile")
|
||||
fileWithResult = uploadedFile
|
||||
}
|
||||
// Get bundle size.
|
||||
bundleSize = null
|
||||
if (project.findProperty('kotlin.bundleBuild') != null) {
|
||||
@@ -172,7 +188,14 @@ task registerBuild(type: BuildRegister) {
|
||||
|
||||
task registerExternalBuild(type: BuildRegister) {
|
||||
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
|
||||
|
||||
@@ -154,6 +154,17 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
js {
|
||||
browser {
|
||||
distribution {
|
||||
directory = new File("$projectDir/web/")
|
||||
}
|
||||
dceTask {
|
||||
keep 'benchmarksAnalyzer.main_kand9s$'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getMingwPath() {
|
||||
|
||||
+3
-3
@@ -292,9 +292,9 @@
|
||||
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
|
||||
},
|
||||
"kotlin": {
|
||||
"version": "1.4.0-M3",
|
||||
"resolved": "https://registry.npmjs.org/kotlin/-/kotlin-1.4.0-M3.tgz",
|
||||
"integrity": "sha512-y6dl1t36tI1hAJlJTxlOB/fj/0iOga+XC8+eg9hUSTu5BDJ9MIUR7XnA8Ld+tQA9IakAPaUmgZZfX2lsHaAdIw=="
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/kotlin/-/kotlin-1.4.0.tgz",
|
||||
"integrity": "sha512-q+Ts9Xr72eT3QkmLjHDObPAHbsKbZ/Vn0ozqrNNr7UbtpLxa26+Hn8ILORPLz1UIUTespZyfXztXJ7AO5Xl/Gg=="
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"debug": "~4.1.1",
|
||||
"ejs": "~2.6.1",
|
||||
"express": "~4.16.4",
|
||||
"kotlin": "~1.4.0-M3",
|
||||
"kotlin": "~1.4.0",
|
||||
"node-fetch": "~2.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-24
@@ -125,14 +125,11 @@ enum class ElasticSearchType {
|
||||
}
|
||||
|
||||
abstract class ElasticSearchIndex(val indexName: String, val connector: ElasticSearchConnector) {
|
||||
var nextId = 0L
|
||||
|
||||
// Insert data.
|
||||
fun insert(data: JsonSerializable): Promise<String> {
|
||||
val description = data.toJson()
|
||||
val writePath = "$indexName/_doc/$nextId?pretty"
|
||||
nextId++
|
||||
return connector.request(RequestMethod.PUT, writePath, body = description)
|
||||
val writePath = "$indexName/_doc/"
|
||||
return connector.request(RequestMethod.POST, writePath, body = description)
|
||||
}
|
||||
|
||||
// Delete data.
|
||||
@@ -148,25 +145,6 @@ abstract class ElasticSearchIndex(val indexName: String, val connector: ElasticS
|
||||
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>
|
||||
|
||||
val mappingDescription: String
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ class UrlNetworkConnector(private val host: String, private val port: Int? = nul
|
||||
)
|
||||
).then { response ->
|
||||
if (!response.ok) {
|
||||
println(JSON.stringify(response))
|
||||
errorHandler(fullUrl, response)
|
||||
} else {
|
||||
response.text()
|
||||
|
||||
@@ -97,13 +97,13 @@ class BenchmarksIndexesDispatcher(connector: ElasticSearchConnector, val feature
|
||||
}
|
||||
|
||||
// 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,
|
||||
normalize: Boolean = false): Promise<List<Pair<String, Array<Double?>>>> {
|
||||
val queryDescription = """
|
||||
{
|
||||
"_source": ["buildNumber"],
|
||||
"size": 1000,
|
||||
"size": ${samples.size * buildsCountToShow},
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
@@ -255,7 +255,7 @@ class BenchmarksIndexesDispatcher(connector: ElasticSearchConnector, val feature
|
||||
// Get geometric mean for benchmarks values of needed metric.
|
||||
fun getGeometricMean(metricName: String, featureValue: String = "",
|
||||
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.
|
||||
val filterBenchmarks = if (excludeNames.isEmpty())
|
||||
"""
|
||||
@@ -345,9 +345,9 @@ class BenchmarksIndexesDispatcher(connector: ElasticSearchConnector, val feature
|
||||
.getObject("metric_samples")
|
||||
.getObject("buckets")
|
||||
.getObject("samples")
|
||||
.getObject("geom_mean")
|
||||
.getPrimitive("value")
|
||||
.double
|
||||
.getObjectOrNull("geom_mean")
|
||||
?.getPrimitive("value")
|
||||
?.double
|
||||
)
|
||||
}
|
||||
} ?: listOf("golden" to listOf(aggregations
|
||||
@@ -355,9 +355,9 @@ class BenchmarksIndexesDispatcher(connector: ElasticSearchConnector, val feature
|
||||
.getObject("metric_samples")
|
||||
.getObject("buckets")
|
||||
.getObject("samples")
|
||||
.getObject("geom_mean")
|
||||
.getPrimitive("value")
|
||||
.double
|
||||
.getObjectOrNull("geom_mean")
|
||||
?.getPrimitive("value")
|
||||
?.double
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,11 +35,12 @@ internal fun deleteBuildInfo(agentInfo: String, buildInfoIndex: ElasticSearchInd
|
||||
|
||||
// Get infromation about builds details from database.
|
||||
internal fun getBuildsDescription(type: String?, branch: String?, agentInfo: String, buildInfoIndex: ElasticSearchIndex,
|
||||
buildsCountToShow: Int, beforeDate: String?, afterDate: String?,
|
||||
onlyNumbers: Boolean = false): Promise<JsonArray> {
|
||||
val queryDescription = """
|
||||
{ "size": 10000,
|
||||
{ "size": $buildsCountToShow,
|
||||
${if (onlyNumbers) """"_source": ["buildNumber"],""" else ""}
|
||||
"sort": {"_id": "desc" },
|
||||
"sort": {"startTime": "desc" },
|
||||
"query": {
|
||||
"bool": {
|
||||
"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 {
|
||||
""",
|
||||
{"match": { "branch": "$it" }}
|
||||
@@ -61,6 +72,7 @@ internal fun getBuildsDescription(type: String?, branch: String?, agentInfo: Str
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
return buildInfoIndex.search(queryDescription, listOf("hits.hits._source")).then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
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.
|
||||
fun getBuildsNumbers(type: String?, branch: String?, agentInfo: String, buildInfoIndex: ElasticSearchIndex) =
|
||||
getBuildsDescription(type, branch, agentInfo, buildInfoIndex, true).then { responseArray ->
|
||||
fun getBuildsNumbers(type: String?, branch: String?, agentInfo: String, buildsCountToShow: Int,
|
||||
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 }
|
||||
}
|
||||
|
||||
// Get full builds information corresponding to machine and branch.
|
||||
fun getBuildsInfo(type: String?, branch: String?, agentInfo: String, buildInfoIndex: ElasticSearchIndex) =
|
||||
getBuildsDescription(type, branch, agentInfo, buildInfoIndex).then { responseArray ->
|
||||
fun getBuildsInfo(type: String?, branch: String?, agentInfo: String, buildsCountToShow: Int,
|
||||
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")) }
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ object CachableResponseDispatcher {
|
||||
// Storage of cached responses.
|
||||
private val cachedResponses = mutableMapOf<String, CachedResponse>()
|
||||
|
||||
private val cacheMaxSize = 200
|
||||
|
||||
// Get response. If response isn't cached, use provided action to get response.
|
||||
fun getResponse(request: dynamic, response: dynamic,
|
||||
action: (success: (result: Any) -> Unit, reject: () -> Unit) -> Unit) {
|
||||
@@ -31,7 +33,9 @@ object CachableResponseDispatcher {
|
||||
response.json(it.cachedResult)
|
||||
} ?: run {
|
||||
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.sendStatus(400) })
|
||||
}
|
||||
|
||||
@@ -39,13 +39,19 @@ external object AWSInstance {
|
||||
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
|
||||
}
|
||||
|
||||
class Signers() {
|
||||
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?,
|
||||
acceptJsonContentType: Boolean, body: String?,
|
||||
errorHandler: (url: String, response: dynamic) -> Nothing?): Promise<T> {
|
||||
val useEnvironmentCredentials = true // For easy test on localhost change to false.
|
||||
val AWSEndpoint = AWSInstance.Endpoint(AWSDomain)
|
||||
var request = AWSInstance.HttpRequest(AWSEndpoint, AWSRegion)
|
||||
request.method = method.toString()
|
||||
@@ -71,7 +78,8 @@ class AWSNetworkConnector : NetworkConnector() {
|
||||
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")
|
||||
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)
|
||||
|
||||
external fun decodeURIComponent(url: String): String
|
||||
|
||||
// Convert saved old report to expected new format.
|
||||
internal fun convertToNewFormat(data: JsonObject): List<Any> {
|
||||
val env = Environment.create(data.getRequiredField("env"))
|
||||
@@ -183,7 +185,7 @@ fun router() {
|
||||
val goldenIndex = GoldenResultsIndex(connector)
|
||||
val buildInfoIndex = BuildInfoIndex(connector)
|
||||
|
||||
router.get("/createMapping") { request, response ->
|
||||
router.get("/createMapping") { _, response ->
|
||||
buildInfoIndex.createMapping().then { _ ->
|
||||
response.sendStatus(200)
|
||||
}.catch { _ ->
|
||||
@@ -220,7 +222,10 @@ fun router() {
|
||||
register.sendTeamCityRequest(register.changesListUrl, true).then { changes ->
|
||||
val commitsList = CommitsList(JsonTreeParser.parse(changes))
|
||||
// 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 {
|
||||
val reportData = JsonTreeParser.parse(resultsContent)
|
||||
val reports = if (reportData is JsonArray) {
|
||||
@@ -292,6 +297,9 @@ fun router() {
|
||||
|
||||
var branch: String? = null
|
||||
var type: String? = null
|
||||
var buildsCountToShow = 200
|
||||
var beforeDate: String? = null
|
||||
var afterDate: String? = null
|
||||
if (request.query != undefined) {
|
||||
if (request.query.branch != undefined) {
|
||||
branch = request.query.branch
|
||||
@@ -299,9 +307,19 @@ fun router() {
|
||||
if (request.query.type != undefined) {
|
||||
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 }
|
||||
// Get number of failed benchmarks for each build.
|
||||
benchmarksDispatcher.getFailuresNumber(target, buildNumbers).then { failures ->
|
||||
@@ -331,6 +349,9 @@ fun router() {
|
||||
var branch: String? = null
|
||||
var type: String? = null
|
||||
var excludeNames: List<String> = emptyList()
|
||||
var buildsCountToShow = 200
|
||||
var beforeDate: String? = null
|
||||
var afterDate: String? = null
|
||||
|
||||
// Parse parameters from request if it exists.
|
||||
if (request.query != undefined) {
|
||||
@@ -352,9 +373,18 @@ fun router() {
|
||||
if (request.query.exclude != undefined) {
|
||||
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") {
|
||||
// Get geometric mean for samples.
|
||||
benchmarksDispatcher.getGeometricMean(metric, target, buildNumbers, normalize,
|
||||
@@ -366,7 +396,8 @@ fun router() {
|
||||
reject()
|
||||
}
|
||||
} 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"))
|
||||
}.catch {
|
||||
println("Error during getting samples")
|
||||
@@ -412,6 +443,7 @@ fun router() {
|
||||
if (request.query != undefined) {
|
||||
if (request.query.buildNumber != undefined) {
|
||||
buildNumber = request.query.buildNumber
|
||||
buildNumber = request.query.buildNumber
|
||||
}
|
||||
}
|
||||
getBuildsInfoFromArtifactory(targetPathName).then { buildInfo ->
|
||||
@@ -448,10 +480,13 @@ fun router() {
|
||||
val buildInfoRecord = BuildInfo(currentBuildNumber, infoParts[1], infoParts[2],
|
||||
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 { _ ->
|
||||
println("[BUILD INFO] Success insert build number ${buildInfoRecord.buildNumber}")
|
||||
externalJsonReport?.let {
|
||||
var externalReports = convert(externalJsonReport, currentBuildNumber, target)
|
||||
var externalReports = convert(externalJsonReport.replace("circlet_iosX64", "SpaceFramework_iosX64"),
|
||||
currentBuildNumber, target)
|
||||
externalReports.forEach { externalReport ->
|
||||
val extrenalAdditionalReport = SummaryBenchmarksReport(externalReport)
|
||||
.getBenchmarksReport().normalizeBenchmarksSet(goldenResults)
|
||||
|
||||
@@ -35,36 +35,12 @@ fun sendGetRequest(url: String) = window.fetch(url, RequestInit("GET")).then { r
|
||||
response.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.
|
||||
fun getChartData(labels: List<String>, valuesList: Collection<List<*>>, stageToShow: Int = 0,
|
||||
buildsNumber: Int? = null, classNames: Array<String>? = null): dynamic {
|
||||
fun getChartData(labels: List<String>, valuesList: Collection<List<*>>,
|
||||
classNames: Array<String>? = null): dynamic {
|
||||
val chartData: dynamic = object {}
|
||||
// Show only some part of data.
|
||||
val (labelsData, valuesData) = buildsNumber?.let {
|
||||
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 ->
|
||||
chartData["labels"] = labels.toTypedArray()
|
||||
chartData["series"] = valuesList.mapIndexed { index, it ->
|
||||
val series: dynamic = object {}
|
||||
series["data"] = it.toTypedArray()
|
||||
classNames?.let { series["className"] = classNames[index] }
|
||||
@@ -83,7 +59,7 @@ fun getChartOptions(samples: Array<String>, yTitle: String, classNames: Array<St
|
||||
val axisXObject: dynamic = object {}
|
||||
axisXObject["offset"] = 40
|
||||
axisXObject["labelInterpolationFnc"] = { value, index, labels ->
|
||||
val labelsCount = 30
|
||||
val labelsCount = 20
|
||||
val skipNumber = ceil((labels.length as Int).toDouble() / labelsCount).toInt()
|
||||
if (skipNumber > 1) {
|
||||
if (index % skipNumber == 0) value else null
|
||||
@@ -126,9 +102,8 @@ fun customizeChart(chart: dynamic, chartContainer: String, jquerySelector: dynam
|
||||
chart.on("draw", { data ->
|
||||
var element = data.element
|
||||
if (data.type == "point") {
|
||||
val buildsGroup = getBuildsGroup(builds)
|
||||
val pointSize = 12
|
||||
val currentBuild = buildsGroup.get(data.index)
|
||||
val currentBuild = builds.get(data.index)
|
||||
currentBuild?.let { currentBuild ->
|
||||
// Higlight builds with failures.
|
||||
if (currentBuild.failuresNumber > 0) {
|
||||
@@ -154,7 +129,7 @@ fun customizeChart(chart: dynamic, chartContainer: String, jquerySelector: dynam
|
||||
var shift = 1
|
||||
var previousBuild: Build? = null
|
||||
while (previousBuild == null && data.index - shift >= 0) {
|
||||
previousBuild = buildsGroup.get(data.index - shift)
|
||||
previousBuild = builds.get(data.index - shift)
|
||||
shift++
|
||||
}
|
||||
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) {
|
||||
if (elements.filterNotNull().size == elements.count())
|
||||
action()
|
||||
else window.setTimeout(waitForElements(elements, action), 500)
|
||||
}
|
||||
fun getDatesComponents() = "${beforeDate?.let {"&before=${encodeURIComponent(it)}"} ?: ""}" +
|
||||
"${afterDate?.let {"&after=${encodeURIComponent(it)}"} ?: ""}"
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val serverUrl = "https://kotlin-native-perf-summary.labs.jb.gg"
|
||||
buildsNumberToShow = null
|
||||
stageToShow = 0
|
||||
val zoomRatio = 3
|
||||
val zoomRatio = 2
|
||||
|
||||
// Get parameters from request.
|
||||
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.
|
||||
val branchesUrl = "$serverUrl/branches"
|
||||
sendGetRequest(branchesUrl).then { response ->
|
||||
@@ -256,7 +233,8 @@ fun main(args: Array<String>) {
|
||||
autocompleteParameters["onSelect"] = { suggestion ->
|
||||
if (suggestion.value != parameters["build"]) {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -265,7 +243,8 @@ fun main(args: Array<String>) {
|
||||
val newValue = js("$(this).val()").toString()
|
||||
if (newValue.isEmpty() || newValue in buildsNumbers) {
|
||||
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
|
||||
}
|
||||
})
|
||||
@@ -282,7 +261,7 @@ fun main(args: Array<String>) {
|
||||
val newValue = js("$(this).val()")
|
||||
if (newValue != parameters["target"]) {
|
||||
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
|
||||
}
|
||||
})
|
||||
@@ -290,7 +269,7 @@ fun main(args: Array<String>) {
|
||||
val newValue = js("$(this).val()")
|
||||
if (newValue != parameters["type"]) {
|
||||
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
|
||||
}
|
||||
})
|
||||
@@ -298,35 +277,35 @@ fun main(args: Array<String>) {
|
||||
val newValue = js("$(this).val()")
|
||||
if (newValue != parameters["branch"]) {
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
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 ""
|
||||
|
||||
// Collect information for charts library.
|
||||
val valuesToShow = mapOf("EXECUTION_TIME" to arrayOf(mapOf(
|
||||
val valuesToShow = mapOf("EXECUTION_TIME" to listOf(mapOf(
|
||||
"normalize" to "true"
|
||||
)),
|
||||
"COMPILE_TIME" to arrayOf(mapOf(
|
||||
"COMPILE_TIME" to listOf(mapOf(
|
||||
"samples" to "HelloWorld,Videoplayer$platformSpecificBenchs",
|
||||
"agr" to "samples"
|
||||
)),
|
||||
"CODE_SIZE" to arrayOf(mapOf(
|
||||
"CODE_SIZE" to listOf(mapOf(
|
||||
"normalize" to "true",
|
||||
"exclude" to if (parameters["target"] == "Linux")
|
||||
"kotlinx.coroutines"
|
||||
else if (parameters["target"] == "Mac_OS_X")
|
||||
"circlet_iosX64"
|
||||
"SpaceFramework_iosX64"
|
||||
else ""
|
||||
), mapOf(
|
||||
), if (platformSpecificBenchs.isNotEmpty()) mapOf(
|
||||
"normalize" to "true",
|
||||
"agr" to "samples",
|
||||
"samples" to platformSpecificBenchs.removePrefix(",")
|
||||
)),
|
||||
"BUNDLE_SIZE" to arrayOf(mapOf("samples" to "KotlinNative",
|
||||
) else null).filterNotNull(),
|
||||
"BUNDLE_SIZE" to listOf(mapOf("samples" to "KotlinNative",
|
||||
"agr" to "samples"))
|
||||
)
|
||||
|
||||
@@ -344,13 +323,27 @@ fun main(args: Array<String>) {
|
||||
var bundleSizeChart: dynamic = null
|
||||
|
||||
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"]}/"
|
||||
|
||||
// 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.
|
||||
valuesToShow.map { (metric, arrayOfSettings) ->
|
||||
val resultValues = arrayOfSettings.map { settings ->
|
||||
valuesToShow.map { (metric, listOfSettings) ->
|
||||
val resultValues = listOfSettings.map { settings ->
|
||||
val getParameters = with(StringBuilder()) {
|
||||
if (settings.isNotEmpty()) {
|
||||
append("?")
|
||||
@@ -372,7 +365,7 @@ fun main(args: Array<String>) {
|
||||
if (parameters["type"] != "all")
|
||||
(if (getParameters.isEmpty() && branchParameter.isEmpty()) "?" else "&") + "type=${parameters["type"]}"
|
||||
else ""
|
||||
}"
|
||||
}&count=$buildsNumberToShow${getDatesComponents()}"
|
||||
sendGetRequest(url)
|
||||
}.toTypedArray()
|
||||
|
||||
@@ -397,33 +390,47 @@ fun main(args: Array<String>) {
|
||||
"COMPILE_TIME" -> {
|
||||
compileData = labels to values.map { it.map { it?.let { it / 1000 } } }
|
||||
compileChart = Chartist.Line("#compile_chart",
|
||||
getChartData(labels, compileData.second, stageToShow, buildsNumberToShow),
|
||||
getChartData(labels, compileData.second),
|
||||
getChartOptions(valuesToShow["COMPILE_TIME"]!![0]!!["samples"]!!.split(',').toTypedArray(),
|
||||
"Time, milliseconds"))
|
||||
buildsInfoPromise.then { builds ->
|
||||
customizeChart(compileChart, "compile_chart", js("$(\"#compile_chart\")"), builds, parameters)
|
||||
compileChart.update(getChartData(compileData.first, compileData.second))
|
||||
}
|
||||
}
|
||||
"EXECUTION_TIME" -> {
|
||||
execData = labels to values
|
||||
execChart = Chartist.Line("#exec_chart",
|
||||
getChartData(labels, execData.second, stageToShow, buildsNumberToShow),
|
||||
getChartData(labels, execData.second),
|
||||
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" -> {
|
||||
codeSizeData = labels to values
|
||||
codeSizeChart = Chartist.Line("#codesize_chart",
|
||||
getChartData(labels, codeSizeData.second,
|
||||
stageToShow, buildsNumberToShow, sizeClassNames),
|
||||
getChartData(labels, codeSizeData.second),
|
||||
getChartOptions(arrayOf("Geometric Mean") + platformSpecificBenchs.split(',')
|
||||
.filter { it.isNotEmpty() },
|
||||
"Normalized size",
|
||||
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" -> {
|
||||
bundleSizeData = labels to values.map { it.map { it?.let { it.toInt() / 1024 / 1024 } } }
|
||||
bundleSizeChart = Chartist.Line("#bundlesize_chart",
|
||||
getChartData(labels,
|
||||
bundleSizeData.second, stageToShow,
|
||||
buildsNumberToShow, sizeClassNames),
|
||||
bundleSizeData.second, sizeClassNames),
|
||||
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")
|
||||
}
|
||||
@@ -433,69 +440,56 @@ fun main(args: Array<String>) {
|
||||
|
||||
// Update all charts with using same data.
|
||||
val updateAllCharts: () -> Unit = {
|
||||
execChart.update(getChartData(execData.first, execData.second, stageToShow, buildsNumberToShow))
|
||||
compileChart.update(getChartData(compileData.first, compileData.second, stageToShow, buildsNumberToShow))
|
||||
codeSizeChart.update(getChartData(codeSizeData.first, codeSizeData.second, stageToShow, buildsNumberToShow, sizeClassNames))
|
||||
bundleSizeChart.update(getChartData(bundleSizeData.first, bundleSizeData.second, stageToShow, buildsNumberToShow, 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()
|
||||
}
|
||||
execChart.update(getChartData(execData.first, execData.second))
|
||||
compileChart.update(getChartData(compileData.first, compileData.second))
|
||||
codeSizeChart.update(getChartData(codeSizeData.first, codeSizeData.second, sizeClassNames))
|
||||
bundleSizeChart.update(getChartData(bundleSizeData.first, bundleSizeData.second, sizeClassNames))
|
||||
}
|
||||
|
||||
js("$('#plusBtn')").click({
|
||||
buildsNumberToShow = buildsNumberToShow?.let {
|
||||
if (it / zoomRatio > zoomRatio) {
|
||||
it / zoomRatio
|
||||
buildsNumberToShow =
|
||||
if (buildsNumberToShow / zoomRatio > zoomRatio) {
|
||||
buildsNumberToShow / zoomRatio
|
||||
} 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({
|
||||
buildsNumberToShow = buildsNumberToShow?.let {
|
||||
if (it * zoomRatio <= execData.first.size) {
|
||||
it * zoomRatio
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
updateAllCharts()
|
||||
buildsNumberToShow = buildsNumberToShow * zoomRatio
|
||||
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("$('#prevBtn')").click({
|
||||
buildsNumberToShow?.let {
|
||||
val bottomBorder = -execData.first.size / (buildsNumberToShow as Int)
|
||||
if (stageToShow - 1 > bottomBorder) {
|
||||
stageToShow--
|
||||
}
|
||||
} ?: run { stageToShow = 0 }
|
||||
updateAllCharts()
|
||||
buildsInfoPromise.then { builds ->
|
||||
beforeDate = builds.firstOrNull()?.startTime
|
||||
afterDate = null
|
||||
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" +
|
||||
"${beforeDate?.let {"&before=${encodeURIComponent(it)}"} ?: ""}"
|
||||
window.location.href = newLink
|
||||
}
|
||||
})
|
||||
|
||||
js("$('#nextBtn')").click({
|
||||
if (stageToShow + 1 <= 0) {
|
||||
stageToShow++
|
||||
buildsInfoPromise.then { builds ->
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user