Add JSON output type for build reports
#KT-65792 Fixed
This commit is contained in:
committed by
Space Team
parent
f493df42a9
commit
5885514c3d
+3
-5
@@ -8,11 +8,9 @@ package org.jetbrains.kotlin.build.report.metrics
|
|||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class BuildAttributes : Serializable {
|
data class BuildAttributes(
|
||||||
private val myAttributes =
|
private val myAttributes: MutableMap<BuildAttribute, Int> = EnumMap(BuildAttribute::class.java)
|
||||||
EnumMap<BuildAttribute, Int>(
|
) : Serializable {
|
||||||
BuildAttribute::class.java
|
|
||||||
)
|
|
||||||
|
|
||||||
fun add(attr: BuildAttribute, count: Int = 1) {
|
fun add(attr: BuildAttribute, count: Int = 1) {
|
||||||
myAttributes[attr] = myAttributes.getOrDefault(attr, 0) + count
|
myAttributes[attr] = myAttributes.getOrDefault(attr, 0) + count
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.buildtools.api.KotlinLogger
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
interface BuildReportService<T> : Serializable {
|
||||||
|
fun process(data: T, log: KotlinLogger)
|
||||||
|
}
|
||||||
+1
-1
@@ -10,7 +10,7 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
//Sensitive data. This object is used directly for statistic via http
|
//Sensitive data. This object is used directly for statistic via http
|
||||||
private val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").also { it.timeZone = TimeZone.getTimeZone("UTC") }
|
internal val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").also { it.timeZone = TimeZone.getTimeZone("UTC") }
|
||||||
|
|
||||||
interface CompileStatisticsData<B : BuildTime, P : BuildPerformanceMetric> {
|
interface CompileStatisticsData<B : BuildTime, P : BuildPerformanceMetric> {
|
||||||
fun getVersion(): Int = 4
|
fun getVersion(): Int = 4
|
||||||
|
|||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.buildtools.api.KotlinLogger
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
abstract class FileReportService<T>(
|
||||||
|
buildReportDir: File,
|
||||||
|
projectName: String,
|
||||||
|
fileSuffix: String,
|
||||||
|
) : BuildReportService<T> {
|
||||||
|
companion object {
|
||||||
|
internal val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").also { it.timeZone = TimeZone.getTimeZone("UTC") }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ts = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(Calendar.getInstance().time)
|
||||||
|
private val outputFile = buildReportDir.resolve("$projectName-build-$ts.$fileSuffix")
|
||||||
|
|
||||||
|
abstract fun printBuildReport(data: T, outputFile: File)
|
||||||
|
|
||||||
|
override fun process(data: T, log: KotlinLogger) {
|
||||||
|
val buildReportPath = outputFile.toPath().toUri().toString()
|
||||||
|
try {
|
||||||
|
outputFile.parentFile.mkdirs()
|
||||||
|
if (!(outputFile.parentFile.exists() && outputFile.parentFile.isDirectory)) {
|
||||||
|
log.error("Kotlin build report cannot be created: '${outputFile.parentFile}' is a file or do not have permissions to create")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printBuildReport(data, outputFile)
|
||||||
|
|
||||||
|
log.lifecycle("Kotlin build report is written to $buildReportPath")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error("Could not write Kotlin build report to $buildReportPath", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class JsonReportService(
|
||||||
|
buildReportDir: File,
|
||||||
|
projectName: String,
|
||||||
|
) : FileReportService<Any>(buildReportDir, projectName, "json") {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints general build information and task/transform build metrics
|
||||||
|
*/
|
||||||
|
override fun printBuildReport(data: Any, outputFile: File) {
|
||||||
|
outputFile.bufferedWriter().use {
|
||||||
|
it.write(Gson().toJson(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-292
@@ -1,292 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.*
|
|
||||||
|
|
||||||
open class FileReportService<B : BuildTime, P : BuildPerformanceMetric>(
|
|
||||||
buildReportDir: File,
|
|
||||||
projectName: String,
|
|
||||||
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") }
|
|
||||||
}
|
|
||||||
private val ts = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(Calendar.getInstance().time)
|
|
||||||
private val outputFile = buildReportDir.resolve("$projectName-build-$ts.txt")
|
|
||||||
|
|
||||||
protected lateinit var p: Printer
|
|
||||||
|
|
||||||
open fun printCustomTaskMetrics(statisticsData: CompileStatisticsData<B, P>) {}
|
|
||||||
|
|
||||||
fun process(
|
|
||||||
statisticsData: List<CompileStatisticsData<B, P>>,
|
|
||||||
startParameters: BuildStartParameters,
|
|
||||||
failureMessages: List<String> = 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<CompileStatisticsData<B, P>>,
|
|
||||||
startParameters: BuildStartParameters,
|
|
||||||
failureMessages: List<String>,
|
|
||||||
) {
|
|
||||||
// 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<String>) {
|
|
||||||
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<out BuildTime, Long>,
|
|
||||||
performanceMetrics: Map<out BuildPerformanceMetric, Long>,
|
|
||||||
nonIncrementalAttributes: Collection<BuildAttribute>,
|
|
||||||
gcTimeMetrics: Map<String, Long>? = emptyMap(),
|
|
||||||
gcCountMetrics: Map<String, Long>? = 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<String, Long>?,
|
|
||||||
gcCountMetrics: Map<String, Long>?,
|
|
||||||
) {
|
|
||||||
val keys = HashSet<String>()
|
|
||||||
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<out BuildTime, Long>) {
|
|
||||||
if (buildTimes.isEmpty()) return
|
|
||||||
|
|
||||||
p.println("Time metrics:")
|
|
||||||
p.withIndent {
|
|
||||||
val visitedBuildTimes = HashSet<BuildTime>()
|
|
||||||
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<out BuildPerformanceMetric, Long>) {
|
|
||||||
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<BuildAttribute>) {
|
|
||||||
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<CompileStatisticsData<B, P>>) {
|
|
||||||
var allTasksTimeMs = 0L
|
|
||||||
var kotlinTotalTimeMs = 0L
|
|
||||||
val kotlinTasks = ArrayList<CompileStatisticsData<B, P>>()
|
|
||||||
|
|
||||||
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<CompileStatisticsData<B, P>>,
|
|
||||||
) {
|
|
||||||
for (task in statisticsData.sortedWith(compareBy({ -it.getDurationMs() }, { it.getStartTimeMs() }))) {
|
|
||||||
printTaskLog(task)
|
|
||||||
p.println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun printTaskLog(
|
|
||||||
statisticsData: CompileStatisticsData<B, P>,
|
|
||||||
) {
|
|
||||||
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()
|
|
||||||
)
|
|
||||||
printCustomTaskMetrics(statisticsData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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 java.io.File
|
||||||
|
|
||||||
|
data class ReadableFileReportData<B : BuildTime, P : BuildPerformanceMetric>(
|
||||||
|
val statisticsData: List<CompileStatisticsData<B, P>>,
|
||||||
|
val startParameters: BuildStartParameters,
|
||||||
|
val failureMessages: List<String> = emptyList(),
|
||||||
|
val version: Int = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
open class ReadableFileReportService<B : BuildTime, P : BuildPerformanceMetric>(
|
||||||
|
buildReportDir: File,
|
||||||
|
projectName: String,
|
||||||
|
private val printMetrics: Boolean,
|
||||||
|
) : FileReportService<ReadableFileReportData<B, P>>(buildReportDir, projectName, "txt") {
|
||||||
|
|
||||||
|
open fun printCustomTaskMetrics(statisticsData: CompileStatisticsData<B, P>, printer: Printer) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints general build information, sum up compile metrics and detailed task and transform information.
|
||||||
|
*
|
||||||
|
* BuildExecutionData / BuildOperationRecord contains data for both tasks and transforms.
|
||||||
|
* We still use the term "tasks" because saying "tasks/transforms" is a bit verbose and "build operations" may sound a bit unfamiliar.
|
||||||
|
*/
|
||||||
|
override fun printBuildReport(data: ReadableFileReportData<B, P>, outputFile: File) {
|
||||||
|
outputFile.bufferedWriter().use { writer ->
|
||||||
|
Printer(writer).printBuildReport(data, printMetrics) { compileStatisticsData ->
|
||||||
|
printCustomTaskMetrics(
|
||||||
|
compileStatisticsData,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+253
@@ -0,0 +1,253 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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.BuildAttribute
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.BuildTime
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.ValueType
|
||||||
|
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.build.report.statistics.formatter
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.HashSet
|
||||||
|
|
||||||
|
internal fun <B : BuildTime, P : BuildPerformanceMetric> Printer.printBuildReport(
|
||||||
|
data: ReadableFileReportData<B, P>,
|
||||||
|
printMetrics: Boolean,
|
||||||
|
printCustomTaskMetrics: Printer.(CompileStatisticsData<B, P>) -> Unit,
|
||||||
|
) {
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
printBuildInfo(data.startParameters, data.failureMessages)
|
||||||
|
if (printMetrics && data.statisticsData.isNotEmpty()) {
|
||||||
|
printMetrics(
|
||||||
|
data.statisticsData.map { it.getBuildTimesMetrics() }.reduce { agg, value ->
|
||||||
|
(agg.keys + value.keys).associateWith { (agg[it] ?: 0) + (value[it] ?: 0) }
|
||||||
|
},
|
||||||
|
data.statisticsData.map { it.getPerformanceMetrics() }.reduce { agg, value ->
|
||||||
|
(agg.keys + value.keys).associateWith { (agg[it] ?: 0) + (value[it] ?: 0) }
|
||||||
|
},
|
||||||
|
data.statisticsData.map { it.getNonIncrementalAttributes().asSequence() }.reduce { agg, value -> agg + value }.toList(),
|
||||||
|
aggregatedMetric = true,
|
||||||
|
)
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
printTaskOverview(data.statisticsData)
|
||||||
|
printTasksLog(data.statisticsData, printMetrics, printCustomTaskMetrics)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Printer.printBuildInfo(startParameters: BuildStartParameters, failureMessages: List<String>) {
|
||||||
|
withIndent("Gradle start parameters:") {
|
||||||
|
startParameters.let {
|
||||||
|
println("tasks = ${it.tasks}")
|
||||||
|
println("excluded tasks = ${it.excludedTasks}")
|
||||||
|
println("current dir = ${it.currentDir}")
|
||||||
|
println("project properties args = ${it.projectProperties}")
|
||||||
|
println("system properties args = ${it.systemProperties}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
|
||||||
|
if (failureMessages.isNotEmpty()) {
|
||||||
|
println("Build failed: ${failureMessages}")
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Printer.printMetrics(
|
||||||
|
buildTimesMetrics: Map<out BuildTime, Long>,
|
||||||
|
performanceMetrics: Map<out BuildPerformanceMetric, Long>,
|
||||||
|
nonIncrementalAttributes: Collection<BuildAttribute>,
|
||||||
|
gcTimeMetrics: Map<String, Long>? = emptyMap(),
|
||||||
|
gcCountMetrics: Map<String, Long>? = emptyMap(),
|
||||||
|
aggregatedMetric: Boolean = false,
|
||||||
|
) {
|
||||||
|
printBuildTimes(buildTimesMetrics)
|
||||||
|
if (aggregatedMetric) println()
|
||||||
|
|
||||||
|
printBuildPerformanceMetrics(performanceMetrics)
|
||||||
|
if (aggregatedMetric) println()
|
||||||
|
|
||||||
|
printBuildAttributes(nonIncrementalAttributes)
|
||||||
|
|
||||||
|
//TODO: KT-57310 Implement build GC metric in
|
||||||
|
if (!aggregatedMetric) {
|
||||||
|
printGcMetrics(gcTimeMetrics, gcCountMetrics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Printer.printGcMetrics(
|
||||||
|
gcTimeMetrics: Map<String, Long>?,
|
||||||
|
gcCountMetrics: Map<String, Long>?,
|
||||||
|
) {
|
||||||
|
val keys = HashSet<String>()
|
||||||
|
gcCountMetrics?.keys?.also { keys.addAll(it) }
|
||||||
|
gcTimeMetrics?.keys?.also { keys.addAll(it) }
|
||||||
|
if (keys.isEmpty()) return
|
||||||
|
|
||||||
|
withIndent("GC metrics:") {
|
||||||
|
for (key in keys) {
|
||||||
|
println("$key:")
|
||||||
|
withIndent {
|
||||||
|
gcCountMetrics?.get(key)?.also { println("GC count: ${it}") }
|
||||||
|
gcTimeMetrics?.get(key)?.also { println("GC time: ${formatTime(it)}") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Printer.printBuildTimes(buildTimes: Map<out BuildTime, Long>) {
|
||||||
|
if (buildTimes.isEmpty()) return
|
||||||
|
|
||||||
|
println("Time metrics:")
|
||||||
|
withIndent {
|
||||||
|
val visitedBuildTimes = HashSet<BuildTime>()
|
||||||
|
fun printBuildTime(buildTime: BuildTime) {
|
||||||
|
if (!visitedBuildTimes.add(buildTime)) return
|
||||||
|
|
||||||
|
val timeMs = buildTimes[buildTime]
|
||||||
|
if (timeMs != null) {
|
||||||
|
println("${buildTime.getReadableString()}: ${formatTime(timeMs)}")
|
||||||
|
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 Printer.printBuildPerformanceMetrics(buildMetrics: Map<out BuildPerformanceMetric, Long>) {
|
||||||
|
if (buildMetrics.isEmpty()) return
|
||||||
|
|
||||||
|
withIndent("Size metrics:") {
|
||||||
|
for (metric in buildMetrics.keys.first().getAllMetrics()) {
|
||||||
|
buildMetrics[metric]?.let { printSizeMetric(metric, it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Printer.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) { pushIndent() }
|
||||||
|
when (sizeMetric.getType()) {
|
||||||
|
ValueType.BYTES -> println("${sizeMetric.getReadableString()}: ${formatSize(value)}")
|
||||||
|
ValueType.NUMBER -> println("${sizeMetric.getReadableString()}: $value")
|
||||||
|
ValueType.NANOSECONDS -> println("${sizeMetric.getReadableString()}: $value")
|
||||||
|
ValueType.MILLISECONDS -> println("${sizeMetric.getReadableString()}: ${formatTime(value)}")
|
||||||
|
ValueType.TIME -> println("${sizeMetric.getReadableString()}: ${formatter.format(value)}")
|
||||||
|
}
|
||||||
|
repeat(indentLevel) { popIndent() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Printer.printBuildAttributes(buildAttributes: Collection<BuildAttribute>) {
|
||||||
|
if (buildAttributes.isEmpty()) return
|
||||||
|
|
||||||
|
val buildAttributesMap = buildAttributes.groupingBy { it }.eachCount()
|
||||||
|
withIndent("Build attributes:") {
|
||||||
|
val attributesByKind = buildAttributesMap.entries.groupBy { it.key.kind }.toSortedMap()
|
||||||
|
for ((kind, attributesCounts) in attributesByKind) {
|
||||||
|
printMap(this, kind.name, attributesCounts.associate { (k, v) -> k.readableString to v })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <B : BuildTime, P : BuildPerformanceMetric> Printer.printTaskOverview(statisticsData: Collection<CompileStatisticsData<B, P>>) {
|
||||||
|
var allTasksTimeMs = 0L
|
||||||
|
var kotlinTotalTimeMs = 0L
|
||||||
|
val kotlinTasks = ArrayList<CompileStatisticsData<B, P>>()
|
||||||
|
|
||||||
|
for (task in statisticsData) {
|
||||||
|
val taskTimeMs = task.getDurationMs()
|
||||||
|
allTasksTimeMs += taskTimeMs
|
||||||
|
|
||||||
|
if (task.getFromKotlinPlugin() == true) {
|
||||||
|
kotlinTotalTimeMs += taskTimeMs
|
||||||
|
kotlinTasks.add(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kotlinTasks.isEmpty()) {
|
||||||
|
println("No Kotlin task was run")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val ktTaskPercent = (kotlinTotalTimeMs.toDouble() / allTasksTimeMs * 100).asString(1)
|
||||||
|
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(this)
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <B : BuildTime, P : BuildPerformanceMetric> Printer.printTasksLog(
|
||||||
|
statisticsData: List<CompileStatisticsData<B, P>>,
|
||||||
|
printMetrics: Boolean,
|
||||||
|
printCustomTaskMetrics: Printer.(CompileStatisticsData<B, P>) -> Unit,
|
||||||
|
) {
|
||||||
|
for (taskData in statisticsData.sortedWith(compareBy({ -it.getDurationMs() }, { it.getStartTimeMs() }))) {
|
||||||
|
printTaskLog(taskData)
|
||||||
|
if (printMetrics) {
|
||||||
|
printMetrics(
|
||||||
|
taskData.getBuildTimesMetrics(), taskData.getPerformanceMetrics(), taskData.getNonIncrementalAttributes(),
|
||||||
|
taskData.getGcTimeMetrics(), taskData.getGcCountMetrics()
|
||||||
|
)
|
||||||
|
printCustomTaskMetrics(taskData)
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun <B : BuildTime, P : BuildPerformanceMetric> Printer.printTaskLog(
|
||||||
|
statisticsData: CompileStatisticsData<B, P>,
|
||||||
|
) {
|
||||||
|
val skipMessage = statisticsData.getSkipMessage()
|
||||||
|
if (skipMessage != null) {
|
||||||
|
println("Task '${statisticsData.getTaskName()}' was skipped: $skipMessage")
|
||||||
|
} else {
|
||||||
|
println("Task '${statisticsData.getTaskName()}' finished in ${formatTime(statisticsData.getDurationMs())}")
|
||||||
|
}
|
||||||
|
|
||||||
|
statisticsData.getKotlinLanguageVersion()?.also {
|
||||||
|
withIndent("Task info:") {
|
||||||
|
println("Kotlin language version: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statisticsData.getIcLogLines().isNotEmpty()) {
|
||||||
|
withIndent("Compilation log for task '${statisticsData.getTaskName()}':") {
|
||||||
|
statisticsData.getIcLogLines().forEach { println(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,8 @@ package org.jetbrains.kotlin.jps.statistic
|
|||||||
import org.jetbrains.kotlin.build.report.metrics.JpsBuildPerformanceMetric
|
import org.jetbrains.kotlin.build.report.metrics.JpsBuildPerformanceMetric
|
||||||
import org.jetbrains.kotlin.build.report.metrics.JpsBuildTime
|
import org.jetbrains.kotlin.build.report.metrics.JpsBuildTime
|
||||||
import org.jetbrains.kotlin.build.report.statistics.CompileStatisticsData
|
import org.jetbrains.kotlin.build.report.statistics.CompileStatisticsData
|
||||||
import org.jetbrains.kotlin.build.report.statistics.file.FileReportService
|
import org.jetbrains.kotlin.build.report.statistics.file.Printer
|
||||||
import org.jetbrains.kotlin.compilerRunner.JpsKotlinLogger
|
import org.jetbrains.kotlin.build.report.statistics.file.ReadableFileReportService
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@@ -17,14 +17,13 @@ internal class JpsFileReportService(
|
|||||||
buildReportDir: File,
|
buildReportDir: File,
|
||||||
projectName: String,
|
projectName: String,
|
||||||
printMetrics: Boolean,
|
printMetrics: Boolean,
|
||||||
logger: JpsKotlinLogger,
|
|
||||||
private val changedFileListPerLimit: Int?
|
private val changedFileListPerLimit: Int?
|
||||||
) : FileReportService<JpsBuildTime, JpsBuildPerformanceMetric>(buildReportDir, projectName, printMetrics, logger) {
|
) : ReadableFileReportService<JpsBuildTime, JpsBuildPerformanceMetric>(buildReportDir, projectName, printMetrics) {
|
||||||
override fun printCustomTaskMetrics(statisticsData: CompileStatisticsData<JpsBuildTime, JpsBuildPerformanceMetric>) {
|
override fun printCustomTaskMetrics(statisticsData: CompileStatisticsData<JpsBuildTime, JpsBuildPerformanceMetric>, printer: Printer) {
|
||||||
val changedFiles = statisticsData.getChanges().let { changes ->
|
val changedFiles = statisticsData.getChanges().let { changes ->
|
||||||
changedFileListPerLimit?.let { changes.subList(0, min(it, changes.size)) } ?: changes
|
changedFileListPerLimit?.let { changes.subList(0, min(it, changes.size)) } ?: changes
|
||||||
}
|
}
|
||||||
p.println("Changed files: ${changedFiles.sorted()}")
|
printer.println("Changed files: ${changedFiles.sorted()}")
|
||||||
p.println("Execution result: ${statisticsData.getTaskResult()}")
|
printer.println("Execution result: ${statisticsData.getTaskResult()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.build.report.FileReportSettings
|
|||||||
import org.jetbrains.kotlin.build.report.HttpReportSettings
|
import org.jetbrains.kotlin.build.report.HttpReportSettings
|
||||||
import org.jetbrains.kotlin.build.report.metrics.*
|
import org.jetbrains.kotlin.build.report.metrics.*
|
||||||
import org.jetbrains.kotlin.build.report.statistics.*
|
import org.jetbrains.kotlin.build.report.statistics.*
|
||||||
|
import org.jetbrains.kotlin.build.report.statistics.file.ReadableFileReportData
|
||||||
import org.jetbrains.kotlin.compilerRunner.JpsKotlinLogger
|
import org.jetbrains.kotlin.compilerRunner.JpsKotlinLogger
|
||||||
import org.jetbrains.kotlin.jps.build.KotlinChunk
|
import org.jetbrains.kotlin.jps.build.KotlinChunk
|
||||||
import org.jetbrains.kotlin.jps.build.KotlinDirtySourceFilesHolder
|
import org.jetbrains.kotlin.jps.build.KotlinDirtySourceFilesHolder
|
||||||
@@ -143,10 +144,13 @@ class JpsStatisticsReportServiceImpl(
|
|||||||
httpService?.sendData(compileStatisticsData, loggerAdapter)
|
httpService?.sendData(compileStatisticsData, loggerAdapter)
|
||||||
fileReportSettings?.also {
|
fileReportSettings?.also {
|
||||||
JpsFileReportService(
|
JpsFileReportService(
|
||||||
it.buildReportDir, context.projectDescriptor.project.name, true, loggerAdapter, it.changedFileListPerLimit
|
it.buildReportDir, context.projectDescriptor.project.name, true, it.changedFileListPerLimit
|
||||||
).process(
|
).process(
|
||||||
compileStatisticsData,
|
ReadableFileReportData(
|
||||||
BuildStartParameters(tasks = listOf(jpsBuildTaskName)), emptyList(),
|
compileStatisticsData,
|
||||||
|
BuildStartParameters(tasks = listOf(jpsBuildTaskName)), emptyList()
|
||||||
|
),
|
||||||
|
loggerAdapter
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+86
@@ -5,9 +5,16 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.gradle
|
package org.jetbrains.kotlin.gradle
|
||||||
|
|
||||||
|
import com.google.gson.*
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
import org.gradle.api.logging.LogLevel
|
import org.gradle.api.logging.LogLevel
|
||||||
import org.gradle.util.GradleVersion
|
import org.gradle.util.GradleVersion
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.BuildMetrics
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.GradleBuildPerformanceMetric
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.GradleBuildTime
|
||||||
|
import org.jetbrains.kotlin.build.report.statistics.StatTag
|
||||||
import org.jetbrains.kotlin.build.report.statistics.formatSize
|
import org.jetbrains.kotlin.build.report.statistics.formatSize
|
||||||
|
import org.jetbrains.kotlin.buildtools.api.SourcesChanges
|
||||||
import org.jetbrains.kotlin.gradle.internal.build.metrics.GradleBuildMetricsData
|
import org.jetbrains.kotlin.gradle.internal.build.metrics.GradleBuildMetricsData
|
||||||
import org.jetbrains.kotlin.gradle.report.BuildReportType
|
import org.jetbrains.kotlin.gradle.report.BuildReportType
|
||||||
import org.jetbrains.kotlin.gradle.testbase.*
|
import org.jetbrains.kotlin.gradle.testbase.*
|
||||||
@@ -17,10 +24,14 @@ import java.nio.file.Path
|
|||||||
import kotlin.io.path.*
|
import kotlin.io.path.*
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
||||||
|
import org.jetbrains.kotlin.gradle.report.data.BuildExecutionData
|
||||||
|
import org.jetbrains.kotlin.gradle.report.data.BuildOperationRecord
|
||||||
import org.jetbrains.kotlin.gradle.testbase.TestVersions.ThirdPartyDependencies.GRADLE_ENTERPRISE_PLUGIN_VERSION
|
import org.jetbrains.kotlin.gradle.testbase.TestVersions.ThirdPartyDependencies.GRADLE_ENTERPRISE_PLUGIN_VERSION
|
||||||
|
import java.lang.reflect.Type
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import kotlin.streams.asSequence
|
import kotlin.streams.asSequence
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
@DisplayName("Build reports")
|
@DisplayName("Build reports")
|
||||||
@JvmGradlePluginTests
|
@JvmGradlePluginTests
|
||||||
@@ -507,4 +518,79 @@ class BuildReportsIT : KGPBaseTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DisplayName("json validation")
|
||||||
|
@GradleTestVersions(
|
||||||
|
additionalVersions = [TestVersions.Gradle.G_7_6, TestVersions.Gradle.G_8_0],
|
||||||
|
)
|
||||||
|
@GradleTest
|
||||||
|
fun testJsonBuildMetricsFileValidation(gradleVersion: GradleVersion) {
|
||||||
|
project("simpleProject", gradleVersion) {
|
||||||
|
buildAndFail(
|
||||||
|
"compileKotlin", "-Pkotlin.build.report.output=JSON",
|
||||||
|
) {
|
||||||
|
assertOutputContains("Can't configure json report: 'kotlin.build.report.json.directory' property is mandatory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("json report")
|
||||||
|
@GradleTestVersions(
|
||||||
|
additionalVersions = [TestVersions.Gradle.G_7_6, TestVersions.Gradle.G_8_0],
|
||||||
|
)
|
||||||
|
@GradleTest
|
||||||
|
fun testJsonBuildReport(gradleVersion: GradleVersion) {
|
||||||
|
project("simpleProject", gradleVersion) {
|
||||||
|
build(
|
||||||
|
"compileKotlin",
|
||||||
|
"-Pkotlin.build.report.output=JSON",
|
||||||
|
"-Pkotlin.build.report.json.directory=${projectPath.resolve("report").pathString}"
|
||||||
|
) {
|
||||||
|
//TODO: KT-66071 update deserialization
|
||||||
|
val gsonBuilder = GsonBuilder()
|
||||||
|
.registerTypeAdapter(BuildOperationRecord::class.java, object : JsonDeserializer<BuildOperationRecord> {
|
||||||
|
override fun deserialize(
|
||||||
|
json: JsonElement?,
|
||||||
|
typeOfT: Type?,
|
||||||
|
context: JsonDeserializationContext?,
|
||||||
|
): BuildOperationRecord? {
|
||||||
|
//workaround to read both TaskRecord and TransformRecord
|
||||||
|
return context?.deserialize(json, BuildOperationRecordImpl::class.java)
|
||||||
|
}
|
||||||
|
}).registerTypeAdapter(SourcesChanges::class.java, object : JsonDeserializer<SourcesChanges> {
|
||||||
|
override fun deserialize(
|
||||||
|
json: JsonElement?,
|
||||||
|
typeOfT: Type?,
|
||||||
|
context: JsonDeserializationContext,
|
||||||
|
): SourcesChanges? {
|
||||||
|
return null //ignore source changes right now
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val jsonReport = projectPath.getSingleFileInDir("report")
|
||||||
|
val buildExecutionData = jsonReport.bufferedReader().use {
|
||||||
|
gsonBuilder.create().fromJson(JsonReader(it), BuildExecutionData::class.java) as BuildExecutionData
|
||||||
|
}
|
||||||
|
val buildOperationRecords = buildExecutionData.buildOperationRecord.first { it.path == ":compileKotlin" } as BuildOperationRecordImpl
|
||||||
|
assertEquals(KotlinVersion.DEFAULT, buildOperationRecords.kotlinLanguageVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class BuildOperationRecordImpl(
|
||||||
|
override val path: String,
|
||||||
|
override val classFqName: String,
|
||||||
|
override val isFromKotlinPlugin: Boolean,
|
||||||
|
override val startTimeMs: Long, // Measured by System.currentTimeMillis(),
|
||||||
|
override val totalTimeMs: Long,
|
||||||
|
override val buildMetrics: BuildMetrics<GradleBuildTime, GradleBuildPerformanceMetric>,
|
||||||
|
override val didWork: Boolean,
|
||||||
|
override val skipMessage: String?,
|
||||||
|
override val icLogLines: List<String>,
|
||||||
|
//taskRecords
|
||||||
|
val kotlinLanguageVersion: KotlinVersion?,
|
||||||
|
val changedFiles: SourcesChanges? = null,
|
||||||
|
val compilerArguments: Array<String> = emptyArray(),
|
||||||
|
val statTags: Set<StatTag> = emptySet(),
|
||||||
|
): BuildOperationRecord
|
||||||
+4
@@ -110,6 +110,9 @@ internal class PropertiesProvider private constructor(private val project: Proje
|
|||||||
val buildReportMetrics: Boolean
|
val buildReportMetrics: Boolean
|
||||||
get() = booleanProperty("kotlin.build.report.metrics") ?: false
|
get() = booleanProperty("kotlin.build.report.metrics") ?: false
|
||||||
|
|
||||||
|
val buildReportJsonDir: File?
|
||||||
|
get() = property(PropertyNames.KOTLIN_BUILD_REPORT_JSON_DIR).orNull?.let { File(it) }
|
||||||
|
|
||||||
val buildReportVerbose: Boolean
|
val buildReportVerbose: Boolean
|
||||||
get() = booleanProperty("kotlin.build.report.verbose") ?: false
|
get() = booleanProperty("kotlin.build.report.verbose") ?: false
|
||||||
|
|
||||||
@@ -643,6 +646,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
|
|||||||
val KOTLIN_JS_KARMA_BROWSERS = property("kotlin.js.browser.karma.browsers")
|
val KOTLIN_JS_KARMA_BROWSERS = property("kotlin.js.browser.karma.browsers")
|
||||||
val KOTLIN_BUILD_REPORT_SINGLE_FILE = property("kotlin.build.report.single_file")
|
val KOTLIN_BUILD_REPORT_SINGLE_FILE = property("kotlin.build.report.single_file")
|
||||||
val KOTLIN_BUILD_REPORT_HTTP_URL = property("kotlin.build.report.http.url")
|
val KOTLIN_BUILD_REPORT_HTTP_URL = property("kotlin.build.report.http.url")
|
||||||
|
val KOTLIN_BUILD_REPORT_JSON_DIR = property("kotlin.build.report.json.directory")
|
||||||
val KOTLIN_OPTIONS_SUPPRESS_FREEARGS_MODIFICATION_WARNING = property("kotlin.options.suppressFreeCompilerArgsModificationWarning")
|
val KOTLIN_OPTIONS_SUPPRESS_FREEARGS_MODIFICATION_WARNING = property("kotlin.options.suppressFreeCompilerArgsModificationWarning")
|
||||||
val KOTLIN_NATIVE_USE_XCODE_MESSAGE_STYLE = property("kotlin.native.useXcodeMessageStyle")
|
val KOTLIN_NATIVE_USE_XCODE_MESSAGE_STYLE = property("kotlin.native.useXcodeMessageStyle")
|
||||||
val KOTLIN_INCREMENTAL_USE_CLASSPATH_SNAPSHOT = property("kotlin.incremental.useClasspathSnapshot")
|
val KOTLIN_INCREMENTAL_USE_CLASSPATH_SNAPSHOT = property("kotlin.incremental.useClasspathSnapshot")
|
||||||
|
|||||||
+2
-4
@@ -7,13 +7,11 @@ package org.jetbrains.kotlin.gradle.plugin.statistics
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.build.report.metrics.GradleBuildPerformanceMetric
|
import org.jetbrains.kotlin.build.report.metrics.GradleBuildPerformanceMetric
|
||||||
import org.jetbrains.kotlin.build.report.metrics.GradleBuildTime
|
import org.jetbrains.kotlin.build.report.metrics.GradleBuildTime
|
||||||
import org.jetbrains.kotlin.build.report.statistics.file.FileReportService
|
import org.jetbrains.kotlin.build.report.statistics.file.ReadableFileReportService
|
||||||
import org.jetbrains.kotlin.buildtools.api.KotlinLogger
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class GradleFileReportService(
|
class GradleFileReportService(
|
||||||
buildReportDir: File,
|
buildReportDir: File,
|
||||||
projectName: String,
|
projectName: String,
|
||||||
printMetrics: Boolean,
|
printMetrics: Boolean,
|
||||||
logger: KotlinLogger,
|
) : ReadableFileReportService<GradleBuildTime, GradleBuildPerformanceMetric>(buildReportDir, projectName, printMetrics)
|
||||||
) : FileReportService<GradleBuildTime, GradleBuildPerformanceMetric>(buildReportDir, projectName, printMetrics, logger) {}
|
|
||||||
+1
@@ -39,6 +39,7 @@ internal fun collectGeneralConfigurationTimeMetrics(
|
|||||||
BuildReportType.HTTP -> configurationTimeMetrics.put(BooleanMetrics.HTTP_BUILD_REPORT, true)
|
BuildReportType.HTTP -> configurationTimeMetrics.put(BooleanMetrics.HTTP_BUILD_REPORT, true)
|
||||||
BuildReportType.SINGLE_FILE -> configurationTimeMetrics.put(BooleanMetrics.SINGLE_FILE_BUILD_REPORT, true)
|
BuildReportType.SINGLE_FILE -> configurationTimeMetrics.put(BooleanMetrics.SINGLE_FILE_BUILD_REPORT, true)
|
||||||
BuildReportType.TRY_NEXT_CONSOLE -> {}//ignore
|
BuildReportType.TRY_NEXT_CONSOLE -> {}//ignore
|
||||||
|
BuildReportType.JSON -> configurationTimeMetrics.put(BooleanMetrics.JSON_BUILD_REPORT, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
configurationTimeMetrics.put(StringMetrics.PROJECT_PATH, gradle.rootProject.projectDir.absolutePath)
|
configurationTimeMetrics.put(StringMetrics.PROJECT_PATH, gradle.rootProject.projectDir.absolutePath)
|
||||||
|
|||||||
+2
-1
@@ -13,9 +13,10 @@ enum class BuildReportType : Serializable {
|
|||||||
BUILD_SCAN,
|
BUILD_SCAN,
|
||||||
SINGLE_FILE,
|
SINGLE_FILE,
|
||||||
TRY_NEXT_CONSOLE,
|
TRY_NEXT_CONSOLE,
|
||||||
|
JSON,
|
||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val serialVersionUID: Long = 2L
|
const val serialVersionUID: Long = 3L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+12
-9
@@ -9,11 +9,8 @@ import org.gradle.api.Project
|
|||||||
import org.gradle.api.logging.Logging
|
import org.gradle.api.logging.Logging
|
||||||
import org.gradle.tooling.events.task.TaskFinishEvent
|
import org.gradle.tooling.events.task.TaskFinishEvent
|
||||||
import org.jetbrains.kotlin.build.report.metrics.ValueType
|
import org.jetbrains.kotlin.build.report.metrics.ValueType
|
||||||
import org.jetbrains.kotlin.build.report.statistics.HttpReportService
|
import org.jetbrains.kotlin.build.report.statistics.*
|
||||||
import org.jetbrains.kotlin.build.report.statistics.formatSize
|
import org.jetbrains.kotlin.build.report.statistics.file.ReadableFileReportData
|
||||||
import org.jetbrains.kotlin.build.report.statistics.BuildFinishStatisticsData
|
|
||||||
import org.jetbrains.kotlin.build.report.statistics.BuildStartParameters
|
|
||||||
import org.jetbrains.kotlin.build.report.statistics.StatTag
|
|
||||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
||||||
import org.jetbrains.kotlin.gradle.plugin.statistics.GradleFileReportService
|
import org.jetbrains.kotlin.gradle.plugin.statistics.GradleFileReportService
|
||||||
import org.jetbrains.kotlin.gradle.report.data.BuildExecutionData
|
import org.jetbrains.kotlin.gradle.report.data.BuildExecutionData
|
||||||
@@ -69,11 +66,13 @@ class BuildReportsService {
|
|||||||
it.buildReportDir,
|
it.buildReportDir,
|
||||||
parameters.projectName,
|
parameters.projectName,
|
||||||
it.includeMetricsInReport,
|
it.includeMetricsInReport,
|
||||||
loggerAdapter
|
|
||||||
).process(
|
).process(
|
||||||
transformOperationRecordsToCompileStatisticsData(buildOperationRecords, parameters, onlyKotlinTask = false),
|
ReadableFileReportData(
|
||||||
parameters.startParameters,
|
transformOperationRecordsToCompileStatisticsData(buildOperationRecords, parameters, onlyKotlinTask = false),
|
||||||
failureMessages.filter { it.isNotEmpty() },
|
parameters.startParameters,
|
||||||
|
failureMessages.filter { it.isNotEmpty() },
|
||||||
|
),
|
||||||
|
loggerAdapter
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +84,10 @@ class BuildReportsService {
|
|||||||
reportTryNextToConsole(buildData)
|
reportTryNextToConsole(buildData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reportingSettings.jsonOutputDir?.also {
|
||||||
|
JsonReportService(it, parameters.projectName).process(buildData, loggerAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
//It's expected that bad internet connection can cause a significant delay for big project
|
//It's expected that bad internet connection can cause a significant delay for big project
|
||||||
executorService.shutdown()
|
executorService.shutdown()
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -18,6 +18,7 @@ data class ReportingSettings(
|
|||||||
val httpReportSettings: HttpReportSettings? = null,
|
val httpReportSettings: HttpReportSettings? = null,
|
||||||
val buildScanReportSettings: BuildScanSettings? = null,
|
val buildScanReportSettings: BuildScanSettings? = null,
|
||||||
val singleOutputFile: File? = null,
|
val singleOutputFile: File? = null,
|
||||||
|
val jsonOutputDir: File? = null,
|
||||||
val experimentalTryNextConsoleOutput: Boolean = false,
|
val experimentalTryNextConsoleOutput: Boolean = false,
|
||||||
val includeCompilerArguments: Boolean = false,
|
val includeCompilerArguments: Boolean = false,
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
|
|||||||
+7
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.build.report.metrics.GradleBuildTime
|
|||||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
|
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
|
||||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_BUILD_REPORT_SINGLE_FILE
|
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_BUILD_REPORT_SINGLE_FILE
|
||||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_BUILD_REPORT_HTTP_URL
|
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_BUILD_REPORT_HTTP_URL
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_BUILD_REPORT_JSON_DIR
|
||||||
import org.jetbrains.kotlin.gradle.plugin.internal.isProjectIsolationEnabled
|
import org.jetbrains.kotlin.gradle.plugin.internal.isProjectIsolationEnabled
|
||||||
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toUpperCaseAsciiOnly
|
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toUpperCaseAsciiOnly
|
||||||
|
|
||||||
@@ -78,6 +79,11 @@ internal fun reportingSettings(project: Project): ReportingSettings {
|
|||||||
?: throw IllegalStateException("Can't configure single file report: '$KOTLIN_BUILD_REPORT_SINGLE_FILE' property is mandatory")
|
?: throw IllegalStateException("Can't configure single file report: '$KOTLIN_BUILD_REPORT_SINGLE_FILE' property is mandatory")
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
|
val jsonReportDir = if (buildReportOutputTypes.contains(BuildReportType.JSON)) {
|
||||||
|
properties.buildReportJsonDir
|
||||||
|
?: throw IllegalStateException("Can't configure json report: '$KOTLIN_BUILD_REPORT_JSON_DIR' property is mandatory")
|
||||||
|
} else null
|
||||||
|
|
||||||
//temporary solution. support old property
|
//temporary solution. support old property
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
val oldSingleBuildMetric = properties.singleBuildMetricsFile?.also { buildReportOutputTypes.add(BuildReportType.SINGLE_FILE) }
|
val oldSingleBuildMetric = properties.singleBuildMetricsFile?.also { buildReportOutputTypes.add(BuildReportType.SINGLE_FILE) }
|
||||||
@@ -90,6 +96,7 @@ internal fun reportingSettings(project: Project): ReportingSettings {
|
|||||||
buildScanReportSettings = buildScanSettings,
|
buildScanReportSettings = buildScanSettings,
|
||||||
buildReportOutputs = buildReportOutputTypes,
|
buildReportOutputs = buildReportOutputTypes,
|
||||||
singleOutputFile = singleOutputFile ?: oldSingleBuildMetric,
|
singleOutputFile = singleOutputFile ?: oldSingleBuildMetric,
|
||||||
|
jsonOutputDir = jsonReportDir,
|
||||||
includeCompilerArguments = properties.buildReportIncludeCompilerArguments,
|
includeCompilerArguments = properties.buildReportIncludeCompilerArguments,
|
||||||
experimentalTryNextConsoleOutput = experimentalTryNextEnabled
|
experimentalTryNextConsoleOutput = experimentalTryNextEnabled
|
||||||
)
|
)
|
||||||
|
|||||||
+5
-6
@@ -10,14 +10,13 @@ import org.jetbrains.kotlin.build.report.metrics.GradleBuildPerformanceMetric
|
|||||||
import org.jetbrains.kotlin.build.report.metrics.GradleBuildTime
|
import org.jetbrains.kotlin.build.report.metrics.GradleBuildTime
|
||||||
import org.jetbrains.kotlin.build.report.statistics.BuildStartParameters
|
import org.jetbrains.kotlin.build.report.statistics.BuildStartParameters
|
||||||
|
|
||||||
class BuildExecutionData(
|
data class BuildExecutionData(
|
||||||
val startParameters: BuildStartParameters,
|
val startParameters: BuildStartParameters,
|
||||||
val failureMessages: List<String?>,
|
val failureMessages: List<String?>,
|
||||||
val buildOperationRecord: Collection<BuildOperationRecord>
|
val buildOperationRecord: Collection<BuildOperationRecord>,
|
||||||
) {
|
) {
|
||||||
val aggregatedMetrics by lazy {
|
val aggregatedMetrics = BuildMetrics<GradleBuildTime, GradleBuildPerformanceMetric>().also { acc ->
|
||||||
BuildMetrics<GradleBuildTime, GradleBuildPerformanceMetric>().also { acc ->
|
buildOperationRecord.forEach { acc.addAll(it.buildMetrics) }
|
||||||
buildOperationRecord.forEach { acc.addAll(it.buildMetrics) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+2
-1
@@ -52,6 +52,7 @@ enum class BooleanMetrics(val type: BooleanOverridePolicy, val anonymization: Bo
|
|||||||
BUILD_SCAN_BUILD_REPORT(OR, SAFE),
|
BUILD_SCAN_BUILD_REPORT(OR, SAFE),
|
||||||
HTTP_BUILD_REPORT(OR, SAFE),
|
HTTP_BUILD_REPORT(OR, SAFE),
|
||||||
SINGLE_FILE_BUILD_REPORT(OR, SAFE),
|
SINGLE_FILE_BUILD_REPORT(OR, SAFE),
|
||||||
|
JSON_BUILD_REPORT(OR, SAFE),
|
||||||
|
|
||||||
//Dokka features
|
//Dokka features
|
||||||
ENABLED_DOKKA(OR, SAFE),
|
ENABLED_DOKKA(OR, SAFE),
|
||||||
@@ -80,6 +81,6 @@ enum class BooleanMetrics(val type: BooleanOverridePolicy, val anonymization: Bo
|
|||||||
COCOAPODS_PLUGIN_ENABLED(OR, SAFE);
|
COCOAPODS_PLUGIN_ENABLED(OR, SAFE);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION = 3
|
const val VERSION = 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ private const val STRING_METRICS_RELATIVE_PATH = "$SOURCE_CODE_RELATIVE_PATH/Str
|
|||||||
private const val NUMERICAL_METRICS_RELATIVE_PATH = "$SOURCE_CODE_RELATIVE_PATH/NumericalMetrics.kt"
|
private const val NUMERICAL_METRICS_RELATIVE_PATH = "$SOURCE_CODE_RELATIVE_PATH/NumericalMetrics.kt"
|
||||||
|
|
||||||
private val STRING_METRICS_EXPECTED_VERSION_AND_HASH = Pair(1, "90347332db2ce54b51e7daa64595371e")
|
private val STRING_METRICS_EXPECTED_VERSION_AND_HASH = Pair(1, "90347332db2ce54b51e7daa64595371e")
|
||||||
private val BOOLEAN_METRICS_EXPECTED_VERSION_AND_HASH = Pair(3, "3c8c4ca636adee168e99862244a22520")
|
private val BOOLEAN_METRICS_EXPECTED_VERSION_AND_HASH = Pair(4, "56284d1f90da498710ecf3207c781dd7")
|
||||||
private val NUMERICAL_METRICS_EXPECTED_VERSION_AND_HASH = Pair(2, "d8c1a1f4fb7227fbe8247320bf3370ca")
|
private val NUMERICAL_METRICS_EXPECTED_VERSION_AND_HASH = Pair(2, "d8c1a1f4fb7227fbe8247320bf3370ca")
|
||||||
private val SOURCE_FOLDER_EXPECTED_VERSION_AND_HASH =
|
private val SOURCE_FOLDER_EXPECTED_VERSION_AND_HASH =
|
||||||
Pair(
|
Pair(
|
||||||
|
|||||||
Reference in New Issue
Block a user