-
Notifications
You must be signed in to change notification settings - Fork 86
fix: collect metrics to help analyze performance problems #1124
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
Merged
+556
−116
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
2e96368
fix: not enough information to root cause performance problems
rix0rrr 6684eb7
fix(toolkit-lib): synth time is not measured accurately
rix0rrr 0ffad66
Merge branch 'huijbers/not-measuring-synth-time' into huijbers/measur…
rix0rrr b1fc30f
WIP
rix0rrr 063862f
Guess agent
rix0rrr 3304597
Version, resource count
rix0rrr 8d37850
Merge remote-tracking branch 'origin/main' into huijbers/measure-more…
rix0rrr 0e32997
Update toolkit metrics
rix0rrr 01acd0d
Boop
rix0rrr be68914
These tests don't make sense
rix0rrr 91e34b2
Some tests
rix0rrr 2203e37
Tests
rix0rrr d2994a0
Errors and warnings
rix0rrr 99ba6e4
linter issues
rix0rrr a12cc87
Lintlint
rix0rrr 3f51f20
Merge remote-tracking branch 'origin/main' into huijbers/measure-more…
rix0rrr 2da0bdc
chore: self mutation
github-actions[bot] 9a5b355
Merge remote-tracking branch 'origin/main' into huijbers/measure-more…
rix0rrr f3ee487
Merge branch 'huijbers/measure-more-perf' of github.com:aws/aws-cdk-c…
rix0rrr 245c919
Fix imports
rix0rrr 878eb70
Merge remote-tracking branch 'origin/main' into huijbers/measure-more…
rix0rrr e5d0a92
Fix integ tests
rix0rrr cceb32c
Review comments
rix0rrr 3841c14
Trigger build
rix0rrr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
.../@aws-cdk-testing/cli-integ/tests/telemetry-integ-tests/cdk-synth-guessagent.integtest.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import * as path from 'path'; | ||
| import * as fs from 'fs-extra'; | ||
| import { integTest, withDefaultFixture } from '../../lib'; | ||
|
|
||
| jest.setTimeout(2 * 60 * 60_000); // Includes the time to acquire locks, worst-case single-threaded runtime | ||
|
|
||
| integTest( | ||
| 'cdk synth telemetry contains an agent guess', | ||
| withDefaultFixture(async (fixture) => { | ||
| const telemetryFile = path.join(fixture.integTestDir, `telemetry-${Date.now()}.json`); | ||
|
|
||
| const synthOutput = await fixture.cdk( | ||
| ['synth', fixture.fullStackName('test-1'), `--telemetry-file=${telemetryFile}`], | ||
| { | ||
| verboseLevel: 3, | ||
| modEnv: { | ||
| AWS_EXECUTION_ENV: 'AmazonQ-For-CLI Version/1.23.1', | ||
| }, | ||
| }, // trace mode | ||
| ); | ||
|
|
||
| // Check the trace that telemetry was executed successfully | ||
| expect(synthOutput).toContain('Telemetry Sent Successfully'); | ||
|
|
||
| const json = fs.readJSONSync(telemetryFile); | ||
| expect(json).toEqual([ | ||
| expect.objectContaining({ | ||
| environment: expect.objectContaining({ | ||
| agent: true, | ||
| }), | ||
| }), | ||
| expect.objectContaining({ | ||
| environment: expect.objectContaining({ | ||
| agent: true, | ||
| }), | ||
| }), | ||
| ]); | ||
| fs.unlinkSync(telemetryFile); | ||
| }), | ||
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,8 +7,12 @@ import { formatTime } from '../../../util'; | |
| import type { IActionAwareIoHost } from '../io-host'; | ||
| import type { IoDefaultMessages } from './io-default-messages'; | ||
|
|
||
| /** | ||
| * These data fields are automatically added by ending a span | ||
| */ | ||
| export interface SpanEnd { | ||
| readonly duration: number; | ||
| readonly counters?: Record<string, number>; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -23,29 +27,41 @@ export interface SpanDefinition<S extends object, E extends SpanEnd> { | |
| readonly end: make.IoMessageMaker<E>; | ||
| } | ||
|
|
||
| /** | ||
| * Arguments to the span.end() function | ||
| * | ||
| * `SpanEndArguments<T>` are the fields that a user still needs to supply, it | ||
| * fields in the type `T` that aren't also in `SpanEnd`. `SpanEnd` represents | ||
| * fields that are automatically added by the underlying `end` function. | ||
| * | ||
| * Fields that are already in `SpanEnd` are still rendered as optionals, so you | ||
| * can override them (but you don't have to). | ||
| * | ||
| * - Does the following: fields that are shared between `T` and `SpanEnd` are | ||
| * made optional, and the rest of the keys of `T` are required. | ||
| * | ||
| * - If `T` is fully subsumed by the `SpanEnd` type, then an object type with | ||
| * all fields optional, OR 'void' so you can avoid passing an argument at all. | ||
| */ | ||
| type SpanEndArguments<T> = keyof T extends keyof SpanEnd | ||
| ? (Pick<Partial<SpanEnd>, keyof T & keyof SpanEnd> | void) | ||
| : Optional<T, keyof T & keyof SpanEnd>; | ||
|
|
||
| /** | ||
| * Used in conditional types to check if a type (e.g. after omitting fields) is an empty object | ||
| * This is needed because counter-intuitive neither `object` nor `{}` represent that. | ||
| */ | ||
| type EmptyObject = { | ||
| [index: string | number | symbol]: never; | ||
| }; | ||
| type EmptyObject = Record<string, never>; | ||
|
|
||
| /** | ||
| * Helper type to force a parameter to be not present of the computed type is an empty object | ||
| */ | ||
| type VoidWhenEmpty<T> = T extends EmptyObject ? void : T; | ||
|
|
||
| /** | ||
| * Helper type to force a parameter to be an empty object if the computed type is an empty object | ||
| * This is weird, but some computed types (e.g. using `Omit`) don't end up enforcing this. | ||
| */ | ||
| type ForceEmpty<T> = T extends EmptyObject ? EmptyObject : T; | ||
|
|
||
| /** | ||
| * Make some properties optional | ||
| */ | ||
| type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>; | ||
| type Optional<T, K extends keyof T> = Omit<T, K> & Pick<Partial<T>, K>; | ||
|
|
||
| /** | ||
| * Ending the span returns the observed duration | ||
|
|
@@ -67,6 +83,7 @@ export interface IMessageSpan<E extends SpanEnd> extends IActionAwareIoHost { | |
| * An IoDefaultMessages wrapped around the span. | ||
| */ | ||
| readonly defaults: IoDefaultMessages; | ||
|
|
||
| /** | ||
| * Get the time elapsed since the start | ||
| */ | ||
|
|
@@ -79,15 +96,34 @@ export interface IMessageSpan<E extends SpanEnd> extends IActionAwareIoHost { | |
| /** | ||
| * End the span with a payload | ||
| */ | ||
| end(payload: VoidWhenEmpty<Omit<E, keyof SpanEnd>>): Promise<ElapsedTime>; | ||
| end(payload: SpanEndArguments<E>): Promise<ElapsedTime>; | ||
| /** | ||
| * End the span with a message and payload | ||
| */ | ||
| end(message: string, payload: SpanEndArguments<E>): Promise<ElapsedTime>; | ||
|
|
||
| /** | ||
| * End the span with a payload, overwriting | ||
| * Increment a counter | ||
| */ | ||
| end(payload: VoidWhenEmpty<Optional<E, keyof SpanEnd>>): Promise<ElapsedTime>; | ||
| incCounter(name: string, delta?: number): void; | ||
|
|
||
| /** | ||
| * End the span with a message and payload | ||
| * Return a new timer object | ||
| * | ||
| * It will be added into the span data when it's stopped. All open timers are | ||
| * automatically stopped when the span is ended. | ||
| * | ||
| * Timers are ultimately added to the `counters` array with `<name>_ms` and | ||
| * `<name>_cnt` keys. | ||
| */ | ||
| end(message: string, payload: ForceEmpty<Optional<E, keyof SpanEnd>>): Promise<ElapsedTime>; | ||
| startTimer(name: string): ITimer; | ||
| } | ||
|
|
||
| /** | ||
| * A timer to time an operation in a span. | ||
| */ | ||
| export interface ITimer { | ||
| stop(): void; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -133,6 +169,8 @@ class MessageSpan<S extends object, E extends SpanEnd> implements IMessageSpan<E | |
| private readonly spanId: string; | ||
| private readonly startTime: number; | ||
| private readonly timingMsgTemplate: string; | ||
| private readonly counters: Record<string, number> = {}; | ||
| private readonly openTimers = new Set<ITimer>(); | ||
|
|
||
| public constructor(ioHelper: IoHelper, definition: SpanDefinition<S, E>, makeHelper: (ioHost: IActionAwareIoHost) => IoHelper) { | ||
| this.definition = definition; | ||
|
|
@@ -161,26 +199,50 @@ class MessageSpan<S extends object, E extends SpanEnd> implements IMessageSpan<E | |
| public async notify(msg: ActionLessMessage<unknown>): Promise<void> { | ||
| return this.ioHelper.notify(withSpanId(this.spanId, msg)); | ||
| } | ||
| public async end(x: any, y?: ForceEmpty<Optional<E, keyof SpanEnd>>): Promise<ElapsedTime> { | ||
| public async end(x: any, y?: SpanEndArguments<E>): Promise<ElapsedTime> { | ||
| const duration = this.time(); | ||
|
|
||
| const endInput = parseArgs<ForceEmpty<Optional<E, keyof SpanEnd>>>(x, y); | ||
| for (const t of this.openTimers) { | ||
| t.stop(); | ||
| } | ||
| this.openTimers.clear(); | ||
|
|
||
| const endInput = parseArgs<SpanEndArguments<E>>(x, y); | ||
| const endMsg = endInput.message ?? util.format(this.timingMsgTemplate, this.definition.name, duration.asSec); | ||
| const endPayload = endInput.payload; | ||
|
|
||
| await this.notify(this.definition.end.msg( | ||
| endMsg, { | ||
| duration: duration.asMs, | ||
| ...(Object.keys(this.counters).length > 0 ? { counters: this.counters } : {}), | ||
|
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. nit, doesn't matter: is there a reason you didn't want to send
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. Yeah, I like sending as little as possible. |
||
| ...endPayload, | ||
| } as E)); | ||
|
|
||
| return duration; | ||
| } | ||
|
|
||
| public incCounter(name: string, delta: number = 1): void { | ||
| this.counters[name] = (this.counters[name] ?? 0) + delta; | ||
| } | ||
|
|
||
| public async requestResponse<T>(msg: ActionLessRequest<unknown, T>): Promise<T> { | ||
| return this.ioHelper.requestResponse(withSpanId(this.spanId, msg)); | ||
| } | ||
|
|
||
| public startTimer(name: string): ITimer { | ||
| const start = Date.now(); | ||
|
|
||
| const t: ITimer = { | ||
| stop: () => { | ||
| this.openTimers.delete(t); | ||
| this.incCounter(`${name}_ms`, Math.floor(Date.now() - start) / 1000); | ||
| this.incCounter(`${name}_cnt`, 1); | ||
| }, | ||
| }; | ||
| this.openTimers.add(t); | ||
| return t; | ||
| } | ||
|
|
||
| private time() { | ||
| const elapsedTime = new Date().getTime() - this.startTime; | ||
| return { | ||
|
|
@@ -190,7 +252,7 @@ class MessageSpan<S extends object, E extends SpanEnd> implements IMessageSpan<E | |
| } | ||
| } | ||
|
|
||
| function parseArgs<S extends object>(first: any, second?: S): { message: string | undefined; payload: S } { | ||
| function parseArgs<S>(first: any, second?: S): { message: string | undefined; payload: S } { | ||
| const firstIsMessage = typeof first === 'string'; | ||
|
|
||
| // When the first argument is a string or we have a second argument, then the first arg is the message | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
i read your comments but this was still greek to me. This is what Kiro said, if true, it was a lot simpler to understand:
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.
Kiro is not entirely correct. You can still, but don't have to override the implicit values with optionals.