44package software.aws.toolkits.jetbrains.services.codewhisperer.telemetry
55
66import com.intellij.openapi.Disposable
7- import com.intellij.openapi.application.runReadAction
87import com.intellij.openapi.components.Service
98import com.intellij.openapi.components.service
109import com.intellij.openapi.editor.event.DocumentEvent
11- import com.intellij.openapi.fileEditor.FileDocumentManager
1210import com.intellij.openapi.project.Project
13- import com.intellij.openapi.util.Key
14- import com.intellij.openapi.util.TextRange
11+ import com.intellij.psi.PsiDocumentManager
1512import com.intellij.util.Alarm
1613import com.intellij.util.AlarmFactory
17- import info.debatty.java.stringsimilarity.Levenshtein
18- import org.assertj.core.util.VisibleForTesting
1914import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException
2015import software.aws.toolkits.core.utils.debug
2116import software.aws.toolkits.core.utils.getLogger
2217import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
2318import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator
24- import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererLanguageManager
25- import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererUnknownLanguage
19+ import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage
2620import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmingLanguage
27- import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererCodeCoverageTracker.Companion
28- import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getConnectionStartUrl
29- import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getUnmodifiedAcceptedCharsCount
3021import software.aws.toolkits.jetbrains.services.codewhisperer.util.runIfIdcConnectionOrTelemetryEnabled
31- import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.InsertedCodeModificationEntry
32- import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
3322import software.aws.toolkits.jetbrains.settings.AwsSettings
34- import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
35- import software.aws.toolkits.telemetry.AmazonqTelemetry
36- import software.aws.toolkits.telemetry.CodewhispererLanguage
37- import software.aws.toolkits.telemetry.CodewhispererTelemetry
3823import java.time.Duration
39- import java.time.Instant
40- import java.util.concurrent.LinkedBlockingDeque
4124import java.util.concurrent.atomic.AtomicBoolean
25+ import java.util.concurrent.atomic.AtomicInteger
4226
4327
4428@Service(Service .Level .PROJECT )
4529class UserWrittenCodeTracker (private val project : Project ) : Disposable {
46- private val userWrittenCodePerLanguage = mutableMapOf<String , Int >()
30+ private val userWrittenCodeLineCount = mutableMapOf<CodeWhispererProgrammingLanguage , Long >()
31+ private val userWrittenCodeCharacterCount = mutableMapOf<CodeWhispererProgrammingLanguage , Long >()
4732 private val alarm = AlarmFactory .getInstance().create(Alarm .ThreadToUse .POOLED_THREAD , this )
4833
4934 private val isShuttingDown = AtomicBoolean (false )
35+ private val qInvocationCount: AtomicInteger = AtomicInteger (0 )
36+ private val isQMakingEdits = AtomicBoolean (false )
5037
5138 init {
5239 scheduleTracker()
@@ -60,22 +47,35 @@ class UserWrittenCodeTracker(private val project: Project) : Disposable {
6047
6148 private fun isTelemetryEnabled (): Boolean = AwsSettings .getInstance().isTelemetryEnabled
6249
50+ fun onQFeatureInvoked () {
51+ qInvocationCount.incrementAndGet()
52+ }
53+
54+ fun onQStartsMakingEdits () {
55+ isQMakingEdits.set(true )
56+ }
57+
58+ fun onQFinishesMakingEdits () {
59+ isQMakingEdits.set(false )
60+ }
6361
6462 private fun flush () {
6563 try {
66- if (! isTelemetryEnabled()) {
64+ if (! isTelemetryEnabled() || qInvocationCount.get() <= 0 ) {
6765 return
6866 }
69- for ((language, userWrittenCode) in userWrittenCodePerLanguage) {
70-
71- }
72-
67+ emitCodeWhispererCodeContribution()
7368 } finally {
7469 scheduleTracker()
7570 }
7671 }
7772
7873 internal fun documentChanged (event : DocumentEvent ) {
74+ // do not listen to document changed made by Amazon Q itself
75+ if (isQMakingEdits.get()) {
76+ return
77+ }
78+
7979 // When open a file for the first time, IDE will also emit DocumentEvent for loading with `isWholeTextReplaced = true`
8080 // Added this condition to filter out those events
8181 if (event.isWholeTextReplaced) {
@@ -88,45 +88,48 @@ class UserWrittenCodeTracker(private val project: Project) : Disposable {
8888 // edge case: event can be from user hit enter with indentation where change is \n\t\t, count as 1 char increase in total chars
8989 // when event is auto closing [{(', there will be 2 separated events, both count as 1 char increase in total chars
9090 val text = event.newFragment.toString()
91+ val lines = text.split(' \n ' ).size - 1
9192 if (event.newLength < DOCUMENT_COPY_THRESSHOLD && text.trim().isNotEmpty()) {
9293 // count doc changes from <50 multi character input as total user written code
9394 // ignore all white space changes, this usually comes from IntelliJ formatting
94- val language = " "
95- val newUserWrittenCode = userWrittenCodePerLanguage.getOrDefault(language, 0 ) + event.newLength
96- userWrittenCodePerLanguage.set(language, newUserWrittenCode)
95+ val language = PsiDocumentManager .getInstance(project).getPsiFile(event.document)?.programmingLanguage()
96+ if (language != null ) {
97+ userWrittenCodeLineCount.set(language, userWrittenCodeLineCount.getOrDefault(language, 0 ) + lines)
98+ userWrittenCodeCharacterCount.set(language, userWrittenCodeCharacterCount.getOrDefault(language, 0 ) + event.newLength)
99+ }
97100 }
98101 }
99102
100- private fun sendUserModificationTelemetryToServiceAPI (
101- suggestion : AcceptedSuggestionEntry ,
102- ) {
103- runIfIdcConnectionOrTelemetryEnabled(project) {
104- try {
105- // should be impossible from the caller logic
106- if (suggestion.vFile == null ) return @runIfIdcConnectionOrTelemetryEnabled
107- val document = runReadAction {
108- FileDocumentManager .getInstance().getDocument(suggestion.vFile)
109- }
110- val modifiedSuggestion = document?.getText(
111- TextRange (suggestion.range.startOffset, suggestion.range.endOffset)
112- ).orEmpty()
113- val response = CodeWhispererClientAdaptor .getInstance(project)
114- .sendUserModificationTelemetry(
115- suggestion.sessionId,
116- suggestion.requestId,
117- CodeWhispererLanguageManager .getInstance().getLanguage(suggestion.vFile),
118- CodeWhispererModelConfigurator .getInstance().activeCustomization(project)?.arn.orEmpty(),
119- suggestion.suggestion.length,
120- getUnmodifiedAcceptedCharsCount(suggestion.suggestion, modifiedSuggestion)
103+
104+ internal fun emitCodeWhispererCodeContribution () {
105+ val customizationArn: String? = CodeWhispererModelConfigurator .getInstance().activeCustomization(project)?.arn
106+ for ((language, _) in userWrittenCodeCharacterCount) {
107+ if (userWrittenCodeCharacterCount.getOrDefault(language, 0 ) <= 0 ) {
108+ continue
109+ }
110+ runIfIdcConnectionOrTelemetryEnabled(project) {
111+ // here acceptedTokensSize is the count of accepted chars post user modification
112+ try {
113+ val response = CodeWhispererClientAdaptor .getInstance(project).sendCodePercentageTelemetry(
114+ language,
115+ customizationArn,
116+ 0 ,
117+ 0 ,
118+ 0 ,
119+ userWrittenCodeCharacterCount = userWrittenCodeCharacterCount.get(language),
120+ userWrittenCodeLineCount = userWrittenCodeLineCount.get((language))
121121 )
122- LOG .debug { " Successfully sent user modification telemetry. RequestId: ${response.responseMetadata().requestId()} " }
123- } catch (e: Exception ) {
124- val requestId = if (e is CodeWhispererRuntimeException ) e.requestId() else null
125- LOG .debug {
126- " Failed to send user modification telemetry. RequestId: $requestId , ErrorMessage: ${e.message} "
122+ LOG .debug { " Successfully sent code percentage telemetry. RequestId: ${response.responseMetadata().requestId()} " }
123+ } catch (e: Exception ) {
124+ val requestId = if (e is CodeWhispererRuntimeException ) e.requestId() else null
125+ LOG .debug {
126+ " Failed to send code percentage telemetry. RequestId: $requestId , ErrorMessage: ${e.message} "
127+ }
127128 }
128129 }
129130 }
131+
132+
130133 }
131134
132135 companion object {
0 commit comments