[fus, code vision] aggregate code vision events - create hists and send them on closing of editor IDEA-331947

GitOrigin-RevId: f0d85f50dbb6c45f828e7912faf7167c9906a9f4
This commit is contained in:
Roman Ivanov
2023-09-25 11:44:00 +00:00
committed by intellij-monorepo-bot
parent 0c27b276dc
commit 4ea6027256
7 changed files with 234 additions and 17 deletions
@@ -3,22 +3,78 @@ package com.intellij.codeInsight.hints.codeVision
import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.eventLog.events.EventId3
import com.intellij.internal.statistic.eventLog.events.IntListEventField
import com.intellij.internal.statistic.eventLog.events.VarargEventId
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
import org.jetbrains.annotations.ApiStatus
import com.intellij.lang.Language
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus.Internal
@ApiStatus.Internal
@Internal
object CodeVisionFusCollector : CounterUsagesCollector() {
private val GROUP = EventLogGroup("daemon.code.vision", 2)
// TODO make some kind of throttling
val CODE_VISION_FINISHED = GROUP.registerEvent(
"finished",
EventFields.DurationMs,
EventFields.Class("provider_class"),
EventFields.Language
)
val ANNOTATION_LOADED = GROUP.registerEvent("vcs.annotation.loaded", EventFields.DurationMs)
private val GROUP = EventLogGroup("daemon.code.vision", 3)
override fun getGroup(): EventLogGroup = GROUP
val HISTOGRAM_FIELD: IntListEventField = EventFields.IntList("histogram")
val PROVIDER_CLASS_FIELD = EventFields.Class("provider_class")
val HISTOGRAM_BUCKETS = longArrayOf(0, 100, 500, 1000, 5000, 10000, 30000, 100000)
/**
* histogram buckets are mentioned in [HISTOGRAM_BUCKETS]
*/
val CODE_VISION_DURATION_HISTOGRAM: VarargEventId = GROUP.registerVarargEvent("code.vision.duration",
HISTOGRAM_FIELD,
EventFields.Language,
EventFields.Size,
PROVIDER_CLASS_FIELD)
val VCS_ANNOTATION_CALCULATION_DURATION_HISTOGRAM: EventId3<List<Int>, Language?, Int> = GROUP.registerEvent("vcs.annotation.calculation",
EventFields.IntList(
"histogram"),
EventFields.Language,
EventFields.Size)
internal val PROVIDER_STORAGE_KEY: Key<CodeVisionFusProviderStorage> = Key.create<CodeVisionFusProviderStorage>("code.vision.fus.provider.storage")
internal val VCS_ANNOTATION_HISTOGRAM_KEY: Key<FusHistogramBuilder> = Key.create<FusHistogramBuilder>("vcs.annotation.histogram")
internal fun reportCodeVisionProviderDuration(editor: Editor, language: Language, durationMs: Long, providerClass: Class<*>) {
val fusProviderStorage = getProviderStorage(editor, language)
fusProviderStorage.logProviderCollectionDuration(providerClass, durationMs)
}
private fun getProviderStorage(editor: Editor, language: Language) : CodeVisionFusProviderStorage {
val providerStorage: CodeVisionFusProviderStorage? = editor.getUserData(PROVIDER_STORAGE_KEY)
// not race free - but it is not a problem. We are OK if we lose data in this case
if (providerStorage == null) {
val newStorage = CodeVisionFusProviderStorage(language)
editor.putUserData(PROVIDER_STORAGE_KEY, newStorage)
return newStorage
}
return providerStorage
}
@Internal
fun reportVcsAnnotationDuration(psiFile: PsiFile, durationMs: Long) {
val histogramBuilder = getHistogramBuilder(psiFile)
histogramBuilder.addValue(durationMs)
}
private fun getHistogramBuilder(psiFile: PsiFile) : FusHistogramBuilder {
val vcsAnnotationHistogramBuilder: FusHistogramBuilder? = VCS_ANNOTATION_HISTOGRAM_KEY.get(psiFile)
// not race free - but it is not a problem. We are OK if we lose data in this case
if (vcsAnnotationHistogramBuilder == null) {
val newHistogram = FusHistogramBuilder(HISTOGRAM_BUCKETS)
psiFile.putUserData(VCS_ANNOTATION_HISTOGRAM_KEY, newHistogram)
return newHistogram
}
return vcsAnnotationHistogramBuilder
}
}
@@ -0,0 +1,74 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.hints.codeVision
import com.intellij.codeInsight.hints.codeVision.CodeVisionFusCollector.HISTOGRAM_FIELD
import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.eventLog.events.EventPair
import com.intellij.internal.statistic.utils.StatisticsUtil
import com.intellij.lang.Language
import com.intellij.openapi.editor.event.EditorFactoryEvent
import com.intellij.openapi.editor.event.EditorFactoryListener
import com.intellij.psi.PsiDocumentManager
import com.intellij.util.concurrency.AppExecutorUtil
import kotlinx.coroutines.Runnable
import java.util.concurrent.ConcurrentHashMap
class CodeVisionFusEditorListener : EditorFactoryListener {
override fun editorReleased(event: EditorFactoryEvent) {
val editor = event.editor
val project = editor.project ?: return
val histogramBuilder = editor.getUserData(CodeVisionFusCollector.PROVIDER_STORAGE_KEY)
if (histogramBuilder != null) {
val fusData: CodeVisionFusData = histogramBuilder.flush()
val document = editor.document
val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document)
val vcsAnnotationHistogram: FusHistogram? = psiFile?.getUserData(CodeVisionFusCollector.VCS_ANNOTATION_HISTOGRAM_KEY)?.build()
// we remove it because editor may be closed, but the file remains. In order not to report its data several times, we need to remove
psiFile?.putUserData(CodeVisionFusCollector.VCS_ANNOTATION_HISTOGRAM_KEY, null)
val textLength = StatisticsUtil.roundToPowerOfTwo(document.textLength)
AppExecutorUtil.getAppExecutorService().submit(Runnable {
if (vcsAnnotationHistogram != null) {
CodeVisionFusCollector.VCS_ANNOTATION_CALCULATION_DURATION_HISTOGRAM.log(project, vcsAnnotationHistogram.buckets.toList(), psiFile.language, textLength)
}
val language = fusData.language
for ((providerClass, histogram) in fusData.providerClassToHistogram) {
CodeVisionFusCollector.CODE_VISION_DURATION_HISTOGRAM.log(project, listOf(
EventPair(HISTOGRAM_FIELD, histogram.toList()),
EventPair(EventFields.Language, language),
EventPair(EventFields.Size, textLength),
EventPair(CodeVisionFusCollector.PROVIDER_CLASS_FIELD, providerClass)
))
}
})
}
}
}
internal class CodeVisionFusProviderStorage(val language: Language) {
private val providerClassToHistogram: ConcurrentHashMap<Class<*>, FusHistogramBuilder> = ConcurrentHashMap()
fun logProviderCollectionDuration(providerClass: Class<*>, durationMs: Long) {
val fusHistogramBuilder = providerClassToHistogram.computeIfAbsent(providerClass,
{ FusHistogramBuilder(CodeVisionFusCollector.HISTOGRAM_BUCKETS) })
synchronized(fusHistogramBuilder) {
fusHistogramBuilder.addValue(durationMs)
}
}
internal fun flush(): CodeVisionFusData {
val providerClassToHistogram = providerClassToHistogram.mapValues {
synchronized(it.value) {
it.value.build().buckets
}
}
return CodeVisionFusData(language, providerClassToHistogram)
}
}
internal class CodeVisionFusData(
val language: Language,
val providerClassToHistogram: Map<Class<*>, IntArray>,
)
@@ -72,10 +72,10 @@ class CodeVisionPass(
span.useWithScope {
computeWithSpan(tracer, provider.javaClass.simpleName) {
val results: List<Pair<TextRange, CodeVisionEntry>>
//val duration = measureTimeMillis {
val duration = measureTimeMillis {
results = provider.computeForEditor(editor, file)
//}
//CodeVisionFusCollector.CODE_VISION_FINISHED.log(file.project, duration, provider::class.java, file.language)
}
CodeVisionFusCollector.reportCodeVisionProviderDuration(editor, file.language, duration, provider::class.java)
providerIdToLenses[provider.id] = DaemonBoundCodeVisionCacheService.CodeVisionWithStamp(results,
modificationTracker.modificationCount)
}
@@ -0,0 +1,33 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.hints.codeVision
import org.jetbrains.annotations.ApiStatus.Internal
@Internal
class FusHistogramBuilder(private val bucketValues: LongArray) {
val buckets: IntArray = IntArray(bucketValues.size)
fun addValue(value: Long) {
var bucketIndex = bucketValues.indexOfFirst { it > value }
if (bucketIndex != -1) {
// We found a bucket that needs to be incremented
if (bucketIndex > 0) {
// The value falls into the previous bucket
bucketIndex--
}
} else {
// The value is greater than all buckets, it falls into the last one
bucketIndex = bucketValues.size - 1
}
buckets[bucketIndex]++
}
fun build(): FusHistogram {
return FusHistogram(buckets)
}
}
@Internal
class FusHistogram(
val buckets: IntArray
)
@@ -0,0 +1,40 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.hints
import com.intellij.codeInsight.hints.codeVision.FusHistogramBuilder
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
class FusHistogramTest {
@Test
fun test() {
testHist_0_10_50(1, 0, 0) {
addValue(1)
}
testHist_0_10_50(0, 1, 0) {
addValue(14)
}
testHist_0_10_50(0, 0, 1) {
addValue(50)
}
testHist_0_10_50(0, 0, 1) {
addValue(100)
}
testHist_0_10_50(1, 2, 1) {
addValue(100)
addValue(15)
addValue(5)
addValue(12)
}
// this situation (out of range) should not happen, it is UB
testHist_0_10_50(1, 0, 0) {
addValue(-10)
}
}
fun testHist_0_10_50(vararg buckets: Int, b: FusHistogramBuilder.() -> Unit) {
val builder = FusHistogramBuilder(longArrayOf(0, 10, 50))
b(builder)
Assertions.assertArrayEquals(buckets, builder.build().buckets)
}
}
@@ -1129,6 +1129,7 @@
<statistics.counterUsagesCollector implementationClass="com.intellij.analysis.problemsView.toolWindow.ProblemsViewStatsCollector"/>
<statistics.counterUsagesCollector implementationClass="com.intellij.ide.util.FileStructurePopupTimeTracker"/>
<statistics.counterUsagesCollector implementationClass="com.intellij.codeInsight.hints.codeVision.CodeVisionFusCollector"/>
<editorFactoryListener implementation="com.intellij.codeInsight.hints.codeVision.CodeVisionFusEditorListener" />
<projectViewPane implementation="com.intellij.ide.scopeView.ScopeViewPane"/>
@@ -9,6 +9,7 @@ import com.intellij.codeInsight.hints.codeVision.CodeVisionFusCollector
import com.intellij.codeInsight.hints.isCodeAuthorInlayHintsEnabled
import com.intellij.codeInsight.hints.refreshCodeAuthorInlayHints
import com.intellij.ide.PowerSaveMode
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.Service.Level
@@ -28,9 +29,12 @@ import com.intellij.openapi.vcs.VcsException
import com.intellij.openapi.vcs.VcsListener
import com.intellij.openapi.vcs.changes.ChangeListManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import com.intellij.util.concurrency.AppExecutorUtil
import com.intellij.util.ui.update.DisposableUpdate
import com.intellij.util.ui.update.MergingUpdateQueue
import com.intellij.vcs.CacheableAnnotationProvider
import kotlinx.coroutines.Runnable
@Service(Level.PROJECT)
internal class AnnotationsPreloader(private val project: Project) {
@@ -53,7 +57,16 @@ internal class AnnotationsPreloader(private val project: Project) {
annotationProvider.populateCache(file)
val durationMs = System.currentTimeMillis() - start
//CodeVisionFusCollector.ANNOTATION_LOADED.log(project, durationMs)
AppExecutorUtil.getAppExecutorService().submit(Runnable {
ApplicationManager.getApplication().runReadAction {
val psiFile = PsiManager.getInstance(project).findFile(file)
if (psiFile != null) {
CodeVisionFusCollector.reportVcsAnnotationDuration(psiFile, durationMs)
}
}
})
LOG.debug { "Preloaded VCS annotations for ${file.name} in $durationMs ms" }
runInEdt {