Add configuration_cache tag into statistic
This commit is contained in:
@@ -66,6 +66,10 @@ dependencies {
|
||||
testImplementation(project(":kotlin-android-extensions"))
|
||||
testImplementation(project(":kotlin-parcelize-compiler"))
|
||||
testImplementation(commonDependency("org.jetbrains.intellij.deps", "trove4j"))
|
||||
testImplementation(commonDependency("io.ktor", "ktor-server-test-host"))
|
||||
testImplementation(commonDependency("io.ktor", "ktor-server-core"))
|
||||
testImplementation(commonDependency("io.ktor", "ktor-server-netty"))
|
||||
testImplementation(commonDependency("io.ktor", "ktor-client-mock"))
|
||||
|
||||
testImplementation(gradleApi())
|
||||
testImplementation(gradleTestKit())
|
||||
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle
|
||||
|
||||
import com.google.gson.Gson
|
||||
import io.ktor.application.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.request.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.server.engine.*
|
||||
import io.ktor.server.netty.*
|
||||
import io.ktor.util.collections.*
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatisticsData
|
||||
import org.jetbrains.kotlin.gradle.plugin.stat.StatTag
|
||||
import org.jetbrains.kotlin.gradle.report.BuildReportType
|
||||
import org.jetbrains.kotlin.gradle.testbase.*
|
||||
import org.jetbrains.kotlin.test.util.joinToArrayString
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.ServerSocket
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
import kotlin.io.path.appendText
|
||||
import kotlin.test.fail
|
||||
|
||||
@DisplayName("Build statistics")
|
||||
@JvmGradlePluginTests
|
||||
class BuildStatisticsWithKtorIT : KGPBaseTest() {
|
||||
|
||||
companion object {
|
||||
fun CompileStatisticsData.validateMandatoryField(kotlinVersion: String, validationData: ValidationData): List<String> {
|
||||
val validationErrors = LinkedList<String>()
|
||||
if (taskResult != validationData.taskResult) {
|
||||
validationErrors.add("Unexpected taskResult: $taskResult instead of ${validationData.taskResult}")
|
||||
}
|
||||
if (this.kotlinVersion != kotlinVersion) {
|
||||
validationErrors.add("Unexpected kotlinVersion: ${this.kotlinVersion} instead of ${kotlinVersion}")
|
||||
}
|
||||
if (compilerArguments.isEmpty()) {
|
||||
validationErrors.add("Empty compiler arguments")
|
||||
}
|
||||
if (performanceMetrics.isEmpty()) {
|
||||
validationErrors.add("Empty performance metrics")
|
||||
}
|
||||
if (buildTimesMetrics.isEmpty()) {
|
||||
validationErrors.add("Empty build metrics")
|
||||
}
|
||||
for (tag: String in validationData.expectedTags) {
|
||||
if (!tags.contains(tag)) {
|
||||
validationErrors.add("Does not contains \'$tag\' tag")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return validationErrors
|
||||
}
|
||||
|
||||
fun CompileStatisticsData.validateIncrementalData(validationData: ValidationData): List<String> {
|
||||
if (validationData.nonIncrementalReasons.isNotEmpty()) return emptyList()
|
||||
val validationErrors = LinkedList<String>()
|
||||
if (changes.size != validationData.changedFiles.size) {
|
||||
validationErrors.add("Changed files do not equal: ${changes.joinToArrayString()} instead of ${validationData.changedFiles.joinToArrayString()} ")
|
||||
}
|
||||
if (!tags.contains(StatTag.INCREMENTAL.name)) {
|
||||
validationErrors.add("INCREMENTAL tag was no set")
|
||||
}
|
||||
return validationErrors
|
||||
}
|
||||
|
||||
fun CompileStatisticsData.validateNonIncrementalData(validationData: ValidationData): List<String> {
|
||||
if (validationData.nonIncrementalReasons.isEmpty()) return emptyList()
|
||||
val validationErrors = LinkedList<String>()
|
||||
if (!tags.contains(StatTag.NON_INCREMENTAL.name)) {
|
||||
validationErrors.add("NON_INCREMENTAL tag was no set")
|
||||
}
|
||||
return validationErrors
|
||||
}
|
||||
|
||||
fun getEmptyPort(): ServerSocket {
|
||||
for (port in 8080..8180) {
|
||||
try {
|
||||
return ServerSocket(port).also { println("Use $port port") }
|
||||
} catch (_: IOException) {
|
||||
continue // try next port
|
||||
}
|
||||
|
||||
}
|
||||
throw IOException("no free port found")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private lateinit var server: ApplicationEngine
|
||||
|
||||
private val port = getEmptyPort().localPort
|
||||
|
||||
@BeforeAll
|
||||
fun initEmbeddedService() {
|
||||
val ktorTestDataAnnotations = this.javaClass.methods.flatMap { it.annotations.filterIsInstance<KtorTestData>() }
|
||||
println("start embedded server with data: ${ktorTestDataAnnotations.joinToArrayString()}")
|
||||
server = embeddedServer(Netty, port = port)
|
||||
{
|
||||
val ktorServerData = ktorTestDataAnnotations.associateBy({ it.projectName }) { it.validationData.toMutableList() }
|
||||
val failedResults = ConcurrentList<String>()
|
||||
|
||||
suspend fun responseBadRequest(call: ApplicationCall, message: String) {
|
||||
println("Validation errors: $message")
|
||||
failedResults.plus(message)
|
||||
call.respond(status = HttpStatusCode.BadRequest, message)
|
||||
}
|
||||
|
||||
routing {
|
||||
post("/badRequest") {
|
||||
call.respond(HttpStatusCode.BadRequest, "Some reason")
|
||||
}
|
||||
post("/validate") {
|
||||
val body = call.receive<String>()
|
||||
println("TRACE: routing was called: $body")
|
||||
|
||||
val statData = Gson().fromJson(body, CompileStatisticsData::class.java)
|
||||
val projectValidation = ktorServerData[statData.projectName]
|
||||
if (projectValidation == null) {
|
||||
responseBadRequest(call, "Unknown validation for project ${statData.projectName}")
|
||||
return@post
|
||||
}
|
||||
|
||||
val ktorTestData = projectValidation.firstOrNull { it.taskName == statData.taskName }
|
||||
|
||||
if (ktorTestData == null) {
|
||||
responseBadRequest(call, "${statData.projectName}: Unknown validation for task ${statData.taskName}")
|
||||
return@post
|
||||
}
|
||||
|
||||
//validate response
|
||||
statData.validateMandatoryField(defaultBuildOptions.kotlinVersion, ktorTestData).let {
|
||||
if (it.isNotEmpty()) {
|
||||
responseBadRequest(
|
||||
call,
|
||||
"${statData.projectName}: Fail to validate mandatory fields: ${it.joinToArrayString()}"
|
||||
)
|
||||
return@post
|
||||
}
|
||||
}
|
||||
statData.validateIncrementalData(ktorTestData).let {
|
||||
if (it.isNotEmpty()) {
|
||||
responseBadRequest(
|
||||
call,
|
||||
"${statData.projectName}: Fail to validate incremental fields: ${it.joinToArrayString()}"
|
||||
)
|
||||
return@post
|
||||
}
|
||||
}
|
||||
statData.validateNonIncrementalData(ktorTestData).let {
|
||||
if (it.isNotEmpty()) {
|
||||
responseBadRequest(
|
||||
call,
|
||||
"${statData.projectName}: Fail to validate non-incremental fields: ${it.joinToArrayString()}"
|
||||
)
|
||||
return@post
|
||||
}
|
||||
}
|
||||
call.respond(HttpStatusCode.OK)
|
||||
}
|
||||
get("/results") {
|
||||
if (failedResults.isEmpty()) {
|
||||
call.respond(HttpStatusCode.OK)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.InternalServerError, message = failedResults.joinToArrayString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
server.start()
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun shutDownEmbeddedService() {
|
||||
server.stop(1000, 1000)
|
||||
}
|
||||
|
||||
|
||||
@DisplayName("Http build report request problems are logged only ones")
|
||||
@GradleTest
|
||||
fun testHttpServiceWithBadRequest(gradleVersion: GradleVersion) {
|
||||
project("incrementalMultiproject", gradleVersion) {
|
||||
enableStatisticReports(BuildReportType.HTTP, "http://localhost:$port/badRequest")
|
||||
build("assemble") {
|
||||
assertOutputContainsExactTimes("Failed to send statistic to", 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@KtorTestData(
|
||||
"validateMandatoryField",
|
||||
[
|
||||
//first run
|
||||
ValidationData(":lib:compileKotlin", expectedTags = ["NON_INCREMENTAL"], nonIncrementalReasons = ["UNKNOWN_CHANGES_IN_GRADLE_INPUTS"]),
|
||||
ValidationData(":app:compileKotlin", expectedTags = ["NON_INCREMENTAL"], nonIncrementalReasons = ["UNKNOWN_CHANGES_IN_GRADLE_INPUTS"])
|
||||
]
|
||||
)
|
||||
@DisplayName("Validate mandatory field for http request body")
|
||||
@GradleTest
|
||||
fun testHttpRequest(gradleVersion: GradleVersion) {
|
||||
project("incrementalMultiproject", gradleVersion) {
|
||||
setProjectForTest("validateMandatoryField")
|
||||
build("assemble") {
|
||||
assertOutputDoesNotContain("Failed to send statistic to")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@KtorTestData(
|
||||
"validateConfigurationCache",
|
||||
[
|
||||
//first run
|
||||
ValidationData(":lib:compileKotlin", expectedTags = ["NON_INCREMENTAL", "CONFIGURATION_CACHE"], nonIncrementalReasons = ["UNKNOWN_CHANGES_IN_GRADLE_INPUTS"]),
|
||||
ValidationData(":app:compileKotlin", expectedTags = ["NON_INCREMENTAL", "CONFIGURATION_CACHE"], nonIncrementalReasons = ["UNKNOWN_CHANGES_IN_GRADLE_INPUTS"]),
|
||||
//second run
|
||||
ValidationData(":lib:compileKotlin", expectedTags = ["INCREMENTAL", "CONFIGURATION_CACHE"]),
|
||||
ValidationData(":app:compileKotlin", expectedTags = ["INCREMENTAL", "CONFIGURATION_CACHE"]),
|
||||
]
|
||||
)
|
||||
@DisplayName("Validate configuration cache tag")
|
||||
@GradleTest
|
||||
fun testConfigurationCache(gradleVersion: GradleVersion) {
|
||||
val buildOptions = defaultBuildOptions.copy(configurationCache = true)
|
||||
project("incrementalMultiproject", gradleVersion) {
|
||||
setProjectForTest("validateConfigurationCache")
|
||||
build("assemble", buildOptions = buildOptions) {
|
||||
assertOutputDoesNotContain("Failed to send statistic to")
|
||||
}
|
||||
projectPath.resolve("lib/src/main/kotlin/bar/B.kt")
|
||||
build("assemble", buildOptions = buildOptions) {
|
||||
assertOutputDoesNotContain("Failed to send statistic to")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestProject.setProjectForTest(projectName: String) {
|
||||
enableStatisticReports(BuildReportType.HTTP, "http://localhost:$port/validate")
|
||||
settingsGradle.appendText("rootProject.name=\'$projectName\'")
|
||||
}
|
||||
}
|
||||
|
||||
@Repeatable
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class KtorTestData(
|
||||
val projectName: String,
|
||||
val validationData: Array<ValidationData>,
|
||||
)
|
||||
|
||||
annotation class ValidationData(
|
||||
//mandatory fields
|
||||
val taskName: String,
|
||||
val taskResult: String = "SUCCESS",
|
||||
val expectedTags: Array<String> = [],
|
||||
|
||||
//fields for non-incremental compilation
|
||||
val nonIncrementalReasons: Array<String> = [], //if empty incremental validation expected
|
||||
|
||||
//fields for incremental compilation
|
||||
val changedFiles: Array<String> = [],
|
||||
)
|
||||
|
||||
|
||||
|
||||
+5
@@ -53,6 +53,11 @@ class BuildScanStatisticsListener(
|
||||
|
||||
readableString(data).forEach { buildScan.value(data.taskName, it) }
|
||||
|
||||
data.label?.takeIf { !tags.contains(it) }?.also {
|
||||
buildScan.tag(it)
|
||||
tags.add(it)
|
||||
}
|
||||
|
||||
data.tags
|
||||
.filter { !tags.contains(it) }
|
||||
.forEach {
|
||||
|
||||
+3
-1
@@ -43,7 +43,9 @@ enum class StatTag {
|
||||
INCREMENTAL,
|
||||
NON_INCREMENTAL,
|
||||
GRADLE_DEBUG,
|
||||
KOTLIN_DEBUG
|
||||
KOTLIN_DEBUG,
|
||||
CONFIGURATION_CACHE,
|
||||
BUILD_CACHE,
|
||||
}
|
||||
|
||||
//Sensitive data. This object is used directly for statistic via http
|
||||
|
||||
+5
-6
@@ -9,8 +9,6 @@ import org.gradle.tooling.events.task.TaskFailureResult
|
||||
import org.gradle.tooling.events.task.TaskFinishEvent
|
||||
import org.gradle.tooling.events.task.TaskSkippedResult
|
||||
import org.gradle.tooling.events.task.TaskSuccessResult
|
||||
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
|
||||
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
||||
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
|
||||
import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatisticsData
|
||||
import org.jetbrains.kotlin.gradle.plugin.stat.StatTag
|
||||
@@ -49,7 +47,8 @@ class KotlinBuildStatListener {
|
||||
projectName: String,
|
||||
uuid: String,
|
||||
label: String?,
|
||||
kotlinVersion: String
|
||||
kotlinVersion: String,
|
||||
additionalTags: List<StatTag> = emptyList()
|
||||
): CompileStatisticsData? {
|
||||
val result = event.result
|
||||
val taskPath = event.descriptor.taskPath
|
||||
@@ -89,7 +88,7 @@ class KotlinBuildStatListener {
|
||||
projectName = projectName,
|
||||
taskName = taskPath,
|
||||
changes = changes,
|
||||
tags = parseTags(taskExecutionResult).map { it.name },
|
||||
tags = parseTags(taskExecutionResult, additionalTags).map { it.name },
|
||||
nonIncrementalAttributes = taskExecutionResult?.buildMetrics?.buildAttributes?.asMap()?.filter { it.value > 0 }?.keys ?: emptySet(),
|
||||
hostName = hostName,
|
||||
kotlinVersion = kotlinVersion,
|
||||
@@ -99,8 +98,8 @@ class KotlinBuildStatListener {
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseTags(taskExecutionResult: TaskExecutionResult?): List<StatTag> {
|
||||
val tags = ArrayList<StatTag>()
|
||||
private fun parseTags(taskExecutionResult: TaskExecutionResult?, additionalTags: List<StatTag>): List<StatTag> {
|
||||
val tags = ArrayList(additionalTags)
|
||||
|
||||
val nonIncrementalAttributes = taskExecutionResult?.buildMetrics?.buildAttributes?.asMap() ?: emptyMap()
|
||||
|
||||
|
||||
+24
-6
@@ -17,8 +17,10 @@ import org.gradle.tooling.events.task.TaskFinishEvent
|
||||
import org.jetbrains.kotlin.gradle.plugin.stat.BuildFinishData
|
||||
import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatisticsData
|
||||
import org.jetbrains.kotlin.gradle.plugin.stat.GradleBuildStartParameters
|
||||
import org.jetbrains.kotlin.gradle.plugin.stat.StatTag
|
||||
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatListener.Companion.prepareData
|
||||
import org.jetbrains.kotlin.gradle.report.BuildMetricsReporterService.Companion.getStartParameters
|
||||
import org.jetbrains.kotlin.gradle.utils.isConfigurationCacheAvailable
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
@@ -31,19 +33,24 @@ abstract class HttpReportService : BuildService<HttpReportService.Parameters>,
|
||||
OperationCompletionListener, AutoCloseable {
|
||||
|
||||
var executorService: ExecutorService = Executors.newSingleThreadExecutor()
|
||||
val uuid = UUID.randomUUID().toString()
|
||||
|
||||
val startTime = System.nanoTime()
|
||||
|
||||
interface Parameters : BuildServiceParameters {
|
||||
var label: String?
|
||||
var uuid: String
|
||||
var projectName: String
|
||||
var httpSettings: HttpReportSettings
|
||||
var kotlinVersion: String
|
||||
var additionalTags: List<StatTag>
|
||||
var startParameters: GradleBuildStartParameters
|
||||
}
|
||||
|
||||
private val log = Logging.getLogger(this.javaClass)
|
||||
val log = Logging.getLogger(this.javaClass)
|
||||
|
||||
init {
|
||||
log.info("Http report service is registered. Unique build id: $uuid")
|
||||
}
|
||||
|
||||
// @Volatile for one thread executor it does not need
|
||||
private var requestPreviousFailed = false
|
||||
@@ -51,7 +58,8 @@ abstract class HttpReportService : BuildService<HttpReportService.Parameters>,
|
||||
|
||||
override fun onFinish(event: FinishEvent?) {
|
||||
if (event is TaskFinishEvent) {
|
||||
val data = prepareData(event, parameters.projectName, parameters.uuid, parameters.label, parameters.kotlinVersion)
|
||||
val data =
|
||||
prepareData(event, parameters.projectName, uuid, parameters.label, parameters.kotlinVersion, parameters.additionalTags)
|
||||
data?.also { executorService.submit { report(data) } }
|
||||
}
|
||||
}
|
||||
@@ -64,20 +72,30 @@ abstract class HttpReportService : BuildService<HttpReportService.Parameters>,
|
||||
companion object {
|
||||
|
||||
fun registerIfAbsent(project: Project, kotlinVersion: String): Provider<HttpReportService>? {
|
||||
val rootProject = project.gradle.rootProject
|
||||
val gradle = project.gradle
|
||||
val rootProject = gradle.rootProject
|
||||
val reportingSettings = reportingSettings(rootProject)
|
||||
|
||||
return reportingSettings.httpReportSettings?.let { httpSettings ->
|
||||
project.gradle.sharedServices.registerIfAbsent(
|
||||
gradle.sharedServices.registerIfAbsent(
|
||||
"build_http_metric_service_${HttpReportService::class.java.classLoader.hashCode()}",
|
||||
HttpReportService::class.java
|
||||
) {
|
||||
it.parameters.label = reportingSettings.buildReportLabel
|
||||
it.parameters.projectName = rootProject.name
|
||||
it.parameters.uuid = UUID.randomUUID().toString()
|
||||
it.parameters.httpSettings = httpSettings
|
||||
it.parameters.kotlinVersion = kotlinVersion
|
||||
it.parameters.startParameters = getStartParameters(project)
|
||||
|
||||
//init gradle tags, that present in build scan
|
||||
val additionalTags = ArrayList<StatTag>()
|
||||
if (isConfigurationCacheAvailable(gradle)) {
|
||||
additionalTags.add(StatTag.CONFIGURATION_CACHE)
|
||||
}
|
||||
if (gradle.startParameter.isBuildCacheEnabled) {
|
||||
additionalTags.add(StatTag.BUILD_CACHE)
|
||||
}
|
||||
it.parameters.additionalTags = additionalTags
|
||||
}!!
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user