diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ConfigurationCacheIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ConfigurationCacheIT.kt index 9e211c73f6e..563989b3ed5 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ConfigurationCacheIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ConfigurationCacheIT.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt index 01fae766387..14689301f3f 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt @@ -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") { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt index a7c3e94183d..e08c30bd6e3 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt @@ -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() } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithKotlincTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithKotlincTask.kt index eb24a0bebc3..c28ad3c5f8a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithKotlincTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithKotlincTask.kt @@ -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() }) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/BuildEventsListenerRegistryHolder.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/BuildEventsListenerRegistryHolder.kt index ee799408a2d..311722a20b4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/BuildEventsListenerRegistryHolder.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/BuildEventsListenerRegistryHolder.kt @@ -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) } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildListener.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildListener.kt deleted file mode 100644 index 90008524041..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildListener.kt +++ /dev/null @@ -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) - } - -} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildServices.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildServices.kt index 6520c31f298..45d45c34b9e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildServices.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildServices.kt @@ -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, AutoCloseable { +internal abstract class KotlinGradleBuildServices : BuildService, AutoCloseable { interface Parameters : BuildServiceParameters { var buildDir: File @@ -29,7 +28,7 @@ abstract class KotlinGradleBuildServices : BuildService - 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(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(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( diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleFinishBuildHandler.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleFinishBuildHandler.kt index 03074b66c39..2a117fe6546 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleFinishBuildHandler.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleFinishBuildHandler.kt @@ -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" diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt index d836ce2e59c..baa25683d6d 100755 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt @@ -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) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index 380f2df59e6..63134cac72a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -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 { 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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/ReportStatisticsToBuildScan.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/ReportStatisticsToBuildScan.kt index 4a8ec4ce8ae..312c447469d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/ReportStatisticsToBuildScan.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/ReportStatisticsToBuildScan.kt @@ -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() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/BuildDataRecorder.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/BuildDataRecorder.kt deleted file mode 100644 index 6eaabc4799c..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/BuildDataRecorder.kt +++ /dev/null @@ -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 -) : BuildAdapter(), TaskExecutionListener { - private val taskRecords = HashMap() - - @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() - 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 = 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 - 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()) - } - } -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/BuildMetricsReporterService.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/BuildMetricsReporterService.kt new file mode 100644 index 00000000000..548e3e8bf57 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/BuildMetricsReporterService.kt @@ -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, + OperationCompletionListener, AutoCloseable { + + interface Parameters : BuildServiceParameters { + var buildDataProcessors: List + var startParameters: List + var reportingSettings: ReportingSettings + } + + private val log = Logging.getLogger(this.javaClass) + + //Task path to build metrics + private val buildMetricsMap = ConcurrentHashMap() + private val taskRecords = ConcurrentHashMap() + private val failureMessages = ConcurrentLinkedQueue() + private val taskClass = ConcurrentHashMap() + + 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() + 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): Provider? { + val buildDataProcessors = ArrayList() + 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, + 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) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/KotlinBuildReporterHandler.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/KotlinBuildReporterHandler.kt index 886b52df2c8..bdd9b460cae 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/KotlinBuildReporterHandler.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/KotlinBuildReporterHandler.kt @@ -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" } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/MetricsWriter.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/MetricsWriter.kt index 4d9b938f285..790a5b16474 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/MetricsWriter.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/MetricsWriter.kt @@ -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 ) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/PlainTextBuildReportWriter.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/PlainTextBuildReportWriter.kt index a9b88b5f323..84702fb6e89 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/PlainTextBuildReportWriter.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/PlainTextBuildReportWriter.kt @@ -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) } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/configureReporing.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/configureReporing.kt index ab539e674f2..108e15c18e6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/configureReporing.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/configureReporing.kt @@ -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() - - val rootProject = gradle.rootProject - val log = rootProject.logger - val reportingSettings = reportingSettings(rootProject) - gradle.taskGraph.whenReady { graph -> - graph.allTasks.asSequence() - .filterIsInstance>() - .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 - ) - } -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/data/BuildExecutionData.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/data/BuildExecutionData.kt index f286e1c41ad..8c0d08d4752 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/data/BuildExecutionData.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/data/BuildExecutionData.kt @@ -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, - val failure: Throwable?, + val failureMessages: List, val taskExecutionData: Collection ) { val aggregatedMetrics by lazy { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/data/BuildExecutionDataProcessor.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/data/BuildExecutionDataProcessor.kt index 3437939a7a2..c7f24521508 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/data/BuildExecutionDataProcessor.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/data/BuildExecutionDataProcessor.kt @@ -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) } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/data/TaskExecutionData.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/data/TaskExecutionData.kt index b6b095a89a5..c98bdc40c31 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/data/TaskExecutionData.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/data/TaskExecutionData.kt @@ -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 val buildMetrics: BuildMetrics val isKotlinTask: Boolean + val type: String } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrLink.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrLink.kt index d4d95197ff4..5d0ac926d0f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrLink.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrLink.kt @@ -144,7 +144,7 @@ abstract class KotlinJsIrLink @Inject constructor( { objects.fileCollection() }, defaultCompilerClasspath, logger, - reportingSettings + reportingSettings() ) val cacheArgs = visitCompilation( compilation as KotlinCompilation<*>, diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt index 6b99923be55..cb438fbf31f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt @@ -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) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index 71db176a33f..c1ee93cb156 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -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 : AbstractKotl incremental @get:Internal - internal var reportingSettings = ReportingSettings() + val startParameters = BuildMetricsReporterService.getStartParameters(project) + + @get:Internal + abstract val buildMetricsReporterService: Property + + internal fun reportingSettings() = buildMetricsReporterService.orNull?.parameters?.reportingSettings ?: ReportingSettings() @get:Input internal val useModuleDetection: Property = objects.property(Boolean::class.java).value(false) @@ -377,6 +383,8 @@ abstract class AbstractKotlinCompile : 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(