├── .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 | [![Build Status](https://travis-ci.com/alexcasalboni/aws-lambda-power-tuning.svg?branch=master)](https://app.travis-ci.com/github/alexcasalboni/aws-lambda-power-tuning) 4 | [![Coverage Status](https://coveralls.io/repos/github/alexcasalboni/aws-lambda-power-tuning/badge.svg)](https://coveralls.io/github/alexcasalboni/aws-lambda-power-tuning) 5 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/alexcasalboni/aws-lambda-power-tuning/graphs/commit-activity) 6 | [![GitHub issues](https://img.shields.io/github/issues/alexcasalboni/aws-lambda-power-tuning.svg)](https://github.com/alexcasalboni/aws-lambda-power-tuning/issues) 7 | [![Open Source Love svg2](https://badges.frapsoft.com/os/v2/open-source.svg?v=103)](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 | ![state-machine](imgs/state-machine-screenshot.png?raw=true) 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 | ![visualization1](imgs/visualization1.jpg?raw=true) 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 | ![visualization2](imgs/visualization2.jpg?raw=true) 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 | ![visualization](imgs/visualization.png?raw=true) 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 | --------------------------------------------------------------------------------