JBAI-5408 Release ML Imports Ranking in 2024.3

[ml tools] Fix build issue

[ml tools] Fix build issue

[ml tools] Fix build issue

[ml tools] Replace task object with service

[ml tools] Fix rebase issues

Add session duration metric

[ml imports ranking] Log fully only during EAP

[ml imports ranking] Set up FUS logs

[ml tools] Add dependency on ml api library


Merge-request: IJ-MR-142857
Merged-by: Gleb Marin <Gleb.Marin@jetbrains.com>

GitOrigin-RevId: 69be00b4b8f38ec71208c3b17cd6566a34508859
This commit is contained in:
Gleb Marin
2024-08-20 06:16:36 +00:00
committed by intellij-monorepo-bot
parent 786deec129
commit dc3c5730ea
21 changed files with 547 additions and 28 deletions
+6 -6
View File
@@ -1,20 +1,20 @@
<component name="libraryTable">
<library name="jetbrains.mlapi.extension" type="repository">
<properties include-transitive-deps="false" maven-id="com.jetbrains.mlapi:extension:32">
<properties include-transitive-deps="false" maven-id="com.jetbrains.mlapi:extension:34">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/extension/32/extension-32.jar">
<sha256sum>39e57d44df6db53d7336ce8b5ab3e6486b3f0beef16e7c792cbe7a0cf3e4d50b</sha256sum>
<artifact url="file://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/extension/34/extension-34.jar">
<sha256sum>1d18aa7a0d891cab230ddb67e3d1174830e1472c7373c37296c23a3905040a7d</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/extension/32/extension-32.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/extension/34/extension-34.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/extension/32/extension-32-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/extension/34/extension-34-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/extension/32/extension-32-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/extension/34/extension-34-sources.jar!/" />
</SOURCES>
</library>
</component>
+6 -6
View File
@@ -1,20 +1,20 @@
<component name="libraryTable">
<library name="jetbrains.mlapi.usage" type="repository">
<properties include-transitive-deps="false" maven-id="com.jetbrains.mlapi:usage:32">
<properties include-transitive-deps="false" maven-id="com.jetbrains.mlapi:usage:34">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/usage/32/usage-32.jar">
<sha256sum>714206436b4e8d619d322cba1db73c8e0908ccaf292fa3278a2c85b64512fe8a</sha256sum>
<artifact url="file://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/usage/34/usage-34.jar">
<sha256sum>b28271a408b89f4a9e00e4a849543b5e9206333472ee379027d969e5d1b42e6f</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/usage/32/usage-32.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/usage/34/usage-34.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/usage/32/usage-32-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/usage/34/usage-34-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/usage/32/usage-32-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/mlapi/usage/34/usage-34-sources.jar!/" />
</SOURCES>
</library>
</component>
@@ -19,7 +19,7 @@ class IJPlatform : com.jetbrains.ml.platform.MLApiPlatform(
override val taskListeners: Map<String, List<com.jetbrains.ml.monitoring.MLTaskListenerTyped<*, *>>>
get() = KeyedMessagingProvider.collect(MLTaskListenerTyped.TOPIC)
override fun addTaskListener(taskId: String, taskListener: com.jetbrains.ml.monitoring.MLTaskListenerTyped<*, *>): ExtensionController {
override fun addTaskListener(taskId: String, taskListener: com.jetbrains.ml.monitoring.MLTaskListenerTyped<*, *>): com.jetbrains.ml.platform.MLApiPlatform.ExtensionController {
val connection = application.messageBus.connect()
fun <M : com.jetbrains.ml.model.MLModel<P>, P : Any> capturingType(taskListenerTyped: com.jetbrains.ml.monitoring.MLTaskListenerTyped<M, P>) {
@@ -8,7 +8,6 @@ import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.platform.ml.feature.Feature
import com.intellij.platform.ml.feature.FeatureDeclaration
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.TestOnly
import java.awt.event.KeyAdapter
@@ -18,6 +17,9 @@ import kotlin.math.pow
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
import com.intellij.platform.ml.feature.FeatureDeclaration as OldFeatureDeclaration
import com.jetbrains.ml.Feature as NewFeature
import com.jetbrains.ml.FeatureDeclaration as NewFeatureDeclaration
@ApiStatus.Internal
@Service
@@ -49,6 +51,12 @@ class TypingSpeedTracker {
}
}
fun getTypingSpeedNewEventPairs(): Collection<Pair<EventPair<*>, NewFeature>> = DECAY_DURATIONS_NEW.mapNotNull { (decayDuration, eventFieldAndFeature) ->
typingSpeeds[decayDuration]?.let {
(eventFieldAndFeature.first with it) to (eventFieldAndFeature.second with it)
}
}
@TestOnly
fun getTypingSpeed(decayDuration: Duration): Float? = typingSpeeds[decayDuration]
@@ -73,10 +81,14 @@ class TypingSpeedTracker {
}
companion object {
private val DECAY_DURATIONS = listOf(1, 2, 5, 30).associate { it.seconds to Pair(EventFields.Float("typing_speed_${it}s"), FeatureDeclaration.float("typing_speed_${it}s").nullable()) }
private val DECAY_DURATIONS = listOf(1, 2, 5, 30)
.associate { it.seconds to Pair(EventFields.Float("typing_speed_${it}s"), OldFeatureDeclaration.float("typing_speed_${it}s").nullable()) }
private val DECAY_DURATIONS_NEW = listOf(1, 2, 5, 30)
.associate { it.seconds to Pair(EventFields.Float("typing_speed_${it}s"), NewFeatureDeclaration.float("typing_speed_${it}s").nullable()) }
fun getInstance(): TypingSpeedTracker = service()
fun getEventFields(): Array<EventField<*>> = DECAY_DURATIONS.values.map { it.first }.toTypedArray()
fun getFeatures(): Set<FeatureDeclaration<*>> = DECAY_DURATIONS.values.map { it.second }.toSet()
fun getFeatures(): Set<OldFeatureDeclaration<*>> = DECAY_DURATIONS.values.map { it.second }.toSet()
fun getFeaturesNew(): List<NewFeatureDeclaration<*>> = DECAY_DURATIONS_NEW.values.map { it.second }
}
}
@@ -0,0 +1,26 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.inline.completion.ml
import com.intellij.codeInsight.inline.completion.logs.TypingSpeedTracker
import com.jetbrains.ml.*
internal class TypingSpeedFeatureProvider : FeatureProvider(MLUnitTyping) {
object Features {
val TIME_SINCE_LAST_TYPING = FeatureDeclaration.long("time_since_last_typing").nullable()
}
override val featureComputationPolicy = FeatureComputationPolicy(true, true)
override suspend fun computeFeatures(units: MLUnitsMap, usefulFeaturesFilter: FeatureFilter): List<Feature> = buildList {
with(units[MLUnitTyping]) {
val timeSinceLastTyping = getTimeSinceLastTyping()
if (timeSinceLastTyping != null) {
add(Features.TIME_SINCE_LAST_TYPING.with(timeSinceLastTyping))
addAll(getTypingSpeedNewEventPairs().map { it.second })
}
}
}
override val featureDeclarations = TypingSpeedTracker.getFeaturesNew() + Features.TIME_SINCE_LAST_TYPING
}
@@ -6,6 +6,7 @@ import com.intellij.lang.Language
import com.intellij.openapi.editor.Editor
import com.intellij.platform.ml.Tier
import com.intellij.psi.PsiFile
import com.jetbrains.ml.MLUnit
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
@@ -14,6 +15,12 @@ object TierTyping : Tier<TypingSpeedTracker>()
@ApiStatus.Internal
object TierCaretLocation : Tier<CaretLocation>()
@ApiStatus.Internal
object MLUnitTyping : MLUnit<TypingSpeedTracker>("typing_speed_tracker")
@ApiStatus.Internal
object MLUnitCaretLocation : MLUnit<CaretLocation>("caret_location")
@ApiStatus.Internal
data class CaretLocation(
val psiFile: PsiFile,
@@ -1692,6 +1692,7 @@
<!-- ML API -->
<platform.ml.descriptor implementation="com.intellij.codeInsight.inline.completion.ml.TypingFeatures"/>
<platform.ml.environmentExtender implementation="com.intellij.codeInsight.inline.completion.ml.TypingSpeedProvider"/>
<platform.ml.featureProvider implementation="com.intellij.codeInsight.inline.completion.ml.TypingSpeedFeatureProvider"/>
<!-- Presentation Assistant -->
<applicationService serviceImplementation="com.intellij.platform.ide.impl.presentationAssistant.PresentationAssistant"/>
@@ -508,6 +508,7 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
<statistics.counterUsagesCollector implementationClass="com.jetbrains.python.run.runAnything.PyRunAnythingCollector"/>
<statistics.counterUsagesCollector implementationClass="com.jetbrains.python.debugger.statistics.PyDataViewerCollector"/>
<statistics.counterUsagesCollector implementationClass="com.jetbrains.python.sdk.installer.BinaryInstallerUsagesCollector"/>
<statistics.counterUsagesCollector implementationClass="com.jetbrains.python.codeInsight.imports.mlapi.PyCharmImportsRankingLogs"/>
<!-- Code-insight IDE bridge -->
<applicationService serviceInterface="com.jetbrains.python.PythonRuntimeService"
@@ -630,6 +631,12 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
description="Show type hints for all parameters in parameter info window"/>
<registryKey key="python.unified.interpreter.configuration" defaultValue="true"
description="Use the same UI to configure interpreters in IDE widget and New Project Wizard"/>
<!-- Machine Learning -->
<registryKey
key="quickfix.ranking.ml"
defaultValue="[IN_EXPERIMENT*|ENABLED|DISABLED]"
description="Enable ML ranking in quick fix for missing imports"/>
</extensions>
<extensionPoints>
@@ -38,5 +38,6 @@
<orderEntry type="module" module-name="intellij.platform.backend.workspace" />
<orderEntry type="module" module-name="intellij.python.parser" />
<orderEntry type="module" module-name="intellij.python.syntax.core" exported="" />
<orderEntry type="module" module-name="intellij.platform.ml" />
</component>
</module>
@@ -30,6 +30,12 @@
<projectService serviceInterface="com.jetbrains.python.debugger.PySignatureCacheManager"
serviceImplementation="com.jetbrains.python.debugger.PySignatureCacheManagerImpl"/>
<!-- ML API -->
<platform.ml.featureProvider implementation="com.jetbrains.python.codeInsight.imports.mlapi.features.CandidatesListFeatures"/>
<platform.ml.featureProvider implementation="com.jetbrains.python.codeInsight.imports.mlapi.features.ImportCandidateRelativeFeatures"/>
<platform.ml.featureProvider implementation="com.jetbrains.python.codeInsight.imports.mlapi.features.PrimitiveImportFeatures"/>
<platform.ml.featureProvider implementation="com.jetbrains.python.codeInsight.imports.mlapi.features.PsiStructureFeatures"/>
<stubIndex implementation="com.jetbrains.python.psi.stubs.PyClassNameIndex"/>
<stubIndex implementation="com.jetbrains.python.psi.stubs.PyClassNameIndexInsensitive"/>
<stubIndex implementation="com.jetbrains.python.psi.stubs.PyFunctionNameIndex"/>
@@ -0,0 +1,24 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.codeInsight.imports.mlapi.features
import com.jetbrains.ml.*
import com.jetbrains.python.codeInsight.imports.mlapi.MLUnitImportCandidatesList
class CandidatesListFeatures : FeatureProvider(MLUnitImportCandidatesList) {
object Features {
val LENGTH = FeatureDeclaration.int("n_candidates") {
"The amount of import candidates"
}
val HIGHEST_OLD_RELEVANCE = FeatureDeclaration.int("highest_old_relevance") {
"The highest heuristic-based relevance com.jetbrains.python.codeInsight.completion.PyCompletionUtilsKt.computeCompletionWeight"
}.nullable()
}
override val featureDeclarations = extractFieldsAsFeatureDeclarations(Features)
override suspend fun computeFeatures(units: MLUnitsMap, usefulFeaturesFilter: FeatureFilter) = buildList<Feature> {
val candidates = units[MLUnitImportCandidatesList]
add(Features.LENGTH with candidates.size)
add(Features.HIGHEST_OLD_RELEVANCE with candidates.maxOfOrNull { it.relevance })
}
}
@@ -0,0 +1,27 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.codeInsight.imports.mlapi.features
import com.jetbrains.ml.*
import com.jetbrains.python.codeInsight.imports.mlapi.MLUnitImportCandidate
import com.jetbrains.python.codeInsight.imports.mlapi.MLUnitImportCandidatesList
class ImportCandidateRelativeFeatures : FeatureProvider(MLUnitImportCandidate) {
object Features {
val RELATIVE_POSITION = FeatureDeclaration.int("original_position") {
"The import's position without ML ranking"
}
}
override val unitSight = setOf(
MLUnitImportCandidatesList
)
override val featureDeclarations = extractFieldsAsFeatureDeclarations(Features)
override suspend fun computeFeatures(units: MLUnitsMap, usefulFeaturesFilter: FeatureFilter) = buildList<Feature> {
val candidate = units[MLUnitImportCandidate]
val allCandidates = units[MLUnitImportCandidatesList]
add(Features.RELATIVE_POSITION with allCandidates.indexOf(candidate))
}
}
@@ -0,0 +1,29 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.codeInsight.imports.mlapi.features
import com.jetbrains.ml.*
import com.jetbrains.python.codeInsight.imports.mlapi.MLUnitImportCandidate
class PrimitiveImportFeatures : FeatureProvider(MLUnitImportCandidate) {
object Features {
val RELEVANCE = FeatureDeclaration.int("old_relevance") {
"""
Heuristic-based relevance computed in com.jetbrains.python.codeInsight.completion.PyCompletionUtilsKt.computeCompletionWeight
""".trimIndent()
}
val COMPONENT_COUNT = FeatureDeclaration.int("number_of_dots") {
"The amount of components in the import statement"
}.nullable()
}
override val featureDeclarations = extractFieldsAsFeatureDeclarations(Features)
override suspend fun computeFeatures(units: MLUnitsMap, usefulFeaturesFilter: FeatureFilter) = buildList {
val importCandidate = units[MLUnitImportCandidate]
add(Features.RELEVANCE with importCandidate.relevance)
add(Features.COMPONENT_COUNT with importCandidate.path?.componentCount)
}
}
@@ -0,0 +1,33 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.codeInsight.imports.mlapi.features
import com.intellij.openapi.application.readAction
import com.intellij.psi.PsiElement
import com.jetbrains.ml.*
import com.jetbrains.python.codeInsight.imports.ImportCandidateHolder
import com.jetbrains.python.codeInsight.imports.mlapi.MLUnitImportCandidate
class PsiStructureFeatures : FeatureProvider(MLUnitImportCandidate) {
object Features {
val PSI_CLASS = FeatureDeclaration.aClass("importable_class") { "PSI class of the imported element" }.nullable()
val PSI_PARENT = (1..4).map { i -> FeatureDeclaration.aClass("psi_parent_$i") { "PSI parent #$i" }.nullable() }
}
override val featureDeclarations = extractFieldsAsFeatureDeclarations(Features)
override suspend fun computeFeatures(units: MLUnitsMap, usefulFeaturesFilter: FeatureFilter) = buildList {
val importCandidate: ImportCandidateHolder = units[MLUnitImportCandidate]
readAction {
add(Features.PSI_CLASS with (importCandidate.importable?.javaClass))
Features.PSI_PARENT.withIndex().forEach { (i, featureDeclaration) ->
var parent: PsiElement? = importCandidate.importable
repeat(i) {
parent = parent?.parent
}
add(featureDeclaration with parent?.javaClass)
}
}
}
}
@@ -0,0 +1,9 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.codeInsight.imports.mlapi
import com.jetbrains.ml.MLUnit
import com.jetbrains.python.codeInsight.imports.ImportCandidateHolder
val MLUnitImportCandidate = MLUnit<ImportCandidateHolder>("import_candidate")
val MLUnitImportCandidatesList = MLUnit<List<ImportCandidateHolder>>("import_candidates_list")
@@ -13,6 +13,7 @@ import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.Consumer;
import com.jetbrains.python.PyPsiBundle;
import kotlin.Unit;
import org.jetbrains.concurrency.AsyncPromise;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.concurrency.Promises;
@@ -21,6 +22,9 @@ import javax.swing.*;
import java.awt.*;
import java.util.List;
import static com.jetbrains.python.codeInsight.imports.mlapi.MlImplementationKt.launchMLRanking;
public final class PyImportChooser implements ImportChooser {
@Override
@@ -28,20 +32,27 @@ public final class PyImportChooser implements ImportChooser {
if (ApplicationManager.getApplication().isUnitTestMode()) {
return Promises.resolvedPromise(sources.get(0));
}
AsyncPromise<ImportCandidateHolder> result = new AsyncPromise<>();
// GUI part
DataManager.getInstance().getDataContextFromFocus().doWhenDone((Consumer<DataContext>)dataContext -> JBPopupFactory.getInstance()
.createPopupChooserBuilder(sources)
.setRenderer(new CellRenderer())
.setTitle(useQualifiedImport ? PyPsiBundle.message("ACT.qualify.with.module") : PyPsiBundle.message("ACT.from.some.module.import"))
.setItemChosenCallback(item -> {
result.setResult(item);
})
.setNamerForFiltering(o -> o.getPresentableText())
.createPopup()
.showInBestPositionFor(dataContext));
launchMLRanking(sources, (mlRanking) -> {
// GUI part
DataManager.getInstance().getDataContextFromFocus().doWhenDone((Consumer<DataContext>)dataContext -> JBPopupFactory.getInstance()
.createPopupChooserBuilder(mlRanking.getOrder())
.setRenderer(new CellRenderer())
.setTitle(useQualifiedImport ? PyPsiBundle.message("ACT.qualify.with.module") : PyPsiBundle.message("ACT.from.some.module.import"))
.setItemChosenCallback(item -> {
result.setResult(item);
mlRanking.submitSelectedItem(item);
})
.setCancelCallback(() -> {
mlRanking.submitPopUpClosed();
return true;
})
.setNamerForFiltering(o -> o.getPresentableText())
.createPopup()
.showInBestPositionFor(dataContext));
return Unit.INSTANCE;
});
return result;
}
@@ -0,0 +1,41 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.codeInsight.imports.mlapi
import com.intellij.internal.statistic.eventLog.EventLogConfiguration
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.util.registry.Registry
import com.intellij.platform.ml.impl.logs.MLEventLoggerProvider.Companion.ML_RECORDER_ID
@Service
internal class FinalImportRankingStatusService {
private val REGISTRY_KEY = "quickfix.ranking.ml"
enum class RegistryOption {
IN_EXPERIMENT,
ENABLED,
DISABLED
}
private val bucket: Int
by lazy { service<EventLogConfiguration>().getOrCreate(ML_RECORDER_ID).bucket }
private val mlEnabledOnBucket: Boolean
by lazy { bucket % 2 == 0 }
private fun getRegistryOption(): RegistryOption = RegistryOption.valueOf(requireNotNull(Registry.get(REGISTRY_KEY).selectedOption) { "Registry key $REGISTRY_KEY can't be empty" })
val status: FinalImportRankingStatus
get() {
return when (getRegistryOption()) {
RegistryOption.IN_EXPERIMENT -> FinalImportRankingStatus(mlEnabledOnBucket, true)
RegistryOption.ENABLED -> FinalImportRankingStatus(true, false)
RegistryOption.DISABLED -> FinalImportRankingStatus(false, false)
}
}
}
internal class FinalImportRankingStatus(
val mlEnabled: Boolean,
val mlStatusCorrespondsToBucket: Boolean,
)
@@ -0,0 +1,38 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.codeInsight.imports.mlapi
import com.jetbrains.ml.analysis.MLTaskFeedback
import com.jetbrains.ml.analysis.MLTaskFeedbackField
import com.jetbrains.ml.analysis.MLUnitFeedbackField
import com.jetbrains.ml.logs.schema.*
// suspendable analysis -- written after the imports were ranked
internal val MLFieldCorrectElement = MLUnitFeedbackField(
unit = MLUnitImportCandidate,
field = BooleanEventField("is_correct") { "The candidate was chosen" },
throwOnTimeout = false
)
internal object MLFeedbackCorrectElementPosition : MLTaskFeedback() {
val CANCELLED = BooleanEventField("selection_cancelled") { "No item has been selected" }
val SELECTED_POSITION = IntEventField("selected_position") { "The position of the selected import statement" }
val INVALID_SELECTED_ITEM = BooleanEventField("invalid_selected_item") { "The selected item hasn't been found in the initial list" }
override val feedbackDeclaration = listOf(CANCELLED, SELECTED_POSITION, INVALID_SELECTED_ITEM)
}
internal val ML_FEEDBACK_TIME_MS_TO_DISPLAY = MLTaskFeedbackField(
field = LongEventField("time_ms_before_displayed") { "Duration from the quickfix start until when the imports were displayed" },
)
internal val ML_FEEDBACK_TIME_MS_BEFORE_CLOSED = MLTaskFeedbackField(
field = LongEventField("time_ms_before_closed") { "Duration from the quickfix start until the pop-up was closed" },
)
// runtime analysis -- written during the imports are ranked
internal val ML_LOGGING_STATE: EnumEventField<LoggingOption> = EnumEventField.of<LoggingOption>("ml_logging_state", { "State of the ML session logging" })
internal val ML_ENABLED = BooleanEventField("ml_enabled") { "Machine Learning ranking is enabled" }
internal val ML_STATUS_CORRESPONDS_TO_BUCKET = BooleanEventField("ml_status_corresponds_to_bucket") { "Field 'ml_enabled' corresponds to the ML bucket % 2" }
@@ -0,0 +1,136 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.codeInsight.imports.mlapi
import com.intellij.codeInsight.inline.completion.logs.TypingSpeedTracker
import com.intellij.codeInsight.inline.completion.ml.MLUnitTyping
import com.intellij.openapi.application.writeAction
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.util.application
import com.jetbrains.ml.MockMLModel
import com.jetbrains.ml.platform.MLApiTaskExecutor
import com.jetbrains.ml.session.MLSession
import com.jetbrains.python.codeInsight.imports.ImportCandidateHolder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.milliseconds
@Service(Service.Level.APP)
private class MLApiComputations(
val coroutineScope: CoroutineScope,
)
internal sealed class FinalCandidatesRanker(
protected val mlSession: MLSession<MockMLModel, Unit>,
protected val timestampStarted: Long,
) {
abstract fun launchMLRanking(initialCandidatesOrder: MutableList<out ImportCandidateHolder>, displayResult: (RateableRankingResult) -> Unit)
abstract val mlEnabled: Boolean
}
private class ExperimentalMLRanker(
private val coroutineScope: CoroutineScope,
mlSession: MLSession<MockMLModel, Unit>, timestampStarted: Long,
) : FinalCandidatesRanker(mlSession, timestampStarted) {
override val mlEnabled = true
override fun launchMLRanking(initialCandidatesOrder: MutableList<out ImportCandidateHolder>, displayResult: (RateableRankingResult) -> Unit) {
coroutineScope.launch(Dispatchers.Default) {
val mlTreeRoot = mlSession.buildRoot(
MLUnitImportCandidatesList with initialCandidatesOrder,
MLUnitTyping with TypingSpeedTracker.getInstance(),
).computingFeatures { computeAllFeaturesNow() }
for (candidate in initialCandidatesOrder) {
mlTreeRoot
.onlyLog(MLUnitImportCandidate with candidate)
.computingFeatures { computeAllFeaturesNow() }
}
mlSession.finish()
writeAction {
displayResult(RateableRankingResult(mlSession, initialCandidatesOrder, timestampStarted))
}
}
}
}
private class InitialOrderKeepingRanker(
mlSession: MLSession<MockMLModel, Unit>, timestampStarted: Long,
) : FinalCandidatesRanker(mlSession, timestampStarted) {
override val mlEnabled = false
override fun launchMLRanking(initialCandidatesOrder: MutableList<out ImportCandidateHolder>, displayResult: (RateableRankingResult) -> Unit) {
mlSession.finish()
application.runWriteAction {
displayResult(RateableRankingResult(mlSession, initialCandidatesOrder, timestampStarted))
}
}
}
internal fun launchMLRanking(initialCandidatesOrder: MutableList<out ImportCandidateHolder>, displayResult: (RateableRankingResult) -> Unit) {
// In EDT
val timestampStarted = System.currentTimeMillis()
val mlCoroutineScope = service<MLApiComputations>().coroutineScope
val mlSession = service<MLTaskPyCharmImportStatementsRanking>().task
.startMLSession(taskExecutor = MLApiTaskExecutor.configure(mlCoroutineScope))
val rankingStatus = service<FinalImportRankingStatusService>().status
val ranker = if (rankingStatus.mlEnabled)
ExperimentalMLRanker(mlCoroutineScope, mlSession, timestampStarted)
else
InitialOrderKeepingRanker(mlSession, timestampStarted)
mlSession.writeRuntimeSessionAnalysis(ML_LOGGING_STATE with getLoggingOption(mlSession))
mlSession.writeRuntimeSessionAnalysis(ML_ENABLED with rankingStatus.mlEnabled)
mlSession.writeRuntimeSessionAnalysis(ML_STATUS_CORRESPONDS_TO_BUCKET with rankingStatus.mlStatusCorrespondsToBucket)
ranker.launchMLRanking(initialCandidatesOrder) { result ->
val timestampDisplayed = System.currentTimeMillis()
displayResult(result)
ML_FEEDBACK_TIME_MS_TO_DISPLAY.feedback(mlSession, timestampDisplayed - timestampStarted)
}
}
internal class RateableRankingResult(
private val mlSession: MLSession<*, *>,
val order: List<ImportCandidateHolder>,
private val timestampStarted: Long,
) {
private var submitted = false
fun submitPopUpClosed() {
val timestampClosed = System.currentTimeMillis()
ML_FEEDBACK_TIME_MS_BEFORE_CLOSED.feedback(mlSession, timestampClosed - timestampStarted)
service<MLApiComputations>().coroutineScope.launch {
delay(100.milliseconds)
synchronized(this@RateableRankingResult) {
if (submitted) return@launch
submitted = true
MLFeedbackCorrectElementPosition.feedbackEventPairs(mlSession, MLFeedbackCorrectElementPosition.CANCELLED with true)
MLFieldCorrectElement.cancelFeedback(mlSession)
}
}
}
fun submitSelectedItem(selected: ImportCandidateHolder) = synchronized(this) {
// In EDT
require(!submitted) { "Some feedback to the ml ranker was already submitted" }
submitted = true
MLFieldCorrectElement.feedback(mlSession, selected, true)
val selectedIndex = order.indexOf(selected)
if (selectedIndex == -1)
MLFeedbackCorrectElementPosition.feedbackEventPairs(mlSession, MLFeedbackCorrectElementPosition.INVALID_SELECTED_ITEM with true)
else
MLFeedbackCorrectElementPosition.feedbackEventPairs(mlSession, MLFeedbackCorrectElementPosition.SELECTED_POSITION with selectedIndex)
}
}
@@ -0,0 +1,73 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.codeInsight.imports.mlapi
import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
import com.intellij.openapi.components.service
import com.intellij.openapi.util.Disposer
import com.intellij.platform.ml.impl.logs.MLEventLoggerProvider.Companion.ML_RECORDER_ID
import com.intellij.platform.ml.impl.tools.registerMLTaskLogging
import com.intellij.util.application
import com.jetbrains.ml.MLUnit
import com.jetbrains.ml.logs.*
import com.jetbrains.ml.logs.schema.BooleanEventField
import com.jetbrains.ml.logs.schema.EventField
import com.jetbrains.ml.logs.schema.EventPair
import com.jetbrains.ml.model.MLModel
import com.jetbrains.ml.session.MLSession
import com.jetbrains.ml.session.MLSessionInfo
import com.jetbrains.ml.tree.LevelFeaturesSchema
import com.jetbrains.ml.tree.MLTree
class PyCharmImportsRankingLogs : CounterUsagesCollector() {
private val GROUP = EventLogGroup("pycharm.quickfix.imports", 1, ML_RECORDER_ID).also {
it.registerMLTaskLogging(service<MLTaskPyCharmImportStatementsRanking>().task,
parentDisposable = Disposer.newDisposable())
}
override fun getGroup() = GROUP
}
internal enum class LoggingOption {
FULL,
NO_TREE,
SKIP
}
internal const val RELEASE_LOGGING_PERCENT = 5
internal fun getLoggingOption(mlSession: MLSession<*, *>): LoggingOption {
if (application.isEAP || application.isUnitTestMode) {
return LoggingOption.FULL
}
if (mlSession.id % 100 > RELEASE_LOGGING_PERCENT) return LoggingOption.SKIP
return LoggingOption.NO_TREE
}
object PyCharmImportsRankingLogger : MLSessionLoggerProvider<Any> {
private val baseLoggerProvider = EntireSessionLoggerProvider<Any, Boolean>(BooleanEventField("prediction") { "ML model prediction" }) { null }
override fun createMLSessionLogger(
eventPrefix: String,
taskId: String,
treeAnalysisDeclaration: Map<MLUnit<*>, List<EventField<*>>>,
sessionAnalysisDeclaration: List<EventField<*>>,
featuresDeclaration: List<LevelFeaturesSchema>,
fusEventRegister: FusEventRegister,
): MLSessionLogger<Any> {
val baseSessionLogger = baseLoggerProvider.createMLSessionLogger(eventPrefix, taskId, treeAnalysisDeclaration, sessionAnalysisDeclaration, featuresDeclaration, fusEventRegister)
return object : MLSessionLogger<Any> {
override suspend fun logMLSession(sessionInfo: MLSessionInfo<out MLModel<Any>, Any>, analysisException: Throwable?, sessionAnalysis: List<EventPair<*>>, structure: MLTree.ATopNode<out MLModel<Any>, Any>?): MLSessionLoggingOutcome {
val loggingOption = checkNotNull(sessionAnalysis.find { it.field == ML_LOGGING_STATE }).data as LoggingOption
return when (loggingOption) {
LoggingOption.FULL -> baseSessionLogger.logMLSession(sessionInfo, analysisException, sessionAnalysis, structure)
LoggingOption.NO_TREE -> {
baseSessionLogger.logMLSession(sessionInfo, analysisException, sessionAnalysis, null)
MLSessionLoggingOutcome.Custom("no tree logged")
}
LoggingOption.SKIP -> MLSessionLoggingOutcome.FilteredOut
}
}
}
}
}
@@ -0,0 +1,38 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.codeInsight.imports.mlapi
import com.intellij.codeInsight.inline.completion.ml.MLUnitTyping
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.platform.ml.impl.tools.IJPlatform
import com.jetbrains.ml.MLTaskBuilder
import kotlin.time.Duration.Companion.days
internal val LONGEST_POPUP_LIFE_DURATION = 365.days
@Service
class MLTaskPyCharmImportStatementsRanking {
val task by lazy {
MLTaskBuilder(
taskId = "pycharm_import_statements_ranking",
taskLevels = listOf(
setOf(MLUnitImportCandidatesList, MLUnitTyping),
setOf(MLUnitImportCandidate)
),
platform = service<IJPlatform>()
).buildWithoutMLModel {
suspendableTreeAnalysis(MLFieldCorrectElement)
suspendableSessionAnalysis(MLFeedbackCorrectElementPosition)
suspendableSessionAnalysis(ML_FEEDBACK_TIME_MS_TO_DISPLAY)
suspendableSessionAnalysis(ML_FEEDBACK_TIME_MS_BEFORE_CLOSED)
runtimeSessionAnalysis(ML_LOGGING_STATE, ML_ENABLED, ML_STATUS_CORRESPONDS_TO_BUCKET)
logger = PyCharmImportsRankingLogger
logFilter
maxAnalysisDuration = LONGEST_POPUP_LIFE_DURATION
}
}
}