diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/CodeVisionFusCollector.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/CodeVisionFusCollector.kt index 8cc808b0ab8b..6adf345646e6 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/CodeVisionFusCollector.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/CodeVisionFusCollector.kt @@ -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, Language?, Int> = GROUP.registerEvent("vcs.annotation.calculation", + EventFields.IntList( + "histogram"), + EventFields.Language, + EventFields.Size) + + internal val PROVIDER_STORAGE_KEY: Key = Key.create("code.vision.fus.provider.storage") + internal val VCS_ANNOTATION_HISTOGRAM_KEY: Key = Key.create("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 + } } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/CodeVisionFusEditorListener.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/CodeVisionFusEditorListener.kt new file mode 100644 index 000000000000..1ddb874f2fd6 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/CodeVisionFusEditorListener.kt @@ -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, 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, IntArray>, +) \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/CodeVisionPass.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/CodeVisionPass.kt index d078aef2fff2..83361dcf446c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/CodeVisionPass.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/CodeVisionPass.kt @@ -72,10 +72,10 @@ class CodeVisionPass( span.useWithScope { computeWithSpan(tracer, provider.javaClass.simpleName) { val results: List> - //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) } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/FusHistogram.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/FusHistogram.kt new file mode 100644 index 000000000000..c393fa81b644 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/codeVision/FusHistogram.kt @@ -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 +) \ No newline at end of file diff --git a/platform/lang-impl/testSources/com/intellij/codeInsight/hints/FusHistogramTest.kt b/platform/lang-impl/testSources/com/intellij/codeInsight/hints/FusHistogramTest.kt new file mode 100644 index 000000000000..891a74529b7e --- /dev/null +++ b/platform/lang-impl/testSources/com/intellij/codeInsight/hints/FusHistogramTest.kt @@ -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) + } +} \ No newline at end of file diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index d844ab0cf895..5bc41163208e 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -1129,6 +1129,7 @@ + diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/annotate/AnnotationsPreloader.kt b/platform/vcs-impl/src/com/intellij/openapi/vcs/annotate/AnnotationsPreloader.kt index 31b9ce742a6a..a445c91e5d35 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/annotate/AnnotationsPreloader.kt +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/annotate/AnnotationsPreloader.kt @@ -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 {