Skip to content

feat: add experimental profiler #528

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 18, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .github/workflows/aws-lambda-java-profiler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Run integration tests for aws-lambda-java-profiler

on:
pull_request:
branches: [ '*' ]
paths:
- 'aws-lambda-java-profiler/**'
- '.github/workflows/aws-lambda-java-profiler.yml'
push:
branches: ['*']
paths:
- 'aws-lambda-java-profiler/**'
- '.github/workflows/aws-lambda-java-profiler.yml'

jobs:

publish:
runs-on: ubuntu-latest

permissions:
id-token: write
contents: read

steps:
- uses: actions/checkout@v4

- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: 21
distribution: corretto

- name: Issue AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ secrets.AWS_REGION_PROFILER_EXTENSION_INTEGRATION_TEST }}
role-to-assume: ${{ secrets.AWS_ROLE_PROFILER_EXTENSION_INTEGRATION_TEST }}
role-session-name: GitHubActionsRunIntegrationTests
role-duration-seconds: 900

- name: Build layer
working-directory: ./experimental/aws-lambda-java-profiler/extension
run: ./build_layer.sh

- name: Publish layer
working-directory: ./experimental/aws-lambda-java-profiler
run: ./integration_tests/publish_layer.sh

- name: Create the bucket layer
working-directory: ./experimental/aws-lambda-java-profiler
run: ./integration_tests/create_bucket.sh

- name: Create Java function
working-directory: ./experimental/aws-lambda-java-profiler
run: ./integration_tests/create_function.sh

- name: Invoke Java function
working-directory: ./experimental/aws-lambda-java-profiler
run: ./integration_tests/invoke_function.sh

- name: Download from s3
working-directory: ./experimental/aws-lambda-java-profiler
run: ./integration_tests/download_from_s3.sh

- name: Upload profiles
uses: actions/upload-artifact@v4
with:
name: profiles
path: /tmp/s3-artifacts

- name: cleanup
if: always()
working-directory: ./experimental/aws-lambda-java-profiler
run: ./integration_tests/cleanup.sh
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,10 @@ dependency-reduced-pom.xml

# snapshot process
aws-lambda-java-runtime-interface-client/pom.xml.versionsBackup

# profiler
experimental/aws-lambda-java-profiler/integration_tests/helloworld/build
experimental/aws-lambda-java-profiler/extension/build/
experimental/aws-lambda-java-profiler/integration_tests/helloworld/bin
!experimental/aws-lambda-java-profiler/extension/gradle/wrapper/*.jar
/scratch/
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,18 @@ See the [README](aws-lambda-java-log4j2/README.md) or the [official documentatio
</dependency>
```

## Lambda Profiler Extension for Java - aws-lambda-java-profiler

<p align="center">
<img src="experimental/aws-lambda-java-profiler/docs/example-cold-start-flame-graph-small.png" alt="A flame graph of a Java Lambda function">
</p>

This project allows you to profile your Java functions invoke by invoke, with high fidelity, and no code changes. It
uses the [async-profiler](https://github.com/async-profiler/async-profiler) project to produce profiling data and
automatically uploads the data as flame graphs to S3.

Follow our [Quick Start](experimental/aws-lambda-java-profiler#quick-start) to profile your functions.

## Java implementation of the Runtime Interface Client API - aws-lambda-java-runtime-interface-client
[![Maven](https://img.shields.io/maven-central/v/com.amazonaws/aws-lambda-java-runtime-interface-client.svg?label=Maven)](https://central.sonatype.com/artifact/com.amazonaws/aws-lambda-java-runtime-interface-client)

Expand Down
3 changes: 3 additions & 0 deletions experimental/aws-lambda-java-profiler/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.zip
/.idea/
/target/
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
113 changes: 113 additions & 0 deletions experimental/aws-lambda-java-profiler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<p align="center">
<img src="docs/Arch_AWS-Lambda_64.svg" alt="AWS Lambda service icon">
</p>

<h2 align="center">AWS Lambda Profiler Extension for Java</h2>

The Lambda profiler extension allows you to profile your Java functions invoke by invoke, with high fidelity, and no
code changes. It uses the [async-profiler](https://github.com/async-profiler/async-profiler) project to produce
profiling data and automatically uploads the data as HTML flame graphs to S3.

<p align="center">
<img src="docs/example-cold-start-flame-graph.png" alt="A flame graph of a Java Lambda function">
</p>

## Current status
**This is an alpha release and not yet ready for production use.** We're especially interested in early feedback on usability, features, performance, and compatibility. Please send feedback by opening a [GitHub issue](https://github.com/aws/aws-lambda-java-libs/issues/new).

The profiler has been tested with Lambda managed runtimes for Java 17 and Java 21.

## How to use the Lambda Profiler

To use the profiler you need to

1. Build the extension in this repo
2. Deploy it as a Lambda Layer and attach the layer to your function
3. Create an S3 bucket for the results, or reuse an existing one
4. Give your function permission to write to the bucket
5. Configure the required environment variables.

The above assumes you're using the ZIP deployment method with managed runtimes. If you deploy your functions as container images instead, you will need to include the profiler in your Dockerfile at `/opt/extensions/` rather than using a Lambda layer.

### Quick Start

The following [Quick Start](#quick-start) gives AWS CLI commands you can run to get started (MacOS/Linux). There are also [examples](examples) using infrastructure as code for you to refer to.

1. Clone the repo

```bash
git clone https://github.com/aws/aws-lambda-java-libs
```

2. Build the extension

```bash
cd aws-lambda-java-libs/experimental/aws-lambda-java-profiler/extension
./build_layer.sh
```

3. Run the `update-function.sh` script which will create a new S3 bucket, Lambda layer and all the configuration required.

```bash
cd ..
./update-function.sh YOUR_FUNCTION_NAME
```

4. Invoke your function and review the flame graph in S3 using your browser.

### Configuration

#### Required Environment Variables

| Name | Value |
|-----------------------------------------|-----------------------------------------------------------------------------------------------|
| AWS_LAMBDA_PROFILER_RESULTS_BUCKET_NAME | Your unique bucket name |
| JAVA_TOOL_OPTIONS | -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -javaagent:/opt/profiler-extension.jar |

#### Optional Environment Variables

| Name | Default Value | Options |
|------------------------------------------|-----------------------------------------------------------|--------------------------------|
| AWS_LAMBDA_PROFILER_START_COMMAND | start,event=wall,interval=1us | |
| AWS_LAMBDA_PROFILER_STOP_COMMAND | stop,file=%s,include=*AWSLambda.main,include=start_thread | file=%s is required |
| AWS_LAMBDA_PROFILER_DEBUG | false | true - to enable debug logging |
| AWS_LAMBDA_PROFILER_COMMUNICATION_PORT | 1234 | a valid port number |

### How does it work?

In `/src` is the code for a Java agent. It's entry point `AgentEntry.premain()` is executed as the runtime starts up.
The environment variable `JAVA_TOOL_OPTIONS` is used to specify which `.jar` file the agent is in. The `MANIFEST.MF` file is used to specify the pre-main class.

When the agent is constructed, it starts the profiler and registers itself as a Lambda extension for `INVOKE` request.

A new thread is created to handle calling `/next` and uploading the results of the profiler to S3. The bucket to upload
the result to is configurable using an environment variable.

### Troubleshooting

- Ensure the Lambda function execution role has the necessary permissions to write to the S3 bucket.
- Verify that the environment variables are set correctly in your Lambda function configuration.
- Check CloudWatch logs for any error messages from the extension.

## Contributing

Contributions to improve the Java profiler extension are welcome. Please see [CONTRIBUTING.md](../../CONTRIBUTING.md) for more information on how to report bugs or submit pull requests.

Issues or contributions to the [async-profiler](https://github.com/async-profiler/async-profiler) itself should be submitted to that project.

### Security

If you discover a potential security issue in this project we ask that you notify AWS Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public GitHub issue.

### Code of conduct

This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). See [CODE_OF_CONDUCT.md](doc/CODE_OF_CONDUCT.md) for more details.

## License

This project is licensed under the [Apache 2.0](../../LICENSE) License. It uses the following projects:

- [async-profiler](https://github.com/async-profiler/async-profiler) (Apache 2.0 license)
- [AWS SDK for Java 2.0](https://github.com/aws/aws-sdk-java-v2) (Apache 2.0 license)
- Other libraries in this repository (Apache 2.0 license)

18 changes: 18 additions & 0 deletions experimental/aws-lambda-java-profiler/docs/Arch_AWS-Lambda_64.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions experimental/aws-lambda-java-profiler/examples/cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.classpath.txt
target
.classpath
.project
.idea
.settings
.vscode
*.iml

# CDK asset staging directory
.cdk.staging
cdk.out

18 changes: 18 additions & 0 deletions experimental/aws-lambda-java-profiler/examples/cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Welcome to your CDK Java project!

This is a blank project for CDK development with Java.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

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.

## Useful commands

* `mvn package` compile and run tests
* `cdk ls` list all stacks in the app
* `cdk synth` emits the synthesized CloudFormation template
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk docs` open CDK documentation

Enjoy!
68 changes: 68 additions & 0 deletions experimental/aws-lambda-java-profiler/examples/cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"app": "mvn -e -q compile exec:java",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"target",
"pom.xml",
"src/test"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false
}
}
Loading