KT-41689: Support statistics for Configuration Cache

#KT-41689 Fixed
This commit is contained in:
nataliya.valtman
2021-11-01 21:43:51 +03:00
committed by Space
parent 84469d0c68
commit 9e4f3e3848
23 changed files with 327 additions and 231 deletions
@@ -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
@@ -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") {
@@ -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()
}
}
@@ -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() })
}
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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(
@@ -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"
@@ -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>) {
@@ -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
@@ -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()
}
@@ -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())
}
}
}
@@ -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)
}
@@ -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"
}
@@ -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
)
}
@@ -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) }
}
}
@@ -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
)
}
}
@@ -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 {
@@ -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)
}
@@ -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
}
@@ -144,7 +144,7 @@ abstract class KotlinJsIrLink @Inject constructor(
{ objects.fileCollection() },
defaultCompilerClasspath,
logger,
reportingSettings
reportingSettings()
)
val cacheArgs = visitCompilation(
compilation as KotlinCompilation<*>,
@@ -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)
@@ -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(