diff --git a/packages/amazonq/src/app/amazonqScan/app.ts b/packages/amazonq/src/app/amazonqScan/app.ts index bd12e3acd01..fa50e990456 100644 --- a/packages/amazonq/src/app/amazonqScan/app.ts +++ b/packages/amazonq/src/app/amazonqScan/app.ts @@ -12,6 +12,7 @@ import { AppToWebViewMessageDispatcher } from './chat/views/connector/connector' import { Messenger } from './chat/controller/messenger/messenger' import { UIMessageListener } from './chat/views/actions/uiMessageListener' import { debounce } from 'lodash' +import { ReviewTelemetryHelper } from './telemetryHelper' export function init(appContext: AmazonQAppInitContext) { const scanChatControllerEventEmitters: ScanChatControllerEventEmitters = { diff --git a/packages/amazonq/src/app/amazonqScan/telemetryHelper.ts b/packages/amazonq/src/app/amazonqScan/telemetryHelper.ts new file mode 100644 index 00000000000..94440936604 --- /dev/null +++ b/packages/amazonq/src/app/amazonqScan/telemetryHelper.ts @@ -0,0 +1,65 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { telemetry } from 'aws-core-vscode/telemetry' +import { AuthUtil } from 'aws-core-vscode/codewhisperer' + +export class ReviewTelemetryHelper { + static #instance: ReviewTelemetryHelper + + public static get instance() { + return (this.#instance ??= new this()) + } + + public recordReviewStart(scope: 'file' | 'project', fileName?: string) { + telemetry.amazonq_reviewStart.emit({ + result: 'Succeeded', + amazonqReviewScope: scope, + amazonqReviewFileName: fileName, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + } + + public recordReviewComplete( + scope: 'file' | 'project', + issuesFound: number, + criticalIssues: number, + highIssues: number, + mediumIssues: number, + lowIssues: number, + infoIssues: number, + duration: number + ) { + telemetry.amazonq_reviewComplete.emit({ + result: 'Succeeded', + amazonqReviewScope: scope, + amazonqReviewIssuesFound: issuesFound, + amazonqReviewCriticalIssues: criticalIssues, + amazonqReviewHighIssues: highIssues, + amazonqReviewMediumIssues: mediumIssues, + amazonqReviewLowIssues: lowIssues, + amazonqReviewInfoIssues: infoIssues, + amazonqReviewDuration: duration, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + } + + public recordReviewError(scope: 'file' | 'project', errorCode: string) { + telemetry.amazonq_reviewError.emit({ + result: 'Failed', + amazonqReviewScope: scope, + amazonqReviewErrorCode: errorCode, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + } + + public recordFixApplied(issueType: string, fixSuccess: boolean) { + telemetry.amazonq_reviewFixApplied.emit({ + result: fixSuccess ? 'Succeeded' : 'Failed', + amazonqReviewIssueType: issueType, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + } +} \ No newline at end of file diff --git a/packages/amazonq/src/app/development/index.ts b/packages/amazonq/src/app/development/index.ts new file mode 100644 index 00000000000..7cc7fd6cedb --- /dev/null +++ b/packages/amazonq/src/app/development/index.ts @@ -0,0 +1,26 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DevelopmentTelemetryHelper } from './telemetryHelper' + +export function recordCodeGenerationUsage( + language: string, + generatedCode: string, + filesCreated: number = 1 +) { + const linesGenerated = generatedCode.split('\n').length + DevelopmentTelemetryHelper.instance.recordCodeGeneration( + language, + linesGenerated, + filesCreated, + true + ) +} + +export function recordDevelopmentFeatureUsage(featureName: string, context?: string) { + DevelopmentTelemetryHelper.instance.recordFeatureUsage(featureName, context) +} + +export { DevelopmentTelemetryHelper } \ No newline at end of file diff --git a/packages/amazonq/src/app/development/telemetryHelper.ts b/packages/amazonq/src/app/development/telemetryHelper.ts new file mode 100644 index 00000000000..ad2d3ad32ed --- /dev/null +++ b/packages/amazonq/src/app/development/telemetryHelper.ts @@ -0,0 +1,61 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { telemetry } from 'aws-core-vscode/telemetry' +import { AuthUtil } from 'aws-core-vscode/codewhisperer' + +export class DevelopmentTelemetryHelper { + static #instance: DevelopmentTelemetryHelper + + public static get instance() { + return (this.#instance ??= new this()) + } + + public recordCodeGeneration( + language: string, + linesGenerated: number, + filesGenerated: number, + success: boolean + ) { + telemetry.amazonq_codeGeneration.emit({ + result: success ? 'Succeeded' : 'Failed', + amazonqCodeGenLanguage: language, + amazonqCodeGenLinesGenerated: linesGenerated, + amazonqCodeGenFilesGenerated: filesGenerated, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + } + + public recordProjectCreation(projectType: string, success: boolean) { + telemetry.amazonq_projectCreation.emit({ + result: success ? 'Succeeded' : 'Failed', + amazonqProjectType: projectType, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + } + + public recordDevelopmentActivity( + activityType: 'explain' | 'optimize' | 'refactor' | 'test', + language: string, + duration: number + ) { + telemetry.amazonq_developmentActivity.emit({ + result: 'Succeeded', + amazonqActivityType: activityType, + amazonqActivityLanguage: language, + amazonqActivityDuration: duration, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + } + + public recordFeatureUsage(featureName: string, context?: string) { + telemetry.amazonq_featureUsage.emit({ + result: 'Succeeded', + amazonqFeatureName: featureName, + amazonqFeatureContext: context, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + } +} \ No newline at end of file diff --git a/packages/core/src/shared/sam/cli/samCliInvoker.ts b/packages/core/src/shared/sam/cli/samCliInvoker.ts index 03d6fa249d4..7eff22e9c89 100644 --- a/packages/core/src/shared/sam/cli/samCliInvoker.ts +++ b/packages/core/src/shared/sam/cli/samCliInvoker.ts @@ -14,6 +14,7 @@ import { import * as nls from 'vscode-nls' import { SamCliSettings } from './samCliSettings' +import { SamCliTelemetryHelper } from './samCliTelemetryHelper' const localize = nls.loadMessageBundle() /** @@ -54,7 +55,9 @@ export class DefaultSamCliProcessInvoker implements SamCliProcessInvoker { getLogger().info(localize('AWS.running.command', 'Command: {0}', `${this.childProcess}`)) log.verbose(`running: ${this.childProcess}`) - return await this.childProcess.run({ + + const startTime = performance.now() + const result = await this.childProcess.run({ onStdout: (text, context) => { getDebugConsoleLogger().info(text) log.verbose(`stdout: ${text}`) @@ -66,5 +69,17 @@ export class DefaultSamCliProcessInvoker implements SamCliProcessInvoker { options?.onStderr?.(text, context) }, }) + + // Record telemetry + const duration = performance.now() - startTime + const command = invokeOptions.arguments[0] as 'build' | 'deploy' | 'init' | 'local-invoke' | 'start-api' + SamCliTelemetryHelper.instance.recordSamCommand( + command, + result.exitCode === 0, + duration, + result.exitCode !== 0 ? result.exitCode.toString() : undefined + ) + + return result } } diff --git a/packages/core/src/shared/sam/cli/samCliTelemetryHelper.ts b/packages/core/src/shared/sam/cli/samCliTelemetryHelper.ts new file mode 100644 index 00000000000..8ac4c36473a --- /dev/null +++ b/packages/core/src/shared/sam/cli/samCliTelemetryHelper.ts @@ -0,0 +1,73 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { telemetry } from '../../telemetry/telemetry' +import { AuthUtil } from '../../../codewhisperer/util/authUtil' + +export class SamCliTelemetryHelper { + static #instance: SamCliTelemetryHelper + + public static get instance() { + return (this.#instance ??= new this()) + } + + public recordSamCommand( + command: 'build' | 'deploy' | 'init' | 'local-invoke' | 'start-api', + success: boolean, + duration: number, + errorCode?: string + ) { + telemetry.amazonq_samCommand.emit({ + result: success ? 'Succeeded' : 'Failed', + amazonqSamCommandType: command, + amazonqSamCommandDuration: duration, + amazonqSamErrorCode: errorCode, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + } + + public recordSamInit( + runtime: string, + template: string, + success: boolean + ) { + telemetry.amazonq_samInit.emit({ + result: success ? 'Succeeded' : 'Failed', + amazonqSamRuntime: runtime, + amazonqSamTemplate: template, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + } + + public recordSamDeploy( + stackName: string, + region: string, + success: boolean, + resourceCount?: number + ) { + telemetry.amazonq_samDeploy.emit({ + result: success ? 'Succeeded' : 'Failed', + amazonqSamStackName: stackName, + amazonqSamRegion: region, + amazonqSamResourceCount: resourceCount, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + } + + public recordSamLocalInvoke( + functionName: string, + runtime: string, + success: boolean, + duration: number + ) { + telemetry.amazonq_samLocalInvoke.emit({ + result: success ? 'Succeeded' : 'Failed', + amazonqSamFunctionName: functionName, + amazonqSamRuntime: runtime, + amazonqSamInvokeDuration: duration, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + } +} \ No newline at end of file diff --git a/packages/core/src/shared/telemetry/amazonqTelemetryEvents.ts b/packages/core/src/shared/telemetry/amazonqTelemetryEvents.ts new file mode 100644 index 00000000000..fd6ae2443c0 --- /dev/null +++ b/packages/core/src/shared/telemetry/amazonqTelemetryEvents.ts @@ -0,0 +1,105 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// Additional telemetry events for Amazon Q Dashboard integration +declare module './telemetry.gen' { + interface TelemetryDefinitions { + // Review functionality + amazonq_reviewStart: { + result: Result + amazonqReviewScope: 'file' | 'project' + amazonqReviewFileName?: string + credentialStartUrl?: string + } + + amazonq_reviewComplete: { + result: Result + amazonqReviewScope: 'file' | 'project' + amazonqReviewIssuesFound: number + amazonqReviewCriticalIssues: number + amazonqReviewHighIssues: number + amazonqReviewMediumIssues: number + amazonqReviewLowIssues: number + amazonqReviewInfoIssues: number + amazonqReviewDuration: number + credentialStartUrl?: string + } + + amazonq_reviewError: { + result: Result + amazonqReviewScope: 'file' | 'project' + amazonqReviewErrorCode: string + credentialStartUrl?: string + } + + amazonq_reviewFixApplied: { + result: Result + amazonqReviewIssueType: string + credentialStartUrl?: string + } + + // Development functionality + amazonq_codeGeneration: { + result: Result + amazonqCodeGenLanguage: string + amazonqCodeGenLinesGenerated: number + amazonqCodeGenFilesGenerated: number + credentialStartUrl?: string + } + + amazonq_projectCreation: { + result: Result + amazonqProjectType: string + credentialStartUrl?: string + } + + amazonq_developmentActivity: { + result: Result + amazonqActivityType: 'explain' | 'optimize' | 'refactor' | 'test' + amazonqActivityLanguage: string + amazonqActivityDuration: number + credentialStartUrl?: string + } + + amazonq_featureUsage: { + result: Result + amazonqFeatureName: string + amazonqFeatureContext?: string + credentialStartUrl?: string + } + + // SAM CLI functionality + amazonq_samCommand: { + result: Result + amazonqSamCommandType: 'build' | 'deploy' | 'init' | 'local-invoke' | 'start-api' + amazonqSamCommandDuration: number + amazonqSamErrorCode?: string + credentialStartUrl?: string + } + + amazonq_samInit: { + result: Result + amazonqSamRuntime: string + amazonqSamTemplate: string + credentialStartUrl?: string + } + + amazonq_samDeploy: { + result: Result + amazonqSamStackName: string + amazonqSamRegion: string + amazonqSamResourceCount?: number + credentialStartUrl?: string + } + + amazonq_samLocalInvoke: { + result: Result + amazonqSamFunctionName: string + amazonqSamRuntime: string + amazonqSamInvokeDuration: number + credentialStartUrl?: string + } + } +} \ No newline at end of file