Skip to content

Commit 9db64b1

Browse files
authored
feat(transform): improve telemetry (#5403)
* Amazon Q Code Transform: Improve telemetry to capture transform journey * Update globals.clock.Date usage across transform code path * Add tech debt unit test * Use run() to emit telemetry
1 parent 4b3804f commit 9db64b1

File tree

16 files changed

+343
-257
lines changed

16 files changed

+343
-257
lines changed

package-lock.json

Lines changed: 4 additions & 4 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
@@ -68,7 +68,7 @@
6868
"webpack-merge": "^5.10.0"
6969
},
7070
"dependencies": {
71-
"@aws-toolkits/telemetry": "^1.0.229",
71+
"@aws-toolkits/telemetry": "^1.0.232",
7272
"vscode-nls": "^5.2.0",
7373
"vscode-nls-dev": "^4.0.4"
7474
}

packages/core/src/amazonqGumby/chat/controller/controller.ts

Lines changed: 118 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
startTransformByQ,
2828
stopTransformByQ,
2929
validateCanCompileProject,
30+
setMaven,
3031
} from '../../../codewhisperer/commands/startTransformByQ'
3132
import { JDKVersion, TransformationCandidateProject, transformByQState } from '../../../codewhisperer/models/model'
3233
import {
@@ -53,6 +54,8 @@ import { getAuthType } from '../../../codewhisperer/service/transformByQ/transfo
5354
import DependencyVersions from '../../models/dependencies'
5455
import { getStringHash } from '../../../shared/utilities/textUtilities'
5556
import { getTelemetryReasonDesc } from '../../../shared/errors'
57+
import { getVersionData } from '../../../codewhisperer/service/transformByQ/transformMavenHandler'
58+
5659
// These events can be interactions within the chat,
5760
// or elsewhere in the IDE
5861
export interface ChatControllerEventEmitters {
@@ -179,47 +182,64 @@ export class GumbyController {
179182
}
180183

181184
private async transformInitiated(message: any) {
182-
// check that a project is open
183-
const workspaceFolders = vscode.workspace.workspaceFolders
184-
if (workspaceFolders === undefined || workspaceFolders.length === 0) {
185-
this.messenger.sendUnrecoverableErrorResponse('no-project-found', message.tabID)
186-
return
187-
}
188-
189-
// check that the session is authenticated
185+
// Start /transform chat flow
190186
const session: Session = this.sessionStorage.getSession()
191-
try {
192-
const authState = await AuthUtil.instance.getChatAuthState()
193-
if (authState.amazonQ !== 'connected') {
194-
void this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID)
195-
session.isAuthenticating = true
196-
return
197-
}
187+
CodeTransformTelemetryState.instance.setSessionId()
198188

199-
switch (this.sessionStorage.getSession().conversationState) {
200-
case ConversationState.JOB_SUBMITTED:
201-
this.messenger.sendAsyncEventProgress(
202-
message.tabID,
203-
true,
204-
undefined,
205-
GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE
206-
)
207-
this.messenger.sendJobSubmittedMessage(message.tabID)
189+
try {
190+
await telemetry.codeTransform_initiateTransform.run(async () => {
191+
const authType = await getAuthType()
192+
telemetry.record({
193+
codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
194+
credentialSourceId: authType,
195+
})
196+
197+
// check that a project is open
198+
const workspaceFolders = vscode.workspace.workspaceFolders
199+
if (workspaceFolders === undefined || workspaceFolders.length === 0) {
200+
this.messenger.sendUnrecoverableErrorResponse('no-project-found', message.tabID)
201+
telemetry.record({ result: MetadataResult.Fail, reason: 'no-project-found' })
208202
return
209-
case ConversationState.COMPILING:
210-
this.messenger.sendAsyncEventProgress(
211-
message.tabID,
212-
true,
213-
undefined,
214-
GumbyNamedMessages.COMPILATION_PROGRESS_MESSAGE
215-
)
216-
this.messenger.sendCompilationInProgress(message.tabID)
203+
}
204+
205+
// check that the session is authenticated
206+
const authState = await AuthUtil.instance.getChatAuthState()
207+
if (authState.amazonQ !== 'connected') {
208+
void this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID)
209+
session.isAuthenticating = true
210+
telemetry.record({ result: MetadataResult.Fail, reason: 'auth-failed' })
217211
return
218-
}
219-
CodeTransformTelemetryState.instance.setSessionId()
220-
this.messenger.sendTransformationIntroduction(message.tabID)
212+
}
221213

222-
// start /transform chat flow
214+
// If previous transformation was already running
215+
switch (this.sessionStorage.getSession().conversationState) {
216+
case ConversationState.JOB_SUBMITTED:
217+
this.messenger.sendAsyncEventProgress(
218+
message.tabID,
219+
true,
220+
undefined,
221+
GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE
222+
)
223+
this.messenger.sendJobSubmittedMessage(message.tabID)
224+
return
225+
case ConversationState.COMPILING:
226+
this.messenger.sendAsyncEventProgress(
227+
message.tabID,
228+
true,
229+
undefined,
230+
GumbyNamedMessages.COMPILATION_PROGRESS_MESSAGE
231+
)
232+
this.messenger.sendCompilationInProgress(message.tabID)
233+
return
234+
}
235+
this.messenger.sendTransformationIntroduction(message.tabID)
236+
})
237+
} catch (e: any) {
238+
// if there was an issue getting the list of valid projects, the error message will be shown here
239+
this.messenger.sendErrorMessage(e.message, message.tabID)
240+
}
241+
242+
try {
223243
const validProjects = await this.validateProjectsWithReplyOnError(message)
224244
if (validProjects.length > 0) {
225245
this.sessionStorage.getSession().updateCandidateProjects(validProjects)
@@ -233,14 +253,30 @@ export class GumbyController {
233253

234254
private async validateProjectsWithReplyOnError(message: any): Promise<TransformationCandidateProject[]> {
235255
let telemetryJavaVersion = JDKToTelemetryValue(JDKVersion.UNSUPPORTED) as CodeTransformJavaSourceVersionsAllowed
256+
236257
let err
237258
try {
238-
const validProjects = await getValidCandidateProjects()
239-
if (validProjects.length > 0) {
240-
// validProjects[0].JDKVersion will be undefined if javap errors out or no .class files found, so call it UNSUPPORTED
241-
const javaVersion = validProjects[0].JDKVersion ?? JDKVersion.UNSUPPORTED
242-
telemetryJavaVersion = JDKToTelemetryValue(javaVersion) as CodeTransformJavaSourceVersionsAllowed
243-
}
259+
const validProjects = await telemetry.codeTransform_validateProject.run(async () => {
260+
telemetry.record({
261+
codeTransformBuildSystem: 'Maven', // default for Maven until we add undefined field to CodeTransformBuildSystem
262+
codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
263+
})
264+
265+
const validProjects = await getValidCandidateProjects()
266+
if (validProjects.length > 0) {
267+
// validProjects[0].JDKVersion will be undefined if javap errors out or no .class files found, so call it UNSUPPORTED
268+
const javaVersion = validProjects[0].JDKVersion ?? JDKVersion.UNSUPPORTED
269+
telemetryJavaVersion = JDKToTelemetryValue(javaVersion) as CodeTransformJavaSourceVersionsAllowed
270+
}
271+
telemetry.record({ codeTransformLocalJavaVersion: telemetryJavaVersion })
272+
273+
await setMaven()
274+
const versionInfo = await getVersionData()
275+
const mavenVersionInfoMessage = `${versionInfo[0]} (${transformByQState.getMavenName()})`
276+
telemetry.record({ buildSystemVersion: mavenVersionInfoMessage })
277+
278+
return validProjects
279+
})
244280
return validProjects
245281
} catch (e: any) {
246282
if (e instanceof NoJavaProjectsFoundError) {
@@ -252,7 +288,7 @@ export class GumbyController {
252288
}
253289
err = e
254290
} finally {
255-
// New projectDetails metric should always be fired whether the project was valid or invalid
291+
// TODO: remove deprecated metric once BI started using new metrics
256292
telemetry.codeTransform_projectDetails.emit({
257293
passive: true,
258294
codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
@@ -269,9 +305,14 @@ export class GumbyController {
269305
const typedAction = MessengerUtils.stringToEnumValue(ButtonActions, message.action as any)
270306
switch (typedAction) {
271307
case ButtonActions.CONFIRM_TRANSFORMATION_FORM:
272-
await this.initiateTransformationOnProject(message)
308+
await this.handleUserProjectSelection(message)
273309
break
274310
case ButtonActions.CANCEL_TRANSFORMATION_FORM:
311+
telemetry.codeTransform_submitSelection.emit({
312+
codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
313+
userChoice: 'Cancel',
314+
result: MetadataResult.Pass,
315+
})
275316
this.messenger.sendJobFinishedMessage(message.tabID, CodeWhispererConstants.jobCancelledChatMessage)
276317
break
277318
case ButtonActions.VIEW_TRANSFORMATION_HUB:
@@ -306,27 +347,38 @@ export class GumbyController {
306347
}
307348

308349
// prompt user to pick project and specify source JDK version
309-
private async initiateTransformationOnProject(message: any) {
310-
const authType = await getAuthType()
311-
telemetry.codeTransform_jobStart.emit({
312-
codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
313-
credentialSourceId: authType,
314-
result: MetadataResult.Pass,
315-
})
316-
const pathToProject: string = message.formSelectedValues['GumbyTransformProjectForm']
317-
const toJDKVersion: JDKVersion = message.formSelectedValues['GumbyTransformJdkToForm']
318-
const fromJDKVersion: JDKVersion = message.formSelectedValues['GumbyTransformJdkFromForm']
350+
private async handleUserProjectSelection(message: any) {
351+
await telemetry.codeTransform_submitSelection.run(async () => {
352+
const pathToProject: string = message.formSelectedValues['GumbyTransformProjectForm']
353+
const toJDKVersion: JDKVersion = message.formSelectedValues['GumbyTransformJdkToForm']
354+
const fromJDKVersion: JDKVersion = message.formSelectedValues['GumbyTransformJdkFromForm']
355+
356+
telemetry.record({
357+
codeTransformJavaSourceVersionsAllowed: JDKToTelemetryValue(
358+
fromJDKVersion
359+
) as CodeTransformJavaSourceVersionsAllowed,
360+
codeTransformJavaTargetVersionsAllowed: JDKToTelemetryValue(
361+
toJDKVersion
362+
) as CodeTransformJavaTargetVersionsAllowed,
363+
codeTransformProjectId: pathToProject === undefined ? telemetryUndefined : getStringHash(pathToProject),
364+
userChoice: 'Confirm',
365+
})
319366

320-
const projectName = path.basename(pathToProject)
321-
this.messenger.sendProjectSelectionMessage(projectName, fromJDKVersion, toJDKVersion, message.tabID)
367+
const projectName = path.basename(pathToProject)
368+
this.messenger.sendProjectSelectionMessage(projectName, fromJDKVersion, toJDKVersion, message.tabID)
322369

323-
if (fromJDKVersion === JDKVersion.UNSUPPORTED) {
324-
this.messenger.sendUnrecoverableErrorResponse('unsupported-source-jdk-version', message.tabID)
325-
return
326-
}
370+
if (fromJDKVersion === JDKVersion.UNSUPPORTED) {
371+
this.messenger.sendUnrecoverableErrorResponse('unsupported-source-jdk-version', message.tabID)
372+
telemetry.record({
373+
result: MetadataResult.Fail,
374+
reason: 'unsupported-source-jdk-version',
375+
})
376+
return
377+
}
327378

328-
await processTransformFormInput(pathToProject, fromJDKVersion, toJDKVersion)
329-
await this.validateBuildWithPromptOnError(message)
379+
await processTransformFormInput(pathToProject, fromJDKVersion, toJDKVersion)
380+
await this.validateBuildWithPromptOnError(message)
381+
})
330382
}
331383

332384
private async prepareProjectForSubmission(message: { pathToJavaHome: string; tabID: string }): Promise<void> {
@@ -338,6 +390,7 @@ export class GumbyController {
338390
}
339391

340392
const projectPath = transformByQState.getProjectPath()
393+
// TODO: remove deprecated metric once BI started using new metrics
341394
telemetry.codeTransform_jobStartedCompleteFromPopupDialog.emit({
342395
codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
343396
codeTransformJavaSourceVersionsAllowed: JDKToTelemetryValue(
@@ -349,6 +402,8 @@ export class GumbyController {
349402
codeTransformProjectId: projectPath === undefined ? telemetryUndefined : getStringHash(projectPath),
350403
result: MetadataResult.Pass,
351404
})
405+
406+
// Pre-build project locally
352407
try {
353408
this.sessionStorage.getSession().conversationState = ConversationState.COMPILING
354409
this.messenger.sendCompilationInProgress(message.tabID)
@@ -382,6 +437,7 @@ export class GumbyController {
382437

383438
private async validateBuildWithPromptOnError(message: any | undefined = undefined): Promise<void> {
384439
try {
440+
// Check Java Home is set (not yet prebuilding)
385441
await validateCanCompileProject()
386442
} catch (err: any) {
387443
if (err instanceof JavaHomeNotSetError) {

packages/core/src/amazonqGumby/telemetry/codeTransformTelemetry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
CodeTransformJavaTargetVersionsAllowed,
1212
} from '../../shared/telemetry/telemetry'
1313
import { JDKVersion } from '../../codewhisperer/models/model'
14+
import globals from '../../shared/extensionGlobals'
1415

1516
export const telemetryUndefined = 'undefined'
1617

@@ -89,4 +90,4 @@ export const javapOutputToTelemetryValue = (javapCommandLineOutput: string) => {
8990
}
9091
}
9192

92-
export const calculateTotalLatency = (startTime: number): number => Date.now() - startTime
93+
export const calculateTotalLatency = (startTime: number): number => globals.clock.Date.now() - startTime

packages/core/src/amazonqGumby/telemetry/codeTransformTelemetryState.ts

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

66
import { randomUUID } from '../../shared/crypto'
77
import { codeTransformMetaDataToJsonString, ICodeTransformMetaData } from './codeTransformMetadata'
8+
import globals from '../../shared/extensionGlobals'
89

910
interface ICodeTransformTelemetryState {
1011
sessionId: string
@@ -19,7 +20,7 @@ export class CodeTransformTelemetryState {
1920
private constructor() {
2021
this.mainState = {
2122
sessionId: randomUUID(),
22-
sessionStartTime: Date.now(),
23+
sessionStartTime: globals.clock.Date.now(),
2324
resultStatus: '',
2425
codeTransformMetadata: {},
2526
}
@@ -36,7 +37,7 @@ export class CodeTransformTelemetryState {
3637
this.mainState.sessionId = randomUUID()
3738
}
3839
public setStartTime = () => {
39-
this.mainState.sessionStartTime = Date.now()
40+
this.mainState.sessionStartTime = globals.clock.Date.now()
4041
}
4142
public setResultStatus = (newValue: string) => {
4243
this.mainState.resultStatus = newValue

0 commit comments

Comments
 (0)