All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.tinkoff.plugins.buildmetrics.gradle.metrics.internal.BuildFailuresMetricsFactory.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0-RC2
Show newest version
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