LAB-29: Record file probability from change reminder as a feature for file prediction

GitOrigin-RevId: 6ecefbf4a296c2e18f59aaeee2a8d126bf8e1be1
This commit is contained in:
Svetlana.Zemlyanskaya
2020-01-29 15:36:20 +00:00
committed by intellij-monorepo-bot
parent 1218c7af0e
commit 9d21e75fdf
5 changed files with 140 additions and 73 deletions
@@ -0,0 +1,35 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.changeReminder.predict
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.vcs.log.data.VcsLogData
import com.jetbrains.changeReminder.getGitRootFiles
import com.jetbrains.changeReminder.repository.Commit
import com.jetbrains.changeReminder.repository.FilesHistoryProvider
class FileProbabilityRequest(private val project: Project,
private val dataManager: VcsLogData,
private val changeListFiles: Collection<FilePath>) {
private fun predict(candidate: FilePath, files: Collection<FilePath>, root: VirtualFile, history: FilesHistoryProvider): Double {
val commit = Commit(-1, System.currentTimeMillis(), dataManager.currentUser[root]?.name ?: "", changeListFiles.toSet())
return ProbabilityProvider.predict(commit, candidate, history.getFilesHistory(root, files).toSet())
}
fun calculate(candidate: FilePath): Double {
val history = dataManager.index.dataGetter?.let { FilesHistoryProvider(project, dataManager, it) } ?: return 0.0
val roots = getGitRootFiles(project, changeListFiles)
return roots.mapNotNull { (root, files) ->
if (dataManager.index.isIndexed(root)) {
predict(candidate, files, root, history)
}
else {
null
}
}.max() ?: 0.0
}
}
@@ -20,76 +20,6 @@ class PredictionProvider(private val minProb: Double = 0.55) {
private const val MAX_HISTORY_COMMIT_SIZE = 15
}
private class FactorCounter {
var sum = 0.0
private set
var max = 0.0
private set
var min = Double.MAX_VALUE
private set
fun append(newValue: Number): FactorCounter {
return append(newValue.toDouble())
}
private fun append(newValue: Double): FactorCounter {
sum += newValue
max = max(max, newValue)
min = min(min, newValue)
return this
}
}
private fun collectFileFactors(newCommit: Commit, candidateFile: FilePath, candidateFileHistory: Set<Commit>): DoubleArray {
val factors = DoubleArray(Factor.values().size)
val timeDistanceCounter = FactorCounter()
val intersectionCounters = mutableMapOf<FilePath, FactorCounter>()
val countFilesCounter = FactorCounter()
val filePrefixCounter = FactorCounter()
val pathPrefixCounter = FactorCounter()
var authorCommitted = 0.0
candidateFileHistory.forEach { oldCommit ->
val filesFromCommit = oldCommit.files.intersect(newCommit.files)
countFilesCounter.append(filesFromCommit.size)
if (filesFromCommit.isNotEmpty()) {
timeDistanceCounter.append(newCommit.time - oldCommit.time)
}
if (authorCommitted != 1.0 && oldCommit.author.startsWith(newCommit.author)) {
authorCommitted = 1.0
}
filesFromCommit.forEach { file ->
intersectionCounters.getOrPut(file) { FactorCounter() }.append(1)
}
}
val candidateFilePath = candidateFile.path
val candidateFileName = candidateFile.name
newCommit.files.forEach {
pathPrefixCounter.append(StringUtil.commonPrefixLength(candidateFilePath, it.path))
filePrefixCounter.append(StringUtil.commonPrefixLength(candidateFileName, it.name))
}
factors[Factor.MAX_INTERSECTION.ordinal] = intersectionCounters.values.maxBy { it.sum }?.sum ?: 0.0
factors[Factor.SUM_INTERSECTION.ordinal] = intersectionCounters.values.sumByDouble { it.sum }
factors[Factor.MIN_DISTANCE_TIME.ordinal] = timeDistanceCounter.min
factors[Factor.COMMIT_SIZE.ordinal] = newCommit.files.size.toDouble()
factors[Factor.MAX_DISTANCE_TIME.ordinal] = timeDistanceCounter.max
factors[Factor.AVG_DISTANCE_TIME.ordinal] = timeDistanceCounter.sum / candidateFileHistory.size.toDouble()
factors[Factor.MAX_COUNT.ordinal] = countFilesCounter.max
factors[Factor.MIN_COUNT.ordinal] = countFilesCounter.min
factors[Factor.AUTHOR_COMMITTED_THE_FILE.ordinal] = authorCommitted
factors[Factor.MAX_PREFIX_PATH.ordinal] = pathPrefixCounter.max
factors[Factor.MAX_PREFIX_FILE_NAME.ordinal] = filePrefixCounter.max
return factors
}
private fun getRelatedFiles(commit: Commit, history: Collection<Commit>): Map<FilePath, Set<Commit>> {
val sortedHistory = history.sortedByDescending { it.time }
@@ -122,7 +52,7 @@ class PredictionProvider(private val minProb: Double = 0.55) {
.asSequence()
.map { (candidateFile, candidateFileHistory) ->
ProgressManager.checkCanceled()
val fileScore = PredictionModel.makePrediction(collectFileFactors(commit, candidateFile, candidateFileHistory))
val fileScore = ProbabilityProvider.predict(commit, candidateFile, candidateFileHistory)
candidateFile to fileScore
}
.filter { it.second > minProb }
@@ -0,0 +1,85 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.changeReminder.predict
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vcs.FilePath
import com.jetbrains.changeReminder.prediction.model.PredictionModel
import com.jetbrains.changeReminder.repository.Commit
import kotlin.math.max
import kotlin.math.min
object ProbabilityProvider {
private class FactorCounter {
var sum = 0.0
private set
var max = 0.0
private set
var min = Double.MAX_VALUE
private set
fun append(newValue: Number): FactorCounter {
return append(newValue.toDouble())
}
private fun append(newValue: Double): FactorCounter {
sum += newValue
max = max(max, newValue)
min = min(min, newValue)
return this
}
}
private fun collectFileFactors(newCommit: Commit, candidateFile: FilePath, candidateFileHistory: Set<Commit>): DoubleArray {
val factors = DoubleArray(Factor.values().size)
val timeDistanceCounter = FactorCounter()
val intersectionCounters = mutableMapOf<FilePath, FactorCounter>()
val countFilesCounter = FactorCounter()
val filePrefixCounter = FactorCounter()
val pathPrefixCounter = FactorCounter()
var authorCommitted = 0.0
candidateFileHistory.forEach { oldCommit ->
val filesFromCommit = oldCommit.files.intersect(newCommit.files)
countFilesCounter.append(filesFromCommit.size)
if (filesFromCommit.isNotEmpty()) {
timeDistanceCounter.append(newCommit.time - oldCommit.time)
}
if (authorCommitted != 1.0 && oldCommit.author.startsWith(newCommit.author)) {
authorCommitted = 1.0
}
filesFromCommit.forEach { file ->
intersectionCounters.getOrPut(file) { FactorCounter() }.append(1)
}
}
val candidateFilePath = candidateFile.path
val candidateFileName = candidateFile.name
newCommit.files.forEach {
pathPrefixCounter.append(StringUtil.commonPrefixLength(candidateFilePath, it.path))
filePrefixCounter.append(StringUtil.commonPrefixLength(candidateFileName, it.name))
}
factors[Factor.MAX_INTERSECTION.ordinal] = intersectionCounters.values.maxBy { it.sum }?.sum ?: 0.0
factors[Factor.SUM_INTERSECTION.ordinal] = intersectionCounters.values.sumByDouble { it.sum }
factors[Factor.MIN_DISTANCE_TIME.ordinal] = timeDistanceCounter.min
factors[Factor.COMMIT_SIZE.ordinal] = newCommit.files.size.toDouble()
factors[Factor.MAX_DISTANCE_TIME.ordinal] = timeDistanceCounter.max
factors[Factor.AVG_DISTANCE_TIME.ordinal] = timeDistanceCounter.sum / candidateFileHistory.size.toDouble()
factors[Factor.MAX_COUNT.ordinal] = countFilesCounter.max
factors[Factor.MIN_COUNT.ordinal] = countFilesCounter.min
factors[Factor.AUTHOR_COMMITTED_THE_FILE.ordinal] = authorCommitted
factors[Factor.MAX_PREFIX_PATH.ordinal] = pathPrefixCounter.max
factors[Factor.MAX_PREFIX_FILE_NAME.ordinal] = filePrefixCounter.max
return factors
}
fun predict(commit: Commit, candidateFile: FilePath, candidateFileHistory: Set<Commit>): Double {
return PredictionModel.makePrediction(collectFileFactors(commit, candidateFile, candidateFileHistory))
}
}
@@ -6,7 +6,7 @@
<description><![CDATA[Predicts next file which will be open in IDE to start long running analysis and pre-load caches.]]></description>
<depends optional="true" config-file="file-prediction-java.xml">com.intellij.java</depends>
<depends optional="true" config-file="file-prediction-vcs.xml">com.intellij.modules.vcs</depends>
<depends optional="true" config-file="file-prediction-vcs.xml">com.jetbrains.changeReminder</depends>
<extensionPoints>
<extensionPoint qualifiedName="com.intellij.filePrediction.featureProvider" interface="com.intellij.filePrediction.FilePredictionFeatureProvider" dynamic="true"/>
@@ -16,7 +16,7 @@
<extensions defaultExtensionNs="com.intellij">
<registryKey key="filePrediction.calculate.features" defaultValue="true" description="Record opened files features to predict which file will be opened next and pre-load caches."/>
<statistics.counterUsagesCollector groupId="file.prediction" version="1"/>
<statistics.counterUsagesCollector groupId="file.prediction" version="2"/>
<filePrediction.featureProvider implementation="com.intellij.filePrediction.FilePredictionGeneralFeatures"/>
<filePrediction.featureProvider implementation="com.intellij.filePrediction.history.FilePredictionHistoryFeatures"/>
@@ -5,8 +5,13 @@ import com.intellij.filePrediction.FilePredictionFeature
import com.intellij.filePrediction.FilePredictionFeatureProvider
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.ProjectLevelVcsManager
import com.intellij.openapi.vcs.actions.VcsContextFactory
import com.intellij.openapi.vcs.changes.ChangeListManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.vcs.log.impl.VcsProjectLog
import com.jetbrains.changeReminder.predict.FileProbabilityRequest
import java.util.*
import kotlin.collections.HashMap
class FilePredictionVcsFeatures : FilePredictionFeatureProvider {
override fun getName(): String = "vcs"
@@ -20,6 +25,18 @@ class FilePredictionVcsFeatures : FilePredictionFeatureProvider {
result["prev_in_changelist"] = FilePredictionFeature.binary(changeListManager.isFileAffected(prevFile))
}
result["in_changelist"] = FilePredictionFeature.binary(changeListManager.isFileAffected(newFile))
if (prevFile != null) {
val dataManager = VcsProjectLog.getInstance(project).dataManager
if (dataManager != null) {
val contextFactory = VcsContextFactory.SERVICE.getInstance()
val newPath = contextFactory.createFilePath(newFile.path, false)
val recentFile = contextFactory.createFilePath(prevFile.path, false)
val request = FileProbabilityRequest(project, dataManager, Collections.singletonList(recentFile))
result["related_prob"] = FilePredictionFeature.numerical(request.calculate(newPath))
}
}
return result
}
}