diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/Parameters.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/Parameters.kt index eb8aecd6a40..b33666ee1de 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/Parameters.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/Parameters.kt @@ -5,6 +5,8 @@ package org.jetbrains.kotlin.descriptors.commonizer +import org.jetbrains.kotlin.descriptors.commonizer.stats.StatsCollector + class Parameters( val statsCollector: StatsCollector? = null ) { diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/builder/context.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/builder/context.kt index 560ae09d780..033cc5db6ce 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/builder/context.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/builder/context.kt @@ -7,11 +7,11 @@ package org.jetbrains.kotlin.descriptors.commonizer.builder import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.descriptors.commonizer.StatsCollector import org.jetbrains.kotlin.descriptors.commonizer.Target import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.CirRootNode import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.dimension import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.indexOfCommon +import org.jetbrains.kotlin.descriptors.commonizer.stats.StatsCollector import org.jetbrains.kotlin.descriptors.commonizer.utils.CommonizedGroup import org.jetbrains.kotlin.descriptors.commonizer.utils.CommonizedGroupMap import org.jetbrains.kotlin.descriptors.commonizer.utils.createKotlinNativeForwardDeclarationsModule diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/BooleanOptionType.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/BooleanOptionType.kt index 9d3297a3dcb..8f9ee970260 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/BooleanOptionType.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/BooleanOptionType.kt @@ -10,18 +10,20 @@ internal class BooleanOptionType( description: String, mandatory: Boolean ) : OptionType(alias, description, mandatory) { - private val trueTokens = setOf("1", "on", "yes", "true") - private val falseTokens = setOf("0", "off", "no", "false") - override fun parse(rawValue: String, onError: (reason: String) -> Nothing): Option { val value = rawValue.toLowerCase().let { when (it) { - in trueTokens -> true - in falseTokens -> false + in TRUE_TOKENS -> true + in FALSE_TOKENS -> false else -> onError("Invalid boolean value: $it") } } return Option(this, value) } + + companion object { + private val TRUE_TOKENS = setOf("1", "on", "yes", "true") + private val FALSE_TOKENS = setOf("0", "off", "no", "false") + } } diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/StatsTypeOptionType.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/StatsTypeOptionType.kt new file mode 100644 index 00000000000..be731ff2d18 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/StatsTypeOptionType.kt @@ -0,0 +1,24 @@ +/* + * 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.descriptors.commonizer.cli + +import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeDistributionCommonizer.StatsType + +internal object StatsTypeOptionType : OptionType("log-stats", DESCRIPTION, mandatory = false) { + override fun parse(rawValue: String, onError: (reason: String) -> Nothing): Option { + val value = StatsType.values().firstOrNull { it.name.equals(rawValue, ignoreCase = true) } + ?: onError("Invalid stats type: $rawValue") + return Option(this, value) + } +} + +private val DESCRIPTION = buildString { + StatsType.values().joinTo(this) { + val item = "\"${it.name.toLowerCase()}\"" + if (it == StatsType.NONE) "$item (default)" else item + } + append(";\nlog commonization stats") +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/TaskType.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/TaskType.kt index 3fec1d4637f..c2030f04cb3 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/TaskType.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/TaskType.kt @@ -28,7 +28,7 @@ internal enum class TaskType( "Boolean (default false);\nwhether to copy Kotlin/Native endorsed libraries to the destination", mandatory = false ), - BooleanOptionType("log-stats", "Boolean (default false); Log commonization stats", mandatory = false) + StatsTypeOptionType ), ::NativeDistributionCommonize ), diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/nativeTasks.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/nativeTasks.kt index c68077344a6..34036641320 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/nativeTasks.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/cli/nativeTasks.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.descriptors.commonizer.cli import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeDistributionCommonizer +import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeDistributionCommonizer.StatsType import org.jetbrains.kotlin.konan.library.KONAN_DISTRIBUTION_KLIB_DIR import org.jetbrains.kotlin.konan.library.KONAN_DISTRIBUTION_PLATFORM_LIBS_DIR import org.jetbrains.kotlin.konan.target.KonanTarget @@ -44,7 +45,7 @@ internal class NativeDistributionCommonize(options: Collection>) : Tas val copyStdlib = getOptional { it == "copy-stdlib" } ?: false val copyEndorsedLibs = getOptional { it == "copy-endorsed-libs" } ?: false - val withStats = getOptional { it == "log-stats" } ?: false + val statsType = getOptional { it == "log-stats" } ?: StatsType.NONE val descriptionSuffix = estimateLibrariesCount(distribution, targets)?.let { " ($it items)" } ?: "" val description = "${logPrefix}Preparing commonized Kotlin/Native libraries for targets $targets$descriptionSuffix" @@ -57,7 +58,7 @@ internal class NativeDistributionCommonize(options: Collection>) : Tas destination = destination, copyStdlib = copyStdlib, copyEndorsedLibs = copyEndorsedLibs, - withStats = withStats, + statsType = statsType, logger = CliLoggerAdapter(2) ).run() diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionCommonizer.kt index 7715b5bfae9..4fa0213ecc9 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeDistributionCommonizer.kt @@ -13,6 +13,10 @@ import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.commonizer.* import org.jetbrains.kotlin.descriptors.commonizer.Target +import org.jetbrains.kotlin.descriptors.commonizer.konan.NativeDistributionCommonizer.StatsType.* +import org.jetbrains.kotlin.descriptors.commonizer.stats.AggregatedStatsCollector +import org.jetbrains.kotlin.descriptors.commonizer.stats.FileStatsOutput +import org.jetbrains.kotlin.descriptors.commonizer.stats.RawStatsCollector import org.jetbrains.kotlin.descriptors.commonizer.utils.ResettableClockMark import org.jetbrains.kotlin.descriptors.konan.NATIVE_STDLIB_MODULE_NAME import org.jetbrains.kotlin.konan.library.* @@ -37,9 +41,13 @@ class NativeDistributionCommonizer( private val destination: File, private val copyStdlib: Boolean, private val copyEndorsedLibs: Boolean, - private val withStats: Boolean, + private val statsType: StatsType, private val logger: Logger ) { + enum class StatsType { + RAW, AGGREGATED, NONE + } + fun run() { checkPreconditions() @@ -123,7 +131,11 @@ class NativeDistributionCommonizer( } private fun commonize(librariesByTargets: Map): Result { - val statsCollector = if (withStats) NativeStatsCollector(targets, destination) else null + val statsCollector = when (statsType) { + RAW -> RawStatsCollector(targets, FileStatsOutput(destination, "raw")) + AGGREGATED -> AggregatedStatsCollector(targets, FileStatsOutput(destination, "aggregated")) + NONE -> null + } statsCollector.use { val parameters = Parameters(statsCollector).apply { librariesByTargets.forEach { (target, libraries) -> diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeStatsCollector.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeStatsCollector.kt deleted file mode 100644 index 64d632635c5..00000000000 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/konan/NativeStatsCollector.kt +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2010-2019 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.descriptors.commonizer.konan - -import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.descriptors.ClassKind.ENUM_CLASS -import org.jetbrains.kotlin.descriptors.ClassKind.ENUM_ENTRY -import org.jetbrains.kotlin.descriptors.commonizer.StatsCollector -import org.jetbrains.kotlin.descriptors.commonizer.utils.firstNonNull -import org.jetbrains.kotlin.descriptors.commonizer.utils.fqNameWithTypeParameters -import org.jetbrains.kotlin.konan.target.KonanTarget -import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe -import java.io.File - -/** - * Allows printing commonization statistics to the file system. - * - * File format: text, "|"-separated columns. - * - * Header row: "FQ Name|Extension Receiver|Parameter Names|Parameter Types|Declaration Type|common||[|...]" - * - * Possible values for "Declaration Type": - * - MODULE - * - CLASS - * - INTERFACE - * - OBJECT - * - COMPANION_OBJECT - * - ENUM_CLASS - * - ENUM_ENTRY - * - TYPE_ALIAS - * - CLASS_CONSTRUCTOR - * - FUN - * - VAL - * - * Possible values for "common" column: - * - L = declaration lifted up to common fragment - * - E = successfully commonized, expect declaration generated - * - "-" = no common declaration - * - * Possible values for each target platform column: - * - A = successfully commonized, actual declaration generated - * - O = not commonized, the declaration is as in the original library - * - "-" = no such declaration in the original library (or declaration has been lifted up) - * - * Example of output: - -FQ Name|Extension Receiver|Parameter Names|Parameter Types|Declaration Type|common|macos_x64|ios_x64 -||||MODULE|E|A|A -platform.SystemConfiguration.SCPreferencesContext||||CLASS|E|A|A -platform.SystemConfiguration.SCPreferencesContext.Companion||||COMPANION_OBJECT|E|A|A -platform.SystemConfiguration.SCNetworkConnectionContext||||CLASS|E|A|A -platform.SystemConfiguration.SCNetworkConnectionContext.Companion||||COMPANION_OBJECT|E|A|A -platform.SystemConfiguration.SCDynamicStoreRefVar||||TYPE_ALIAS|-|O|O -platform.SystemConfiguration.SCVLANInterfaceRef||||TYPE_ALIAS|-|O|O - - */ -class NativeStatsCollector( - private val targets: List, - destination: File -) : StatsCollector { - - init { - destination.mkdirs() - } - - private val writer = destination.resolve("plain_stats.csv").printWriter() - private var headerWritten = false - - override fun logStats(output: List) { - if (!headerWritten) { - headerWritten = true - writeHeader() - } - - val firstNotNull = output.firstNonNull() - val lastIsNull = output.last() == null - - val row = buildString { - appendName(firstNotNull) - append(SEPARATOR) - append(firstNotNull.declarationType) // readable declaration type - append(SEPARATOR) - - var isLiftedUp = !lastIsNull - val platformItems = StringBuilder().apply { - for (index in 0 until output.size - 1) { - append(SEPARATOR) - append( - when { - output[index] == null -> '-' // absent - lastIsNull -> 'O' // original (not commonized) - else -> { - isLiftedUp = false - 'A' // actual (commonized) - } - } - ) - } - } - - append( - when { - isLiftedUp -> 'L' - lastIsNull -> '-' - else -> 'E' - } - ) // common - - append(platformItems) - } - - writer.println(row) - } - - override fun close() = writer.close() - - private fun writeHeader() { - val row = buildString { - append("FQ Name") - append(SEPARATOR) - append("Extension Receiver") - append(SEPARATOR) - append("Parameter Names") - append(SEPARATOR) - append("Parameter Types") - append(SEPARATOR) - append("Declaration Type") - append(SEPARATOR) - append("common") - - targets.forEach { target -> - append(SEPARATOR) - append(target.name) - } - } - - writer.println(row) - } - - companion object { - private const val SEPARATOR = '|' - - @Suppress("NOTHING_TO_INLINE") - private inline fun StringBuilder.appendName(descriptor: DeclarationDescriptor) { - // name - append(if (descriptor is ModuleDescriptor) descriptor.name.asString() else descriptor.fqNameSafe.asString()) - append(SEPARATOR) - - // extension receiver - if (descriptor is PropertyDescriptor || descriptor is SimpleFunctionDescriptor) { - append((descriptor as CallableDescriptor).extensionReceiverParameter?.type?.fqNameWithTypeParameters.orEmpty()) - } - append(SEPARATOR) - - if (descriptor is ConstructorDescriptor || descriptor is SimpleFunctionDescriptor) { - // parameter names - (descriptor as FunctionDescriptor).valueParameters.joinTo(this) { it.name.asString() } - append(SEPARATOR) - // parameter types - descriptor.valueParameters.joinTo(this) { it.type.fqNameWithTypeParameters } - } else { - append(SEPARATOR) - } - } - - private inline val DeclarationDescriptor.topLevelnessPrefix: String - get() = if (DescriptorUtils.isTopLevelDeclaration(this)) "TOP-LEVEL " else "NESTED " - - private inline val DeclarationDescriptor.declarationType: String - get() = when (this) { - is ClassDescriptor -> when { - isCompanionObject -> "COMPANION_OBJECT" - kind == ENUM_CLASS || kind == ENUM_ENTRY -> kind.toString() - else -> topLevelnessPrefix + kind.toString() - } - is TypeAliasDescriptor -> "TYPE_ALIAS" - is ClassConstructorDescriptor -> "CLASS_CONSTRUCTOR" - is FunctionDescriptor -> topLevelnessPrefix + "FUN" - is PropertyDescriptor -> topLevelnessPrefix + if (isConst) "CONST-VAL" else "VAL" - is ModuleDescriptor -> "MODULE" - else -> "UNKNOWN: ${this::class.java}" - } - } -} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/AggregatedStatsCollector.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/AggregatedStatsCollector.kt new file mode 100644 index 00000000000..d2cd2487302 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/AggregatedStatsCollector.kt @@ -0,0 +1,110 @@ +/* + * 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.descriptors.commonizer.stats + +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.commonizer.stats.AggregatedStatsCollector.AggregatedStatsRow +import org.jetbrains.kotlin.descriptors.commonizer.stats.RawStatsCollector.CommonDeclarationStatus.* +import org.jetbrains.kotlin.konan.target.KonanTarget + +class AggregatedStatsCollector( + targets: List, + private val output: StatsOutput +) : StatsCollector { + private val aggregatingOutput = AggregatingOutput() + private val wrappedCollector = RawStatsCollector(targets, aggregatingOutput) + + override fun logStats(result: List) { + wrappedCollector.logStats(result) + } + + override fun close() { + output.writeHeader(AggregatedStatsHeader) + + aggregatingOutput.aggregatedStats.keys.sortedBy { it }.forEach { key -> + val row = aggregatingOutput.aggregatedStats.getValue(key) + output.writeRow(row) + } + + output.close() + wrappedCollector.close() + } + + object AggregatedStatsHeader : StatsOutput.StatsHeader { + private val headerItems = listOf( + "Declaration Type", + "Lifted Up", + "Lifted Up, %%", + "Commonized", + "Commonized, %%", + "Missed in s. targets", + "Missed in s. targets, %%", + "Failed: Other", + "Failed: Other, %%", + "Total" + ) + + override fun toList(): List = headerItems + } + + class AggregatedStatsRow( + private val declarationType: DeclarationType + ) : StatsOutput.StatsRow { + var liftedUp: Int = 0 + var successfullyCommonized: Int = 0 + var failedBecauseAbsent: Int = 0 + var failedOther: Int = 0 + + override fun toList(): List { + val total = liftedUp + successfullyCommonized + failedBecauseAbsent + failedOther + + fun fraction(amount: Int): Double = if (total > 0) amount.toDouble() / total else 0.0 + + return listOf( + declarationType.alias, + liftedUp.toString(), + fraction(liftedUp).toString(), + successfullyCommonized.toString(), + fraction(successfullyCommonized).toString(), + failedBecauseAbsent.toString(), + fraction(failedBecauseAbsent).toString(), + failedOther.toString(), + fraction(failedOther).toString(), + total.toString() + ) + } + } + +} + +@Suppress("MoveVariableDeclarationIntoWhen") +private class AggregatingOutput : StatsOutput { + val aggregatedStats = HashMap() + + override fun writeHeader(header: StatsOutput.StatsHeader) { + check(header is RawStatsCollector.RawStatsHeader) + // do nothing + } + + override fun writeRow(row: StatsOutput.StatsRow) { + check(row is RawStatsCollector.RawStatsRow) + + val aggregatedStatsRow = aggregatedStats.computeIfAbsent(row.declarationType, ::AggregatedStatsRow) + when (row.common) { + LIFTED_UP -> aggregatedStatsRow.liftedUp++ + EXPECT -> aggregatedStatsRow.successfullyCommonized++ + ABSENT -> { + if (row.platform.any { it == RawStatsCollector.PlatformDeclarationStatus.ABSENT }) { + aggregatedStatsRow.failedBecauseAbsent++ + } else { + aggregatedStatsRow.failedOther++ + } + } + } + } + + override fun close() = Unit +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/DeclarationType.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/DeclarationType.kt new file mode 100644 index 00000000000..ef510c4e964 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/DeclarationType.kt @@ -0,0 +1,52 @@ +/* + * 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.descriptors.commonizer.stats + +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.resolve.DescriptorUtils + +enum class DeclarationType(val alias: String) { + TOP_LEVEL_CONST_VAL("TOP-LEVEL CONST-VAL"), + TOP_LEVEL_VAL("TOP-LEVEL VAL"), + TOP_LEVEL_FUN("TOP-LEVEL FUN"), + TOP_LEVEL_CLASS("TOP-LEVEL CLASS"), + CLASS_CONSTRUCTOR("CLASS_CONSTRUCTOR"), + NESTED_VAL("NESTED VAL"), + NESTED_FUN("NESTED FUN"), + NESTED_CLASS("NESTED CLASS"), + NESTED_INTERFACE("NESTED INTERFACE"), + COMPANION_OBJECT("COMPANION_OBJECT"), + TOP_LEVEL_INTERFACE("TOP-LEVEL INTERFACE"), + TYPE_ALIAS("TYPE_ALIAS"), + ENUM_ENTRY("ENUM_ENTRY"), + ENUM_CLASS("ENUM_CLASS"), + MODULE("MODULE"), + UNKNOWN("UNKNOWN"); + + companion object { + val DeclarationDescriptor.declarationType: DeclarationType + get() = when (this) { + is ClassDescriptor -> { + if (isCompanionObject) { + COMPANION_OBJECT + } else when (kind) { + ClassKind.ENUM_CLASS -> ENUM_CLASS + ClassKind.ENUM_ENTRY -> ENUM_ENTRY + ClassKind.INTERFACE -> if (DescriptorUtils.isTopLevelDeclaration(this)) TOP_LEVEL_INTERFACE else NESTED_INTERFACE + else -> if (DescriptorUtils.isTopLevelDeclaration(this)) TOP_LEVEL_CLASS else NESTED_CLASS + } + } + is TypeAliasDescriptor -> TYPE_ALIAS + is ClassConstructorDescriptor -> CLASS_CONSTRUCTOR + is FunctionDescriptor -> if (DescriptorUtils.isTopLevelDeclaration(this)) TOP_LEVEL_FUN else NESTED_FUN + is PropertyDescriptor -> if (DescriptorUtils.isTopLevelDeclaration(this)) { + if (isConst) TOP_LEVEL_CONST_VAL else TOP_LEVEL_VAL + } else NESTED_VAL + is ModuleDescriptor -> MODULE + else -> UNKNOWN + } + } +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/RawStatsCollector.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/RawStatsCollector.kt new file mode 100644 index 00000000000..27178545c74 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/RawStatsCollector.kt @@ -0,0 +1,168 @@ +/* + * 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.descriptors.commonizer.stats + +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.commonizer.stats.DeclarationType.Companion.declarationType +import org.jetbrains.kotlin.descriptors.commonizer.utils.firstNonNull +import org.jetbrains.kotlin.descriptors.commonizer.utils.fqNameWithTypeParameters +import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe + +/** + * Allows printing commonization statistics to the file system. + * + * Output format is defined in [RawStatsCollector.output]. + * + * Header row: "FQ Name, Extension Receiver, Parameter Names, Parameter Types, Declaration Type, common, , [...]" + * + * Possible values for "Declaration Type": + * - MODULE + * - CLASS + * - INTERFACE + * - OBJECT + * - COMPANION_OBJECT + * - ENUM_CLASS + * - ENUM_ENTRY + * - TYPE_ALIAS + * - CLASS_CONSTRUCTOR + * - FUN + * - VAL + * + * Possible values for "common" column: + * - L = declaration lifted up to common fragment + * - E = successfully commonized, expect declaration generated + * - "-" = no common declaration + * + * Possible values for each target platform column: + * - A = successfully commonized, actual declaration generated + * - O = not commonized, the declaration is as in the original library + * - "-" = no such declaration in the original library (or declaration has been lifted up) + * + * Example of output: + +FQ Name|Extension Receiver|Parameter Names|Parameter Types|Declaration Type|common|macos_x64|ios_x64 +||||MODULE|E|A|A +platform.SystemConfiguration.SCPreferencesContext||||CLASS|E|A|A +platform.SystemConfiguration.SCPreferencesContext.Companion||||COMPANION_OBJECT|E|A|A +platform.SystemConfiguration.SCNetworkConnectionContext||||CLASS|E|A|A +platform.SystemConfiguration.SCNetworkConnectionContext.Companion||||COMPANION_OBJECT|E|A|A +platform.SystemConfiguration.SCDynamicStoreRefVar||||TYPE_ALIAS|-|O|O +platform.SystemConfiguration.SCVLANInterfaceRef||||TYPE_ALIAS|-|O|O + + */ +class RawStatsCollector( + private val targets: List, + private val output: StatsOutput +) : StatsCollector { + private var headerWritten = false + + override fun logStats(result: List) { + if (!headerWritten) { + writeHeader() + headerWritten = true + } + + val firstNotNull = result.firstNonNull() + val lastIsNull = result.last() == null + + val statsRow = RawStatsRow( + dimension = targets.size, + fqName = if (firstNotNull is ModuleDescriptor) firstNotNull.name.asString() else firstNotNull.fqNameSafe.asString(), + declarationType = firstNotNull.declarationType + ) + + // extension receiver + if (firstNotNull is PropertyDescriptor || firstNotNull is SimpleFunctionDescriptor) { + statsRow.extensionReceiver = + (firstNotNull as CallableDescriptor).extensionReceiverParameter?.type?.fqNameWithTypeParameters.orEmpty() + } + + if (firstNotNull is ConstructorDescriptor || firstNotNull is SimpleFunctionDescriptor) { + val functionDescriptor = (firstNotNull as FunctionDescriptor) + // parameter names + statsRow.parameterNames = functionDescriptor.valueParameters.joinToString { it.name.asString() } + // parameter types + statsRow.parameterTypes = functionDescriptor.valueParameters.joinToString { it.type.fqNameWithTypeParameters } + } + + var isLiftedUp = !lastIsNull + for (index in 0 until result.size - 1) { + statsRow.platform += when { + result[index] == null -> PlatformDeclarationStatus.ABSENT + lastIsNull -> PlatformDeclarationStatus.ORIGINAL + else -> { + isLiftedUp = false + PlatformDeclarationStatus.ACTUAL + } + } + } + + statsRow.common = when { + isLiftedUp -> CommonDeclarationStatus.LIFTED_UP + lastIsNull -> CommonDeclarationStatus.ABSENT + else -> CommonDeclarationStatus.EXPECT + } + + output.writeRow(statsRow) + } + + override fun close() { + output.close() + } + + private fun writeHeader() { + output.writeHeader(RawStatsHeader(targets.map { it.name })) + } + + class RawStatsHeader( + private val targetNames: List + ) : StatsOutput.StatsHeader { + override fun toList(): List = mutableListOf().apply { + this += "FQ Name" + this += "Extension Receiver" + this += "Parameter Names" + this += "Parameter Types" + this += "Declaration Type" + this += "common" + this += targetNames + } + } + + class RawStatsRow( + dimension: Int, + val fqName: String, + val declarationType: DeclarationType + ) : StatsOutput.StatsRow { + var extensionReceiver: String = "" + var parameterNames: String = "" + var parameterTypes: String = "" + lateinit var common: CommonDeclarationStatus + val platform: MutableList = ArrayList(dimension) + + override fun toList(): List = mutableListOf().apply { + this += fqName + this += extensionReceiver + this += parameterNames + this += parameterTypes + this += declarationType.alias + this += common.alias.toString() + platform.forEach { this += it.alias.toString() } + } + } + + enum class CommonDeclarationStatus(val alias: Char) { + LIFTED_UP('L'), + EXPECT('E'), + ABSENT('-') + } + + enum class PlatformDeclarationStatus(val alias: Char) { + ACTUAL('A'), + ORIGINAL('O'), + ABSENT('-') + } +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/StatsCollector.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/StatsCollector.kt similarity index 60% rename from native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/StatsCollector.kt rename to native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/StatsCollector.kt index 0a78eee8c8d..c7872277fc0 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/StatsCollector.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/StatsCollector.kt @@ -1,13 +1,13 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.descriptors.commonizer +package org.jetbrains.kotlin.descriptors.commonizer.stats import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import java.io.Closeable interface StatsCollector : Closeable { - fun logStats(output: List) + fun logStats(result: List) } diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/StatsOutput.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/StatsOutput.kt new file mode 100644 index 00000000000..b39da797eda --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/stats/StatsOutput.kt @@ -0,0 +1,53 @@ +/* + * 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.descriptors.commonizer.stats + +import java.io.Closeable +import java.io.File +import java.io.PrintWriter + +interface StatsOutput : Closeable { + interface StatsHeader { + fun toList(): List + } + + interface StatsRow { + fun toList(): List + } + + fun writeHeader(header: StatsHeader) + fun writeRow(row: StatsRow) +} + +class FileStatsOutput(directory: File, baseName: String) : StatsOutput { + init { + directory.mkdirs() + } + + private val writer: PrintWriter = directory.resolve("${baseName}_stats.csv").printWriter() + private var width: Int = 0 + + override fun writeHeader(header: StatsOutput.StatsHeader) { + check(width == 0) + val headerItems = header.toList() + require(headerItems.isNotEmpty()) + + width = headerItems.size + headerItems.joinTo(writer, separator = "|", postfix = "\n") + } + + override fun writeRow(row: StatsOutput.StatsRow) { + check(width > 0) + val rowItems = row.toList() + require(rowItems.size == width) + + rowItems.joinTo(writer, separator = "|", postfix = "\n") + } + + override fun close() { + writer.close() + } +}