[FIR/AA] WIP - Stats for speculative vs. precise symbol provider accesses

^KT-72663
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirModuleWithDependenciesSymbolProvider.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirModuleWithDependenciesSymbolProvider.kt
index 47d0010..6e38b6e 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirModuleWithDependenciesSymbolProvider.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirModuleWithDependenciesSymbolProvider.kt
@@ -5,7 +5,10 @@
 
 package org.jetbrains.kotlin.analysis.low.level.api.fir.providers
 
+import org.jetbrains.kotlin.analysis.api.platform.KaCachedService
+import org.jetbrains.kotlin.analysis.low.level.api.fir.projectStructure.llFirModuleData
 import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSession
+import org.jetbrains.kotlin.analysis.low.level.api.fir.statistics.LLStatisticsService
 import org.jetbrains.kotlin.analysis.low.level.api.fir.stubBased.deserialization.StubBasedFirDeserializedSymbolProvider
 import org.jetbrains.kotlin.analysis.utils.collections.buildSmartList
 import org.jetbrains.kotlin.fir.FirSession
@@ -34,6 +37,11 @@
     val providers: List<FirSymbolProvider>,
     val dependencyProvider: LLFirDependenciesSymbolProvider,
 ) : FirSymbolProvider(session) {
+    @KaCachedService
+    private val symbolProviderStatistics by lazy(LazyThreadSafetyMode.PUBLICATION) {
+        LLStatisticsService.getInstance(session.llFirModuleData.ktModule.project)?.symbolProviders
+    }
+
     /**
      * This symbol names provider is not used directly by [LLFirModuleWithDependenciesSymbolProvider], because in the IDE, Java symbol
      * providers currently cannot provide name sets (see KTIJ-24642). So in most cases, name sets would be `null` anyway.
@@ -53,9 +61,24 @@
         )
     }
 
-    override fun getClassLikeSymbolByClassId(classId: ClassId): FirClassLikeSymbol<*>? =
-        getClassLikeSymbolByClassIdWithoutDependencies(classId)
-            ?: dependencyProvider.getClassLikeSymbolByClassId(classId)
+    override fun getClassLikeSymbolByClassId(classId: ClassId): FirClassLikeSymbol<*>? {
+        val symbol =
+            getClassLikeSymbolByClassIdWithoutDependencies(classId)
+                ?: dependencyProvider.getClassLikeSymbolByClassId(classId)
+
+        symbolProviderStatistics?.let { statistics ->
+            if (symbol != null) {
+                statistics.moduleClassHits.add(1)
+            } else {
+                statistics.moduleClassMisses.add(1)
+                if (classId.isNestedClass) {
+                    statistics.moduleNestedClassMisses.add(1)
+                }
+            }
+        }
+
+        return symbol
+    }
 
     fun getClassLikeSymbolByClassIdWithoutDependencies(classId: ClassId): FirClassLikeSymbol<*>? =
         providers.firstNotNullOfOrNull { it.getClassLikeSymbolByClassId(classId) }
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/statistics/LLStatisticsScopes.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/statistics/LLStatisticsScopes.kt
index 2ad5b36..3d5b42b 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/statistics/LLStatisticsScopes.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/statistics/LLStatisticsScopes.kt
@@ -32,6 +32,19 @@
     }
 
     object SymbolProviders : LLStatisticsScope("$name.symbolProviders") {
+        // The number of uniquely computed "classifier names in package" sets per `KaModule`.
+        // How to calculate: Keep a global "`KaModule` -> package name" map and record a hit when a classifier name set is computed (in any
+        // symbol provider). Every new module/package name combination leads to one count increment.
+        object UniqueClassifierNameSets : LLStatisticsScope("$name.uniqueClassifierNameSets")
+
+        object Module : LLStatisticsScope("$name.module") {
+            // NOTE: These are symbol provider hits themselves, i.e. "does the symbol provider return non-null here?"
+            // Not to be confused with cache hits/misses, which need much clearer naming.
+            object ClassHits : LLStatisticsScope("$name.classHits")
+            object ClassMisses : LLStatisticsScope("$name.classMisses")
+            object NestedClassMisses : LLStatisticsScope("$name.nestedClassMisses")
+        }
+
         object Combined : LLStatisticsScope("$name.combined"), LLCaffeineStatisticsScope {
             object Hits : LLStatisticsScope("$name.hits")
             object Misses : LLStatisticsScope("$name.misses")
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/statistics/domains/LLSymbolProviderStatistics.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/statistics/domains/LLSymbolProviderStatistics.kt
index 12f7c08..80ec226 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/statistics/domains/LLSymbolProviderStatistics.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/statistics/domains/LLSymbolProviderStatistics.kt
@@ -5,6 +5,7 @@
 
 package org.jetbrains.kotlin.analysis.low.level.api.fir.statistics.domains
 
+import io.opentelemetry.api.metrics.LongCounter
 import org.jetbrains.kotlin.analysis.low.level.api.fir.statistics.LLCaffeineStatsCounter
 import org.jetbrains.kotlin.analysis.low.level.api.fir.statistics.LLStatisticsScopes
 import org.jetbrains.kotlin.analysis.low.level.api.fir.statistics.LLStatisticsService
@@ -17,4 +18,8 @@
      * A global [Caffeine stats counter][com.github.benmanes.caffeine.cache.stats.StatsCounter] for combined symbol provider caches.
      */
     val combinedSymbolProviderCacheStatsCounter = LLCaffeineStatsCounter(meter, LLStatisticsScopes.SymbolProviders.Combined)
+
+    val moduleClassHits: LongCounter = meter.counterBuilder(LLStatisticsScopes.SymbolProviders.Module.ClassHits.name).build()
+    val moduleClassMisses: LongCounter = meter.counterBuilder(LLStatisticsScopes.SymbolProviders.Module.ClassMisses.name).build()
+    val moduleNestedClassMisses: LongCounter = meter.counterBuilder(LLStatisticsScopes.SymbolProviders.Module.NestedClassMisses.name).build()
 }