Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jetbrains.kotlin.build.report.statistics.file.FileReportService.kt Maven / Gradle / Ivy
/*
* Copyright 2010-2023 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.build.report.statistics.file
import org.jetbrains.kotlin.build.report.metrics.*
import org.jetbrains.kotlin.build.report.statistics.*
import org.jetbrains.kotlin.build.report.statistics.asString
import org.jetbrains.kotlin.build.report.statistics.formatTime
import org.jetbrains.kotlin.buildtools.api.KotlinLogger
import java.io.File
import java.io.Serializable
import java.text.SimpleDateFormat
import java.util.*
class FileReportService(
private val outputFile: File,
private val printMetrics: Boolean,
private val logger: KotlinLogger,
) : Serializable {
companion object {
private val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").also { it.timeZone = TimeZone.getTimeZone("UTC") }
fun reportBuildStatInFile(
buildReportDir: File,
projectName: String,
includeMetricsInReport: Boolean,
buildData: List>,
startParameters: BuildStartParameters,
failureMessages: List,
logger: KotlinLogger,
) {
val ts = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(Calendar.getInstance().time)
val reportFile = buildReportDir.resolve("$projectName-build-$ts.txt")
FileReportService(
outputFile = reportFile,
printMetrics = includeMetricsInReport,
logger = logger
).process(buildData, startParameters, failureMessages)
}
}
private lateinit var p: Printer
fun process(
statisticsData: List>,
startParameters: BuildStartParameters,
failureMessages: List = emptyList(),
) {
val buildReportPath = outputFile.toPath().toUri().toString()
try {
outputFile.parentFile.mkdirs()
if (!(outputFile.parentFile.exists() && outputFile.parentFile.isDirectory)) {
logger.error("Kotlin build report cannot be created: '$outputFile.parentFile' is a file or do not have permissions to create")
return
}
outputFile.bufferedWriter().use { writer ->
p = Printer(writer)
printBuildReport(statisticsData, startParameters, failureMessages)
}
logger.lifecycle("Kotlin build report is written to $buildReportPath")
} catch (e: Exception) {
logger.error("Could not write Kotlin build report to $buildReportPath", e)
}
}
private fun printBuildReport(
statisticsData: List>,
startParameters: BuildStartParameters,
failureMessages: List,
) {
// NOTE: BuildExecutionData / BuildOperationRecord contains data for both tasks and transforms.
// Where possible, we still use the term "tasks" because saying "tasks/transforms" is a bit verbose and "build operations" may sound
// a bit unfamiliar.
// TODO: If it is confusing, consider renaming "tasks" to "build operations" in this class.
printBuildInfo(startParameters, failureMessages)
if (printMetrics && statisticsData.isNotEmpty()) {
printMetrics(
statisticsData.map { it.getBuildTimesMetrics() }.reduce { agg, value ->
(agg.keys + value.keys).associateWith { (agg[it] ?: 0) + (value[it] ?: 0) }
},
statisticsData.map { it.getPerformanceMetrics() }.reduce { agg, value ->
(agg.keys + value.keys).associateWith { (agg[it] ?: 0) + (value[it] ?: 0) }
},
statisticsData.map { it.getNonIncrementalAttributes().asSequence() }.reduce { agg, value -> agg + value }.toList(),
aggregatedMetric = true
)
p.println()
}
printTaskOverview(statisticsData)
printTasksLog(statisticsData)
}
private fun printBuildInfo(startParameters: BuildStartParameters, failureMessages: List) {
p.withIndent("Gradle start parameters:") {
startParameters.let {
p.println("tasks = ${it.tasks}")
p.println("excluded tasks = ${it.excludedTasks}")
p.println("current dir = ${it.currentDir}")
p.println("project properties args = ${it.projectProperties}")
p.println("system properties args = ${it.systemProperties}")
}
}
p.println()
if (failureMessages.isNotEmpty()) {
p.println("Build failed: ${failureMessages}")
p.println()
}
}
private fun printMetrics(
buildTimesMetrics: Map,
performanceMetrics: Map,
nonIncrementalAttributes: Collection,
gcTimeMetrics: Map? = emptyMap(),
gcCountMetrics: Map? = emptyMap(),
aggregatedMetric: Boolean = false,
) {
printBuildTimes(buildTimesMetrics)
if (aggregatedMetric) p.println()
printBuildPerformanceMetrics(performanceMetrics)
if (aggregatedMetric) p.println()
printBuildAttributes(nonIncrementalAttributes)
//TODO: KT-57310 Implement build GC metric in
if (!aggregatedMetric) {
printGcMetrics(gcTimeMetrics, gcCountMetrics)
}
}
private fun printGcMetrics(
gcTimeMetrics: Map?,
gcCountMetrics: Map?,
) {
val keys = HashSet()
gcCountMetrics?.keys?.also { keys.addAll(it) }
gcTimeMetrics?.keys?.also { keys.addAll(it) }
if (keys.isEmpty()) return
p.withIndent("GC metrics:") {
for (key in keys) {
p.println("$key:")
p.withIndent {
gcCountMetrics?.get(key)?.also { p.println("GC count: ${it}") }
gcTimeMetrics?.get(key)?.also { p.println("GC time: ${formatTime(it)}") }
}
}
}
}
private fun printBuildTimes(buildTimes: Map) {
if (buildTimes.isEmpty()) return
p.println("Time metrics:")
p.withIndent {
val visitedBuildTimes = HashSet()
fun printBuildTime(buildTime: BuildTime) {
if (!visitedBuildTimes.add(buildTime)) return
val timeMs = buildTimes[buildTime]
if (timeMs != null) {
p.println("${buildTime.getReadableString()}: ${formatTime(timeMs)}")
p.withIndent {
buildTime.children()?.forEach { printBuildTime(it) }
}
} else {
//Skip formatting if parent metric does not set
buildTime.children()?.forEach { printBuildTime(it) }
}
}
for (buildTime in buildTimes.keys.first().getAllMetrics()) {
if (buildTime.getParent() != null) continue
printBuildTime(buildTime)
}
}
}
private fun printBuildPerformanceMetrics(buildMetrics: Map) {
if (buildMetrics.isEmpty()) return
p.withIndent("Size metrics:") {
for (metric in buildMetrics.keys.first().getAllMetrics()) {
buildMetrics[metric]?.let { printSizeMetric(metric, it) }
}
}
}
private fun printSizeMetric(sizeMetric: BuildPerformanceMetric, value: Long) {
fun BuildPerformanceMetric.numberOfAncestors(): Int {
var count = 0
var parent: BuildPerformanceMetric? = getParent()
while (parent != null) {
count++
parent = parent.getParent()
}
return count
}
val indentLevel = sizeMetric.numberOfAncestors()
repeat(indentLevel) { p.pushIndent() }
when (sizeMetric.getType()) {
ValueType.BYTES -> p.println("${sizeMetric.getReadableString()}: ${formatSize(value)}")
ValueType.NUMBER -> p.println("${sizeMetric.getReadableString()}: $value")
ValueType.NANOSECONDS -> p.println("${sizeMetric.getReadableString()}: $value")
ValueType.MILLISECONDS -> p.println("${sizeMetric.getReadableString()}: ${formatTime(value)}")
ValueType.TIME -> p.println("${sizeMetric.getReadableString()}: ${formatter.format(value)}")
}
repeat(indentLevel) { p.popIndent() }
}
private fun printBuildAttributes(buildAttributes: Collection) {
if (buildAttributes.isEmpty()) return
val buildAttributesMap = buildAttributes.groupingBy { it }.eachCount()
p.withIndent("Build attributes:") {
val attributesByKind = buildAttributesMap.entries.groupBy { it.key.kind }.toSortedMap()
for ((kind, attributesCounts) in attributesByKind) {
printMap(p, kind.name, attributesCounts.associate { (k, v) -> k.readableString to v })
}
}
}
private fun printTaskOverview(statisticsData: Collection>) {
var allTasksTimeMs = 0L
var kotlinTotalTimeMs = 0L
val kotlinTasks = ArrayList>()
for (task in statisticsData) {
val taskTimeMs = task.getDurationMs()
allTasksTimeMs += taskTimeMs
if (task.getFromKotlinPlugin() == true) {
kotlinTotalTimeMs += taskTimeMs
kotlinTasks.add(task)
}
}
if (kotlinTasks.isEmpty()) {
p.println("No Kotlin task was run")
return
}
val ktTaskPercent = (kotlinTotalTimeMs.toDouble() / allTasksTimeMs * 100).asString(1)
p.println("Total time for Kotlin tasks: ${formatTime(kotlinTotalTimeMs)} ($ktTaskPercent % of all tasks time)")
val table = TextTable("Time", "% of Kotlin time", "Task")
for (task in kotlinTasks.sortedWith(compareBy({ -it.getDurationMs() }, { it.getStartTimeMs() }))) {
val timeMs = task.getDurationMs()
val percent = (timeMs.toDouble() / kotlinTotalTimeMs * 100).asString(1)
table.addRow(formatTime(timeMs), "$percent %", task.getTaskName())
}
table.printTo(p)
p.println()
}
private fun printTasksLog(statisticsData: List>) {
for (task in statisticsData.sortedWith(compareBy({ -it.getDurationMs() }, { it.getStartTimeMs() }))) {
printTaskLog(task)
p.println()
}
}
private fun printTaskLog(statisticsData: CompileStatisticsData) {
val skipMessage = statisticsData.getSkipMessage()
if (skipMessage != null) {
p.println("Task '${statisticsData.getTaskName()}' was skipped: $skipMessage")
} else {
p.println("Task '${statisticsData.getTaskName()}' finished in ${formatTime(statisticsData.getDurationMs())}")
}
statisticsData.getKotlinLanguageVersion()?.also {
p.withIndent("Task info:") {
p.println("Kotlin language version: $it")
}
}
if (statisticsData.getIcLogLines().isNotEmpty()) {
p.withIndent("Compilation log for task '${statisticsData.getTaskName()}':") {
statisticsData.getIcLogLines().forEach { p.println(it) }
}
}
if (printMetrics) {
printMetrics(
statisticsData.getBuildTimesMetrics(), statisticsData.getPerformanceMetrics(), statisticsData.getNonIncrementalAttributes(),
statisticsData.getGcTimeMetrics(), statisticsData.getGcCountMetrics()
)
}
}
}