Move kotlin-build-statistic project to :compiler
This commit is contained in:
committed by
Space Team
parent
e34dd043da
commit
2a391f7330
@@ -0,0 +1,28 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion as GradleKotlinVersion
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
|
||||
|
||||
description = "Kotlin Build Report Common"
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":core:util.runtime"))
|
||||
compileOnly(project(":compiler:util"))
|
||||
compileOnly(project(":kotlin-util-io"))
|
||||
compileOnly(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false }
|
||||
|
||||
compileOnly(kotlinStdlib())
|
||||
compileOnly(intellijCore())
|
||||
implementation(commonDependency("com.google.code.gson:gson"))
|
||||
testApi(kotlinStdlib())
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" { projectDefault() }
|
||||
}
|
||||
|
||||
projectTest(jUnitMode = JUnitMode.JUnit5, parallel = true)
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.metrics
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
enum class BuildAttributeKind : Serializable {
|
||||
REBUILD_REASON;
|
||||
|
||||
companion object {
|
||||
const val serialVersionUID = 0L
|
||||
}
|
||||
}
|
||||
|
||||
enum class BuildAttribute(val kind: BuildAttributeKind, val readableString: String) : Serializable {
|
||||
NO_BUILD_HISTORY(BuildAttributeKind.REBUILD_REASON, "Build history file not found"),
|
||||
NO_ABI_SNAPSHOT(BuildAttributeKind.REBUILD_REASON, "ABI snapshot not found"),
|
||||
NO_LAST_BUILD_INFO(BuildAttributeKind.REBUILD_REASON, "Last build info not found"),
|
||||
INVALID_LAST_BUILD_INFO(BuildAttributeKind.REBUILD_REASON, "Last build info corrupted"),
|
||||
CLASSPATH_SNAPSHOT_NOT_FOUND(BuildAttributeKind.REBUILD_REASON, "Classpath snapshot not found"),
|
||||
IC_FAILED_TO_GET_CHANGED_FILES(BuildAttributeKind.REBUILD_REASON, "Failed to get changed files"),
|
||||
IC_FAILED_TO_COMPUTE_FILES_TO_RECOMPILE(BuildAttributeKind.REBUILD_REASON, "Failed to compute files to recompile"),
|
||||
IC_FAILED_TO_COMPILE_INCREMENTALLY(BuildAttributeKind.REBUILD_REASON, "Failed to compile incrementally"),
|
||||
IC_FAILED_TO_CLOSE_CACHES(BuildAttributeKind.REBUILD_REASON, "Failed to close caches"),
|
||||
UNKNOWN_CHANGES_IN_GRADLE_INPUTS(BuildAttributeKind.REBUILD_REASON, "Unknown Gradle changes"),
|
||||
JAVA_CHANGE_UNTRACKED_FILE_IS_REMOVED(BuildAttributeKind.REBUILD_REASON, "Untracked Java file is removed"),
|
||||
JAVA_CHANGE_UNEXPECTED_PSI(BuildAttributeKind.REBUILD_REASON, "Java PSI file is expected"),
|
||||
JAVA_CHANGE_UNKNOWN_QUALIFIER(BuildAttributeKind.REBUILD_REASON, "Unknown Java qualifier name"),
|
||||
DEP_CHANGE_REMOVED_ENTRY(BuildAttributeKind.REBUILD_REASON, "Jar file is removed form dependency"),
|
||||
DEP_CHANGE_HISTORY_IS_NOT_FOUND(BuildAttributeKind.REBUILD_REASON, "Dependency history not found"),
|
||||
DEP_CHANGE_HISTORY_CANNOT_BE_READ(BuildAttributeKind.REBUILD_REASON, "Dependency history can not be read"),
|
||||
DEP_CHANGE_HISTORY_NO_KNOWN_BUILDS(BuildAttributeKind.REBUILD_REASON, "Dependency history id not available"),
|
||||
DEP_CHANGE_NON_INCREMENTAL_BUILD_IN_DEP(BuildAttributeKind.REBUILD_REASON, "Non incremental build in history"),
|
||||
IN_PROCESS_EXECUTION(BuildAttributeKind.REBUILD_REASON, "In-process execution"),
|
||||
OUT_OF_PROCESS_EXECUTION(BuildAttributeKind.REBUILD_REASON, "Out of process execution"),
|
||||
IC_IS_NOT_ENABLED(BuildAttributeKind.REBUILD_REASON, "Incremental compilation is not enabled");
|
||||
|
||||
companion object {
|
||||
const val serialVersionUID = 0L
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.metrics
|
||||
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
class BuildAttributes : Serializable {
|
||||
private val myAttributes =
|
||||
EnumMap<BuildAttribute, Int>(
|
||||
BuildAttribute::class.java
|
||||
)
|
||||
|
||||
fun add(attr: BuildAttribute, count: Int = 1) {
|
||||
myAttributes[attr] = myAttributes.getOrDefault(attr, 0) + count
|
||||
}
|
||||
|
||||
fun addAll(other: BuildAttributes) {
|
||||
other.myAttributes.forEach { (attr, n) -> add(attr, n) }
|
||||
}
|
||||
|
||||
fun asMap(): Map<BuildAttribute, Int> = myAttributes
|
||||
|
||||
companion object {
|
||||
const val serialVersionUID = 0L
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.metrics
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class BuildMetrics(
|
||||
val buildTimes: BuildTimes = BuildTimes(),
|
||||
val buildPerformanceMetrics: BuildPerformanceMetrics = BuildPerformanceMetrics(),
|
||||
val buildAttributes: BuildAttributes = BuildAttributes(),
|
||||
val gcMetrics: GcMetrics = GcMetrics()
|
||||
) : Serializable {
|
||||
fun addAll(other: BuildMetrics) {
|
||||
buildTimes.addAll(other.buildTimes)
|
||||
buildPerformanceMetrics.addAll(other.buildPerformanceMetrics)
|
||||
buildAttributes.addAll(other.buildAttributes)
|
||||
gcMetrics.addAll(other.gcMetrics)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val serialVersionUID = 0L
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.metrics
|
||||
|
||||
import java.lang.management.ManagementFactory
|
||||
|
||||
interface BuildMetricsReporter {
|
||||
fun startMeasure(time: BuildTime)
|
||||
fun endMeasure(time: BuildTime)
|
||||
fun addTimeMetricNs(time: BuildTime, durationNs: Long)
|
||||
fun addTimeMetricMs(time: BuildTime, durationMs: Long) = addTimeMetricNs(time, durationMs * 1_000_000)
|
||||
|
||||
fun addMetric(metric: BuildPerformanceMetric, value: Long)
|
||||
fun addTimeMetric(metric: BuildPerformanceMetric)
|
||||
|
||||
//Change metric to enum if possible
|
||||
fun addGcMetric(metric: String, value: GcMetric)
|
||||
fun startGcMetric(name: String, value: GcMetric)
|
||||
fun endGcMetric(name: String, value: GcMetric)
|
||||
|
||||
fun addAttribute(attribute: BuildAttribute)
|
||||
|
||||
fun getMetrics(): BuildMetrics
|
||||
fun addMetrics(metrics: BuildMetrics)
|
||||
}
|
||||
|
||||
inline fun <T> BuildMetricsReporter.measure(time: BuildTime, fn: () -> T): T {
|
||||
startMeasure(time)
|
||||
try {
|
||||
return fn()
|
||||
} finally {
|
||||
endMeasure(time)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun BuildMetricsReporter.startMeasureGc() {
|
||||
ManagementFactory.getGarbageCollectorMXBeans().forEach {
|
||||
startGcMetric(it.name, GcMetric(it.collectionTime, it.collectionCount))
|
||||
}
|
||||
}
|
||||
|
||||
fun BuildMetricsReporter.endMeasureGc() {
|
||||
ManagementFactory.getGarbageCollectorMXBeans().forEach {
|
||||
endGcMetric(it.name, GcMetric(it.collectionTime, it.collectionCount))
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.metrics
|
||||
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
class BuildMetricsReporterImpl : BuildMetricsReporter, Serializable {
|
||||
private val myBuildTimeStartNs: EnumMap<BuildTime, Long> =
|
||||
EnumMap(
|
||||
BuildTime::class.java
|
||||
)
|
||||
private val myGcPerformance = HashMap<String, GcMetric>()
|
||||
private val myBuildTimes = BuildTimes()
|
||||
private val myBuildMetrics = BuildPerformanceMetrics()
|
||||
private val myBuildAttributes = BuildAttributes()
|
||||
private val myGcMetrics = GcMetrics()
|
||||
|
||||
override fun startMeasure(time: BuildTime) {
|
||||
if (time in myBuildTimeStartNs) {
|
||||
error("$time was restarted before it finished")
|
||||
}
|
||||
myBuildTimeStartNs[time] = System.nanoTime()
|
||||
}
|
||||
|
||||
override fun endMeasure(time: BuildTime) {
|
||||
val startNs = myBuildTimeStartNs.remove(time) ?: error("$time finished before it started")
|
||||
val durationNs = System.nanoTime() - startNs
|
||||
myBuildTimes.addTimeNs(time, durationNs)
|
||||
}
|
||||
|
||||
override fun startGcMetric(name: String, value: GcMetric) {
|
||||
if (name in myGcPerformance) {
|
||||
error("$name was restarted before it finished")
|
||||
}
|
||||
myGcPerformance[name] = value
|
||||
}
|
||||
|
||||
override fun endGcMetric(name: String, value: GcMetric) {
|
||||
val startValue = myGcPerformance.remove(name) ?: error("$name finished before it started")
|
||||
val diff = value - startValue
|
||||
myGcMetrics.add(name, diff)
|
||||
}
|
||||
|
||||
override fun addTimeMetricNs(time: BuildTime, durationNs: Long) {
|
||||
myBuildTimes.addTimeNs(time, durationNs)
|
||||
}
|
||||
|
||||
override fun addMetric(metric: BuildPerformanceMetric, value: Long) {
|
||||
myBuildMetrics.add(metric, value)
|
||||
}
|
||||
|
||||
override fun addTimeMetric(metric: BuildPerformanceMetric) {
|
||||
when (metric.type) {
|
||||
ValueType.NANOSECONDS -> myBuildMetrics.add(metric, System.nanoTime())
|
||||
ValueType.MILLISECONDS -> myBuildMetrics.add(metric, System.currentTimeMillis())
|
||||
ValueType.TIME -> myBuildMetrics.add(metric, System.currentTimeMillis())
|
||||
else -> error("Unable to add time metric for '${metric.type}' type")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun addGcMetric(metric: String, value: GcMetric) {
|
||||
myGcMetrics.add(metric, value)
|
||||
}
|
||||
|
||||
override fun addAttribute(attribute: BuildAttribute) {
|
||||
myBuildAttributes.add(attribute)
|
||||
}
|
||||
|
||||
override fun getMetrics(): BuildMetrics =
|
||||
BuildMetrics(
|
||||
buildTimes = myBuildTimes,
|
||||
buildPerformanceMetrics = myBuildMetrics,
|
||||
buildAttributes = myBuildAttributes,
|
||||
gcMetrics = myGcMetrics
|
||||
)
|
||||
|
||||
override fun addMetrics(metrics: BuildMetrics) {
|
||||
myBuildAttributes.addAll(metrics.buildAttributes)
|
||||
myBuildTimes.addAll(metrics.buildTimes)
|
||||
myBuildMetrics.addAll(metrics.buildPerformanceMetrics)
|
||||
myGcMetrics.addAll(metrics.gcMetrics)
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.metrics
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
@Suppress("Reformat")
|
||||
enum class BuildPerformanceMetric(val parent: BuildPerformanceMetric? = null, val readableString: String, val type: ValueType) : Serializable {
|
||||
CACHE_DIRECTORY_SIZE(readableString = "Total size of the cache directory", type = ValueType.BYTES),
|
||||
LOOKUP_SIZE(CACHE_DIRECTORY_SIZE, "Lookups size", type = ValueType.BYTES),
|
||||
SNAPSHOT_SIZE(CACHE_DIRECTORY_SIZE, "ABI snapshot size", type = ValueType.BYTES),
|
||||
|
||||
BUNDLE_SIZE(readableString = "Total size of the final bundle", type = ValueType.BYTES),
|
||||
|
||||
DAEMON_INCREASED_MEMORY(readableString = "Increase memory usage", type = ValueType.BYTES),
|
||||
DAEMON_MEMORY_USAGE(readableString = "Total memory usage at the end of build", type = ValueType.BYTES),
|
||||
|
||||
DAEMON_GC_TIME(readableString = "Time spent in GC", type = ValueType.NANOSECONDS),
|
||||
DAEMON_GC_COUNT(readableString = "Count of GC", type = ValueType.NUMBER),
|
||||
|
||||
COMPILE_ITERATION(parent = null, "Total compiler iteration", type = ValueType.NUMBER),
|
||||
ANALYZED_LINES_NUMBER(parent = COMPILE_ITERATION, "Number of lines analyzed", type = ValueType.NUMBER),
|
||||
CODE_GENERATED_LINES_NUMBER(parent = COMPILE_ITERATION, "Number of lines for code generation", type = ValueType.NUMBER),
|
||||
ANALYSIS_LPS(parent = COMPILE_ITERATION, "Analysis lines per second", type = ValueType.NUMBER),
|
||||
CODE_GENERATION_LPS(parent = COMPILE_ITERATION, "Code generation lines per second", type = ValueType.NUMBER),
|
||||
|
||||
// Metrics for the `kotlin.incremental.useClasspathSnapshot` feature
|
||||
CLASSPATH_ENTRY_SNAPSHOT_TRANSFORM_EXECUTION_COUNT(parent = null, "Number of times 'ClasspathEntrySnapshotTransform' ran", type = ValueType.NUMBER),
|
||||
JAR_CLASSPATH_ENTRY_SIZE(parent = CLASSPATH_ENTRY_SNAPSHOT_TRANSFORM_EXECUTION_COUNT, "Size of jar classpath entry", type = ValueType.BYTES),
|
||||
JAR_CLASSPATH_ENTRY_SNAPSHOT_SIZE(parent = CLASSPATH_ENTRY_SNAPSHOT_TRANSFORM_EXECUTION_COUNT, "Size of jar classpath entry's snapshot", type = ValueType.BYTES),
|
||||
DIRECTORY_CLASSPATH_ENTRY_SNAPSHOT_SIZE(parent = CLASSPATH_ENTRY_SNAPSHOT_TRANSFORM_EXECUTION_COUNT, "Size of directory classpath entry's snapshot", type = ValueType.BYTES),
|
||||
COMPUTE_CLASSPATH_CHANGES_EXECUTION_COUNT(parent = null, "Number of times classpath changes are computed", type = ValueType.NUMBER),
|
||||
SHRINK_AND_SAVE_CLASSPATH_SNAPSHOT_EXECUTION_COUNT(parent = null, "Number of times classpath snapshot is shrunk and saved after compilation", type = ValueType.NUMBER),
|
||||
CLASSPATH_ENTRY_COUNT(parent = SHRINK_AND_SAVE_CLASSPATH_SNAPSHOT_EXECUTION_COUNT, "Number of classpath entries", type = ValueType.NUMBER),
|
||||
CLASSPATH_SNAPSHOT_SIZE(parent = SHRINK_AND_SAVE_CLASSPATH_SNAPSHOT_EXECUTION_COUNT, "Size of classpath snapshot", type = ValueType.BYTES),
|
||||
SHRUNK_CLASSPATH_SNAPSHOT_SIZE(parent = SHRINK_AND_SAVE_CLASSPATH_SNAPSHOT_EXECUTION_COUNT, "Size of shrunk classpath snapshot", type = ValueType.BYTES),
|
||||
LOAD_CLASSPATH_SNAPSHOT_EXECUTION_COUNT(parent = null, "Number of times classpath snapshot is loaded", type = ValueType.NUMBER),
|
||||
LOAD_CLASSPATH_ENTRY_SNAPSHOT_CACHE_HITS(parent = LOAD_CLASSPATH_SNAPSHOT_EXECUTION_COUNT, "Number of cache hits when loading classpath entry snapshots", type = ValueType.NUMBER),
|
||||
LOAD_CLASSPATH_ENTRY_SNAPSHOT_CACHE_MISSES(parent = LOAD_CLASSPATH_SNAPSHOT_EXECUTION_COUNT, "Number of cache misses when loading classpath entry snapshots", type = ValueType.NUMBER),
|
||||
|
||||
//time metrics
|
||||
START_TASK_ACTION_EXECUTION(readableString = "Start time of task action", type = ValueType.TIME),
|
||||
FINISH_KOTLIN_DAEMON_EXECUTION(readableString = "Finish time of kotlin daemon execution", type = ValueType.TIME),
|
||||
|
||||
CALL_KOTLIN_DAEMON(readableString = "Finish gradle part of task execution", type = ValueType.NANOSECONDS),
|
||||
CALL_WORKER(readableString = "Worker submit time", type = ValueType.NANOSECONDS),
|
||||
START_WORKER_EXECUTION(readableString = "Start time of worker execution", type = ValueType.NANOSECONDS),
|
||||
START_KOTLIN_DAEMON_EXECUTION(readableString = "Start time of kotlin daemon task execution", type = ValueType.NANOSECONDS),
|
||||
;
|
||||
companion object {
|
||||
const val serialVersionUID = 0L
|
||||
|
||||
val children by lazy {
|
||||
values().filter { it.parent != null }.groupBy { it.parent }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class ValueType {
|
||||
BYTES,
|
||||
NUMBER,
|
||||
NANOSECONDS,
|
||||
MILLISECONDS,
|
||||
TIME
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.metrics
|
||||
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
class BuildPerformanceMetrics : Serializable {
|
||||
companion object {
|
||||
const val serialVersionUID = 0L
|
||||
}
|
||||
|
||||
private val myBuildMetrics = EnumMap<BuildPerformanceMetric, Long>(BuildPerformanceMetric::class.java)
|
||||
|
||||
fun addAll(other: BuildPerformanceMetrics) {
|
||||
for ((bt, timeNs) in other.myBuildMetrics) {
|
||||
add(bt, timeNs)
|
||||
}
|
||||
}
|
||||
|
||||
fun add(metric: BuildPerformanceMetric, value: Long = 1) {
|
||||
myBuildMetrics[metric] = myBuildMetrics.getOrDefault(metric, 0) + value
|
||||
}
|
||||
|
||||
fun asMap(): Map<BuildPerformanceMetric, Long> = myBuildMetrics
|
||||
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.metrics
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
@Suppress("Reformat")
|
||||
enum class BuildTime(val parent: BuildTime? = null, val readableString: String) : Serializable {
|
||||
GRADLE_TASK(readableString = "Total Gradle task time"),
|
||||
GRADLE_TASK_PREPARATION(readableString = "Spent time before task action"),
|
||||
GRADLE_TASK_ACTION(readableString = "Task action"),
|
||||
OUT_OF_WORKER_TASK_ACTION(GRADLE_TASK_ACTION, "Task action before worker execution"),
|
||||
BACKUP_OUTPUT(OUT_OF_WORKER_TASK_ACTION, "Backup output"),
|
||||
RUN_WORKER_DELAY(readableString = "Start gradle worker"),
|
||||
RUN_COMPILATION_IN_WORKER(GRADLE_TASK_ACTION, "Run compilation in Gradle worker"),
|
||||
CLEAR_JAR_CACHE(RUN_COMPILATION_IN_WORKER, "Clear jar cache"),
|
||||
CLEAR_OUTPUT(RUN_COMPILATION_IN_WORKER, "Clear output"),
|
||||
PRECISE_BACKUP_OUTPUT(RUN_COMPILATION_IN_WORKER, "Precise backup output"),
|
||||
RESTORE_OUTPUT_FROM_BACKUP(RUN_COMPILATION_IN_WORKER, "Restore output"),
|
||||
CLEAN_BACKUP_STASH(RUN_COMPILATION_IN_WORKER, "Cleaning up the backup stash"),
|
||||
CONNECT_TO_DAEMON(RUN_COMPILATION_IN_WORKER, "Connect to Kotlin daemon"),
|
||||
CALCULATE_OUTPUT_SIZE(RUN_COMPILATION_IN_WORKER, "Calculate output size"),
|
||||
RUN_COMPILATION(RUN_COMPILATION_IN_WORKER, "Run compilation"),
|
||||
NON_INCREMENTAL_COMPILATION_IN_PROCESS(RUN_COMPILATION, "Non incremental inprocess compilation"),
|
||||
NON_INCREMENTAL_COMPILATION_OUT_OF_PROCESS(RUN_COMPILATION, "Non incremental out of process compilation"),
|
||||
NON_INCREMENTAL_COMPILATION_DAEMON(RUN_COMPILATION, "Non incremental compilation in daemon"),
|
||||
INCREMENTAL_COMPILATION_DAEMON(RUN_COMPILATION, "Incremental compilation in daemon"),
|
||||
STORE_BUILD_INFO(INCREMENTAL_COMPILATION_DAEMON, "Store build info"),
|
||||
JAR_SNAPSHOT(INCREMENTAL_COMPILATION_DAEMON, "ABI JAR Snapshot support"),
|
||||
SET_UP_ABI_SNAPSHOTS(JAR_SNAPSHOT, "Set up ABI snapshot"),
|
||||
IC_ANALYZE_JAR_FILES(JAR_SNAPSHOT, "Analyze jar files"),
|
||||
IC_CALCULATE_INITIAL_DIRTY_SET(INCREMENTAL_COMPILATION_DAEMON, "Calculate initial dirty sources set"), //TODO
|
||||
COMPUTE_CLASSPATH_CHANGES(IC_CALCULATE_INITIAL_DIRTY_SET, "Compute classpath changes"),
|
||||
LOAD_CURRENT_CLASSPATH_SNAPSHOT(COMPUTE_CLASSPATH_CHANGES, "Load current classpath snapshot"),
|
||||
REMOVE_DUPLICATE_CLASSES(LOAD_CURRENT_CLASSPATH_SNAPSHOT, "Remove duplicate classes"),
|
||||
SHRINK_CURRENT_CLASSPATH_SNAPSHOT(COMPUTE_CLASSPATH_CHANGES, "Shrink current classpath snapshot"),
|
||||
GET_LOOKUP_SYMBOLS(SHRINK_CURRENT_CLASSPATH_SNAPSHOT, "Get lookup symbols"),
|
||||
FIND_REFERENCED_CLASSES(SHRINK_CURRENT_CLASSPATH_SNAPSHOT, "Find referenced classes"),
|
||||
FIND_TRANSITIVELY_REFERENCED_CLASSES(SHRINK_CURRENT_CLASSPATH_SNAPSHOT, "Find transitively referenced classes"),
|
||||
LOAD_SHRUNK_PREVIOUS_CLASSPATH_SNAPSHOT(COMPUTE_CLASSPATH_CHANGES, "Load shrunk previous classpath snapshot"),
|
||||
COMPUTE_CHANGED_AND_IMPACTED_SET(COMPUTE_CLASSPATH_CHANGES, "Compute changed and impacted set"),
|
||||
COMPUTE_CLASS_CHANGES(COMPUTE_CHANGED_AND_IMPACTED_SET, "Compute class changes"),
|
||||
COMPUTE_KOTLIN_CLASS_CHANGES(COMPUTE_CLASS_CHANGES, "Compute Kotlin class changes"),
|
||||
COMPUTE_JAVA_CLASS_CHANGES(COMPUTE_CLASS_CHANGES, "Compute Java class changes"),
|
||||
COMPUTE_IMPACTED_SET(COMPUTE_CHANGED_AND_IMPACTED_SET, "Compute impacted set"),
|
||||
IC_ANALYZE_CHANGES_IN_DEPENDENCIES(IC_CALCULATE_INITIAL_DIRTY_SET, "Analyze dependency changes"),
|
||||
IC_FIND_HISTORY_FILES(IC_ANALYZE_CHANGES_IN_DEPENDENCIES, "Find history files"),
|
||||
IC_ANALYZE_HISTORY_FILES(IC_ANALYZE_CHANGES_IN_DEPENDENCIES, "Analyze history files"),
|
||||
IC_ANALYZE_CHANGES_IN_JAVA_SOURCES(IC_CALCULATE_INITIAL_DIRTY_SET, "Analyze Java file changes"),
|
||||
IC_ANALYZE_CHANGES_IN_ANDROID_LAYOUTS(IC_CALCULATE_INITIAL_DIRTY_SET, "Analyze Android layouts"),
|
||||
IC_DETECT_REMOVED_CLASSES(IC_CALCULATE_INITIAL_DIRTY_SET, "Detect removed classes"),
|
||||
CLEAR_OUTPUT_ON_REBUILD(INCREMENTAL_COMPILATION_DAEMON, "Clear outputs on rebuild"),
|
||||
IC_UPDATE_CACHES(INCREMENTAL_COMPILATION_DAEMON, "Update caches"),
|
||||
COMPILATION_ROUND(INCREMENTAL_COMPILATION_DAEMON, "Sources compilation round"),
|
||||
COMPILER_PERFORMANCE(COMPILATION_ROUND, readableString = "Compiler time"),
|
||||
COMPILER_INITIALIZATION(COMPILER_PERFORMANCE, "Compiler initialization time"),
|
||||
CODE_ANALYSIS(COMPILER_PERFORMANCE, "Compiler code analysis"),
|
||||
CODE_GENERATION(COMPILER_PERFORMANCE, "Compiler code generation"),
|
||||
IC_WRITE_HISTORY_FILE(INCREMENTAL_COMPILATION_DAEMON, "Write history file"),
|
||||
SHRINK_AND_SAVE_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION(INCREMENTAL_COMPILATION_DAEMON, "Shrink and save current classpath snapshot after compilation"),
|
||||
INCREMENTAL_SHRINK_CURRENT_CLASSPATH_SNAPSHOT(SHRINK_AND_SAVE_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION, "Shrink current classpath snapshot incrementally"),
|
||||
INCREMENTAL_LOAD_CURRENT_CLASSPATH_SNAPSHOT(INCREMENTAL_SHRINK_CURRENT_CLASSPATH_SNAPSHOT, "Load current classpath snapshot"),
|
||||
INCREMENTAL_LOAD_SHRUNK_CURRENT_CLASSPATH_SNAPSHOT_AGAINST_PREVIOUS_LOOKUPS(INCREMENTAL_SHRINK_CURRENT_CLASSPATH_SNAPSHOT, "Load shrunk current classpath snapshot against previous lookups"),
|
||||
NON_INCREMENTAL_SHRINK_CURRENT_CLASSPATH_SNAPSHOT(SHRINK_AND_SAVE_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION, "Shrink current classpath snapshot non-incrementally"),
|
||||
NON_INCREMENTAL_LOAD_CURRENT_CLASSPATH_SNAPSHOT(NON_INCREMENTAL_SHRINK_CURRENT_CLASSPATH_SNAPSHOT, "Load current classpath snapshot"),
|
||||
SAVE_SHRUNK_CURRENT_CLASSPATH_SNAPSHOT(SHRINK_AND_SAVE_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION, "Save shrunk current classpath snapshot"),
|
||||
TASK_FINISH_LISTENER_NOTIFICATION(readableString = "Task finish event notification"),
|
||||
CLASSPATH_ENTRY_SNAPSHOT_TRANSFORM(readableString = "Classpath entry snapshot transform"),
|
||||
LOAD_CLASSES(parent = CLASSPATH_ENTRY_SNAPSHOT_TRANSFORM, "Load classes"),
|
||||
SNAPSHOT_CLASSES(parent = CLASSPATH_ENTRY_SNAPSHOT_TRANSFORM, "Snapshot classes"),
|
||||
READ_CLASSES_BASIC_INFO(parent = SNAPSHOT_CLASSES, "Read basic information about classes"),
|
||||
FIND_INACCESSIBLE_CLASSES(parent = SNAPSHOT_CLASSES, "Find inaccessible classes"),
|
||||
SNAPSHOT_KOTLIN_CLASSES(parent = SNAPSHOT_CLASSES, "Snapshot Kotlin classes"),
|
||||
SNAPSHOT_JAVA_CLASSES(parent = SNAPSHOT_CLASSES, "Snapshot Java classes"),
|
||||
SAVE_CLASSPATH_ENTRY_SNAPSHOT(parent = CLASSPATH_ENTRY_SNAPSHOT_TRANSFORM, "Save classpath entry snapshot"),
|
||||
|
||||
;
|
||||
|
||||
companion object {
|
||||
const val serialVersionUID = 0L
|
||||
|
||||
val children by lazy {
|
||||
values().filter { it.parent != null }.groupBy { it.parent }
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.metrics
|
||||
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
class BuildTimes : Serializable {
|
||||
private val buildTimesNs = EnumMap<BuildTime, Long>(BuildTime::class.java)
|
||||
|
||||
fun addAll(other: BuildTimes) {
|
||||
for ((buildTime, timeNs) in other.buildTimesNs) {
|
||||
addTimeNs(buildTime, timeNs)
|
||||
}
|
||||
}
|
||||
|
||||
fun addTimeNs(buildTime: BuildTime, timeNs: Long) {
|
||||
buildTimesNs[buildTime] = buildTimesNs.getOrDefault(buildTime, 0) + timeNs
|
||||
}
|
||||
|
||||
fun addTimeMs(buildTime: BuildTime, timeMs: Long) = addTimeNs(buildTime, timeMs * 1_000_000)
|
||||
|
||||
fun asMapMs(): Map<BuildTime, Long> = buildTimesNs.mapValues { it.value / 1_000_000 }
|
||||
|
||||
companion object {
|
||||
const val serialVersionUID = 0L
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.metrics
|
||||
|
||||
object DoNothingBuildMetricsReporter : BuildMetricsReporter {
|
||||
override fun startMeasure(time: BuildTime) {
|
||||
}
|
||||
|
||||
override fun endMeasure(time: BuildTime) {
|
||||
}
|
||||
|
||||
override fun addTimeMetricNs(time: BuildTime, durationNs: Long) {
|
||||
}
|
||||
|
||||
override fun addMetric(metric: BuildPerformanceMetric, value: Long) {
|
||||
}
|
||||
|
||||
override fun addTimeMetric(metric: BuildPerformanceMetric) {
|
||||
}
|
||||
|
||||
override fun addAttribute(attribute: BuildAttribute) {
|
||||
}
|
||||
|
||||
override fun addGcMetric(metric: String, value: GcMetric) {
|
||||
}
|
||||
|
||||
override fun startGcMetric(name: String, value: GcMetric) {
|
||||
}
|
||||
|
||||
override fun endGcMetric(name: String, value: GcMetric) {
|
||||
}
|
||||
|
||||
override fun getMetrics(): BuildMetrics =
|
||||
BuildMetrics(
|
||||
BuildTimes(),
|
||||
BuildPerformanceMetrics(),
|
||||
BuildAttributes()
|
||||
)
|
||||
|
||||
override fun addMetrics(metrics: BuildMetrics) {}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.metrics
|
||||
|
||||
import java.io.Serializable
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
class GcMetrics : Serializable {
|
||||
|
||||
private val myGcMetrics = HashMap<String, GcMetric>()
|
||||
fun addAll(gcMetrics: GcMetrics) {
|
||||
gcMetrics.myGcMetrics.forEach { (key, value) ->
|
||||
val gcMetric = myGcMetrics[key]
|
||||
myGcMetrics[key] = gcMetric?.let { gcMetric + value } ?: value
|
||||
}
|
||||
}
|
||||
|
||||
fun add(metric: String, value: GcMetric) {
|
||||
myGcMetrics[metric] = value
|
||||
}
|
||||
|
||||
fun asGcCountMap(): Map<String, Long> = myGcMetrics.mapValues { it.value.count }
|
||||
fun asGcTimeMap(): Map<String, Long> = myGcMetrics.mapValues { it.value.time }
|
||||
fun asMap(): Map<String, GcMetric> = myGcMetrics
|
||||
|
||||
fun isEmpty() = myGcMetrics.isEmpty()
|
||||
}
|
||||
data class GcMetric(
|
||||
val time: Long,
|
||||
val count: Long
|
||||
): Serializable {
|
||||
operator fun minus(increment: GcMetric): GcMetric {
|
||||
return GcMetric(time - increment.time, count - increment.count)
|
||||
}
|
||||
|
||||
operator fun plus(increment: GcMetric?): GcMetric {
|
||||
return GcMetric(time + (increment?.time ?: 0), count + (increment?.count ?: 0))
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
|
||||
data class FileReportSettings(
|
||||
val buildReportDir: File,
|
||||
val includeMetricsInReport: Boolean = false,
|
||||
) : Serializable {
|
||||
companion object {
|
||||
const val serialVersionUID: Long = 0
|
||||
}
|
||||
}
|
||||
|
||||
data class HttpReportSettings(
|
||||
val url: String,
|
||||
val password: String?,
|
||||
val user: String?,
|
||||
val verboseEnvironment: Boolean,
|
||||
val includeGitBranchName: Boolean
|
||||
) : Serializable {
|
||||
companion object {
|
||||
const val serialVersionUID: Long = 0
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
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 java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
//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")}
|
||||
data class CompileStatisticsData(
|
||||
val version: Int = 3,
|
||||
val projectName: String?,
|
||||
val label: String?,
|
||||
val taskName: String,
|
||||
val taskResult: String?,
|
||||
val startTimeMs: Long,
|
||||
val durationMs: Long,
|
||||
val tags: Set<StatTag>,
|
||||
val changes: List<String>,
|
||||
val buildUuid: String = "Unset",
|
||||
val kotlinVersion: String,
|
||||
val kotlinLanguageVersion: String?,
|
||||
val hostName: String? = "Unset",
|
||||
val finishTime: Long,
|
||||
val timestamp: String = formatter.format(finishTime),
|
||||
val compilerArguments: List<String>,
|
||||
val nonIncrementalAttributes: Set<BuildAttribute>,
|
||||
//TODO think about it,time in milliseconds
|
||||
val buildTimesMetrics: Map<BuildTime, Long>,
|
||||
val performanceMetrics: Map<BuildPerformanceMetric, Long>,
|
||||
val gcTimeMetrics: Map<String, Long>?,
|
||||
val gcCountMetrics: Map<String, Long>?,
|
||||
val type: String = BuildDataType.TASK_DATA.name,
|
||||
val fromKotlinPlugin: Boolean?,
|
||||
val compiledSources: List<String> = emptyList(),
|
||||
val skipMessage: String?,
|
||||
val icLogLines: List<String>,
|
||||
)
|
||||
|
||||
|
||||
enum class StatTag(val readableString: String) {
|
||||
ABI_SNAPSHOT("ABI Snapshot"),
|
||||
ARTIFACT_TRANSFORM("Classpath Snapshot"),
|
||||
INCREMENTAL("Incremental compilation"),
|
||||
NON_INCREMENTAL("Non incremental compilation"),
|
||||
INCREMENTAL_AND_NON_INCREMENTAL("Incremental and Non incremental compilation"),
|
||||
GRADLE_DEBUG("Gradle debug enabled"),
|
||||
KOTLIN_DEBUG("Kotlin debug enabled"),
|
||||
CONFIGURATION_CACHE("Configuration cache enabled"),
|
||||
BUILD_CACHE("Build cache enabled"),
|
||||
KOTLIN_1("Kotlin language version 1"),
|
||||
KOTLIN_2("Kotlin language version 2"),
|
||||
KOTLIN_1_AND_2("Kotlin language version 1 and 2"),
|
||||
}
|
||||
|
||||
enum class BuildDataType {
|
||||
TASK_DATA,
|
||||
BUILD_DATA,
|
||||
JPS_DATA
|
||||
}
|
||||
|
||||
//Sensitive data. This object is used directly for statistic via http
|
||||
data class BuildStartParameters(
|
||||
val tasks: List<String>,
|
||||
val excludedTasks: Set<String> = emptySet(),
|
||||
val currentDir: String? = null,
|
||||
val projectProperties: List<String> = emptyList(),
|
||||
val systemProperties: List<String> = emptyList(),
|
||||
) : java.io.Serializable
|
||||
|
||||
//Sensitive data. This object is used directly for statistic via http
|
||||
data class BuildFinishStatisticsData(
|
||||
val projectName: String,
|
||||
val startParameters: BuildStartParameters,
|
||||
val buildUuid: String = "Unset",
|
||||
val label: String?,
|
||||
val totalTime: Long,
|
||||
val type: String = BuildDataType.BUILD_DATA.name,
|
||||
val finishTime: Long,
|
||||
val timestamp: String = formatter.format(finishTime),
|
||||
val hostName: String? = "Unset",
|
||||
val tags: Set<StatTag>,
|
||||
val gitBranch: String = "Unset"
|
||||
)
|
||||
|
||||
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.google.gson.Gson
|
||||
import org.jetbrains.kotlin.compilerRunner.KotlinLogger
|
||||
import java.io.IOException
|
||||
import java.io.Serializable
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
class HttpReportService(
|
||||
private val url: String,
|
||||
private val password: String?,
|
||||
private val user: String?,
|
||||
) : Serializable {
|
||||
|
||||
private var invalidUrl = false
|
||||
private var requestPreviousFailed = false
|
||||
|
||||
private fun checkResponseAndLog(connection: HttpURLConnection, log: KotlinLogger) {
|
||||
val isResponseBad = connection.responseCode !in 200..299
|
||||
if (isResponseBad) {
|
||||
val message = "Failed to send statistic to ${connection.url} with ${connection.responseCode}: ${connection.responseMessage}"
|
||||
if (!requestPreviousFailed) {
|
||||
log.warn(message)
|
||||
} else {
|
||||
log.debug(message)
|
||||
}
|
||||
requestPreviousFailed = true
|
||||
}
|
||||
}
|
||||
|
||||
fun sendData(data: Any, log: KotlinLogger) {
|
||||
val elapsedTime = measureTimeMillis {
|
||||
if (invalidUrl) {
|
||||
return
|
||||
}
|
||||
val connection = try {
|
||||
URL(url).openConnection() as HttpURLConnection
|
||||
} catch (e: IOException) {
|
||||
log.warn("Unable to open connection to ${url}: ${e.message}")
|
||||
invalidUrl = true
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (user != null && password != null) {
|
||||
val auth = Base64.getEncoder()
|
||||
.encode("${user}:${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, log)
|
||||
} catch (e: Exception) {
|
||||
log.warn("Unexpected exception happened ${e.message}: ${e.stackTrace}")
|
||||
checkResponseAndLog(connection, log)
|
||||
} finally {
|
||||
connection.disconnect()
|
||||
}
|
||||
}
|
||||
log.debug("Report statistic by http takes $elapsedTime ms")
|
||||
}
|
||||
}
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* 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.compilerRunner.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<CompileStatisticsData>,
|
||||
startParameters: BuildStartParameters,
|
||||
failureMessages: List<String>,
|
||||
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<CompileStatisticsData>,
|
||||
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>,
|
||||
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) {
|
||||
printMetrics(
|
||||
statisticsData.map { it.buildTimesMetrics }.reduce { agg, value ->
|
||||
(agg.keys + value.keys).associateWith { (agg[it] ?: 0) + (value[it] ?: 0) }
|
||||
},
|
||||
statisticsData.map { it.performanceMetrics }.reduce { agg, value ->
|
||||
(agg.keys + value.keys).associateWith { (agg[it] ?: 0) + (value[it] ?: 0) }
|
||||
},
|
||||
statisticsData.map { it.nonIncrementalAttributes.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<BuildTime, Long>,
|
||||
performanceMetrics: Map<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<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.readableString}: ${formatTime(timeMs)}")
|
||||
p.withIndent {
|
||||
BuildTime.children[buildTime]?.forEach { printBuildTime(it) }
|
||||
}
|
||||
} else {
|
||||
//Skip formatting if parent metric does not set
|
||||
BuildTime.children[buildTime]?.forEach { printBuildTime(it) }
|
||||
}
|
||||
}
|
||||
|
||||
for (buildTime in BuildTime.values()) {
|
||||
if (buildTime.parent != null) continue
|
||||
|
||||
printBuildTime(buildTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun printBuildPerformanceMetrics(buildMetrics: Map<BuildPerformanceMetric, Long>) {
|
||||
if (buildMetrics.isEmpty()) return
|
||||
|
||||
p.withIndent("Size metrics:") {
|
||||
for (metric in BuildPerformanceMetric.values()) {
|
||||
buildMetrics[metric]?.let { printSizeMetric(metric, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun printSizeMetric(sizeMetric: BuildPerformanceMetric, value: Long) {
|
||||
fun BuildPerformanceMetric.numberOfAncestors(): Int {
|
||||
var count = 0
|
||||
var parent: BuildPerformanceMetric? = parent
|
||||
while (parent != null) {
|
||||
count++
|
||||
parent = parent.parent
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
val indentLevel = sizeMetric.numberOfAncestors()
|
||||
|
||||
repeat(indentLevel) { p.pushIndent() }
|
||||
when (sizeMetric.type) {
|
||||
ValueType.BYTES -> p.println("${sizeMetric.readableString}: ${formatSize(value)}")
|
||||
ValueType.NUMBER -> p.println("${sizeMetric.readableString}: $value")
|
||||
ValueType.NANOSECONDS -> p.println("${sizeMetric.readableString}: $value")
|
||||
ValueType.MILLISECONDS -> p.println("${sizeMetric.readableString}: ${formatTime(value)}")
|
||||
ValueType.TIME -> p.println("${sizeMetric.readableString}: ${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>) {
|
||||
var allTasksTimeMs = 0L
|
||||
var kotlinTotalTimeMs = 0L
|
||||
val kotlinTasks = ArrayList<CompileStatisticsData>()
|
||||
|
||||
for (task in statisticsData) {
|
||||
val taskTimeMs = task.durationMs
|
||||
allTasksTimeMs += taskTimeMs
|
||||
|
||||
if (task.fromKotlinPlugin == 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.durationMs }, { it.startTimeMs }))) {
|
||||
val timeMs = task.durationMs
|
||||
val percent = (timeMs.toDouble() / kotlinTotalTimeMs * 100).asString(1)
|
||||
table.addRow(formatTime(timeMs), "$percent %", task.taskName)
|
||||
}
|
||||
table.printTo(p)
|
||||
p.println()
|
||||
}
|
||||
|
||||
private fun printTasksLog(statisticsData: List<CompileStatisticsData>) {
|
||||
for (task in statisticsData.sortedWith(compareBy({ -it.durationMs }, { it.startTimeMs }))) {
|
||||
printTaskLog(task)
|
||||
p.println()
|
||||
}
|
||||
}
|
||||
|
||||
private fun printTaskLog(statisticsData: CompileStatisticsData) {
|
||||
val skipMessage = statisticsData.skipMessage
|
||||
if (skipMessage != null) {
|
||||
p.println("Task '${statisticsData.taskName}' was skipped: $skipMessage")
|
||||
} else {
|
||||
p.println("Task '${statisticsData.taskName}' finished in ${formatTime(statisticsData.durationMs)}")
|
||||
}
|
||||
|
||||
if (statisticsData.icLogLines.isNotEmpty()) {
|
||||
p.withIndent("Compilation log for task '${statisticsData.taskName}':") {
|
||||
statisticsData.icLogLines.forEach { p.println(it) }
|
||||
}
|
||||
}
|
||||
|
||||
if (printMetrics) {
|
||||
printMetrics(statisticsData.buildTimesMetrics, statisticsData.performanceMetrics, statisticsData.nonIncrementalAttributes,
|
||||
statisticsData.gcTimeMetrics, statisticsData.gcCountMetrics)
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 java.util.*
|
||||
import kotlin.math.max
|
||||
|
||||
internal fun printMap(p: Printer, name: String, mapping: Map<String, Int>) {
|
||||
if (mapping.isEmpty()) return
|
||||
|
||||
if (mapping.size == 1) {
|
||||
p.println("$name: ${mapping.keys.single()}")
|
||||
return
|
||||
}
|
||||
|
||||
p.withIndent("$name:") {
|
||||
val sortedEnumMap = mapping.toSortedMap()
|
||||
for ((k, v) in sortedEnumMap) {
|
||||
p.println("$k($v)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class TextTable(vararg columnNames: String) {
|
||||
private val rows = ArrayList<List<String>>()
|
||||
private val columnsCount = columnNames.size
|
||||
private val maxLengths = IntArray(columnsCount) { columnNames[it].length }
|
||||
|
||||
init {
|
||||
rows.add(columnNames.toList())
|
||||
}
|
||||
|
||||
fun addRow(vararg row: String) {
|
||||
check(row.size == columnsCount) { "Row size ${row.size} differs from columns count $columnsCount" }
|
||||
rows.add(row.toList())
|
||||
|
||||
for ((i, col) in row.withIndex()) {
|
||||
maxLengths[i] = max(maxLengths[i], col.length)
|
||||
}
|
||||
}
|
||||
|
||||
fun printTo(p: Printer) {
|
||||
for (row in rows) {
|
||||
val rowStr = row.withIndex().joinToString("|") { (i, col) -> col.padEnd(maxLengths[i], ' ') }
|
||||
p.println(rowStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 java.io.IOException
|
||||
|
||||
private val LINE_SEPARATOR = System.getProperty("line.separator")
|
||||
|
||||
internal class Printer(
|
||||
private val out: Appendable,
|
||||
private val indentUnit: String = " ",
|
||||
private var indent: String = ""
|
||||
) {
|
||||
private fun append(s: String) {
|
||||
try {
|
||||
out.append(s)
|
||||
} catch (e: IOException) { // Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
fun println(vararg strings: String) {
|
||||
this.print(*strings)
|
||||
printLineSeparator()
|
||||
}
|
||||
|
||||
private fun printLineSeparator() {
|
||||
append(LINE_SEPARATOR)
|
||||
}
|
||||
|
||||
fun print(vararg strings: String) {
|
||||
if (strings.isNotEmpty()) {
|
||||
this.printIndent()
|
||||
}
|
||||
this.printWithNoIndent(*strings)
|
||||
}
|
||||
|
||||
private fun printIndent() {
|
||||
append(indent)
|
||||
}
|
||||
|
||||
private fun printWithNoIndent(vararg strings: String) {
|
||||
for (s in strings) {
|
||||
append(s)
|
||||
}
|
||||
}
|
||||
|
||||
fun pushIndent() {
|
||||
indent += indentUnit
|
||||
}
|
||||
|
||||
fun popIndent() {
|
||||
check(indent.length >= indentUnit.length) { "No indentation to pop" }
|
||||
indent = indent.substring(indentUnit.length)
|
||||
}
|
||||
|
||||
inline fun <T> withIndent(headLine: String? = null, fn: () -> T): T {
|
||||
if (headLine != null) {
|
||||
this.println(headLine)
|
||||
}
|
||||
pushIndent()
|
||||
|
||||
return try {
|
||||
fn()
|
||||
} finally {
|
||||
popIndent()
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
internal fun formatTime(ms: Long): String {
|
||||
val seconds = ms.toDouble() / 1_000
|
||||
return seconds.asString(2) + " s"
|
||||
}
|
||||
|
||||
private const val kbSize = 1024
|
||||
private const val mbSize = kbSize * 1024
|
||||
private const val gbSize = mbSize * 1024
|
||||
|
||||
fun formatSize(sizeInBytes: Long): String = when {
|
||||
sizeInBytes / gbSize >= 1 -> "${(sizeInBytes.toDouble() / gbSize).asString(1)} GB"
|
||||
sizeInBytes / mbSize >= 1 -> "${(sizeInBytes.toDouble() / mbSize).asString(1)} MB"
|
||||
sizeInBytes / kbSize >= 1 -> "${(sizeInBytes.toDouble() / kbSize).asString(1)} KB"
|
||||
else -> "$sizeInBytes B"
|
||||
}
|
||||
|
||||
internal fun Double.asString(decPoints: Int): String = "%,.${decPoints}f".format(this)
|
||||
@@ -26,12 +26,7 @@ import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.isSubpackageOf
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.util.Logger
|
||||
import org.jetbrains.kotlin.utils.KotlinPaths
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
fun incrementalCompilationIsEnabled(arguments: CommonCompilerArguments): Boolean {
|
||||
@@ -87,7 +82,7 @@ fun <PathProvider : Any> getLibraryFromHome(
|
||||
|
||||
fun MessageCollector.toLogger(): Logger =
|
||||
object : Logger {
|
||||
override fun error(message: String, throwable: Throwable?) {
|
||||
override fun error(message: String) {
|
||||
report(CompilerMessageSeverity.ERROR, message)
|
||||
}
|
||||
|
||||
@@ -103,9 +98,5 @@ fun MessageCollector.toLogger(): Logger =
|
||||
override fun log(message: String) {
|
||||
report(CompilerMessageSeverity.LOGGING, message)
|
||||
}
|
||||
|
||||
override fun lifecycle(message: String) {
|
||||
report(CompilerMessageSeverity.INFO, message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ dependencies {
|
||||
api(project(":compiler:backend.jvm.entrypoint"))
|
||||
api(project(":kotlin-build-common"))
|
||||
api(project(":daemon-common"))
|
||||
api(project(":kotlin-build-statistic"))
|
||||
api(project(":compiler:build-tools:kotlin-build-statistics"))
|
||||
compileOnly(intellijCore())
|
||||
|
||||
testApi(commonDependency("junit:junit"))
|
||||
@@ -29,7 +29,6 @@ dependencies {
|
||||
testApi(intellijCore())
|
||||
testApi(commonDependency("org.jetbrains.intellij.deps:log4j"))
|
||||
testApi(commonDependency("org.jetbrains.intellij.deps:jdom"))
|
||||
testApi(projectTests(":kotlin-build-statistic"))
|
||||
|
||||
testImplementation(commonDependency("com.google.code.gson:gson"))
|
||||
testRuntimeOnly(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false }
|
||||
|
||||
@@ -106,9 +106,8 @@ val CompilerConfiguration.resolverLogger: Logger
|
||||
null -> DummyLogger
|
||||
else -> object : Logger {
|
||||
override fun log(message: String) = messageLogger.report(IrMessageLogger.Severity.INFO, message, null)
|
||||
override fun error(message: String, throwable: Throwable?) = messageLogger.report(IrMessageLogger.Severity.ERROR, message, null)
|
||||
override fun error(message: String) = messageLogger.report(IrMessageLogger.Severity.ERROR, message, null)
|
||||
override fun warning(message: String) = messageLogger.report(IrMessageLogger.Severity.WARNING, message, null)
|
||||
override fun lifecycle(message: String) = messageLogger.report(IrMessageLogger.Severity.INFO, message, null)
|
||||
|
||||
override fun fatal(message: String): Nothing {
|
||||
messageLogger.report(IrMessageLogger.Severity.ERROR, message, null)
|
||||
|
||||
@@ -175,9 +175,8 @@ object GenerationUtils {
|
||||
|
||||
fun messageCollectorLogger(collector: MessageCollector) = object : Logger {
|
||||
override fun warning(message: String) = collector.report(CompilerMessageSeverity.STRONG_WARNING, message)
|
||||
override fun error(message: String, throwable: Throwable?) = collector.report(CompilerMessageSeverity.ERROR, message)
|
||||
override fun error(message: String) = collector.report(CompilerMessageSeverity.ERROR, message)
|
||||
override fun log(message: String) = collector.report(CompilerMessageSeverity.LOGGING, message)
|
||||
override fun lifecycle(message: String) = collector.report(CompilerMessageSeverity.INFO, message)
|
||||
override fun fatal(message: String): Nothing {
|
||||
collector.report(CompilerMessageSeverity.ERROR, message)
|
||||
(collector as? GroupingMessageCollector)?.flush()
|
||||
|
||||
@@ -4,10 +4,9 @@ import kotlin.system.exitProcess
|
||||
|
||||
interface Logger {
|
||||
fun log(message: String)
|
||||
fun error(message: String, throwable: Throwable? = null)
|
||||
fun error(message: String)
|
||||
fun warning(message: String)
|
||||
fun fatal(message: String): Nothing
|
||||
fun lifecycle(message: String)
|
||||
}
|
||||
|
||||
interface WithLogger {
|
||||
@@ -16,18 +15,10 @@ interface WithLogger {
|
||||
|
||||
object DummyLogger : Logger {
|
||||
override fun log(message: String) = println(message)
|
||||
override fun error(message: String, throwable: Throwable?) {
|
||||
println("e: $message")
|
||||
throwable?.also {
|
||||
println("${it.message}:\n${it.stackTraceToString()} ")
|
||||
}
|
||||
}
|
||||
|
||||
override fun error(message: String) = println("e: $message")
|
||||
override fun warning(message: String) = println("w: $message")
|
||||
override fun fatal(message: String): Nothing {
|
||||
println("e: $message")
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
override fun lifecycle(message: String) = println("i: $message")
|
||||
}
|
||||
|
||||
@@ -16,10 +16,9 @@ fun resolveSingleFileKlib(
|
||||
libraryFile: File,
|
||||
logger: Logger = object : Logger {
|
||||
override fun log(message: String) {}
|
||||
override fun error(message: String, throwable: Throwable?) = kotlin.error("e: $message")
|
||||
override fun error(message: String) = kotlin.error("e: $message")
|
||||
override fun warning(message: String) {}
|
||||
override fun fatal(message: String) = kotlin.error("e: $message")
|
||||
override fun lifecycle(message: String) {}
|
||||
},
|
||||
strategy: SingleFileKlibResolveStrategy = CompilerSingleFileKlibResolveStrategy
|
||||
): KotlinLibrary = strategy.resolve(libraryFile, logger)
|
||||
|
||||
+4
-3
@@ -1,14 +1,15 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.compilerRunner
|
||||
|
||||
interface KotlinLogger {
|
||||
fun error(msg: String)
|
||||
val isDebugEnabled: Boolean
|
||||
fun error(msg: String, throwable: Throwable? = null)
|
||||
fun warn(msg: String)
|
||||
fun info(msg: String)
|
||||
fun debug(msg: String)
|
||||
val isDebugEnabled: Boolean
|
||||
fun lifecycle(msg: String)
|
||||
}
|
||||
Reference in New Issue
Block a user