Skip to content

Commit 0a66eea

Browse files
authored
feat(codewhisperer): Add Client component latency telemetry (#3090)
Problem: Adding a new telemetry : Client component latency for codewhisperer. telemetry definition in: aws/aws-toolkit-common@618a9fb A client component latency metric is emitted only once for each complete GenerationRecommendation pagination session if the suggestion is shown to the user. { "name": "codewhispererFirstCompletionLatency", "type": "double", "description": "The time it takes for the response to be received after the plugin makes a first GenerateCompletions API call." }, { "name": "codewhispererEndToEndLatency", "type": "double", "description": "The time it takes for the first completion to be shown in the IDE after the user performs the CW trigger action." }, { "name": "codewhispererAllCompletionsLatency", "type": "double", "description": "The time it takes for the last GenerateCompletions response to be received after plugin makes a first call to GenerateCompletions API." }, { "name": "codewhispererPostprocessingLatency", "type": "double", "description": "The time it takes for the first completions to be displayed in the IDE after the plugin receives the initial Completions object." }, { "name": "codewhispererPreprocessingLatency", "type": "double", "description": "The time it takes for the plugin to make the first GenerateCompletions API call after the user performs the CW trigger action." }, { "name": "codewhispererCredentialFetchingLatency", "type": "double", "description": "The time it takes to get the Sono/SSO credential for the invocation." }, Solution: Record timestamp and compare the timestamp to build the Client component latency metric and report it to service.
1 parent ee2c7f1 commit 0a66eea

File tree

8 files changed

+117
-10
lines changed

8 files changed

+117
-10
lines changed

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3518,7 +3518,7 @@
35183518
"report": "nyc report --reporter=html --reporter=json"
35193519
},
35203520
"devDependencies": {
3521-
"@aws-toolkits/telemetry": "^1.0.82",
3521+
"@aws-toolkits/telemetry": "^1.0.87",
35223522
"@cspotcode/source-map-support": "^0.8.1",
35233523
"@sinonjs/fake-timers": "^8.1.0",
35243524
"@types/adm-zip": "^0.4.34",

src/codewhisperer/client/codewhisperer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { getLogger } from '../../shared/logger'
2020
import { throttle } from 'lodash'
2121
import { Credentials } from 'aws-sdk'
2222
import { AuthUtil } from '../util/authUtil'
23+
import { TelemetryHelper } from '../util/telemetryHelper'
2324

2425
const refreshCredentials = throttle(() => {
2526
getLogger().verbose('codewhisperer: invalidating expired credentials')
@@ -135,7 +136,9 @@ export class DefaultCodeWhispererClient {
135136

136137
async createUserSdkClient(): Promise<CodeWhispererUserClient> {
137138
const isOptedOut = CodeWhispererSettings.instance.isOptoutEnabled()
139+
TelemetryHelper.instance.setFetchCredentialStartTime()
138140
const bearerToken = await AuthUtil.instance.getBearerToken()
141+
TelemetryHelper.instance.setSdkApiCallStartTime()
139142
return (await globals.sdkClientBuilder.createAwsService(
140143
Service,
141144
{

src/codewhisperer/commands/invokeRecommendation.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { KeyStrokeHandler } from '../service/keyStrokeHandler'
1414
import { isInlineCompletionEnabled } from '../util/commonUtil'
1515
import { InlineCompletionService } from '../service/inlineCompletionService'
1616
import { AuthUtil } from '../util/authUtil'
17+
import { TelemetryHelper } from '../util/telemetryHelper'
1718

1819
/**
1920
* This function is for manual trigger CodeWhisperer
@@ -81,7 +82,8 @@ export async function invokeRecommendation(
8182
if (AuthUtil.instance.isConnectionExpired()) {
8283
await AuthUtil.instance.showReauthenticatePrompt()
8384
}
84-
InlineCompletionService.instance.getPaginatedRecommendation(client, editor, 'OnDemand', config)
85+
TelemetryHelper.instance.setInvokeSuggestionStartTime()
86+
await InlineCompletionService.instance.getPaginatedRecommendation(client, editor, 'OnDemand', config)
8587
} else {
8688
if (
8789
!vsCodeState.isCodeWhispererEditing &&

src/codewhisperer/service/inlineCompletionService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ class CodeWhispererInlineCompletionItemProvider implements vscode.InlineCompleti
174174
r.references
175175
)
176176
this.nextMove = 0
177+
TelemetryHelper.instance.setFirstSuggestionShowTime()
178+
TelemetryHelper.instance.tryRecordClientComponentLatency(document.languageId)
177179
this._onDidShow.fire()
178180
if (matchedCount >= 2 || RecommendationHandler.instance.hasNextToken()) {
179181
return [item, { insertText: 'x' }]
@@ -368,6 +370,7 @@ export class InlineCompletionService {
368370
showTimedMessage(CodeWhispererConstants.noSuggestions, 2000)
369371
}
370372
}
373+
TelemetryHelper.instance.tryRecordClientComponentLatency(editor.document.languageId)
371374
}
372375

373376
async showRecommendation(indexShift: number, isFirstRecommendation: boolean = false) {

src/codewhisperer/service/keyStrokeHandler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { CodewhispererAutomatedTriggerType } from '../../shared/telemetry/teleme
1515
import { getTabSizeSetting } from '../../shared/utilities/editorUtilities'
1616
import { isInlineCompletionEnabled } from '../util/commonUtil'
1717
import { InlineCompletionService } from './inlineCompletionService'
18+
import { TelemetryHelper } from '../util/telemetryHelper'
1819

1920
const performance = globalThis.performance ?? require('perf_hooks').performance
2021

@@ -155,7 +156,8 @@ export class KeyStrokeHandler {
155156
RecommendationHandler.instance.isGenerateRecommendationInProgress = false
156157
}
157158
} else if (isInlineCompletionEnabled()) {
158-
InlineCompletionService.instance.getPaginatedRecommendation(
159+
TelemetryHelper.instance.setInvokeSuggestionStartTime()
160+
await InlineCompletionService.instance.getPaginatedRecommendation(
159161
client,
160162
editor,
161163
'AutoTrigger',

src/codewhisperer/service/recommendationHandler.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ export class RecommendationHandler {
179179
page === 0,
180180
codewhispererPromise
181181
)
182+
TelemetryHelper.instance.setSdkApiCallEndTime()
182183
latency = startTime !== 0 ? performance.now() - startTime : 0
183184
if ('recommendations' in resp) {
184185
recommendation = (resp && resp.recommendations) || []
@@ -199,6 +200,11 @@ export class RecommendationHandler {
199200
requestId = resp?.$response && resp?.$response?.requestId
200201
nextToken = resp?.nextToken ? resp?.nextToken : ''
201202
sessionId = resp?.$response?.httpResponse?.headers['x-amzn-sessionid']
203+
TelemetryHelper.instance.setFirstResponseRequestId(requestId)
204+
TelemetryHelper.instance.setSessionId(sessionId)
205+
if (nextToken === '') {
206+
TelemetryHelper.instance.setAllPaginationEndTime()
207+
}
202208
} else {
203209
getLogger().info('Invalid Request : ', JSON.stringify(req, undefined, EditorContext.getTabSize()))
204210
getLogger().verbose(`Invalid Request : ${JSON.stringify(req, undefined, EditorContext.getTabSize())}`)

src/codewhisperer/util/telemetryHelper.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,23 @@ export class TelemetryHelper {
3535

3636
public startUrl: string | undefined
3737

38+
// variables for client component latency
39+
private invokeSuggestionStartTime = 0
40+
private fetchCredentialStartTime = 0
41+
private sdkApiCallStartTime = 0
42+
private sdkApiCallEndTime = 0
43+
private firstSuggestionShowTime = 0
44+
private allPaginationEndTime = 0
45+
private firstResponseRequestId = ''
46+
private sessionId = ''
47+
3848
constructor() {
3949
this.triggerType = 'OnDemand'
4050
this.CodeWhispererAutomatedtriggerType = 'KeyStrokeCount'
4151
this.completionType = 'Line'
4252
this.cursorOffset = 0
4353
this.startUrl = ''
54+
this.sessionId = ''
4455
}
4556

4657
static #instance: TelemetryHelper
@@ -137,4 +148,84 @@ export class TelemetryHelper {
137148
public isTelemetryEnabled(): boolean {
138149
return globals.telemetry.telemetryEnabled
139150
}
151+
152+
public resetClientComponentLatencyTime() {
153+
this.invokeSuggestionStartTime = 0
154+
this.sdkApiCallStartTime = 0
155+
this.sdkApiCallEndTime = 0
156+
this.fetchCredentialStartTime = 0
157+
this.firstSuggestionShowTime = 0
158+
this.allPaginationEndTime = 0
159+
this.firstResponseRequestId = ''
160+
this.sessionId = ''
161+
}
162+
163+
/** This method is assumed to be invoked first at the start of execution **/
164+
public setInvokeSuggestionStartTime() {
165+
this.resetClientComponentLatencyTime()
166+
this.invokeSuggestionStartTime = performance.now()
167+
}
168+
169+
public setFetchCredentialStartTime() {
170+
if (this.fetchCredentialStartTime === 0 && this.invokeSuggestionStartTime !== 0) {
171+
this.fetchCredentialStartTime = performance.now()
172+
}
173+
}
174+
175+
public setSdkApiCallStartTime() {
176+
if (this.sdkApiCallStartTime === 0 && this.fetchCredentialStartTime !== 0) {
177+
this.sdkApiCallStartTime = performance.now()
178+
}
179+
}
180+
181+
public setSdkApiCallEndTime() {
182+
if (this.sdkApiCallEndTime === 0 && this.sdkApiCallStartTime !== 0) {
183+
this.sdkApiCallEndTime = performance.now()
184+
}
185+
}
186+
187+
public setAllPaginationEndTime() {
188+
if (this.allPaginationEndTime === 0 && this.sdkApiCallEndTime !== 0) {
189+
this.allPaginationEndTime = performance.now()
190+
}
191+
}
192+
193+
public setFirstSuggestionShowTime() {
194+
if (this.firstSuggestionShowTime === 0 && this.sdkApiCallEndTime !== 0) {
195+
this.firstSuggestionShowTime = performance.now()
196+
}
197+
}
198+
199+
public setFirstResponseRequestId(requestId: string) {
200+
if (this.firstResponseRequestId === '') {
201+
this.firstResponseRequestId = requestId
202+
}
203+
}
204+
205+
public setSessionId(sessionId: string) {
206+
this.sessionId = sessionId
207+
}
208+
209+
// report client component latency after all pagination call finish
210+
// and at least one suggestion is shown to the user
211+
public tryRecordClientComponentLatency(languageId: string) {
212+
if (this.firstSuggestionShowTime === 0 || this.allPaginationEndTime === 0) {
213+
return
214+
}
215+
telemetry.codewhisperer_clientComponentLatency.emit({
216+
codewhispererRequestId: this.firstResponseRequestId,
217+
codewhispererSessionId: this.sessionId,
218+
codewhispererFirstCompletionLatency: this.sdkApiCallEndTime - this.sdkApiCallStartTime,
219+
codewhispererEndToEndLatency: this.firstSuggestionShowTime - this.invokeSuggestionStartTime,
220+
codewhispererAllCompletionsLatency: this.allPaginationEndTime - this.sdkApiCallStartTime,
221+
codewhispererPostprocessingLatency: this.firstSuggestionShowTime - this.sdkApiCallEndTime,
222+
codewhispererCredentialFetchingLatency: this.sdkApiCallStartTime - this.fetchCredentialStartTime,
223+
codewhispererPreprocessingLatency: this.fetchCredentialStartTime - this.invokeSuggestionStartTime,
224+
codewhispererCompletionType: this.completionType,
225+
codewhispererTriggerType: this.triggerType,
226+
codewhispererLanguage: runtimeLanguageContext.getLanguageContext(languageId).language,
227+
credentialStartUrl: this.startUrl,
228+
})
229+
this.resetClientComponentLatencyTime()
230+
}
140231
}

0 commit comments

Comments
 (0)