Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: telemetry v2 #2485

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/violet-dolphins-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@aws-amplify/platform-core': patch
---

Add TypeScript definitions for CLI telemetry usage data v2
1 change: 1 addition & 0 deletions .eslint_dictionary.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"amazoncognito",
"amplifyconfiguration",
"ampx",
"anonymize",
"anthropic",
"apns",
"apollo",
Expand Down
2 changes: 2 additions & 0 deletions packages/backend-deployer/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export type BackendDeployerOutputFormatter = {
export type DeploymentTimes = {
synthesisTime?: number;
totalTime?: number;
deploymentTime?: number;
hotSwapTime?: number;
};

// @public (undocumented)
Expand Down
14 changes: 14 additions & 0 deletions packages/backend-deployer/src/cdk_deployer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ export class CDKDeployer implements BackendDeployer {
synthesisTime: synthTimeSeconds,
totalTime:
synthTimeSeconds + (deployResult?.deploymentTimes?.totalTime || 0),
deploymentTime: deployResult?.deploymentTimes?.deploymentTime,
hotSwapTime: deployResult?.deploymentTimes?.hotSwapTime,
},
};
};
Expand Down Expand Up @@ -341,6 +343,9 @@ export class CDKDeployer implements BackendDeployer {
) => {
const regexTotalTime = /✨ {2}Total time: (\d*\.*\d*)s.*/;
const regexSynthTime = /✨ {2}Synthesis time: (\d*\.*\d*)s/;
const regexDeploymentTime = /✨ {2}Deployment time: (\d*\.*\d*)s/;
const regexHotSwappingResources = /✨ {1}hotswapping resources:/;
let isHotSwapping = false;
const reader = readline.createInterface(stdout);
for await (const line of reader) {
if (line.includes('✨')) {
Expand All @@ -353,6 +358,15 @@ export class CDKDeployer implements BackendDeployer {
if (synthTime && synthTime.length > 1 && !isNaN(+synthTime[1])) {
output.deploymentTimes.synthesisTime = +synthTime[1];
}
const deploymentTime = line.match(regexDeploymentTime);
isHotSwapping = isHotSwapping ? isHotSwapping : line.match(regexHotSwappingResources) !== null;
if (deploymentTime && deploymentTime.length > 1 && !isNaN(+deploymentTime[1])) {
if (isHotSwapping) {
output.deploymentTimes.hotSwapTime = +deploymentTime[1];
} else {
output.deploymentTimes.deploymentTime = +deploymentTime[1];
}
}
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export type DestroyResult = {
export type DeploymentTimes = {
synthesisTime?: number;
totalTime?: number;
deploymentTime?: number;
hotSwapTime?: number;
};

/**
Expand Down
43 changes: 41 additions & 2 deletions packages/cli/src/ampx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ import { extractSubCommands } from './extract_sub_commands.js';
import {
AmplifyFault,
PackageJsonReader,
TelemetryDataEmitterFactory,
UsageDataEmitterFactory,
} from '@aws-amplify/platform-core';
import { fileURLToPath } from 'node:url';
import { verifyCommandName } from './verify_command_name.js';
import { hideBin } from 'yargs/helpers';
import { PackageManagerControllerFactory, format } from '@aws-amplify/cli-core';
import { extractCommandInfo } from './extract_command_info.js';

const startTime = Date.now();

const packageJson = new PackageJsonReader().read(
fileURLToPath(new URL('../package.json', import.meta.url))
Expand All @@ -36,15 +40,38 @@ const usageDataEmitter = await new UsageDataEmitterFactory().getInstance(
dependencies
);

attachUnhandledExceptionListeners(usageDataEmitter);
const telemetryDataEmitter =
await new TelemetryDataEmitterFactory().getInstance(dependencies);

attachUnhandledExceptionListeners(usageDataEmitter, telemetryDataEmitter);

verifyCommandName();

const parser = createMainParser(libraryVersion);
const errorHandler = generateCommandFailureHandler(parser, usageDataEmitter);

const initTime = Date.now() - startTime;

// Below is a workaround in order to send data to telemetry when user force closes a prompt (ie with Ctrl+C)
// without the counter we would send both success and abort data.
// Trying to do await telemetryDataEmitter.emitAbortion in errorHandler ends up with:
// Warning: Detected unsettled top-level await
let telemetryEmitCount = 0;
// eslint-disable-next-line @typescript-eslint/no-misused-promises
process.on('beforeExit', async (code) => {
if (telemetryEmitCount !== 0) {
process.exit(code);
}
const totalTime = Date.now() - startTime;
await telemetryDataEmitter.emitAbortion(
{ totalTime, initTime },
extractCommandInfo(parser)
);
process.exit(code);
});

try {
await parser.parseAsync(hideBin(process.argv));
const totalTime = Date.now() - startTime;
const metricDimension: Record<string, string> = {};
const subCommands = extractSubCommands(parser);

Expand All @@ -53,8 +80,20 @@ try {
}

await usageDataEmitter.emitSuccess({}, metricDimension);
await telemetryDataEmitter.emitSuccess(
{ totalTime, initTime },
extractCommandInfo(parser)
);
telemetryEmitCount++;
} catch (e) {
if (e instanceof Error) {
const totalTime = Date.now() - startTime;
const errorHandler = generateCommandFailureHandler(
parser,
usageDataEmitter,
telemetryDataEmitter,
{ totalTime, initTime }
);
await errorHandler(format.error(e), e);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { LocalNamespaceResolver } from '../../backend-identifier/local_namespace
import { createSandboxSecretCommand } from './sandbox-secret/sandbox_secret_command_factory.js';
import {
PackageJsonReader,
TelemetryDataEmitterFactory,
UsageDataEmitterFactory,
} from '@aws-amplify/platform-core';
import { SandboxEventHandlerFactory } from './sandbox_event_handler_factory.js';
Expand Down Expand Up @@ -69,6 +70,14 @@ export const createSandboxCommand = (): CommandModule<
libraryVersion,
dependencies
);
},
async () => {
const dependencies = await new PackageManagerControllerFactory()
.getPackageManagerController()
.tryGetDependencies();
return await new TelemetryDataEmitterFactory().getInstance(
dependencies
);
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ClientConfigFormat } from '@aws-amplify/client-config';
import {
AmplifyFault,
AmplifyUserError,
TelemetryDataEmitter,
UsageDataEmitter,
} from '@aws-amplify/platform-core';
import assert from 'node:assert';
Expand Down Expand Up @@ -36,6 +37,14 @@ void describe('sandbox_event_handler_factory', () => {
emitFailure: emitFailureMock,
} as unknown as UsageDataEmitter;

// Telemetry data emitter mocks
const emitTelemetrySuccessMock = mock.fn();
const emitTelemetryFailureMock = mock.fn();
const telemetryDataEmitterMock = {
emitSuccess: emitTelemetrySuccessMock,
emitFailure: emitTelemetryFailureMock,
} as unknown as TelemetryDataEmitter;

const printMock = mock.method(printer, 'print');

// Class under test
Expand All @@ -45,7 +54,8 @@ void describe('sandbox_event_handler_factory', () => {
name: 'name',
type: 'sandbox',
}),
async () => usageDataEmitterMock
async () => usageDataEmitterMock,
async () => telemetryDataEmitterMock,
);

afterEach(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SandboxEventHandlerCreator } from './sandbox_command.js';
import { BackendIdentifier } from '@aws-amplify/plugin-types';
import { AmplifyError, UsageDataEmitter } from '@aws-amplify/platform-core';
import { AmplifyError, TelemetryDataEmitter, UsageDataEmitter } from '@aws-amplify/platform-core';
import { DeployResult } from '@aws-amplify/backend-deployer';
import { format, printer } from '@aws-amplify/cli-core';

Expand All @@ -15,7 +15,8 @@ export class SandboxEventHandlerFactory {
private readonly getBackendIdentifier: (
sandboxIdentifier?: string
) => Promise<BackendIdentifier>,
private readonly getUsageDataEmitter: () => Promise<UsageDataEmitter>
private readonly getUsageDataEmitter: () => Promise<UsageDataEmitter>,
private readonly getTelemetryDataEmitter: () => Promise<TelemetryDataEmitter>,
) {}

getSandboxEventHandlers: SandboxEventHandlerCreator = ({
Expand All @@ -29,6 +30,7 @@ export class SandboxEventHandlerFactory {
sandboxIdentifier
);
const usageDataEmitter = await this.getUsageDataEmitter();
const telemetryDataEmitter = await this.getTelemetryDataEmitter();
try {
await clientConfigLifecycleHandler.generateClientConfigFile(
backendIdentifier
Expand All @@ -40,6 +42,10 @@ export class SandboxEventHandlerFactory {
deployResult.deploymentTimes,
{ command: 'Sandbox' }
);
await telemetryDataEmitter.emitSuccess(
deployResult.deploymentTimes,
{ subCommands: 'SandboxEvent'}
);
}
}
} catch (error) {
Expand All @@ -60,6 +66,7 @@ export class SandboxEventHandlerFactory {
failedDeployment: [
async (...args: unknown[]) => {
const usageDataEmitter = await this.getUsageDataEmitter();
const telemetryDataEmitter = await this.getTelemetryDataEmitter();
if (args.length == 0 || !args[0]) {
return;
}
Expand All @@ -68,13 +75,23 @@ export class SandboxEventHandlerFactory {
await usageDataEmitter.emitFailure(deployError, {
command: 'Sandbox',
});
await telemetryDataEmitter.emitFailure(
deployError,
undefined,
{ subCommands: 'SandboxEvent'}
);
} else {
await usageDataEmitter.emitFailure(
AmplifyError.fromError(deployError),
{
command: 'Sandbox',
}
);
await telemetryDataEmitter.emitFailure(
AmplifyError.fromError(deployError),
undefined,
{ subCommands: 'SandboxEvent'}
);
}
},
],
Expand Down
Loading
Loading