KT-41689: Support statistics for Configuration Cache
#KT-41689 Fixed
This commit is contained in:
+15
@@ -171,6 +171,21 @@ class ConfigurationCacheIT : AbstractConfigurationCacheIT() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("with build report")
|
||||
@GradleTest
|
||||
fun testBuildReportSmokeTestForConfigurationCache(gradleVersion: GradleVersion) {
|
||||
project("simpleProject", gradleVersion) {
|
||||
val buildOptions = defaultBuildOptions.copy(buidReport = true)
|
||||
build("assemble", buildOptions = buildOptions) {
|
||||
assertOutputContains("Kotlin build report is written to")
|
||||
}
|
||||
|
||||
build("assemble", buildOptions = buildOptions) {
|
||||
assertOutputContains("Kotlin build report is written to")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SimpleGradlePluginTests
|
||||
|
||||
+45
-4
@@ -30,11 +30,9 @@ import org.junit.Test
|
||||
import java.io.File
|
||||
import java.nio.file.FileSystemException
|
||||
import java.nio.file.Files
|
||||
import java.util.*
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.*
|
||||
|
||||
class KotlinGradleIT : BaseGradleIT() {
|
||||
|
||||
@@ -920,6 +918,49 @@ class KotlinGradleIT : BaseGradleIT() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBuildMetricsSmokeTest() = with(Project("simpleProject")) {
|
||||
build("assemble", "-Pkotlin.build.report.enable=true", "-Pkotlin.build.report.verbose=true") {
|
||||
assertSuccessful()
|
||||
assertContains("Kotlin build report is written to")
|
||||
}
|
||||
val reportFolder = projectDir.resolve("build/reports/kotlin-build")
|
||||
val reports = reportFolder.listFiles()
|
||||
assertNotNull(reports)
|
||||
assertEquals(1, reports.size)
|
||||
val report = reports[0].readText()
|
||||
|
||||
//Should contains build metrics for all compile kotlin tasks
|
||||
assertTrue { report.contains("Time metrics:") }
|
||||
assertTrue { report.contains("RUN_COMPILER:") }
|
||||
assertTrue { report.contains("INCREMENTAL_COMPILATION:") }
|
||||
assertTrue { report.contains("Build performance metrics:") }
|
||||
assertTrue { report.contains("OUTPUT_SIZE:") }
|
||||
assertTrue { report.contains("SNAPSHOT_SIZE:") }
|
||||
assertTrue { report.contains("Build attributes:") }
|
||||
assertTrue { report.contains("REBUILD_REASON:") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompilerBuildMetricsSmokeTest() {
|
||||
val buildOptions = defaultBuildOptions().copy(customEnvironmentVariables = mapOf("KOTLIN_REPORT_PERF" to "true"))
|
||||
|
||||
return with(Project("simpleProject")) {
|
||||
build("assemble", "-Pkotlin.build.report.enable=true", "-Pkotlin.build.report.verbose=true", options = buildOptions) {
|
||||
assertSuccessful()
|
||||
assertContains("Kotlin build report is written to")
|
||||
}
|
||||
val reportFolder = projectDir.resolve("build/reports/kotlin-build")
|
||||
val reports = reportFolder.listFiles()
|
||||
assertNotNull(reports)
|
||||
assertEquals(1, reports.size)
|
||||
val report = reports[0].readText()
|
||||
assertTrue { report.contains("CODE_ANALYSIS:") }
|
||||
assertTrue { report.contains("CODE_GENERATION:") }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testKt29971() = with(Project("kt-29971", GradleVersionRequired.FOR_MPP_SUPPORT)) {
|
||||
build("jvm-app:build") {
|
||||
|
||||
+5
@@ -26,6 +26,7 @@ data class BuildOptions(
|
||||
val kaptOptions: KaptOptions? = null,
|
||||
val androidVersion: String? = null,
|
||||
val jsOptions: JsOptions? = null,
|
||||
val buidReport: Boolean = false,
|
||||
) {
|
||||
data class KaptOptions(
|
||||
val verbose: Boolean = false,
|
||||
@@ -111,6 +112,10 @@ data class BuildOptions(
|
||||
}
|
||||
arguments.add("-Ptest_fixes_version=${TestVersions.Kotlin.CURRENT}")
|
||||
|
||||
if (buidReport) {
|
||||
arguments.add("-Pkotlin.build.report.enable=true")
|
||||
arguments.add("-Pkotlin.build.report.verbose=true")
|
||||
}
|
||||
return arguments.toList()
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -42,7 +42,7 @@ abstract class KaptWithKotlincTask @Inject constructor(
|
||||
task.project.provider { kotlinCompileTask.compilerArgumentsContributor }
|
||||
)
|
||||
task.javaPackagePrefix.set(task.project.provider { kotlinCompileTask.javaPackagePrefix })
|
||||
task.reportingSettings.set(task.project.provider { kotlinCompileTask.reportingSettings })
|
||||
task.reportingSettings.set(task.project.provider { kotlinCompileTask.reportingSettings() })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-4
@@ -11,8 +11,7 @@ import javax.inject.Inject
|
||||
|
||||
open class BuildEventsListenerRegistryHolder @Inject constructor(val listenerRegistry: BuildEventsListenerRegistry) {
|
||||
companion object {
|
||||
fun getInstance(project: Project) = run {
|
||||
project.objects.newInstance(BuildEventsListenerRegistryHolder::class.java)
|
||||
}
|
||||
}
|
||||
fun getInstance(project: Project) =
|
||||
project.objects.newInstance(BuildEventsListenerRegistryHolder::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.plugin
|
||||
|
||||
import org.gradle.tooling.events.FinishEvent
|
||||
import org.gradle.tooling.events.OperationCompletionListener
|
||||
|
||||
//Available since Gradle 6.1
|
||||
class KotlinGradleBuildListener(
|
||||
// val gradle: Gradle,
|
||||
val services: KotlinGradleFinishBuildHandler
|
||||
) : OperationCompletionListener {
|
||||
|
||||
override fun onFinish(event: FinishEvent) {
|
||||
// services.buildFinished(gradle)
|
||||
}
|
||||
|
||||
}
|
||||
+11
-16
@@ -18,10 +18,9 @@ import org.jetbrains.kotlin.gradle.plugin.stat.ReportStatistics
|
||||
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildEsStatListener
|
||||
import org.jetbrains.kotlin.gradle.plugin.statistics.ReportStatisticsToBuildScan
|
||||
import org.jetbrains.kotlin.gradle.plugin.statistics.ReportStatisticsToElasticSearch
|
||||
import org.jetbrains.kotlin.gradle.report.configureReporting
|
||||
import java.io.File
|
||||
|
||||
abstract class KotlinGradleBuildServices : BuildService<KotlinGradleBuildServices.Parameters>, AutoCloseable {
|
||||
internal abstract class KotlinGradleBuildServices : BuildService<KotlinGradleBuildServices.Parameters>, AutoCloseable {
|
||||
|
||||
interface Parameters : BuildServiceParameters {
|
||||
var buildDir: File
|
||||
@@ -29,7 +28,7 @@ abstract class KotlinGradleBuildServices : BuildService<KotlinGradleBuildService
|
||||
}
|
||||
|
||||
private val log = Logging.getLogger(this.javaClass)
|
||||
private var buildHandler: KotlinGradleFinishBuildHandler = KotlinGradleFinishBuildHandler()
|
||||
private val buildHandler: KotlinGradleFinishBuildHandler = KotlinGradleFinishBuildHandler()
|
||||
private val CLASS_NAME = KotlinGradleBuildServices::class.java.simpleName
|
||||
val INIT_MESSAGE = "Initialized $CLASS_NAME"
|
||||
val DISPOSE_MESSAGE = "Disposed $CLASS_NAME"
|
||||
@@ -53,26 +52,22 @@ abstract class KotlinGradleBuildServices : BuildService<KotlinGradleBuildService
|
||||
"kotlin-build-service-${KotlinGradleBuildServices::class.java.canonicalName}_${KotlinGradleBuildServices::class.java.classLoader.hashCode()}",
|
||||
KotlinGradleBuildServices::class.java
|
||||
) { service ->
|
||||
configureReporting(project.gradle)
|
||||
service.parameters.rootDir = project.rootProject.rootDir
|
||||
service.parameters.buildDir = project.rootProject.buildDir
|
||||
addListeners(project)
|
||||
}
|
||||
|
||||
fun addListeners(project: Project) {
|
||||
val kotlinGradleEsListenerProvider = project.provider {
|
||||
val listeners = project.rootProject.objects.listProperty(ReportStatistics::class.java)
|
||||
.value(listOf<ReportStatistics>(ReportStatisticsToElasticSearch))
|
||||
if (project.gradle.startParameter.isBuildScan) {
|
||||
project.rootProject.extensions.findByName("buildScan")
|
||||
?.also { listeners.add(ReportStatisticsToBuildScan(it as BuildScanExtension)) }
|
||||
project.rootProject.extensions.findByName("buildScan")
|
||||
?.also {
|
||||
val listeners = project.rootProject.objects.listProperty(ReportStatistics::class.java)
|
||||
.value(listOf<ReportStatistics>(ReportStatisticsToElasticSearch))
|
||||
listeners.add(ReportStatisticsToBuildScan(it as BuildScanExtension))
|
||||
val esStatListener = KotlinBuildEsStatListener(project.rootProject.name, listeners.get())
|
||||
val listenerRegistryHolder = BuildEventsListenerRegistryHolder.getInstance(project)
|
||||
|
||||
listenerRegistryHolder.listenerRegistry.onTaskCompletion(project.provider { esStatListener })
|
||||
}
|
||||
KotlinBuildEsStatListener(project.rootProject.name, listeners.get())
|
||||
}
|
||||
|
||||
val listenerRegistryHolder = BuildEventsListenerRegistryHolder.getInstance(project)
|
||||
|
||||
listenerRegistryHolder.listenerRegistry.onTaskCompletion(kotlinGradleEsListenerProvider)
|
||||
}
|
||||
|
||||
private val multipleProjectsHolder = KotlinPluginInMultipleProjectsHolder(
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import java.io.File
|
||||
import java.lang.management.ManagementFactory
|
||||
import kotlin.math.max
|
||||
|
||||
class KotlinGradleFinishBuildHandler {
|
||||
internal class KotlinGradleFinishBuildHandler {
|
||||
|
||||
companion object {
|
||||
const val SHOULD_REPORT_MEMORY_USAGE_PROPERTY = "kotlin.gradle.test.report.memory.usage"
|
||||
|
||||
+2
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.gradle.model.builder.KotlinModelBuilder
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.isMainCompilationData
|
||||
import org.jetbrains.kotlin.gradle.report.BuildMetricsReporterService
|
||||
import org.jetbrains.kotlin.gradle.scripting.internal.ScriptingGradleSubplugin
|
||||
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode
|
||||
import org.jetbrains.kotlin.gradle.targets.js.ir.*
|
||||
@@ -415,6 +416,7 @@ internal abstract class AbstractKotlinPlugin(
|
||||
registry.register(KotlinModelBuilder(kotlinPluginVersion, null))
|
||||
|
||||
project.components.addAll(target.components)
|
||||
|
||||
}
|
||||
|
||||
private fun rewritePom(pom: MavenPom, rewriter: PomDependenciesRewriter, shouldRewritePom: Provider<Boolean>) {
|
||||
|
||||
+11
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20GradlePlugin
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSetFactory
|
||||
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
|
||||
import org.jetbrains.kotlin.gradle.report.BuildMetricsReporterService
|
||||
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute
|
||||
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsPlugin
|
||||
import org.jetbrains.kotlin.gradle.targets.js.npm.addNpmDependencyExtension
|
||||
@@ -92,6 +93,16 @@ abstract class KotlinBasePluginWrapper : Plugin<Project> {
|
||||
|
||||
KotlinGradleBuildServices.detectKotlinPluginLoadedInMultipleProjects(project, kotlinPluginVersion)
|
||||
|
||||
val buildMetricReporter = BuildMetricsReporterService.registerIfAbsent(project, BuildMetricsReporterService.getStartParameters(project))
|
||||
|
||||
buildMetricReporter?.also { BuildEventsListenerRegistryHolder.getInstance(project).listenerRegistry.onTaskCompletion(it) }
|
||||
|
||||
project.tasks.withType(AbstractKotlinCompile::class.java).configureEach {
|
||||
if (buildMetricReporter != null) {
|
||||
it.buildMetricsReporterService.set(buildMetricReporter)
|
||||
}
|
||||
}
|
||||
|
||||
project.createKotlinExtension(projectExtensionClass).apply {
|
||||
coreLibrariesVersion = kotlinPluginVersion
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ class ReportStatisticsToBuildScan(
|
||||
data.changes.joinTo(readableString, prefix = "Changes: [", postfix = "]; ") { it.substringAfterLast(File.separator) }
|
||||
|
||||
val timeData = data.timeData.map { (key, value) -> "${key.readableString}: ${value}ms"} //sometimes it is better to have separate variable to be able debug
|
||||
val perfData = data.perfData.map { (key, value) -> "$key: ${readableFileLength(value)}"}
|
||||
val perfData = data.perfData.map { (key, value) -> "${key.readableString}: ${readableFileLength(value)}"}
|
||||
timeData.union(perfData).joinTo(readableString, ",", "Performance: [", "]")
|
||||
return readableString.toString()
|
||||
}
|
||||
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.report
|
||||
|
||||
import org.gradle.BuildAdapter
|
||||
import org.gradle.BuildResult
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.execution.TaskExecutionListener
|
||||
import org.gradle.api.invocation.Gradle
|
||||
import org.gradle.api.tasks.TaskState
|
||||
import org.jetbrains.kotlin.build.report.metrics.BuildMetrics
|
||||
import org.jetbrains.kotlin.build.report.metrics.BuildTime
|
||||
import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState
|
||||
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
|
||||
import org.jetbrains.kotlin.gradle.report.data.BuildExecutionData
|
||||
import org.jetbrains.kotlin.gradle.report.data.BuildExecutionDataProcessor
|
||||
import org.jetbrains.kotlin.gradle.report.data.TaskExecutionData
|
||||
import java.util.*
|
||||
|
||||
internal class BuildDataRecorder(
|
||||
private val gradle: Gradle,
|
||||
private val buildDataProcessors: Iterable<BuildExecutionDataProcessor>
|
||||
) : BuildAdapter(), TaskExecutionListener {
|
||||
private val taskRecords = HashMap<Task, TaskRecord>()
|
||||
|
||||
@Synchronized
|
||||
override fun beforeExecute(task: Task) {
|
||||
val startNs = System.nanoTime()
|
||||
taskRecords[task] = TaskRecord(task, startNs)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun afterExecute(task: Task, state: TaskState) {
|
||||
taskRecords[task]?.apply { processResult(state, TaskExecutionResults[task.path]) }
|
||||
}
|
||||
|
||||
override fun buildFinished(result: BuildResult) {
|
||||
val startParams = arrayListOf<String>()
|
||||
gradle.startParameter.apply {
|
||||
startParams.add("tasks = ${taskRequests.joinToString { it.args.toString() }}")
|
||||
startParams.add("excluded tasks = $excludedTaskNames")
|
||||
startParams.add("current dir = $currentDir")
|
||||
startParams.add("project properties args = $projectProperties")
|
||||
startParams.add("system properties args = $systemPropertiesArgs")
|
||||
}
|
||||
val buildData = BuildExecutionData(
|
||||
startParameters = startParams,
|
||||
failure = result.failure,
|
||||
taskExecutionData = taskRecords.values.sortedBy { it.startNs }
|
||||
)
|
||||
|
||||
buildDataProcessors.forEach { it.process(buildData) }
|
||||
}
|
||||
}
|
||||
|
||||
private class TaskRecord(
|
||||
override val task: Task,
|
||||
override val startNs: Long
|
||||
) : TaskExecutionData {
|
||||
private var myEndNs: Long = 0
|
||||
private lateinit var myTaskState: TaskState
|
||||
private var myIcLogLines: List<String> = emptyList()
|
||||
private val myBuildMetrics = BuildMetrics()
|
||||
|
||||
override val isKotlinTask by lazy {
|
||||
task.javaClass.name.startsWith("org.jetbrains.kotlin")
|
||||
}
|
||||
|
||||
override val endNs: Long
|
||||
get() = myEndNs
|
||||
|
||||
override val totalTimeMs: Long
|
||||
get() = (endNs - startNs) / 1_000_000
|
||||
|
||||
override val resultState: TaskState
|
||||
get() = myTaskState
|
||||
|
||||
override val icLogLines: List<String>
|
||||
get() = myIcLogLines
|
||||
|
||||
override val buildMetrics: BuildMetrics
|
||||
get() = myBuildMetrics
|
||||
|
||||
fun processResult(state: TaskState, executionResult: TaskExecutionResult?) {
|
||||
myEndNs = System.nanoTime()
|
||||
myTaskState = state
|
||||
myBuildMetrics.buildTimes.add(BuildTime.GRADLE_TASK, totalTimeMs)
|
||||
|
||||
if (executionResult != null) {
|
||||
myBuildMetrics.addAll(executionResult.buildMetrics)
|
||||
myIcLogLines = executionResult.icLogLines
|
||||
}
|
||||
|
||||
if (task is TaskWithLocalState) {
|
||||
myBuildMetrics.addAll(task.metrics.get().getMetrics())
|
||||
}
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.report
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.logging.Logging
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.services.BuildService
|
||||
import org.gradle.api.services.BuildServiceParameters
|
||||
import org.gradle.tooling.events.FinishEvent
|
||||
import org.gradle.tooling.events.OperationCompletionListener
|
||||
import org.gradle.tooling.events.task.TaskExecutionResult
|
||||
import org.gradle.tooling.events.task.TaskFailureResult
|
||||
import org.gradle.tooling.events.task.TaskFinishEvent
|
||||
import org.gradle.tooling.events.task.TaskSkippedResult
|
||||
import org.jetbrains.kotlin.build.report.metrics.BuildMetrics
|
||||
import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter
|
||||
import org.jetbrains.kotlin.build.report.metrics.BuildTime
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinGradleBuildServices
|
||||
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
|
||||
import org.jetbrains.kotlin.gradle.report.data.BuildExecutionData
|
||||
import org.jetbrains.kotlin.gradle.report.data.BuildExecutionDataProcessor
|
||||
import org.jetbrains.kotlin.gradle.report.data.TaskExecutionData
|
||||
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
abstract class BuildMetricsReporterService : BuildService<BuildMetricsReporterService.Parameters>,
|
||||
OperationCompletionListener, AutoCloseable {
|
||||
|
||||
interface Parameters : BuildServiceParameters {
|
||||
var buildDataProcessors: List<BuildExecutionDataProcessor>
|
||||
var startParameters: List<String>
|
||||
var reportingSettings: ReportingSettings
|
||||
}
|
||||
|
||||
private val log = Logging.getLogger(this.javaClass)
|
||||
|
||||
//Task path to build metrics
|
||||
private val buildMetricsMap = ConcurrentHashMap<String, BuildMetricsReporter>()
|
||||
private val taskRecords = ConcurrentHashMap<String, TaskRecord>()
|
||||
private val failureMessages = ConcurrentLinkedQueue<String?>()
|
||||
private val taskClass = ConcurrentHashMap<String, String>()
|
||||
|
||||
open fun add(taskPath: String, type: String, metrics: BuildMetricsReporter) {
|
||||
if (buildMetricsMap.containsKey(taskPath)) {
|
||||
log.warn("Duplicate path $taskPath")
|
||||
}
|
||||
buildMetricsMap[taskPath] = metrics
|
||||
taskClass[taskPath] = type
|
||||
}
|
||||
|
||||
override fun onFinish(event: FinishEvent?) {
|
||||
if (event is TaskFinishEvent) {
|
||||
val result = event.result
|
||||
val taskPath = event.descriptor.taskPath
|
||||
val startMs = event.result.startTime
|
||||
val finishMs = event.result.endTime
|
||||
if (result is TaskFailureResult) {
|
||||
failureMessages.addAll(result.failures.map { it.message })
|
||||
}
|
||||
val skipMessage = when (result) {
|
||||
is TaskSkippedResult -> result.skipMessage
|
||||
else -> null
|
||||
}
|
||||
val didWork = result is TaskExecutionResult
|
||||
val buildMetrics = BuildMetrics()
|
||||
buildMetrics.buildTimes.add(BuildTime.GRADLE_TASK, finishMs - startMs)
|
||||
|
||||
buildMetricsMap[taskPath]?.also { buildMetrics.addAll(it.getMetrics()) }
|
||||
val taskExecutionResult = TaskExecutionResults[taskPath]
|
||||
val icLogLines = taskExecutionResult?.icLogLines ?: emptyList()
|
||||
taskExecutionResult?.buildMetrics?.also { buildMetrics.addAll(it) }
|
||||
taskRecords[taskPath] = TaskRecord(taskPath, startMs, finishMs, skipMessage, didWork, icLogLines, buildMetrics, taskClass[taskPath] ?: "unknown")
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
val buildData = BuildExecutionData(
|
||||
startParameters = parameters.startParameters,
|
||||
failureMessages = failureMessages.toList(),
|
||||
taskExecutionData = taskRecords.values.sortedBy { it.startMs }
|
||||
)
|
||||
parameters.buildDataProcessors.forEach { it.process(buildData, log) }
|
||||
buildMetricsMap.clear()
|
||||
taskRecords.clear()
|
||||
failureMessages.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun getStartParameters(project: Project) = project.gradle.startParameter.let {
|
||||
val startParameters = arrayListOf<String>()
|
||||
startParameters.add("tasks = ${it.taskRequests.joinToString { it.args.toString() }}")
|
||||
startParameters.add("excluded tasks = ${it.excludedTaskNames}")
|
||||
startParameters.add("current dir = ${it.currentDir}")
|
||||
startParameters.add("project properties args = ${it.projectProperties}")
|
||||
startParameters.add("system properties args = ${it.systemPropertiesArgs}")
|
||||
startParameters
|
||||
}
|
||||
|
||||
fun registerIfAbsent(project: Project, startParameters: List<String>): Provider<BuildMetricsReporterService>? {
|
||||
val buildDataProcessors = ArrayList<BuildExecutionDataProcessor>()
|
||||
val rootProject = project.gradle.rootProject
|
||||
val reportingSettings = reportingSettings(rootProject)
|
||||
|
||||
if (reportingSettings.buildReportMode != BuildReportMode.NONE && reportingSettings.buildReportDir != null) {
|
||||
buildDataProcessors.add(
|
||||
PlainTextBuildReportWriterDataProcessor(
|
||||
reportingSettings,
|
||||
reportingSettings.buildReportDir,
|
||||
rootProject.name
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (reportingSettings.metricsOutputFile != null) {
|
||||
buildDataProcessors.add(MetricsWriter(reportingSettings.metricsOutputFile.absoluteFile))
|
||||
}
|
||||
|
||||
if (buildDataProcessors.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return project.gradle.sharedServices.registerIfAbsent(
|
||||
"build_metric_service_${KotlinGradleBuildServices::class.java.classLoader.hashCode()}",
|
||||
BuildMetricsReporterService::class.java
|
||||
) {
|
||||
it.parameters.startParameters = startParameters
|
||||
it.parameters.buildDataProcessors = buildDataProcessors
|
||||
it.parameters.reportingSettings = reportingSettings
|
||||
}!!
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class TaskRecord(
|
||||
override val taskPath: String,
|
||||
override val startMs: Long,
|
||||
override val endMs: Long,
|
||||
override val skipMessage: String?,
|
||||
override val didWork: Boolean,
|
||||
override val icLogLines: List<String>,
|
||||
override val buildMetrics: BuildMetrics,
|
||||
override val type: String
|
||||
) : TaskExecutionData {
|
||||
override val isKotlinTask by lazy {
|
||||
type.startsWith("org.jetbrains.kotlin")
|
||||
}
|
||||
|
||||
override val totalTimeMs: Long
|
||||
get() = (endMs - startMs)
|
||||
}
|
||||
+2
-2
@@ -101,8 +101,8 @@ class KotlinBuildReporterHandler {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun formatTime(ns: Long): String {
|
||||
val seconds = ns.toDouble() / 1_000
|
||||
internal fun formatTime(ms: Long): String {
|
||||
val seconds = ms.toDouble() / 1_000
|
||||
return seconds.asString(2) + " s"
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -18,10 +18,9 @@ import java.io.ObjectOutputStream
|
||||
|
||||
internal class MetricsWriter(
|
||||
private val outputFile: File,
|
||||
private val log: Logger
|
||||
) : BuildExecutionDataProcessor {
|
||||
override fun process(build: BuildExecutionData) {
|
||||
if (build.failure != null) return
|
||||
override fun process(build: BuildExecutionData, log: Logger) {
|
||||
if (build.failureMessages.isNotEmpty()) return
|
||||
|
||||
try {
|
||||
outputFile.parentFile?.apply { mkdirs() }
|
||||
@@ -35,8 +34,8 @@ internal class MetricsWriter(
|
||||
}
|
||||
|
||||
for (data in build.taskExecutionData) {
|
||||
val path = data.task.path
|
||||
val type = data.task::class.java.canonicalName
|
||||
val path = data.taskPath
|
||||
val type = data.type
|
||||
val buildTimes = data.buildMetrics.buildTimes.asMap().mapKeys { (k, _) -> k.name }
|
||||
val buildPerfMetrics = data.buildMetrics.buildPerformanceMetrics.asMap().mapKeys { (k, _) -> k.name }
|
||||
val buildAttributes = data.buildMetrics.buildAttributes.asMap().mapKeys { (k, _) -> k.name }
|
||||
@@ -47,7 +46,7 @@ internal class MetricsWriter(
|
||||
timeMetrics = buildTimes,
|
||||
performanceMetrics = buildPerfMetrics,
|
||||
buildAttributes = buildAttributes,
|
||||
didWork = data.task.didWork
|
||||
didWork = data.didWork
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+39
-14
@@ -12,17 +12,37 @@ import org.jetbrains.kotlin.gradle.report.data.BuildExecutionDataProcessor
|
||||
import org.jetbrains.kotlin.gradle.report.data.TaskExecutionData
|
||||
import org.jetbrains.kotlin.gradle.utils.Printer
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashSet
|
||||
import kotlin.math.max
|
||||
|
||||
internal class PlainTextBuildReportWriterDataProcessor(
|
||||
val reportingSettings: ReportingSettings,
|
||||
val reportDir: File, //from reportingSettings.buildReportDir to avoid null check
|
||||
val rootProjectName: String
|
||||
) : BuildExecutionDataProcessor, Serializable {
|
||||
override fun process(build: BuildExecutionData, log: Logger) {
|
||||
val ts = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(Calendar.getInstance().time)
|
||||
val reportFile = reportDir.resolve("${rootProjectName}-build-$ts.txt")
|
||||
|
||||
PlainTextBuildReportWriter(
|
||||
outputFile = reportFile,
|
||||
printMetrics = reportingSettings.includeMetricsInReport
|
||||
).process(build, log)
|
||||
}
|
||||
}
|
||||
|
||||
internal class PlainTextBuildReportWriter(
|
||||
private val outputFile: File,
|
||||
private val printMetrics: Boolean,
|
||||
private val log: Logger
|
||||
) : BuildExecutionDataProcessor {
|
||||
private val printMetrics: Boolean
|
||||
) : Serializable {
|
||||
|
||||
private lateinit var p: Printer
|
||||
|
||||
override fun process(build: BuildExecutionData) {
|
||||
fun process(build: BuildExecutionData, log: Logger) {
|
||||
try {
|
||||
outputFile.parentFile.mkdirs()
|
||||
if (!(outputFile.parentFile.exists() && outputFile.parentFile.isDirectory)) {
|
||||
@@ -54,8 +74,8 @@ internal class PlainTextBuildReportWriter(
|
||||
}
|
||||
|
||||
p.println()
|
||||
if (build.failure != null) {
|
||||
p.println("Build failed: ${build.failure}")
|
||||
if (build.failureMessages.isNotEmpty()) {
|
||||
p.println("Build failed: ${build.failureMessages}")
|
||||
}
|
||||
p.println()
|
||||
}
|
||||
@@ -79,9 +99,14 @@ internal class PlainTextBuildReportWriter(
|
||||
fun printBuildTime(buildTime: BuildTime) {
|
||||
if (!visitedBuildTimes.add(buildTime)) return
|
||||
|
||||
val timeMs = collectedBuildTimes[buildTime] ?: return
|
||||
p.println("${buildTime.name}: ${formatTime(timeMs)}")
|
||||
p.withIndent {
|
||||
val timeMs = collectedBuildTimes[buildTime]
|
||||
if (timeMs != null) {
|
||||
p.println("${buildTime.name}: ${formatTime(timeMs)}")
|
||||
p.withIndent {
|
||||
BuildTime.children[buildTime]?.forEach { printBuildTime(it) }
|
||||
}
|
||||
} else {
|
||||
//Skip formatting if parent metric does not set
|
||||
BuildTime.children[buildTime]?.forEach { printBuildTime(it) }
|
||||
}
|
||||
}
|
||||
@@ -147,7 +172,7 @@ internal class PlainTextBuildReportWriter(
|
||||
for (task in kotlinTasks.sortedByDescending { it.totalTimeMs }) {
|
||||
val timeMs = task.totalTimeMs
|
||||
val percent = (timeMs.toDouble() / kotlinTotalTimeMs * 100).asString(1)
|
||||
table.addRow(formatTime(timeMs), "$percent %", task.task.path)
|
||||
table.addRow(formatTime(timeMs), "$percent %", task.taskPath)
|
||||
}
|
||||
table.printTo(p)
|
||||
p.println()
|
||||
@@ -161,15 +186,15 @@ internal class PlainTextBuildReportWriter(
|
||||
}
|
||||
|
||||
private fun printTaskLog(task: TaskExecutionData) {
|
||||
val skipMessage = task.resultState.skipMessage
|
||||
val skipMessage = task.skipMessage
|
||||
if (skipMessage != null) {
|
||||
p.println("Task '${task.task.path}' was skipped: $skipMessage")
|
||||
p.println("Task '${task.taskPath}' was skipped: $skipMessage")
|
||||
} else {
|
||||
p.println("Task '${task.task.path}' finished in ${formatTime(task.totalTimeMs)}")
|
||||
p.println("Task '${task.taskPath}' finished in ${formatTime(task.totalTimeMs)}")
|
||||
}
|
||||
|
||||
if (task.icLogLines.isNotEmpty()) {
|
||||
p.withIndent("Compilation log for task '${task.task.path}':") {
|
||||
p.withIndent("Compilation log for task '${task.taskPath}':") {
|
||||
task.icLogLines.forEach { p.println(it) }
|
||||
}
|
||||
}
|
||||
|
||||
+1
-46
@@ -15,35 +15,7 @@ import org.jetbrains.kotlin.gradle.utils.isConfigurationCacheAvailable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
internal fun configureReporting(gradle: Gradle) {
|
||||
val buildDataProcessors = ArrayList<BuildExecutionDataProcessor>()
|
||||
|
||||
val rootProject = gradle.rootProject
|
||||
val log = rootProject.logger
|
||||
val reportingSettings = reportingSettings(rootProject)
|
||||
gradle.taskGraph.whenReady { graph ->
|
||||
graph.allTasks.asSequence()
|
||||
.filterIsInstance<AbstractKotlinCompile<*>>()
|
||||
.forEach { it.reportingSettings = reportingSettings }
|
||||
}
|
||||
|
||||
if (reportingSettings.buildReportMode != BuildReportMode.NONE && reportingSettings.buildReportDir != null) {
|
||||
configurePlainTextReportWriter(rootProject.name, log, reportingSettings)?.let {
|
||||
buildDataProcessors.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
if (reportingSettings.metricsOutputFile != null) {
|
||||
buildDataProcessors.add(MetricsWriter(reportingSettings.metricsOutputFile.absoluteFile, log))
|
||||
}
|
||||
|
||||
if (buildDataProcessors.isNotEmpty() && !isConfigurationCacheAvailable(gradle)) {
|
||||
val listener = BuildDataRecorder(gradle, buildDataProcessors)
|
||||
gradle.addBuildListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reportingSettings(rootProject: Project): ReportingSettings {
|
||||
internal fun reportingSettings(rootProject: Project): ReportingSettings {
|
||||
val properties = PropertiesProvider(rootProject)
|
||||
val buildReportMode =
|
||||
when {
|
||||
@@ -62,20 +34,3 @@ private fun reportingSettings(rootProject: Project): ReportingSettings {
|
||||
buildReportMode = buildReportMode
|
||||
)
|
||||
}
|
||||
|
||||
private fun configurePlainTextReportWriter(
|
||||
rootProjectName: String,
|
||||
log: Logger,
|
||||
reportingSettings: ReportingSettings
|
||||
): BuildExecutionDataProcessor? {
|
||||
return reportingSettings.buildReportDir?.let { reportDir ->
|
||||
val ts = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(Calendar.getInstance().time)
|
||||
val reportFile = reportDir.resolve("${rootProjectName}-build-$ts.txt")
|
||||
|
||||
return PlainTextBuildReportWriter(
|
||||
outputFile = reportFile,
|
||||
printMetrics = reportingSettings.includeMetricsInReport,
|
||||
log = log
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -7,9 +7,9 @@ package org.jetbrains.kotlin.gradle.report.data
|
||||
|
||||
import org.jetbrains.kotlin.build.report.metrics.BuildMetrics
|
||||
|
||||
internal class BuildExecutionData(
|
||||
class BuildExecutionData(
|
||||
val startParameters: Collection<String>,
|
||||
val failure: Throwable?,
|
||||
val failureMessages: List<String?>,
|
||||
val taskExecutionData: Collection<TaskExecutionData>
|
||||
) {
|
||||
val aggregatedMetrics by lazy {
|
||||
|
||||
+4
-2
@@ -5,6 +5,8 @@
|
||||
|
||||
package org.jetbrains.kotlin.gradle.report.data
|
||||
|
||||
internal interface BuildExecutionDataProcessor {
|
||||
fun process(build: BuildExecutionData)
|
||||
import org.gradle.api.logging.Logger
|
||||
|
||||
interface BuildExecutionDataProcessor {
|
||||
fun process(build: BuildExecutionData, log: Logger)
|
||||
}
|
||||
+7
-5
@@ -9,14 +9,16 @@ import org.gradle.api.Task
|
||||
import org.gradle.api.tasks.TaskState
|
||||
import org.jetbrains.kotlin.build.report.metrics.BuildMetrics
|
||||
|
||||
internal interface TaskExecutionData {
|
||||
val task: Task
|
||||
val startNs: Long
|
||||
val endNs: Long
|
||||
interface TaskExecutionData {
|
||||
val taskPath: String
|
||||
val startMs: Long
|
||||
val endMs: Long
|
||||
val totalTimeMs: Long
|
||||
val resultState: TaskState
|
||||
val skipMessage: String?
|
||||
val didWork: Boolean
|
||||
val icLogLines: List<String>
|
||||
val buildMetrics: BuildMetrics
|
||||
val isKotlinTask: Boolean
|
||||
val type: String
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -144,7 +144,7 @@ abstract class KotlinJsIrLink @Inject constructor(
|
||||
{ objects.fileCollection() },
|
||||
defaultCompilerClasspath,
|
||||
logger,
|
||||
reportingSettings
|
||||
reportingSettings()
|
||||
)
|
||||
val cacheArgs = visitCompilation(
|
||||
compilation as KotlinCompilation<*>,
|
||||
|
||||
+1
-1
@@ -146,7 +146,7 @@ abstract class KotlinCompileCommon @Inject constructor(
|
||||
val compilerRunner = compilerRunner.get()
|
||||
val environment = GradleCompilerEnvironment(
|
||||
defaultCompilerClasspath, messageCollector, outputItemCollector,
|
||||
reportingSettings = reportingSettings,
|
||||
reportingSettings = reportingSettings(),
|
||||
outputFiles = allOutputFiles()
|
||||
)
|
||||
compilerRunner.runMetadataCompilerAsync(sourceRoots.kotlinSourceFiles.files.toList(), args, environment)
|
||||
|
||||
+11
-3
@@ -50,6 +50,7 @@ import org.jetbrains.kotlin.gradle.plugin.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData
|
||||
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
|
||||
import org.jetbrains.kotlin.gradle.report.BuildMetricsReporterService
|
||||
import org.jetbrains.kotlin.gradle.report.ReportingSettings
|
||||
import org.jetbrains.kotlin.gradle.targets.js.ir.isProduceUnzippedKlib
|
||||
import org.jetbrains.kotlin.gradle.utils.*
|
||||
@@ -245,7 +246,12 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments> : AbstractKotl
|
||||
incremental
|
||||
|
||||
@get:Internal
|
||||
internal var reportingSettings = ReportingSettings()
|
||||
val startParameters = BuildMetricsReporterService.getStartParameters(project)
|
||||
|
||||
@get:Internal
|
||||
abstract val buildMetricsReporterService: Property<BuildMetricsReporterService?>
|
||||
|
||||
internal fun reportingSettings() = buildMetricsReporterService.orNull?.parameters?.reportingSettings ?: ReportingSettings()
|
||||
|
||||
@get:Input
|
||||
internal val useModuleDetection: Property<Boolean> = objects.property(Boolean::class.java).value(false)
|
||||
@@ -377,6 +383,8 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments> : AbstractKotl
|
||||
|
||||
executeImpl(inputChanges, outputsBackup)
|
||||
}
|
||||
|
||||
buildMetricsReporterService.orNull?.also { it.add(path, this::class.java.name, buildMetrics) }
|
||||
}
|
||||
|
||||
protected open fun skipCondition(): Boolean =
|
||||
@@ -762,7 +770,7 @@ abstract class KotlinCompile @Inject constructor(
|
||||
// outputFiles here. (See TaskOutputsBackup's kdoc for more info.)
|
||||
outputFiles = allOutputFiles()
|
||||
- (classpathSnapshotProperties.classpathSnapshotDir.orNull?.asFile?.let { setOf(it) } ?: emptySet()),
|
||||
reportingSettings = reportingSettings,
|
||||
reportingSettings = reportingSettings(),
|
||||
incrementalCompilationEnvironment = icEnv,
|
||||
kotlinScriptExtensions = sourceFilesExtensions.get().toTypedArray()
|
||||
)
|
||||
@@ -1124,7 +1132,7 @@ abstract class Kotlin2JsCompile @Inject constructor(
|
||||
val environment = GradleCompilerEnvironment(
|
||||
defaultCompilerClasspath, messageCollector, outputItemCollector,
|
||||
outputFiles = allOutputFiles(),
|
||||
reportingSettings = reportingSettings,
|
||||
reportingSettings = reportingSettings(),
|
||||
incrementalCompilationEnvironment = icEnv
|
||||
)
|
||||
compilerRunner.runJsCompilerAsync(
|
||||
|
||||
Reference in New Issue
Block a user