diff --git a/build.gradle.kts b/build.gradle.kts index afa56f06a5a..c7926923c2e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -664,3 +664,5 @@ tasks.create("findShadowJarsInClasspath").doLast { project.checkConfig("testCompileClasspath") } } + +BuildTimeReporter.configure(gradle) \ No newline at end of file diff --git a/build.gradle.kts.182 b/build.gradle.kts.182 index 72704ac610d..27630bad3fa 100644 --- a/build.gradle.kts.182 +++ b/build.gradle.kts.182 @@ -663,3 +663,5 @@ tasks.create("findShadowJarsInClasspath").doLast { project.checkConfig("testCompileClasspath") } } + +BuildTimeReporter.configure(gradle) \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/BuildTimeReporter.kt b/buildSrc/src/main/kotlin/BuildTimeReporter.kt new file mode 100644 index 00000000000..ab75e5ce89f --- /dev/null +++ b/buildSrc/src/main/kotlin/BuildTimeReporter.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2010-2018 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. + */ + +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 java.util.HashMap + +class BuildTimeReporter( + private val kotlinCompileClass: Class<*>, + private val javaCompileClass: Class<*>, + private val jarClass: Class<*>, + private val proguardClass: Class<*> +) : TaskExecutionListener { + companion object { + fun configure(gradle: Gradle) { + val rootProject = gradle.rootProject + val logger = rootProject.logger + val classloader = rootProject.buildscript.classLoader + + fun findClass(name: String): Class<*>? = + try { + Class.forName(name, false, classloader) + } catch (e: ClassNotFoundException) { + logger.warn("Could not find class '$name'. Build times won't be reported") + null + } + + val reporter = BuildTimeReporter( + kotlinCompileClass = findClass("org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile") ?: return, + javaCompileClass = findClass("org.gradle.api.tasks.compile.JavaCompile") ?: return, + jarClass = findClass("org.gradle.jvm.tasks.Jar") ?: return, + proguardClass = findClass("proguard.gradle.ProGuardTask") ?: return + ) + gradle.taskGraph.addTaskExecutionListener(reporter) + gradle.buildFinished { + reporter.report(logger) + } + } + } + + private enum class TaskCategory { + COMPILING_KOTLIN, + COMPILING_JAVA, + PROCESSING_JARS, + UNCATEGORIZED; + + private fun String.toTitleCase() = + if (length <= 1) capitalize() else { get(0).toUpperCase() + substring(1).toLowerCase() } + + fun description(): String = + name.replace("_", " ").toTitleCase() + } + + private val taskStartTime = HashMap() + private val categoryTimeNs = HashMap() + private var totalTimeNs = 0L + + @Synchronized + override fun beforeExecute(task: Task) { + taskStartTime[task] = System.nanoTime() + } + + @Synchronized + override fun afterExecute(task: Task, state: TaskState) { + val startTimeNs = taskStartTime.remove(task) ?: return + val endTimeNs = System.nanoTime() + val timeNs = endTimeNs - startTimeNs + totalTimeNs += timeNs + val category = taskCategory(task) + categoryTimeNs[category] = (categoryTimeNs[category] ?: 0L) + timeNs + } + + private fun taskCategory(task: Task): TaskCategory = when { + kotlinCompileClass.isInstance(task) -> TaskCategory.COMPILING_KOTLIN + javaCompileClass.isInstance(task) -> TaskCategory.COMPILING_JAVA + jarClass.isInstance(task) || + proguardClass.isInstance(task) -> TaskCategory.PROCESSING_JARS + else -> TaskCategory.UNCATEGORIZED + } + + private fun Double.asShortString() = + String.format("%.2f", this) + + @Synchronized + private fun report(log: Logger) { + val secondInNs = 1000_000_000 + val totalTimeSec = totalTimeNs.toDouble() / secondInNs + if (totalTimeSec < 1) return + + log.warn("Build time for tasks:") + for (category in TaskCategory.values()) { + val timeNs = categoryTimeNs[category] ?: 0L + val timeSec = timeNs.toDouble() / secondInNs + if (timeSec < 1) continue + + val percent = timeSec / totalTimeSec * 100 + log.warn("${category.description()}: ${timeSec.asShortString()}s (${percent.asShortString()}% of total time)") + } + } +} \ No newline at end of file