Report build reports for http in separate executor

This commit is contained in:
nataliya.valtman
2022-01-21 13:27:06 +03:00
parent 7e1a9e2b9a
commit 0382610120
8 changed files with 159 additions and 120 deletions
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers
import org.jetbrains.kotlin.gradle.plugin.stat.ReportStatistics import org.jetbrains.kotlin.gradle.plugin.stat.ReportStatistics
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatListener import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatListener
import org.jetbrains.kotlin.gradle.plugin.statistics.ReportStatisticsToBuildScan import org.jetbrains.kotlin.gradle.plugin.statistics.ReportStatisticsToBuildScan
import org.jetbrains.kotlin.gradle.plugin.statistics.ReportStatisticsByHttp
import org.jetbrains.kotlin.gradle.report.BuildReportType import org.jetbrains.kotlin.gradle.report.BuildReportType
import org.jetbrains.kotlin.gradle.report.ReportingSettings import org.jetbrains.kotlin.gradle.report.ReportingSettings
import org.jetbrains.kotlin.gradle.report.reportingSettings import org.jetbrains.kotlin.gradle.report.reportingSettings
@@ -50,7 +49,6 @@ internal abstract class KotlinGradleBuildServices : BuildService<KotlinGradleBui
} }
companion object { companion object {
fun registerIfAbsent(project: Project): Provider<KotlinGradleBuildServices> = project.gradle.sharedServices.registerIfAbsent( fun registerIfAbsent(project: Project): Provider<KotlinGradleBuildServices> = project.gradle.sharedServices.registerIfAbsent(
"kotlin-build-service-${KotlinGradleBuildServices::class.java.canonicalName}_${KotlinGradleBuildServices::class.java.classLoader.hashCode()}", "kotlin-build-service-${KotlinGradleBuildServices::class.java.canonicalName}_${KotlinGradleBuildServices::class.java.classLoader.hashCode()}",
KotlinGradleBuildServices::class.java KotlinGradleBuildServices::class.java
@@ -66,12 +64,6 @@ internal abstract class KotlinGradleBuildServices : BuildService<KotlinGradleBui
val listeners = project.rootProject.objects.listProperty(ReportStatistics::class.java) val listeners = project.rootProject.objects.listProperty(ReportStatistics::class.java)
.value(listOf<ReportStatistics>()) .value(listOf<ReportStatistics>())
reportingSettings.httpReportSettings?.let {
listeners.add(
ReportStatisticsByHttp(reportingSettings.httpReportSettings)
)
}
project.rootProject.extensions.findByName("buildScan") project.rootProject.extensions.findByName("buildScan")
?.also { ?.also {
if (reportingSettings.buildReportOutputs.contains(BuildReportType.BUILD_SCAN)) { if (reportingSettings.buildReportOutputs.contains(BuildReportType.BUILD_SCAN)) {
@@ -81,7 +73,7 @@ internal abstract class KotlinGradleBuildServices : BuildService<KotlinGradleBui
if (listeners.get().isNotEmpty()) { if (listeners.get().isNotEmpty()) {
val listenerRegistryHolder = BuildEventsListenerRegistryHolder.getInstance(project) val listenerRegistryHolder = BuildEventsListenerRegistryHolder.getInstance(project)
val statListener = KotlinBuildStatListener(project.rootProject.name, listeners.get()) val statListener = KotlinBuildStatListener(project.rootProject.name, reportingSettings.buildReportLabel, listeners.get())
listenerRegistryHolder.listenerRegistry.onTaskCompletion(project.provider { statListener }) listenerRegistryHolder.listenerRegistry.onTaskCompletion(project.provider { statListener })
} }
} }
@@ -37,6 +37,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSetFactory import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSetFactory
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.report.BuildMetricsReporterService import org.jetbrains.kotlin.gradle.report.BuildMetricsReporterService
import org.jetbrains.kotlin.gradle.report.HttpReportService
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsPlugin import org.jetbrains.kotlin.gradle.targets.js.KotlinJsPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.addNpmDependencyExtension import org.jetbrains.kotlin.gradle.targets.js.npm.addNpmDependencyExtension
@@ -97,6 +98,9 @@ abstract class KotlinBasePluginWrapper : Plugin<Project> {
buildMetricReporter?.also { BuildEventsListenerRegistryHolder.getInstance(project).listenerRegistry.onTaskCompletion(it) } buildMetricReporter?.also { BuildEventsListenerRegistryHolder.getInstance(project).listenerRegistry.onTaskCompletion(it) }
HttpReportService.registerIfAbsent(project)
?.also { BuildEventsListenerRegistryHolder.getInstance(project).listenerRegistry.onTaskCompletion(it) }
project.tasks.withType(AbstractKotlinCompile::class.java).configureEach { project.tasks.withType(AbstractKotlinCompile::class.java).configureEach {
if (buildMetricReporter != null) { if (buildMetricReporter != null) {
it.buildMetricsReporterService.set(buildMetricReporter) it.buildMetricsReporterService.set(buildMetricReporter)
@@ -12,7 +12,6 @@ import org.gradle.tooling.events.task.TaskFailureResult
import org.gradle.tooling.events.task.TaskFinishEvent import org.gradle.tooling.events.task.TaskFinishEvent
import org.gradle.tooling.events.task.TaskSkippedResult import org.gradle.tooling.events.task.TaskSkippedResult
import org.gradle.tooling.events.task.TaskSuccessResult import org.gradle.tooling.events.task.TaskSuccessResult
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatData import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatData
import org.jetbrains.kotlin.gradle.plugin.stat.ReportStatistics import org.jetbrains.kotlin.gradle.plugin.stat.ReportStatistics
@@ -32,58 +31,45 @@ enum class TaskExecutionState {
; ;
} }
class KotlinBuildStatListener(val projectName: String, val reportStatistics: List<ReportStatistics>) : class KotlinBuildStatListener(
OperationCompletionListener, AutoCloseable { val projectName: String,
val label: String?,
companion object { val reportStatistics: List<ReportStatistics>
/* ) : OperationCompletionListener, AutoCloseable {
All listeners process events in single thread pool. After build finished it has only 60 seconds to finish processing.
Our listeners should not spend significant amount of time during event processing.
*/
const val LIMIT_DURATION_MS = 5 * 1000
}
private val log = Logging.getLogger(this.javaClass) private val log = Logging.getLogger(this.javaClass)
val buildUuid: String = UUID.randomUUID().toString() val buildUuid: String = UUID.randomUUID().toString()
val label by lazy { CompilerSystemProperties.KOTLIN_STAT_LABEl_PROPERTY.value } companion object {
val hostName: String? = try { val hostName: String? = try {
InetAddress.getLocalHost().hostName InetAddress.getLocalHost().hostName
} catch (_: Exception) { } catch (_: Exception) {
//do nothing //do nothing
null null
} }
override fun onFinish(event: FinishEvent?) { private fun availableForStat(taskPath: String): Boolean {
val measuredTimeMs = measureTimeMillis { return taskPath.contains("Kotlin") && (TaskExecutionResults[taskPath] != null)
if (event is TaskFinishEvent) { }
val result = event.result
val taskPath = event.descriptor.taskPath internal fun prepareData(event: TaskFinishEvent, projectName:String, uuid: String, label: String?): CompileStatData? {
val durationMs = result.endTime - result.startTime val result = event.result
val taskResult = when (result) { val taskPath = event.descriptor.taskPath
is TaskSuccessResult -> when { val durationMs = result.endTime - result.startTime
result.isFromCache -> TaskExecutionState.FROM_CACHE val taskResult = when (result) {
result.isUpToDate -> TaskExecutionState.UP_TO_DATE is TaskSuccessResult -> when {
else -> TaskExecutionState.SUCCESS result.isFromCache -> TaskExecutionState.FROM_CACHE
} result.isUpToDate -> TaskExecutionState.UP_TO_DATE
else -> TaskExecutionState.SUCCESS
}
is TaskSkippedResult -> TaskExecutionState.SKIPPED is TaskSkippedResult -> TaskExecutionState.SKIPPED
is TaskFailureResult -> TaskExecutionState.FAILED is TaskFailureResult -> TaskExecutionState.FAILED
else -> TaskExecutionState.UNKNOWN else -> TaskExecutionState.UNKNOWN
} }
reportData(taskPath, durationMs, taskResult)
}
}
if (measuredTimeMs > LIMIT_DURATION_MS) {
log.warn("Exceed time limit for $event. Takes ${measuredTimeMs}ms ")
}
}
private fun reportData(taskPath: String, durationMs: Long, taskResult: TaskExecutionState) {
val (reportDataDuration, compileStatData) = measureTimeMillisWithResult {
if (!availableForStat(taskPath)) { if (!availableForStat(taskPath)) {
return return null
} }
val taskExecutionResult = TaskExecutionResults[taskPath] val taskExecutionResult = TaskExecutionResults[taskPath]
@@ -96,21 +82,29 @@ class KotlinBuildStatListener(val projectName: String, val reportStatistics: Lis
else -> emptyList<String>() else -> emptyList<String>()
} }
val compileStatData = CompileStatData( return CompileStatData(
durationMs = durationMs, taskResult = taskResult.name, label = label, durationMs = durationMs, taskResult = taskResult.name, label = label,
buildTimesMs = buildTimesMs, perfData = perfData, projectName = projectName, taskName = taskPath, changes = changes, buildTimesMs = buildTimesMs, perfData = perfData, projectName = projectName, taskName = taskPath, changes = changes,
tags = taskExecutionResult?.taskInfo?.properties?.map { it.name } ?: emptyList(), tags = taskExecutionResult?.taskInfo?.properties?.map { it.name } ?: emptyList(),
nonIncrementalAttributes = taskExecutionResult?.buildMetrics?.buildAttributes?.asMap() ?: emptyMap(), nonIncrementalAttributes = taskExecutionResult?.buildMetrics?.buildAttributes?.asMap() ?: emptyMap(),
hostName = hostName, kotlinVersion = "1.6", buildUuid = buildUuid, timeInMillis = System.currentTimeMillis() hostName = hostName, kotlinVersion = "1.6", buildUuid = uuid, timeInMillis = System.currentTimeMillis()
) )
reportStatistics.forEach { it.report(compileStatData) }
compileStatData
} }
log.debug("Report data takes $reportDataDuration: $compileStatData")
} }
private fun availableForStat(taskPath: String): Boolean { override fun onFinish(event: FinishEvent?) {
return taskPath.contains("Kotlin") && (TaskExecutionResults[taskPath] != null) if (event is TaskFinishEvent) {
val (collectDataDuration, compileStatData) = measureTimeMillisWithResult {
prepareData(event, projectName, buildUuid, label)
}
log.debug("Collect data takes $collectDataDuration: $compileStatData")
val reportDataDuration = measureTimeMillis {
compileStatData?.also { data -> reportStatistics.forEach { it.report(data) }}
}
log.debug("Report data takes $reportDataDuration: $compileStatData")
}
} }
override fun close() { override fun close() {
@@ -1,62 +0,0 @@
/*
* Copyright 2010-2021 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.statistics
import com.google.gson.Gson
import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
import org.jetbrains.kotlin.cli.common.toBooleanLenient
import org.jetbrains.kotlin.gradle.plugin.stat.ReportStatistics
import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatData
import org.jetbrains.kotlin.gradle.report.HttpReportSettings
import java.net.HttpURLConnection
import java.net.URL
import java.util.*
import kotlin.system.measureTimeMillis
class ReportStatisticsByHttp(
private val httpProperties: HttpReportSettings
) : ReportStatistics {
private val log = Logging.getLogger(this.javaClass)
override fun report(data: CompileStatData) {
val elapsedTime = measureTimeMillis {
val connection = URL(httpProperties.url).openConnection() as HttpURLConnection
try {
if (httpProperties.user != null && httpProperties.password != null) {
val auth = Base64.getEncoder()
.encode("${httpProperties.user}:${httpProperties.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)
connection.inputStream.use { it.reader().use { reader -> reader.readText() } }
} catch (e: Exception) {
checkResponseAndLog(connection)
} finally {
connection.disconnect()
}
}
log.debug("Report statistic to elastic search takes $elapsedTime ms")
}
private fun checkResponseAndLog(connection: HttpURLConnection) {
val isResponseBad = connection.responseCode !in 200..299
if (isResponseBad) {
throw Exception(
"Failed to send statistic to ${connection.url}: ${connection.responseMessage}"
)
}
}
}
@@ -10,6 +10,7 @@ import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatData import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatData
import org.jetbrains.kotlin.gradle.plugin.stat.ReportStatistics import org.jetbrains.kotlin.gradle.plugin.stat.ReportStatistics
import org.jetbrains.kotlin.konan.file.File import org.jetbrains.kotlin.konan.file.File
import java.util.function.Consumer
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
class ReportStatisticsToBuildScan( class ReportStatisticsToBuildScan(
@@ -19,7 +20,7 @@ class ReportStatisticsToBuildScan(
const val kbSize = 1024 const val kbSize = 1024
const val mbSize = kbSize * kbSize const val mbSize = kbSize * kbSize
const val gbSize = kbSize * mbSize const val gbSize = kbSize * mbSize
const val lengthLimit = 20 const val lengthLimit = 100_000
} }
private val tags = LinkedHashSet<String>() private val tags = LinkedHashSet<String>()
@@ -0,0 +1,108 @@
/*
* 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.Project
import org.gradle.api.logging.Logging
import org.gradle.api.provider.Provider
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.TaskFinishEvent
import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatData
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatListener.Companion.prepareData
import java.net.HttpURLConnection
import java.net.URL
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import kotlin.system.measureTimeMillis
abstract class HttpReportService : BuildService<HttpReportService.Parameters>,
OperationCompletionListener, AutoCloseable {
var executorService: ExecutorService = Executors.newSingleThreadExecutor()
interface Parameters : BuildServiceParameters {
var label: String?
var uuid: String
var projectName: String
var httpSettings: HttpReportSettings
}
private val log = Logging.getLogger(this.javaClass)
override fun onFinish(event: FinishEvent?) {
if (event is TaskFinishEvent) {
val data = prepareData(event, parameters.projectName, parameters.uuid, parameters.label)
data?.also { executorService.submit { report(data) } }
}
}
override fun close() {
executorService.shutdown()
}
companion object {
fun registerIfAbsent(project: Project): Provider<HttpReportService>? {
val rootProject = project.gradle.rootProject
val reportingSettings = reportingSettings(rootProject)
return reportingSettings.httpReportSettings?.let { httpSettings ->
project.gradle.sharedServices.registerIfAbsent(
"build_http_metric_service_${HttpReportService::class.java.classLoader.hashCode()}",
HttpReportService::class.java
) {
it.parameters.label = reportingSettings.buildReportLabel
it.parameters.projectName = rootProject.name
it.parameters.uuid = UUID.randomUUID().toString()
it.parameters.httpSettings = httpSettings
}!!
}
}
}
fun report(data: CompileStatData) {
val elapsedTime = measureTimeMillis {
val connection = URL(parameters.httpSettings.url).openConnection() as HttpURLConnection
try {
if (parameters.httpSettings.user != null && parameters.httpSettings.password != null) {
val auth = Base64.getEncoder()
.encode("${parameters.httpSettings.user}:${parameters.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)
connection.inputStream.use { it.reader().use { reader -> reader.readText() } }
} catch (e: Exception) {
checkResponseAndLog(connection)
} finally {
connection.disconnect()
}
}
log.debug("Report statistic to elastic search takes $elapsedTime ms")
}
private fun checkResponseAndLog(connection: HttpURLConnection) {
val isResponseBad = connection.responseCode !in 200..299
if (isResponseBad) {
log.warn("Failed to send statistic to ${connection.url}: ${connection.responseMessage}")
}
}
}
@@ -12,6 +12,7 @@ data class ReportingSettings(
val metricsOutputFile: File? = null, val metricsOutputFile: File? = null,
val buildReportOutputs: List<BuildReportType> = emptyList(), val buildReportOutputs: List<BuildReportType> = emptyList(),
val buildReportMode: BuildReportMode = BuildReportMode.NONE, val buildReportMode: BuildReportMode = BuildReportMode.NONE,
val buildReportLabel: String? = null,
val fileReportSettings: FileReportSettings? = null, val fileReportSettings: FileReportSettings? = null,
val httpReportSettings: HttpReportSettings? = null val httpReportSettings: HttpReportSettings? = null
) : Serializable { ) : Serializable {
@@ -40,6 +40,7 @@ internal fun reportingSettings(rootProject: Project): ReportingSettings {
return ReportingSettings( return ReportingSettings(
metricsOutputFile = metricsOutputFile, metricsOutputFile = metricsOutputFile,
buildReportMode = buildReportMode, buildReportMode = buildReportMode,
buildReportLabel = properties.buildReportLabel,
fileReportSettings = fileReportSettings, fileReportSettings = fileReportSettings,
httpReportSettings = httpReportSettings, httpReportSettings = httpReportSettings,
buildReportOutputs = buildReportOutputTypes buildReportOutputs = buildReportOutputTypes