-
Notifications
You must be signed in to change notification settings - Fork 103
feat: telemetry v2 #2485
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
Changes from all commits
65b3566
f8fcec6
d085c26
099b13e
c65d304
b515676
0ca1c3f
8dcce09
51c7839
347bc83
0d00cd1
d0f748a
f403651
ce9c91a
46b83c6
67f4184
2294d58
5963b6d
064e113
8ef823d
68c3a79
c0e29e9
b1826fe
5239b78
5f9e93d
d7c758c
79dee7e
2a08ebf
38e3dd3
e726051
6186822
68b4d1a
da8d3c5
49a97e6
8e4c765
845961a
5201dc4
7d4f367
c7d847f
b7c6291
4d30d67
b8adb03
4e5484d
b2d8c8c
0ee8e2a
b624e52
4aceea0
34581d6
851fa96
db32583
9726840
9dc20f8
01943c3
925fac9
81b9274
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| --- | ||
| '@aws-amplify/backend-deployer': minor | ||
| '@aws-amplify/platform-core': minor | ||
| '@aws-amplify/cli-core': minor | ||
| '@aws-amplify/sandbox': minor | ||
| '@aws-amplify/backend-cli': patch | ||
| --- | ||
|
|
||
| add new version of telemetry |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ import { | |
| AmplifyError, | ||
| AmplifyUserError, | ||
| BackendLocator, | ||
| TelemetryDataEmitter, | ||
| } from '@aws-amplify/platform-core'; | ||
| import { DeployProps } from './cdk_deployer_singleton_factory.js'; | ||
| import { CDKDeploymentError, CdkErrorMapper } from './cdk_error_mapper.js'; | ||
|
|
@@ -76,12 +77,24 @@ void describe('invokeCDKCommand', () => { | |
| fromAssemblyDirectory: fromAssemblyDirectoryMock, | ||
| } as unknown as Toolkit; | ||
|
|
||
| const mockTelemetryEmitSuccess = mock.fn(); | ||
| const mockTelemetryEmitFailure = mock.fn(); | ||
| const mockTelemetryEmitAbortion = mock.fn(); | ||
| const telemetryDataEmitter = { | ||
| emitSuccess: mockTelemetryEmitSuccess, | ||
| emitFailure: mockTelemetryEmitFailure, | ||
| emitAbortion: mockTelemetryEmitAbortion, | ||
| } as unknown as TelemetryDataEmitter; | ||
|
|
||
| const cdkErrorMapper = new CdkErrorMapper(formatterStub); | ||
|
|
||
| const invoker = new CDKDeployer( | ||
| new CdkErrorMapper(formatterStub), | ||
| cdkErrorMapper, | ||
| backendLocator, | ||
| packageManagerControllerMock as never, | ||
| cdkToolkit, | ||
| mockIoHost, | ||
| telemetryDataEmitter, | ||
| ); | ||
|
|
||
| const tsCompilerMock = mock.method(invoker, 'compileProject', () => {}); | ||
|
|
@@ -94,6 +107,7 @@ void describe('invokeCDKCommand', () => { | |
| fromAssemblyBuilderMock.mock.resetCalls(); | ||
| fromAssemblyDirectoryMock.mock.resetCalls(); | ||
| tsCompilerMock.mock.resetCalls(); | ||
| mockTelemetryEmitFailure.mock.resetCalls(); | ||
| }); | ||
|
|
||
| void it('handles options for branch deployments', async () => { | ||
|
|
@@ -203,6 +217,9 @@ void describe('invokeCDKCommand', () => { | |
| contextualTsCompilerCommandMock.mock.calls[0].arguments, | ||
| ['amplify'], | ||
| ); | ||
|
|
||
| // can't test arguments of mockTelemetryEmitFailure because of flakiness around stack trace and latency times | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we can't assert values, we could at least assert presence of certain elements in the event. |
||
| assert.strictEqual(mockTelemetryEmitFailure.mock.callCount(), 1); | ||
| }); | ||
|
|
||
| void it('throws the original synth error if the synth failed but tsc succeeded', async () => { | ||
|
|
@@ -232,6 +249,9 @@ void describe('invokeCDKCommand', () => { | |
| assert.equal(synthMock.mock.callCount(), 1); | ||
|
|
||
| assert.deepStrictEqual(tsCompilerMock.mock.calls[0].arguments, ['amplify']); | ||
|
|
||
| // can't test arguments of mockTelemetryEmitFailure because of flakiness around stack trace and latency times | ||
| assert.strictEqual(mockTelemetryEmitFailure.mock.callCount(), 1); | ||
| }); | ||
|
|
||
| void it('throws the original synth error if the synth failed to generate function env declaration files', async () => { | ||
|
|
@@ -275,11 +295,15 @@ void describe('invokeCDKCommand', () => { | |
| assert.strictEqual(tsCompilerMock.mock.callCount(), 1); | ||
|
|
||
| assert.deepStrictEqual(tsCompilerMock.mock.calls[0].arguments, ['amplify']); | ||
|
|
||
| // can't test arguments of mockTelemetryEmitFailure because of flakiness around stack trace and latency times | ||
| assert.strictEqual(mockTelemetryEmitFailure.mock.callCount(), 1); | ||
| }); | ||
|
|
||
| void it('returns human readable errors', async () => { | ||
| const accessDeniedError = new Error('Access Denied'); | ||
| synthMock.mock.mockImplementationOnce(() => { | ||
| throw new Error('Access Denied'); | ||
| throw accessDeniedError; | ||
| }); | ||
|
|
||
| await assert.rejects( | ||
|
|
@@ -294,6 +318,9 @@ void describe('invokeCDKCommand', () => { | |
| return true; | ||
| }, | ||
| ); | ||
|
|
||
| // can't test arguments of mockTelemetryEmitFailure because of flakiness around stack trace and latency times | ||
| assert.strictEqual(mockTelemetryEmitFailure.mock.callCount(), 1); | ||
| }); | ||
|
|
||
| void it('displays actionable error when older version of node is used during branch deployments', async () => { | ||
|
|
@@ -321,6 +348,9 @@ void describe('invokeCDKCommand', () => { | |
| }, | ||
| ); | ||
|
|
||
| // can't test arguments of mockTelemetryEmitFailure because of flakiness around stack trace and latency times | ||
| assert.strictEqual(mockTelemetryEmitFailure.mock.callCount(), 1); | ||
|
|
||
| synthMock.mock.mockImplementationOnce(() => { | ||
| throw olderNodeVersionError; | ||
| }); | ||
|
|
@@ -341,5 +371,8 @@ void describe('invokeCDKCommand', () => { | |
| return true; | ||
| }, | ||
| ); | ||
|
|
||
| // can't test arguments of mockTelemetryEmitFailure because of flakiness around stack trace and latency times | ||
| assert.strictEqual(mockTelemetryEmitFailure.mock.callCount(), 2); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -15,6 +15,8 @@ import { | |||||
| AmplifyFault, | ||||||
| BackendLocator, | ||||||
| CDKContextKey, | ||||||
| LatencyDetails, | ||||||
| TelemetryDataEmitter, | ||||||
| } from '@aws-amplify/platform-core'; | ||||||
| import path from 'path'; | ||||||
| import { | ||||||
|
|
@@ -47,6 +49,7 @@ export class CDKDeployer implements BackendDeployer { | |||||
| private readonly packageManagerController: PackageManagerController, | ||||||
| private readonly cdkToolkit: Toolkit, | ||||||
| private readonly ioHost: AmplifyIOHost, | ||||||
| private readonly telemetryDataEmitter: TelemetryDataEmitter, | ||||||
| ) {} | ||||||
|
|
||||||
| /** | ||||||
|
|
@@ -94,7 +97,9 @@ export class CDKDeployer implements BackendDeployer { | |||||
| action: 'amplify', | ||||||
| time: new Date(), | ||||||
| level: 'result', | ||||||
| data: undefined, | ||||||
| data: { | ||||||
| duration: synthTimeSeconds * 1000, // convert back to ms for telemetry | ||||||
| }, | ||||||
| }); | ||||||
|
|
||||||
| // Typescript compilation. For type related errors, we prefer to show errors from TS to customers rather than synth | ||||||
|
|
@@ -112,16 +117,36 @@ export class CDKDeployer implements BackendDeployer { | |||||
| try { | ||||||
| await this.compileProject(path.dirname(this.backendLocator.locate())); | ||||||
| } catch (typeError) { | ||||||
| const synthTime = synthTimeSeconds * 1000; // convert back to ms | ||||||
| const totalTime = synthTime + (Date.now() - typeCheckStartTime); | ||||||
| const latencyDetails: LatencyDetails = { | ||||||
| total: totalTime, | ||||||
| synthesis: synthTime, | ||||||
| }; | ||||||
| if ( | ||||||
| synthError && | ||||||
| AmplifyError.isAmplifyError(typeError) && | ||||||
| typeError.name === 'FunctionEnvVarFileNotGeneratedError' | ||||||
| ) { | ||||||
| const error = this.cdkErrorMapper.getAmplifyError( | ||||||
| synthError, | ||||||
| backendId.type, | ||||||
| ); | ||||||
| await this.telemetryDataEmitter.emitFailure(error, latencyDetails, { | ||||||
| subCommands: 'SandboxEvent', | ||||||
| }); | ||||||
|
Comment on lines
+135
to
+137
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here and below. we shouldn't be encoding "sandbox" logic in deployer. I'm guessing that the problem being solved here is to get some latency metrics in error case.
Edit: after reaching end of this PR I notices we haven't touched this piece amplify-backend/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.ts Line 40 in 26f122d
amplify-backend/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.ts Line 76 in 26f122d
perhaps it would be possible to plumb metrics from deployer up to there and just replace emitter call there ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, this won't always necessarily be exclusively sandbox but I believe we would still want to capture this even for
I did touch those places before but they were intentionally removed after merging in changes from pretty sandbox, see my previous comment #2485 (comment)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Btw. Some other variation of 2. above would be
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some other thought. @Amplifiyer plz take a look at this discussion and share your thoughts.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes we would double emit, which I believe is what is happening today for sandbox but not for pipeline deploy: 1 emit for Edit: And on this note I believe we currently don't have the granularity for time metrics for pipeline deploy (we have total time it took to execute the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the double emit, I don't see that as a problem, 1 is a deployment event and 1 is a command event. The deployment could be a failure from customer issue, but the command would have been a success.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I forgot I could add more As for double emit, since this would also happen for pipeline deploy if we are using some form of event logger (new or existing) then I would probably rename the sub command
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
To keep concerns separate for logging versus emitting metrics in case we are adding a bunch of logic for telemetry.
For this, why don't we emit from Sandbox when deployer returns with an error. It's sandbox where we swallow errors, so we should be able to emit there?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
+1 on that one.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Problem is we don't know when a hotswap happens instead of full deployment without CDK toolkit code |
||||||
| // synth has failed and we don't have auto generated function environment definition files. This | ||||||
| // resulted in the exception caught here, which is not very useful for the customers. | ||||||
| // We instead throw the synth error for customers to fix what caused the synth to fail. | ||||||
| throw this.cdkErrorMapper.getAmplifyError(synthError, backendId.type); | ||||||
| throw error; | ||||||
| } | ||||||
| const error = this.cdkErrorMapper.getAmplifyError( | ||||||
| typeError as Error, | ||||||
| backendId.type, | ||||||
| ); | ||||||
| await this.telemetryDataEmitter.emitFailure(error, latencyDetails, { | ||||||
| subCommands: 'SandboxEvent', | ||||||
| }); | ||||||
| throw typeError; | ||||||
| } finally { | ||||||
| const typeCheckTimeSeconds = | ||||||
|
|
@@ -132,14 +157,29 @@ export class CDKDeployer implements BackendDeployer { | |||||
| action: 'amplify', | ||||||
| time: new Date(), | ||||||
| level: 'result', | ||||||
| data: undefined, | ||||||
| data: { | ||||||
| duration: typeCheckTimeSeconds * 1000, // convert back to ms for telemetry | ||||||
| }, | ||||||
| }); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // If typescript compilation was successful but synth had failed, we throw synth error | ||||||
| if (synthError) { | ||||||
| throw this.cdkErrorMapper.getAmplifyError(synthError, backendId.type); | ||||||
| const synthTime = synthTimeSeconds * 1000; // convert back to ms | ||||||
| const totalTime = synthTime + (Date.now() - typeCheckStartTime); | ||||||
| const latencyDetails: LatencyDetails = { | ||||||
| total: totalTime, | ||||||
| synthesis: synthTime, | ||||||
| }; | ||||||
| const error = this.cdkErrorMapper.getAmplifyError( | ||||||
| synthError, | ||||||
| backendId.type, | ||||||
| ); | ||||||
| await this.telemetryDataEmitter.emitFailure(error, latencyDetails, { | ||||||
| subCommands: 'SandboxEvent', | ||||||
| }); | ||||||
| throw error; | ||||||
| } | ||||||
|
|
||||||
| // Perform actual deployment. CFN or hotswap | ||||||
|
|
@@ -157,8 +197,24 @@ export class CDKDeployer implements BackendDeployer { | |||||
| requireApproval: | ||||||
| backendId.type !== 'sandbox' ? RequireApproval.NEVER : undefined, | ||||||
| }); | ||||||
| } catch (error) { | ||||||
| throw this.cdkErrorMapper.getAmplifyError(error as Error, backendId.type); | ||||||
| } catch (deployError) { | ||||||
| const synthTime = synthTimeSeconds * 1000; // convert back to ms | ||||||
| const deploymentTime = Date.now() - deployStartTime; | ||||||
| const totalTime = | ||||||
| synthTime + (Date.now() - typeCheckStartTime) + deploymentTime; | ||||||
| const latencyDetails: LatencyDetails = { | ||||||
| total: totalTime, | ||||||
| synthesis: synthTime, | ||||||
| deployment: deploymentTime, | ||||||
| }; | ||||||
| const error = this.cdkErrorMapper.getAmplifyError( | ||||||
| deployError as Error, | ||||||
| backendId.type, | ||||||
| ); | ||||||
| await this.telemetryDataEmitter.emitFailure(error, latencyDetails, { | ||||||
| subCommands: 'SandboxEvent', | ||||||
| }); | ||||||
| throw error; | ||||||
| } | ||||||
|
|
||||||
| return { | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason that we're passing telemetry emitter to deployer?
We used to have deployer to return times to sandbox and sandbox emitting the metric.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We used to use
amplify-backend/packages/backend-deployer/src/cdk_deployer.ts
Line 340 in 7d4f367
With this gone after the introduction of CDK toolkit we now have to rely on the events from CDK which are already being captured in
AmplifyEventLogger.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also the event logging captures when a hotswap happens, I can try to work on copying how we used to do this but we lose the ability to differentiate hotswap from deployment times (so in that case maybe we remove hotswap from telemetry schema)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That can also be achieved if we emit this data from deployer via return type and/or exception. Let's continue this discussion below in #2485 (comment)