Update metric collection and tags for build statistics

This commit is contained in:
nataliya.valtman
2022-10-28 12:10:35 +02:00
committed by Space Team
parent d9eda3c485
commit 5fa3b85581
4 changed files with 199 additions and 164 deletions
@@ -72,6 +72,7 @@ data class BuildFinishStatisticsData(
val finishTime: Long,
val timestamp: String = formatter.format(finishTime),
val hostName: String? = "Unset",
val tags: List<String>,
)
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.gradle.report
import com.google.gson.Gson
import org.gradle.api.Project
import org.gradle.api.invocation.Gradle
import org.gradle.api.logging.Logging
@@ -16,14 +15,10 @@ 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.TaskFailureResult
import org.gradle.tooling.events.task.TaskFinishEvent
import org.gradle.tooling.events.task.TaskSkippedResult
import org.gradle.tooling.events.task.TaskSuccessResult
import org.jetbrains.kotlin.build.report.metrics.SizeMetricType
import org.jetbrains.kotlin.gradle.plugin.BuildEventsListenerRegistryHolder
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
import org.jetbrains.kotlin.gradle.plugin.stat.BuildFinishStatisticsData
import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatisticsData
import org.jetbrains.kotlin.gradle.plugin.stat.GradleBuildStartParameters
@@ -31,15 +26,9 @@ import org.jetbrains.kotlin.gradle.plugin.stat.StatTag
import org.jetbrains.kotlin.gradle.report.data.BuildExecutionData
import org.jetbrains.kotlin.gradle.utils.formatSize
import org.jetbrains.kotlin.gradle.utils.isConfigurationCacheAvailable
import org.jetbrains.kotlin.incremental.ChangedFiles
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.utils.addToStdlib.ifTrue
import org.jetbrains.kotlin.utils.addToStdlib.measureTimeMillisWithResult
import java.io.IOException
import java.lang.management.ManagementFactory
import java.net.HttpURLConnection
import java.net.InetAddress
import java.net.URL
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ExecutorService
@@ -119,9 +108,10 @@ abstract class BuildReportsService : BuildService<BuildReportsService.Parameters
label = parameters.label.orNull,
totalTime = (System.nanoTime() - startTime) / 1_000_000,
finishTime = System.currentTimeMillis(),
hostName = hostName
hostName = hostName,
tags = tags.toList()
)
sendDataViaHttp(buildFinishData)
parameters.reportingSettings.get().httpReportSettings?.also { sendDataViaHttp(it, buildFinishData, log) }
}
private fun GradleBuildStartParameters.includeVerboseEnvironment(verboseEnvironment: Boolean): GradleBuildStartParameters {
@@ -148,76 +138,26 @@ abstract class BuildReportsService : BuildService<BuildReportsService.Parameters
buildUuid,
parameters.label.orNull,
parameters.kotlinVersion.get(),
parameters.buildMetricsService.get().buildOperationRecords,
parameters.additionalTags.get()
)
data?.also { executorService.submit { sendDataViaHttp(data) } }
data?.also { parameters.reportingSettings.get().httpReportSettings?.also { executorService.submit { sendDataViaHttp(it, data, log) } } }
}
}
}
private var invalidUrl = false
private var requestPreviousFailed = false
private fun sendDataViaHttp(data: Any) {
val httpSettings = parameters.reportingSettings.get().httpReportSettings ?: return
val elapsedTime = measureTimeMillis {
if (invalidUrl) {
return
}
val connection = try {
URL(httpSettings.url).openConnection() as HttpURLConnection
} catch (e: IOException) {
log.warn("Unable to open connection to ${httpSettings.url}: ${e.message}")
invalidUrl = true
return
}
try {
if (httpSettings.user != null && httpSettings.password != null) {
val auth = Base64.getEncoder()
.encode("${httpSettings.user}:${httpSettings.password}".toByteArray())
.toString(Charsets.UTF_8)
connection.addRequestProperty("Authorization", "Basic $auth")
}
connection.addRequestProperty("Content-Type", "application/json")
connection.requestMethod = "POST"
connection.doOutput = true
connection.outputStream.use {
it.write(Gson().toJson(data).toByteArray())
}
connection.connect()
checkResponseAndLog(connection)
} catch (e: Exception) {
log.debug("Unexpected exception happened ${e.message}: ${e.stackTrace}")
checkResponseAndLog(connection)
} finally {
connection.disconnect()
}
}
log.debug("Report statistic by http takes $elapsedTime ms")
}
private fun checkResponseAndLog(connection: HttpURLConnection) {
val isResponseBad = connection.responseCode !in 200..299
if (isResponseBad) {
val message = "Failed to send statistic to ${connection.url} with ${connection.responseCode}: ${connection.responseMessage}"
if (!requestPreviousFailed) {
log.warn(message)
} else {
log.debug(message)
}
requestPreviousFailed = true
}
}
private fun addBuildScanReport(event: FinishEvent?, buildScan: BuildScanExtensionHolder) {
val buildScanSettings = parameters.reportingSettings.orNull?.buildScanReportSettings
if (buildScanSettings != null && buildScan.buildScan != null) {
if (event is TaskFinishEvent) {
val (collectDataDuration, compileStatData) = measureTimeMillisWithResult {
prepareData(event, parameters.projectName.get(), buildUuid, parameters.label.orNull, parameters.kotlinVersion.get())
prepareData(
event, parameters.projectName.get(), buildUuid, parameters.label.orNull,
parameters.kotlinVersion.get(),
parameters.buildMetricsService.get().buildOperationRecords,
)
}
log.debug("Collect data takes $collectDataDuration: $compileStatData")
@@ -231,12 +171,6 @@ abstract class BuildReportsService : BuildService<BuildReportsService.Parameters
private fun addBuildScanReport(data: CompileStatisticsData, customValuesLimit: Int, buildScan: BuildScanExtensionHolder) {
val elapsedTime = measureTimeMillis {
buildScan.buildScan?.also {
if (!tags.contains(buildUuid)) {
addBuildScanTag(buildScan, buildUuid)
}
data.label?.takeIf { !tags.contains(it) }?.also {
addBuildScanTag(buildScan, it)
}
data.tags
.filter { !tags.contains(it) }
.forEach {
@@ -323,9 +257,15 @@ abstract class BuildReportsService : BuildService<BuildReportsService.Parameters
return splattedString
}
private fun initBuildScanTags(buildScan: BuildScanExtensionHolder) {
buildScan.buildScan?.tag(buildUuid)
parameters.label.orNull?.also {
buildScan.buildScan?.tag(it)
}
}
companion object {
private val log = Logging.getLogger(this.javaClass)
const val lengthLimit = 100_000
fun getStartParameters(project: Project) = project.gradle.startParameter.let {
@@ -371,6 +311,7 @@ abstract class BuildReportsService : BuildService<BuildReportsService.Parameters
val buildScanExtension = project.rootProject.extensions.findByName("buildScan")
if (reportingSettings.buildScanReportSettings != null && buildScanExtension != null) {
it.get().initBuildScanTags(BuildScanExtensionHolder(buildScanExtension))
BuildEventsListenerRegistryHolder.getInstance(project).listenerRegistry.onTaskCompletion(project.provider {
OperationCompletionListener { event ->
it.get().addBuildScanReport(event, BuildScanExtensionHolder(buildScanExtension))
@@ -391,99 +332,12 @@ abstract class BuildReportsService : BuildService<BuildReportsService.Parameters
return additionalTags
}
internal fun prepareData(
event: TaskFinishEvent,
projectName: String,
uuid: String,
label: String?,
kotlinVersion: String,
additionalTags: List<StatTag> = emptyList()
): CompileStatisticsData? {
val result = event.result
val taskPath = event.descriptor.taskPath
val durationMs = result.endTime - result.startTime
val taskResult = when (result) {
is TaskSuccessResult -> when {
result.isFromCache -> TaskExecutionState.FROM_CACHE
result.isUpToDate -> TaskExecutionState.UP_TO_DATE
else -> TaskExecutionState.SUCCESS
}
is TaskSkippedResult -> TaskExecutionState.SKIPPED
is TaskFailureResult -> TaskExecutionState.FAILED
else -> TaskExecutionState.UNKNOWN
}
if (!availableForStat(taskPath)) {
return null
}
val taskExecutionResult = TaskExecutionResults[taskPath]
val buildTimesMs = taskExecutionResult?.buildMetrics?.buildTimes?.asMapMs()?.filterValues { value -> value != 0L } ?: emptyMap()
val perfData =
taskExecutionResult?.buildMetrics?.buildPerformanceMetrics?.asMap()?.filterValues { value -> value != 0L } ?: emptyMap()
val changes = when (val changedFiles = taskExecutionResult?.taskInfo?.changedFiles) {
is ChangedFiles.Known -> changedFiles.modified.map { it.absolutePath } + changedFiles.removed.map { it.absolutePath }
else -> emptyList<String>()
}
return CompileStatisticsData(
durationMs = durationMs,
taskResult = taskResult.name,
label = label,
buildTimesMetrics = buildTimesMs,
performanceMetrics = perfData,
projectName = projectName,
taskName = taskPath,
changes = changes,
tags = parseTags(taskExecutionResult, additionalTags).map { it.name },
nonIncrementalAttributes = taskExecutionResult?.buildMetrics?.buildAttributes?.asMap()?.filter { it.value > 0 }?.keys
?: emptySet(),
hostName = hostName,
kotlinVersion = kotlinVersion,
buildUuid = uuid,
finishTime = System.currentTimeMillis(),
compilerArguments = taskExecutionResult?.taskInfo?.compilerArguments?.asList() ?: emptyList()
)
}
val hostName: String? = try {
InetAddress.getLocalHost().hostName
} catch (_: Exception) {
//do nothing
null
}
private fun availableForStat(taskPath: String): Boolean {
return taskPath.contains("Kotlin") && (TaskExecutionResults[taskPath] != null)
}
private fun parseTags(taskExecutionResult: TaskExecutionResult?, additionalTags: List<StatTag>): List<StatTag> {
val tags = ArrayList(additionalTags)
val nonIncrementalAttributes = taskExecutionResult?.buildMetrics?.buildAttributes?.asMap() ?: emptyMap()
if (nonIncrementalAttributes.isEmpty()) {
tags.add(StatTag.INCREMENTAL)
} else {
tags.add(StatTag.NON_INCREMENTAL)
}
val taskInfo = taskExecutionResult?.taskInfo
taskInfo?.withAbiSnapshot?.ifTrue {
tags.add(StatTag.ABI_SNAPSHOT)
}
taskInfo?.withArtifactTransform?.ifTrue {
tags.add(StatTag.ARTIFACT_TRANSFORM)
}
val debugConfiguration = "-agentlib:"
if (ManagementFactory.getRuntimeMXBean().inputArguments.firstOrNull { it.startsWith(debugConfiguration) } != null) {
tags.add(StatTag.GRADLE_DEBUG)
}
return tags
}
}
}
@@ -0,0 +1,68 @@
/*
* 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 com.google.gson.Gson
import org.gradle.api.logging.Logger
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import java.util.*
import kotlin.system.measureTimeMillis
private var invalidUrl = false
private var requestPreviousFailed = false
internal fun sendDataViaHttp(httpSettings: HttpReportSettings, data: Any, log: Logger) {
val elapsedTime = measureTimeMillis {
if (invalidUrl) {
return
}
val connection = try {
URL(httpSettings.url).openConnection() as HttpURLConnection
} catch (e: IOException) {
log.warn("Unable to open connection to ${httpSettings.url}: ${e.message}")
invalidUrl = true
return
}
try {
if (httpSettings.user != null && httpSettings.password != null) {
val auth = Base64.getEncoder()
.encode("${httpSettings.user}:${httpSettings.password}".toByteArray())
.toString(Charsets.UTF_8)
connection.addRequestProperty("Authorization", "Basic $auth")
}
connection.addRequestProperty("Content-Type", "application/json")
connection.requestMethod = "POST"
connection.doOutput = true
connection.outputStream.use {
it.write(Gson().toJson(data).toByteArray())
}
connection.connect()
checkResponseAndLog(connection, log)
} catch (e: Exception) {
log.debug("Unexpected exception happened ${e.message}: ${e.stackTrace}")
checkResponseAndLog(connection, log)
} finally {
connection.disconnect()
}
}
log.debug("Report statistic by http takes $elapsedTime ms")
}
private fun checkResponseAndLog(connection: HttpURLConnection, log: Logger) {
val isResponseBad = connection.responseCode !in 200..299
if (isResponseBad) {
val message = "Failed to send statistic to ${connection.url} with ${connection.responseCode}: ${connection.responseMessage}"
if (!requestPreviousFailed) {
log.warn(message)
} else {
log.debug(message)
}
requestPreviousFailed = true
}
}
@@ -0,0 +1,112 @@
/*
* 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.task.TaskFailureResult
import org.gradle.tooling.events.task.TaskFinishEvent
import org.gradle.tooling.events.task.TaskSkippedResult
import org.gradle.tooling.events.task.TaskSuccessResult
import org.jetbrains.kotlin.build.report.metrics.BuildMetrics
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatisticsData
import org.jetbrains.kotlin.gradle.plugin.stat.StatTag
import org.jetbrains.kotlin.gradle.report.data.BuildOperationRecord
import org.jetbrains.kotlin.incremental.ChangedFiles
import org.jetbrains.kotlin.utils.addToStdlib.ifTrue
import java.lang.management.ManagementFactory
import java.util.ArrayList
private fun availableForStat(taskPath: String): Boolean {
return taskPath.contains("Kotlin") && (TaskExecutionResults[taskPath] != null)
}
internal fun prepareData(
event: TaskFinishEvent,
projectName: String,
uuid: String,
label: String?,
kotlinVersion: String,
buildOperationRecords: Collection<BuildOperationRecord>,
additionalTags: List<StatTag> = emptyList()
): CompileStatisticsData? {
val result = event.result
val taskPath = event.descriptor.taskPath
val durationMs = result.endTime - result.startTime
val taskResult = when (result) {
is TaskSuccessResult -> when {
result.isFromCache -> TaskExecutionState.FROM_CACHE
result.isUpToDate -> TaskExecutionState.UP_TO_DATE
else -> TaskExecutionState.SUCCESS
}
is TaskSkippedResult -> TaskExecutionState.SKIPPED
is TaskFailureResult -> TaskExecutionState.FAILED
else -> TaskExecutionState.UNKNOWN
}
if (!availableForStat(taskPath)) {
return null
}
val buildMetrics = buildOperationRecords.firstOrNull { it.path == taskPath }?.buildMetrics
val taskExecutionResult = TaskExecutionResults[taskPath]
val buildTimesMs = buildMetrics?.buildTimes?.asMapMs()?.filterValues { value -> value != 0L } ?: emptyMap()
val perfData = buildMetrics?.buildPerformanceMetrics?.asMap()?.filterValues { value -> value != 0L } ?: emptyMap()
val changes = when (val changedFiles = taskExecutionResult?.taskInfo?.changedFiles) {
is ChangedFiles.Known -> changedFiles.modified.map { it.absolutePath } + changedFiles.removed.map { it.absolutePath }
else -> emptyList<String>()
}
return CompileStatisticsData(
durationMs = durationMs,
taskResult = taskResult.name,
label = label,
buildTimesMetrics = buildTimesMs,
performanceMetrics = perfData,
projectName = projectName,
taskName = taskPath,
changes = changes,
tags = parseTags(taskExecutionResult, buildMetrics, additionalTags).map { it.name },
nonIncrementalAttributes = buildMetrics?.buildAttributes?.asMap()?.filter { it.value > 0 }?.keys
?: emptySet(),
hostName = BuildReportsService.hostName,
kotlinVersion = kotlinVersion,
buildUuid = uuid,
finishTime = System.currentTimeMillis(),
compilerArguments = taskExecutionResult?.taskInfo?.compilerArguments?.asList() ?: emptyList()
)
}
private fun parseTags(taskExecutionResult: TaskExecutionResult?, buildMetrics: BuildMetrics?, additionalTags: List<StatTag>): List<StatTag> {
val tags = parseTags(taskExecutionResult, additionalTags)
val nonIncrementalAttributes = buildMetrics?.buildAttributes?.asMap() ?: emptyMap()
if (nonIncrementalAttributes.isEmpty()) {
tags.add(StatTag.INCREMENTAL)
} else {
tags.add(StatTag.NON_INCREMENTAL)
}
return tags
}
private fun parseTags(
taskExecutionResult: TaskExecutionResult?,
additionalTags: List<StatTag>,
): MutableList<StatTag>{
val tags = ArrayList(additionalTags)
val taskInfo = taskExecutionResult?.taskInfo
taskInfo?.withAbiSnapshot?.ifTrue {
tags.add(StatTag.ABI_SNAPSHOT)
}
taskInfo?.withArtifactTransform?.ifTrue {
tags.add(StatTag.ARTIFACT_TRANSFORM)
}
val debugConfiguration = "-agentlib:"
if (ManagementFactory.getRuntimeMXBean().inputArguments.firstOrNull { it.startsWith(debugConfiguration) } != null) {
tags.add(StatTag.GRADLE_DEBUG)
}
return tags
}