fixup! Support configuration cache and project isolation for FUS statistics
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFusService.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFusService.kt
index f2b32e9..cdd5b1b 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFusService.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFusService.kt
@@ -7,6 +7,7 @@
 
 import org.gradle.api.Project
 import org.gradle.api.Task
+import org.gradle.api.artifacts.component.BuildIdentifier
 import org.gradle.api.logging.Logging
 import org.gradle.api.provider.ListProperty
 import org.gradle.api.provider.Property
@@ -29,6 +30,8 @@
 import org.jetbrains.kotlin.gradle.report.reportingSettings
 import org.jetbrains.kotlin.gradle.tasks.withType
 import org.jetbrains.kotlin.gradle.utils.SingleActionPerProject
+import org.jetbrains.kotlin.gradle.utils.currentBuild
+import org.jetbrains.kotlin.gradle.utils.currentBuildId
 import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics
 import org.jetbrains.kotlin.statistics.metrics.StatisticsValuesConsumer
 import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics
@@ -52,6 +55,7 @@
     interface Parameters : BuildServiceParameters {
         val configurationMetrics: ListProperty<MetricContainer>
         val useBuildFinishFlowAction: Property<Boolean>
+        val buildId: Property<String>
     }
 
     private val fusMetricsConsumer = NonSynchronizedMetricsContainer()
@@ -99,8 +103,9 @@
             // so there is a change that no VariantImplementationFactory will be found
             return project.gradle.sharedServices.registerIfAbsent(serviceName, BuildFusService::class.java) { spec ->
                 KotlinBuildStatsService.getOrCreateInstance(project)
+                val buildId = generateBuildId(project.currentBuildId())
                 KotlinBuildStatsService.applyIfInitialised {
-                    it.recordProjectsEvaluated(project)
+                    it.recordProjectsEvaluated(project, buildId)
                 }
 
                 spec.parameters.configurationMetrics.add(project.provider {
@@ -115,6 +120,7 @@
                     KotlinBuildStatHandler.collectProjectConfigurationTimeMetrics(project)
                 })
                 spec.parameters.useBuildFinishFlowAction.set(GradleVersion.current().baseVersion >= GradleVersion.version("8.1"))
+                spec.parameters.buildId.set(project.provider { generateBuildId(project.currentBuildId()) })
             }.also { buildService ->
                 BuildEventsListenerRegistryHolder.getInstance(project).listenerRegistry.onTaskCompletion(buildService)
                 if (GradleVersion.current().baseVersion >= GradleVersion.version("8.1")) {
@@ -122,6 +128,16 @@
                 }
             }
         }
+        private fun generateBuildId(buildIdentifier: BuildIdentifier): String {
+//            val buildName = if (GradleVersion.current().baseVersion < GradleVersion.version("8.2")) {
+//                buildIdentifier.name
+//            } else {
+//                buildIdentifier.buildPath
+//            }
+//                .replace("\\W+", "")
+//            return "${buildName}_${buildIdentifier.hashCode()}"
+                return buildIdentifier.hashCode().toString()
+        }
     }
 
     override fun onFinish(event: FinishEvent?) {
@@ -146,10 +162,11 @@
         KotlinBuildStatHandler.reportGlobalMetrics(fusMetricsConsumer)
         parameters.configurationMetrics.orElse(emptyList()).get().forEach { it.addToConsumer(fusMetricsConsumer) }
         KotlinBuildStatsService.applyIfInitialised {
-            it.recordBuildFinish(action, buildFailed, fusMetricsConsumer)
+            it.recordBuildFinish(action, buildFailed, fusMetricsConsumer, parameters.buildId.get())
         }
         KotlinBuildStatsService.closeServices()
     }
+
     private fun reportTaskMetrics(taskExecutionResult: TaskExecutionResult, event: TaskFinishEvent) {
         val totalTimeMs = event.result.endTime - event.result.startTime
         val buildMetrics = taskExecutionResult.buildMetrics
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/KotlinBuildStatHandler.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/KotlinBuildStatHandler.kt
index 93d891f..d6bcc26 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/KotlinBuildStatHandler.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/KotlinBuildStatHandler.kt
@@ -268,8 +268,10 @@
         action: String?,
         buildFailed: Boolean,
         metricsContainer: NonSynchronizedMetricsContainer,
+        buildId: String
     ) {
         runSafe("${KotlinBuildStatHandler::class.java}.reportBuildFinish") {
+//            sessionLogger.initTrackingFile(buildId)
             metricsContainer.sendToConsumer(sessionLogger)
             sessionLogger.finishBuildSession(action, buildFailed)
         }
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/KotlinBuildStatsService.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/KotlinBuildStatsService.kt
index 7fe900a..0928397 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/KotlinBuildStatsService.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/KotlinBuildStatsService.kt
@@ -190,9 +190,9 @@
     /**
      * Collects metrics at the end of a build
      */
-    open fun recordBuildFinish(action: String?, buildFailed: Boolean, metricsContainer: NonSynchronizedMetricsContainer) {}
+    open fun recordBuildFinish(action: String?, buildFailed: Boolean, metricsContainer: NonSynchronizedMetricsContainer, buildId: String) {}
 
-    open fun recordProjectsEvaluated(project: Project) {}
+    open fun recordProjectsEvaluated(project: Project, buildId: String? = null) {}
 }
 
 internal class JMXKotlinBuildStatsService(private val mbs: MBeanServer, private val beanName: ObjectName) :
@@ -259,12 +259,13 @@
         return (gradle as? DefaultGradle)?.services?.get(BuildRequestMetaData::class.java)?.startTime
     }
 
-    override fun recordProjectsEvaluated(project: Project) {
+    override fun recordProjectsEvaluated(project: Project, buildId: String?) {
         runSafe("${DefaultKotlinBuildStatsService::class.java}.projectEvaluated") {
             if (!sessionLogger.isBuildSessionStarted()) {
                 sessionLogger.startBuildSession(
                     DaemonReuseCounter.incrementAndGetOrdinal(),
                     gradleBuildStartTime(project.gradle),
+                    buildId
                 )
             }
         }
@@ -302,7 +303,7 @@
         report(StringMetrics.valueOf(name), value, subprojectName, weight)
 
     //only one jmx bean service should report global metrics
-    override fun recordBuildFinish(action: String?, buildFailed: Boolean, metricsContainer: NonSynchronizedMetricsContainer) {
-        KotlinBuildStatHandler().reportBuildFinished(sessionLogger, action, buildFailed, metricsContainer)
+    override fun recordBuildFinish(action: String?, buildFailed: Boolean, metricsContainer: NonSynchronizedMetricsContainer, buildId: String) {
+        KotlinBuildStatHandler().reportBuildFinished(sessionLogger, action, buildFailed, metricsContainer, buildId)
     }
 }
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 b648fee..e301d7c 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
@@ -72,7 +72,7 @@
      * - files with age (current time - last modified) more than maxFileAge should be deleted (if we trust lastModified returned by FS)
      */
     @Synchronized
-    private fun initTrackingFile(buildUid: String?) {
+    internal fun initTrackingFile(buildUid: String?) {
         closeTrackingFile()
 
         val fileName = buildUid ?: profileFileNameFormatter.format(LocalDateTime.now())
@@ -81,7 +81,7 @@
 
     private fun clearOldFiles() {
         // Get list of existing files. Try to create folder if possible, return from function if failed to create folder
-        val fileCandidates = listProfileFiles(statisticsFolder) ?: return
+        val fileCandidates = listProfileFiles(statisticsFolder) ?: if (statisticsFolder.mkdirs()) emptyList() else return
 
         for ((index, file) in fileCandidates.withIndex()) {
             if (index < fileCandidates.size - maxProfileFiles) {
@@ -102,6 +102,7 @@
         try {
             // nanotime could not be used as build start time in nanotime is unknown. As result, the measured duration
             // could be affected by system clock correction
+
             val finishTime = System.currentTimeMillis()
             buildSession?.also {
                 if (it.buildStartedTime != null) {
diff --git a/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/fileloggers/FileRecordLogger.kt b/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/fileloggers/FileRecordLogger.kt
index b5708f4..d5f0c93 100644
--- a/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/fileloggers/FileRecordLogger.kt
+++ b/libraries/tools/kotlin-gradle-statistics/src/main/kotlin/org/jetbrains/kotlin/statistics/fileloggers/FileRecordLogger.kt
@@ -25,9 +25,17 @@
             statisticsFolder.mkdirs()
             val file = File(statisticsFolder, fileName + PROFILE_FILE_NAME_SUFFIX)
 
-            FileOutputStream(file, true).bufferedWriter().use {
-                for (value in metrics) {
-                    it.appendLine(value)
+            FileOutputStream(file, true).use {
+                val channel = it.getChannel()
+                val lockFile = channel.lock()
+                try {
+                    it.bufferedWriter().use { writer ->
+                        for (value in metrics) {
+                            writer.appendLine(value)
+                        }
+                    }
+                } finally {
+                    lockFile.release()
                 }
             }
         } catch (e: IOException) {
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 95a8306..a2a19cb 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
@@ -51,12 +51,15 @@
             val channel = FileChannel.open(Paths.get(file.toURI()), StandardOpenOption.WRITE, StandardOpenOption.READ)
             channel.tryLock() ?: return false
 
+            //Any plugin can create fus-report-file and write into it. The file is ready to read by IDEA only when kotlin build is finished
+            var buildFinished = false
             val inputStream = Channels.newInputStream(channel)
             try {
                 var container = MetricsContainer()
                 // Note: close is called at forEachLine
                 BufferedReader(InputStreamReader(inputStream, ENCODING)).forEachLine { line ->
                     if (BUILD_SESSION_SEPARATOR == line) {
+                        buildFinished = true
                         consumer.invoke(container)
                         container = MetricsContainer()
                     } else {
@@ -96,7 +99,7 @@
             } finally {
                 channel.close()
             }
-            return true
+            return buildFinished
         }
     }