├── .eslintrc.json
├── .github
├── iam-oidc.yml
└── workflows
│ ├── tests-integration.yml
│ └── tests.yml
├── .gitignore
├── .mocharc.js
├── .nycrc.json
├── CHANGELOG.md
├── LICENSE.txt
├── README-ADVANCED.md
├── README-DEPLOY.md
├── README-EXECUTE.md
├── README-SAR.md
├── README.md
├── cdk
├── README.md
├── csharp
│ ├── .gitignore
│ ├── Cdk.sln
│ ├── README.md
│ ├── cdk.json
│ ├── src
│ │ └── Cdk
│ │ │ ├── Cdk.csproj
│ │ │ ├── GlobalSuppressions.cs
│ │ │ ├── Program.cs
│ │ │ └── TheLambdaPowerTunerStack.cs
│ └── tests
│ │ └── Cdk.Tests
│ │ ├── Cdk.Tests.csproj
│ │ ├── GlobalUsings.cs
│ │ └── TheLambdaPowerTunerStackTest.cs
├── go
│ ├── .gitignore
│ ├── README.md
│ ├── cdk.go
│ ├── cdk.json
│ ├── go.mod
│ ├── go.sum
│ ├── lambda_power_tuner_stack.go
│ └── lambda_power_tuner_stack_test.go
├── java
│ ├── .gitignore
│ ├── README.md
│ ├── cdk.json
│ ├── pom.xml
│ └── src
│ │ ├── main
│ │ └── java
│ │ │ └── com
│ │ │ └── myorg
│ │ │ ├── TheLambdaPowerTunerApp.java
│ │ │ └── TheLambdaPowerTunerStack.java
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── myorg
│ │ └── JavaTest.java
├── python
│ ├── README.md
│ ├── app.py
│ ├── app
│ │ └── lambdapowertuner_stack.py
│ ├── cdk.json
│ ├── requirements.txt
│ ├── source.bat
│ └── tests
│ │ └── unit
│ │ └── test_lambdapowertuner_stack.py
└── typescript
│ ├── .gitignore
│ ├── .npmignore
│ ├── README.md
│ ├── bin
│ └── the-lambda-power-tuner.ts
│ ├── cdk.json
│ ├── jest.config.js
│ ├── lib
│ └── the-lambda-power-tuner-stack.ts
│ ├── package-lock.json
│ ├── package.json
│ ├── test
│ └── the-lambda-power-tuner.test.ts
│ └── tsconfig.json
├── imgs
├── state-machine-screenshot.png
├── step-aborted-screenshot.png
├── visualization.png
├── visualization1.jpg
└── visualization2.jpg
├── lambda
├── analyzer.js
├── cleaner.js
├── executor.js
├── initializer.js
├── optimizer.js
├── publisher.js
└── utils.js
├── layer-sdk
├── package-lock.json
└── package.json
├── package-lock.json
├── package.json
├── scripts
├── deploy-sar-app.sh
├── deploy-sar-app.yml
├── execute.sh
├── publish.sh
└── sample-execution-input.json
├── statemachine
└── statemachine.asl.json
├── template.yml
├── terraform
├── .terraform-version
├── Readme.md
├── main.tf
├── module
│ ├── data.tf
│ ├── json_files
│ │ ├── cleaner.json
│ │ ├── executor.json
│ │ ├── initializer.json
│ │ ├── lambda.json
│ │ ├── optimizer.json
│ │ ├── publisher.json
│ │ ├── sfn.json
│ │ └── state_machine.json
│ ├── lambda.tf
│ ├── locals.tf
│ ├── policies.tf
│ ├── roles.tf
│ ├── scripts
│ │ └── build-layer.sh
│ ├── state_machine.tf
│ └── variables.tf
├── provider.tf
└── variables.tf
└── test
├── setup.spec.js
└── unit
├── test-lambda.js
└── test-utils.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "ecmaVersion": 2017
4 | },
5 |
6 | "env": {
7 | "es6": true,
8 | "mocha": true
9 | },
10 | "rules": {
11 | "indent": ["error", 4],
12 | "max-len": "off"
13 | },
14 | "extends": "strongloop"
15 | }
16 |
--------------------------------------------------------------------------------
/.github/iam-oidc.yml:
--------------------------------------------------------------------------------
1 | Resources:
2 | Role:
3 | Type: AWS::IAM::Role
4 | Properties:
5 | AssumeRolePolicyDocument:
6 | Statement:
7 | - Effect: Allow
8 | Action: sts:AssumeRoleWithWebIdentity
9 | Principal:
10 | Federated: !Ref GithubOidc
11 | Condition:
12 | StringEquals:
13 | token.actions.githubusercontent.com:aud: sts.amazonaws.com
14 | StringLike:
15 | token.actions.githubusercontent.com:sub: !Sub repo:alexcasalboni/aws-lambda-power-tuning:*
16 |
17 | GithubOidc:
18 | Type: AWS::IAM::OIDCProvider
19 | Properties:
20 | Url: https://token.actions.githubusercontent.com
21 | ClientIdList:
22 | - sts.amazonaws.com
23 | ThumbprintList:
24 | - 6938fd4d98bab03faadb97b34396831e3780aea1
25 |
26 | Outputs:
27 | Role:
28 | Value: !GetAtt Role.Arn
--------------------------------------------------------------------------------
/.github/workflows/tests-integration.yml:
--------------------------------------------------------------------------------
1 | name: aws-lambda-power-tuning-integration-tests
2 | run-name: ${{ github.actor }} is running integration tests
3 | on:
4 | push:
5 | branches:
6 | - 'master'
7 | pull_request:
8 | jobs:
9 | build:
10 | permissions:
11 | id-token: write
12 | contents: read
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | node-version: [20.x]
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Use Node.js ${{ matrix.node-version }}
20 | uses: actions/setup-node@v3
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 |
24 | - uses: aws-actions/configure-aws-credentials@master
25 | with:
26 | audience: sts.amazonaws.com
27 | role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
28 | aws-region: ${{ secrets.AWS_REGION }}
29 |
30 | - run: npm ci
31 | - uses: aws-actions/setup-sam@v2
32 | with:
33 | use-installer: true
34 |
35 | - run: sam build --use-container
36 | - run: sam deploy --no-confirm-changeset --no-fail-on-empty-changeset --stack-name aws-lambda-power-tuning-gh-${GITHUB_REF_NAME/\//-} --s3-bucket ${{ secrets.AWS_S3_BUCKET }} --capabilities CAPABILITY_IAM --region ${{ secrets.AWS_REGION }}
37 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: aws-lambda-power-tuning-unit-tests
2 | run-name: ${{ github.actor }} is running unit tests
3 | on:
4 | push:
5 | branches:
6 | - 'master'
7 | pull_request:
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | node-version: [16.x, 17.x, 18.x, 19.x, 20.x, 22.x, 23.x]
14 | steps:
15 | - uses: actions/checkout@v3
16 | - name: Use Node.js ${{ matrix.node-version }}
17 | uses: actions/setup-node@v3
18 | with:
19 | node-version: ${{ matrix.node-version }}
20 |
21 | - run: npm ci
22 | - run: npm run coverage
23 |
24 | - name: Coveralls
25 | uses: coverallsapp/github-action@v2
26 | with:
27 | github-token: ${{ secrets.GITHUB_TOKEN }}
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .pyc
3 |
4 | # Dependency directories
5 | node_modules
6 | jspm_packages
7 | venv
8 | .venv
9 | coverage
10 | .nyc_output
11 |
12 | # Serverless directories
13 | .serverless
14 |
15 | # ignore serverless.yml
16 | serverless.yml
17 |
18 | .idea/
19 | .vscode/
20 |
21 | layer-sdk/src
22 |
23 | # ignore SAM CLI created files/dirs
24 | .aws-sam/
25 | samconfig.toml
26 |
27 | # ignore terraform created files/dirs
28 | .terraform/
29 | .terraform.*
30 | *.tfstate*
31 | cid.log
--------------------------------------------------------------------------------
/.mocharc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | require: [
3 | './test/setup.spec.js',
4 | ],
5 | spec: './test/unit/**/*.js'
6 | };
7 |
--------------------------------------------------------------------------------
/.nycrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "check-coverage": true,
3 | "extension": [
4 | ".js"
5 | ],
6 | "include": [
7 | "lambda/**/*"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## CHANGELOG (SAR versioning)
2 |
3 | From most recent to oldest, with major releases in bold:
4 |
5 | * *4.3.6* (2024-11-07): updated dependencies (CVE-2024-41818)
6 | * *4.3.5* (2024-09-16): array-shape input support, JSON logging bugfix, externalized ASL, updated dependencies
7 | * *4.3.4* (2024-02-26): upgrade to Nodejs20, custom state machine prefix, SDKv3 migration, new includeOutputResults input parameter, JSON loggin support
8 | * *4.3.3* (2023-10-30): parametrized currency for visualization URL (USD|CNY)
9 | * *4.3.2* (2023-08-16): new disablePayloadLogs flag, updated documentation
10 | * *4.3.1* (2023-05-09): update dependencies, add VPC Configuration support, use Billed Duration instead Duration from logs, update state machine with ItemSelector
11 | * ***4.3.0*** (2023-03-06): SnapStart support (alias waiter)
12 | * *4.2.3* (2023-03-01): fix layer runtime (nodejs16.x)
13 | * *4.2.2* (2023-02-15): configurable sleep parameter, bump runtime to nodejs16.x, docs updates, GH Actions, and minor bug fixes
14 | * *4.2.1* (2022-08-02): customizable SDK layer name and logs retention value
15 | * ***4.2.0*** (2022-01-03): support S3 payloads
16 | * *4.1.4* (2022-01-03): sorting bugfix and updated dependencies
17 | * *4.1.3* (2021-12-16): support simple strings as event payload
18 | * *4.1.2* (2021-10-12): add x86_64 fallback when Graviton is not supported yet
19 | * *4.1.1* (2021-10-12): fixed connection timeout for long-running functions
20 | * ***4.1.0*** (2021-10-11): support Lambda functions powered by Graviton2
21 | * ***4.0.0*** (2021-08-16): support AWS Lambda states expansion to all functions
22 | * *3.4.2* (2020-12-03): permissions boundary bugfix (Step Functions role)
23 | * *3.4.1* (2020-12-02): permissions boundary support
24 | * ***3.4.0*** (2020-12-01): 1ms billing
25 | * *3.3.3* (2020-07-17): payload logging bugfix for pre-processors
26 | * *3.3.2* (2020-06-17): weighted payloads bugfix (for real)
27 | * *3.3.1* (2020-06-16): weighted payloads bugfix
28 | * ***3.3.0*** (2020-06-10): Pre/Post-processing functions, correct regional pricing, customizable execution timeouts, and other internal improvements
29 | * *3.2.5* (2020-05-19): improved logging for weighted payloads and in case of invocation errors
30 | * *3.2.4* (2020-03-11): dryRun bugfix
31 | * *3.2.3* (2020-02-25): new dryRun input parameter
32 | * *3.2.2* (2020-01-30): upgraded runtime to Node.js 12.x
33 | * *3.2.1* (2020-01-27): improved scripts and SAR template reference
34 | * ***3.2.0*** (2020-01-17): support for weighted payloads
35 | * *3.1.2* (2020-01-17): improved optimal selection when same speed/cost
36 | * *3.1.1* (2019-10-24): customizable least-privilege (lambdaResource CFN param)
37 | * ***3.1.0*** (2019-10-24): $LATEST power reset and optional auto-tuning (new Optimizer step)
38 | * ***3.0.0*** (2019-10-22): dynamic parallelism (powerValues as execution parameter)
39 | * *2.1.3* (2019-10-22): upgraded runtime to Node.js 10.x
40 | * *2.1.2* (2019-10-17): new balanced optimization strategy
41 | * *2.1.1* (2019-10-10): custom domain for visualization URL
42 | * ***2.1.0*** (2019-10-10): average statistics visualization (URL in state machine output)
43 | * ***2.0.0*** (2019-07-28): multiple optimization strategies (cost and speed), new output format with AWS Step Functions and AWS Lambda cost
44 | * *1.3.1* (2019-07-23): retry policies and failed invocations management
45 | * ***1.3.0*** (2019-07-22): implemented error handling
46 | * *1.2.1* (2019-07-22): Node.js refactor and updated IAM permissions (added lambda:UpdateAlias)
47 | * ***1.2.0*** (2019-05-24): updated IAM permissions (least privilege for actions)
48 | * *1.1.1* (2019-05-15): updated docs
49 | * ***1.1.0*** (2019-05-15): cross-region invocation support
50 | * *1.0.1* (2019-05-13): new README for SAR
51 | * ***1.0.0*** (2019-05-13): AWS SAM refactor (published on SAR)
52 | * *0.0.1* (2017-03-27): previous project (serverless framework)
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README-ADVANCED.md:
--------------------------------------------------------------------------------
1 | # Lambda Power Tuning advanced features
2 |
3 | This section describes some advanced features of this project, as well as some considerations related to security and cost.
4 |
5 |
6 | ## Error handling
7 |
8 | If something goes wrong during the initialization or execution states, the `CleanUpOnError` step will be executed. All temporary versions and aliases will be deleted as expected (the same happens in the `Cleaner` step).
9 |
10 | You can customize the `totalExecutionTimeout` parameter at deploy time (up to 15min). This parameter will be used both for Lambda function timeouts and Step Function tasks timeouts. In case the `Executor` raises a timeout error, you will see a `States.Timeout` error. Keep in mind that the timeout you configure will vary whether you're setting `parallelInvocation` to `true` or `false`. When you enable parallel invocation, all the function executions will run concurrently (rather than in series) so that you can keep that timeout lower and your overall state machine execution faster.
11 |
12 | In all other error cases, you will see a `Lambda.Unknown` error, which corresponds to unhandled errors in Lambda such as out-of-memory errors and hitting the concurrent Lambda invoke limit. If you encounter it as input of `CleanUpOnError`, it's very likely that something went wrong with the function you're power-tuning.
13 |
14 | ### Retry policy
15 |
16 | The executor will retry twice in case any invocation fails. This is helpful in case of execution timeouts or memory errors. You will find the failed execution's stack trace in the `CleanUpOnError` state input.
17 |
18 | ### How do I know which executor failed and why?
19 |
20 | You can inspect the "Execution event history" and look for the corresponding `TaskStateAborted` event type.
21 |
22 | Additionally, you can inspect the `CleanUpOnError` state input. Here you will find the stack trace of the error.
23 |
24 |
25 | ## Security
26 |
27 | All the IAM roles used by the state machine adopt the least privilege best practice, meaning that only a minimal set of `Actions` are granted to each Lambda function.
28 |
29 | For example, the Executor function can only call `lambda:InvokeFunction`. The Analyzer function doesn't require any permission at all. On the other hand, the Initializer, Cleaner, and Optimizer functions require a broader set of actions.
30 |
31 | By default, the Executor function is allowed to invoke any Lambda function in your account, in any region. This happens because the default resource defined in the IAM role is `"*"`, but you can change this value at deploy-time, via the the `lambdaResource` CloudFormation parameter.
32 |
33 | For example, you could use a mix of the following:
34 |
35 | * Same-region prefix: `arn:aws:lambda:us-east-1:*:function:*`
36 | * Function name prefix: `arn:aws:lambda:*:*:function:my-prefix-*`
37 | * Function name suffix: `arn:aws:lambda:*:*:function:*-dev`
38 | * By account ID: `arn:aws:lambda:*:ACCOUNT_ID:function:*`
39 |
40 |
41 | ## Execution cost
42 |
43 | There are three main costs associated with AWS Lambda Power Tuning:
44 |
45 | * **AWS Step Functions cost**: it corresponds to the number of state transitions; this cost depends on the number of tested power values, and it's approximately `0.000025 * (6 + N)` where `N` is the number of power values; for example, if you test the 6 default power values, the state machine cost will be `$0.0003`
46 | * **AWS Lambda cost** it relates to your function's executions and depends on three factors: 1) number of invocations that you configure as input (`num`), the number of tested power configurations (`powerValues`), and the average invocation time of your function; for example, if you test all the default power configurations with `num: 100` and all invocations take less than 100ms, the Lambda cost will be approximately `$0.001`
47 | * **AWS Lambda cost** related to `Initializer`, `Executor`, `Cleaner`, and `Analyzer`: for most cases it's negligible, especially if you enable `parallelInvocation: true`; this cost is not included in the `results.stateMachine` output to keep the state machine simple and easy to read and debug
48 |
49 |
50 | ## State Machine Internals
51 |
52 | The AWS Step Functions state machine is composed of five Lambda functions:
53 |
54 | * **Initializer**: define all the versions and aliases that need to be created (see Publisher below)
55 | * **Publisher**: create a new version and aliases corresponding to one of the power values provided as input (e.g. 128MB, 256MB, etc.)
56 | * **IsCountReached**: go back to Publisher until all the versiona and aliases have been created
57 | * **Executor**: execute the given Lambda function `num` times, extract execution time from logs, and compute average cost per invocation
58 | * **Cleaner**: delete all the previously generated aliases and versions
59 | * **Analyzer**: compute the optimal power value (current logic: lowest average cost per invocation)
60 | * **Optimizer**: automatically set the power to its optimal value (only if `autoOptimize` is `true`)
61 |
62 | Initializer, Cleaner, Analyzer, and Optimizer are invoked only once, while the Publisher and Executor are invoked multiple times. Publisher is used in a loop to create all the required versions and aliases, which depend on the values of `num`, `powerValues`, and `onlyColdStarts`. Executor is used by N parallel branches - one for each configured power value. By default, the Executor will invike the given Lambda function `num` consecutive times, but you can enable parallel invocation by setting `parallelInvocation` to `true`.
63 |
64 | ## Weighted Payloads
65 |
66 | > [!IMPORTANT]
67 | > Your payload will only be treated as a weighted payload if it adheres to the JSON structure that follows. Otherwise, it's assumed to be an array-shaped payload.
68 |
69 | Weighted payloads can be used in scenarios where the payload structure and the corresponding performance/speed could vary a lot in production and you'd like to include multiple payloads in the tuning process.
70 |
71 | You may want to use weighted payloads also in case of functions with side effects that would be hard or impossible to test with the very same payload (for example, a function that deletes records from a database).
72 |
73 | You configure weighted payloads as follows:
74 |
75 | ```json
76 | {
77 | ...
78 | "num": 50,
79 | "payload": [
80 | { "payload": {...}, "weight": 5 },
81 | { "payload": {...}, "weight": 15 },
82 | { "payload": {...}, "weight": 30 }
83 | ]
84 | }
85 | ```
86 |
87 | In the example above, the weights `5`, `15`, and `30` are used as relative weights. They will correspond to `10%` (5 out of 50), `30%` (15 out of 50), and `60%` (30 out of 50) respectively - meaning that the corresponding payload will be used 10%, 30% and 60% of the time.
88 |
89 | For example, if `num=100` the first payload will be used 10 times, the second 30 times, and the third 60 times.
90 |
91 | To simplify these calculations, you could use weights that sum up to 100.
92 |
93 | Note: the number of weighted payloads must always be smaller or equal than `num` (or `num >= count(payloads)`). For example, if you have 50 weighted payloads, you'll need to set at least `num: 50` so that each payload will be used at least once.
94 |
95 |
96 | ## Pre/Post-processing functions
97 |
98 | Sometimes you need to power-tune Lambda functions that have side effects such as creating or deleting records in a database. In these cases, you may need to execute some pre-processing or post-processing logic before and/or after each function invocation.
99 |
100 | For example, imagine that you are power-tuning a function that deletes one record from a downstream database. Since you want to execute this function `num` times you'd need to insert some records in advance and then find a way to delete all of them with a dynamic payload. Or you could simply configure a pre-processing function (using the `preProcessorARN` input parameter) that will create a brand new record before the actual function is executed.
101 |
102 | Here's the flow in pseudo-code:
103 |
104 | ```
105 | function Executor:
106 | iterate from 0 to num:
107 | [payload = execute Pre-processor (payload)]
108 | results = execute Main Function (payload)
109 | [execute Post-processor (results)]
110 | ```
111 |
112 | Please also keep in mind the following:
113 |
114 | * You can configure a pre-processor and/or a post-processor independently
115 | * The pre-processor will receive the original payload
116 | * If the pre-processor returns a non-empty output, it will overwrite the original payload
117 | * The post-processor will receive the main function's output as payload
118 | * If a pre-processor or post-processor fails, the whole power-tuning state machine will fail
119 | * Pre/post-processors don't have to be in the same region of the main function
120 | * Pre/post-processors don't alter the statistics related to cost and performance
121 |
122 | ## S3 payloads
123 |
124 | In case of very large payloads above 256KB, you can provide an S3 object reference (`s3://bucket/key`) instead of an inline payload.
125 |
126 | Your state machine input will look like this:
127 |
128 | ```json
129 | {
130 | "lambdaARN": "your-lambda-function-arn",
131 | "powerValues": [128, 256, 512, 1024],
132 | "num": 50,
133 | "payloadS3": "s3://your-bucket/your-object.json"
134 | }
135 | ```
136 |
137 | Please note that the state machine will require IAM access to your S3 bucket, so you might need to redeploy the Lambda Power Tuning application and configure the `payloadS3Bucket` parameter at deployment time. This will automatically generate a custom IAM managed policy to grant read-only access to that bucket. If you want to narrow down the read-only policy to a specific object or pattern, use the `payloadS3Key` parameter (which is `*` by default).
138 |
139 | S3 payloads work fine with weighted payloads too.
140 |
--------------------------------------------------------------------------------
/README-DEPLOY.md:
--------------------------------------------------------------------------------
1 | # How to deploy the AWS Lambda Power Tuning tool
2 |
3 | There are 5 deployment options for deploying the tool using Infrastructure as Code (IaC).
4 |
5 |
6 | 1. The easiest way is to [deploy the app via the AWS Serverless Application Repository (SAR)](#option1).
7 | 1. [Using the AWS SAM CLI](#option2)
8 | 1. [Using the AWS CDK](#option3)
9 | 1. [Using Terraform by Hashicorp and SAR](#option4)
10 | 1. [Using native Terraform](#option5)
11 |
12 | Read more about the [deployment parameters here](README.md#state-machine-configuration-at-deployment-time).
13 |
14 | ## Option 1: AWS Serverless Application Repository
15 |
16 | You can find this app in the [Serverless Application Repository](https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:451282441545:applications~aws-lambda-power-tuning) and deploy it with just a few clicks in the AWS Management Console.
17 |
18 | You can also integrate the SAR app in your existing CloudFormation stacks - check [scripts/deploy-sar-app.yml](scripts/deploy-sar-app.yml) and [scripts/deploy-sar-app.sh](scripts/deploy-sar-app.sh) for a sample implementation.
19 |
20 |
21 | ## Option 2: Build and deploy with the AWS SAM CLI
22 |
23 | **Note**: This method requires Docker.
24 |
25 | 1. Install the [AWS SAM CLI in your local environment](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html).
26 |
27 | 1. Configure your [AWS credentials (requires AWS CLI installed)](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html#cli-quick-configuration):
28 | ```bash
29 | $ aws configure
30 | ```
31 | 1. Install [Docker](https://docs.docker.com/get-docker/).
32 | 1. Clone this git repository:
33 | ```bash
34 | $ git clone https://github.com/alexcasalboni/aws-lambda-power-tuning.git
35 | ```
36 | 1. Build the Lambda layer and any other dependencies (Docker is required):
37 | ```bash
38 | $ cd ./aws-lambda-power-tuning
39 | $ sam build -u
40 | ```
41 | [`sam build -u`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-build.html) will run SAM build using a Docker container image that provides an environment similar to that which your function would run in. SAM build in-turn looks at your AWS SAM template file for information about Lambda functions and layers in this project.
42 |
43 | Once the build completes successfully you will see output stating `Build Succeeded`. If the build is not successful, there will be error messages providing guidance on what went wrong.
44 | 1. Deploy the application using the guided SAM deploy mode:
45 | ```bash
46 | $ sam deploy -g
47 | ```
48 | * For **Stack Name**, enter a unique name for the stack.
49 | * For **AWS Region**, enter the region you want to deploy in.
50 |
51 | Accept the defaults for all other prompts.
52 |
53 | [`sam deploy -g`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-deploy.html) provides simple prompts to walk you through the process of deploying the tool. The responses are saved in a configuration file, `samconfig.toml`, to be reused during subsequent deployments.
54 |
55 | SAM CLI will run the required commands to create the resources for the Lambda Power Tuning tool.
56 |
57 | A successful deployment displays the message `Successfully created/updated stack`.
58 | 1. To delete Lambda Power Tuning, run
59 | ```bash
60 | sam delete
61 | ```
62 | Answer `Y` to the prompts.
63 |
64 |
65 | ## Option 3: Deploy the AWS SAR app with AWS CDK
66 |
67 | 1. [Install AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html).
68 | ```bash
69 | $ npm install -g aws-cdk
70 | ```
71 |
72 | 1. [Bootstrap](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_bootstrap) your account.
73 |
74 | 1. [Configure your AWS credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html#cli-quick-configuration):
75 |
76 | ```bash
77 | $ aws configure
78 | ```
79 |
80 | 1. If you already have a CDK project you can include the following to use the [sam module](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-sam-readme.html):
81 |
82 | ```typescript
83 | import * as sam from 'aws-cdk-lib/aws-sam';
84 |
85 | new sam.CfnApplication(this, 'powerTuner', {
86 | location: {
87 | applicationId: 'arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning',
88 | semanticVersion: '4.3.6'
89 | },
90 | parameters: {
91 | "lambdaResource": "*",
92 | "PowerValues": "128,256,512,1024,1536,3008"
93 | }
94 | })
95 | ```
96 |
97 | Alternatively, you can build and deploy the solution from the source in this repo. See the following pages for language-specific instructions.
98 |
99 | ### TypeScript
100 | See the [Typescript instructions](cdk/typescript/README.md)
101 |
102 | ### Python
103 | See the [Python instructions](cdk/python/README.md)
104 |
105 | ### go
106 | See the [go instructions](cdk/go/README.md)
107 |
108 | ### C\#
109 | See the [Csharp instructions](cdk/csharp/README.md)
110 |
111 | ## Option 4: Deploy the SAR app with Terraform
112 |
113 | Simply add the `aws_serverlessapplicationrepository_cloudformation_stack` resource below to your Terraform code and deploy as usual through `terraform apply`.
114 |
115 | ```hcl
116 | resource "aws_serverlessapplicationrepository_cloudformation_stack" "lambda-power-tuning" {
117 | name = "lambda-power-tuner"
118 | application_id = "arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning"
119 | capabilities = ["CAPABILITY_IAM"]
120 | # Uncomment the next line to deploy a specific version
121 | # semantic_version = "4.3.6"
122 |
123 | parameters = {
124 | # All of these parameters are optional and are only shown here for demonstration purposes
125 | # See https://github.com/alexcasalboni/aws-lambda-power-tuning/blob/master/README-INPUT-OUTPUT.md#state-machine-input-at-deployment-time
126 | # PowerValues = "128,192,256,512,1024,2048,3072,4096,5120,6144,7168,8192,9216,10240"
127 | # lambdaResource = "*"
128 | # totalExecutionTimeout = 900
129 | # visualizationURL = "https://lambda-power-tuning.show/"
130 | }
131 | }
132 | ```
133 |
134 | See the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/serverlessapplicationrepository_cloudformation_stack) for more configuration options of `aws_serverlessapplicationrepository_cloudformation_stack`.
135 |
136 | If you don't yet have a Terraform project, check out the [Terraform introduction](https://www.terraform.io/intro/index.html).
137 |
138 | ## Option 5: Deploy natively with Terraform
139 |
140 | The Terraform modules are located in the [terraform](terraform) directory. Deployment documentation is [here](terraform/Readme.md).
141 |
142 | ## State machine configuration (at deployment time)
143 |
144 | The CloudFormation template (used for option 1 to 4) accepts the following parameters:
145 |
146 | |
**Parameter**
| Description |
147 | |:-----------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
148 | | **PowerValues**
type: _list of numbers_
default: [128,256,512,1024,1536,3008] | These power values (in MB) will be used as the default in case no `powerValues` input parameter is provided at execution time. |
149 | | **visualizationURL**
type: _string_
default: `lambda-power-tuning.show` | The base URL for the visualization tool, you can bring your own visualization tool. |
150 | | **totalExecutionTimeout**
type: _number_
default: `300` | The timeout in seconds applied to all functions of the state machine. |
151 | | **lambdaResource**
type: _string_
default: `*` | The `Resource` used in IAM policies; it's `*` by default but you could restrict it to a prefix or a specific function ARN. |
152 | | **permissionsBoundary**
type: _string_
| The ARN of a permissions boundary (policy), applied to all functions of the state machine. |
153 | | **payloadS3Bucket**
type: _string_
| The S3 bucket name used for large payloads (>256KB); if provided, it's added to a custom managed IAM policy that grants read-only permission to the S3 bucket; more details in the [S3 payloads section](README-ADVANCED.md#user-content-s3-payloads). |
154 | | **payloadS3Key**
type: _string_
default: `*` | The S3 object key used for large payloads (>256KB); the default value grants access to all S3 objects in the bucket specified with `payloadS3Bucket`; more details in the [S3 payloads section](README-ADVANCED.md#user-content-s3-payloads). |
155 | | **layerSdkName**
type: _string_
| The name of the SDK layer, in case you need to customize it (optional). |
156 | | **logGroupRetentionInDays**
type: _number_
default: `7` | The number of days to retain log events in the Lambda log groups (a week by default). |
157 | | **securityGroupIds**
type: _list of SecurityGroup IDs_
| List of Security Groups to use in every Lambda function's VPC Configuration (optional); please note that your VPC should be configured to allow public internet access (via NAT Gateway) or include VPC Endpoints to the Lambda service. |
158 | | **subnetIds**
type: _list of Subnet IDs_
| List of Subnets to use in every Lambda function's VPC Configuration (optional); please note that your VPC should be configured to allow public internet access (via NAT Gateway) or include VPC Endpoints to the Lambda service. |
159 | | **stateMachineNamePrefix**
type: _string_
default: `powerTuningStateMachine` | Allows you to customize the name of the state machine. Maximum 43 characters, only alphanumeric (plus `-` and `_`). The last portion of the `AWS::StackId` will be appended to this value, so the full name will look like `powerTuningStateMachine-89549da0-a4f9-11ee-844d-12a2895ed91f`. Note: `StateMachineName` has a maximum of 80 characters and 36+1 from the `StackId` are appended, allowing 43 for a custom prefix. |
160 |
161 | Please note that the total execution time should stay below 300 seconds (5 min), which is the default timeout. You can estimate the total execution timeout based on the average duration of your functions. For example, if your function's average execution time is 5 seconds and you haven't enabled `parallelInvocation`, you should set `totalExecutionTimeout` to at least `num * 5`: 50 seconds if `num=10`, 500 seconds if `num=100`, and so on. If you have enabled `parallelInvocation`, usually you don't need to tune the value of `totalExecutionTimeout` unless your average execution time is above 5 min. If you have set a sleep between invocations, remember to include that in your timeout calculations.
162 |
163 |
164 | ## How to execute the state machine once deployed
165 |
166 | See the [execution](README-EXECUTE.md) instructions to run the state machine.
167 |
--------------------------------------------------------------------------------
/README-EXECUTE.md:
--------------------------------------------------------------------------------
1 | # How to execute the state machine
2 |
3 | Independently of how you've deployed the state machine, you can execute it in a few different ways. Programmatically, using the AWS CLI, or AWS SDK. Manually, using the AWS Step Functions web console.
4 |
5 | ## Option 1: Execute the state machine programmatically (CLI)
6 |
7 | You'll find a few sample scripts in the `scripts` folder.
8 |
9 | Feel free to customize the `scripts/sample-execution-input.json` or add a new json file, and then run `scripts/execute.sh [input json]` by default if input json is not passed then the script will default to `sample-execution-input.json`.
10 |
11 | The script will start a state machine execution, wait for the execution to complete (polling), and then show the execution results.
12 |
13 | ### Usage in CI/CD pipelines
14 |
15 | If you want to run the state machine as part of your continuous integration pipeline and automatically fine-tune your functions at every deployment, you can execute it with the script `scripts/execute.sh` (or similar) by providing the following input parameters:
16 |
17 | ```json
18 | {
19 | "lambdaARN": "...",
20 | "num": 10,
21 | "payload": {},
22 | "powerValues": [128, 256, 512, ...],
23 | "autoOptimize": true,
24 | "autoOptimizeAlias": "prod",
25 | "allowedExceptions": ["HandledError"]
26 | }
27 | ```
28 |
29 | You can use different alias names such as `dev`, `test`, `production`, etc. If you don't configure any alias name, the state machine will only update the `$LATEST` alias.
30 |
31 | ## Option 2: Execute the state machine manually (web console)
32 |
33 | Once the state machine is deployed, you can execute it and provide an input object.
34 |
35 | You will find the new state machine in the [Step Functions Console](https://console.aws.amazon.com/states/) or in your SAR app's `Resources` section.
36 |
37 | The state machine name will depend on the stack name (default: `aws-lambda-power-tuning`). Find it and click "**Start execution**".
38 |
39 | You'll be able to provide the execution input (check the [full documentation here](README.md#state-machine-input-at-execution-time)), which will look like this:
40 |
41 | ```json
42 | {
43 | "lambdaARN": "your-lambda-function-arn",
44 | "powerValues": [128, 256, 512, 1024, 1536, 2048, 3008],
45 | "num": 50,
46 | "payload": {},
47 | "parallelInvocation": true,
48 | "strategy": "cost",
49 | "allowedExceptions": ["HandledError"]
50 | }
51 | ```
52 |
53 | Click "**Start Execution**" again and the execution will start. In the next page, you'll be able to visualize the execution flow.
54 |
55 | Once the execution has completed, you will find the execution results in the "**Output**" tab of the "**Execution Details**" section at the top of the page. The output will contain the optimal power configuration and its corresponding average cost per execution.
56 |
57 | ## State machine input (at execution time)
58 |
59 | Each execution of the state machine will require an input where you can define the following input parameters:
60 |
61 | | **Parameter**
| Description |
62 | |:-----------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
63 | | **lambdaARN** (required)
type: _string_
| Unique identifier of the Lambda function you want to optimize. |
64 | | **num** (required)
type: _integer_ | The # of invocations for each power configuration (minimum 5, recommended: between 10 and 100). |
65 | | **powerValues**
type: _string or list of integers_ | The list of power values to be tested; if not provided, the default values configured at deploy-time are used; you can provide any power values between 128MB and 10,240MB. ⚠️ New AWS accounts have reduced concurrency and memory quotas (3008MB max). |
66 | | **payload**
type: _string, object, or list_ | The static payload that will be used for every invocation (object or string); when using a list, a weighted payload is expected in the shape of `[{"payload": {...}, "weight": X }, {"payload": {...}, "weight": Y }, {"payload": {...}, "weight": Z }]`, where the weights `X`, `Y`, and `Z` are treated as relative weights (not percentages); more details in the [Weighted Payloads section](README-ADVANCED.md#user-content-weighted-payloads). |
67 | | **payloadS3**
type: _string_ | An Amazon S3 object reference for large payloads (>256KB), formatted as `s3://bucket/key`; it requires read-only IAM permissions, see `payloadS3Bucket` and `payloadS3Key` below and find more details in the [S3 payloads section](README-ADVANCED.md#user-content-s3-payloads). |
68 | | **parallelInvocation**
type: _boolean_
default: `false` | If true, all the invocations will run in parallel. ⚠️ Note: depending on the value of `num`, you might experience throttling. |
69 | | **strategy**
type: _string_
default: `"cost"` | It can be `"cost"` or `"speed"` or `"balanced"`; if you use `"cost"` the state machine will suggest the cheapest option (disregarding its performance), while if you use `"speed"` the state machine will suggest the fastest option (disregarding its cost). When using `"balanced"` the state machine will choose a compromise between `"cost"` and `"speed"` according to the parameter `"balancedWeight"`. |
70 | | **balancedWeight**
type: _number_
default: `0.5` | Parameter that represents the trade-off between cost and speed. Value is between 0 and 1, where 0.0 is equivalent to `"speed"` strategy, 1.0 is equivalent to `"cost"` strategy. |
71 | | **autoOptimize**
type: _boolean_
default: `false` | If true, the state machine will apply the optimal configuration at the end of its execution. |
72 | | **autoOptimizeAlias**
type: _string_ | If provided - and only if `autoOptimize` is true, the state machine will create or update this alias with the new optimal power value. |
73 | | **dryRun**
type: _boolean_
default: `false` | If true, the state machine will invoke the input function only once and disable every functionality related to logs analysis, auto-tuning, and visualization; this is intended for testing purposes, for example to verify that IAM permissions are set up correctly. |
74 | | **preProcessorARN**
type: _string_ | The ARN of a Lambda function that will be invoked before every invocation of `lambdaARN`; more details in the [Pre/Post-processing functions section](README-ADVANCED.md#user-content-prepost-processing-functions). |
75 | | **postProcessorARN**
type: _string_ | The ARN of a Lambda function that will be invoked after every invocation of `lambdaARN`; more details in the [Pre/Post-processing functions section](README-ADVANCED.md#user-content-prepost-processing-functions). |
76 | | **discardTopBottom**
type: _number_
default: `0.2` | By default, the state machine will discard the top/bottom 20% of "outlier invocations" (the fastest and slowest) to filter out the effects of cold starts and remove any bias from overall averages. You can customize this parameter by providing a value between 0 and 0.4, where 0 means no results are discarded and 0.4 means 40% of the top/bottom results are discarded (i.e. only 20% of the results are considered). |
77 | | **sleepBetweenRunsMs**
type: _integer_ | If provided, the time in milliseconds that the tuner will sleep/wait after invoking your function, but before carrying out the Post-Processing step, should that be provided. This could be used if you have aggressive downstream rate limits you need to respect. By default this will be set to 0 and the function won't sleep between invocations. This has no effect if running the invocations in parallel. |
78 | | **disablePayloadLogs**
type: _boolean_
default: `false` | If true, suppresses `payload` from error messages and logs. If `preProcessorARN` is provided, this also suppresses the output payload of the pre-processor. |
79 | | **includeOutputResults**
type: _boolean_
default: `false` | If true, the average cost and average duration for every power value configuration will be included in the state machine output. |
80 | | **onlyColdStarts**
type: _boolean_
default: `false` | If true, the tool will force all invocations to be cold starts. The initialization phase will be considerably slower as `num` versions/aliases need to be created for each power value. |
81 | | **allowedExceptions"
type: _list_
default: `[]` | Set Errors that will be handlded be the executor rather than causing it to error out. | |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AWS Lambda Power Tuning
2 |
3 | [](https://app.travis-ci.com/github/alexcasalboni/aws-lambda-power-tuning)
4 | [](https://coveralls.io/github/alexcasalboni/aws-lambda-power-tuning)
5 | [](https://GitHub.com/alexcasalboni/aws-lambda-power-tuning/graphs/commit-activity)
6 | [](https://github.com/alexcasalboni/aws-lambda-power-tuning/issues)
7 | [](https://github.com/ellerbrock/open-source-badges/)
8 |
9 | AWS Lambda Power Tuning is a state machine powered by AWS Step Functions that helps you optimize your Lambda functions for cost and/or performance in a data-driven way.
10 |
11 | The state machine is designed to be easy to deploy and fast to execute. Also, it's language agnostic so you can optimize any Lambda functions in your account.
12 |
13 | Basically, you can provide a Lambda function ARN as input and the state machine will invoke that function with multiple power configurations (from 128MB to 10GB, you decide which values). Then it will analyze all the execution logs and suggest you the best power configuration to minimize cost and/or maximize performance.
14 |
15 | > [!NOTE]
16 | > Please note that the input function will be executed in your AWS account and perform real HTTP requests, SDK calls, cold starts, etc. The state machine also supports cross-region invocations and you can enable parallel execution to generate results in just a few seconds.
17 |
18 | ## What does the state machine look like?
19 |
20 | It's pretty simple and you can visually inspect each step in the AWS management console.
21 |
22 | 
23 |
24 | ## What can I expect from AWS Lambda Power Tuning?
25 |
26 | The state machine will generate a visualization of average cost and speed for each power configuration.
27 |
28 | 
29 |
30 | The graph helps you understand the impact of the power configuration on cost and performance for your specific AWS Lambda function.
31 |
32 |
33 |
34 | For example, this is what the results look like for two CPU-intensive functions, which become cheaper AND faster with more power:
35 |
36 |
37 |
38 | How to interpret the chart above: execution time goes from 35s with 128MB to less than 3s with 1.5GB, while being 14% cheaper to run.
39 |
40 | 
41 |
42 | How to interpret the chart above: execution time goes from 2.4s with 128MB to 300ms with 1GB, for the very same average cost.
43 |
44 | ## Quick Start
45 |
46 | > [!NOTE]
47 | > There are 5 deployment options for deploying AWS Lambda Power Tuning using Infrastucture as Code (IaC). In this Quick Start guide we will use the AWS SAM CLI. Read more about the [deployment options here](README-DEPLOY.md).
48 |
49 | ### How to deploy the AWS Lambda Power Tuning tool with SAM CLI
50 |
51 | **Prerequisites**: This method requires Docker.
52 |
53 | 1. Install the [AWS SAM CLI in your local environment](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html).
54 |
55 | 1. Configure your [AWS credentials (requires AWS CLI installed)](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html#cli-quick-configuration):
56 | ```bash
57 | $ aws configure
58 | ```
59 | 1. Install [Docker](https://docs.docker.com/get-docker/).
60 | 1. Clone this git repository:
61 | ```bash
62 | $ git clone https://github.com/alexcasalboni/aws-lambda-power-tuning.git
63 | ```
64 | 1. Build the Lambda layer and any other dependencies (Docker is required):
65 | ```bash
66 | $ cd ./aws-lambda-power-tuning
67 | $ sam build -u
68 | ```
69 | [`sam build -u`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-build.html) will run SAM build using a Docker container image that provides an environment similar to that which your function would run in. SAM build in-turn looks at your AWS SAM template file for information about Lambda functions and layers in this project.
70 |
71 | Once the build completes successfully you will see output stating `Build Succeeded`. If the build is not successful, there will be error messages providing guidance on what went wrong.
72 | 1. Deploy the application using the guided SAM deploy mode:
73 | ```bash
74 | $ sam deploy -g
75 | ```
76 | * For **Stack Name**, enter a unique name for the stack.
77 | * For **AWS Region**, enter the region you want to deploy in.
78 |
79 | Accept the defaults for all other prompts.
80 |
81 | [`sam deploy -g`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-deploy.html) provides simple prompts to walk you through the process of deploying the tool. The responses are saved in a configuration file, `samconfig.toml`, to be reused during subsequent deployments.
82 |
83 | SAM CLI will run the required commands to create the resources for the Lambda Power Tuning tool.
84 |
85 | A successful deployment displays the message `Successfully created/updated stack`.
86 | 1. To delete Lambda Power Tuning, run
87 | ```bash
88 | sam delete
89 | ```
90 | Answer `Y` to the prompts.
91 |
92 | Deployment configuration [config](README-DEPLOY.md#state-machine-configuration-at-deployment-time)
93 |
94 | ### How to run the state machine
95 |
96 | >[!NOTE]
97 | >You can run the state machine manually or programmatically, see the detailed documentation [here](README-EXECUTE.md).
98 |
99 | In this Quick Start guide we will execute the state machine with the CLI.
100 |
101 | You'll find a few sample scripts in the `scripts` folder.
102 |
103 | The `scripts/sample-execution-input.json` let's you specify execution parameters, such as the lambdaARN and the number of invocations. You can find an extensive list of [execution parameters here](README-EXECUTE.md#state-machine-input-at-execution-time). To run the state machine you have to run the execute script in `scripts/execute.sh`.
104 |
105 | Here's a typical state machine input with basic parameters:
106 |
107 | ```json
108 | {
109 | "lambdaARN": "your-lambda-function-arn",
110 | "powerValues": [128, 256, 512, 1024],
111 | "num": 50,
112 | "payload": {}
113 | }
114 | ```
115 |
116 | ### State Machine Output
117 |
118 | The state machine will return the following output:
119 |
120 | ```json
121 | {
122 | "results": {
123 | "power": "128",
124 | "cost": 0.0000002083,
125 | "duration": 2.9066666666666667,
126 | "stateMachine": {
127 | "executionCost": 0.00045,
128 | "lambdaCost": 0.0005252,
129 | "visualization": "https://lambda-power-tuning.show/#"
130 | },
131 | "stats": [{ "averagePrice": 0.0000002083, "averageDuration": 2.9066666666666667, "value": 128}, ... ]
132 | }
133 | }
134 | ```
135 |
136 | More details on each value:
137 |
138 | * **results.power**: the optimal power configuration (RAM)
139 | * **results.cost**: the corresponding average cost (per invocation)
140 | * **results.duration**: the corresponding average duration (per invocation)
141 | * **results.stateMachine.executionCost**: the AWS Step Functions cost corresponding to this state machine execution (fixed value for "worst" case)
142 | * **results.stateMachine.lambdaCost**: the AWS Lambda cost corresponding to this state machine execution (depending on `num` and average execution time)
143 | * **results.stateMachine.visualization**: if you visit this autogenerated URL, you will be able to visualize and inspect average statistics about cost and performance; important note: average statistics are NOT shared with the server since all the data is encoded in the URL hash ([example](https://lambda-power-tuning.show/#gAAAAQACAAQABsAL;ZooQR4yvkUa/pQRGRC5zRaADHUVjOftE;QdWhOEMkoziDT5Q4xhiIOMYYiDi6RNc4)), which is available only client-side
144 | * **results.stats**: the average duration and cost for every tested power value configuration (only included if `includeOutputResults` is set to a truthy value)
145 |
146 | ## Data visualization
147 |
148 | You can visually inspect the tuning results to identify the optimal tradeoff between cost and performance.
149 |
150 | 
151 |
152 | The data visualization tool has been built by the community: it's a static website deployed via AWS Amplify Console and it's free to use. If you don't want to use the visualization tool, you can simply ignore the visualization URL provided in the execution output. No data is ever shared or stored by this tool.
153 |
154 | Website repository: [matteo-ronchetti/aws-lambda-power-tuning-ui](https://github.com/matteo-ronchetti/aws-lambda-power-tuning-ui)
155 |
156 | Optionally, you could deploy your own custom visualization tool and configure the CloudFormation Parameter named `visualizationURL` with your own URL.
157 |
158 | ## Additional features, considerations, and internals
159 |
160 | [Here](README-ADVANCED.md) you can find out more about some advanced features of this project, its internals, and some considerations about security and execution cost.
161 |
162 | ## Contributing
163 |
164 | Feature requests and pull requests are more than welcome!
165 |
166 | ### How to get started with local development?
167 |
168 | For this repository, install dev dependencies with `npm install`. You can run tests with `npm test`, linting with `npm run lint`, and coverage with `npm run coverage`. Unit tests will run automatically on every commit and PR.
169 |
--------------------------------------------------------------------------------
/cdk/README.md:
--------------------------------------------------------------------------------
1 | # Deploy AWS Lambda Power Tuning using the CDK
2 |
3 | Here you find various CDK projects to deploy *AWS Lambda Power Tuning* using your preferred programming language.
4 |
5 | Currently we support:
6 |
7 | - [TypeScript](typescript/README.md)
8 | - [C#](csharp/README.md)
9 | - [Python](python/README.md)
10 | - [go](go/README.md)
11 | - [Java](java/README.md)
12 |
13 | You can use these projects as a standalone or reuse it within your own CDK projects.
14 |
15 | ## Prerequisites
16 |
17 | - [AWS CDK Toolkit](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_install)
18 | - [General prerequisites for CDK](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_prerequisites)
19 |
20 | Check also the langauge specific requirements in the respective README.
21 |
22 | ## Useful commands
23 |
24 | Run these commands from the project folders:
25 |
26 | * `cdk bootstrap` bootstrap one or more environments
27 | * `cdk deploy` deploy this stack to your default AWS account/region
28 | * `cdk diff` compare deployed stack with current state
29 | * `cdk synth` emits the synthesized CloudFormation template
30 |
--------------------------------------------------------------------------------
/cdk/csharp/.gitignore:
--------------------------------------------------------------------------------
1 | # CDK asset staging directory
2 | .cdk.staging
3 | cdk.out
4 |
5 | # Created by https://www.gitignore.io/api/csharp
6 |
7 | ### Csharp ###
8 | ## Ignore Visual Studio temporary files, build results, and
9 | ## files generated by popular Visual Studio add-ons.
10 | ##
11 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
12 |
13 | # User-specific files
14 | *.suo
15 | *.user
16 | *.userosscache
17 | *.sln.docstates
18 |
19 | # User-specific files (MonoDevelop/Xamarin Studio)
20 | *.userprefs
21 |
22 | # Build results
23 | [Dd]ebug/
24 | [Dd]ebugPublic/
25 | [Rr]elease/
26 | [Rr]eleases/
27 | x64/
28 | x86/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUNIT
47 | *.VisualState.xml
48 | TestResult.xml
49 |
50 | # Build Results of an ATL Project
51 | [Dd]ebugPS/
52 | [Rr]eleasePS/
53 | dlldata.c
54 |
55 | # Benchmark Results
56 | BenchmarkDotNet.Artifacts/
57 |
58 | # .NET Core
59 | project.lock.json
60 | project.fragment.lock.json
61 | artifacts/
62 |
63 | # StyleCop
64 | StyleCopReport.xml
65 |
66 | # Files built by Visual Studio
67 | *_i.c
68 | *_p.c
69 | *_i.h
70 | *.ilk
71 | *.meta
72 | *.obj
73 | *.iobj
74 | *.pch
75 | *.pdb
76 | *.ipdb
77 | *.pgc
78 | *.pgd
79 | *.rsp
80 | *.sbr
81 | *.tlb
82 | *.tli
83 | *.tlh
84 | *.tmp
85 | *.tmp_proj
86 | *.log
87 | *.vspscc
88 | *.vssscc
89 | .builds
90 | *.pidb
91 | *.svclog
92 | *.scc
93 |
94 | # Chutzpah Test files
95 | _Chutzpah*
96 |
97 | # Visual C++ cache files
98 | ipch/
99 | *.aps
100 | *.ncb
101 | *.opendb
102 | *.opensdf
103 | *.sdf
104 | *.cachefile
105 | *.VC.db
106 | *.VC.VC.opendb
107 |
108 | # Visual Studio profiler
109 | *.psess
110 | *.vsp
111 | *.vspx
112 | *.sap
113 |
114 | # Visual Studio Trace Files
115 | *.e2e
116 |
117 | # TFS 2012 Local Workspace
118 | $tf/
119 |
120 | # Guidance Automation Toolkit
121 | *.gpState
122 |
123 | # ReSharper is a .NET coding add-in
124 | _ReSharper*/
125 | *.[Rr]e[Ss]harper
126 | *.DotSettings.user
127 |
128 | # JustCode is a .NET coding add-in
129 | .JustCode
130 |
131 | # TeamCity is a build add-in
132 | _TeamCity*
133 |
134 | # DotCover is a Code Coverage Tool
135 | *.dotCover
136 |
137 | # AxoCover is a Code Coverage Tool
138 | .axoCover/*
139 | !.axoCover/settings.json
140 |
141 | # Visual Studio code coverage results
142 | *.coverage
143 | *.coveragexml
144 |
145 | # NCrunch
146 | _NCrunch_*
147 | .*crunch*.local.xml
148 | nCrunchTemp_*
149 |
150 | # MightyMoose
151 | *.mm.*
152 | AutoTest.Net/
153 |
154 | # Web workbench (sass)
155 | .sass-cache/
156 |
157 | # Installshield output folder
158 | [Ee]xpress/
159 |
160 | # DocProject is a documentation generator add-in
161 | DocProject/buildhelp/
162 | DocProject/Help/*.HxT
163 | DocProject/Help/*.HxC
164 | DocProject/Help/*.hhc
165 | DocProject/Help/*.hhk
166 | DocProject/Help/*.hhp
167 | DocProject/Help/Html2
168 | DocProject/Help/html
169 |
170 | # Click-Once directory
171 | publish/
172 |
173 | # Publish Web Output
174 | *.[Pp]ublish.xml
175 | *.azurePubxml
176 | # Note: Comment the next line if you want to checkin your web deploy settings,
177 | # but database connection strings (with potential passwords) will be unencrypted
178 | *.pubxml
179 | *.publishproj
180 |
181 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
182 | # checkin your Azure Web App publish settings, but sensitive information contained
183 | # in these scripts will be unencrypted
184 | PublishScripts/
185 |
186 | # NuGet Packages
187 | *.nupkg
188 | # The packages folder can be ignored because of Package Restore
189 | **/[Pp]ackages/*
190 | # except build/, which is used as an MSBuild target.
191 | !**/[Pp]ackages/build/
192 | # Uncomment if necessary however generally it will be regenerated when needed
193 | #!**/[Pp]ackages/repositories.config
194 | # NuGet v3's project.json files produces more ignorable files
195 | *.nuget.props
196 | *.nuget.targets
197 |
198 | # Microsoft Azure Build Output
199 | csx/
200 | *.build.csdef
201 |
202 | # Microsoft Azure Emulator
203 | ecf/
204 | rcf/
205 |
206 | # Windows Store app package directories and files
207 | AppPackages/
208 | BundleArtifacts/
209 | Package.StoreAssociation.xml
210 | _pkginfo.txt
211 | *.appx
212 |
213 | # Visual Studio cache files
214 | # files ending in .cache can be ignored
215 | *.[Cc]ache
216 | # but keep track of directories ending in .cache
217 | !*.[Cc]ache/
218 |
219 | # Others
220 | ClientBin/
221 | ~$*
222 | *~
223 | *.dbmdl
224 | *.dbproj.schemaview
225 | *.jfm
226 | *.pfx
227 | *.publishsettings
228 | orleans.codegen.cs
229 |
230 | # Including strong name files can present a security risk
231 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
232 | #*.snk
233 |
234 | # Since there are multiple workflows, uncomment next line to ignore bower_components
235 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
236 | #bower_components/
237 |
238 | # RIA/Silverlight projects
239 | Generated_Code/
240 |
241 | # Backup & report files from converting an old project file
242 | # to a newer Visual Studio version. Backup files are not needed,
243 | # because we have git ;-)
244 | _UpgradeReport_Files/
245 | Backup*/
246 | UpgradeLog*.XML
247 | UpgradeLog*.htm
248 | ServiceFabricBackup/
249 | *.rptproj.bak
250 |
251 | # SQL Server files
252 | *.mdf
253 | *.ldf
254 | *.ndf
255 |
256 | # Business Intelligence projects
257 | *.rdl.data
258 | *.bim.layout
259 | *.bim_*.settings
260 | *.rptproj.rsuser
261 |
262 | # Microsoft Fakes
263 | FakesAssemblies/
264 |
265 | # GhostDoc plugin setting file
266 | *.GhostDoc.xml
267 |
268 | # Node.js Tools for Visual Studio
269 | .ntvs_analysis.dat
270 | node_modules/
271 |
272 | # Visual Studio 6 build log
273 | *.plg
274 |
275 | # Visual Studio 6 workspace options file
276 | *.opt
277 |
278 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
279 | *.vbw
280 |
281 | # Visual Studio LightSwitch build output
282 | **/*.HTMLClient/GeneratedArtifacts
283 | **/*.DesktopClient/GeneratedArtifacts
284 | **/*.DesktopClient/ModelManifest.xml
285 | **/*.Server/GeneratedArtifacts
286 | **/*.Server/ModelManifest.xml
287 | _Pvt_Extensions
288 |
289 | # Paket dependency manager
290 | .paket/paket.exe
291 | paket-files/
292 |
293 | # FAKE - F# Make
294 | .fake/
295 |
296 | # JetBrains Rider
297 | .idea/
298 | *.sln.iml
299 |
300 | # CodeRush
301 | .cr/
302 |
303 | # Python Tools for Visual Studio (PTVS)
304 | __pycache__/
305 | *.pyc
306 |
307 | # Cake - Uncomment if you are using it
308 | # tools/**
309 | # !tools/packages.config
310 |
311 | # Tabs Studio
312 | *.tss
313 |
314 | # Telerik's JustMock configuration file
315 | *.jmconfig
316 |
317 | # BizTalk build output
318 | *.btp.cs
319 | *.btm.cs
320 | *.odx.cs
321 | *.xsd.cs
322 |
323 | # OpenCover UI analysis results
324 | OpenCover/
325 |
326 | # Azure Stream Analytics local run output
327 | ASALocalRun/
328 |
329 | # MSBuild Binary and Structured Log
330 | *.binlog
331 |
332 | # NVidia Nsight GPU debugger configuration file
333 | *.nvuser
334 |
335 | # MFractors (Xamarin productivity tool) working folder
336 | .mfractor/
337 |
338 | # Local History for Visual Studio
339 | .localhistory/
340 |
341 |
342 | # End of https://www.gitignore.io/api/csharp
--------------------------------------------------------------------------------
/cdk/csharp/Cdk.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26124.0
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cdk", "src\Cdk\Cdk.csproj", "{F90E7C82-F4CE-4CF0-8B3C-3BBEE813389A}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheLambdaPowerTunerStackTest", "tests\Cdk.Tests\Cdk.Tests.csproj", "{C97C3EF9-2E85-4625-B512-6D43FB272F8C}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|x64 = Debug|x64
14 | Debug|x86 = Debug|x86
15 | Release|Any CPU = Release|Any CPU
16 | Release|x64 = Release|x64
17 | Release|x86 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {F90E7C82-F4CE-4CF0-8B3C-3BBEE813389A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {F90E7C82-F4CE-4CF0-8B3C-3BBEE813389A}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {F90E7C82-F4CE-4CF0-8B3C-3BBEE813389A}.Debug|x64.ActiveCfg = Debug|Any CPU
26 | {F90E7C82-F4CE-4CF0-8B3C-3BBEE813389A}.Debug|x64.Build.0 = Debug|Any CPU
27 | {F90E7C82-F4CE-4CF0-8B3C-3BBEE813389A}.Debug|x86.ActiveCfg = Debug|Any CPU
28 | {F90E7C82-F4CE-4CF0-8B3C-3BBEE813389A}.Debug|x86.Build.0 = Debug|Any CPU
29 | {F90E7C82-F4CE-4CF0-8B3C-3BBEE813389A}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {F90E7C82-F4CE-4CF0-8B3C-3BBEE813389A}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {F90E7C82-F4CE-4CF0-8B3C-3BBEE813389A}.Release|x64.ActiveCfg = Release|Any CPU
32 | {F90E7C82-F4CE-4CF0-8B3C-3BBEE813389A}.Release|x64.Build.0 = Release|Any CPU
33 | {F90E7C82-F4CE-4CF0-8B3C-3BBEE813389A}.Release|x86.ActiveCfg = Release|Any CPU
34 | {F90E7C82-F4CE-4CF0-8B3C-3BBEE813389A}.Release|x86.Build.0 = Release|Any CPU
35 | {C97C3EF9-2E85-4625-B512-6D43FB272F8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {C97C3EF9-2E85-4625-B512-6D43FB272F8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {C97C3EF9-2E85-4625-B512-6D43FB272F8C}.Debug|x64.ActiveCfg = Debug|Any CPU
38 | {C97C3EF9-2E85-4625-B512-6D43FB272F8C}.Debug|x64.Build.0 = Debug|Any CPU
39 | {C97C3EF9-2E85-4625-B512-6D43FB272F8C}.Debug|x86.ActiveCfg = Debug|Any CPU
40 | {C97C3EF9-2E85-4625-B512-6D43FB272F8C}.Debug|x86.Build.0 = Debug|Any CPU
41 | {C97C3EF9-2E85-4625-B512-6D43FB272F8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {C97C3EF9-2E85-4625-B512-6D43FB272F8C}.Release|Any CPU.Build.0 = Release|Any CPU
43 | {C97C3EF9-2E85-4625-B512-6D43FB272F8C}.Release|x64.ActiveCfg = Release|Any CPU
44 | {C97C3EF9-2E85-4625-B512-6D43FB272F8C}.Release|x64.Build.0 = Release|Any CPU
45 | {C97C3EF9-2E85-4625-B512-6D43FB272F8C}.Release|x86.ActiveCfg = Release|Any CPU
46 | {C97C3EF9-2E85-4625-B512-6D43FB272F8C}.Release|x86.Build.0 = Release|Any CPU
47 | EndGlobalSection
48 | EndGlobal
49 |
--------------------------------------------------------------------------------
/cdk/csharp/README.md:
--------------------------------------------------------------------------------
1 | # How to deploy AWS Lambda Power Tuning using the CDK for C#
2 |
3 | This CDK project deploys *AWS Lambda Power Tuning* using C#.
4 |
5 | You can use the project as a standalone or reuse it within your own CDK projects.
6 |
7 |
8 | ## CDK Prerequisites
9 |
10 | See [here](../README.md).
11 |
12 |
13 | ## Language specific prerequisites
14 |
15 | - [.NET 8.0 or later](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_prerequisites)
16 | - [Requirements for CDK with C#](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-csharp.html#csharp-prerequisites)
17 |
18 | ## Building, testing, and deploying the app
19 |
20 | * `dotnet build` compile this app
21 | * `dotnet test` test this app
22 | * `cdk deploy` deploy this app
23 |
--------------------------------------------------------------------------------
/cdk/csharp/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "dotnet build && dotnet run --project src/Cdk/Cdk.csproj",
3 | "watch": {
4 | "include": [
5 | "**"
6 | ],
7 | "exclude": [
8 | "README.md",
9 | "cdk*.json",
10 | "src/*/obj",
11 | "src/*/bin",
12 | "src/*.sln",
13 | "src/*/GlobalSuppressions.cs",
14 | "src/*/*.csproj"
15 | ]
16 | },
17 | "context": {
18 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
19 | "@aws-cdk/core:checkSecretUsage": true,
20 | "@aws-cdk/core:target-partitions": [
21 | "aws",
22 | "aws-cn"
23 | ],
24 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
25 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
26 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
27 | "@aws-cdk/aws-iam:minimizePolicies": true,
28 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
29 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
30 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
31 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
32 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
33 | "@aws-cdk/core:enablePartitionLiterals": true,
34 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
35 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true,
36 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
37 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
38 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
39 | "@aws-cdk/aws-route53-patters:useCertificate": true,
40 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
41 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
42 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
43 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
44 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
45 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
46 | "@aws-cdk/aws-redshift:columnId": true,
47 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
48 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
49 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
50 | "@aws-cdk/aws-kms:aliasNameRef": true,
51 | "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
52 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
53 | "@aws-cdk/aws-efs:denyAnonymousAccess": true,
54 | "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
55 | "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
56 | "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
57 | "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
58 | "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
59 | "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/cdk/csharp/src/Cdk/Cdk.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 |
7 | Major
8 | C#
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 | <_Parameter1>$(MSBuildProjectName).Tests
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/cdk/csharp/src/Cdk/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Potential Code Quality Issues", "RECS0026:Possible unassigned object created by 'new'", Justification = "Constructs add themselves to the scope in which they are created")]
2 |
--------------------------------------------------------------------------------
/cdk/csharp/src/Cdk/Program.cs:
--------------------------------------------------------------------------------
1 | using Amazon.CDK;
2 |
3 | namespace TheLambdaPowerTunerStack
4 | {
5 | sealed class Program
6 | {
7 | public static void Main(string[] args)
8 | {
9 | var app = new App();
10 | new TheLambdaPowerTunerStack(app, "TheLambdaPowerTunerStack", new StackProps());
11 | app.Synth();
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/cdk/csharp/src/Cdk/TheLambdaPowerTunerStack.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Amazon.CDK;
3 | using Amazon.CDK.AWS.SAM;
4 | using Constructs;
5 |
6 | namespace TheLambdaPowerTunerStack
7 | {
8 | public class TheLambdaPowerTunerStack : Stack
9 | {
10 | internal TheLambdaPowerTunerStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
11 | {
12 | var stateMachineConfiguration = new Dictionary
13 | {
14 | // { "lambdaResource", "*" },
15 | // { "PowerValues", "128,256,512,1024,1536,3008" },
16 | // { "visualizationURL", "https://lambda-power-tuning.show/" },
17 | // { "totalExecutionTimeout", "300" },
18 | // { "payloadS3Key", "*" },
19 | // { "logGroupRetentionInDays", "7" },
20 | // { "stateMachineNamePrefix", "powerTuningStateMachine" },
21 | // { "permissionsBoundary", "" },
22 | // { "payloadS3Bucket", "" },
23 | // { "layerSdkName", "" },
24 | // { "securityGroupIds", "" },
25 | // { "subnetIds", "" },
26 | };
27 |
28 | new CfnApplication(this, "SAR", new CfnApplicationProps
29 | {
30 | Location = new CfnApplication.ApplicationLocationProperty {
31 | ApplicationId = "arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning",
32 | SemanticVersion = "4.3.6"
33 | },
34 | Parameters = stateMachineConfiguration
35 | }
36 | );
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/cdk/csharp/tests/Cdk.Tests/Cdk.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 | false
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/cdk/csharp/tests/Cdk.Tests/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using Microsoft.VisualStudio.TestTools.UnitTesting;
--------------------------------------------------------------------------------
/cdk/csharp/tests/Cdk.Tests/TheLambdaPowerTunerStackTest.cs:
--------------------------------------------------------------------------------
1 | using Amazon.CDK;
2 | using Amazon.CDK.Assertions;
3 |
4 | using ObjectDict = System.Collections.Generic.Dictionary;
5 |
6 | namespace TheLambdaPowerTunerStack.Tests
7 | {
8 | [TestClass]
9 | public class TheLambdaPowerTunerStackTest
10 | {
11 | private Template? _template;
12 |
13 | [TestInitialize()]
14 | public void Startup()
15 | {
16 | var app = new App();
17 | var stack = new TheLambdaPowerTunerStack(app, "TheLambdaPowerTunerStack", new StackProps());
18 | _template = Template.FromStack(stack);
19 | }
20 |
21 | [TestMethod]
22 | public void TestSar()
23 | {
24 | _template?.HasResourceProperties("AWS::Serverless::Application", new ObjectDict
25 | {
26 | { "Location" , new ObjectDict
27 | {
28 | { "ApplicationId", "arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning" },
29 | }
30 | }
31 | }
32 | );
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/cdk/go/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # go.sum should be committed
15 | !go.sum
16 |
17 | # CDK asset staging directory
18 | .cdk.staging
19 | cdk.out
20 | cdk
21 |
--------------------------------------------------------------------------------
/cdk/go/README.md:
--------------------------------------------------------------------------------
1 | # How to deploy AWS Lambda Power Tuning using the CDK for go
2 |
3 | This CDK project deploys *AWS Lambda Power Tuning* using go.
4 |
5 | You can use the project as a standalone or reuse it within your own CDK projects.
6 |
7 |
8 | ## CDK Prerequisites
9 |
10 | See [here](../README.md).
11 |
12 |
13 | ## Language specific prerequisites
14 |
15 | - [Go 1.18 or later.](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_prerequisites)
16 | - [Requirements for CDK with go](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-go.html)
17 |
18 | ## Building, testing, and deploying the app
19 |
20 | * `go build` compile this app
21 | * `go test` test this app
22 | * `cdk deploy` deploy this app
--------------------------------------------------------------------------------
/cdk/go/cdk.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/aws/aws-cdk-go/awscdk/v2"
5 |
6 | "github.com/aws/jsii-runtime-go"
7 | )
8 |
9 | func main() {
10 | defer jsii.Close()
11 |
12 | app := awscdk.NewApp(nil)
13 |
14 | NewLambdaPowerTunerStack(app, "The-Lambda-Power-Tuner-Stack", &LambdaPowerTunerStackProps{
15 | awscdk.StackProps{
16 | Env: env(),
17 | },
18 | })
19 |
20 | app.Synth(nil)
21 | }
22 |
23 | // env determines the AWS environment (account+region) in which our stack is to
24 | // be deployed. For more information see: https://docs.aws.amazon.com/cdk/latest/guide/environments.html
25 | func env() *awscdk.Environment {
26 | // If unspecified, this stack will be "environment-agnostic".
27 | // Account/Region-dependent features and context lookups will not work, but a
28 | // single synthesized template can be deployed anywhere.
29 | //---------------------------------------------------------------------------
30 | return nil
31 |
32 | // Uncomment if you know exactly what account and region you want to deploy
33 | // the stack to. This is the recommendation for production stacks.
34 | //---------------------------------------------------------------------------
35 | // return &awscdk.Environment{
36 | // Account: jsii.String("123456789012"),
37 | // Region: jsii.String("us-east-1"),
38 | // }
39 |
40 | // Uncomment to specialize this stack for the AWS Account and Region that are
41 | // implied by the current CLI configuration. This is recommended for dev
42 | // stacks.
43 | //---------------------------------------------------------------------------
44 | // return &awscdk.Environment{
45 | // Account: jsii.String(os.Getenv("CDK_DEFAULT_ACCOUNT")),
46 | // Region: jsii.String(os.Getenv("CDK_DEFAULT_REGION")),
47 | // }
48 | }
49 |
--------------------------------------------------------------------------------
/cdk/go/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "go mod download && go run cdk.go",
3 | "watch": {
4 | "include": [
5 | "**"
6 | ],
7 | "exclude": [
8 | "README.md",
9 | "cdk*.json",
10 | "go.mod",
11 | "go.sum",
12 | "**/*test.go"
13 | ]
14 | },
15 | "context": {
16 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
17 | "@aws-cdk/core:checkSecretUsage": true,
18 | "@aws-cdk/core:target-partitions": [
19 | "aws",
20 | "aws-cn"
21 | ],
22 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
23 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
24 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
25 | "@aws-cdk/aws-iam:minimizePolicies": true,
26 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
27 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
28 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
29 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
30 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
31 | "@aws-cdk/core:enablePartitionLiterals": true,
32 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
33 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true,
34 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
35 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
36 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
37 | "@aws-cdk/aws-route53-patters:useCertificate": true,
38 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
39 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
40 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
41 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
42 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
43 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
44 | "@aws-cdk/aws-redshift:columnId": true,
45 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
46 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
47 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
48 | "@aws-cdk/aws-kms:aliasNameRef": true,
49 | "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
50 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
51 | "@aws-cdk/aws-efs:denyAnonymousAccess": true,
52 | "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
53 | "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
54 | "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
55 | "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
56 | "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
57 | "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
58 | "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
59 | "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
60 | "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/cdk/go/go.mod:
--------------------------------------------------------------------------------
1 | module cdk
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/aws/aws-cdk-go/awscdk/v2 v2.131.0
7 | github.com/aws/constructs-go/constructs/v10 v10.3.0
8 | github.com/aws/jsii-runtime-go v1.94.0
9 | )
10 |
11 | require (
12 | github.com/Masterminds/semver/v3 v3.2.1 // indirect
13 | github.com/cdklabs/awscdk-asset-awscli-go/awscliv1/v2 v2.2.202 // indirect
14 | github.com/cdklabs/awscdk-asset-kubectl-go/kubectlv20/v2 v2.1.2 // indirect
15 | github.com/cdklabs/awscdk-asset-node-proxy-agent-go/nodeproxyagentv6/v2 v2.0.1 // indirect
16 | github.com/fatih/color v1.16.0 // indirect
17 | github.com/mattn/go-colorable v0.1.13 // indirect
18 | github.com/mattn/go-isatty v0.0.20 // indirect
19 | github.com/yuin/goldmark v1.4.13 // indirect
20 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
21 | golang.org/x/mod v0.14.0 // indirect
22 | golang.org/x/sys v0.14.0 // indirect
23 | golang.org/x/tools v0.16.1 // indirect
24 | )
25 |
--------------------------------------------------------------------------------
/cdk/go/go.sum:
--------------------------------------------------------------------------------
1 | github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
2 | github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
3 | github.com/aws/aws-cdk-go/awscdk/v2 v2.131.0 h1:U/sGMzodvu5mVQpFBWI/BGkdZ3el/sb7J3pF3HBjQLQ=
4 | github.com/aws/aws-cdk-go/awscdk/v2 v2.131.0/go.mod h1:TpmJwOnoajvRtwnLlJoxEoppb9sVoCLfPGLdgoTDH7o=
5 | github.com/aws/constructs-go/constructs/v10 v10.3.0 h1:LsjBIMiaDX/vqrXWhzTquBJ9pPdi02/H+z1DCwg0PEM=
6 | github.com/aws/constructs-go/constructs/v10 v10.3.0/go.mod h1:GgzwIwoRJ2UYsr3SU+JhAl+gq5j39bEMYf8ev3J+s9s=
7 | github.com/aws/jsii-runtime-go v1.94.0 h1:VuVDx0xL2gbsJthUMfP+SwAXGkSEQd0GKm0ydZ8xga8=
8 | github.com/aws/jsii-runtime-go v1.94.0/go.mod h1:tQOz8aAMzM2XsRUDsnUgPvGcHNAzR/xtH0OgeM0lTWo=
9 | github.com/cdklabs/awscdk-asset-awscli-go/awscliv1/v2 v2.2.202 h1:VixXB9DnHN8oP7pXipq8GVFPjWCOdeNxIaS/ZyUwTkI=
10 | github.com/cdklabs/awscdk-asset-awscli-go/awscliv1/v2 v2.2.202/go.mod h1:iPUti/SWjA3XAS3CpnLciFjS8TN9Y+8mdZgDfSgcyus=
11 | github.com/cdklabs/awscdk-asset-kubectl-go/kubectlv20/v2 v2.1.2 h1:k+WD+6cERd59Mao84v0QtRrcdZuuSMfzlEmuIypKnVs=
12 | github.com/cdklabs/awscdk-asset-kubectl-go/kubectlv20/v2 v2.1.2/go.mod h1:CvFHBo0qcg8LUkJqIxQtP1rD/sNGv9bX3L2vHT2FUAo=
13 | github.com/cdklabs/awscdk-asset-node-proxy-agent-go/nodeproxyagentv6/v2 v2.0.1 h1:MBBQNKKPJ5GArbctgwpiCy7KmwGjHDjUUH5wEzwIq8w=
14 | github.com/cdklabs/awscdk-asset-node-proxy-agent-go/nodeproxyagentv6/v2 v2.0.1/go.mod h1:/2WiXEft9s8ViJjD01CJqDuyJ8HXBjhBLtK5OvJfdSc=
15 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
16 | github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
17 | github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
18 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
19 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
20 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
21 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
22 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
23 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
24 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
25 | github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
26 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
27 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
28 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
29 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
30 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
31 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
32 | golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
33 | golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
34 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
35 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
36 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
37 | golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
38 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
39 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
40 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
41 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
42 | golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
43 | golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
44 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
45 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
46 | golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
47 | golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
48 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
49 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
50 |
--------------------------------------------------------------------------------
/cdk/go/lambda_power_tuner_stack.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/aws/aws-cdk-go/awscdk/v2"
5 | "github.com/aws/aws-cdk-go/awscdk/v2/awssam"
6 | "github.com/aws/constructs-go/constructs/v10"
7 | "github.com/aws/jsii-runtime-go"
8 | )
9 |
10 | type LambdaPowerTunerStackProps struct {
11 | awscdk.StackProps
12 | }
13 |
14 | func NewLambdaPowerTunerStack(scope constructs.Construct, id string, props *LambdaPowerTunerStackProps) awscdk.Stack {
15 | var sprops awscdk.StackProps
16 | if props != nil {
17 | sprops = props.StackProps
18 | }
19 | stack := awscdk.NewStack(scope, &id, &sprops)
20 |
21 | awssam.NewCfnApplication(stack, jsii.String("powerTuner"), &awssam.CfnApplicationProps{
22 | Location: map[string]string{
23 | "applicationId": "arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning",
24 | "semanticVersion": "4.3.6",
25 | },
26 | Parameters: map[string]string{
27 | // "lambdaResource": "*",
28 | // "PowerValues": "128,256,512,1024,1536,3008",
29 | // "visualizationURL": "https://lambda-power-tuning.show/",
30 | // "totalExecutionTimeout": "300",
31 | // "payloadS3Key": "*",
32 | // "logGroupRetentionInDays": "7",
33 | // "stateMachineNamePrefix": "powerTuningStateMachine",
34 | // "permissionsBoundary": "",
35 | // "payloadS3Bucket": "",
36 | // "layerSdkName": "",
37 | // "securityGroupIds": "",
38 | // "subnetIds": ""
39 | },
40 | })
41 |
42 | return stack
43 | }
44 |
--------------------------------------------------------------------------------
/cdk/go/lambda_power_tuner_stack_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/aws/aws-cdk-go/awscdk/v2"
7 | "github.com/aws/aws-cdk-go/awscdk/v2/assertions"
8 | "github.com/aws/jsii-runtime-go"
9 | )
10 |
11 | func TestLambdaPowerTunerStack(t *testing.T) {
12 | // GIVEN
13 | app := awscdk.NewApp(nil)
14 |
15 | // WHEN
16 | stack := NewLambdaPowerTunerStack(app, "MyStack", nil)
17 |
18 | // THEN
19 | template := assertions.Template_FromStack(stack, nil)
20 |
21 | template.HasResourceProperties(jsii.String("AWS::Serverless::Application"), map[string]interface{}{
22 | "Location": map[string]string{
23 | "ApplicationId": "arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning",
24 | },
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/cdk/java/.gitignore:
--------------------------------------------------------------------------------
1 | .classpath.txt
2 | target
3 | .classpath
4 | .project
5 | .idea
6 | .settings
7 | .vscode
8 | *.iml
9 |
10 | # CDK asset staging directory
11 | .cdk.staging
12 | cdk.out
13 |
14 |
--------------------------------------------------------------------------------
/cdk/java/README.md:
--------------------------------------------------------------------------------
1 | # How to deploy the AWS Lambda Power Tuning using the CDK for Java
2 |
3 | This CDK project deploys *AWS Lambda Power Tuning* using Java.
4 |
5 | You can use the project as a standalone or reuse it within your own CDK projects.
6 |
7 |
8 | ## CDK Prerequisites
9 |
10 | See [here](../README.md).
11 |
12 |
13 | ## Language specific prerequisites
14 | - [Java Development Kit (JDK) 8 (a.k.a. 1.8) or later](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_prerequisites)
15 | - [Apache Maven 3.5 or later](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_prerequisites)
16 | - [Requirements for CDK with Java](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-java.html)
17 |
18 | ## Building, testing, and deploying the app
19 | * `mvn package` compile and run tests
20 | * `cdk synth` emits the synthesized CloudFormation template
21 | * `cdk deploy` deploy this app
22 |
23 | The `cdk.json` file tells the CDK Toolkit how to execute your app.
24 |
25 | It is a [Maven](https://maven.apache.org/) based project, so you can open this project with any Maven compatible Java IDE to build and run tests.
--------------------------------------------------------------------------------
/cdk/java/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "mvn -e -q compile exec:java",
3 | "watch": {
4 | "include": [
5 | "**"
6 | ],
7 | "exclude": [
8 | "README.md",
9 | "cdk*.json",
10 | "target",
11 | "pom.xml",
12 | "src/test"
13 | ]
14 | },
15 | "context": {
16 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
17 | "@aws-cdk/core:checkSecretUsage": true,
18 | "@aws-cdk/core:target-partitions": [
19 | "aws",
20 | "aws-cn"
21 | ],
22 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
23 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
24 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
25 | "@aws-cdk/aws-iam:minimizePolicies": true,
26 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
27 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
28 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
29 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
30 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
31 | "@aws-cdk/core:enablePartitionLiterals": true,
32 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
33 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true,
34 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
35 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
36 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
37 | "@aws-cdk/aws-route53-patters:useCertificate": true,
38 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
39 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
40 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
41 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
42 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
43 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
44 | "@aws-cdk/aws-redshift:columnId": true,
45 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
46 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
47 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
48 | "@aws-cdk/aws-kms:aliasNameRef": true,
49 | "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
50 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
51 | "@aws-cdk/aws-efs:denyAnonymousAccess": true,
52 | "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
53 | "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
54 | "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
55 | "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
56 | "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
57 | "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
58 | "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/cdk/java/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.myorg
7 | java
8 | 0.1
9 |
10 |
11 | UTF-8
12 | 2.114.1
13 | [10.0.0,11.0.0)
14 | 5.7.1
15 |
16 |
17 |
18 |
19 |
20 | org.apache.maven.plugins
21 | maven-compiler-plugin
22 | 3.11.0
23 |
24 | 17
25 |
26 |
27 |
28 |
29 | org.codehaus.mojo
30 | exec-maven-plugin
31 | 3.1.0
32 |
33 | com.myorg.TheLambdaPowerTunerApp
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | software.amazon.awscdk
43 | aws-cdk-lib
44 | ${cdk.version}
45 |
46 |
47 |
48 | software.constructs
49 | constructs
50 | ${constructs.version}
51 |
52 |
53 |
54 | org.junit.jupiter
55 | junit-jupiter
56 | ${junit.version}
57 | test
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/cdk/java/src/main/java/com/myorg/TheLambdaPowerTunerApp.java:
--------------------------------------------------------------------------------
1 | package com.myorg;
2 |
3 | import software.amazon.awscdk.App;
4 | import software.amazon.awscdk.StackProps;
5 |
6 | public class TheLambdaPowerTunerApp {
7 | public static void main(final String[] args) {
8 | App app = new App();
9 |
10 | new TheLambdaPowerTunerStack(app, "JavaStack", StackProps.builder()
11 | // If you don't specify 'env', this stack will be environment-agnostic.
12 | // Account/Region-dependent features and context lookups will not work,
13 | // but a single synthesized template can be deployed anywhere.
14 |
15 | // Uncomment the next block to specialize this stack for the AWS Account
16 | // and Region that are implied by the current CLI configuration.
17 | /*
18 | .env(Environment.builder()
19 | .account(System.getenv("CDK_DEFAULT_ACCOUNT"))
20 | .region(System.getenv("CDK_DEFAULT_REGION"))
21 | .build())
22 | */
23 |
24 | // Uncomment the next block if you know exactly what Account and Region you
25 | // want to deploy the stack to.
26 | /*
27 | .env(Environment.builder()
28 | .account("123456789012")
29 | .region("us-east-1")
30 | .build())
31 | */
32 |
33 | // For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html
34 | .build());
35 |
36 | app.synth();
37 | }
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/cdk/java/src/main/java/com/myorg/TheLambdaPowerTunerStack.java:
--------------------------------------------------------------------------------
1 | package com.myorg;
2 |
3 | import software.constructs.Construct;
4 |
5 | import software.amazon.awscdk.Stack;
6 | import software.amazon.awscdk.StackProps;
7 | import software.amazon.awscdk.services.sam.CfnApplication;
8 | import software.amazon.awscdk.services.sam.CfnApplication.ApplicationLocationProperty;
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 | public class TheLambdaPowerTunerStack extends Stack {
13 |
14 | //Set constants
15 | private static final String SAR_APPLICATION_ID = "arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning";
16 | private static final String SAR_SEMANTIC_VERSION = "4.3.6";
17 |
18 | public TheLambdaPowerTunerStack(final Construct scope, final String id) {
19 | this(scope, id, null);
20 | }
21 |
22 | public TheLambdaPowerTunerStack(final Construct scope, final String id, final StackProps props) {
23 | super(scope, id, props);
24 |
25 | //create an empty Map
26 | Map parameters = new HashMap<>();
27 |
28 | // parameters.put("lambdaResource", "*")
29 | // parameters.put("PowerValues", "128,256,512,1024,1536,3008")
30 | // parameters.put("visualizationURL", "https://lambda-power-tuning.shw/")
31 | // parameters.put("totalExecutionTimeout", "300")
32 | // parameters.put("payloadS3Key", "*")
33 | // parameters.put("logGroupRetentionInDays", "7")
34 | // parameters.put("stateMachineNamePrefix", "powerTuningStateMachine")
35 | // parameters.put("permissionsBoundary", "")
36 | // parameters.put("payloadS3Bucket", "")
37 | // parameters.put("layerSdkName", "")
38 | // parameters.put("securityGroupIds", "")
39 | // parameters.put("subnetIds", "")
40 | // parameters.put("lambdaResource", "*")
41 |
42 |
43 | CfnApplication cfnApplication = CfnApplication.Builder.create(this, "SAR")
44 | .location(
45 | ApplicationLocationProperty.builder()
46 | .applicationId(SAR_APPLICATION_ID)
47 | .semanticVersion(SAR_SEMANTIC_VERSION)
48 | .build()
49 | )
50 | .parameters(parameters)
51 | .build();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/cdk/java/src/test/java/com/myorg/JavaTest.java:
--------------------------------------------------------------------------------
1 | package com.myorg;
2 |
3 | import software.amazon.awscdk.App;
4 | import software.amazon.awscdk.assertions.Template;
5 | import java.io.IOException;
6 |
7 | import java.util.HashMap;
8 | import java.util.Map;
9 |
10 | import org.junit.jupiter.api.Test;
11 |
12 | public class JavaTest {
13 |
14 | @Test
15 | public void testStack() throws IOException {
16 | App app = new App();
17 | TheLambdaPowerTunerStack stack = new TheLambdaPowerTunerStack(app, "test");
18 |
19 | Template template = Template.fromStack(stack);
20 | Map child = new HashMap<>();
21 | Map root = new HashMap<>();
22 |
23 | root.put("Location", child);
24 | child.put("ApplicationId", "arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning");
25 |
26 | template.hasResourceProperties("AWS::Serverless::Application", root);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/cdk/python/README.md:
--------------------------------------------------------------------------------
1 | # How to deploy the AWS Lambda Power Tuning using the CDK for Python
2 |
3 | This CDK project deploys *AWS Lambda Power Tuning* using Python.
4 |
5 | You can use the project as a standalone or reuse it within your own CDK projects.
6 |
7 |
8 | ## CDK Prerequisites
9 |
10 | See [here](../README.md).
11 |
12 |
13 | ## Language specific prerequisites
14 |
15 | - [Python 3.6 or later](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_prerequisites)
16 | - [Requirements for CDK with Python](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-python.html)
17 |
18 | ## Virtualenv setup and requirements
19 |
20 | To manually create a virtualenv on MacOS and Linux:
21 |
22 | ```bash
23 | $ python -m venv .venv
24 | ```
25 |
26 | After the init process completes and the virtualenv is created, you can use the following
27 | step to activate your virtualenv.
28 |
29 | ```bash
30 | $ source .venv/bin/activate
31 | ```
32 |
33 | If you are on Windows platform, you would activate the virtualenv like this:
34 |
35 | ```bash
36 | % .venv\Scripts\activate.bat
37 | ```
38 |
39 | Once the virtualenv is activated, you can install the required dependencies.
40 | ```bash
41 | $ pip install -r requirements.txt
42 | ```
43 |
44 | ## Building, testing, and deploying the app
45 | * `pytest` test this app
46 | * `cdk deploy` deploy this app
47 |
--------------------------------------------------------------------------------
/cdk/python/app.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import os
3 |
4 | import aws_cdk as cdk
5 |
6 | from app.lambdapowertuner_stack import TheLambdaPowerTunerStack
7 |
8 |
9 | app = cdk.App()
10 | TheLambdaPowerTunerStack(app, "The-Lambda-Power-Tuner-Stack",
11 | # If you don't specify 'env', this stack will be environment-agnostic.
12 | # Account/Region-dependent features and context lookups will not work,
13 | # but a single synthesized template can be deployed anywhere.
14 |
15 | # Uncomment the next line to specialize this stack for the AWS Account
16 | # and Region that are implied by the current CLI configuration.
17 |
18 | #env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')),
19 |
20 | # Uncomment the next line if you know exactly what Account and Region you
21 | # want to deploy the stack to. */
22 |
23 | #env=cdk.Environment(account='123456789012', region='us-east-1'),
24 |
25 | # For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html
26 | )
27 |
28 | app.synth()
29 |
--------------------------------------------------------------------------------
/cdk/python/app/lambdapowertuner_stack.py:
--------------------------------------------------------------------------------
1 | from aws_cdk import (
2 | Stack
3 | )
4 | from constructs import Construct
5 |
6 | from aws_cdk import aws_sam as sam
7 |
8 |
9 |
10 | class TheLambdaPowerTunerStack(Stack):
11 |
12 | def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
13 | super().__init__(scope, construct_id, **kwargs)
14 |
15 | # The code that defines your stack goes here
16 |
17 |
18 | stateMachineConfiguration= {
19 | # "lambdaResource": "*",
20 | # "PowerValues": "128,256,512,1024,1536,3008",
21 | # "visualizationURL": "https://lambda-power-tuning.shw/",
22 | # "totalExecutionTimeout": "300",
23 | # "payloadS3Key": "*",
24 | # "logGroupRetentionInDays": "7",
25 | # "stateMachineNamePrefix": "powerTuningStateMachine",
26 | # "permissionsBoundary": "",
27 | # "payloadS3Bucket": "",
28 | # "layerSdkName": "",
29 | # "securityGroupIds": "",
30 | # "subnetIds": ""
31 | }
32 |
33 | cfn_application =sam.CfnApplication(
34 | self, "SAR",
35 | location={
36 | "applicationId": "arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning",
37 | "semanticVersion": "4.3.6"
38 | },
39 | parameters = stateMachineConfiguration
40 | )
41 |
--------------------------------------------------------------------------------
/cdk/python/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "python app.py",
3 | "watch": {
4 | "include": [
5 | "**"
6 | ],
7 | "exclude": [
8 | "README.md",
9 | "cdk*.json",
10 | "requirements*.txt",
11 | "source.bat",
12 | "**/__init__.py",
13 | "**/__pycache__",
14 | "tests"
15 | ]
16 | },
17 | "context": {
18 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
19 | "@aws-cdk/core:checkSecretUsage": true,
20 | "@aws-cdk/core:target-partitions": [
21 | "aws",
22 | "aws-cn"
23 | ],
24 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
25 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
26 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
27 | "@aws-cdk/aws-iam:minimizePolicies": true,
28 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
29 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
30 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
31 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
32 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
33 | "@aws-cdk/core:enablePartitionLiterals": true,
34 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
35 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true,
36 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
37 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
38 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
39 | "@aws-cdk/aws-route53-patters:useCertificate": true,
40 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
41 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
42 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
43 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
44 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
45 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
46 | "@aws-cdk/aws-redshift:columnId": true,
47 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
48 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
49 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
50 | "@aws-cdk/aws-kms:aliasNameRef": true,
51 | "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
52 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
53 | "@aws-cdk/aws-efs:denyAnonymousAccess": true,
54 | "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
55 | "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
56 | "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
57 | "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
58 | "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
59 | "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/cdk/python/requirements.txt:
--------------------------------------------------------------------------------
1 | aws-cdk-lib==2.104.0
2 | constructs>=10.0.0,<11.0.0
3 |
--------------------------------------------------------------------------------
/cdk/python/source.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | rem The sole purpose of this script is to make the command
4 | rem
5 | rem source .venv/bin/activate
6 | rem
7 | rem (which activates a Python virtualenv on Linux or Mac OS X) work on Windows.
8 | rem On Windows, this command just runs this batch file (the argument is ignored).
9 | rem
10 | rem Now we don't need to document a Windows command for activating a virtualenv.
11 |
12 | echo Executing .venv\Scripts\activate.bat for you
13 | .venv\Scripts\activate.bat
14 |
--------------------------------------------------------------------------------
/cdk/python/tests/unit/test_lambdapowertuner_stack.py:
--------------------------------------------------------------------------------
1 | import aws_cdk as cdk
2 | import aws_cdk.assertions as assertions
3 |
4 | from app.lambdapowertuner_stack import TheLambdaPowerTunerStack
5 |
6 | # example tests. To run these tests, uncomment this file along with the example
7 | # resource in python/python_stack.py
8 | def test_sar_app_created():
9 | app = cdk.App()
10 | stack = TheLambdaPowerTunerStack(app, "TheLambdaPowerTunerStack")
11 | template = assertions.Template.from_stack(stack)
12 |
13 | template.has_resource_properties("AWS::Serverless::Application", {
14 | "Location":{
15 | "ApplicationId": "arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning"
16 | }
17 | })
18 |
--------------------------------------------------------------------------------
/cdk/typescript/.gitignore:
--------------------------------------------------------------------------------
1 | *.js
2 | !jest.config.js
3 | *.d.ts
4 | node_modules
5 |
6 | # CDK asset staging directory
7 | .cdk.staging
8 | cdk.out
9 |
10 | # Parcel build directories
11 | .cache
12 | .build
13 |
14 | #template file
15 | template.yml
16 |
--------------------------------------------------------------------------------
/cdk/typescript/.npmignore:
--------------------------------------------------------------------------------
1 | *.ts
2 | !*.d.ts
3 |
4 | # CDK asset staging directory
5 | .cdk.staging
6 | cdk.out
7 |
--------------------------------------------------------------------------------
/cdk/typescript/README.md:
--------------------------------------------------------------------------------
1 | # How to deploy the AWS Lambda Power Tuning using the CDK for TypeScript
2 |
3 | This CDK project deploys *AWS Lambda Power Tuning* using TypeScript.
4 |
5 | You can use the project as a standalone or reuse it within your own CDK projects.
6 |
7 |
8 | ## CDK Prerequisites
9 |
10 | See [here](../README.md).
11 |
12 |
13 | ## Language specific prerequisites
14 |
15 | - [TypeScript 3.8 or later](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_prerequisites)
16 | - [Requirements for CDK with TypeScript](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-typescript.html)
17 |
18 | ## Building, testing, and deploying the app
19 |
20 | * `npm run build` compile typescript to js
21 | * `npm run watch` watch for changes and compile
22 | * `npm run test` perform the jest unit tests
23 | * `npm run cdk deploy` deploy this stack to your default AWS account/region
24 |
--------------------------------------------------------------------------------
/cdk/typescript/bin/the-lambda-power-tuner.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import 'source-map-support/register';
3 | import * as cdk from 'aws-cdk-lib';
4 | import { TheLambdaPowerTunerStack } from '../lib/the-lambda-power-tuner-stack';
5 |
6 | const app = new cdk.App();
7 | new TheLambdaPowerTunerStack(app, 'TheLambdaPowerTunerStack');
8 |
--------------------------------------------------------------------------------
/cdk/typescript/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "npx ts-node --prefer-ts-exts bin/the-lambda-power-tuner.ts",
3 | "watch": {
4 | "include": [
5 | "**"
6 | ],
7 | "exclude": [
8 | "README.md",
9 | "cdk*.json",
10 | "**/*.d.ts",
11 | "**/*.js",
12 | "tsconfig.json",
13 | "package*.json",
14 | "yarn.lock",
15 | "node_modules",
16 | "test"
17 | ]
18 | },
19 | "context": {
20 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21 | "@aws-cdk/core:checkSecretUsage": true,
22 | "@aws-cdk/core:target-partitions": [
23 | "aws",
24 | "aws-cn"
25 | ],
26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29 | "@aws-cdk/aws-iam:minimizePolicies": true,
30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35 | "@aws-cdk/core:enablePartitionLiterals": true,
36 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true,
38 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
39 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
40 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
41 | "@aws-cdk/aws-route53-patters:useCertificate": true,
42 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
43 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
44 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
45 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
46 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
47 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
48 | "@aws-cdk/aws-redshift:columnId": true,
49 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
50 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
51 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
52 | "@aws-cdk/aws-kms:aliasNameRef": true,
53 | "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
54 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
55 | "@aws-cdk/aws-efs:denyAnonymousAccess": true,
56 | "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
57 | "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
58 | "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
59 | "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
60 | "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
61 | "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
62 | "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/cdk/typescript/jest.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | roots: ['/test'],
4 | testMatch: ['**/*.test.ts'],
5 | transform: {
6 | '^.+\\.tsx?$': 'ts-jest',
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/cdk/typescript/lib/the-lambda-power-tuner-stack.ts:
--------------------------------------------------------------------------------
1 | import * as cdk from 'aws-cdk-lib';
2 | import { Construct } from 'constructs';
3 | import * as lambda from 'aws-cdk-lib/aws-lambda';
4 | import * as sam from 'aws-cdk-lib/aws-sam';
5 |
6 | export class TheLambdaPowerTunerStack extends cdk.Stack {
7 | constructor(scope: Construct, id: string, props?: cdk.StackProps) {
8 | super(scope, id, props);
9 |
10 | // Custom parameters (optional)
11 | // let powerValues = '128,256,512,1024,1536,3008';
12 | // let lambdaResource = "*";
13 | // let visualizationURL: https://lambda-power-tuning.show/;
14 | // let totalExecutionTimeout: 300;
15 | // let permissionsBoundary: ARN;
16 | // let payloadS3Bucket: my-bucket;
17 | // let payloadS3Key: my-key.json;
18 | // let stateMachineNamePrefix: my-custom-name-prefix;
19 |
20 |
21 | // Deploy the aws-lambda-powertuning application from the Serverless Application Repository
22 | // https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:451282441545:applications~aws-lambda-power-tuning
23 | new sam.CfnApplication(this, 'powerTuner', {
24 | location: {
25 | applicationId: 'arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning',
26 | semanticVersion: '4.3.6'
27 | },
28 | parameters: {
29 | //"lambdaResource": lambdaResource,
30 | //"PowerValues": powerValues,
31 | //"visualizationURL": visualizationURL,
32 | //"totalExecutionTimeout": totalExecutionTimeout,
33 | //"permissionsBoundary": permissionsBoundary,
34 | //"payloadS3Bucket": payloadS3Bucket,
35 | //"payloadS3Key": payloadS3Key,
36 | //"stateMachineNamePrefix": stateMachineNamePrefix
37 | }
38 | })
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/cdk/typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "the-lambda-power-tuner",
3 | "version": "0.1.0",
4 | "bin": {
5 | "the-lambda-power-tuner": "bin/the-lambda-power-tuner.js"
6 | },
7 | "scripts": {
8 | "build": "tsc",
9 | "watch": "tsc -w",
10 | "test": "jest",
11 | "cdk": "cdk"
12 | },
13 | "devDependencies": {
14 | "@types/jest": "^29.5.11",
15 | "@types/node": "20.10.4",
16 | "aws-cdk": "2.117.0",
17 | "jest": "^29.7.0",
18 | "ts-jest": "^29.1.1",
19 | "ts-node": "^10.9.2",
20 | "typescript": "~5.3.3"
21 | },
22 | "dependencies": {
23 | "aws-cdk-lib": "^2.189.1",
24 | "constructs": "^10.0.0",
25 | "source-map-support": "^0.5.21"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/cdk/typescript/test/the-lambda-power-tuner.test.ts:
--------------------------------------------------------------------------------
1 | import { Template } from 'aws-cdk-lib/assertions';
2 | import * as cdk from 'aws-cdk-lib';
3 | import * as TheLambdaPowerTuner from '../lib/the-lambda-power-tuner-stack';
4 |
5 | test('SAR Application Created', () => {
6 | const app = new cdk.App();
7 | // WHEN
8 | const stack = new TheLambdaPowerTuner.TheLambdaPowerTunerStack(app, 'MyTestStack');
9 | // THEN
10 |
11 | Template.fromStack(stack).hasResourceProperties('AWS::Serverless::Application', {
12 | "Location":{
13 | "ApplicationId": "arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning"
14 | }
15 | });
16 |
17 | });
--------------------------------------------------------------------------------
/cdk/typescript/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018",
4 | "module": "commonjs",
5 | "lib": ["es2018"],
6 | "declaration": true,
7 | "strict": true,
8 | "noImplicitAny": true,
9 | "strictNullChecks": true,
10 | "noImplicitThis": true,
11 | "alwaysStrict": true,
12 | "noUnusedLocals": false,
13 | "noUnusedParameters": false,
14 | "noImplicitReturns": true,
15 | "noFallthroughCasesInSwitch": false,
16 | "inlineSourceMap": true,
17 | "inlineSources": true,
18 | "experimentalDecorators": true,
19 | "strictPropertyInitialization": false,
20 | "typeRoots": ["./node_modules/@types"]
21 | },
22 | "exclude": ["cdk.out"]
23 | }
24 |
--------------------------------------------------------------------------------
/imgs/state-machine-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexcasalboni/aws-lambda-power-tuning/3ee22f3c511ec7c63250e82b27c7da521c80f24a/imgs/state-machine-screenshot.png
--------------------------------------------------------------------------------
/imgs/step-aborted-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexcasalboni/aws-lambda-power-tuning/3ee22f3c511ec7c63250e82b27c7da521c80f24a/imgs/step-aborted-screenshot.png
--------------------------------------------------------------------------------
/imgs/visualization.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexcasalboni/aws-lambda-power-tuning/3ee22f3c511ec7c63250e82b27c7da521c80f24a/imgs/visualization.png
--------------------------------------------------------------------------------
/imgs/visualization1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexcasalboni/aws-lambda-power-tuning/3ee22f3c511ec7c63250e82b27c7da521c80f24a/imgs/visualization1.jpg
--------------------------------------------------------------------------------
/imgs/visualization2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexcasalboni/aws-lambda-power-tuning/3ee22f3c511ec7c63250e82b27c7da521c80f24a/imgs/visualization2.jpg
--------------------------------------------------------------------------------
/lambda/analyzer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const utils = require('./utils');
4 |
5 | const visualizationURL = process.env.visualizationURL;
6 |
7 | const defaultStrategy = 'cost';
8 | const defaultBalancedWeight = 0.5;
9 | const optimizationStrategies = {
10 | cost: () => findCheapest,
11 | speed: () => findFastest,
12 | balanced: () => findBalanced,
13 | };
14 |
15 | /**
16 | * Receive average cost and decide which power config wins.
17 | */
18 | module.exports.handler = async(event, context) => {
19 |
20 | if (!Array.isArray(event.stats) || !event.stats.length) {
21 | throw new Error('Wrong input ' + JSON.stringify(event));
22 | }
23 |
24 | if (event.dryRun) {
25 | return console.log('[Dry-run] Skipping analysis');
26 | }
27 |
28 | const result = findOptimalConfiguration(event);
29 |
30 | if (event.includeOutputResults) {
31 | // add stats to final result
32 | result.stats = event.stats.map(stat => ({
33 | value: stat.value,
34 | averagePrice: stat.averagePrice,
35 | averageDuration: stat.averageDuration,
36 | // totalCost is omitted here
37 | }));
38 | }
39 |
40 | return result;
41 | };
42 |
43 | const getStrategy = (event) => {
44 | // extract strategy name or fallback to default (cost)
45 | return event.strategy || defaultStrategy;
46 | };
47 |
48 | const getBalancedWeight = (event) => {
49 | // extract weight used by balanced strategy or fallback to default (0.5)
50 | let weight = event.balancedWeight;
51 | if (typeof weight === 'undefined') {
52 | weight = defaultBalancedWeight;
53 | }
54 | // weight must be between 0 and 1
55 | return Math.min(Math.max(weight, 0.0), 1.0);
56 | };
57 |
58 | const findOptimalConfiguration = (event) => {
59 | const stats = extractStatistics(event);
60 | const strategy = getStrategy(event);
61 | const balancedWeight = getBalancedWeight(event);
62 | const optimizationFunction = optimizationStrategies[strategy]();
63 | const optimal = optimizationFunction(stats, balancedWeight);
64 | const onlyColdStarts = event.onlyColdStarts;
65 | const num = event.num;
66 |
67 | // also compute total cost of optimization state machine & lambda
68 | optimal.stateMachine = {};
69 | optimal.stateMachine.executionCost = utils.stepFunctionsCost(event.stats.length, onlyColdStarts, num);
70 | optimal.stateMachine.lambdaCost = stats
71 | .map((p) => p.totalCost)
72 | .reduce((a, b) => a + b, 0);
73 | optimal.stateMachine.visualization = utils.buildVisualizationURL(stats, visualizationURL);
74 |
75 | // the total cost of the optimal branch execution is not needed
76 | delete optimal.totalCost;
77 |
78 | return optimal;
79 | };
80 |
81 |
82 | const extractStatistics = (event) => {
83 | // generate a list of objects with only the relevant data/stats
84 | return event.stats
85 | // handle empty results from executor
86 | .filter(stat => stat && stat.averageDuration)
87 | .map(stat => ({
88 | power: stat.value,
89 | cost: stat.averagePrice,
90 | duration: stat.averageDuration,
91 | totalCost: stat.totalCost,
92 | }));
93 | };
94 |
95 | const findCheapest = (stats) => {
96 | console.log('Finding cheapest');
97 |
98 | // sort by cost
99 | stats.sort((p1, p2) => {
100 | if (p1.cost === p2.cost) {
101 | // return fastest if same cost
102 | return p1.duration - p2.duration;
103 | }
104 | return p1.cost - p2.cost;
105 | });
106 |
107 | console.log('Stats: ', stats);
108 |
109 | // just return the first one
110 | return stats[0];
111 | };
112 |
113 | const findFastest = (stats) => {
114 | console.log('Finding fastest');
115 |
116 | // sort by duration/speed
117 | stats.sort((p1, p2) => {
118 | if (p1.duration === p2.duration) {
119 | // return cheapest if same speed
120 | return p1.cost - p2.cost;
121 | }
122 | return p1.duration - p2.duration;
123 | });
124 |
125 | console.log('Stats: ', stats);
126 |
127 | // just return the first one
128 | return stats[0];
129 | };
130 |
131 | const findBalanced = (stats, weight) => {
132 | // choose a balanced configuration, weight is a number between 0 and 1 that express trade-off
133 | // between cost and time (0 = min time, 1 = min cost)
134 | console.log('Finding balanced configuration with balancedWeight = ', weight);
135 |
136 |
137 | // compute max cost and max duration
138 | const maxCost = Math.max(...stats.map(x => x['cost']));
139 | const maxDuration = Math.max(...stats.map(x => x['duration']));
140 |
141 | // formula for balanced value of a configuration ( value is minimized )
142 | const getValue = x => weight * x['cost'] / maxCost + (1 - weight) * x['duration'] / maxDuration;
143 |
144 | // sort stats by value
145 | stats.sort((x, y) => getValue(x) - getValue(y));
146 |
147 | console.log('Stats: ', stats);
148 |
149 | // just return the first one
150 | return stats[0];
151 | };
152 |
--------------------------------------------------------------------------------
/lambda/cleaner.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { ResourceNotFoundException } = require('@aws-sdk/client-lambda');
4 | const utils = require('./utils');
5 |
6 | /**
7 | * Delete aliases and versions.
8 | */
9 | module.exports.handler = async(event, context) => {
10 |
11 | const {
12 | lambdaARN,
13 | powerValues,
14 | onlyColdStarts,
15 | num,
16 | } = extractDataFromInput(event);
17 |
18 | validateInput(lambdaARN, powerValues); // may throw
19 |
20 | // build list of aliases to clean up
21 | const aliases = buildAliasListForCleanup(lambdaARN, onlyColdStarts, powerValues, num);
22 |
23 | const ops = aliases.map(async(alias) => {
24 | await cleanup(lambdaARN, alias);
25 | });
26 |
27 | // run everything in parallel and wait until completed
28 | await Promise.all(ops);
29 |
30 | return 'OK';
31 | };
32 |
33 | const buildAliasListForCleanup = (lambdaARN, onlyColdStarts, powerValues, num) => {
34 | if (onlyColdStarts){
35 | return powerValues.map((powerValue) => {
36 | return utils.range(num).map((index) => {
37 | return utils.buildAliasString(`RAM${powerValue}`, onlyColdStarts, index);
38 | });
39 | }).flat();
40 | }
41 | return powerValues.map((powerValue) => utils.buildAliasString(`RAM${powerValue}`));
42 | };
43 |
44 | const extractDataFromInput = (event) => {
45 | return {
46 | lambdaARN: event.lambdaARN,
47 | powerValues: event.lambdaConfigurations.powerValues,
48 | onlyColdStarts: event.onlyColdStarts,
49 | num: parseInt(event.num, 10), // parse as we do in the initializer
50 | };
51 | };
52 |
53 | const validateInput = (lambdaARN, powerValues) => {
54 | if (!lambdaARN) {
55 | throw new Error('Missing or empty lambdaARN');
56 | }
57 | if (!powerValues || !powerValues.length) {
58 | throw new Error('Missing or empty powerValues values');
59 | }
60 | };
61 |
62 | const cleanup = async(lambdaARN, alias) => {
63 | try {
64 | // check if it exists and fetch version ID
65 | const {FunctionVersion} = await utils.getLambdaAlias(lambdaARN, alias);
66 | // delete both alias and version (could be done in parallel!)
67 | await utils.deleteLambdaAlias(lambdaARN, alias);
68 | await utils.deleteLambdaVersion(lambdaARN, FunctionVersion);
69 | } catch (error) {
70 | if (error instanceof ResourceNotFoundException) {
71 | console.error('OK, even if version/alias was not found');
72 | console.error(error);
73 | } else {
74 | console.error(error);
75 | throw error;
76 | }
77 | }
78 | };
79 |
--------------------------------------------------------------------------------
/lambda/executor.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const utils = require('./utils');
4 |
5 | const minRAM = parseInt(process.env.minRAM, 10);
6 |
7 | /**
8 | * Execute the given function N times in series or in parallel.
9 | * Then compute execution statistics (average cost and duration).
10 | */
11 | module.exports.handler = async(event, context) => {
12 | // read input from event
13 | let {
14 | lambdaARN,
15 | value,
16 | num,
17 | enableParallel,
18 | payload,
19 | dryRun,
20 | preProcessorARN,
21 | postProcessorARN,
22 | discardTopBottom,
23 | onlyColdStarts,
24 | sleepBetweenRunsMs,
25 | disablePayloadLogs,
26 | allowedExceptions,
27 | } = await extractDataFromInput(event);
28 |
29 | validateInput(lambdaARN, value, num); // may throw
30 |
31 | // force only 1 execution if dryRun
32 | if (dryRun) {
33 | console.log('[Dry-run] forcing num=1');
34 | num = 1;
35 | }
36 |
37 | const lambdaAlias = 'RAM' + value;
38 | let results;
39 |
40 | // defaulting the index to 0 as the index is required for onlyColdStarts
41 | let aliasToInvoke = utils.buildAliasString(lambdaAlias, onlyColdStarts, 0);
42 | // We need the architecture, regardless of onlyColdStarts or not
43 | const {architecture, isPending} = await utils.getLambdaConfig(lambdaARN, aliasToInvoke);
44 |
45 | console.log(`Detected architecture type: ${architecture}, isPending: ${isPending}`);
46 |
47 | // pre-generate an array of N payloads
48 | const payloads = utils.generatePayloads(num, payload);
49 |
50 | const runInput = {
51 | num: num,
52 | lambdaARN: lambdaARN,
53 | lambdaAlias: lambdaAlias,
54 | payloads: payloads,
55 | preARN: preProcessorARN,
56 | postARN: postProcessorARN,
57 | onlyColdStarts: onlyColdStarts,
58 | sleepBetweenRunsMs: sleepBetweenRunsMs,
59 | disablePayloadLogs: disablePayloadLogs,
60 | allowedExceptions: allowedExceptions,
61 | };
62 |
63 | // wait if the function/alias state is Pending
64 | // in the case of onlyColdStarts, we will verify each alias in the runInParallel or runInSeries
65 | if (isPending && !onlyColdStarts) {
66 | await utils.waitForAliasActive(lambdaARN, lambdaAlias);
67 | console.log('Alias active');
68 | }
69 |
70 | if (enableParallel) {
71 | results = await runInParallel(runInput);
72 | } else {
73 | results = await runInSeries(runInput);
74 | }
75 |
76 | // get base cost for Lambda
77 | const baseCost = utils.lambdaBaseCost(utils.regionFromARN(lambdaARN), architecture);
78 |
79 | return computeStatistics(baseCost, results, value, discardTopBottom);
80 | };
81 |
82 | const validateInput = (lambdaARN, value, num) => {
83 | if (!lambdaARN) {
84 | throw new Error('Missing or empty lambdaARN');
85 | }
86 | if (!value || isNaN(value)) {
87 | throw new Error('Invalid value: ' + value);
88 | }
89 | if (!num || isNaN(num)) {
90 | throw new Error('Invalid num: ' + num);
91 | }
92 | };
93 |
94 | const extractPayloadValue = async(input) => {
95 | if (input.payloadS3) {
96 | return await utils.fetchPayloadFromS3(input.payloadS3); // might throw if access denied or 404
97 | } else if (input.payload) {
98 | return input.payload;
99 | }
100 | return null;
101 | };
102 |
103 |
104 | const extractDiscardTopBottomValue = (event) => {
105 | // extract discardTopBottom used to trim values from average duration
106 | let discardTopBottom = event.discardTopBottom;
107 | if (typeof discardTopBottom === 'undefined') {
108 | // default value for discardTopBottom
109 | discardTopBottom = 0.2;
110 | }
111 | // In case of onlyColdStarts, we only have 1 invocation per alias, therefore we shouldn't discard any execution
112 | if (event.onlyColdStarts){
113 | discardTopBottom = 0;
114 | console.log('Setting discardTopBottom to 0, every invocation should be accounted when onlyColdStarts');
115 | }
116 | // discardTopBottom must be between 0 and 0.4
117 | return Math.min(Math.max(discardTopBottom, 0.0), 0.4);
118 | };
119 |
120 | const extractSleepTime = (event) => {
121 | let sleepBetweenRunsMs = event.sleepBetweenRunsMs;
122 | if (isNaN(sleepBetweenRunsMs)) {
123 | sleepBetweenRunsMs = 0;
124 | } else {
125 | sleepBetweenRunsMs = parseInt(sleepBetweenRunsMs, 10);
126 | }
127 | return sleepBetweenRunsMs;
128 | };
129 |
130 | const extractDataFromInput = async(event) => {
131 | const input = event.input; // original state machine input
132 | const payload = await extractPayloadValue(input);
133 | const discardTopBottom = extractDiscardTopBottomValue(input);
134 | const sleepBetweenRunsMs = extractSleepTime(input);
135 | return {
136 | value: parseInt(event.value, 10),
137 | lambdaARN: input.lambdaARN,
138 | num: parseInt(input.num, 10),
139 | enableParallel: !!input.parallelInvocation,
140 | payload: payload,
141 | dryRun: input.dryRun === true,
142 | preProcessorARN: input.preProcessorARN,
143 | postProcessorARN: input.postProcessorARN,
144 | discardTopBottom: discardTopBottom,
145 | onlyColdStarts: !!input.onlyColdStarts,
146 | sleepBetweenRunsMs: sleepBetweenRunsMs,
147 | disablePayloadLogs: !!input.disablePayloadLogs,
148 | allowedExceptions: input.allowedExceptions
149 | };
150 | };
151 |
152 | const runInParallel = async({num, lambdaARN, lambdaAlias, payloads, preARN, postARN, disablePayloadLogs, onlyColdStarts, allowedExceptions = []}) => {
153 | const results = [];
154 | // run all invocations in parallel ...
155 | const invocations = utils.range(num).map(async(_, i) => {
156 | let aliasToInvoke = utils.buildAliasString(lambdaAlias, onlyColdStarts, i);
157 | if (onlyColdStarts){
158 | await utils.waitForAliasActive(lambdaARN, aliasToInvoke);
159 | console.log(`${aliasToInvoke} is active`);
160 | }
161 | const {invocationResults, actualPayload} = await utils.invokeLambdaWithProcessors(lambdaARN, aliasToInvoke, payloads[i], preARN, postARN, disablePayloadLogs);
162 |
163 | // invocation errors return 200 and contain FunctionError and Payload
164 | if (invocationResults.FunctionError) {
165 | const parsedResults = JSON.parse(Buffer.from(invocationResults.Payload));
166 | if (allowedExceptions.includes(parsedResults.errorType)) {
167 | // do nothing
168 | console.log(`Error ${parsedResults.errorType} is in the allowedExceptions list: ${allowedExceptions}`);
169 | } else {
170 | // throw error
171 | let errorMessage = 'Invocation error (running in parallel)';
172 | utils.handleLambdaInvocationError(errorMessage, invocationResults, actualPayload, disablePayloadLogs);
173 | }
174 | }
175 | results.push(invocationResults);
176 | });
177 | // ... and wait for results
178 | await Promise.all(invocations);
179 | return results;
180 | };
181 |
182 | const runInSeries = async({num, lambdaARN, lambdaAlias, payloads, preARN, postARN, sleepBetweenRunsMs, disablePayloadLogs, onlyColdStarts, allowedExceptions = []}) => {
183 | const results = [];
184 | for (let i = 0; i < num; i++) {
185 | let aliasToInvoke = utils.buildAliasString(lambdaAlias, onlyColdStarts, i);
186 | // run invocations in series
187 | if (onlyColdStarts){
188 | await utils.waitForAliasActive(lambdaARN, aliasToInvoke);
189 | console.log(`${aliasToInvoke} is active`);
190 | }
191 | const {invocationResults, actualPayload} = await utils.invokeLambdaWithProcessors(lambdaARN, aliasToInvoke, payloads[i], preARN, postARN, disablePayloadLogs, allowedExceptions);
192 |
193 | // invocation errors return 200 and contain FunctionError and Payload
194 | if (invocationResults.FunctionError) {
195 | const parsedResults = JSON.parse(Buffer.from(invocationResults.Payload));
196 | if (allowedExceptions.includes(parsedResults.errorType)) {
197 | // do nothing
198 | console.log(`Error ${parsedResults.errorType} is in the allowedExceptions list: ${allowedExceptions}`);
199 | } else {
200 | // throw error
201 | let errorMessage = 'Invocation error (running in series)';
202 | utils.handleLambdaInvocationError(errorMessage, invocationResults, actualPayload, disablePayloadLogs);
203 | }
204 | }
205 |
206 | if (sleepBetweenRunsMs > 0) {
207 | await utils.sleep(sleepBetweenRunsMs);
208 | }
209 | results.push(invocationResults);
210 | }
211 | return results;
212 | };
213 |
214 | const computeStatistics = (baseCost, results, value, discardTopBottom) => {
215 |
216 | // use results (which include logs) to compute average duration ...
217 | const totalDurations = utils.parseLogAndExtractDurations(results);
218 | const averageDuration = utils.computeAverageDuration(totalDurations, discardTopBottom);
219 | console.log('Average duration: ', averageDuration);
220 |
221 | // ... and overall cost statistics
222 | const billedDurations = utils.parseLogAndExtractBilledDurations(results);
223 | const averageBilledDuration = utils.computeAverageDuration(billedDurations, discardTopBottom);
224 | console.log('Average Billed duration: ', averageBilledDuration);
225 | const averagePrice = utils.computePrice(baseCost, minRAM, value, averageBilledDuration);
226 | // .. and total cost (exact $)
227 | const totalCost = utils.computeTotalCost(baseCost, minRAM, value, billedDurations);
228 |
229 | const stats = {
230 | averagePrice,
231 | averageDuration,
232 | totalCost,
233 | value,
234 | };
235 |
236 | console.log('Stats: ', stats);
237 | return stats;
238 | };
239 |
--------------------------------------------------------------------------------
/lambda/initializer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const utils = require('./utils');
4 | const defaultPowerValues = process.env.defaultPowerValues.split(',');
5 |
6 | /**
7 | * Initialize versions & aliases so we can execute everything in parallel.
8 | */
9 | module.exports.handler = async(event, context) => {
10 |
11 | const {
12 | lambdaARN,
13 | num,
14 | powerValues,
15 | onlyColdStarts,
16 | } = extractDataFromInput(event);
17 |
18 | validateInput(lambdaARN, num); // may throw
19 |
20 | // fetch initial $LATEST value so we can reset it later
21 | const {power, description} = await utils.getLambdaPower(lambdaARN);
22 | console.log(power, description);
23 |
24 | let initConfigurations = [];
25 |
26 | // reminder: configuration updates must run sequentially
27 | // (otherwise you get a ResourceConflictException)
28 | for (let powerValue of powerValues){
29 | const baseAlias = 'RAM' + powerValue;
30 | if (!onlyColdStarts){
31 | initConfigurations.push({powerValue: powerValue, alias: baseAlias});
32 | } else {
33 | for (let n of utils.range(num)){
34 | let alias = utils.buildAliasString(baseAlias, onlyColdStarts, n);
35 | // here we inject a custom description to force the creation of a new version
36 | // even if the power is the same, which will force a cold start
37 | initConfigurations.push({powerValue: powerValue, alias: alias, description: `${description} - ${alias}`});
38 | }
39 | }
40 | }
41 | // Publish another version to revert the Lambda Function to its original configuration
42 | initConfigurations.push({powerValue: power, description: description});
43 |
44 | return {
45 | initConfigurations: initConfigurations,
46 | iterator: {
47 | index: 0,
48 | count: initConfigurations.length,
49 | continue: true,
50 | },
51 | powerValues: powerValues,
52 | };
53 | };
54 |
55 |
56 | const extractDataFromInput = (event) => {
57 | return {
58 | lambdaARN: event.lambdaARN,
59 | num: parseInt(event.num, 10),
60 | powerValues: extractPowerValues(event),
61 | onlyColdStarts: !!event.onlyColdStarts,
62 | };
63 | };
64 |
65 | const extractPowerValues = (event) => {
66 | var powerValues = event.powerValues; // could be undefined
67 |
68 | // auto-generate all possible values if ALL
69 | if (powerValues === 'ALL') {
70 | powerValues = utils.allPowerValues();
71 | }
72 |
73 | // use default list of values (defined at deploy-time) if not provided
74 | if (!powerValues || powerValues.length === 0) {
75 | powerValues = defaultPowerValues;
76 | }
77 |
78 | return powerValues;
79 | };
80 |
81 | const validateInput = (lambdaARN, num) => {
82 | if (!lambdaARN) {
83 | throw new Error('Missing or empty lambdaARN');
84 | }
85 | if (!num || num < 5) {
86 | throw new Error('Missing num or num below 5');
87 | }
88 | };
89 |
--------------------------------------------------------------------------------
/lambda/optimizer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const utils = require('./utils');
4 |
5 | /**
6 | * Optionally auto-optimize based on the optimal power value.
7 | */
8 | module.exports.handler = async(event, context) => {
9 |
10 | const {lambdaARN, analysis, autoOptimize, autoOptimizeAlias, dryRun} = event;
11 |
12 | const optimalValue = (analysis || {}).power;
13 |
14 | if (dryRun) {
15 | return console.log('[Dry-run] Not optimizing');
16 | }
17 |
18 | validateInput(lambdaARN, optimalValue); // may throw
19 |
20 | if (!autoOptimize) {
21 | return console.log('Not optimizing');
22 | }
23 |
24 | if (!autoOptimizeAlias) {
25 | // only update $LATEST power
26 | await utils.setLambdaPower(lambdaARN, optimalValue);
27 | } else {
28 | // create/update alias
29 | await utils.createPowerConfiguration(lambdaARN, optimalValue, autoOptimizeAlias);
30 | }
31 |
32 | return 'OK';
33 | };
34 |
35 | const validateInput = (lambdaARN, optimalValue) => {
36 | if (!lambdaARN) {
37 | throw new Error('Missing or empty lambdaARN');
38 | }
39 | if (!optimalValue) {
40 | throw new Error('Missing or empty optimal value');
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/lambda/publisher.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const utils = require('./utils');
4 |
5 |
6 | module.exports.handler = async(event, context) => {
7 | const {lambdaConfigurations, currConfig, lambdaARN} = validateInputs(event);
8 | const currentIterator = lambdaConfigurations.iterator;
9 | // publish version & assign alias (if present)
10 | await utils.createPowerConfiguration(lambdaARN, currConfig.powerValue, currConfig.alias, currConfig.description);
11 |
12 | const result = {
13 | powerValues: lambdaConfigurations.powerValues,
14 | initConfigurations: lambdaConfigurations.initConfigurations,
15 | iterator: {
16 | index: (currentIterator.index + 1),
17 | count: currentIterator.count,
18 | continue: ((currentIterator.index + 1) < currentIterator.count),
19 | },
20 | };
21 |
22 | if (!result.iterator.continue) {
23 | // clean the list of configuration if we're done iterating
24 | delete result.initConfigurations;
25 | }
26 |
27 | return result;
28 | };
29 | function validateInputs(event) {
30 | if (!event.lambdaARN) {
31 | throw new Error('Missing or empty lambdaARN');
32 | }
33 | const lambdaARN = event.lambdaARN;
34 | if (!(event.lambdaConfigurations && event.lambdaConfigurations.iterator && event.lambdaConfigurations.initConfigurations)){
35 | throw new Error('Invalid iterator for initialization');
36 | }
37 | const iterator = event.lambdaConfigurations.iterator;
38 | if (!(iterator.index >= 0 && iterator.index < iterator.count)){
39 | throw new Error(`Invalid iterator index: ${iterator.index}`);
40 | }
41 | const lambdaConfigurations = event.lambdaConfigurations;
42 | const currIdx = iterator.index;
43 | const currConfig = lambdaConfigurations.initConfigurations[currIdx];
44 | if (!(currConfig && currConfig.powerValue)){
45 | throw new Error(`Invalid init configuration: ${JSON.stringify(currConfig)}`);
46 | }
47 | return {lambdaConfigurations, currConfig, lambdaARN};
48 | }
49 |
--------------------------------------------------------------------------------
/layer-sdk/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws-sdk-layer",
3 | "version": "1.0.0",
4 | "description": "A layer used by Lambda functions in this project.",
5 | "author": "Alex Casalboni ",
6 | "licence": "MIT-0",
7 | "scripts": {},
8 | "private": true,
9 | "dependencies": {
10 | "@aws-sdk/client-lambda": "^3.651.1",
11 | "@aws-sdk/client-s3": "^3.651.1"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws-lambda-power-tuning",
3 | "version": "4.3.6",
4 | "description": "Step Functions state machine generator for AWS Lambda Power Tuning",
5 | "author": "Alex Casalboni ",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/alexcasalboni/aws-lambda-power-tuning"
9 | },
10 | "bugs": {
11 | "url": "https://github.com/alexcasalboni/aws-lambda-power-tuning/issues"
12 | },
13 | "keywords": [
14 | "aws",
15 | "aws lambda",
16 | "serverless",
17 | "aws step functions",
18 | "cost",
19 | "performance",
20 | "visualization"
21 | ],
22 | "licence": "Apache",
23 | "scripts": {
24 | "lint": "eslint --ignore-path .gitignore .",
25 | "test": "mocha",
26 | "coverage": "c8 --reporter=lcov --reporter=cobertura --reporter=text-summary mocha"
27 | },
28 | "devDependencies": {
29 | "@aws-sdk/client-lambda": "^3.651.1",
30 | "@aws-sdk/client-s3": "^3.651.1",
31 | "ansi-regex": ">=5.0.1",
32 | "aws-sdk-client-mock": "^3.0.0",
33 | "c8": "^7.13.0",
34 | "eslint-config-strongloop": "^2.1.0",
35 | "eslint": "^8.35.0",
36 | "expect.js": "^0.3.1",
37 | "json-schema": ">=0.4.0",
38 | "mocha": "^11.1.0",
39 | "path-parse": ">=1.0.7",
40 | "sinon": "^9.0.2"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/scripts/deploy-sar-app.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # config
3 | BUCKET_NAME=your-sam-templates-bucket
4 | STACK_NAME=lambda-power-tuning-app
5 |
6 | # package
7 | sam package --s3-bucket $BUCKET_NAME --template-file scripts/deploy-sar-app.yml --output-template-file packaged-sar.yml
8 |
9 | # deploy
10 | sam deploy --template-file packaged-sar.yml --stack-name $STACK_NAME --capabilities CAPABILITY_AUTO_EXPAND CAPABILITY_IAM
11 |
--------------------------------------------------------------------------------
/scripts/deploy-sar-app.yml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: '2010-09-09'
2 | Transform: AWS::Serverless-2016-10-31
3 |
4 | Resources:
5 |
6 | PowerTuningApp:
7 | Type: AWS::Serverless::Application
8 | Properties:
9 | Location:
10 | ApplicationId: arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning
11 | SemanticVersion: 4.3.6
12 |
13 | # Parameters:
14 | # PowerValues: 128,256,512,1024,1536,3008 # default RAM values
15 | # lambdaResource: '*' # IAM permission (ARN or prefix)
16 | # visualizationURL: https://lambda-power-tuning.show/ # you can use your own visualization URL
17 | # totalExecutionTimeout: 300 # execution timeout for individual steps
18 | # permissionsBoundary: ARN
19 | # payloadS3Bucket: my-bucket
20 | # payloadS3Key: my-key.json
21 | # stateMachineNamePrefix: my-custom-name-prefix
22 |
23 | Outputs:
24 | PowerTuningStateMachine:
25 | Value: !GetAtt PowerTuningApp.Outputs.StateMachineARN
26 |
--------------------------------------------------------------------------------
/scripts/execute.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # config
3 |
4 | set -euo pipefail
5 |
6 | if [[ $# -gt 1 ]]
7 | then
8 | echo "Incorrect Usage: $0 [input json file path]"
9 | exit 1
10 | fi
11 |
12 | STACK_NAME=lambda_power_tuning
13 | INPUT=$(cat "${1:-sample-execution-input.json}") # or use a static string
14 |
15 | # retrieve state machine ARN
16 | STATE_MACHINE_ARN=$(aws stepfunctions list-state-machines --query "stateMachines[?contains(name,\`${STACK_NAME}\`)]|[0].stateMachineArn" --output text | cat)
17 |
18 | # start execution
19 | EXECUTION_ARN=$(aws stepfunctions start-execution --state-machine-arn $STATE_MACHINE_ARN --input "$INPUT" --query 'executionArn' --output text)
20 |
21 | echo -n "Execution started..."
22 |
23 | # poll execution status until completed
24 | while true;
25 | do
26 | # retrieve execution status
27 | STATUS=$(aws stepfunctions describe-execution --execution-arn $EXECUTION_ARN --query 'status' --output text)
28 |
29 | if test "$STATUS" == "RUNNING"; then
30 | # keep looping and wait if still running
31 | echo -n "."
32 | sleep 1
33 | elif test "$STATUS" == "FAILED"; then
34 | # exit if failed
35 | echo -e "\nThe execution failed, you can check the execution logs with the following script:\naws stepfunctions get-execution-history --execution-arn $EXECUTION_ARN"
36 | break
37 | else
38 | # print execution output if succeeded
39 | echo $STATUS
40 | echo "Execution output: "
41 | # retrieve output
42 | aws stepfunctions describe-execution --execution-arn $EXECUTION_ARN --query 'output' --output text | cat
43 | break
44 | fi
45 | done
46 |
--------------------------------------------------------------------------------
/scripts/publish.sh:
--------------------------------------------------------------------------------
1 | # note: this only works in the main AWS account where Lambda Power Tuning is deployed (SAR)
2 |
3 | S3_BUCKET=alex-casalboni-apps
4 |
5 | sam package --template-file template.yml --output-template-file packaged.yml --s3-bucket $S3_BUCKET --region us-east-1
6 |
7 | sam publish --template packaged.yml --region us-east-1
--------------------------------------------------------------------------------
/scripts/sample-execution-input.json:
--------------------------------------------------------------------------------
1 | {
2 | "lambdaARN": "arn:aws:lambda:XXX:YYY:function:ZZZ",
3 | "powerValues": [128, 256, 512, 3008],
4 | "num": 5,
5 | "payload": {},
6 | "parallelInvocation": true,
7 | "strategy": "cost"
8 | }
9 |
--------------------------------------------------------------------------------
/statemachine/statemachine.asl.json:
--------------------------------------------------------------------------------
1 | {
2 | "Comment": "AWS Lambda Power Tuning state machine",
3 | "StartAt": "Initializer",
4 | "States": {
5 | "Initializer": {
6 | "Type": "Task",
7 | "Resource": "${initializerArn}",
8 | "Next": "Publisher",
9 | "ResultPath": "$.lambdaConfigurations",
10 | "TimeoutSeconds": ${totalExecutionTimeout}
11 | },
12 | "Publisher": {
13 | "Type": "Task",
14 | "Resource": "${publisherArn}",
15 | "Next": "IsCountReached",
16 | "ResultPath": "$.lambdaConfigurations",
17 | "TimeoutSeconds": ${totalExecutionTimeout},
18 | "Catch": [{
19 | "ErrorEquals": [ "States.ALL" ],
20 | "Next": "CleanUpOnError",
21 | "ResultPath": "$.error"
22 | }]
23 | },
24 | "IsCountReached": {
25 | "Type": "Choice",
26 | "Choices": [
27 | {
28 | "Variable": "$.lambdaConfigurations.iterator.continue",
29 | "BooleanEquals": true,
30 | "Next": "Publisher"
31 | }
32 | ],
33 | "Default": "Branching"
34 | },
35 | "Branching": {
36 | "Type": "Map",
37 | "Next": "Cleaner",
38 | "ItemsPath": "$.lambdaConfigurations.powerValues",
39 | "ResultPath": "$.stats",
40 | "ItemSelector": {
41 | "input.$": "$",
42 | "value.$": "$$.Map.Item.Value"
43 | },
44 | "MaxConcurrency": 0,
45 | "Catch": [
46 | {
47 | "ErrorEquals": ["States.ALL"],
48 | "Next": "CleanUpOnError",
49 | "ResultPath": "$.error"
50 | }
51 | ],
52 | "Iterator": {
53 | "StartAt": "Executor",
54 | "States": {
55 | "Executor": {
56 | "Type": "Task",
57 | "Resource": "${executorArn}",
58 | "End": true,
59 | "TimeoutSeconds": ${totalExecutionTimeout},
60 | "Retry": [{
61 | "ErrorEquals": [
62 | "States.ALL"
63 | ],
64 | "IntervalSeconds": 3,
65 | "MaxAttempts": 2
66 | }]
67 | }
68 | }
69 | }
70 | },
71 | "Cleaner": {
72 | "Type": "Task",
73 | "Next": "Analyzer",
74 | "ResultPath": null,
75 | "TimeoutSeconds": ${totalExecutionTimeout},
76 | "Resource": "${cleanerArn}"
77 | },
78 | "Analyzer": {
79 | "Type": "Task",
80 | "Resource": "${analyzerArn}",
81 | "ResultPath": "$.analysis",
82 | "TimeoutSeconds": 10,
83 | "Next": "Optimizer"
84 | },
85 | "Optimizer": {
86 | "Type": "Task",
87 | "Resource": "${optimizerArn}",
88 | "ResultPath": null,
89 | "OutputPath": "$.analysis",
90 | "TimeoutSeconds": ${totalExecutionTimeout},
91 | "End": true
92 | },
93 | "CleanUpOnError": {
94 | "Type": "Task",
95 | "ResultPath": null,
96 | "OutputPath": null,
97 | "Resource": "${cleanerArn}",
98 | "TimeoutSeconds": ${totalExecutionTimeout},
99 | "End": true
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/template.yml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: '2010-09-09'
2 | Transform: AWS::Serverless-2016-10-31
3 | Description: AWS Lambda Power Tuning resources.
4 |
5 | Metadata:
6 | AWS::ServerlessRepo::Application:
7 | Name: aws-lambda-power-tuning
8 | Description: AWS Lambda Power Tuning is an open-source tool that can help you visualize and fine-tune the memory/power configuration of Lambda functions. It runs in your AWS account - powered by AWS Step Functions - and it supports multiple optimization strategies.
9 | Author: Alex Casalboni
10 | SpdxLicenseId: Apache-2.0
11 | LicenseUrl: LICENSE.txt
12 | ReadmeUrl: README-SAR.md
13 | Labels: ['lambda', 'power', 'state-machine', 'step-functions', 'optimization']
14 | HomePageUrl: https://github.com/alexcasalboni/aws-lambda-power-tuning
15 | SemanticVersion: 4.3.6
16 | SourceCodeUrl: https://github.com/alexcasalboni/aws-lambda-power-tuning
17 |
18 | Parameters:
19 | PowerValues:
20 | Type: List
21 | Default: 128,256,512,1024,1536,3008
22 | Description: Default RAM values, used only if not provided as execution input (comma-separated).
23 | visualizationURL:
24 | Type: String
25 | Default: https://lambda-power-tuning.show/
26 | Description: Stats visualization URL
27 | lambdaResource:
28 | Type: String
29 | Default: '*'
30 | Description: AWS Lambda resource (or prefix) to be used for IAM policies
31 | totalExecutionTimeout:
32 | Type: Number
33 | Default: 300
34 | MinValue: 10
35 | MaxValue: 900
36 | Description: Maximum invocation timeout (in seconds) for the Executor step, after which you get a States.Timeout error
37 | permissionsBoundary:
38 | Type: String
39 | Default: ''
40 | Description: The ARN of a permissions boundary to use for all functions' execution role.
41 | payloadS3Bucket:
42 | Type: String
43 | Default: ''
44 | Description: S3 bucket name used for large payloads (optional), included in IAM policy if not an empty string
45 | payloadS3Key:
46 | Type: String
47 | Default: '*'
48 | Description: S3 object key used for large payloads (optional), included in IAM policy if bucket is not an empty string (all objects are readable by default)
49 | layerSdkName:
50 | Type: String
51 | Default: ''
52 | Description: Name of the SDK layer, if customization is needed (optional)
53 | logGroupRetentionInDays:
54 | Type: Number
55 | Default: 7
56 | Description: The number of days to retain the log events in the Lambda log groups.
57 | securityGroupIds:
58 | Type: CommaDelimitedList
59 | Default: ''
60 | Description: List of Security Groups to use in every Lambda function's VPC Configuration (optional).
61 | subnetIds:
62 | Type: CommaDelimitedList
63 | Default: ''
64 | Description: List of Subnets to use in every Lambda function's VPC Configuration (optional).
65 | stateMachineNamePrefix:
66 | Type: String
67 | MaxLength: 43
68 | AllowedPattern: ^[a-zA-Z0-9\-_]*$
69 | ConstraintDescription: Prefix must conform to StateMachineName requirements.
70 | Default: 'powerTuningStateMachine'
71 | Description: Prefix to the name of the StateMachine. The StackId will be appended to this value (optional).
72 |
73 | Conditions:
74 | UsePermissionsBoundary: !Not [!Equals [!Ref permissionsBoundary, '']]
75 | S3BucketProvided: !Not [!Equals [!Ref payloadS3Bucket, '']]
76 | UseLayerSdkName: !Not [!Equals [!Ref layerSdkName, '']]
77 | UseSecurityGroupIds: !Not [!Equals [!Join ['', !Ref securityGroupIds], '']]
78 | UseSubnetIds: !Not [!Equals [!Join ['', !Ref subnetIds], '']]
79 | UseVPCConfig: !Or [Condition: UseSecurityGroupIds, Condition: UseSubnetIds]
80 |
81 | Globals:
82 | Function:
83 | Runtime: nodejs20.x
84 | MemorySize: 128
85 | Timeout: !Ref totalExecutionTimeout
86 | PermissionsBoundary: !If [UsePermissionsBoundary, !Ref permissionsBoundary, !Ref AWS::NoValue]
87 | VpcConfig: !If [UseVPCConfig, {
88 | SecurityGroupIds: !If [UseSecurityGroupIds, !Ref securityGroupIds, !Ref AWS::NoValue],
89 | SubnetIds: !If [UseSubnetIds, !Ref subnetIds, !Ref AWS::NoValue]
90 | }, !Ref AWS::NoValue]
91 | Environment:
92 | Variables:
93 | defaultPowerValues: !Join [ ",", !Ref PowerValues ]
94 | minRAM: '128'
95 | baseCosts: '{"x86_64": {"ap-east-1":2.9e-9,"af-south-1":2.8e-9,"me-south-1":2.6e-9,"eu-south-1":2.4e-9,"ap-northeast-3":2.7e-9,"cn-north-1":0.0000000142,"cn-northwest-1":0.0000000142,"default":2.1e-9}, "arm64": {"default":1.7e-9}}'
96 | sfCosts: '{"default": 0.000025,"us-gov-west-1": 0.00003,"ap-northeast-2": 0.0000271,"eu-south-1": 0.00002625,"af-south-1": 0.00002975,"us-west-1": 0.0000279,"eu-west-3": 0.0000297,"ap-east-1": 0.0000275,"me-south-1": 0.0000275,"ap-south-1": 0.0000285,"us-gov-east-1": 0.00003,"sa-east-1": 0.0000375,"cn-north-1":0.0001891,"cn-northwest-1":0.0001891}'
97 | visualizationURL: !Ref visualizationURL
98 |
99 | Resources:
100 |
101 | SDKlayer:
102 | Type: AWS::Serverless::LayerVersion
103 | Properties:
104 | LayerName: !If [UseLayerSdkName, !Ref layerSdkName, AWS-SDK-v3]
105 | Description: AWS SDK 3
106 | ContentUri: ./layer-sdk
107 | CompatibleRuntimes:
108 | - nodejs20.x
109 | LicenseInfo: 'Available under the MIT-0 license.'
110 | RetentionPolicy: Retain
111 | Metadata:
112 | BuildMethod: nodejs20.x
113 |
114 | initializerLogGroup:
115 | Type: AWS::Logs::LogGroup
116 | DependsOn: initializer
117 | Properties:
118 | LogGroupName: !Sub '/aws/lambda/${initializer}'
119 | RetentionInDays: !Ref logGroupRetentionInDays
120 |
121 | initializer:
122 | Type: AWS::Serverless::Function
123 | Properties:
124 | CodeUri: lambda
125 | Handler: initializer.handler
126 | Layers:
127 | - !Ref SDKlayer
128 | Policies:
129 | - AWSLambdaBasicExecutionRole # Only logs
130 | - Version: '2012-10-17' # allow Lambda actions
131 | Statement:
132 | - Effect: Allow
133 | Action:
134 | - lambda:GetFunctionConfiguration
135 | Resource: !Ref lambdaResource
136 | publisher:
137 | Type: AWS::Serverless::Function
138 | Properties:
139 | CodeUri: lambda
140 | Handler: publisher.handler
141 | Layers:
142 | - !Ref SDKlayer
143 | Policies:
144 | - AWSLambdaBasicExecutionRole # Only logs
145 | - Version: '2012-10-17' # allow Lambda actions
146 | Statement:
147 | - Effect: Allow
148 | Action:
149 | - lambda:GetAlias
150 | - lambda:GetFunctionConfiguration
151 | - lambda:PublishVersion
152 | - lambda:UpdateFunctionConfiguration
153 | - lambda:CreateAlias
154 | - lambda:UpdateAlias
155 | Resource: !Ref lambdaResource
156 |
157 | executorLogGroup:
158 | Type: AWS::Logs::LogGroup
159 | DependsOn: executor
160 | Properties:
161 | LogGroupName: !Sub '/aws/lambda/${executor}'
162 | RetentionInDays: !Ref logGroupRetentionInDays
163 |
164 | executor:
165 | Type: AWS::Serverless::Function
166 | Properties:
167 | CodeUri: lambda
168 | Handler: executor.handler
169 | Layers:
170 | - !Ref SDKlayer
171 | Policies:
172 | - AWSLambdaBasicExecutionRole # only logs
173 | - Version: '2012-10-17' # allow Lambda actions
174 | Statement:
175 | - Effect: Allow
176 | Action:
177 | - lambda:InvokeFunction
178 | - lambda:GetFunctionConfiguration
179 | Resource: !Ref lambdaResource
180 | - !If
181 | - S3BucketProvided # if S3 bucket is provided
182 | - !Ref payloadS3Policy # allow read-only access to S3
183 | - !Ref AWS::NoValue
184 |
185 | payloadS3Policy:
186 | Condition: S3BucketProvided
187 | Type: AWS::IAM::ManagedPolicy
188 | Properties:
189 | Description: Lambda Power Tunning - S3 Read Only for large payloads
190 | PolicyDocument:
191 | Version: '2012-10-17'
192 | Statement:
193 | - Effect: Allow
194 | Action:
195 | - s3:ListBucket # this is needed only to detect 404 errors correctly
196 | - s3:GetObject
197 | Resource:
198 | - !Sub arn:${AWS::Partition}:s3:::${payloadS3Bucket}
199 | - !Sub arn:${AWS::Partition}:s3:::${payloadS3Bucket}/${payloadS3Key} # payloadS3Key is * by default
200 |
201 | cleanerLogGroup:
202 | Type: AWS::Logs::LogGroup
203 | DependsOn: cleaner
204 | Properties:
205 | LogGroupName: !Sub '/aws/lambda/${cleaner}'
206 | RetentionInDays: !Ref logGroupRetentionInDays
207 |
208 | cleaner:
209 | Type: AWS::Serverless::Function
210 | Properties:
211 | CodeUri: lambda
212 | Handler: cleaner.handler
213 | Layers:
214 | - !Ref SDKlayer
215 | Policies:
216 | - AWSLambdaBasicExecutionRole # only logs
217 | - Version: '2012-10-17' # allow Lambda actions
218 | Statement:
219 | - Effect: Allow
220 | Action:
221 | - lambda:GetAlias
222 | - lambda:DeleteAlias
223 | - lambda:DeleteFunction # only by version/qualifier
224 | Resource: !Ref lambdaResource
225 |
226 | analyzerLogGroup:
227 | Type: AWS::Logs::LogGroup
228 | DependsOn: analyzer
229 | Properties:
230 | LogGroupName: !Sub '/aws/lambda/${analyzer}'
231 | RetentionInDays: !Ref logGroupRetentionInDays
232 |
233 | analyzer:
234 | Type: AWS::Serverless::Function
235 | Properties:
236 | CodeUri: lambda
237 | Handler: analyzer.handler
238 | Timeout: 10
239 | Policies:
240 | - AWSLambdaBasicExecutionRole # only logs
241 |
242 | optimizerLogGroup:
243 | Type: AWS::Logs::LogGroup
244 | DependsOn: optimizer
245 | Properties:
246 | LogGroupName: !Sub '/aws/lambda/${optimizer}'
247 | RetentionInDays: !Ref logGroupRetentionInDays
248 |
249 | optimizer:
250 | Type: AWS::Serverless::Function
251 | Properties:
252 | CodeUri: lambda
253 | Handler: optimizer.handler
254 | Layers:
255 | - !Ref SDKlayer
256 | Policies:
257 | - AWSLambdaBasicExecutionRole # only logs
258 | - Version: '2012-10-17' # allow Lambda actions
259 | Statement:
260 | - Effect: Allow
261 | Action:
262 | - lambda:GetAlias
263 | - lambda:PublishVersion
264 | - lambda:UpdateFunctionConfiguration
265 | - lambda:GetFunctionConfiguration
266 | - lambda:CreateAlias
267 | - lambda:UpdateAlias
268 | Resource: !Ref lambdaResource
269 |
270 | statemachineRole:
271 | Type: AWS::IAM::Role
272 | Properties:
273 | PermissionsBoundary: !If [UsePermissionsBoundary, !Ref permissionsBoundary, !Ref AWS::NoValue]
274 | ManagedPolicyArns:
275 | - arn:aws:iam::aws:policy/service-role/AWSLambdaRole
276 | AssumeRolePolicyDocument:
277 | Version: '2012-10-17'
278 | Statement:
279 | - Effect: Allow
280 | Principal:
281 | Service: !Sub states.${AWS::Region}.amazonaws.com
282 | Action: sts:AssumeRole
283 |
284 | powerTuningStateMachine:
285 | Type: AWS::Serverless::StateMachine
286 | Properties:
287 | Name:
288 | Fn::Join:
289 | - '-'
290 | - - !Ref stateMachineNamePrefix
291 | - !Select [2, !Split ['/', !Ref AWS::StackId]]
292 | Role: !GetAtt statemachineRole.Arn
293 | DefinitionUri: statemachine/statemachine.asl.json
294 | DefinitionSubstitutions:
295 | initializerArn: !GetAtt initializer.Arn
296 | publisherArn: !GetAtt publisher.Arn
297 | executorArn: !GetAtt executor.Arn
298 | cleanerArn: !GetAtt cleaner.Arn
299 | analyzerArn: !GetAtt analyzer.Arn
300 | optimizerArn: !GetAtt optimizer.Arn
301 | totalExecutionTimeout: !Ref totalExecutionTimeout
302 |
303 | Outputs:
304 | StateMachineARN:
305 | Value: !Ref powerTuningStateMachine
306 |
--------------------------------------------------------------------------------
/terraform/.terraform-version:
--------------------------------------------------------------------------------
1 | 0.13.3
2 |
--------------------------------------------------------------------------------
/terraform/Readme.md:
--------------------------------------------------------------------------------
1 | # Deploy with Terraform natively
2 |
3 | ## Overview
4 | This deployment option is intended for those who may not be in a position to use AWS Cloudformation, in cases where you do not have access or when CloudFormation is not an approved service within your company.
5 |
6 | The Terraform code is contained in the `terraform` directory of this project. All commands should be run from within this directory.
7 |
8 | ## Before you start
9 |
10 | Modify the `variables.tf` file with your target AWS Account and region.
11 | ```
12 | variable "account_id" {
13 | default = "123456789101"
14 | }
15 | variable "aws_region" {
16 | default = "eu-west-1"
17 | }
18 | ```
19 |
20 | ## Deploy the solution
21 | ```
22 | terraform init
23 | terraform plan
24 | terraform apply
25 | ```
26 |
27 | Once deployed, follow [these instructions](../README-EXECUTE.md) to run Lambda Power Tuning.
28 |
29 | ## Deploy to multiple accounts/regions
30 |
31 | If you're planning on deploying to multiple accounts or regions, it's recommended to adopt a folder strategy by either account or region. This will make sure you keep your statefile lightweight and plans/applies faster.
32 |
33 | ## Delete the solution
34 | Run the below command to remove all resources from your account:
35 | ```bash
36 | terraform destroy
37 | ```
38 | Enter 'yes' at the confirmation prompt.
39 |
40 | ## Versions tested
41 | - 0.13.3
42 | - 1.0.11
43 | - 1.7.3
44 |
45 | This should provide good coverage between those versions. If there's any problems, please raise an issue.
46 |
--------------------------------------------------------------------------------
/terraform/main.tf:
--------------------------------------------------------------------------------
1 | module "power_tuning" {
2 | source = "./module"
3 | account_id = var.account_id
4 | }
--------------------------------------------------------------------------------
/terraform/module/data.tf:
--------------------------------------------------------------------------------
1 | resource "null_resource" "build_layer" {
2 | provisioner "local-exec" {
3 | command = "${path.module}/scripts/build-layer.sh"
4 | interpreter = ["bash"]
5 | }
6 | triggers = {
7 | always_run = "${timestamp()}"
8 | }
9 | }
10 |
11 | data "archive_file" "layer" {
12 | type = "zip"
13 | source_dir = "../layer-sdk/src/"
14 | output_path = "../src/layer.zip"
15 |
16 | depends_on = [
17 | null_resource.build_layer
18 | ]
19 | }
20 |
21 | data "archive_file" "app" {
22 | type = "zip"
23 | output_path = "../src/app.zip"
24 | source_dir = "../lambda/"
25 |
26 | depends_on = [
27 | null_resource.build_layer
28 | ]
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/terraform/module/json_files/cleaner.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": "2012-10-17",
3 | "Statement": [
4 | {
5 | "Effect": "Allow",
6 | "Action": [
7 | "lambda:GetAlias",
8 | "lambda:DeleteAlias",
9 | "lambda:DeleteFunction"
10 | ],
11 | "Resource": "arn:aws:lambda:*:${account_id}:function:*"
12 | }
13 | ]
14 | }
--------------------------------------------------------------------------------
/terraform/module/json_files/executor.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": "2012-10-17",
3 | "Statement": [
4 | {
5 | "Effect": "Allow",
6 | "Action": [
7 | "lambda:InvokeFunction",
8 | "lambda:GetFunctionConfiguration"
9 | ],
10 | "Resource": "arn:aws:lambda:*:${account_id}:function:*"
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/terraform/module/json_files/initializer.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": "2012-10-17",
3 | "Statement": [
4 | {
5 | "Effect": "Allow",
6 | "Action": [
7 | "lambda:GetFunctionConfiguration"
8 | ],
9 | "Resource": "arn:aws:lambda:*:${account_id}:function:*"
10 | }
11 | ]
12 | }
--------------------------------------------------------------------------------
/terraform/module/json_files/lambda.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": "2012-10-17",
3 | "Statement": [
4 | {
5 | "Action": "sts:AssumeRole",
6 | "Effect": "Allow",
7 | "Sid": "",
8 | "Principal": {
9 | "Service": "lambda.amazonaws.com"
10 | }
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/terraform/module/json_files/optimizer.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": "2012-10-17",
3 | "Statement": [
4 | {
5 | "Effect": "Allow",
6 | "Action": [
7 | "lambda:GetAlias",
8 | "lambda:PublishVersion",
9 | "lambda:UpdateFunctionConfiguration",
10 | "lambda:GetFunctionConfiguration",
11 | "lambda:CreateAlias",
12 | "lambda:UpdateAlias"
13 | ],
14 | "Resource": "arn:aws:lambda:*:${account_id}:function:*"
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/terraform/module/json_files/publisher.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": "2012-10-17",
3 | "Statement": [
4 | {
5 | "Effect": "Allow",
6 | "Action": [
7 | "lambda:GetAlias",
8 | "lambda:GetFunctionConfiguration",
9 | "lambda:PublishVersion",
10 | "lambda:UpdateFunctionConfiguration",
11 | "lambda:CreateAlias",
12 | "lambda:UpdateAlias"
13 | ],
14 | "Resource": "arn:aws:lambda:*:${account_id}:function:*"
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/terraform/module/json_files/sfn.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": "2012-10-17",
3 | "Statement": [
4 | {
5 | "Action": "sts:AssumeRole",
6 | "Effect": "Allow",
7 | "Sid": "",
8 | "Principal": {
9 | "Service": "states.amazonaws.com"
10 | }
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/terraform/module/json_files/state_machine.json:
--------------------------------------------------------------------------------
1 | {
2 | "Comment": "AWS Lambda Power Tuning state machine",
3 | "StartAt": "Initializer",
4 | "States": {
5 | "Initializer": {
6 | "Type": "Task",
7 | "Resource": "${initializerArn}",
8 | "Next": "Publisher",
9 | "ResultPath": "$.lambdaConfigurations",
10 | "TimeoutSeconds": 600,
11 | "Catch": [
12 | {
13 | "ErrorEquals": ["States.ALL"],
14 | "Next": "CleanUpOnError",
15 | "ResultPath": "$.error"
16 | }
17 | ]
18 | },
19 | "Publisher": {
20 | "Type": "Task",
21 | "Resource": "${publisherArn}",
22 | "Next": "IsCountReached",
23 | "ResultPath": "$.lambdaConfigurations",
24 | "TimeoutSeconds": 600,
25 | "Catch": [{
26 | "ErrorEquals": [ "States.ALL" ],
27 | "Next": "CleanUpOnError",
28 | "ResultPath": "$.error"
29 | }]
30 | },
31 | "IsCountReached": {
32 | "Type": "Choice",
33 | "Choices": [
34 | {
35 | "Variable": "$.lambdaConfigurations.iterator.continue",
36 | "BooleanEquals": true,
37 | "Next": "Publisher"
38 | }
39 | ],
40 | "Default": "Branching"
41 | },
42 | "Branching": {
43 | "Type": "Map",
44 | "Next": "Cleaner",
45 | "ItemsPath": "$.lambdaConfigurations.powerValues",
46 | "ResultPath": "$.stats",
47 | "ItemSelector": {
48 | "input.$": "$",
49 | "value.$": "$$.Map.Item.Value"
50 | },
51 | "MaxConcurrency": 0,
52 | "Catch": [{
53 | "ErrorEquals": ["States.ALL"],
54 | "Next": "CleanUpOnError",
55 | "ResultPath": "$.error"
56 | }],
57 | "Iterator": {
58 | "StartAt": "Executor",
59 | "States": {
60 | "Executor": {
61 | "Type": "Task",
62 | "Resource": "${executorArn}",
63 | "End": true,
64 | "TimeoutSeconds": 30,
65 | "Retry": [{
66 | "ErrorEquals": ["States.ALL"],
67 | "IntervalSeconds": 3,
68 | "MaxAttempts": 2
69 | }]
70 | }
71 | }
72 | }
73 | },
74 | "Cleaner": {
75 | "Type": "Task",
76 | "Next": "Analyzer",
77 | "ResultPath": null,
78 | "TimeoutSeconds": 30,
79 | "Resource": "${cleanerArn}"
80 | },
81 | "Analyzer": {
82 | "Type": "Task",
83 | "Resource": "${analyzerArn}",
84 | "ResultPath": "$.analysis",
85 | "TimeoutSeconds": 10,
86 | "Next": "Optimizer"
87 | },
88 | "Optimizer": {
89 | "Type": "Task",
90 | "Resource": "${optimizerArn}",
91 | "ResultPath": null,
92 | "OutputPath": "$.analysis",
93 | "TimeoutSeconds": 30,
94 | "End": true
95 | },
96 | "CleanUpOnError": {
97 | "Type": "Task",
98 | "ResultPath": null,
99 | "OutputPath": null,
100 | "Resource": "${cleanerArn}",
101 | "TimeoutSeconds": 30,
102 | "End": true
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/terraform/module/lambda.tf:
--------------------------------------------------------------------------------
1 |
2 | resource "aws_lambda_function" "analyzer" {
3 | filename = "../src/app.zip"
4 | function_name = "${var.lambda_function_prefix}-analyzer"
5 | role = aws_iam_role.analyzer_role.arn
6 | handler = "analyzer.handler"
7 | layers = [aws_lambda_layer_version.lambda_layer.arn]
8 | memory_size = 128
9 | timeout = 30
10 |
11 | # The filebase64sha256() function is available in Terraform 0.11.12 and later
12 | # For Terraform 0.11.11 and earlier, use the base64sha256() function and the file() function:
13 | # source_code_hash = "${base64sha256(file("lambda_function_payload.zip"))}"
14 | source_code_hash = data.archive_file.app.output_base64sha256
15 |
16 | runtime = "nodejs20.x"
17 |
18 | dynamic "vpc_config" {
19 | for_each = var.vpc_subnet_ids != null && var.vpc_security_group_ids != null ? [true] : []
20 | content {
21 | security_group_ids = var.vpc_security_group_ids
22 | subnet_ids = var.vpc_subnet_ids
23 | }
24 | }
25 |
26 | environment {
27 | variables = {
28 | defaultPowerValues = local.defaultPowerValues,
29 | minRAM = local.minRAM,
30 | baseCosts = local.baseCosts,
31 | sfCosts = local.sfCosts,
32 | visualizationURL = local.visualizationURL
33 | }
34 | }
35 |
36 | depends_on = [aws_lambda_layer_version.lambda_layer]
37 | }
38 |
39 | resource "aws_lambda_function" "cleaner" {
40 | filename = "../src/app.zip"
41 | function_name = "${var.lambda_function_prefix}-cleaner"
42 | role = aws_iam_role.cleaner_role.arn
43 | handler = "cleaner.handler"
44 | layers = [aws_lambda_layer_version.lambda_layer.arn]
45 | memory_size = 128
46 | timeout = 40
47 |
48 | # The filebase64sha256() function is available in Terraform 0.11.12 and later
49 | # For Terraform 0.11.11 and earlier, use the base64sha256() function and the file() function:
50 | # source_code_hash = "${base64sha256(file("lambda_function_payload.zip"))}"
51 | source_code_hash = data.archive_file.app.output_base64sha256
52 |
53 | runtime = "nodejs20.x"
54 |
55 | dynamic "vpc_config" {
56 | for_each = var.vpc_subnet_ids != null && var.vpc_security_group_ids != null ? [true] : []
57 | content {
58 | security_group_ids = var.vpc_security_group_ids
59 | subnet_ids = var.vpc_subnet_ids
60 | }
61 | }
62 |
63 | environment {
64 | variables = {
65 | defaultPowerValues = local.defaultPowerValues,
66 | minRAM = local.minRAM,
67 | baseCosts = local.baseCosts,
68 | sfCosts = local.sfCosts,
69 | visualizationURL = local.visualizationURL
70 | }
71 | }
72 |
73 | depends_on = [aws_lambda_layer_version.lambda_layer]
74 | }
75 |
76 | resource "aws_lambda_function" "executor" {
77 | filename = "../src/app.zip"
78 | function_name = "${var.lambda_function_prefix}-executor"
79 | role = aws_iam_role.executor_role.arn
80 | handler = "executor.handler"
81 | layers = [aws_lambda_layer_version.lambda_layer.arn]
82 | memory_size = 128
83 | timeout = 30
84 |
85 | # The filebase64sha256() function is available in Terraform 0.11.12 and later
86 | # For Terraform 0.11.11 and earlier, use the base64sha256() function and the file() function:
87 | # source_code_hash = "${base64sha256(file("lambda_function_payload.zip"))}"
88 | source_code_hash = data.archive_file.app.output_base64sha256
89 |
90 | runtime = "nodejs20.x"
91 |
92 | dynamic "vpc_config" {
93 | for_each = var.vpc_subnet_ids != null && var.vpc_security_group_ids != null ? [true] : []
94 | content {
95 | security_group_ids = var.vpc_security_group_ids
96 | subnet_ids = var.vpc_subnet_ids
97 | }
98 | }
99 |
100 | environment {
101 | variables = {
102 | defaultPowerValues = local.defaultPowerValues,
103 | minRAM = local.minRAM,
104 | baseCosts = local.baseCosts,
105 | sfCosts = local.sfCosts,
106 | visualizationURL = local.visualizationURL
107 | }
108 | }
109 |
110 | depends_on = [aws_lambda_layer_version.lambda_layer]
111 | }
112 |
113 | resource "aws_lambda_function" "initializer" {
114 | filename = "../src/app.zip"
115 | function_name = "${var.lambda_function_prefix}-initializer"
116 | role = aws_iam_role.initializer_role.arn
117 | handler = "initializer.handler"
118 | layers = [aws_lambda_layer_version.lambda_layer.arn]
119 | memory_size = 128
120 | timeout = 30
121 |
122 | # The filebase64sha256() function is available in Terraform 0.11.12 and later
123 | # For Terraform 0.11.11 and earlier, use the base64sha256() function and the file() function:
124 | # source_code_hash = "${base64sha256(file("lambda_function_payload.zip"))}"
125 | source_code_hash = data.archive_file.app.output_base64sha256
126 |
127 | runtime = "nodejs20.x"
128 |
129 | dynamic "vpc_config" {
130 | for_each = var.vpc_subnet_ids != null && var.vpc_security_group_ids != null ? [true] : []
131 | content {
132 | security_group_ids = var.vpc_security_group_ids
133 | subnet_ids = var.vpc_subnet_ids
134 | }
135 | }
136 |
137 | environment {
138 | variables = {
139 | defaultPowerValues = local.defaultPowerValues,
140 | minRAM = local.minRAM,
141 | baseCosts = local.baseCosts,
142 | sfCosts = local.sfCosts,
143 | visualizationURL = local.visualizationURL
144 | }
145 | }
146 |
147 | depends_on = [aws_lambda_layer_version.lambda_layer]
148 | }
149 |
150 | resource "aws_lambda_function" "publisher" {
151 | filename = "../src/app.zip"
152 | function_name = "${var.lambda_function_prefix}-publisher"
153 | role = aws_iam_role.publisher_role.arn
154 | handler = "publisher.handler"
155 | layers = [aws_lambda_layer_version.lambda_layer.arn]
156 | memory_size = 128
157 | timeout = 300
158 |
159 | # The filebase64sha256() function is available in Terraform 0.11.12 and later
160 | # For Terraform 0.11.11 and earlier, use the base64sha256() function and the file() function:
161 | # source_code_hash = "${base64sha256(file("lambda_function_payload.zip"))}"
162 | source_code_hash = data.archive_file.app.output_base64sha256
163 |
164 | runtime = "nodejs20.x"
165 |
166 | dynamic "vpc_config" {
167 | for_each = var.vpc_subnet_ids != null && var.vpc_security_group_ids != null ? [true] : []
168 | content {
169 | security_group_ids = var.vpc_security_group_ids
170 | subnet_ids = var.vpc_subnet_ids
171 | }
172 | }
173 |
174 | environment {
175 | variables = {
176 | defaultPowerValues = local.defaultPowerValues,
177 | minRAM = local.minRAM,
178 | baseCosts = local.baseCosts,
179 | sfCosts = local.sfCosts,
180 | visualizationURL = local.visualizationURL
181 | }
182 | }
183 |
184 | depends_on = [aws_lambda_layer_version.lambda_layer]
185 | }
186 |
187 | resource "aws_lambda_function" "optimizer" {
188 | filename = "../src/app.zip"
189 | function_name = "${var.lambda_function_prefix}-optimizer"
190 | role = aws_iam_role.optimizer_role.arn
191 | handler = "optimizer.handler"
192 | layers = [aws_lambda_layer_version.lambda_layer.arn]
193 | memory_size = 128
194 | timeout = 30
195 |
196 | # The filebase64sha256() function is available in Terraform 0.11.12 and later
197 | # For Terraform 0.11.11 and earlier, use the base64sha256() function and the file() function:
198 | # source_code_hash = "${base64sha256(file("lambda_function_payload.zip"))}"
199 | source_code_hash = data.archive_file.app.output_base64sha256
200 |
201 | runtime = "nodejs20.x"
202 |
203 | dynamic "vpc_config" {
204 | for_each = var.vpc_subnet_ids != null && var.vpc_security_group_ids != null ? [true] : []
205 | content {
206 | security_group_ids = var.vpc_security_group_ids
207 | subnet_ids = var.vpc_subnet_ids
208 | }
209 | }
210 |
211 | environment {
212 | variables = {
213 | defaultPowerValues = local.defaultPowerValues,
214 | minRAM = local.minRAM,
215 | baseCosts = local.baseCosts,
216 | sfCosts = local.sfCosts,
217 | visualizationURL = local.visualizationURL
218 | }
219 | }
220 |
221 | depends_on = [aws_lambda_layer_version.lambda_layer]
222 | }
223 |
224 |
225 | resource "aws_lambda_layer_version" "lambda_layer" {
226 | filename = "../src/layer.zip"
227 | layer_name = "AWS-SDK-v3"
228 | description = "AWS SDK 3"
229 | compatible_architectures = ["x86_64"]
230 | compatible_runtimes = ["nodejs20.x"]
231 |
232 | depends_on = [data.archive_file.layer]
233 | }
234 |
235 |
--------------------------------------------------------------------------------
/terraform/module/locals.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | defaultPowerValues = "[128,256,512,1024,1536,3008]"
3 | minRAM = 128
4 | baseCosts = jsonencode({"x86_64": {"ap-east-1":2.9e-9,"af-south-1":2.8e-9,"me-south-1":2.6e-9,"eu-south-1":2.4e-9,"ap-northeast-3":2.7e-9,"default":2.1e-9}, "arm64": {"default":1.7e-9}})
5 | sfCosts = jsonencode({ "default" : 0.000025, "us-gov-west-1" : 0.00003, "ap-northeast-2" : 0.0000271, "eu-south-1" : 0.00002625, "af-south-1" : 0.00002975, "us-west-1" : 0.0000279, "eu-west-3" : 0.0000297, "ap-east-1" : 0.0000275, "me-south-1" : 0.0000275, "ap-south-1" : 0.0000285, "us-gov-east-1" : 0.00003, "sa-east-1" : 0.0000375 })
6 | visualizationURL = "https://lambda-power-tuning.show/"
7 |
8 | role_path = var.role_path_override != "" ? var.role_path_override : "/${var.lambda_function_prefix}/"
9 |
10 | state_machine = templatefile(
11 | "${path.module}/json_files/state_machine.json",
12 | {
13 | initializerArn = aws_lambda_function.initializer.arn,
14 | publisherArn = aws_lambda_function.publisher.arn,
15 | executorArn = aws_lambda_function.executor.arn,
16 | cleanerArn = aws_lambda_function.cleaner.arn,
17 | analyzerArn = aws_lambda_function.analyzer.arn,
18 | optimizerArn = aws_lambda_function.optimizer.arn
19 | }
20 | )
21 |
22 | cleaner_template = templatefile(
23 | "${path.module}/json_files/cleaner.json",
24 | {
25 | account_id = var.account_id
26 | }
27 | )
28 |
29 | executor_template = templatefile(
30 | "${path.module}/json_files/executor.json",
31 | {
32 | account_id = var.account_id
33 | }
34 | )
35 |
36 | initializer_template = templatefile(
37 | "${path.module}/json_files/initializer.json",
38 | {
39 | account_id = var.account_id
40 | }
41 | )
42 |
43 | publisher_template = templatefile(
44 | "${path.module}/json_files/publisher.json",
45 | {
46 | account_id = var.account_id
47 | }
48 | )
49 |
50 | optimizer_template = templatefile(
51 | "${path.module}/json_files/optimizer.json",
52 | {
53 | account_id = var.account_id
54 | }
55 | )
56 | }
57 |
--------------------------------------------------------------------------------
/terraform/module/policies.tf:
--------------------------------------------------------------------------------
1 | data "aws_iam_policy" "analyzer_policy" {
2 | name = "AWSLambdaExecute"
3 | }
4 |
5 | resource "aws_iam_policy_attachment" "execute-attach" {
6 | name = "execute-attachment"
7 | roles = [aws_iam_role.analyzer_role.name, aws_iam_role.optimizer_role.name, aws_iam_role.executor_role.name, aws_iam_role.cleaner_role.name, aws_iam_role.initializer_role.name]
8 | policy_arn = data.aws_iam_policy.analyzer_policy.arn
9 | }
10 |
11 | resource "aws_iam_policy" "executor_policy" {
12 | name = "${var.lambda_function_prefix}_executor-policy"
13 | description = "Lambda power tuning policy - Executor - Terraform"
14 |
15 | policy = local.executor_template
16 | }
17 |
18 | resource "aws_iam_policy_attachment" "executor-attach" {
19 | name = "executor-attachment"
20 | roles = [aws_iam_role.executor_role.name]
21 | policy_arn = aws_iam_policy.executor_policy.arn
22 | }
23 |
24 | resource "aws_iam_policy" "initializer_policy" {
25 | name = "${var.lambda_function_prefix}_initializer-policy"
26 | description = "Lambda power tuning policy - Initializer - Terraform"
27 |
28 | policy = local.initializer_template
29 | }
30 |
31 | resource "aws_iam_policy_attachment" "initializer-attach" {
32 | name = "initializer-attachment"
33 | roles = [aws_iam_role.initializer_role.name]
34 | policy_arn = aws_iam_policy.initializer_policy.arn
35 | }
36 |
37 | resource "aws_iam_policy" "publisher_policy" {
38 | name = "${var.lambda_function_prefix}_publisher-policy"
39 | description = "Lambda power tuning policy - Publisher - Terraform"
40 |
41 | policy = local.publisher_template
42 | }
43 |
44 | resource "aws_iam_policy_attachment" "publisher-attach" {
45 | name = "publisher-attachment"
46 | roles = [aws_iam_role.publisher_role.name]
47 | policy_arn = aws_iam_policy.publisher_policy.arn
48 | }
49 |
50 | resource "aws_iam_policy" "cleaner_policy" {
51 | name = "${var.lambda_function_prefix}_cleaner-policy"
52 | description = "Lambda power tuning policy - Cleaner - Terraform"
53 |
54 | policy = local.cleaner_template
55 | }
56 |
57 | resource "aws_iam_policy_attachment" "cleaner-attach" {
58 | name = "cleaner-attachment"
59 | roles = [aws_iam_role.cleaner_role.name]
60 | policy_arn = aws_iam_policy.cleaner_policy.arn
61 | }
62 |
63 | resource "aws_iam_policy" "optimizer_policy" {
64 | name = "${var.lambda_function_prefix}_optimizer-policy"
65 | description = "Lambda power tuning policy - Optimizer - Terraform"
66 |
67 | policy = local.optimizer_template
68 | }
69 |
70 | resource "aws_iam_policy_attachment" "optimizer-attach" {
71 | name = "optimizer-attachment"
72 | roles = [aws_iam_role.optimizer_role.name]
73 | policy_arn = aws_iam_policy.optimizer_policy.arn
74 | }
75 |
76 |
77 | data "aws_iam_policy" "sfn_policy" {
78 | name = "AWSLambdaRole"
79 | }
80 |
81 | resource "aws_iam_policy_attachment" "sfn-attach" {
82 | name = "sfn-attachment"
83 | roles = [aws_iam_role.sfn_role.name]
84 | policy_arn = data.aws_iam_policy.sfn_policy.arn
85 | }
--------------------------------------------------------------------------------
/terraform/module/roles.tf:
--------------------------------------------------------------------------------
1 | resource "aws_iam_role" "analyzer_role" {
2 | name = "${var.lambda_function_prefix}-analyzer_role"
3 | permissions_boundary = var.permissions_boundary
4 | path = local.role_path
5 | assume_role_policy = file("${path.module}/json_files/lambda.json")
6 | }
7 |
8 | resource "aws_iam_role" "optimizer_role" {
9 | name = "${var.lambda_function_prefix}-optimizer_role"
10 | permissions_boundary = var.permissions_boundary
11 | path = local.role_path
12 | assume_role_policy = file("${path.module}/json_files/lambda.json")
13 | }
14 |
15 | resource "aws_iam_role" "executor_role" {
16 | name = "${var.lambda_function_prefix}-executor_role"
17 | permissions_boundary = var.permissions_boundary
18 | path = local.role_path
19 | assume_role_policy = file("${path.module}/json_files/lambda.json")
20 | }
21 |
22 | resource "aws_iam_role" "initializer_role" {
23 | name = "${var.lambda_function_prefix}-initializer_role"
24 | permissions_boundary = var.permissions_boundary
25 | path = local.role_path
26 | assume_role_policy = file("${path.module}/json_files/lambda.json")
27 | }
28 |
29 | resource "aws_iam_role" "publisher_role" {
30 | name = "${var.lambda_function_prefix}-publisher_role"
31 | permissions_boundary = var.permissions_boundary
32 | path = local.role_path
33 | assume_role_policy = file("${path.module}/json_files/lambda.json")
34 | }
35 |
36 | resource "aws_iam_role" "cleaner_role" {
37 | name = "${var.lambda_function_prefix}-cleaner_role"
38 | permissions_boundary = var.permissions_boundary
39 | path = local.role_path
40 | assume_role_policy = file("${path.module}/json_files/lambda.json")
41 | }
42 |
43 | resource "aws_iam_role" "sfn_role" {
44 | name = "${var.lambda_function_prefix}-sfn_role"
45 | permissions_boundary = var.permissions_boundary
46 | path = local.role_path
47 | assume_role_policy = file("${path.module}/json_files/sfn.json")
48 | }
49 |
--------------------------------------------------------------------------------
/terraform/module/scripts/build-layer.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # make sure we're working on the layer folder
4 | cd ../layer-sdk
5 |
6 | # create subfolders
7 | ## ./src is referenced by the LayerVersion resource (.gitignored)
8 | ## ./src/nodejs will contain the node_modules
9 | mkdir -p ./src/nodejs
10 |
11 | # install layer dependencies (the SDK)
12 | npm i
13 |
14 | # clean up previous build ...
15 | rm -rf ./src/nodejs/node_modules
16 |
17 | # ... and move everything into the layer sub-folder
18 | mv ./node_modules ./src/nodejs
--------------------------------------------------------------------------------
/terraform/module/state_machine.tf:
--------------------------------------------------------------------------------
1 |
2 | resource "aws_sfn_state_machine" "state-machine" {
3 | name_prefix = var.lambda_function_prefix
4 | role_arn = aws_iam_role.sfn_role.arn
5 |
6 | definition = local.state_machine
7 | }
--------------------------------------------------------------------------------
/terraform/module/variables.tf:
--------------------------------------------------------------------------------
1 | variable "account_id" {
2 | description = "Your AWS account id."
3 | }
4 |
5 | variable "lambda_function_prefix" {
6 | default = "lambda_power_tuning"
7 | description = "Prefix used for the names of Lambda functions, Step Functions state machines, IAM roles, and IAM policies."
8 | }
9 |
10 | variable "role_path_override" {
11 | default = ""
12 | type = string
13 | description = "IAM Role path to use for each Lambda function's role, instead of the default path /lambda_power_tuning/ (see variable lambda_function_prefix)."
14 | }
15 |
16 | variable "permissions_boundary" {
17 | default = null
18 | description = "ARN of the policy that is used to set the permissions boundary for the role."
19 | }
20 |
21 | variable "vpc_subnet_ids" {
22 | description = "List of subnet ids when Lambda Function should run in the VPC. Usually private or intra subnets."
23 | type = list(string)
24 | default = null
25 | }
26 |
27 | variable "vpc_security_group_ids" {
28 | description = "List of security group ids when Lambda Function should run in the VPC."
29 | type = list(string)
30 | default = null
31 | }
--------------------------------------------------------------------------------
/terraform/provider.tf:
--------------------------------------------------------------------------------
1 | provider "aws" {
2 | region = var.aws_region
3 | }
--------------------------------------------------------------------------------
/terraform/variables.tf:
--------------------------------------------------------------------------------
1 | variable "aws_region" {
2 | default = "eu-west-1"
3 | }
4 |
5 | variable "account_id" {
6 | default = "123456789101"
7 | }
--------------------------------------------------------------------------------
/test/setup.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const sinon = require('sinon');
4 |
5 | let consoleLogStub;
6 |
7 | // hide all logging for tests
8 | // comment out the line which
9 | // you would like to see logged
10 | // during test run
11 | consoleLogStub = sinon.stub(console, 'log');
12 | sinon.stub(console, 'info');
13 | sinon.stub(console, 'debug');
14 | sinon.stub(console, 'error');
15 |
16 | module.exports = { consoleLogStub };
17 |
--------------------------------------------------------------------------------