-
Notifications
You must be signed in to change notification settings - Fork 146
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(commons): centralize cold start heuristic #547
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
86c476f
feat: added Utility common class w/ cold start heuristic + tests
dreamorosi 9c905b7
feat: exported new Utility class
dreamorosi 9088c7f
chore: (housekeeping) added missing jest group comments to LambdaInte…
dreamorosi 40c6afd
Update packages/commons/src/Utility.ts
dreamorosi 25fd4a0
Update packages/commons/src/Utility.ts
dreamorosi 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 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,76 @@ | ||
/** | ||
* ## Intro | ||
* Utility is a base class that other Powertools utilites can extend to inherit shared logic. | ||
* | ||
* | ||
* ## Key features | ||
* * Cold Start heuristic to determine if the current | ||
* | ||
* ## Usage | ||
* | ||
* ### Cold Start | ||
* | ||
* Cold start is a term commonly used to describe the `Init` phase of a Lambda function. In this phase, Lambda creates or unfreezes an execution environment with the configured resources, downloads the code for the function and all layers, initializes any extensions, initializes the runtime, and then runs the function’s initialization code (the code outside the main handler). The Init phase happens either during the first invocation, or in advance of function invocations if you have enabled provisioned concurrency. | ||
* | ||
* To learn more about the Lambda execution environment lifecycle, see the [Execution environment section](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html) of the AWS Lambda documentation. | ||
* | ||
* As a Powertools user you probably won't be using this class directly, in fact if you use other Powertools utilities the cold start heuristic found here is already used to: | ||
* * Add a `coldStart` key to the structured logs when injecting context information in `Logger` | ||
* * Emit a metric during a cold start function invocation in `Metrics` | ||
* * Annotate the invocation segment with a `coldStart` key in `Tracer` | ||
* | ||
* If you want to use this logic in your own utilities, `Utility` provides two methods: | ||
* | ||
* #### `getColdStart()` | ||
* | ||
* Since the `Utility` class is instantiated outside of the Lambda handler it will persist across invocations of the same execution environment. This means that if you call `getColdStart()` multiple times, it will return `true` during the first invocation, and `false` afterwards. | ||
* | ||
* @example | ||
* ```typescript | ||
* import { Utility } from '@aws-lambda-powertools/commons'; | ||
* | ||
* const utility = new Utility(); | ||
* | ||
* export const handler = async (_event: any, _context: any) => { | ||
* utility.getColdStart(); | ||
* }; | ||
* ``` | ||
* | ||
* #### `isColdStart()` | ||
* | ||
* This method is an alias of `getColdStart()` and is exposed for convenience and better readability in certain usages. | ||
* | ||
* @example | ||
* ```typescript | ||
* import { Utility } from '@aws-lambda-powertools/commons'; | ||
* | ||
* const utility = new Utility(); | ||
* | ||
* export const handler = async (_event: any, _context: any) => { | ||
* if (utility.isColdStart()) { | ||
* // do something, this block is only executed on the first invocation of the function | ||
* } else { | ||
* // do something else, this block gets executed on all subsequent invocations | ||
* } | ||
* }; | ||
* ``` | ||
*/ | ||
export class Utility { | ||
|
||
private coldStart: boolean = true; | ||
|
||
public getColdStart(): boolean { | ||
if (this.coldStart) { | ||
this.coldStart = false; | ||
|
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public isColdStart(): boolean { | ||
return this.getColdStart(); | ||
} | ||
|
||
} |
This file contains 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 |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './utils/lambda'; | ||
export * from './Utility'; | ||
export * as ContextExamples from './tests/resources/contexts'; | ||
export * as Events from './tests/resources/events'; |
This file contains 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 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,145 @@ | ||
/** | ||
* Test Utility class | ||
* | ||
* @group unit/commons/utility | ||
*/ | ||
import { Utility } from '../../src'; | ||
|
||
describe('Class: Utility', () => { | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
jest.resetModules(); | ||
}); | ||
|
||
describe('Method: getColdStart', () => { | ||
|
||
test('when called multiple times on the parent class, it returns true the first time, then false afterwards', () => { | ||
|
||
// Prepare | ||
const utility = new Utility(); | ||
const getColdStartSpy = jest.spyOn(utility, 'getColdStart'); | ||
|
||
// Act | ||
utility.getColdStart(); | ||
utility.getColdStart(); | ||
utility.getColdStart(); | ||
utility.getColdStart(); | ||
utility.getColdStart(); | ||
|
||
// Assess | ||
expect(getColdStartSpy).toHaveBeenCalledTimes(5); | ||
expect(getColdStartSpy.mock.results).toEqual([ | ||
expect.objectContaining({ value: true }), | ||
expect.objectContaining({ value: false }), | ||
expect.objectContaining({ value: false }), | ||
expect.objectContaining({ value: false }), | ||
expect.objectContaining({ value: false }), | ||
]); | ||
|
||
}); | ||
|
||
test('when called multiple times on a child class, it returns true the first time, then false afterwards', () => { | ||
|
||
// Prepare | ||
class PowerTool extends Utility { | ||
public constructor() { | ||
super(); | ||
} | ||
|
||
public dummyMethod(): boolean { | ||
return this.getColdStart(); | ||
} | ||
} | ||
const powertool = new PowerTool(); | ||
const dummyMethodSpy = jest.spyOn(powertool, 'dummyMethod'); | ||
const getColdStartSpy = jest.spyOn(powertool, 'getColdStart'); | ||
|
||
// Act | ||
powertool.dummyMethod(); | ||
powertool.dummyMethod(); | ||
powertool.dummyMethod(); | ||
powertool.dummyMethod(); | ||
powertool.dummyMethod(); | ||
|
||
// Assess | ||
expect(dummyMethodSpy).toHaveBeenCalledTimes(5); | ||
expect(getColdStartSpy).toHaveBeenCalledTimes(5); | ||
expect(dummyMethodSpy.mock.results).toEqual([ | ||
expect.objectContaining({ value: true }), | ||
expect.objectContaining({ value: false }), | ||
expect.objectContaining({ value: false }), | ||
expect.objectContaining({ value: false }), | ||
expect.objectContaining({ value: false }), | ||
]); | ||
|
||
}); | ||
|
||
}); | ||
|
||
describe('Method: isColdStart', () => { | ||
|
||
test('when called multiple times on the parent class, it returns true the first time, then false afterwards', () => { | ||
|
||
// Prepare | ||
const utility = new Utility(); | ||
const isColdStartSpy = jest.spyOn(utility, 'isColdStart'); | ||
|
||
// Act | ||
utility.isColdStart(); | ||
utility.isColdStart(); | ||
utility.isColdStart(); | ||
utility.isColdStart(); | ||
utility.isColdStart(); | ||
|
||
// Assess | ||
expect(isColdStartSpy).toHaveBeenCalledTimes(5); | ||
expect(isColdStartSpy.mock.results).toEqual([ | ||
expect.objectContaining({ value: true }), | ||
expect.objectContaining({ value: false }), | ||
expect.objectContaining({ value: false }), | ||
expect.objectContaining({ value: false }), | ||
expect.objectContaining({ value: false }), | ||
]); | ||
|
||
}); | ||
|
||
test('when called multiple times on a child class, it returns true the first time, then false afterwards', () => { | ||
|
||
// Prepare | ||
class PowerTool extends Utility { | ||
public constructor() { | ||
super(); | ||
} | ||
|
||
public dummyMethod(): boolean { | ||
return this.isColdStart(); | ||
} | ||
} | ||
const powertool = new PowerTool(); | ||
const dummyMethodSpy = jest.spyOn(powertool, 'dummyMethod'); | ||
const isColdStartSpy = jest.spyOn(powertool, 'isColdStart'); | ||
|
||
// Act | ||
powertool.dummyMethod(); | ||
powertool.dummyMethod(); | ||
powertool.dummyMethod(); | ||
powertool.dummyMethod(); | ||
powertool.dummyMethod(); | ||
|
||
// Assess | ||
expect(dummyMethodSpy).toHaveBeenCalledTimes(5); | ||
expect(isColdStartSpy).toHaveBeenCalledTimes(5); | ||
expect(dummyMethodSpy.mock.results).toEqual([ | ||
expect.objectContaining({ value: true }), | ||
expect.objectContaining({ value: false }), | ||
expect.objectContaining({ value: false }), | ||
expect.objectContaining({ value: false }), | ||
expect.objectContaining({ value: false }), | ||
]); | ||
|
||
}); | ||
|
||
}); | ||
|
||
}); |
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.
What would be the use case of testing the functionality from a child class?
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.
The other utilities (Tracer, Logger, etc.) will extend this
Utility
class and use it just like in this test case. I agree that it might be a little redundant but adding one test case was easier/faster than build+pack the new version ofcommons
& install it in another package to test it.