diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildReportsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildReportsIT.kt index 87ef258fcc3..59bd766fd09 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildReportsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildReportsIT.kt @@ -223,4 +223,16 @@ class BuildReportsIT : KGPBaseTest() { } } + @DisplayName("build scan metrics validation") + @GradleTest + fun testBuildScanMetricsValidation(gradleVersion: GradleVersion) { + project("simpleProject", gradleVersion) { + buildAndFail( + "compileKotlin", "-Pkotlin.build.report.output=BUILD_SCAN", "-Pkotlin.build.report.build_scan.metrics=unknown_prop" + ) { + assertOutputContains("Unknown metric: 'unknown_prop', list of available metrics") + } + } + } + } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt index bb9cdc19c81..d89f554af27 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt @@ -118,6 +118,9 @@ internal class PropertiesProvider private constructor(private val project: Proje val buildReportBuildScanCustomValuesLimit: Int get() = property("kotlin.build.report.build_scan.custom_values_limit")?.toInt() ?: 1000 + val buildReportBuildScanMetrics: String? + get() = property("kotlin.build.report.build_scan.metrics") + val buildReportMetrics: Boolean get() = booleanProperty("kotlin.build.report.metrics") ?: false diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/BuildMetricsService.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/BuildMetricsService.kt index 4873e765e6e..eead1d2e204 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/BuildMetricsService.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/BuildMetricsService.kt @@ -163,7 +163,7 @@ abstract class BuildMetricsService : BuildService, } -private class TaskRecord( +internal class TaskRecord( override val path: String, override val classFqName: String, override val startTimeMs: Long, diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/BuildReportsService.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/BuildReportsService.kt index 61d18cf6328..6f7a2016627 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/BuildReportsService.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/BuildReportsService.kt @@ -187,6 +187,7 @@ abstract class BuildReportsService : BuildService? ): Serializable { companion object { const val serialVersionUID: Long = 0 diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/configureReporing.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/configureReporing.kt index bf22d07b7a1..db169a6b868 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/configureReporing.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/configureReporing.kt @@ -6,12 +6,16 @@ package org.jetbrains.kotlin.gradle.report import org.gradle.api.Project +import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric +import org.jetbrains.kotlin.build.report.metrics.BuildTime import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_BUILD_REPORT_SINGLE_FILE import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_BUILD_REPORT_HTTP_URL import org.jetbrains.kotlin.gradle.utils.isProjectIsolationEnabled import org.jetbrains.kotlin.util.capitalizeDecapitalize.toUpperCaseAsciiOnly -import java.io.File + + +private val availableMetrics = BuildTime.values().map { it.name } + BuildPerformanceMetric.values().map { it.name } internal fun reportingSettings(project: Project): ReportingSettings { val properties = PropertiesProvider(project) @@ -49,7 +53,13 @@ internal fun reportingSettings(project: Project): ReportingSettings { } val buildScanSettings = if (buildReportOutputTypes.contains(BuildReportType.BUILD_SCAN)) { - BuildScanSettings(properties.buildReportBuildScanCustomValuesLimit) + val metrics = properties.buildReportBuildScanMetrics?.split(",")?.toSet() + metrics?.forEach { + if (!availableMetrics.contains(it.trim().toUpperCase())) { + throw IllegalStateException("Unknown metric: '$it', list of available metrics: $availableMetrics") + } + } + BuildScanSettings(properties.buildReportBuildScanCustomValuesLimit, metrics) } else { null } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/reportDataUtil.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/reportDataUtil.kt index fe3a3354f61..2a8a9a5a973 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/reportDataUtil.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/reportDataUtil.kt @@ -32,7 +32,8 @@ internal fun prepareData( label: String?, kotlinVersion: String, buildOperationRecords: Collection, - additionalTags: List = emptyList() + additionalTags: List = emptyList(), + metricsToShow: Set? = null ): CompileStatisticsData? { val result = event.result val taskPath = event.descriptor.taskPath @@ -56,9 +57,11 @@ internal fun prepareData( val buildMetrics = buildOperationRecords.firstOrNull { it.path == taskPath }?.buildMetrics val performanceMetrics = collectBuildPerformanceMetrics(taskExecutionResult, buildMetrics) - val buildTimesMetrics = collectBuildMetrics(taskExecutionResult, buildMetrics, performanceMetrics, result.startTime, System.currentTimeMillis()) + val buildTimesMetrics = collectBuildMetrics( + taskExecutionResult, buildMetrics, performanceMetrics, result.startTime, + System.currentTimeMillis() + ) val buildAttributes = collectBuildAttributes(taskExecutionResult, buildMetrics) - val changes = when (val changedFiles = taskExecutionResult?.taskInfo?.changedFiles) { is ChangedFiles.Known -> changedFiles.modified.map { it.absolutePath } + changedFiles.removed.map { it.absolutePath } else -> emptyList() @@ -67,13 +70,13 @@ internal fun prepareData( durationMs = durationMs, taskResult = taskResult.name, label = label, - buildTimesMetrics = buildTimesMetrics, - performanceMetrics = performanceMetrics, + buildTimesMetrics = filterMetrics(metricsToShow, buildTimesMetrics), + performanceMetrics = filterMetrics(metricsToShow, performanceMetrics), projectName = projectName, taskName = taskPath, changes = changes, tags = collectTags(taskExecutionResult, buildMetrics, additionalTags).map { it.name }, - nonIncrementalAttributes = collectBuildAttributes(taskExecutionResult, buildMetrics), + nonIncrementalAttributes = buildAttributes, hostName = BuildReportsService.hostName, kotlinVersion = kotlinVersion, buildUuid = uuid, @@ -82,6 +85,11 @@ internal fun prepareData( ) } +private fun > filterMetrics( + expectedMetrics: Set?, + buildTimesMetrics: Map +): Map = expectedMetrics?.let { buildTimesMetrics.filterKeys { metric -> it.contains(metric.name) } } ?: buildTimesMetrics + private fun collectBuildAttributes(taskExecutionResult: TaskExecutionResult?, buildMetrics: BuildMetrics?): Set { val attributes = HashSet() buildMetrics?.buildAttributes?.asMap()?.filter { it.value > 0 }?.keys?.also { attributes.addAll(it) } @@ -92,13 +100,12 @@ private fun collectBuildAttributes(taskExecutionResult: TaskExecutionResult?, bu private fun collectBuildPerformanceMetrics( taskExecutionResult: TaskExecutionResult?, - buildMetrics: BuildMetrics?, + buildMetrics: BuildMetrics? ): Map { val taskBuildPerformanceMetrics = HashMap() taskExecutionResult?.buildMetrics?.buildPerformanceMetrics?.asMap()?.let { taskBuildPerformanceMetrics.putAll(it) } buildMetrics?.buildPerformanceMetrics?.asMap()?.let { taskBuildPerformanceMetrics.putAll(it) } - taskBuildPerformanceMetrics.filterValues { value -> value != 0L } - return taskBuildPerformanceMetrics + return taskBuildPerformanceMetrics.filterValues { value -> value != 0L } } private fun collectBuildMetrics( taskExecutionResult: TaskExecutionResult?, @@ -124,10 +131,9 @@ private fun collectBuildMetrics( performanceMetrics[BuildPerformanceMetric.START_WORKER_EXECUTION]?.let { startWorkerExecutionTime -> taskBuildMetrics.put(BuildTime.RUN_WORKER_DELAY, TimeUnit.NANOSECONDS.toMillis(startWorkerExecutionTime - callWorkerTime)) } - } - taskBuildMetrics.filterValues { value -> value != 0L } - return taskBuildMetrics + return taskBuildMetrics.filterValues { value -> value != 0L } + } private fun collectTags( diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/report/ReportDataTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/report/ReportDataTest.kt new file mode 100644 index 00000000000..43c56856b1e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/report/ReportDataTest.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2010-2022 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.tooling.events.OperationDescriptor +import org.gradle.tooling.events.PluginIdentifier +import org.gradle.tooling.events.task.TaskFinishEvent +import org.gradle.tooling.events.task.TaskOperationDescriptor +import org.gradle.tooling.events.task.TaskOperationResult +import org.jetbrains.kotlin.build.report.metrics.* +import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults +import org.jetbrains.kotlin.gradle.plugin.stat.StatTag +import org.junit.Test +import java.util.concurrent.TimeUnit +import kotlin.math.sign +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class ReportDataTest { + private val kotlinTaskPath = "testKotlin" + + @Test + fun testTags() { + TaskExecutionResults[kotlinTaskPath] = TaskExecutionResult( + buildMetrics = BuildMetrics(buildAttributes = BuildAttributes()), + taskInfo = TaskExecutionInfo(), + icLogLines = emptyList() + ) + val buildOperationRecord = + taskRecord(BuildMetrics(buildAttributes = BuildAttributes().also { it.add(BuildAttribute.CLASSPATH_SNAPSHOT_NOT_FOUND) })) + val statisticData = prepareData( + event = taskFinishEvent(), + projectName = "test", + uuid = "uuid", + label = "label", + kotlinVersion = "version", + buildOperationRecords = listOf(buildOperationRecord), + additionalTags = listOf(StatTag.KOTLIN_DEBUG) + ) + + assertNotNull(statisticData) + assertTrue(statisticData.tags.contains(StatTag.KOTLIN_DEBUG.name)) + assertTrue(statisticData.tags.contains(StatTag.NON_INCREMENTAL.name)) + } + + private fun taskRecord(buildMetrics: BuildMetrics) = TaskRecord( + path = kotlinTaskPath, + classFqName = "class name", + startTimeMs = 10, + totalTimeMs = 20, + buildMetrics = buildMetrics, + didWork = true, + skipMessage = null, + icLogLines = emptyList() + ) + + @Test + fun testMetricFilter() { + TaskExecutionResults["testKotlin"] = TaskExecutionResult( + buildMetrics = BuildMetrics( + buildPerformanceMetrics = BuildPerformanceMetrics().also { + it.add(BuildPerformanceMetric.BUNDLE_SIZE) + it.add(BuildPerformanceMetric.CACHE_DIRECTORY_SIZE) + }, + buildTimes = BuildTimes().also { + it.addTimeMs(BuildTime.RESTORE_OUTPUT_FROM_BACKUP, 10) + it.addTimeMs(BuildTime.IC_ANALYZE_JAR_FILES, 10) + } + ), + taskInfo = TaskExecutionInfo(), + icLogLines = emptyList() + ) + val buildOperationRecord = taskRecord( + BuildMetrics( + buildPerformanceMetrics = BuildPerformanceMetrics().also { + it.add(BuildPerformanceMetric.COMPILE_ITERATION) + it.add(BuildPerformanceMetric.CLASSPATH_ENTRY_COUNT) + }, + buildTimes = BuildTimes().also { + it.addTimeMs(BuildTime.STORE_BUILD_INFO, 20) + it.addTimeMs(BuildTime.GRADLE_TASK_ACTION, 100) + } + ) + ) + + val statisticData = prepareData( + event = taskFinishEvent(), + projectName = "test", + uuid = "uuid", + label = "label", + kotlinVersion = "version", + buildOperationRecords = listOf(buildOperationRecord), + additionalTags = listOf(StatTag.KOTLIN_DEBUG), + metricsToShow = setOf( + BuildPerformanceMetric.BUNDLE_SIZE.name,// from TaskExecutionResult + BuildTime.GRADLE_TASK_ACTION.name,// from buildOperationRecord + BuildPerformanceMetric.COMPILE_ITERATION.name, //from buildOperationRecord + BuildTime.IC_CALCULATE_INITIAL_DIRTY_SET.name, //not set + BuildPerformanceMetric.START_WORKER_EXECUTION.name, //not set + BuildTime.RESTORE_OUTPUT_FROM_BACKUP.name, //from TaskExecutionResult + ) + ) + + assertNotNull(statisticData) + assertEquals(2, statisticData.performanceMetrics.size) + assertTrue(statisticData.performanceMetrics.containsKey(BuildPerformanceMetric.BUNDLE_SIZE)) + assertTrue(statisticData.performanceMetrics.containsKey(BuildPerformanceMetric.COMPILE_ITERATION)) + assertEquals(2, statisticData.buildTimesMetrics.size) + assertTrue(statisticData.buildTimesMetrics.containsKey(BuildTime.GRADLE_TASK_ACTION)) + assertTrue(statisticData.buildTimesMetrics.containsKey(BuildTime.RESTORE_OUTPUT_FROM_BACKUP)) + } + + @Test + fun testCalculatedMetrics() { + val startGradleTask = 20L + val startTaskAction = 40L + val callWorker = 100L + val startWorker = 110L + val finishGradleTask = System.nanoTime() + + TaskExecutionResults["testKotlin"] = TaskExecutionResult( + buildMetrics = BuildMetrics( + buildPerformanceMetrics = BuildPerformanceMetrics().also { + it.add(BuildPerformanceMetric.FINISH_KOTLIN_DAEMON_EXECUTION, System.currentTimeMillis()) + it.add(BuildPerformanceMetric.START_WORKER_EXECUTION, TimeUnit.MILLISECONDS.toNanos(startWorker)) + } + ), + taskInfo = TaskExecutionInfo(), + icLogLines = emptyList() + ) + val buildOperationRecord = taskRecord( + BuildMetrics( + buildPerformanceMetrics = BuildPerformanceMetrics().also { + it.add(BuildPerformanceMetric.START_TASK_ACTION_EXECUTION, startTaskAction) + it.add(BuildPerformanceMetric.CALL_WORKER, TimeUnit.MILLISECONDS.toNanos(callWorker)) + } + ) + ) + + val statisticData = prepareData( + event = taskFinishEvent(startTime = startGradleTask, endTime = finishGradleTask), + projectName = "test", + uuid = "uuid", + label = "label", + kotlinVersion = "version", + buildOperationRecords = listOf(buildOperationRecord), + additionalTags = listOf(StatTag.KOTLIN_DEBUG), + ) + assertNotNull(statisticData) + assertEquals(startTaskAction - startGradleTask, statisticData.buildTimesMetrics[BuildTime.GRADLE_TASK_PREPARATION]) + assertEquals(1, statisticData.buildTimesMetrics[BuildTime.TASK_FINISH_LISTENER_NOTIFICATION]?.sign) + assertEquals(startWorker - callWorker, statisticData.buildTimesMetrics[BuildTime.RUN_WORKER_DELAY]) + } + + private fun taskFinishEvent(startTime: Long = 1L, endTime: Long =10L) = object : TaskFinishEvent { + override fun getEventTime(): Long = System.currentTimeMillis() + override fun getDisplayName(): String = "some name" + override fun getDescriptor(): TaskOperationDescriptor = object : TaskOperationDescriptor { + override fun getName(): String = "task_name" + override fun getDisplayName(): String = "some name" + override fun getParent(): OperationDescriptor? = null + override fun getTaskPath(): String = kotlinTaskPath + override fun getDependencies(): MutableSet = mutableSetOf() + override fun getOriginPlugin(): PluginIdentifier? = null + } + + override fun getResult(): TaskOperationResult = object : TaskOperationResult { + override fun getStartTime(): Long = startTime + override fun getEndTime(): Long = endTime + } + } + +} \ No newline at end of file