ru.tinkoff.plugins.buildmetrics.gradle.metrics.internal.BuildFailuresMetricsFactory.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of build-metrics-gradle Show documentation
Show all versions of build-metrics-gradle Show documentation
Extension for tinkoff build metrics gradle plugin.
package ru.tinkoff.plugins.buildmetrics.gradle.metrics.internal
import org.gradle.api.internal.tasks.execution.ExecuteTaskBuildOperationType
import ru.tinkoff.plugins.buildmetrics.api.NOT_AVAILABLE_VALUE
import ru.tinkoff.plugins.buildmetrics.api.builds.internal.BuildOperationData
import ru.tinkoff.plugins.buildmetrics.api.builds.internal.BuildOperationDataListener
import ru.tinkoff.plugins.buildmetrics.api.factories.Factory
import ru.tinkoff.plugins.buildmetrics.api.labels.Label
import ru.tinkoff.plugins.buildmetrics.api.metrics.Metric
/**
* Metrics:
* - gradle_failed_build_operations_count;
* - gradle_task_failure - if build has task failed operations;
* - gradle_subproject_path - if failed operation has `task path` value;
* - gradle_task_name - if failed operation has `task path` value;
*/
class BuildFailuresMetricsFactory(
private val maxTaskFailures: Int = 50,
) : Factory.Metrics, BuildOperationDataListener {
private class FactoryData(
val failedBuildOperationsCount: Int,
val taskFailures: Set,
) {
data class FailureDetails(
val failure: String,
val taskName: String?,
val subprojectPath: String?,
)
class Builder(
private val maxTaskFailures: Int,
) {
private var failedBuildOperationsCount: Int = 0
private class FailedOperationDetails(
val id: Long,
val failure: String,
)
private val failedOperationsByParent: MutableMap> = mutableMapOf()
private val taskFailures: MutableMap = mutableMapOf()
fun onBuildOperationData(data: BuildOperationData) {
val failure = data.failure
if (failure != null) {
++failedBuildOperationsCount
saveFailedBuildOperationByParent(id = data.id, parentId = data.parentId, failure = failure)
val details = data.details
if (details is ExecuteTaskBuildOperationType.Details) {
if (taskFailures.size < maxTaskFailures) {
saveTaskFailure(
operationId = data.id,
failure = failure,
details = details,
)
}
}
} else {
if (failedOperationsByParent.containsKey(data.id)) {
saveSuccessBuildOperationWithChildFailure(
id = data.id,
parentId = data.parentId,
)
}
}
}
fun build(): FactoryData = FactoryData(
failedBuildOperationsCount = failedBuildOperationsCount,
taskFailures = taskFailures.map { (id, details) ->
details.copy(
failure = findFirstChildFailureForOperation(
operationId = id,
fallback = details.failure,
)
)
}.toSet(),
)
private fun saveFailedBuildOperationByParent(
id: Long,
parentId: Long,
failure: Throwable,
) {
if (failedOperationsByParent.containsKey(parentId).not()) {
failedOperationsByParent[parentId] = mutableListOf()
}
failedOperationsByParent[parentId]?.add(
FailedOperationDetails(
id = id,
failure = failureMessage(failure = failure),
)
)
}
private fun saveSuccessBuildOperationWithChildFailure(
id: Long,
parentId: Long,
) {
if (failedOperationsByParent.containsKey(parentId).not()) {
failedOperationsByParent[parentId] = mutableListOf()
}
failedOperationsByParent[parentId]?.add(
FailedOperationDetails(id = id, failure = "")
)
}
private fun saveTaskFailure(
operationId: Long,
failure: Throwable,
details: ExecuteTaskBuildOperationType.Details,
) {
val (taskName, subprojectPath) = splitTaskPathToNameAndPath(details.taskPath)
taskFailures[operationId] = FailureDetails(
failure = failureMessage(failure = failure),
taskName = taskName,
subprojectPath = subprojectPath,
)
}
private fun failureMessage(failure: Throwable): String = failure.message ?: failure.toString()
private fun splitTaskPathToNameAndPath(taskPath: String): Pair {
if (taskPath.isEmpty()) return NOT_AVAILABLE_VALUE to NOT_AVAILABLE_VALUE
val taskName = taskPath.substringAfterLast(":", NOT_AVAILABLE_VALUE)
val subprojectPath = taskPath.substringBeforeLast(":", NOT_AVAILABLE_VALUE)
return taskName to subprojectPath
}
private fun findFirstChildFailureForOperation(
operationId: Long,
fallback: String,
): String {
var id = operationId
var failure = fallback
do {
failedOperationsByParent[id]?.firstOrNull()?.let { details ->
id = details.id
failure = details.failure.ifEmpty { failure }
} ?: break
} while (failedOperationsByParent[id] != null)
return failure
}
}
}
@Transient
private var factoryDataBuilder: FactoryData.Builder = FactoryData.Builder(maxTaskFailures = maxTaskFailures)
override fun reinitialize() {
factoryDataBuilder = FactoryData.Builder(maxTaskFailures = maxTaskFailures)
}
override fun onBuildOperationData(data: BuildOperationData) {
factoryDataBuilder.onBuildOperationData(data = data)
}
override fun create(): List> {
val factoryData = factoryDataBuilder.build()
return listOf(
Metric(
name = "gradle_failed_build_operations_count",
value = factoryData.failedBuildOperationsCount,
),
).plus(taskFailureMetrics(taskFailures = factoryData.taskFailures))
}
private fun taskFailureMetrics(taskFailures: Set): List> {
return taskFailures.map { taskFailure ->
Metric(
name = "gradle_task_failure",
value = taskFailure.failure,
labels = listOfNotNull(
taskFailure.subprojectPath?.let { projectPath ->
Label(name = "gradle_subproject_path", value = projectPath)
},
taskFailure.taskName?.let { taskName ->
Label(name = "gradle_task_name", value = taskName)
},
),
)
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy