diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/performance/KotlinPerformancePlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/performance/KotlinPerformancePlugin.kt new file mode 100644 index 00000000000..49aa89d6063 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/performance/KotlinPerformancePlugin.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2010-2019 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.performance + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.execution.TaskExecutionListener +import org.gradle.api.plugins.ExtraPropertiesExtension +import org.gradle.api.tasks.TaskState +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.addExtension +import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName +import org.jetbrains.kotlin.gradle.targets.native.tasks.NativePerformanceReport +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File +import java.util.concurrent.ConcurrentHashMap + +class TaskTime(val startTime: Long) { + var duration: Double? = null +} + +class TaskTimerListener(project: Project) : TaskExecutionListener { + val tasksTimes = getOrRegisterStorage(project, "org.jetbrains.kotlin.native.taskTimes") + fun getTime(taskName: String) = tasksTimes[taskName]?.duration ?: 0.0 + + override fun beforeExecute(task: Task) { + tasksTimes[task.path] = TaskTime(System.nanoTime()) + } + + override fun afterExecute(task: Task, taskState: TaskState) { + val taskTime = tasksTimes.getValue(task.path) + taskTime.duration = (System.nanoTime() - taskTime.startTime) / 1000.0 + } + + companion object { + @Suppress("UNCHECKED_CAST") + private fun getOrRegisterStorage(project: Project, propertyName: String): ConcurrentHashMap = + project.rootProject.extensions.getByType(ExtraPropertiesExtension::class.java).run { + if (!has(propertyName)) { + set(propertyName, ConcurrentHashMap()) + } + get(propertyName) + } as ConcurrentHashMap + } +} + +open class KotlinPerformancePlugin : Plugin { + private fun checkSettings(project: Project, performanceExtension: PerformanceExtension): Boolean { + var result = true + if (performanceExtension.metrics.isEmpty()) { + project.logger.warn("There is no tracked metrics. Please, provide metrics in settings of $EXTENSION_NAME plugin.") + result = false + } + // Find binaries with trackable performance. + if (performanceExtension.trackedBinaries.isEmpty()) { + project.logger.warn("There is no tracked binaries. Please, provide binaries in settings of $EXTENSION_NAME plugin.") + result = false + } + return result + } + + private fun configureTasks(project: Project, performanceExtension: PerformanceExtension) { + // Add time listener. + val timeListener = TaskTimerListener(project) + project.gradle.addListener(timeListener) + performanceExtension.trackedBinaries.forEach { binary -> + project.tasks.create( + binary.target.disambiguateName(lowerCamelCaseName("perfReport", binary.name)), + NativePerformanceReport::class.java + ) { + it.binary = binary + it.settings = performanceExtension + it.timeListener = timeListener + it.group = TASK_GROUP + it.description = "Report performance measurement results for binary '${binary.name}' of target '${binary.target.name}'." + binary.linkTask.finalizedBy(it) + } + } + } + + override fun apply(project: Project): Unit = with(project) { + pluginManager.withPlugin("kotlin-multiplatform") { + val kotlinExtension = project.multiplatformExtension + val performanceExtension = PerformanceExtension(this) + + kotlinExtension.addExtension(EXTENSION_NAME, performanceExtension) + afterEvaluate { + if (checkSettings(project, performanceExtension)) { + configureTasks(project, performanceExtension) + } + } + } + } + + companion object { + const val EXTENSION_NAME = "performance" + const val TASK_GROUP = "Kotlin/Native Performance" + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/performance/PerformanceExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/performance/PerformanceExtension.kt new file mode 100644 index 00000000000..55f51b30b72 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/performance/PerformanceExtension.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2019 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.performance + +import org.gradle.api.Project +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional +import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBinary + +enum class TrackableMetric { COMPILE_TIME, CODE_SIZE } +open class PerformanceExtension(private val project: Project) { + val version: String + get() = project.version.toString() + + var metrics = listOf(TrackableMetric.COMPILE_TIME, TrackableMetric.CODE_SIZE) + + val trackedBinaries = mutableListOf() + + val binaryNamesForReport = mutableMapOf() + + var includeAssociatedTasks = true + + @JvmOverloads + fun binary(binary: NativeBinary, nameForReport: String = binary.name) { + trackedBinaries.add(binary) + binaryNamesForReport[binary] = nameForReport + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativePerformanceTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativePerformanceTask.kt new file mode 100644 index 00000000000..075425eed98 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativePerformanceTask.kt @@ -0,0 +1,98 @@ +/* + * Copyright 2010-2019 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.targets.native.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.Task +import org.gradle.api.tasks.* +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBinary +import org.jetbrains.kotlin.gradle.plugin.performance.PerformanceExtension +import org.jetbrains.kotlin.gradle.plugin.performance.TaskTimerListener +import org.jetbrains.kotlin.gradle.plugin.performance.TrackableMetric +import java.io.File + +/** + * The task generates performance report for Kotlin/Native binary. + */ +open class NativePerformanceReport : DefaultTask() { + @Internal + lateinit var binary: NativeBinary + + @Internal + lateinit var timeListener: TaskTimerListener + + @Internal + val reportDirectory = File(project.buildDir, "perfReports") + + @OutputFile + val outputFile = File(reportDirectory, "${name}.txt") + + @Internal + lateinit var settings: PerformanceExtension + + private fun getCompilationResults(tasksPaths: Iterable, success: Boolean): String { + val status = success && tasksPaths.all { timeListener.tasksTimes.containsKey(it) } + val time = tasksPaths.map { timeListener.getTime(it) }.sum() + return "${if (status) "PASSED" else "FAILED"}\nCOMPILE_TIME $time" + } + + // Get compile task and associated with it other compile tasks. + private fun getAllExecutedTasks(compilation: KotlinCompilation<*>): List { + val tasks = mutableListOf(compilation.compileKotlinTask as Task) + compilation.associateWith.forEach { + tasks += getAllExecutedTasks(it) + } + return tasks + } + + @TaskAction + fun generate() { + val compileTasks = if (settings.includeAssociatedTasks) + getAllExecutedTasks(binary.linkTask.compilation) + else + listOf(binary.linkTask.compilation.compileKotlinTask) + val allExecutedTasks = listOf(binary.linkTask) + compileTasks + val upToDateTasks = allExecutedTasks.filter { it.state.upToDate }.map { it.name } + if (upToDateTasks.isNotEmpty()) { + if (outputFile.exists()) { + project.delete(outputFile.absolutePath) + } + project.logger.warn("Next compile tasks which are needed for time measurement are upToDate" + + " and weren't executed:\n${upToDateTasks.joinToString("\n", "- ")}") + return + } + val successStatus = allExecutedTasks.all { it.state.failure == null } + // Get code size metric. + var codeSize: String? = null + if (TrackableMetric.CODE_SIZE in settings.metrics) { + codeSize = binary.outputFile.let { + if (it.exists()) "CODE_SIZE ${it.length()}" else null + } + } + // Get compile time. + var compileTime: String? = null + if (TrackableMetric.COMPILE_TIME in settings.metrics) { + compileTime = getCompilationResults( + allExecutedTasks.map { it.path }, + successStatus + ) + } + + // Create report. + if (!reportDirectory.exists()) { + project.mkdir(reportDirectory.absolutePath) + } + val name = settings.binaryNamesForReport[binary]!! + outputFile.writeText(name) + if (compileTime != null) { + outputFile.appendText("\n$compileTime") + } + if (codeSize != null) { + outputFile.appendText("\n$codeSize") + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin-native-performance.properties b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin-native-performance.properties new file mode 100644 index 00000000000..82b91e3d661 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin-native-performance.properties @@ -0,0 +1 @@ +implementation-class=org.jetbrains.kotlin.gradle.plugin.performance.KotlinPerformancePlugin \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.native.performance.properties b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.native.performance.properties new file mode 100644 index 00000000000..82b91e3d661 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.native.performance.properties @@ -0,0 +1 @@ +implementation-class=org.jetbrains.kotlin.gradle.plugin.performance.KotlinPerformancePlugin \ No newline at end of file