Use comma separator for .kotlin-profile files and semicolon for .profile Right now expected format for FUS files is different for old Fus files and new ones. Comma or semicolon is used to separate a list of metric's values. #KT-79585: Varification pending
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FusStatisticsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FusStatisticsIT.kt index 5469e3e..abb294c 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FusStatisticsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FusStatisticsIT.kt
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.gradle.util.filterKotlinFusFiles import org.jetbrains.kotlin.gradle.util.replaceText import org.jetbrains.kotlin.gradle.util.swiftExportEmbedAndSignEnvVariables +import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.statistics.metrics.StringAnonymizationPolicy import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.condition.OS @@ -197,6 +198,7 @@ build("linkDebugExecutableHost", "-Pkotlin.session.logger.root.path=$projectPath") { assertOutputDoesNotContainFusErrors() fusStatisticsDirectory.assertFusReportContains("KOTLIN_INCREMENTAL_NATIVE_ENABLED=true") + fusStatisticsDirectory.assertFusReportContainsMetricWithValues("MPP_PLATFORMS", listOf("common", HostManager.host.name)) } } } @@ -747,7 +749,7 @@ @GradleTest @JvmGradlePluginTests fun testCompilerExecutionSettings(gradleVersion: GradleVersion) { - val kotlinVersion = StringAnonymizationPolicy.ComponentVersionAnonymizer().anonymize(KOTLIN_VERSION) + val kotlinVersion = StringAnonymizationPolicy.ComponentVersionAnonymizer().anonymize(KOTLIN_VERSION, ";") project("simpleProject", gradleVersion) { assertNoErrorFilesCreated { build("compileKotlin", "-Pkotlin.session.logger.root.path=$projectPath") { @@ -860,3 +862,8 @@ assertOutputDoesNotContain("finish-profile already exists") assertOutputDoesNotContain("Unable to collect finish file for build") } + +private fun Path.assertFusReportContainsMetricWithValues(metricName: String, expectedValues: List<String>) { + assertFilesCombinedContains(filterKotlinFusFiles(), "$metricName=${expectedValues.joinToString(",")}") + assertFilesCombinedContains(filterBackwardCompatibilityKotlinFusFiles(), "$metricName=${expectedValues.joinToString(";")}") +}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFinishBuildService.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFinishBuildService.kt index 573b9c8..95407e2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFinishBuildService.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFinishBuildService.kt
@@ -25,11 +25,13 @@ import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider import org.jetbrains.kotlin.gradle.utils.kotlinErrorsDir import org.jetbrains.kotlin.statistics.fileloggers.MetricsContainer +import org.jetbrains.kotlin.statistics.fileloggers.MetricsContainer.Companion.addMetricFromFusKotlinProfileFile +import org.jetbrains.kotlin.statistics.fileloggers.MetricsContainer.Companion.createValidateAndAnonymizeCopy import java.io.File import java.util.concurrent.atomic.AtomicBoolean -import kotlin.String -internal abstract class BuildFinishBuildService : BuildService<BuildFinishBuildService.Parameters>, AutoCloseable, OperationCompletionListener { +internal abstract class BuildFinishBuildService : BuildService<BuildFinishBuildService.Parameters>, AutoCloseable, + OperationCompletionListener { protected val buildId = parameters.buildId.get() private val log = Logging.getLogger(this.javaClass) private val errorWasReported = AtomicBoolean(false) @@ -50,7 +52,11 @@ private val serviceName = "${BuildFinishBuildService::class.java.canonicalName}_${BuildFinishBuildService::class.java.classLoader.hashCode()}" - fun registerIfAbsent(project: Project, buildUidService: Provider<BuildUidService>, kotlinPluginVersion: String): Provider<BuildFinishBuildService>? { + fun registerIfAbsent( + project: Project, + buildUidService: Provider<BuildUidService>, + kotlinPluginVersion: String, + ): Provider<BuildFinishBuildService>? { if (!project.buildServiceShouldBeCreated) { return null } @@ -88,21 +94,19 @@ log: Logger, ): Errors { try { - val metricContainer = MetricsContainer() + val metricContainer = MetricsContainer.createMetricsContainerForProfileFile() fusReportDirectory.listFiles() .filter { it.name.startsWith(buildUid) && (it.name.endsWith("plugin-profile") || it.name.endsWith("kotlin-profile")) } .forEach { - MetricsContainer.readFromFile(it) { - metricContainer.populateFromMetricsContainer(it) - } + metricContainer.addMetricFromFusKotlinProfileFile(it) } val fusFile = fusReportDirectory.resolve("$buildUid.profile") fusFile.writer().buffered().use { it.appendLine("Build: $buildUid") it.appendLine("Kotlin version: $kotlinVersion") - metricContainer.flush(it) + metricContainer.createValidateAndAnonymizeCopy().flush(it) } if (!fusReportDirectory.resolve("$buildUid.finish-profile").createNewFile()) { @@ -145,4 +149,4 @@ } } } -} \ No newline at end of file +}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/NonSynchronizedMetricsContainer.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/NonSynchronizedMetricsContainer.kt index 6b56894..1a5b3c3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/NonSynchronizedMetricsContainer.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/NonSynchronizedMetricsContainer.kt
@@ -1,13 +1,15 @@ package org.jetbrains.kotlin.gradle.plugin.statistics -import org.jetbrains.kotlin.statistics.metrics.* +import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics +import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics +import org.jetbrains.kotlin.statistics.metrics.StatisticsValuesConsumer +import org.jetbrains.kotlin.statistics.metrics.StringMetrics import java.io.Serializable /* * Copyright 2010-2025 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. */ - internal open class NonSynchronizedMetricsContainer : StatisticsValuesConsumer, Serializable { data class MetricDescriptor<T : Comparable<T>>(val name: T, val subprojectName: String?) : Comparable<MetricDescriptor<T>> { override fun compareTo(other: MetricDescriptor<T>): Int { @@ -20,52 +22,57 @@ } } - private val numericalMetrics = HashMap<MetricDescriptor<NumericalMetrics>, IMetricContainer<Long>>() + private val numericalMetrics = HashMap<MetricDescriptor<NumericalMetrics>, MutableList<Pair<Long, Long?>>>() - private val booleanMetrics = HashMap<MetricDescriptor<BooleanMetrics>, IMetricContainer<Boolean>>() + private val booleanMetrics = HashMap<MetricDescriptor<BooleanMetrics>, MutableList<Pair<Boolean, Long?>>>() - private val stringMetrics = HashMap<MetricDescriptor<StringMetrics>, IMetricContainer<String>>() + private val stringMetrics = HashMap<MetricDescriptor<StringMetrics>, MutableList<Pair<String, Long?>>>() - private fun <V, T : IMetricContainerFactory<V>, K : MetricDescriptor<*>> putToCollection( - collection: MutableMap<K, IMetricContainer<V>>, + private fun <V, K : MetricDescriptor<*>> putToCollection( + collection: MutableMap<K, MutableList<Pair<V, Long?>>>, key: K, - type: T, value: V, weight: Long? = null, ) { - collection.getOrPut(key) { type.newMetricContainer() }.addValue(value, weight) + collection.getOrPut(key) { ArrayList<Pair<V, Long?>>() }.add(value to weight) } override fun report(metric: BooleanMetrics, value: Boolean, subprojectName: String?, weight: Long?): Boolean { - putToCollection(booleanMetrics, MetricDescriptor(metric, subprojectName), metric.type, value, weight) + putToCollection(booleanMetrics, MetricDescriptor(metric, subprojectName), value, weight) return true } override fun report(metric: NumericalMetrics, value: Long, subprojectName: String?, weight: Long?): Boolean { - putToCollection(numericalMetrics, MetricDescriptor(metric, subprojectName), metric.type, value, weight) + putToCollection(numericalMetrics, MetricDescriptor(metric, subprojectName), value, weight) return true } override fun report(metric: StringMetrics, value: String, subprojectName: String?, weight: Long?): Boolean { - putToCollection(stringMetrics, MetricDescriptor(metric, subprojectName), metric.type, value, weight) + putToCollection(stringMetrics, MetricDescriptor(metric, subprojectName), value, weight) return true } open fun readFromMetricConsumer(metricConsumer: NonSynchronizedMetricsContainer) { metricConsumer.booleanMetrics.forEach { - putToCollection(booleanMetrics, MetricDescriptor(it.key.name, it.key.subprojectName), it.key.name.type, it.value.getValue()!!) + it.value.forEach { (value, weight) -> putToCollection(booleanMetrics, MetricDescriptor(it.key.name, it.key.subprojectName), value, weight) } } metricConsumer.stringMetrics.forEach { - putToCollection(stringMetrics, MetricDescriptor(it.key.name, it.key.subprojectName), it.key.name.type, it.value.getValue()!!) + it.value.forEach { (value, weight) -> putToCollection(stringMetrics, MetricDescriptor(it.key.name, it.key.subprojectName), value, weight) } } metricConsumer.numericalMetrics.forEach { - putToCollection(numericalMetrics, MetricDescriptor(it.key.name, it.key.subprojectName), it.key.name.type, it.value.getValue()!!) + it.value.forEach { (value, weight) -> putToCollection(numericalMetrics, MetricDescriptor(it.key.name, it.key.subprojectName), value, weight) } } } open fun sendToConsumer(metricConsumer: StatisticsValuesConsumer) { - booleanMetrics.forEach { metricConsumer.report(it.key.name, it.value.getValue()!!, it.key.subprojectName) } - numericalMetrics.forEach { metricConsumer.report(it.key.name, it.value.getValue()!!, it.key.subprojectName) } - stringMetrics.forEach { metricConsumer.report(it.key.name, it.value.getValue()!!, it.key.subprojectName) } + booleanMetrics.forEach { + it.value.forEach { (value, weight) -> metricConsumer.report(it.key.name, value, it.key.subprojectName, weight) } + } + numericalMetrics.forEach { + it.value.forEach { (value, weight) -> metricConsumer.report(it.key.name, value, it.key.subprojectName, weight) } + } + stringMetrics.forEach { + it.value.forEach { (value, weight) -> metricConsumer.report(it.key.name, value, it.key.subprojectName, weight) } + } } -} \ No newline at end of file +}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/statistics/BuildFinishBuildServiceTest.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/statistics/BuildFinishBuildServiceTest.kt index f096549..4b24d1e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/statistics/BuildFinishBuildServiceTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/statistics/BuildFinishBuildServiceTest.kt
@@ -9,9 +9,11 @@ import org.jetbrains.kotlin.gradle.plugin.statistics.BuildFinishBuildService import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics +import org.jetbrains.kotlin.statistics.metrics.StringMetrics import org.junit.jupiter.api.io.TempDir import java.io.File import kotlin.test.Test +import kotlin.test.assertContains import kotlin.test.assertTrue class BuildFinishBuildServiceTest { @@ -31,6 +33,11 @@ unknown-metric=1 ${NumericalMetrics.COMPILATION_DURATION}=10 + ${StringMetrics.OS_VERSION}=1.0.0-SNAPSHOT + ${StringMetrics.IDES_INSTALLED}=invalid_version,AS,WC + ${BooleanMetrics.ENABLED_COMPILER_REFERENCE_INDEX}=true + ${BooleanMetrics.KOTLIN_PROGRESSIVE_MODE}=true + BUILD FINISHED """.trimIndent() ) @@ -39,6 +46,9 @@ unknown-metric=1 wrong format ${NumericalMetrics.COMPILATION_DURATION}=10 + ${StringMetrics.OS_VERSION}=invalid_version + ${BooleanMetrics.ENABLED_COMPILER_REFERENCE_INDEX}=invalid + ${BooleanMetrics.KOTLIN_PROGRESSIVE_MODE}=invalid BUILD FINISHED """.trimIndent() ) @@ -51,6 +61,10 @@ fusDir.resolve("$buildId.kajfsjfh.kotlin-profile").writeText( """ ${BooleanMetrics.BUILD_SCAN_BUILD_REPORT}=true + ${StringMetrics.OS_VERSION}=2.0.0-SNAPSHOT + ${StringMetrics.IDES_INSTALLED}=IU + ${BooleanMetrics.ENABLED_COMPILER_REFERENCE_INDEX}=false + ${BooleanMetrics.KOTLIN_PROGRESSIVE_MODE}=false BUILD FINISHED """.trimIndent() ) @@ -72,6 +86,10 @@ assertTrue("Profile file should contain valid metrics") { profileContent.contains("${NumericalMetrics.COMPILATION_DURATION}=20") } + assertContains(profileContent, "${StringMetrics.OS_VERSION}=2.0.0-snapshot") + assertContains(profileContent, "${StringMetrics.IDES_INSTALLED}=AS;IU;WC;UNEXPECTED-VALUE") + assertContains(profileContent,"${BooleanMetrics.ENABLED_COMPILER_REFERENCE_INDEX}=true") + assertContains(profileContent,"${BooleanMetrics.KOTLIN_PROGRESSIVE_MODE}=false") assertTrue("Profile file should not contain metrics from another build") { !profileContent.contains(BooleanMetrics.TESTS_EXECUTED.name) }
diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/statistics/BuildSessionLoggerTest.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/statistics/BuildSessionLoggerTest.kt index 1e50bc8..78b0964 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/statistics/BuildSessionLoggerTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/statistics/BuildSessionLoggerTest.kt
@@ -8,9 +8,12 @@ import org.jetbrains.kotlin.statistics.BuildSessionLogger import org.jetbrains.kotlin.statistics.BuildSessionLogger.Companion.FUS_KOTLIN_FILE_NAME_SUFFIX import org.jetbrains.kotlin.statistics.fileloggers.MetricsContainer +import org.jetbrains.kotlin.statistics.fileloggers.MetricsContainer.Companion.addMetricFromFusKotlinProfileFile import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics +import org.jetbrains.kotlin.statistics.metrics.StringAnonymizationPolicy import org.jetbrains.kotlin.statistics.metrics.StringMetrics +import org.jetbrains.kotlin.statistics.metrics.StringOverridePolicy import java.io.File import java.nio.file.Files import java.util.* @@ -169,29 +172,72 @@ fun testSaveAndReadAllMetrics() { val logger = BuildSessionLogger(statsFolder) logger.startBuildSession("test") - for (metric in StringMetrics.values()) { - logger.report(metric, "value") - logger.report(metric, metric.name) + for (metric in StringMetrics.entries) { + when (val anonymization = metric.anonymization) { + is StringAnonymizationPolicy.ComponentVersionAnonymizer -> { + logger.report(metric, "1.2.3") + logger.report(metric, "1.2.3-SNAPSHOT") + } + is StringAnonymizationPolicy.AllowedListAnonymizer -> { + anonymization.allowedValues.sorted().forEach { + logger.report(metric, it) + } + } + is StringAnonymizationPolicy.RegexControlled -> logger.report(metric, metric.name) + else -> { + logger.report(metric, "value") + logger.report(metric, metric.name) + } + } } - for (metric in BooleanMetrics.values()) { + for (metric in BooleanMetrics.entries) { logger.report(metric, true) } - for (metric in NumericalMetrics.values()) { + for (metric in NumericalMetrics.entries) { logger.report(metric, System.currentTimeMillis()) } - logger.finishBuildSession() - MetricsContainer.readFromFile(statsFolder.listFiles()?.single() ?: fail("Could not find stat file")) { - for (metric in StringMetrics.values()) { - assertNotNull(it.getMetric(metric), "Could not find metric ${metric.name}") - } - for (metric in BooleanMetrics.values()) { - assertTrue(it.getMetric(metric)?.getValue() != null, "Could not find metric ${metric.name}") - } - for (metric in NumericalMetrics.values()) { - assertNotNull(it.getMetric(metric), "Could not find metric ${metric.name}") + logger.finishBuildSession() // create kotlin-profile fus file with comma-separated values + + // read metrics from kotlin-profile file in old format with semicolon-separated values + val metricContainer = MetricsContainer.createMetricsContainerForProfileFile() + + metricContainer.addMetricFromFusKotlinProfileFile(statsFolder.listFiles()?.single() ?: fail("Could not find stat file")) + + for (metric in StringMetrics.entries) { + val metricValue = metricContainer.getMetric(metric)?.getValue() ?: fail("Could not find metric ${metric.name}") + + when (val anonymization = metric.anonymization) { + is StringAnonymizationPolicy.ComponentVersionAnonymizer -> validateMetricValueBasedOnOverrideRule(metric, listOf("1.2.3", "1.2.3-snapshot"), metricValue) + is StringAnonymizationPolicy.AllowedListAnonymizer -> validateMetricValueBasedOnOverrideRule(metric, anonymization.allowedValues.sorted(), metricValue) + is StringAnonymizationPolicy.RegexControlled -> assertMetricValueEquals(metric, metric.name, metricValue) + else -> validateMetricValueBasedOnOverrideRule(metric, listOf("value", metric.name), metricValue) } } + + for (metric in BooleanMetrics.entries) { + assertTrue(metricContainer.getMetric(metric)?.getValue() != null, "Could not find metric ${metric.name}") + } + + for (metric in NumericalMetrics.entries) { + assertNotNull(metricContainer.getMetric(metric), "Could not find metric ${metric.name}") + } + } + + private fun validateMetricValueBasedOnOverrideRule(metric: StringMetrics, possibleExpectedValues: List<String>, actualValue: String) { + when (metric.type) { + StringOverridePolicy.OVERRIDE -> assertMetricValueEquals(metric, possibleExpectedValues.last(), actualValue) + StringOverridePolicy.OVERRIDE_VERSION_IF_NOT_SET -> assertMetricValueEquals(metric, possibleExpectedValues.first(), actualValue) + StringOverridePolicy.CONCAT -> assertMetricValueEquals(metric, possibleExpectedValues.joinToString(";"), actualValue) + } + } + + private fun assertMetricValueEquals(metric: StringMetrics, expectedValue: String, actualValue: String) { + assertEquals( + expectedValue, + actualValue, + "Metric ${metric.name} contains unexpected value: expected $expectedValue, but found $actualValue" + ) } @Test
diff --git a/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/AnonymizerUtils.kt b/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/AnonymizerUtils.kt index 3ad68bf..b91b2f5 100644 --- a/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/AnonymizerUtils.kt +++ b/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/AnonymizerUtils.kt
@@ -7,9 +7,11 @@ import java.security.MessageDigest +internal const val DEFAULT_SEPARATOR = ";" + internal interface ValueAnonymizer<T> { - fun anonymize(t: T): T + fun anonymize(t: T, separator: String = DEFAULT_SEPARATOR): T fun anonymizeOnIdeSize(): Boolean = false
diff --git a/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/BuildSessionLogger.kt b/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/BuildSessionLogger.kt index d5aaa38..280fdbd 100644 --- a/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/BuildSessionLogger.kt +++ b/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/BuildSessionLogger.kt
@@ -46,7 +46,8 @@ private var buildSession: BuildSession? = null - private val metricsContainer = MetricsContainer(forceValuesValidation) + //New FUS file should use comma (,) as a separator, but for old FUS files a semicolon (;) is used + private val metricsContainer = MetricsContainer.createMetricsContainerForKotlinProfileFile(forceValuesValidation) @Synchronized fun startBuildSession(buildUid: String) {
diff --git a/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/fileloggers/MetricsContainer.kt b/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/fileloggers/MetricsContainer.kt index e4b7513..39ee9b2 100644 --- a/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/fileloggers/MetricsContainer.kt +++ b/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/fileloggers/MetricsContainer.kt
@@ -16,7 +16,15 @@ import java.nio.file.StandardOpenOption import java.util.* -class MetricsContainer(private val forceValuesValidation: Boolean = false) : StatisticsValuesConsumer { +/** + * A container class for managing and storing metrics of various types (numerical, boolean, and string) + * in a structured way. This class facilitates the collection, aggregation, and processing of + * metric data, supporting operations like reporting and reading metrics from files. + */ +class MetricsContainer( + private val forceValuesValidation: Boolean = false, + private val metricValuesSeparator: String = FUS_METRIC_SEPARATOR_FOR_BACKWARD_COMPATIBILITY_PROFILE_FILE, +) : StatisticsValuesConsumer { data class MetricDescriptor(val name: String, val projectHash: String?) : Comparable<MetricDescriptor> { override fun compareTo(other: MetricDescriptor): Int { val compareNames = name.compareTo(other.name) @@ -38,6 +46,10 @@ companion object { private const val BUILD_SESSION_SEPARATOR = "BUILD FINISHED" + private const val METRIC_SEPARATOR = '=' + private const val PROJECT_METRIC_NAME_SEPARATOR = '.' + private const val FUS_METRIC_SEPARATOR_FOR_KOTLIN_PROFILE_FILE = "," + private const val FUS_METRIC_SEPARATOR_FOR_BACKWARD_COMPATIBILITY_PROFILE_FILE = ";" val ENCODING = Charsets.UTF_8 @@ -47,11 +59,74 @@ private val numericalMetricsMap = NumericalMetrics.values().associateBy(NumericalMetrics::name) + fun createMetricsContainerForProfileFile(forceValuesValidation: Boolean = false) = + MetricsContainer( + forceValuesValidation = forceValuesValidation, + metricValuesSeparator = FUS_METRIC_SEPARATOR_FOR_BACKWARD_COMPATIBILITY_PROFILE_FILE + ) + + fun createMetricsContainerForKotlinProfileFile(forceValuesValidation: Boolean = false) = + MetricsContainer( + forceValuesValidation = forceValuesValidation, + metricValuesSeparator = FUS_METRIC_SEPARATOR_FOR_KOTLIN_PROFILE_FILE + ) + + private fun MetricsContainer.addMetricToContainer( + metricDescriptor: MetricDescriptor, + representation: String, + separator: String = metricValuesSeparator, + ) { + stringMetricsMap[metricDescriptor.name]?.also { metricType -> + synchronized(metricsLock) { + stringMetrics.getOrPut(metricDescriptor) { + metricType.type.newMetricContainer() + }.addValueFromStringPresentation(representation, separator) + } + } + + booleanMetricsMap[metricDescriptor.name]?.also { metricType -> + synchronized(metricsLock) { + booleanMetrics.getOrPut(metricDescriptor) { + metricType.type.newMetricContainer() + }.addValueFromStringPresentation(representation, separator) + } + } + + numericalMetricsMap[metricDescriptor.name]?.also { metricType -> + synchronized(metricsLock) { + numericalMetrics.getOrPut(metricDescriptor) { + metricType.type.newMetricContainer() + }.addValueFromStringPresentation(representation, separator) + } + } + } + + // for new Fus files a comma (,) separator is used, but for old FUS files a semicolon (;) is used + private fun MetricsContainer.addMetricFromFusFile(file: File, separator: String) = + FileChannel.open(Paths.get(file.toURI()), StandardOpenOption.WRITE, StandardOpenOption.READ).use { channel -> + BufferedReader(InputStreamReader(Channels.newInputStream(channel), ENCODING)).use { + it.forEachLine { line -> + if (line.contains(METRIC_SEPARATOR)) { + // format: metricName.hash=string representation + parseLine(line)?.also { (metricDescriptor, representation) -> + addMetricToContainer(metricDescriptor, representation, separator) + } + } + } + } + } + + fun MetricsContainer.addMetricFromFusKotlinProfileFile(file: File) = + addMetricFromFusFile(file, separator = FUS_METRIC_SEPARATOR_FOR_KOTLIN_PROFILE_FILE) + + // This method also used IntelliJ project in KotlinGradleFUSLoggerProcessor#process. + // It expects to read a fus file with a semicolon (;) separator for metric values. fun readFromFile(file: File, consumer: (MetricsContainer) -> Unit): Boolean { val channel = FileChannel.open(Paths.get(file.toURI()), StandardOpenOption.WRITE, StandardOpenOption.READ) channel.tryLock() ?: return false val inputStream = Channels.newInputStream(channel) + try { var container = MetricsContainer() // Note: close is called at forEachLine @@ -59,37 +134,10 @@ if (BUILD_SESSION_SEPARATOR == line) { consumer.invoke(container) container = MetricsContainer() - } else if (line.contains('=')) { + } else if (line.contains(METRIC_SEPARATOR)) { // format: metricName.hash=string representation - val lineParts = line.split('=') - if (lineParts.size == 2) { - val name = lineParts[0].split('.')[0] - val subProjectHash = lineParts[0].split('.').getOrNull(1) - val representation = lineParts[1] - - stringMetricsMap[name]?.also { metricType -> - metricType.type.fromStringRepresentation(representation)?.also { - synchronized(container.metricsLock) { - container.stringMetrics[MetricDescriptor(name, subProjectHash)] = it - } - } - } - - booleanMetricsMap[name]?.also { metricType -> - metricType.type.fromStringRepresentation(representation)?.also { - synchronized(container.metricsLock) { - container.booleanMetrics[MetricDescriptor(name, subProjectHash)] = it - } - } - } - - numericalMetricsMap[name]?.also { metricType -> - metricType.type.fromStringRepresentation(representation)?.also { - synchronized(container.metricsLock) { - container.numericalMetrics[MetricDescriptor(name, subProjectHash)] = it - } - } - } + parseLine(line)?.also { (metricDescriptor, representation) -> + container.addMetricToContainer(metricDescriptor, representation) } } } @@ -98,8 +146,46 @@ } return true } + + private fun parseLine(line: String): Pair<MetricDescriptor, String>? { + val lineParts = line.split(METRIC_SEPARATOR) + if (lineParts.size == 2) { + val name = lineParts[0].split(PROJECT_METRIC_NAME_SEPARATOR)[0] + val subProjectHash = lineParts[0].split(PROJECT_METRIC_NAME_SEPARATOR).getOrNull(1) + val representation = lineParts[1] + return MetricDescriptor(name, subProjectHash) to representation + } + return null + } + + fun MetricsContainer.createValidateAndAnonymizeCopy(): MetricsContainer { + val metricsContainer = MetricsContainer(forceValuesValidation, metricValuesSeparator) + numericalMetrics.forEach { (metricDescriptor, container) -> + val metric = numericalMetricsMap[metricDescriptor.name] + val value = container.getValue() + if (metric != null && value != null) { + metricsContainer.report(metric, value, metricDescriptor.projectHash, null) + } + } + booleanMetrics.forEach { (metricDescriptor, container) -> + val metric = booleanMetricsMap[metricDescriptor.name] + val value = container.getValue() + if (metric != null && value != null) { + metricsContainer.report(metric, value, metricDescriptor.projectHash, null) + } + } + stringMetrics.forEach { (metricDescriptor, container) -> + val metric = stringMetricsMap[metricDescriptor.name] + val value = container.getValue() + if (metric != null && value != null) { + metricsContainer.report(metric, value, metricDescriptor.projectHash, null) + } + } + return metricsContainer + } } + private fun processProjectName(subprojectName: String?, perProject: Boolean) = if (perProject && subprojectName != null) sha256(subprojectName) else null @@ -109,9 +195,8 @@ override fun report(metric: BooleanMetrics, value: Boolean, subprojectName: String?, weight: Long?): Boolean { val projectHash = getProjectHash(metric.perProject, subprojectName) synchronized(metricsLock) { - val metricContainer = booleanMetrics[MetricDescriptor(metric.name, projectHash)] ?: metric.type.newMetricContainer() - .also { booleanMetrics[MetricDescriptor(metric.name, projectHash)] = it } - metricContainer.addValue(metric.anonymization.anonymize(value), weight) + val metricContainer = booleanMetrics.getOrPut(MetricDescriptor(metric.name, projectHash)) { metric.type.newMetricContainer() } + metricContainer.addValue(metric.anonymization.anonymize(value, metricValuesSeparator), weight) } return true } @@ -119,42 +204,34 @@ override fun report(metric: NumericalMetrics, value: Long, subprojectName: String?, weight: Long?): Boolean { val projectHash = getProjectHash(metric.perProject, subprojectName) synchronized(metricsLock) { - val metricContainer = numericalMetrics[MetricDescriptor(metric.name, projectHash)] ?: metric.type.newMetricContainer() - .also { numericalMetrics[MetricDescriptor(metric.name, projectHash)] = it } - metricContainer.addValue(metric.anonymization.anonymize(value), weight) + val metricContainer = numericalMetrics.getOrPut(MetricDescriptor(metric.name, projectHash)) { metric.type.newMetricContainer() } + metricContainer.addValue(metric.anonymization.anonymize(value, metricValuesSeparator), weight) } return true } + internal fun validateMetric(metric: StringMetrics, value: String) { + if (value.contains(UNEXPECTED_VALUE) || !value.matches(Regex(metric.anonymization.validationRegexp(metricValuesSeparator)))) { + throw MetricValueValidationFailed("Metric ${metric.name} has value [${value}]. Validation regex: ${metric.anonymization.validationRegexp()}.") + } + } + + internal fun anonymizeMetric(metric: StringMetrics, value: String) = metric.anonymization.anonymize(value, metricValuesSeparator) + override fun report(metric: StringMetrics, value: String, subprojectName: String?, weight: Long?): Boolean { val projectHash = getProjectHash(metric.perProject, subprojectName) synchronized(metricsLock) { - val metricContainer = stringMetrics[MetricDescriptor(metric.name, projectHash)] ?: metric.type.newMetricContainer() - .also { stringMetrics[MetricDescriptor(metric.name, projectHash)] = it } + val metricContainer = stringMetrics.getOrPut(MetricDescriptor(metric.name, projectHash)) { metric.type.newMetricContainer() } - val anonymizedValue = metric.anonymization.anonymize(value) + val anonymizedValue = anonymizeMetric(metric, value) if (forceValuesValidation && !metric.anonymization.anonymizeOnIdeSize()) { - if (anonymizedValue.contains(UNEXPECTED_VALUE) || !anonymizedValue.matches(Regex(metric.anonymization.validationRegexp()))) { - throw MetricValueValidationFailed("Metric ${metric.name} has value [${value}], after anonymization [${anonymizedValue}]. Validation regex: ${metric.anonymization.validationRegexp()}.") - } + validateMetric(metric, anonymizedValue) } metricContainer.addValue(anonymizedValue, weight) } return true } - fun populateFromMetricsContainer(metrics: MetricsContainer) = synchronized(metricsLock) { - metrics.booleanMetrics.forEach { (descriptor, metric) -> - report(BooleanMetrics.valueOf(descriptor.name), metric.getValue() ?: return@forEach, descriptor.projectHash, null) - } - metrics.stringMetrics.forEach { (descriptor, metric) -> - report(StringMetrics.valueOf(descriptor.name), metric.getValue() ?: return@forEach, descriptor.projectHash, null) - } - metrics.numericalMetrics.forEach { (descriptor, metric) -> - report(NumericalMetrics.valueOf(descriptor.name), metric.getValue() ?: return@forEach, descriptor.projectHash, null) - } - } - fun flush(writer: BufferedWriter) { val allMetrics = TreeMap<MetricDescriptor, IMetricContainer<out Any>>() synchronized(metricsLock) { @@ -165,7 +242,7 @@ writer.appendLine() for (entry in allMetrics.entries) { val suffix = if (entry.key.projectHash == null) "" else ".${entry.key.projectHash}" - writer.appendLine("${entry.key.name}$suffix=${entry.value.toStringRepresentation()}") + writer.appendLine("${entry.key.name}$suffix$METRIC_SEPARATOR${entry.value.toStringRepresentation(metricValuesSeparator)}") } writer.appendLine(BUILD_SESSION_SEPARATOR) @@ -185,6 +262,8 @@ stringMetrics[MetricDescriptor(metric.name, null)] } + fun getStringMetricPresentation(metric: StringMetrics): String? = getMetric(metric)?.toStringRepresentation(metricValuesSeparator) + fun getMetric(metric: BooleanMetrics): IMetricContainer<Boolean>? = synchronized(metricsLock) { booleanMetrics[MetricDescriptor(metric.name, null)] }
diff --git a/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/metrics/MetricContainers.kt b/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/metrics/MetricContainers.kt index 4a4eb40..6051c8a 100644 --- a/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/metrics/MetricContainers.kt +++ b/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/metrics/MetricContainers.kt
@@ -5,13 +5,16 @@ package org.jetbrains.kotlin.statistics.metrics +import org.jetbrains.kotlin.statistics.DEFAULT_SEPARATOR import java.io.Serializable import java.util.* interface IMetricContainer<T> : Serializable { fun addValue(t: T, weight: Long? = null) - fun toStringRepresentation(): String + fun addValueFromStringPresentation(str: String, separator: String) + + fun toStringRepresentation(separator: String? = DEFAULT_SEPARATOR): String fun getValue(): T? } @@ -19,27 +22,41 @@ interface IMetricContainerFactory<T> { fun newMetricContainer(): IMetricContainer<T> - fun fromStringRepresentation(state: String): IMetricContainer<T>? + fun fromStringRepresentation(state: String, separator: String = DEFAULT_SEPARATOR): IMetricContainer<T>? } -open class OverrideMetricContainer<T>() : IMetricContainer<T> { +abstract class OverrideMetricContainer<T>() : IMetricContainer<T> { internal var myValue: T? = null override fun addValue(t: T, weight: Long?) { myValue = t } - internal constructor(v: T?) : this() { - myValue = v - } - - override fun toStringRepresentation(): String { + override fun toStringRepresentation(separator: String?): String { return myValue?.toString() ?: "null" } override fun getValue() = myValue } +open class OverrideStringMetricContainer() : OverrideMetricContainer<String>() { + override fun addValueFromStringPresentation(str: String, separator: String) { + addValue(str, null) + } +} + +open class OverrideLongMetricContainer() : OverrideMetricContainer<Long>() { + override fun addValueFromStringPresentation(str: String, separator: String) { + str.toLongOrNull()?.also { addValue(it, null) } + } +} + +open class OverrideBooleanMetricContainer(): OverrideMetricContainer<Boolean>() { + override fun addValueFromStringPresentation(str: String, separator: String) { + str.toBooleanStrictOrNull()?.also { addValue(it, null) } + } +} + class OverrideVersionMetricContainer() : OverrideMetricContainer<String>() { constructor(v: String) : this() { myValue = v @@ -50,6 +67,10 @@ myValue = t } } + + override fun addValueFromStringPresentation(str: String, separator: String) { + addValue(str, null) + } } class SumMetricContainer() : OverrideMetricContainer<Long>() { @@ -60,6 +81,10 @@ override fun addValue(t: Long, weight: Long?) { myValue = (myValue ?: 0) + t } + + override fun addValueFromStringPresentation(str: String, separator: String) { + str.toLongOrNull()?.also { addValue(it, null) } + } } class AverageMetricContainer() : IMetricContainer<Long> { @@ -77,7 +102,11 @@ totalWeight += w } - override fun toStringRepresentation(): String { + override fun addValueFromStringPresentation(str: String, separator: String) { + str.toLongOrNull()?.also { addValue(it, null) } + } + + override fun toStringRepresentation(separator: String?): String { return getValue()?.toString() ?: "null" } @@ -94,25 +123,29 @@ override fun addValue(t: Boolean, weight: Long?) { myValue = (myValue ?: false) || t } + + override fun addValueFromStringPresentation(str: String, separator: String) { + str.toBooleanStrictOrNull()?.also { addValue(it, null) } + } } class ConcatMetricContainer() : IMetricContainer<String> { private val myValues = TreeSet<String>() - companion object { - const val SEPARATOR = ";" - } - constructor(values: Collection<String>) : this() { myValues.addAll(values) } override fun addValue(t: String, weight: Long?) { - myValues.add(t.replace(SEPARATOR, ",")) + myValues.add(t) } - override fun toStringRepresentation(): String { - return myValues.sorted().joinToString(SEPARATOR) + override fun addValueFromStringPresentation(str: String, separator: String) { + str.split(separator).forEach { addValue(it, null) } + } + + override fun toStringRepresentation(separator: String?): String { + return myValues.sorted().joinToString(separator.toString()) } override fun getValue() = toStringRepresentation()
diff --git a/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/metrics/MetricPolicies.kt b/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/metrics/MetricPolicies.kt index 4a4fde8..67d81e8 100644 --- a/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/metrics/MetricPolicies.kt +++ b/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/metrics/MetricPolicies.kt
@@ -5,33 +5,39 @@ package org.jetbrains.kotlin.statistics.metrics +import org.jetbrains.kotlin.statistics.DEFAULT_SEPARATOR import org.jetbrains.kotlin.statistics.ValueAnonymizer import org.jetbrains.kotlin.statistics.anonymizeComponentVersion -import org.jetbrains.kotlin.statistics.sha256 import kotlin.math.abs -enum class StringOverridePolicy: IMetricContainerFactory<String> { +enum class StringOverridePolicy : IMetricContainerFactory<String> { OVERRIDE { - override fun newMetricContainer(): IMetricContainer<String> = OverrideMetricContainer<String>() + override fun newMetricContainer(): IMetricContainer<String> = OverrideStringMetricContainer() - override fun fromStringRepresentation(state: String): IMetricContainer<String>? = OverrideMetricContainer(state) + override fun fromStringRepresentation(state: String, separator: String): IMetricContainer<String>? = + OverrideStringMetricContainer().also { + it.addValueFromStringPresentation(state, separator) + } }, OVERRIDE_VERSION_IF_NOT_SET { override fun newMetricContainer(): IMetricContainer<String> = OverrideVersionMetricContainer() - override fun fromStringRepresentation(state: String): IMetricContainer<String>? = OverrideVersionMetricContainer(state) + override fun fromStringRepresentation(state: String, separator: String): IMetricContainer<String>? = + OverrideVersionMetricContainer(state) }, CONCAT { override fun newMetricContainer(): IMetricContainer<String> = ConcatMetricContainer() - override fun fromStringRepresentation(state: String): IMetricContainer<String>? = ConcatMetricContainer(state.split(ConcatMetricContainer.SEPARATOR)) + override fun fromStringRepresentation(state: String, separator: String): IMetricContainer<String>? = ConcatMetricContainer().also { + it.addValueFromStringPresentation(state, separator) + } } //Should be useful counting container? } -private fun applyIfLong(v: String, action: (Long) -> IMetricContainer<Long>) : IMetricContainer<Long>? { +private fun applyIfLong(v: String, action: (Long) -> IMetricContainer<Long>): IMetricContainer<Long>? { val longVal = v.toLongOrNull() return if (longVal == null) { null @@ -40,40 +46,45 @@ } } -enum class NumberOverridePolicy: IMetricContainerFactory<Long> { +enum class NumberOverridePolicy : IMetricContainerFactory<Long> { OVERRIDE { - override fun newMetricContainer(): IMetricContainer<Long> = OverrideMetricContainer<Long>() + override fun newMetricContainer(): IMetricContainer<Long> = OverrideLongMetricContainer() - override fun fromStringRepresentation(state: String): IMetricContainer<Long>? = applyIfLong(state) { - OverrideMetricContainer(it) + override fun fromStringRepresentation(state: String, separator: String): IMetricContainer<Long>? = applyIfLong(state) { value -> + OverrideLongMetricContainer().also { it.addValue(value) } } }, SUM { override fun newMetricContainer(): IMetricContainer<Long> = SumMetricContainer() - override fun fromStringRepresentation(state: String): IMetricContainer<Long>? = applyIfLong(state) { - SumMetricContainer(it) + override fun fromStringRepresentation(state: String, separator: String): IMetricContainer<Long>? = SumMetricContainer().also { + it.addValueFromStringPresentation(state, separator) } }, AVERAGE { override fun newMetricContainer(): IMetricContainer<Long> = AverageMetricContainer() - override fun fromStringRepresentation(state: String): IMetricContainer<Long>? = applyIfLong(state) { - AverageMetricContainer(it) + override fun fromStringRepresentation(state: String, separator: String): IMetricContainer<Long>? = AverageMetricContainer().also { + it.addValueFromStringPresentation(state, separator) } + } } -enum class BooleanOverridePolicy: IMetricContainerFactory<Boolean> { +enum class BooleanOverridePolicy : IMetricContainerFactory<Boolean> { OVERRIDE { - override fun newMetricContainer(): IMetricContainer<Boolean> = OverrideMetricContainer<Boolean>() + override fun newMetricContainer(): IMetricContainer<Boolean> = OverrideBooleanMetricContainer() - override fun fromStringRepresentation(state: String): IMetricContainer<Boolean>? = OverrideMetricContainer(state.toBoolean()) + override fun fromStringRepresentation(state: String, separator: String): IMetricContainer<Boolean>? = + OverrideBooleanMetricContainer().also { + it.addValueFromStringPresentation(state, separator) + } }, OR { override fun newMetricContainer(): IMetricContainer<Boolean> = OrMetricContainer() - override fun fromStringRepresentation(state: String): IMetricContainer<Boolean>? = OrMetricContainer(state.toBoolean()) + override fun fromStringRepresentation(state: String, separator: String): IMetricContainer<Boolean>? = + OrMetricContainer(state.toBoolean()) } // may be add disctribution counter metric container @@ -81,28 +92,28 @@ enum class BooleanAnonymizationPolicy : ValueAnonymizer<Boolean> { SAFE { - override fun anonymize(t: Boolean) = t + override fun anonymize(t: Boolean, separator: String) = t } } abstract class StringAnonymizationPolicy : ValueAnonymizer<String> { - abstract fun validationRegexp(): String + abstract fun validationRegexp(separator: String = DEFAULT_SEPARATOR): String class AllowedListAnonymizer(val allowedValues: Collection<String>) : StringAnonymizationPolicy() { companion object { const val UNEXPECTED_VALUE = "UNEXPECTED-VALUE" } - override fun validationRegexp(): String { - return "^((${UNEXPECTED_VALUE}|${allowedValues.joinToString("|")})${ConcatMetricContainer.SEPARATOR}?)+$" + override fun validationRegexp(separator: String): String { + return "^((${UNEXPECTED_VALUE}|${allowedValues.joinToString("|")})($separator)?)+$" } - override fun anonymize(t: String): String { - return if (t.matches(Regex(validationRegexp()))) { + override fun anonymize(t: String, separator: String): String { + return if (t.matches(Regex(validationRegexp(separator)))) { t } else { - t.split(ConcatMetricContainer.SEPARATOR).joinToString(ConcatMetricContainer.SEPARATOR) { + t.split(separator).joinToString(separator.toString()) { if (allowedValues.contains(it)) it else @@ -113,27 +124,27 @@ } class RegexControlled(private val regex: String, private val anonymizeInIde: Boolean) : StringAnonymizationPolicy() { - override fun validationRegexp() = regex + override fun validationRegexp(separator: String): String = regex - override fun anonymize(t: String) = t + override fun anonymize(t: String, separator: String) = t override fun anonymizeOnIdeSize() = anonymizeInIde } class ComponentVersionAnonymizer() : StringAnonymizationPolicy() { - override fun validationRegexp() = "(\\d+).(\\d+).(\\d+)-?(dev|snapshot|m\\d?|rc\\d?|beta\\d?)?" + override fun validationRegexp(separator: String): String = "(\\d+).(\\d+).(\\d+)-?(dev|snapshot|m\\d?|rc\\d?|beta\\d?)?" - override fun anonymize(t: String) = anonymizeComponentVersion(t) + override fun anonymize(t: String, separator: String) = anonymizeComponentVersion(t) } } enum class NumberAnonymizationPolicy : ValueAnonymizer<Long> { SAFE { - override fun anonymize(t: Long) = t + override fun anonymize(t: Long, separator: String) = t }, RANDOM_10_PERCENT { - override fun anonymize(t: Long): Long { + override fun anonymize(t: Long, separator: String): Long { if (abs(t) < 10) return t val sign = if (t < 0) -1
diff --git a/libraries/tools/kotlin-gradle-statistics/src/test/kotlin/org/jetbrains/kotlin/statistics/MetricPolicyTest.kt b/libraries/tools/kotlin-gradle-statistics/src/test/kotlin/org/jetbrains/kotlin/statistics/MetricPolicyTest.kt index f9bb71a..4dcc49c 100644 --- a/libraries/tools/kotlin-gradle-statistics/src/test/kotlin/org/jetbrains/kotlin/statistics/MetricPolicyTest.kt +++ b/libraries/tools/kotlin-gradle-statistics/src/test/kotlin/org/jetbrains/kotlin/statistics/MetricPolicyTest.kt
@@ -60,13 +60,13 @@ @Test fun versionStringValidation() { - val separator = ConcatMetricContainer.SEPARATOR - val container = MetricsContainer() + val separator = "0123" + val container = MetricsContainer(metricValuesSeparator = separator) fun whenAdded(newValue: String, expected: String) { container.report(StringMetrics.MPP_PLATFORMS, newValue) - val currentValue = container.getMetric(StringMetrics.MPP_PLATFORMS)!!.toStringRepresentation() + val currentValue = container.getStringMetricPresentation(StringMetrics.MPP_PLATFORMS)!! assertEquals(expected, currentValue) - val regex = StringMetrics.MPP_PLATFORMS.anonymization.validationRegexp() + val regex = StringMetrics.MPP_PLATFORMS.anonymization.validationRegexp(separator) assertTrue(currentValue.matches(Regex(regex)), "'${currentValue}' should match '${regex}'") } whenAdded("js", "js")
diff --git a/libraries/tools/kotlin-gradle-statistics/src/test/kotlin/org/jetbrains/kotlin/statistics/ModuleChangesCatchingTest.kt b/libraries/tools/kotlin-gradle-statistics/src/test/kotlin/org/jetbrains/kotlin/statistics/ModuleChangesCatchingTest.kt index 60c77e1..589edfb 100644 --- a/libraries/tools/kotlin-gradle-statistics/src/test/kotlin/org/jetbrains/kotlin/statistics/ModuleChangesCatchingTest.kt +++ b/libraries/tools/kotlin-gradle-statistics/src/test/kotlin/org/jetbrains/kotlin/statistics/ModuleChangesCatchingTest.kt
@@ -30,7 +30,7 @@ STRING_METRICS_EXPECTED_VERSION_AND_HASH.first + BOOLEAN_METRICS_EXPECTED_VERSION_AND_HASH.first + NUMERICAL_METRICS_EXPECTED_VERSION_AND_HASH.first, - "8b845ce616f5bdb9b885ededcdfd812d" + "86fb86878cd730955f7685f935bfc152" ) private const val HASH_ALG = "MD5"