Skip to content
This repository was archived by the owner on Jun 11, 2024. It is now read-only.

Commit 678b430

Browse files
committed
Adds initial code
1 parent f87e4a8 commit 678b430

31 files changed

+4624
-11
lines changed

.gitignore

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
*.js
2+
!functions/**
3+
!jest.config.js
4+
*.d.ts
5+
node_modules
6+
7+
# CDK asset staging directory
8+
.cdk.staging
9+
cdk.out
10+
11+
12+
packaged.yaml
13+
synth.yaml
14+
.DS_Store
15+
.aws-sam
16+
samconfig.toml

.npmignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.ts
2+
!*.d.ts
3+
4+
# CDK asset staging directory
5+
.cdk.staging
6+
cdk.out

CODE_OF_CONDUCT.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
## Code of Conduct
1+
# Code of Conduct
2+
23
This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
34
For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4-
[email protected] with any additional questions or comments.
5+
[email protected] with any additional questions or comments.

LICENSE

-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,3 @@ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
1212
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
1313
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1414
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15-

README.md

+168-8
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,177 @@
1-
## My Project
1+
# Monitoring the Java Virtual Machine Garbage Collection on AWS Lambda
22

3-
TODO: Fill this README out!
3+
This is the implementation for the solution architecture introduced in the blog post about [monitoring the Java Virtual Machine garbage collection on AWS Lambda](https://aws.amazon.com/blogs/architecture/field-notes-monitoring-the-java-virtual-machine-garbage-collection-on-aws-lambda/).
44

5-
Be sure to:
5+
When you want to optimize your Java application on [AWS Lambda](https://aws.amazon.com/lambda/) for performance and cost the general steps are: Build, measure, then optimize! To accomplish this, you need a solid monitoring mechanism. [Amazon CloudWatch](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html) and [AWS X-Ray](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html) are well suited for this task since they already provide lots of data about your AWS Lambda function. This includes overall memory consumption, initialization time, and duration of your invocations. To examine the Java Virtual Machine (JVM) memory you require garbage collection logs from your functions. Instances of an AWS Lambda function have a short lifecycle compared to a long-running Java application server. It can be challenging to process the logs from tens or hundreds of these instances.
66

7-
* Change the title in this README
8-
* Edit your repository description on GitHub
7+
With this solution you emit and collect data to monitor the JVM garbage collector activity. Having this data, you can visualize out-of-memory situations of your applications in a Kibana dashboard like in the following screenshot. You gain actionable insights into your application’s memory consumption on AWS Lambda for troubleshooting and optimization.
8+
9+
![Lambda GC Activity dashboard](img/dashboard.png)
10+
11+
## Get Started
12+
13+
To start, launch the solution architecture described above as a prepackaged application from the AWS Serverless Application Repository. It contains all resources ready to visualize the garbage collection logs for your Java 11 AWS Lambda functions in a Kbana dashboard. The search cluster consists of a single `t2.small.elasticsearch` instance with 10GB of EBS storage. It is protected with Amazon Cognito User Pools so you only need to add your user(s). The T2 instance types do not support encryption of data at rest. The application template prefixes the search domain and the Amazon Cognito Hosted UI with a string that you can define with the `applicationPrefix` template parameter.
14+
15+
### Option A: Spin up the application from the AWS Serverless Application Repository:
16+
17+
[![cloudformation-launch-button](https://s3.amazonaws.com/cloudformation-examples/cloudformation-launch-stack.png)](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:387304072572:applications~monitor-java-gc-on-aws-lambda)
18+
19+
### Option B: Deploy the template from code
20+
21+
You deploy the template with [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM). Additionally, you will need the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) and the [CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html).
22+
23+
1. The CDK template is written in TypeScript. TypeScript sources must be compiled to JavaScript initially and after each modification. Open a new terminal and keep this terminal open in the background if you like to change the source files. Change the directory to the one where `cdk.json` is and execute:
24+
25+
```bash
26+
npm install
27+
npm run watch
28+
```
29+
30+
Read the [CDK developer guide](https://docs.aws.amazon.com/cdk/latest/guide/home.html) for more information.
31+
32+
2. Synthesize the CDK template to an AWS CloudFormation template:
33+
34+
```bash
35+
cdk synth --version-reporting false > synth.yaml
36+
```
37+
38+
3. Optionally run the tests:
39+
40+
```bash
41+
./run-unit-tests.sh
42+
```
43+
44+
4. Build the application. The `sam build` builds all AWS Lambda functions with `npm`:
45+
46+
```bash
47+
sam build -t synth.yaml
48+
```
49+
50+
5. Package and deploy the template. `sam deploy` [transforms](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-deploying.html) the template to AWS CloudFormation code and uploads this template and the AWS Lambda function code as a package to S3.
51+
52+
```bash
53+
sam deploy --guided
54+
```
55+
56+
### Access the Example Dashboard
57+
58+
1. As soon as the application is deployed completely the outputs of the [AWS CloudFormation stack](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacks.html) provides the links for the next steps. You will find two URLs in the AWS CloudFormation console called `createUserUrl` and `kibanaUrl`.
59+
![AWS CloudFormation outputs](img/cfn_outputs.png)
60+
61+
2. Use the `createUserUrl` link from the outputs, or navigate to the [Amazon Cognito user pool](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html) in the console to create a new user in the pool.
62+
63+
- Enter an email address as username and email. Enter a temporary password of your choice with at least 8 characters.
64+
- Leave the phone number empty and uncheck the checkbox to mark the phone number as verified.
65+
- If necessary, you can check the checkboxes to send an invitation to the new user or to make the user verify the email address.
66+
- Choose **Create user**.
67+
![AWS CloudFormation outputs](img/create_user.png)
68+
69+
3. Access the Kibana dashboard with the `kibanaUrl` link from the AWS CloudFormation stack outputs, or navigate to the Kibana link displayed in the Amazon Elasticsearch Service console.
70+
71+
- In Kibana, choose the Dashboard icon in the left menu bar
72+
- Open the *Lambda GC Activity* dashboard.
73+
74+
4. You can test that new events appear by using the Kibana Developer Console:
75+
76+
```json
77+
POST gc-logs-2020.09.03/_doc
78+
{
79+
"@timestamp": "2020-09-03T15:12:34.567+0000",
80+
"@gc_type": "Pause Young",
81+
"@gc_cause": "Allocation Failure",
82+
"@heap_before_gc": "2",
83+
"@heap_after_gc": "1",
84+
"@heap_size_gc": "9",
85+
"@gc_duration": "5.432",
86+
"@owner": "123456789012",
87+
"@log_group": "/aws/lambda/myfunction",
88+
"@log_stream": "2020/09/03/[$LATEST]123456"
89+
}
90+
```
91+
92+
5. When you go to the *Lambda GC Activity* dashboard you can see the new event. You must select the right timeframe with the **Show dates** link.
93+
94+
![Lambda GC Activity with a single GC log event](img/initial_dashboard.png)
95+
96+
The dashboard consists of six tiles:
97+
98+
- In the **Filters** you can optionally select the log group and filter for a specific AWS Lambda function execution context by the name of its log stream.
99+
- In the **GC Activity Count by Execution Context** you see a heatmap of all filtered execution contexts by garbage collection activity count.
100+
- The **GC Activity Metrics** display a graph for the metrics for all filtered execution contexts.
101+
- The **GC Activity Count** shows the amount of garbage collection activities that are currently displayed.
102+
- The **GC Duration** show the sum of the duration of all displayed garbage collection activities.
103+
- The **GC Activity Raw Data** at the bottom displays the raw items as ingested into the search cluster for a further drill down.
104+
105+
### Configure your AWS Lambda function for garbage collection logging
106+
107+
1. The application that you want to monitor needs to log garbage collection activities. Currently the solution supports logs from **Java 11**. Add the following environment variable to your AWS Lambda function to activate the logging.
108+
109+
```bash
110+
JAVA_TOOL_OPTIONS=-Xlog:gc:stderr:time,tags
111+
```
112+
113+
The **environment variables** must reflect this parameter like the following screenshot:
114+
115+
![JAVA_TOOL_OPTIONS environment variable to activate GC logging in the AWS Lambda console](img/envvars.png)
116+
117+
2. Go to the `streamLogs` function in the AWS Lambda console that has been created by the stack, and subscribe it to the log group of the function you want to monitor.
118+
119+
![view on the designer in the AWS Lambda console with the add trigger button](img/streamlogs.png)
120+
121+
3. Select **Add Trigger**.
122+
123+
4. Select **CloudWatch Logs** as **Trigger Configuration**.
124+
125+
5. Input a **Filter name** of your choice.
126+
127+
6. Input `"[gc"` (including quotes) as the **Filter pattern** to match all garbage collection log entries.
128+
129+
7. Select the **Log Group** of the function you want to monitor. The following screenshot subscribes to the logs of the application’s function `resize-lambda-ResizeFn-[...]`.
130+
131+
![add trigger form](img/addtrigger.png)
132+
133+
8. Select **Add**.
134+
135+
9. Execute the AWS Lambda function you want to monitor.
136+
137+
10. Refresh the dashboard in Amazon Elasticsearch Service and see the datapoint added manually before appearing in the graph.
138+
139+
### Cost estimation and clean up
140+
141+
If you do not need the garbage collection monitoring anymore, delete the subscription filter from the log group of your AWS Lambda function(s). Also, delete the stack of the solution above in the AWS CloudFormation console to clean up resources.
142+
143+
1. Sign in to the AWS CloudFormation console and choose your stack.
144+
2. Choose the `streamLogs` AWS Lambda function so the function is displayed in the AWS Lambda console.
145+
3. Delete all triggers in the **Designer** panel.
146+
4. Go back to the AWS CloudFormation console and your stack.
147+
5. Choose **Delete** to delete all resources, including the search cluster and the Amazon Cognito user pool.
148+
149+
## FAQs
150+
151+
### Q: In which region can I deploy the sample application?
152+
153+
The Launch Stack button above opens the AWS Serverless Application Repository in the US East 1 (Northern Virginia) region. You may switch to other regions from there before deployment.
154+
155+
### Q: How much do resources in this template cost?
156+
157+
Standard AWS charges apply to the resources you deploy with this template.
158+
159+
Cost for the processing and transformation of your function's Amazon CloudWatch Logs incurs when your function is called. This cost depends on your application and how often GC activities are triggered.
160+
161+
Amazon Elasticsearch Service provides customers in the [AWS Free Tier](https://aws.amazon.com/free/) free usage of up to 750 hours per month of the configuration in this template, i.e. a single-AZ `t2.small.elasticsearch` instance and 10GB of EBS storage for up to one year from the date the account was created. If you exceed the free tier limits, you will be charged the Amazon Elasticsearch Service rates for the additional resources you use. Read an [estimate of the monthly cost of the search cluster](https://calculator.aws/#/estimate?id=2eb5bf80f1aa4177a0021101488874078a2d847e).
162+
163+
The Amazon Cognito User Pool feature has a free tier of 50,000 monthly active users for users who sign in directly to Cognito User Pools. The free tier does not automatically expire at the end of your 12 month AWS Free Tier term, and it is available to both existing and new AWS customers indefinitely.
164+
165+
See offer terms of [Amazon Cognito](https://aws.amazon.com/cognito/pricing/), [Amazon Elasticsearch Service](https://aws.amazon.com/elasticsearch-service/pricing/), [AWS Lambda](https://aws.amazon.com/lambda/pricing/), and [Amazon CloudWatch](https://aws.amazon.com/cloudwatch/pricing/) for more details.
166+
167+
### Q: How can I contribute?
168+
169+
See the [Contributing Guidelines](CONTRIBUTING.md) for details.
9170
10171
## Security
11172
12-
See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
173+
See [CONTRIBUTING](CONTRIBUTING.md) for more information.
13174
14175
## License
15176
16-
This library is licensed under the MIT-0 License. See the LICENSE file.
17-
177+
This library is licensed under the MIT-0 License. See the [LICENSE](LICENSE) file.

bin/search.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env node
2+
import 'source-map-support/register';
3+
import cdk = require('@aws-cdk/core');
4+
import { SearchStack } from '../lib/search-stack';
5+
6+
const app = new cdk.App();
7+
new SearchStack(app, 'searchStack');

cdk.context.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"@aws-cdk/core:enableStackNameDuplicates": "true"
3+
}

cdk.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"app": "npx ts-node bin/search.ts"
3+
}

functions/es-requests/cfn-response.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: MIT-0
3+
4+
exports.SUCCESS = "SUCCESS";
5+
exports.FAILED = "FAILED";
6+
7+
/**
8+
* derived from: https://github.com/awsdocs/aws-cloudformation-user-guide/blob/master/doc_source/cfn-lambda-function-code-cfnresponsemodule.md#module-source-code
9+
* adopted for calls from within an async function
10+
*/
11+
exports.send = function (event, context, responseStatus, responseData, physicalResourceId, noEcho) {
12+
13+
return new Promise((resolve, reject) => {
14+
15+
var responseBody = JSON.stringify({
16+
Status: responseStatus,
17+
Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName,
18+
PhysicalResourceId: physicalResourceId || context.logStreamName,
19+
StackId: event.StackId,
20+
RequestId: event.RequestId,
21+
LogicalResourceId: event.LogicalResourceId,
22+
NoEcho: noEcho || false,
23+
Data: responseData
24+
});
25+
26+
console.log("Response body:\n", responseBody);
27+
28+
var https = require("https");
29+
var url = require("url");
30+
31+
var parsedUrl = url.parse(event.ResponseURL);
32+
var options = {
33+
hostname: parsedUrl.hostname,
34+
port: 443,
35+
path: parsedUrl.path,
36+
method: "PUT",
37+
headers: {
38+
"content-type": "",
39+
"content-length": responseBody.length
40+
}
41+
};
42+
43+
var request = https.request(options, function (response) {
44+
console.log("Status code: " + response.statusCode);
45+
console.log("Status message: " + response.statusMessage);
46+
resolve(response);
47+
});
48+
49+
request.on("error", function (error) {
50+
console.log("send(..) failed executing https.request(..): " + error);
51+
reject(error);
52+
});
53+
54+
request.write(responseBody);
55+
request.end();
56+
});
57+
}

functions/es-requests/es-requests.js

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: MIT-0
3+
4+
const AWS = require('aws-sdk');
5+
const cfn_response = require('./cfn-response.js');
6+
7+
const region = process.env.REGION;
8+
const domain = process.env.DOMAIN;
9+
10+
function handleSuccess(response) {
11+
if (response.status >= 200 && response.status < 300) {
12+
console.log(`Successful, status: ${response.status}, body: ${response.body}`);
13+
} else {
14+
throw new Error("Request failed: " + JSON.stringify(response));
15+
}
16+
}
17+
18+
exports.handler = async function (event, context) {
19+
const physicalId = "TheOnlyCustomResource";
20+
const requests = event.ResourceProperties.Requests;
21+
22+
var requestSuccessful = true;
23+
24+
// run the promises sequentially
25+
await requests.reduce(async (previousPromise, request) => {
26+
return previousPromise
27+
.then(_result => { return sendDocument(request.method, request.path, request.body) })
28+
.then(handleSuccess);
29+
}, Promise.resolve())
30+
.catch(error => {
31+
console.log({ error });
32+
requestSuccessful = false;
33+
});
34+
35+
if (event.ResponseURL) {
36+
console.log({requestSuccessful});
37+
const status = requestSuccessful ? cfn_response.SUCCESS : cfn_response.FAILED;
38+
await cfn_response.send(event, context, status, {}, physicalId);
39+
}
40+
};
41+
42+
/**
43+
* Derived from https://github.com/awsdocs/amazon-elasticsearch-service-developer-guide/blob/master/doc_source/es-request-signing.md#node
44+
*/
45+
function sendDocument(httpMethod, path, document) {
46+
return new Promise(function (resolve, reject) {
47+
console.log({ httpMethod, path, document });
48+
49+
var endpoint = new AWS.Endpoint(domain);
50+
var request = new AWS.HttpRequest(endpoint, region);
51+
52+
// if this is a kibana request pass it to the kibana API,
53+
// add a xsrf header
54+
if (path.startsWith("api/kibana")) {
55+
request.path += "_plugin/kibana/";
56+
request.headers['kbn-xsrf'] = 'kibana';
57+
}
58+
59+
request.path += path;
60+
request.method = httpMethod;
61+
if (typeof document === 'string') {
62+
request.body = document;
63+
}
64+
request.headers['host'] = domain;
65+
request.headers['Content-Type'] = 'application/json';
66+
// Content-Length is only needed for DELETE requests that include a request
67+
// body, but including it for all requests doesn't seem to hurt anything.
68+
request.headers['Content-Length'] = Buffer.byteLength(request.body);
69+
70+
var credentials = new AWS.EnvironmentCredentials('AWS');
71+
var signer = new AWS.Signers.V4(request, 'es');
72+
signer.addAuthorization(credentials, new Date());
73+
74+
var client = new AWS.HttpClient();
75+
client.handleRequest(request, null, function (response) {
76+
var responseBody = "";
77+
response.on('data', function (chunk) {
78+
responseBody += chunk;
79+
});
80+
response.on('end', function (_chunk) {
81+
resolve({ "status": response.statusCode, "body": responseBody });
82+
});
83+
}, function (error) {
84+
reject(error);
85+
});
86+
});
87+
}

functions/es-requests/package.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "es-requests",
3+
"version": "1.0.0"
4+
}

functions/stream-logs/.npmignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tests

0 commit comments

Comments
 (0)