Skip to content

Commit d441f68

Browse files
kaizenccgithub-actionsiliapolo
authored
feat(cli): telemetry event deploy (#698)
Extension on #631 to provide deploy telemetry events. --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license --------- Signed-off-by: github-actions <github-actions@github.com> Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: Eli Polonsky <epolon@amazon.com> Co-authored-by: Eli Polonsky <Eli.polonsky@gmail.com>
1 parent 869b109 commit d441f68

File tree

7 files changed

+104
-8
lines changed

7 files changed

+104
-8
lines changed

packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export interface CdkCliOptions extends ShellOptions {
188188
options?: string[];
189189
neverRequireApproval?: boolean;
190190
verbose?: boolean;
191+
telemetryFile?: string;
191192
}
192193

193194
export interface CdkDestroyCliOptions extends CdkCliOptions {
@@ -383,6 +384,7 @@ export class TestFixture extends ShellHelper {
383384
// use events because bar renders bad in tests
384385
'--progress', 'events',
385386
...(skipStackRename ? stackNames : this.fullStackName(stackNames)),
387+
...(options.telemetryFile ? ['--unstable=telemetry', `--telemetry-file=${options.telemetryFile}`] : []),
386388
];
387389
}
388390

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as path from 'path';
2+
import * as fs from 'fs-extra';
3+
import { integTest, withDefaultFixture } from '../../../lib';
4+
5+
jest.setTimeout(2 * 60 * 60_000); // Includes the time to acquire locks, worst-case single-threaded runtime
6+
7+
integTest(
8+
'cdk deploy with telemetry data',
9+
withDefaultFixture(async (fixture) => {
10+
const telemetryFile = path.join(fixture.integTestDir, 'telemetry.json');
11+
12+
// Deploy stack while collecting telemetry
13+
await fixture.cdkDeploy('test-1', {
14+
telemetryFile,
15+
});
16+
const json = fs.readJSONSync(telemetryFile);
17+
expect(json).toEqual([
18+
expect.objectContaining({
19+
event: expect.objectContaining({
20+
command: expect.objectContaining({
21+
path: ['deploy', '$STACKS_1'],
22+
}),
23+
state: 'SUCCEEDED',
24+
eventType: 'SYNTH',
25+
}),
26+
identifiers: expect.objectContaining({
27+
eventId: expect.stringContaining(':1'),
28+
}),
29+
}),
30+
expect.objectContaining({
31+
event: expect.objectContaining({
32+
command: expect.objectContaining({
33+
path: ['deploy', '$STACKS_1'],
34+
}),
35+
state: 'SUCCEEDED',
36+
eventType: 'DEPLOY',
37+
}),
38+
identifiers: expect.objectContaining({
39+
eventId: expect.stringContaining(':2'),
40+
}),
41+
}),
42+
expect.objectContaining({
43+
event: expect.objectContaining({
44+
command: expect.objectContaining({
45+
path: ['deploy', '$STACKS_1'],
46+
}),
47+
state: 'SUCCEEDED',
48+
eventType: 'INVOKE',
49+
}),
50+
identifiers: expect.objectContaining({
51+
eventId: expect.stringContaining(':3'),
52+
}),
53+
}),
54+
]);
55+
fs.unlinkSync(telemetryFile);
56+
}),
57+
);

packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/synth/cdk-synth-telemetry.integtest.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,3 @@ integTest(
114114
fs.unlinkSync(telemetryFile);
115115
}),
116116
);
117-

packages/aws-cdk/lib/cli/cdk-toolkit.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ import {
6565
validateSnsTopicArn,
6666
} from '../util';
6767
import { canCollectTelemetry } from './telemetry/collect-telemetry';
68+
import { cdkCliErrorName } from './telemetry/error';
69+
import { CLI_PRIVATE_SPAN } from './telemetry/messages';
70+
import type { ErrorDetails } from './telemetry/schema';
6871

6972
// Must use a require() otherwise esbuild complains about calling a namespace
7073
// eslint-disable-next-line @typescript-eslint/no-require-imports,@typescript-eslint/consistent-type-imports
@@ -519,12 +522,15 @@ export class CdkToolkit {
519522
const stackIndex = stacks.indexOf(stack) + 1;
520523
await this.ioHost.asIoHelper().defaults.info(`${chalk.bold(stack.displayName)}: deploying... [${stackIndex}/${stackCollection.stackCount}]`);
521524
const startDeployTime = new Date().getTime();
522-
523525
let tags = options.tags;
524526
if (!tags || tags.length === 0) {
525527
tags = tagsForStack(stack);
526528
}
527529

530+
// There is already a startDeployTime constant, but that does not work with telemetry.
531+
// We should integrate the two in the future
532+
const deploySpan = await this.ioHost.asIoHelper().span(CLI_PRIVATE_SPAN.DEPLOY).begin({});
533+
let error: ErrorDetails | undefined;
528534
let elapsedDeployTime = 0;
529535
try {
530536
let deployResult: SuccessfulDeployStackResult | undefined;
@@ -638,10 +644,18 @@ export class CdkToolkit {
638644
} catch (e: any) {
639645
// It has to be exactly this string because an integration test tests for
640646
// "bold(stackname) failed: ResourceNotReady: <error>"
641-
throw new ToolkitError(
647+
const wrappedError = new ToolkitError(
642648
[`❌ ${chalk.bold(stack.stackName)} failed:`, ...(e.name ? [`${e.name}:`] : []), formatErrorMessage(e)].join(' '),
643649
);
650+
651+
error = {
652+
name: cdkCliErrorName(wrappedError.name),
653+
};
654+
655+
throw wrappedError;
644656
} finally {
657+
await deploySpan.end({ error });
658+
645659
if (options.cloudWatchLogMonitor) {
646660
const foundLogGroupsResult = await findCloudWatchLogGroups(this.props.sdkProvider, asIoHelper(this.ioHost, 'deploy'), stack);
647661
options.cloudWatchLogMonitor.addLogGroups(

packages/aws-cdk/lib/cli/io-host/cli-io-host.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import type { IoHelper, ActivityPrinterProps, IActivityPrinter } from '../../../
1010
import { asIoHelper, IO, isMessageRelevantForLevel, CurrentActivityPrinter, HistoryActivityPrinter } from '../../../lib/api-private';
1111
import { StackActivityProgress } from '../../commands/deploy';
1212
import { FileTelemetrySink } from '../telemetry/file-sink';
13-
import { CLI_PRIVATE_IO } from '../telemetry/messages';
13+
import type { EventResult } from '../telemetry/messages';
14+
import { CLI_PRIVATE_IO, CLI_TELEMETRY_CODES } from '../telemetry/messages';
1415
import type { EventType } from '../telemetry/schema';
1516
import { TelemetrySession } from '../telemetry/session';
1617
import { isCI } from '../util/ci';
@@ -552,12 +553,12 @@ function targetStreamObject(x: TargetStream): NodeJS.WriteStream | undefined {
552553
}
553554
}
554555

555-
function isNoticesMessage(msg: IoMessage<unknown>) {
556+
function isNoticesMessage(msg: IoMessage<unknown>): msg is IoMessage<void> {
556557
return IO.CDK_TOOLKIT_I0100.is(msg) || IO.CDK_TOOLKIT_W0101.is(msg) || IO.CDK_TOOLKIT_E0101.is(msg) || IO.CDK_TOOLKIT_I0101.is(msg);
557558
}
558559

559-
function isTelemetryMessage(msg: IoMessage<unknown>) {
560-
return CLI_PRIVATE_IO.CDK_CLI_I1001.is(msg) || CLI_PRIVATE_IO.CDK_CLI_I2001.is(msg);
560+
function isTelemetryMessage(msg: IoMessage<unknown>): msg is IoMessage<EventResult> {
561+
return CLI_TELEMETRY_CODES.some((c) => c.is(msg));
561562
}
562563

563564
function getEventType(msg: IoMessage<unknown>): EventType {
@@ -566,6 +567,8 @@ function getEventType(msg: IoMessage<unknown>): EventType {
566567
return 'SYNTH';
567568
case CLI_PRIVATE_IO.CDK_CLI_I2001.code:
568569
return 'INVOKE';
570+
case CLI_PRIVATE_IO.CDK_CLI_I3001.code:
571+
return 'DEPLOY';
569572
default:
570573
throw new ToolkitError(`Unrecognized Telemetry Message Code: ${msg.code}`);
571574
}

packages/aws-cdk/lib/cli/telemetry/messages.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ export const CLI_PRIVATE_IO = {
3434
description: 'Command has finished executing',
3535
interface: 'EventResult',
3636
}),
37+
CDK_CLI_I3000: make.trace<EventStart>({
38+
code: 'CDK_CLI_I3000',
39+
description: 'Deploy has started',
40+
interface: 'EventStart',
41+
}),
42+
CDK_CLI_I3001: make.trace<EventResult>({
43+
code: 'CDK_CLI_I3001',
44+
description: 'Deploy has finished',
45+
interface: 'EventResult',
46+
}),
3747
};
3848

3949
/**
@@ -50,4 +60,15 @@ export const CLI_PRIVATE_SPAN = {
5060
start: CLI_PRIVATE_IO.CDK_CLI_I2000,
5161
end: CLI_PRIVATE_IO.CDK_CLI_I2001,
5262
},
63+
DEPLOY: {
64+
name: 'Deploy',
65+
start: CLI_PRIVATE_IO.CDK_CLI_I3000,
66+
end: CLI_PRIVATE_IO.CDK_CLI_I3001,
67+
},
5368
} satisfies Record<string, SpanDefinition<any, any>>;
69+
70+
export const CLI_TELEMETRY_CODES = [
71+
CLI_PRIVATE_IO.CDK_CLI_I1001,
72+
CLI_PRIVATE_IO.CDK_CLI_I2001,
73+
CLI_PRIVATE_IO.CDK_CLI_I3001,
74+
];

packages/aws-cdk/lib/cli/telemetry/schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ interface SessionEvent {
2424
readonly command: Command;
2525
}
2626

27-
export type EventType = 'SYNTH' | 'INVOKE';
27+
export type EventType = 'SYNTH' | 'INVOKE' | 'DEPLOY';
2828
export type State = 'ABORTED' | 'FAILED' | 'SUCCEEDED';
2929
interface Event extends SessionEvent {
3030
readonly state: State;

0 commit comments

Comments
 (0)