Add build reports for diagnosing build problems in Gradle

#KT-12700 Fixed

To turn build reports, add 'kotlin.build.report.enable=true' to
gradle.properties file
(or pass it in CLI via`-Pkotlin.build.report.enable=true`).
By default build reports are created in
`rootProject/build/reports/kotlin-build`.
Build report dir can be customized via `kotlin.build.report.dir`
property.
This commit is contained in:
Alexey Tsvetkov
2019-01-28 15:35:26 +03:00
parent 8fc8b95d83
commit 98ef00b957
15 changed files with 375 additions and 27 deletions
@@ -26,5 +26,6 @@ interface CompilationResults : Remote {
} }
enum class CompilationResultCategory(val code: Int) { enum class CompilationResultCategory(val code: Int) {
IC_COMPILE_ITERATION(0) IC_COMPILE_ITERATION(0),
IC_LOG(1)
} }
@@ -511,13 +511,18 @@ class CompileServiceImpl(
val workingDir = incrementalCompilationOptions.workingDir val workingDir = incrementalCompilationOptions.workingDir
val modulesApiHistory = ModulesApiHistoryJs(incrementalCompilationOptions.modulesInfo) val modulesApiHistory = ModulesApiHistoryJs(incrementalCompilationOptions.modulesInfo)
val compiler = IncrementalJsCompilerRunner( val compiler = IncrementalJsCompilerRunner(
workingDir = workingDir, workingDir = workingDir,
reporter = reporter, reporter = reporter,
buildHistoryFile = incrementalCompilationOptions.multiModuleICSettings.buildHistoryFile, buildHistoryFile = incrementalCompilationOptions.multiModuleICSettings.buildHistoryFile,
modulesApiHistory = modulesApiHistory modulesApiHistory = modulesApiHistory
) )
return compiler.compile(allKotlinFiles, args, compilerMessageCollector, changedFiles) return try {
compiler.compile(allKotlinFiles, args, compilerMessageCollector, changedFiles)
} finally {
reporter.flush()
}
} }
private fun execIncrementalCompiler( private fun execIncrementalCompiler(
@@ -570,6 +575,8 @@ class CompileServiceImpl(
val workingDir = incrementalCompilationOptions.workingDir val workingDir = incrementalCompilationOptions.workingDir
val modulesApiHistory = incrementalCompilationOptions.run { val modulesApiHistory = incrementalCompilationOptions.run {
reporter.report { "Use module detection: ${multiModuleICSettings.useModuleDetection}" }
if (!multiModuleICSettings.useModuleDetection) { if (!multiModuleICSettings.useModuleDetection) {
ModulesApiHistoryJvm(modulesInfo) ModulesApiHistoryJvm(modulesInfo)
} else { } else {
@@ -587,7 +594,11 @@ class CompileServiceImpl(
modulesApiHistory = modulesApiHistory, modulesApiHistory = modulesApiHistory,
kotlinSourceFilesExtensions = allKotlinExtensions kotlinSourceFilesExtensions = allKotlinExtensions
) )
return compiler.compile(allKotlinFiles, k2jvmArgs, compilerMessageCollector, changedFiles) return try {
compiler.compile(allKotlinFiles, k2jvmArgs, compilerMessageCollector, changedFiles)
} finally {
reporter.flush()
}
} }
override fun leaseReplSession( override fun leaseReplSession(
@@ -22,26 +22,54 @@ import org.jetbrains.kotlin.incremental.ICReporter
import java.io.File import java.io.File
internal class RemoteICReporter( internal class RemoteICReporter(
private val servicesFacade: CompilerServicesFacadeBase, private val servicesFacade: CompilerServicesFacadeBase,
private val compilationResults: CompilationResults, private val compilationResults: CompilationResults,
compilationOptions: CompilationOptions compilationOptions: IncrementalCompilationOptions
) : ICReporter { ) : ICReporter {
private val rootDir = compilationOptions.modulesInfo.projectRoot
private val shouldReportMessages = ReportCategory.IC_MESSAGE.code in compilationOptions.reportCategories private val shouldReportMessages = ReportCategory.IC_MESSAGE.code in compilationOptions.reportCategories
private val isVerbose = compilationOptions.reportSeverity == ReportSeverity.DEBUG.code private val isVerbose = compilationOptions.reportSeverity == ReportSeverity.DEBUG.code
private val shouldReportCompileIteration = CompilationResultCategory.IC_COMPILE_ITERATION.code in compilationOptions.requestedCompilationResults private val shouldReportCompileIteration =
CompilationResultCategory.IC_COMPILE_ITERATION.code in compilationOptions.requestedCompilationResults
private val shouldReportICLog = CompilationResultCategory.IC_LOG.code in compilationOptions.requestedCompilationResults
private val icLogLines = arrayListOf<String>()
override fun report(message: () -> String) { override fun report(message: () -> String) {
val lazyMessage = lazy { message() }
if (shouldReportMessages && isVerbose) { if (shouldReportMessages && isVerbose) {
servicesFacade.report(ReportCategory.IC_MESSAGE, ReportSeverity.DEBUG, message()) servicesFacade.report(ReportCategory.IC_MESSAGE, ReportSeverity.DEBUG, lazyMessage.value)
}
if (shouldReportICLog) {
icLogLines.add(lazyMessage.value)
} }
} }
override fun reportCompileIteration(sourceFiles: Collection<File>, exitCode: ExitCode) { override fun reportCompileIteration(sourceFiles: Collection<File>, exitCode: ExitCode) {
if (shouldReportCompileIteration) { if (shouldReportCompileIteration) {
compilationResults.add(CompilationResultCategory.IC_COMPILE_ITERATION.code, compilationResults.add(
CompileIterationResult(sourceFiles, exitCode.toString()) CompilationResultCategory.IC_COMPILE_ITERATION.code,
CompileIterationResult(sourceFiles, exitCode.toString())
) )
} }
if (shouldReportICLog) {
icLogLines.add("compile iteration:")
sourceFiles.relativePaths(rootDir).forEach {
icLogLines.add(" $it")
}
}
} }
fun flush() {
if (shouldReportICLog) {
compilationResults.add(CompilationResultCategory.IC_LOG.code, icLogLines)
}
}
private fun File.relativeOrCanonical(base: File): String =
relativeToOrNull(base)?.path ?: canonicalPath
private fun Iterable<File>.relativePaths(base: File): List<String> =
map { it.relativeOrCanonical(base) }.sorted()
} }
@@ -21,18 +21,27 @@ internal class GradleCompilationResults(
LoopbackNetworkInterface.clientLoopbackSocketFactory, LoopbackNetworkInterface.clientLoopbackSocketFactory,
LoopbackNetworkInterface.serverLoopbackSocketFactory LoopbackNetworkInterface.serverLoopbackSocketFactory
) { ) {
var icLogLines: List<String>? = null
@Throws(RemoteException::class) @Throws(RemoteException::class)
override fun add(compilationResultCategory: Int, value: Serializable) { override fun add(compilationResultCategory: Int, value: Serializable) {
if (compilationResultCategory == CompilationResultCategory.IC_COMPILE_ITERATION.code) { when (compilationResultCategory) {
@Suppress("UNCHECKED_CAST") CompilationResultCategory.IC_COMPILE_ITERATION.code -> {
val compileIterationResult = value as? CompileIterationResult @Suppress("UNCHECKED_CAST")
if (compileIterationResult != null) { val compileIterationResult = value as? CompileIterationResult
val sourceFiles = compileIterationResult.sourceFiles if (compileIterationResult != null) {
if (sourceFiles.any()) { val sourceFiles = compileIterationResult.sourceFiles
log.kotlinDebug { "compile iteration: ${sourceFiles.pathsAsStringRelativeTo(projectRootFile)}" } if (sourceFiles.any()) {
log.kotlinDebug { "compile iteration: ${sourceFiles.pathsAsStringRelativeTo(projectRootFile)}" }
}
val exitCode = compileIterationResult.exitCode
log.kotlinDebug { "compiler exit code: $exitCode" }
} }
val exitCode = compileIterationResult.exitCode }
log.kotlinDebug { "compiler exit code: $exitCode" } CompilationResultCategory.IC_LOG.code -> {
@Suppress("UNCHECKED_CAST")
icLogLines = value as? List<String>
} }
} }
} }
@@ -16,6 +16,7 @@ internal class GradleCompilerEnvironment(
messageCollector: GradlePrintingMessageCollector, messageCollector: GradlePrintingMessageCollector,
outputItemsCollector: OutputItemsCollector, outputItemsCollector: OutputItemsCollector,
val outputFiles: FileCollection, val outputFiles: FileCollection,
val reportExecutionResult: Boolean,
val incrementalCompilationEnvironment: IncrementalCompilationEnvironment? = null val incrementalCompilationEnvironment: IncrementalCompilationEnvironment? = null
) : CompilerEnvironment(Services.EMPTY, messageCollector, outputItemsCollector) { ) : CompilerEnvironment(Services.EMPTY, messageCollector, outputItemsCollector) {
val toolsJar: File? by lazy { findToolsJar() } val toolsJar: File? by lazy { findToolsJar() }
@@ -141,7 +141,8 @@ internal open class GradleCompilerRunner(protected val task: Task) {
incrementalModuleInfo = modulesInfo, incrementalModuleInfo = modulesInfo,
buildFile = buildFile, buildFile = buildFile,
outputFiles = environment.outputFiles.toList(), outputFiles = environment.outputFiles.toList(),
taskPath = task.path taskPath = task.path,
reportExecutionResult = environment.reportExecutionResult
) )
TaskLoggers.put(task.path, task.logger) TaskLoggers.put(task.path, task.logger)
runCompilerAsync(workArgs) runCompilerAsync(workArgs)
@@ -11,7 +11,9 @@ import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.config.Services import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.daemon.common.* import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.gradle.logging.* import org.jetbrains.kotlin.gradle.logging.*
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers
import org.jetbrains.kotlin.gradle.report.TaskExecutionResult
import org.jetbrains.kotlin.gradle.tasks.throwGradleExceptionIfError import org.jetbrains.kotlin.gradle.tasks.throwGradleExceptionIfError
import org.jetbrains.kotlin.gradle.utils.stackTraceAsString import org.jetbrains.kotlin.gradle.utils.stackTraceAsString
import org.jetbrains.kotlin.incremental.ChangedFiles import org.jetbrains.kotlin.incremental.ChangedFiles
@@ -20,6 +22,7 @@ import org.slf4j.LoggerFactory
import java.io.* import java.io.*
import java.net.URLClassLoader import java.net.URLClassLoader
import java.rmi.RemoteException import java.rmi.RemoteException
import java.util.*
import java.util.concurrent.Callable import java.util.concurrent.Callable
import java.util.concurrent.Executors import java.util.concurrent.Executors
import javax.inject.Inject import javax.inject.Inject
@@ -50,7 +53,8 @@ internal class GradleKotlinCompilerWorkArguments(
val incrementalModuleInfo: IncrementalModuleInfo?, val incrementalModuleInfo: IncrementalModuleInfo?,
val buildFile: File?, val buildFile: File?,
val outputFiles: List<File>, val outputFiles: List<File>,
val taskPath: String val taskPath: String,
val reportExecutionResult: Boolean
) : Serializable { ) : Serializable {
companion object { companion object {
const val serialVersionUID: Long = 0 const val serialVersionUID: Long = 0
@@ -88,6 +92,7 @@ internal class GradleKotlinCompilerWork @Inject constructor(
private val buildFile = config.buildFile private val buildFile = config.buildFile
private val outputFiles = config.outputFiles private val outputFiles = config.outputFiles
private val taskPath = config.taskPath private val taskPath = config.taskPath
private val reportExecutionResult = config.reportExecutionResult
private val log: KotlinLogger = private val log: KotlinLogger =
TaskLoggers.get(taskPath)?.let { GradleKotlinLogger(it).apply { debug("Using '$taskPath' logger") } } TaskLoggers.get(taskPath)?.let { GradleKotlinLogger(it).apply { debug("Using '$taskPath' logger") } }
@@ -226,7 +231,16 @@ internal class GradleKotlinCompilerWork @Inject constructor(
requestedCompilationResults = emptyArray() requestedCompilationResults = emptyArray()
) )
val servicesFacade = GradleCompilerServicesFacadeImpl(log, bufferingMessageCollector) val servicesFacade = GradleCompilerServicesFacadeImpl(log, bufferingMessageCollector)
return daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults = null) return try {
daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults = null)
} finally {
reportExecutionResultIfNeeded {
TaskExecutionResult(
executionStrategy = DAEMON_EXECUTION_STRATEGY,
icLogLines = nonIcBuildLog("incremental compilation is not enabled for '$taskPath'")
)
}
}
} }
private fun incrementalCompilationWithDaemon( private fun incrementalCompilationWithDaemon(
@@ -238,6 +252,11 @@ internal class GradleKotlinCompilerWork @Inject constructor(
val icEnv = incrementalCompilationEnvironment ?: error("incrementalCompilationEnvironment is null!") val icEnv = incrementalCompilationEnvironment ?: error("incrementalCompilationEnvironment is null!")
val knownChangedFiles = icEnv.changedFiles as? ChangedFiles.Known val knownChangedFiles = icEnv.changedFiles as? ChangedFiles.Known
val requestedCompilationResults = EnumSet.of(CompilationResultCategory.IC_COMPILE_ITERATION)
if (reportExecutionResult) {
requestedCompilationResults.add(CompilationResultCategory.IC_LOG)
}
val compilationOptions = IncrementalCompilationOptions( val compilationOptions = IncrementalCompilationOptions(
areFileChangesKnown = knownChangedFiles != null, areFileChangesKnown = knownChangedFiles != null,
modifiedFiles = knownChangedFiles?.modified, modifiedFiles = knownChangedFiles?.modified,
@@ -245,7 +264,7 @@ internal class GradleKotlinCompilerWork @Inject constructor(
workingDir = icEnv.workingDir, workingDir = icEnv.workingDir,
reportCategories = reportCategories(isVerbose), reportCategories = reportCategories(isVerbose),
reportSeverity = reportSeverity(isVerbose), reportSeverity = reportSeverity(isVerbose),
requestedCompilationResults = arrayOf(CompilationResultCategory.IC_COMPILE_ITERATION.code), requestedCompilationResults = requestedCompilationResults.map { it.code }.toTypedArray(),
compilerMode = CompilerMode.INCREMENTAL_COMPILER, compilerMode = CompilerMode.INCREMENTAL_COMPILER,
targetPlatform = targetPlatform, targetPlatform = targetPlatform,
usePreciseJavaTracking = icEnv.usePreciseJavaTracking, usePreciseJavaTracking = icEnv.usePreciseJavaTracking,
@@ -257,11 +276,29 @@ internal class GradleKotlinCompilerWork @Inject constructor(
log.info("Options for KOTLIN DAEMON: $compilationOptions") log.info("Options for KOTLIN DAEMON: $compilationOptions")
val servicesFacade = GradleIncrementalCompilerServicesFacadeImpl(log, bufferingMessageCollector) val servicesFacade = GradleIncrementalCompilerServicesFacadeImpl(log, bufferingMessageCollector)
val compilationResults = GradleCompilationResults(log, projectRootFile) val compilationResults = GradleCompilationResults(log, projectRootFile)
return daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults) val result = daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults)
reportExecutionResultIfNeeded {
TaskExecutionResult(
executionStrategy = DAEMON_EXECUTION_STRATEGY,
icLogLines = compilationResults.icLogLines
)
}
return result
} }
private fun compileOutOfProcess(): ExitCode = private fun compileOutOfProcess(): ExitCode =
runToolInSeparateProcess(compilerArgs, compilerClassName, compilerFullClasspath, log) try {
runToolInSeparateProcess(compilerArgs, compilerClassName, compilerFullClasspath, log)
} finally {
reportExecutionResultIfNeeded {
TaskExecutionResult(
executionStrategy = OUT_OF_PROCESS_EXECUTION_STRATEGY,
icLogLines = nonIcBuildLog("$OUT_OF_PROCESS_EXECUTION_STRATEGY execution strategy does not support incremental compilation")
)
}
}
private fun compileInProcess(messageCollector: MessageCollector): ExitCode { private fun compileInProcess(messageCollector: MessageCollector): ExitCode {
// in-process compiler should always be run in a different thread // in-process compiler should always be run in a different thread
@@ -276,6 +313,13 @@ internal class GradleKotlinCompilerWork @Inject constructor(
} finally { } finally {
bufferingMessageCollector.flush(messageCollector) bufferingMessageCollector.flush(messageCollector)
threadPool.shutdown() threadPool.shutdown()
reportExecutionResultIfNeeded {
TaskExecutionResult(
executionStrategy = IN_PROCESS_EXECUTION_STRATEGY,
icLogLines = nonIcBuildLog("$IN_PROCESS_EXECUTION_STRATEGY execution strategy does not support incremental compilation")
)
}
} }
} }
@@ -320,4 +364,14 @@ internal class GradleKotlinCompilerWork @Inject constructor(
} else { } else {
ReportSeverity.DEBUG.code ReportSeverity.DEBUG.code
} }
private inline fun reportExecutionResultIfNeeded(fn: () -> TaskExecutionResult) {
if (reportExecutionResult) {
val result = fn()
TaskExecutionResults[taskPath] = result
}
}
private fun nonIcBuildLog(reason: String): List<String> =
listOf("Performing non-incremental build: $reason")
} }
@@ -64,6 +64,7 @@ open class KaptWithKotlincTask : KaptTask(), CompilerArgumentAwareWithInput<K2JV
val outputItemCollector = OutputItemsCollectorImpl() val outputItemCollector = OutputItemsCollectorImpl()
val environment = GradleCompilerEnvironment( val environment = GradleCompilerEnvironment(
compilerClasspath, messageCollector, outputItemCollector, compilerClasspath, messageCollector, outputItemCollector,
reportExecutionResult = kotlinCompileTask.reportExecutionResult,
outputFiles = allOutputFiles() outputFiles = allOutputFiles()
) )
if (environment.toolsJar == null && !isAtLeastJava9) { if (environment.toolsJar == null && !isAtLeastJava9) {
@@ -19,16 +19,26 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.BuildAdapter import org.gradle.BuildAdapter
import org.gradle.BuildResult import org.gradle.BuildResult
import org.gradle.api.invocation.Gradle import org.gradle.api.invocation.Gradle
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.compilerRunner.DELETED_SESSION_FILE_PREFIX import org.jetbrains.kotlin.compilerRunner.DELETED_SESSION_FILE_PREFIX
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers
import org.jetbrains.kotlin.gradle.logging.kotlinDebug import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
import org.jetbrains.kotlin.gradle.report.KotlinBuildReporter
import org.jetbrains.kotlin.gradle.report.configureBuildReporter
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.utils.relativeToRoot import org.jetbrains.kotlin.gradle.utils.relativeToRoot
import org.jetbrains.kotlin.utils.addToStdlib.sumByLong import org.jetbrains.kotlin.utils.addToStdlib.sumByLong
import java.io.File
import java.lang.management.ManagementFactory import java.lang.management.ManagementFactory
import java.text.SimpleDateFormat
import java.util.*
internal class KotlinGradleBuildServices private constructor(gradle: Gradle) : BuildAdapter() { internal class KotlinGradleBuildServices private constructor(
private val gradle: Gradle
) : BuildAdapter() {
companion object { companion object {
private val CLASS_NAME = KotlinGradleBuildServices::class.java.simpleName private val CLASS_NAME = KotlinGradleBuildServices::class.java.simpleName
const val FORCE_SYSTEM_GC_MESSAGE = "Forcing System.gc()" const val FORCE_SYSTEM_GC_MESSAGE = "Forcing System.gc()"
@@ -68,10 +78,17 @@ internal class KotlinGradleBuildServices private constructor(gradle: Gradle) : B
// but it is called before any plugin can attach build listener // but it is called before any plugin can attach build listener
fun buildStarted() { fun buildStarted() {
startMemory = getUsedMemoryKb() startMemory = getUsedMemoryKb()
TaskLoggers.clear() TaskLoggers.clear()
TaskExecutionResults.clear()
configureBuildReporter(gradle, log)
} }
override fun buildFinished(result: BuildResult) { override fun buildFinished(result: BuildResult) {
TaskLoggers.clear()
TaskExecutionResults.clear()
val gradle = result.gradle!! val gradle = result.gradle!!
GradleCompilerRunner.clearBuildModulesInfo() GradleCompilerRunner.clearBuildModulesInfo()
@@ -100,7 +117,6 @@ internal class KotlinGradleBuildServices private constructor(gradle: Gradle) : B
log.lifecycle("[KOTLIN][PERF] Used memory after build: $endMem kb (difference since build start: ${"%+d".format(endMem - startMem)} kb)") log.lifecycle("[KOTLIN][PERF] Used memory after build: $endMem kb (difference since build start: ${"%+d".format(endMem - startMem)} kb)")
} }
TaskLoggers.clear()
gradle.removeListener(this) gradle.removeListener(this)
instance = null instance = null
log.kotlinDebug(DISPOSE_MESSAGE) log.kotlinDebug(DISPOSE_MESSAGE)
@@ -122,3 +138,4 @@ internal class KotlinGradleBuildServices private constructor(gradle: Gradle) : B
private fun getGcCount(): Long = private fun getGcCount(): Long =
ManagementFactory.getGarbageCollectorMXBeans().sumByLong { Math.max(0, it.collectionCount) } ManagementFactory.getGarbageCollectorMXBeans().sumByLong { Math.max(0, it.collectionCount) }
} }
@@ -18,9 +18,11 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.Coroutines import org.jetbrains.kotlin.gradle.dsl.Coroutines
import org.jetbrains.kotlin.gradle.report.BuildReportMode
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File
import java.util.* import java.util.*
internal fun PropertiesProvider.mapKotlinTaskProperties(task: AbstractKotlinCompile<*>) { internal fun PropertiesProvider.mapKotlinTaskProperties(task: AbstractKotlinCompile<*>) {
@@ -54,6 +56,12 @@ internal class PropertiesProvider(private val project: Project) {
val coroutines: Coroutines? val coroutines: Coroutines?
get() = property("kotlin.coroutines")?.let { Coroutines.byCompilerArgument(it) } get() = property("kotlin.coroutines")?.let { Coroutines.byCompilerArgument(it) }
val buildReportEnabled: Boolean
get() = booleanProperty("kotlin.build.report.enable") ?: false
val buildReportDir: File?
get() = property("kotlin.build.report.dir")?.let { File(it) }
val incrementalJvm: Boolean? val incrementalJvm: Boolean?
get() = booleanProperty("kotlin.incremental") get() = booleanProperty("kotlin.incremental")
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. 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.internal.state
import org.jetbrains.kotlin.gradle.report.TaskExecutionResult
import java.util.concurrent.ConcurrentHashMap
internal object TaskExecutionResults {
private val results = ConcurrentHashMap<String, TaskExecutionResult>()
operator fun get(taskPath: String): TaskExecutionResult? =
results[taskPath]
operator fun set(taskPath: String, result: TaskExecutionResult) {
results[taskPath] = result
}
fun clear() {
results.clear()
}
}
@@ -0,0 +1,176 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. 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.logging.Logger
import org.gradle.api.tasks.TaskState
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import java.io.File
import java.lang.StringBuilder
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.max
internal fun configureBuildReporter(gradle: Gradle, log: Logger) {
val rootProject = gradle.rootProject
val properties = PropertiesProvider(rootProject)
if (!properties.buildReportEnabled) return
val perfLogDir = properties.buildReportDir
?: rootProject.buildDir.resolve("reports/kotlin-build").apply { mkdirs() }
if (perfLogDir.isFile) {
log.error("Kotlin build report cannot be created: '$perfLogDir' is a file")
return
}
perfLogDir.mkdirs()
val ts = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(Calendar.getInstance().time)
val perfReportFile = perfLogDir.resolve("${gradle.rootProject.name}-build-$ts.txt")
val reporter = KotlinBuildReporter(perfReportFile)
gradle.addBuildListener(reporter)
gradle.taskGraph.whenReady { graph ->
graph.allTasks.asSequence()
.filterIsInstance<AbstractKotlinCompile<*>>()
.forEach { it.reportExecutionResult = true }
}
log.kotlinDebug { "Configured Kotlin build reporter" }
}
internal class KotlinBuildReporter(private val perfReportFile: File) : BuildAdapter(), TaskExecutionListener {
init {
val dir = perfReportFile.parentFile
check(dir.isDirectory) { "$dir does not exist or is a file" }
check(!perfReportFile.isFile) { "Build report log file $perfReportFile exists already" }
}
private val taskStartNs = HashMap<Task, Long>()
private val kotlinTaskTimeNs = HashMap<Task, Long>()
private val tasksSb = StringBuilder()
@Volatile
private var allTasksTimeNs: Long = 0L
@Synchronized
override fun beforeExecute(task: Task) {
taskStartNs[task] = System.nanoTime()
}
@Synchronized
override fun afterExecute(task: Task, state: TaskState) {
val startNs = taskStartNs[task] ?: return
val endNs = System.nanoTime()
val timeNs = endNs - startNs
allTasksTimeNs += timeNs
if (!task.javaClass.name.startsWith("org.jetbrains.kotlin")) {
return
}
kotlinTaskTimeNs[task] = timeNs
tasksSb.appendln()
val skipMessage = state.skipMessage
if (skipMessage != null) {
tasksSb.appendln("$task was skipped: $skipMessage")
} else {
tasksSb.appendln("$task finished in ${formatTime(timeNs)}")
}
val path = task.path
val executionResult = TaskExecutionResults[path]
if (executionResult != null) {
tasksSb.appendln("Execution strategy: ${executionResult.executionStrategy}")
executionResult.icLogLines?.let { lines ->
tasksSb.appendln("Compilation log for $task:")
lines.forEach { tasksSb.appendln(" $it") }
}
}
}
@Synchronized
override fun buildFinished(result: BuildResult) {
val logger = result.gradle?.rootProject?.logger
try {
perfReportFile.writeText(taskOverview() + tasksSb.toString())
logger?.lifecycle("Kotlin build report is written to ${perfReportFile.canonicalPath}")
} catch (e: Throwable) {
logger?.error("Could not write Kotlin build report to ${perfReportFile.canonicalPath}", e)
}
}
private fun taskOverview(): String {
val sb = StringBuilder()
val kotlinTotalTimeNs = kotlinTaskTimeNs.values.sum()
val ktTaskPercent = (kotlinTotalTimeNs.toDouble() / allTasksTimeNs * 100).asString(1)
sb.appendln("Total time for Kotlin tasks: ${formatTime(kotlinTotalTimeNs)} ($ktTaskPercent % of all tasks time)")
val table = TextTable("Time", "% of Kotlin time", "Task")
kotlinTaskTimeNs.entries
.sortedByDescending { (_, timeNs) -> timeNs }
.forEach { (task, timeNs) ->
val percent = (timeNs.toDouble() / kotlinTotalTimeNs * 100).asString(1)
table.addRow(formatTime(timeNs), "$percent %", task.path)
}
table.printTo(sb)
sb.appendln()
return sb.toString()
}
private fun formatTime(ns: Long): String {
val seconds = ns.toDouble() / 1_000_000_000
return seconds.asString(2) + " s"
}
private fun Double.asString(decPoints: Int): String =
String.format("%.${decPoints}f", this)
private class TextTable(vararg columnNames: String) {
private val rows = ArrayList<List<String>>()
private val columnsCount = columnNames.size
private val maxLengths = IntArray(columnsCount) { columnNames[it].length }
init {
rows.add(columnNames.toList())
}
fun addRow(vararg row: String) {
check(row.size == columnsCount) { "Row size ${row.size} differs from columns count $columnsCount" }
rows.add(row.toList())
for ((i, col) in row.withIndex()) {
maxLengths[i] = max(maxLengths[i], col.length)
}
}
fun printTo(sb: StringBuilder) {
for (row in rows) {
sb.appendln()
for ((i, col) in row.withIndex()) {
if (i > 0) sb.append("|")
sb.append(col.padEnd(maxLengths[i], ' '))
}
}
}
}
}
@@ -0,0 +1,11 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. 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
internal class TaskExecutionResult(
val executionStrategy: String,
val icLogLines: List<String>?
)
@@ -71,6 +71,7 @@ open class KotlinCompileCommon : AbstractKotlinCompile<K2MetadataCompilerArgumen
val compilerRunner = compilerRunner() val compilerRunner = compilerRunner()
val environment = GradleCompilerEnvironment( val environment = GradleCompilerEnvironment(
computedCompilerClasspath, messageCollector, outputItemCollector, computedCompilerClasspath, messageCollector, outputItemCollector,
reportExecutionResult = reportExecutionResult,
outputFiles = allOutputFiles() outputFiles = allOutputFiles()
) )
compilerRunner.runMetadataCompilerAsync(sourceRoots.kotlinSourceFiles, args, environment) compilerRunner.runMetadataCompilerAsync(sourceRoots.kotlinSourceFiles, args, environment)
@@ -132,6 +132,9 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
logger.kotlinDebug { "Set $this.incremental=$value" } logger.kotlinDebug { "Set $this.incremental=$value" }
} }
@get:Internal
var reportExecutionResult: Boolean = false
@get:Internal @get:Internal
internal val buildHistoryFile: File internal val buildHistoryFile: File
get() = File(taskBuildDirectory, "build-history.bin") get() = File(taskBuildDirectory, "build-history.bin")
@@ -436,6 +439,7 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
val environment = GradleCompilerEnvironment( val environment = GradleCompilerEnvironment(
computedCompilerClasspath, messageCollector, outputItemCollector, computedCompilerClasspath, messageCollector, outputItemCollector,
outputFiles = allOutputFiles(), outputFiles = allOutputFiles(),
reportExecutionResult = reportExecutionResult,
incrementalCompilationEnvironment = icEnv incrementalCompilationEnvironment = icEnv
) )
compilerRunner.runJvmCompilerAsync( compilerRunner.runJvmCompilerAsync(
@@ -593,6 +597,7 @@ open class Kotlin2JsCompile : AbstractKotlinCompile<K2JSCompilerArguments>(), Ko
val environment = GradleCompilerEnvironment( val environment = GradleCompilerEnvironment(
computedCompilerClasspath, messageCollector, outputItemCollector, computedCompilerClasspath, messageCollector, outputItemCollector,
outputFiles = allOutputFiles(), outputFiles = allOutputFiles(),
reportExecutionResult = reportExecutionResult,
incrementalCompilationEnvironment = icEnv incrementalCompilationEnvironment = icEnv
) )
compilerRunner.runJsCompilerAsync(sourceRoots.kotlinSourceFiles, commonSourceSet.toList(), args, environment) compilerRunner.runJsCompilerAsync(sourceRoots.kotlinSourceFiles, commonSourceSet.toList(), args, environment)