Skip to content

Commit c592449

Browse files
committed
Add new fields in UserTriggerDecision and UserModification events
1. bring back UserModification SendTelemetryEvent and track acceptedCharacterCount and unmodifiedAcceptedCharacterCount. 2. combine some latency tracking to the session object
1 parent 62e9cfb commit c592449

File tree

8 files changed

+105
-52
lines changed

8 files changed

+105
-52
lines changed

packages/core/src/codewhisperer/client/user-service-2.json

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2197,14 +2197,24 @@
21972197
},
21982198
"UserModificationEvent": {
21992199
"type": "structure",
2200-
"required": ["sessionId", "requestId", "programmingLanguage", "modificationPercentage", "timestamp"],
2200+
"required": [
2201+
"sessionId",
2202+
"requestId",
2203+
"programmingLanguage",
2204+
"modificationPercentage",
2205+
"timestamp",
2206+
"acceptedCharacterCount",
2207+
"unmodifiedAcceptedCharacterCount"
2208+
],
22012209
"members": {
22022210
"sessionId": { "shape": "UUID" },
22032211
"requestId": { "shape": "UUID" },
22042212
"programmingLanguage": { "shape": "ProgrammingLanguage" },
22052213
"modificationPercentage": { "shape": "Double" },
22062214
"customizationArn": { "shape": "CustomizationArn" },
2207-
"timestamp": { "shape": "Timestamp" }
2215+
"timestamp": { "shape": "Timestamp" },
2216+
"acceptedCharacterCount": { "shape": "PrimitiveInteger" },
2217+
"unmodifiedAcceptedCharacterCount": { "shape": "PrimitiveInteger" }
22082218
}
22092219
},
22102220
"UserTriggerDecisionEvent": {
@@ -2230,7 +2240,9 @@
22302240
"triggerToResponseLatencyMilliseconds": { "shape": "Double" },
22312241
"suggestionReferenceCount": { "shape": "PrimitiveInteger" },
22322242
"generatedLine": { "shape": "PrimitiveInteger" },
2233-
"numberOfRecommendations": { "shape": "PrimitiveInteger" }
2243+
"numberOfRecommendations": { "shape": "PrimitiveInteger" },
2244+
"perceivedLatencyMilliseconds": { "shape": "Double" },
2245+
"acceptedCharacterCount": { "shape": "PrimitiveInteger" }
22342246
}
22352247
},
22362248
"ValidationException": {

packages/core/src/codewhisperer/service/inlineCompletionService.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ export class InlineCompletionService {
112112

113113
await this.setState('loading')
114114

115-
TelemetryHelper.instance.setInvocationStartTime(performance.now())
116115
RecommendationHandler.instance.checkAndResetCancellationTokens()
117116
RecommendationHandler.instance.documentUri = editor.document.uri
118117
let response: GetRecommendationsResponse = {

packages/core/src/codewhisperer/service/recommendationHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ export class RecommendationHandler {
256256
sessionId = resp?.$response?.httpResponse?.headers['x-amzn-sessionid']
257257
TelemetryHelper.instance.setFirstResponseRequestId(requestId)
258258
if (page === 0) {
259-
TelemetryHelper.instance.setTimeToFirstRecommendation(performance.now())
259+
session.setTimeToFirstRecommendation(performance.now())
260260
}
261261
if (nextToken === '') {
262262
TelemetryHelper.instance.setAllPaginationEndTime()

packages/core/src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import { getLogger } from '../../shared/logger/logger'
88
import * as CodeWhispererConstants from '../models/constants'
99
import globals from '../../shared/extensionGlobals'
1010
import { vsCodeState } from '../models/model'
11-
import { distance } from 'fastest-levenshtein'
11+
1212
import { CodewhispererLanguage, telemetry } from '../../shared/telemetry/telemetry'
1313
import { runtimeLanguageContext } from '../util/runtimeLanguageContext'
1414
import { TelemetryHelper } from '../util/telemetryHelper'
1515
import { AuthUtil } from '../util/authUtil'
1616
import { getSelectedCustomization } from '../util/customizationUtil'
1717
import { codeWhispererClient as client } from '../client/codewhisperer'
1818
import { isAwsError } from '../../shared/errors'
19+
import { getUnmodifiedAcceptedTokens } from '../util/commonUtil'
1920

2021
interface CodeWhispererToken {
2122
range: vscode.Range
@@ -86,18 +87,10 @@ export class CodeWhispererCodeCoverageTracker {
8687
for (let i = 0; i < this._acceptedTokens[filename].length; i++) {
8788
const oldText = this._acceptedTokens[filename][i].text
8889
const newText = editor.document.getText(this._acceptedTokens[filename][i].range)
89-
this._acceptedTokens[filename][i].accepted = this.getUnmodifiedAcceptedTokens(oldText, newText)
90+
this._acceptedTokens[filename][i].accepted = getUnmodifiedAcceptedTokens(oldText, newText)
9091
}
9192
}
9293
}
93-
// With edit distance, complicate usermodification can be considered as simple edit(add, delete, replace),
94-
// and thus the unmodified part of recommendation length can be deducted/approximated
95-
// ex. (modified > original): originalRecom: foo -> modifiedRecom: fobarbarbaro, distance = 9, delta = 12 - 9 = 3
96-
// ex. (modified == original): originalRecom: helloworld -> modifiedRecom: HelloWorld, distance = 2, delta = 10 - 2 = 8
97-
// ex. (modified < original): originalRecom: CodeWhisperer -> modifiedRecom: CODE, distance = 12, delta = 13 - 12 = 1
98-
public getUnmodifiedAcceptedTokens(origin: string, after: string) {
99-
return Math.max(origin.length, after.length) - distance(origin, after)
100-
}
10194

10295
public emitCodeWhispererCodeContribution() {
10396
let totalTokens = 0

packages/core/src/codewhisperer/tracker/codewhispererTracker.ts

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import { codeWhispererClient } from '../client/codewhisperer'
1515
import { logSendTelemetryEventFailure } from '../../codewhispererChat/controllers/chat/telemetryHelper'
1616
import { Timeout } from '../../shared/utilities/timeoutUtils'
1717
import { getSelectedCustomization } from '../util/customizationUtil'
18-
import { undefinedIfEmpty } from '../../shared'
18+
import { isAwsError, undefinedIfEmpty } from '../../shared'
19+
import { getUnmodifiedAcceptedTokens } from '../indexNode'
1920

2021
/**
2122
* This singleton class is mainly used for calculating the percentage of user modification.
@@ -89,19 +90,20 @@ export class CodeWhispererTracker {
8990

9091
public async emitTelemetryOnSuggestion(suggestion: AcceptedSuggestionEntry | InsertedCode) {
9192
let percentage = 1.0
93+
let currString = ''
94+
const customizationArn = undefinedIfEmpty(getSelectedCustomization().arn)
9295

9396
try {
9497
if (suggestion.fileUrl?.scheme !== '') {
9598
const document = await vscode.workspace.openTextDocument(suggestion.fileUrl)
9699
if (document) {
97-
const currString = document.getText(
98-
new vscode.Range(suggestion.startPosition, suggestion.endPosition)
99-
)
100+
currString = document.getText(new vscode.Range(suggestion.startPosition, suggestion.endPosition))
100101
percentage = this.checkDiff(currString, suggestion.originalString)
101102
}
102103
}
103104
} catch (e) {
104105
getLogger().verbose(`Exception Thrown from CodeWhispererTracker: ${e}`)
106+
return
105107
} finally {
106108
if ('conversationID' in suggestion) {
107109
const event: AmazonqModifyCode = {
@@ -120,7 +122,7 @@ export class CodeWhispererTracker {
120122
conversationId: event.cwsprChatConversationId,
121123
messageId: event.cwsprChatMessageId,
122124
modificationPercentage: event.cwsprChatModificationPercentage,
123-
customizationArn: undefinedIfEmpty(getSelectedCustomization().arn),
125+
customizationArn: customizationArn,
124126
},
125127
},
126128
})
@@ -139,9 +141,39 @@ export class CodeWhispererTracker {
139141
codewhispererCharactersAccepted: suggestion.originalString.length,
140142
codewhispererCharactersModified: 0, // TODO: currently we don't have an accurate number for this field with existing implementation
141143
})
142-
// TODO:
143-
// Temperary comment out user modification event, need further discussion on how to calculate this metric
144-
// TelemetryHelper.instance.sendUserModificationEvent(suggestion, percentage)
144+
145+
codeWhispererClient
146+
.sendTelemetryEvent({
147+
telemetryEvent: {
148+
userModificationEvent: {
149+
sessionId: suggestion.sessionId,
150+
requestId: suggestion.requestId,
151+
programmingLanguage: { languageName: suggestion.language },
152+
// deprecated % value and should not be used by service-side
153+
modificationPercentage: percentage,
154+
customizationArn: customizationArn,
155+
timestamp: new Date(Date.now()),
156+
acceptedCharacterCount: suggestion.originalString.length,
157+
unmodifiedAcceptedCharacterCount: getUnmodifiedAcceptedTokens(
158+
suggestion.originalString,
159+
currString
160+
),
161+
},
162+
},
163+
})
164+
.then()
165+
.catch((error) => {
166+
let requestId: string | undefined
167+
if (isAwsError(error)) {
168+
requestId = error.requestId
169+
}
170+
171+
getLogger().debug(
172+
`Failed to send UserModificationEvent to CodeWhisperer, requestId: ${requestId ?? ''}, message: ${
173+
error.message
174+
}`
175+
)
176+
})
145177
}
146178
}
147179
}

packages/core/src/codewhisperer/util/codeWhispererSession.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
} from '../../shared/telemetry/telemetry.gen'
1313
import { GenerateRecommendationsRequest, ListRecommendationsRequest, Recommendation } from '../client/codewhisperer'
1414
import { Position } from 'vscode'
15-
import { CodeWhispererSupplementalContext } from '../models/model'
15+
import { CodeWhispererSupplementalContext, vsCodeState } from '../models/model'
1616

1717
class CodeWhispererSession {
1818
static #instance: CodeWhispererSession
@@ -41,6 +41,8 @@ class CodeWhispererSession {
4141
fetchCredentialStartTime = 0
4242
sdkApiCallStartTime = 0
4343
invokeSuggestionStartTime = 0
44+
timeToFirstRecommendation = 0
45+
firstSuggestionShowTime = 0
4446

4547
public static get instance() {
4648
return (this.#instance ??= new CodeWhispererSession())
@@ -58,6 +60,12 @@ class CodeWhispererSession {
5860
}
5961
}
6062

63+
setTimeToFirstRecommendation(timeToFirstRecommendation: number) {
64+
if (this.invokeSuggestionStartTime) {
65+
this.timeToFirstRecommendation = timeToFirstRecommendation - this.invokeSuggestionStartTime
66+
}
67+
}
68+
6169
setSuggestionState(index: number, value: string) {
6270
this.suggestionStates.set(index, value)
6371
}
@@ -75,6 +83,14 @@ class CodeWhispererSession {
7583
return this.completionTypes.get(index) || 'Line'
7684
}
7785

86+
getPerceivedLatency(triggerType: CodewhispererTriggerType) {
87+
if (triggerType === 'OnDemand') {
88+
return this.timeToFirstRecommendation
89+
} else {
90+
return session.firstSuggestionShowTime - vsCodeState.lastUserModificationTime
91+
}
92+
}
93+
7894
reset() {
7995
this.sessionId = ''
8096
this.requestContext = { request: {} as any, supplementalMetadata: {} as any }

packages/core/src/codewhisperer/util/commonUtil.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import * as vscode from 'vscode'
77
import * as semver from 'semver'
8+
import { distance } from 'fastest-levenshtein'
89
import { isCloud9 } from '../../shared/extensionUtilities'
910
import { getInlineSuggestEnabled } from '../../shared/utilities/editorUtilities'
1011
import {
@@ -76,3 +77,12 @@ export function checkLeftContextKeywordsForJson(fileName: string, leftFileConten
7677
}
7778
return false
7879
}
80+
81+
// With edit distance, complicate usermodification can be considered as simple edit(add, delete, replace),
82+
// and thus the unmodified part of recommendation length can be deducted/approximated
83+
// ex. (modified > original): originalRecom: foo -> modifiedRecom: fobarbarbaro, distance = 9, delta = 12 - 9 = 3
84+
// ex. (modified == original): originalRecom: helloworld -> modifiedRecom: HelloWorld, distance = 2, delta = 10 - 2 = 8
85+
// ex. (modified < original): originalRecom: CodeWhisperer -> modifiedRecom: CODE, distance = 12, delta = 13 - 12 = 1
86+
export function getUnmodifiedAcceptedTokens(origin: string, after: string) {
87+
return Math.max(origin.length, after.length) - distance(origin, after)
88+
}

packages/core/src/codewhisperer/util/telemetryHelper.ts

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ import { AuthUtil } from './authUtil'
2323
import { isAwsError } from '../../shared/errors'
2424
import { getLogger } from '../../shared/logger'
2525
import { session } from './codeWhispererSession'
26-
import { CodeWhispererSupplementalContext } from '../models/model'
26+
import { AcceptedSuggestionEntry, CodeWhispererSupplementalContext } from '../models/model'
2727
import { FeatureConfigProvider } from '../../shared/featureConfig'
2828
import { CodeScanRemediationsEventType } from '../client/codewhispereruserclient'
29+
import { InsertedCode } from '../../codewhispererChat/controllers/chat/model'
2930

3031
export class TelemetryHelper {
3132
// Some variables for client component latency
3233
private sdkApiCallEndTime = 0
33-
private firstSuggestionShowTime = 0
3434
private allPaginationEndTime = 0
3535
private firstResponseRequestId = ''
3636
// variables for user trigger decision
@@ -41,8 +41,6 @@ export class TelemetryHelper {
4141
private typeAheadLength = 0
4242
private timeSinceLastModification = 0
4343
private lastTriggerDecisionTime = 0
44-
private invocationTime = 0
45-
private timeToFirstRecommendation = 0
4644
private classifierResult?: number = undefined
4745
private classifierThreshold?: number = undefined
4846
// variables for tracking end to end sessions
@@ -285,7 +283,7 @@ export class TelemetryHelper {
285283
codewhispererTimeSinceLastUserDecision: this.lastTriggerDecisionTime
286284
? performance.now() - this.lastTriggerDecisionTime
287285
: undefined,
288-
codewhispererTimeToFirstRecommendation: this.timeToFirstRecommendation,
286+
codewhispererTimeToFirstRecommendation: session.timeToFirstRecommendation,
289287
codewhispererTriggerCharacter: autoTriggerType === 'SpecialCharacters' ? this.triggerChar : undefined,
290288
codewhispererSuggestionState: aggregatedSuggestionState,
291289
codewhispererPreviousSuggestionState: this.prevTriggerDecision,
@@ -305,11 +303,11 @@ export class TelemetryHelper {
305303
this.prevTriggerDecision = this.getAggregatedSuggestionState(this.sessionDecisions)
306304
this.lastTriggerDecisionTime = performance.now()
307305

308-
// When we send a userTriggerDecision of Empty or Discard, we set the time users see the first
309-
// suggestion to be now.
310-
let e2eLatency = this.firstSuggestionShowTime - session.invokeSuggestionStartTime
311-
if (e2eLatency < 0) {
312-
e2eLatency = performance.now() - session.invokeSuggestionStartTime
306+
// When we send a userTriggerDecision for neither Accept nor Reject, service side should not use this value
307+
// and client side will set this value to 0.0.
308+
let e2eLatency = session.firstSuggestionShowTime - session.invokeSuggestionStartTime
309+
if (aggregatedSuggestionState != 'Reject' && aggregatedSuggestionState != 'Accept') {
310+
e2eLatency = 0.0
313311
}
314312

315313
client
@@ -327,8 +325,11 @@ export class TelemetryHelper {
327325
completionType: this.getSendTelemetryCompletionType(aggregatedCompletionType),
328326
suggestionState: this.getSendTelemetrySuggestionState(aggregatedSuggestionState),
329327
recommendationLatencyMilliseconds: e2eLatency,
328+
triggerToResponseLatencyMilliseconds: session.timeToFirstRecommendation,
329+
perceivedLatencyMilliseconds: session.getPerceivedLatency(
330+
this.sessionDecisions[0].codewhispererTriggerType
331+
),
330332
timestamp: new Date(Date.now()),
331-
triggerToResponseLatencyMilliseconds: this.timeToFirstRecommendation,
332333
suggestionReferenceCount: referenceCount,
333334
generatedLine: generatedLines,
334335
numberOfRecommendations: suggestionCount,
@@ -377,16 +378,6 @@ export class TelemetryHelper {
377378
this.timeSinceLastModification = timeSinceLastModification
378379
}
379380

380-
public setInvocationStartTime(invocationTime: number) {
381-
this.invocationTime = invocationTime
382-
}
383-
384-
public setTimeToFirstRecommendation(timeToFirstRecommendation: number) {
385-
if (this.invocationTime) {
386-
this.timeToFirstRecommendation = timeToFirstRecommendation - this.invocationTime
387-
}
388-
}
389-
390381
public setTraceId(traceId: string) {
391382
this.traceId = traceId
392383
}
@@ -396,7 +387,7 @@ export class TelemetryHelper {
396387
this.triggerChar = ''
397388
this.typeAheadLength = 0
398389
this.timeSinceLastModification = 0
399-
this.timeToFirstRecommendation = 0
390+
session.timeToFirstRecommendation = 0
400391
this.classifierResult = undefined
401392
this.classifierThreshold = undefined
402393
}
@@ -479,7 +470,7 @@ export class TelemetryHelper {
479470
session.sdkApiCallStartTime = 0
480471
this.sdkApiCallEndTime = 0
481472
session.fetchCredentialStartTime = 0
482-
this.firstSuggestionShowTime = 0
473+
session.firstSuggestionShowTime = 0
483474
this.allPaginationEndTime = 0
484475
this.firstResponseRequestId = ''
485476
}
@@ -503,8 +494,8 @@ export class TelemetryHelper {
503494
}
504495

505496
public setFirstSuggestionShowTime() {
506-
if (this.firstSuggestionShowTime === 0 && this.sdkApiCallEndTime !== 0) {
507-
this.firstSuggestionShowTime = performance.now()
497+
if (session.firstSuggestionShowTime === 0 && this.sdkApiCallEndTime !== 0) {
498+
session.firstSuggestionShowTime = performance.now()
508499
}
509500
}
510501

@@ -517,16 +508,16 @@ export class TelemetryHelper {
517508
// report client component latency after all pagination call finish
518509
// and at least one suggestion is shown to the user
519510
public tryRecordClientComponentLatency() {
520-
if (this.firstSuggestionShowTime === 0 || this.allPaginationEndTime === 0) {
511+
if (session.firstSuggestionShowTime === 0 || this.allPaginationEndTime === 0) {
521512
return
522513
}
523514
telemetry.codewhisperer_clientComponentLatency.emit({
524515
codewhispererRequestId: this.firstResponseRequestId,
525516
codewhispererSessionId: session.sessionId,
526517
codewhispererFirstCompletionLatency: this.sdkApiCallEndTime - session.sdkApiCallStartTime,
527-
codewhispererEndToEndLatency: this.firstSuggestionShowTime - session.invokeSuggestionStartTime,
518+
codewhispererEndToEndLatency: session.firstSuggestionShowTime - session.invokeSuggestionStartTime,
528519
codewhispererAllCompletionsLatency: this.allPaginationEndTime - session.sdkApiCallStartTime,
529-
codewhispererPostprocessingLatency: this.firstSuggestionShowTime - this.sdkApiCallEndTime,
520+
codewhispererPostprocessingLatency: session.firstSuggestionShowTime - this.sdkApiCallEndTime,
530521
codewhispererCredentialFetchingLatency: session.sdkApiCallStartTime - session.fetchCredentialStartTime,
531522
codewhispererPreprocessingLatency: session.fetchCredentialStartTime - session.invokeSuggestionStartTime,
532523
codewhispererCompletionType: 'Line',

0 commit comments

Comments
 (0)