mirror of
https://github.com/swift-server/swift-aws-lambda-runtime.git
synced 2026-05-03 07:22:27 +00:00
Add scripts to measure cold start performance (#604)
I added two shell scripts under `scripts/lambda-performance` to help measure Lambda cold and warm start time. Co-authored-by: Sebastien Stormacq <stormacq@amazon.lu>
This commit is contained in:
committed by
GitHub
parent
85bd29e4de
commit
0305cb31b8
@@ -0,0 +1,470 @@
|
||||
# AWS Lambda Startup Time Measurement Scripts
|
||||
|
||||
A set of simple bash scripts to measure AWS Lambda cold start and warm start times. These scripts deploy a Lambda function, invoke it multiple times, retrieve execution metrics from CloudWatch Logs (the authoritative source), and output statistical results.
|
||||
|
||||
## Overview
|
||||
|
||||
This toolkit provides two measurement scripts:
|
||||
|
||||
- **measure-cold-start.sh** - Measures cold start times by forcing a new execution environment between invocations
|
||||
- **measure-warm-start.sh** - Measures warm start times by reusing the same execution environment
|
||||
|
||||
Both scripts follow the KISS (Keep It Simple, Stupid) principle with minimal dependencies and straightforward implementations.
|
||||
|
||||
**Note:** Lambda functions are deployed with arm64 architecture by default.
|
||||
|
||||
## Purpose
|
||||
|
||||
Understanding Lambda startup performance is critical for optimizing serverless applications. These scripts help you:
|
||||
|
||||
- Measure cold start initialization times when Lambda creates a new execution environment
|
||||
- Measure warm start execution times when Lambda reuses an existing environment
|
||||
- Gather statistically valid datasets through multiple iterations
|
||||
- Compare performance across different runtimes, configurations, or code changes
|
||||
|
||||
## Dependencies
|
||||
|
||||
The following tools must be installed and available in your PATH:
|
||||
|
||||
- **AWS CLI v2** - For Lambda and CloudWatch Logs operations
|
||||
- Installation: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
|
||||
- Must be configured with valid credentials (`aws configure`)
|
||||
|
||||
- **jq** - For JSON parsing
|
||||
- Installation: `brew install jq` (macOS) or `apt-get install jq` (Linux)
|
||||
|
||||
- **bc** - For floating-point arithmetic in statistics calculations
|
||||
- Usually pre-installed on most systems
|
||||
- Installation: `brew install bc` (macOS) or `apt-get install bc` (Linux)
|
||||
|
||||
- **grep** with Perl regex support - For log parsing
|
||||
- Usually pre-installed on most systems
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before running the scripts, ensure you have:
|
||||
|
||||
1. **AWS Credentials** - Configured via `aws configure` or environment variables
|
||||
2. **IAM Role** - An IAM role with Lambda execution permissions (ARN required)
|
||||
3. **Lambda Deployment Package** - A ZIP file containing your Lambda function code
|
||||
4. **Permissions** - Your AWS credentials must have permissions to:
|
||||
- Create/update Lambda functions
|
||||
- Invoke Lambda functions
|
||||
- Read CloudWatch Logs
|
||||
|
||||
## Command-Line Parameters
|
||||
|
||||
### Required Parameters
|
||||
|
||||
Both scripts require these parameters:
|
||||
|
||||
| Parameter | Description | Example |
|
||||
|-----------|-------------|---------|
|
||||
| `--zip-file <path>` | Path to Lambda deployment package (ZIP file) | `--zip-file ./my-function.zip` |
|
||||
| `--role-arn <arn>` | IAM role ARN for Lambda execution | `--role-arn arn:aws:iam::123456789012:role/lambda-role` |
|
||||
|
||||
### Optional Parameters
|
||||
|
||||
| Parameter | Description | Default Value |
|
||||
|-----------|-------------|---------------|
|
||||
| `--runtime <runtime>` | Lambda runtime environment | `provided.al2023` |
|
||||
| `--iterations <n>` | Number of measurements to collect | `10` |
|
||||
| `--event-file <path>` | Path to JSON event payload file | Simple string: "Performance test" |
|
||||
| `--function-name <name>` | Lambda function name | `lambda-perf-test` |
|
||||
| `--handler <handler>` | Lambda handler | `bootstrap` |
|
||||
|
||||
### Default Values
|
||||
|
||||
- **Runtime**: `provided.al2023` - AWS Lambda custom runtime
|
||||
- **Iterations**: `10` - Provides a reasonable sample size for statistical analysis
|
||||
- **Function Name**: `lambda-perf-test` - Automatically generated name
|
||||
- **Handler**: `bootstrap` - Default for custom runtimes
|
||||
- **Event Payload**: `"Performance test"` - Simple string payload
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Cold Start Measurement
|
||||
|
||||
Measure cold start times with default settings (10 iterations):
|
||||
|
||||
```bash
|
||||
./measure-cold-start.sh \
|
||||
--zip-file ./my-function.zip \
|
||||
--role-arn arn:aws:iam::123456789012:role/lambda-role
|
||||
```
|
||||
|
||||
### Cold Start with Custom Configuration
|
||||
|
||||
Measure cold start times with custom runtime and more iterations:
|
||||
|
||||
```bash
|
||||
./measure-cold-start.sh \
|
||||
--zip-file ./my-function.zip \
|
||||
--role-arn arn:aws:iam::123456789012:role/lambda-role \
|
||||
--runtime provided.al2023 \
|
||||
--iterations 15 \
|
||||
--function-name my-perf-test
|
||||
```
|
||||
|
||||
### Cold Start with Custom Event Payload
|
||||
|
||||
Use a JSON file for the invocation payload:
|
||||
|
||||
```bash
|
||||
./measure-cold-start.sh \
|
||||
--zip-file ./my-function.zip \
|
||||
--role-arn arn:aws:iam::123456789012:role/lambda-role \
|
||||
--event-file ./example-event.json \
|
||||
--iterations 20
|
||||
```
|
||||
|
||||
### Basic Warm Start Measurement
|
||||
|
||||
Measure warm start times with default settings:
|
||||
|
||||
```bash
|
||||
./measure-warm-start.sh \
|
||||
--zip-file ./my-function.zip \
|
||||
--role-arn arn:aws:iam::123456789012:role/lambda-role
|
||||
```
|
||||
|
||||
### Warm Start with Custom Configuration
|
||||
|
||||
Measure warm start times with more iterations:
|
||||
|
||||
```bash
|
||||
./measure-warm-start.sh \
|
||||
--zip-file ./my-function.zip \
|
||||
--role-arn arn:aws:iam::123456789012:role/lambda-role \
|
||||
--runtime provided.al2023 \
|
||||
--iterations 25 \
|
||||
--event-file ./custom-event.json
|
||||
```
|
||||
|
||||
### Comparing Cold vs Warm Starts
|
||||
|
||||
Run both scripts with the same configuration to compare:
|
||||
|
||||
```bash
|
||||
# Measure cold starts
|
||||
./measure-cold-start.sh \
|
||||
--zip-file ./my-function.zip \
|
||||
--role-arn arn:aws:iam::123456789012:role/lambda-role \
|
||||
--iterations 10
|
||||
|
||||
# Measure warm starts
|
||||
./measure-warm-start.sh \
|
||||
--zip-file ./my-function.zip \
|
||||
--role-arn arn:aws:iam::123456789012:role/lambda-role \
|
||||
--iterations 10
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
### Cold Start Measurement Output
|
||||
|
||||
```
|
||||
=== Cold Start Measurement Configuration ===
|
||||
Function Name: lambda-perf-test
|
||||
ZIP File: ./my-function.zip
|
||||
Runtime: provided.al2023
|
||||
Handler: bootstrap
|
||||
Iterations: 10
|
||||
Role ARN: arn:aws:iam::123456789012:role/lambda-role
|
||||
|
||||
Deploying function: lambda-perf-test
|
||||
Function exists, updating code...
|
||||
Waiting for function to be active...
|
||||
Function deployed successfully
|
||||
|
||||
=== Starting Cold Start Measurements ===
|
||||
Iteration 1/10
|
||||
Request ID: abc-123-def-456
|
||||
Duration: 245.67ms
|
||||
Forcing cold start...
|
||||
|
||||
Iteration 2/10
|
||||
Request ID: ghi-789-jkl-012
|
||||
Duration: 198.34ms
|
||||
Forcing cold start...
|
||||
|
||||
Iteration 3/10
|
||||
Request ID: mno-345-pqr-678
|
||||
Duration: 223.45ms
|
||||
Forcing cold start...
|
||||
|
||||
...
|
||||
|
||||
=== Cold Start Measurement Results ===
|
||||
Individual measurements:
|
||||
Measurement 1: 245.67ms
|
||||
Measurement 2: 198.34ms
|
||||
Measurement 3: 223.45ms
|
||||
Measurement 4: 267.89ms
|
||||
Measurement 5: 201.23ms
|
||||
Measurement 6: 234.56ms
|
||||
Measurement 7: 189.12ms
|
||||
Measurement 8: 256.78ms
|
||||
Measurement 9: 212.34ms
|
||||
Measurement 10: 225.67ms
|
||||
|
||||
=== Statistics ===
|
||||
Count: 10
|
||||
Average: 225.51ms
|
||||
Min: 189.12ms
|
||||
Max: 267.89ms
|
||||
```
|
||||
|
||||
### Warm Start Measurement Output
|
||||
|
||||
```
|
||||
=== Warm Start Measurement Configuration ===
|
||||
Function Name: lambda-perf-test
|
||||
ZIP File: ./my-function.zip
|
||||
Runtime: provided.al2023
|
||||
Handler: bootstrap
|
||||
Iterations: 10
|
||||
Role ARN: arn:aws:iam::123456789012:role/lambda-role
|
||||
|
||||
Deploying function: lambda-perf-test
|
||||
Function exists, updating code...
|
||||
Waiting for function to be active...
|
||||
Function deployed successfully
|
||||
|
||||
=== Starting Warm Start Measurements ===
|
||||
Iteration 1/10
|
||||
Request ID: stu-901-vwx-234
|
||||
Duration: 12.45ms
|
||||
|
||||
Iteration 2/10
|
||||
Request ID: yza-567-bcd-890
|
||||
Duration: 8.23ms
|
||||
|
||||
Iteration 3/10
|
||||
Request ID: efg-123-hij-456
|
||||
Duration: 9.67ms
|
||||
|
||||
...
|
||||
|
||||
=== Warm Start Measurement Results ===
|
||||
Individual measurements:
|
||||
Measurement 1: 12.45ms
|
||||
Measurement 2: 8.23ms
|
||||
Measurement 3: 9.67ms
|
||||
Measurement 4: 11.34ms
|
||||
Measurement 5: 7.89ms
|
||||
Measurement 6: 10.12ms
|
||||
Measurement 7: 8.56ms
|
||||
Measurement 8: 9.23ms
|
||||
Measurement 9: 10.78ms
|
||||
Measurement 10: 8.91ms
|
||||
|
||||
=== Statistics ===
|
||||
Count: 10
|
||||
Average: 9.72ms
|
||||
Min: 7.89ms
|
||||
Max: 12.45ms
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Cold Start Measurement Process
|
||||
|
||||
1. **Deploy Function** - Creates or updates the Lambda function with your ZIP file (using arm64 architecture)
|
||||
2. **Invoke Function** - Calls the Lambda function via AWS CLI
|
||||
3. **Capture Invocation ID** - Extracts the request ID from the invocation response
|
||||
4. **Retrieve Metrics** - Queries CloudWatch Logs using the invocation ID
|
||||
5. **Parse Duration** - Extracts the duration from the REPORT log line
|
||||
6. **Force Cold Start** - Updates an environment variable to force a new execution environment
|
||||
7. **Repeat** - Continues for the specified number of iterations
|
||||
8. **Calculate Statistics** - Computes average, min, and max from all measurements
|
||||
|
||||
### Warm Start Measurement Process
|
||||
|
||||
1. **Deploy Function** - Creates or updates the Lambda function with your ZIP file (using arm64 architecture)
|
||||
2. **Invoke Function** - Calls the Lambda function via AWS CLI
|
||||
3. **Capture Invocation ID** - Extracts the request ID from the invocation response
|
||||
4. **Retrieve Metrics** - Queries CloudWatch Logs using the invocation ID
|
||||
5. **Parse Duration** - Extracts the duration from the REPORT log line
|
||||
6. **Repeat** - Continues for the specified number of iterations (without forcing cold starts)
|
||||
7. **Calculate Statistics** - Computes average, min, and max from all measurements
|
||||
|
||||
### CloudWatch Logs as Source of Truth
|
||||
|
||||
The scripts use CloudWatch Logs as the authoritative source for execution times because:
|
||||
|
||||
- CloudWatch Logs contain the official AWS-recorded execution duration
|
||||
- The REPORT log line includes precise timing information
|
||||
- Invocation IDs ensure correct correlation between invocations and log entries
|
||||
- This approach is more reliable than client-side timing
|
||||
|
||||
### Cold Start Forcing Mechanism
|
||||
|
||||
The cold start script forces new execution environments by:
|
||||
|
||||
1. Updating the Lambda function's environment variables with a timestamp
|
||||
2. Waiting for the configuration update to complete
|
||||
3. Adding a 2-second delay to ensure the environment is recycled
|
||||
4. The next invocation will use a fresh execution environment (cold start)
|
||||
|
||||
## Performance Expectations
|
||||
|
||||
### Typical Execution Times
|
||||
|
||||
- **Cold Start Measurement** (10 iterations): ~60-100 seconds
|
||||
- Each iteration: ~6-10 seconds (invocation + log retrieval + cold start forcing)
|
||||
|
||||
- **Warm Start Measurement** (10 iterations): ~30-50 seconds
|
||||
- Each iteration: ~3-5 seconds (invocation + log retrieval)
|
||||
|
||||
### CloudWatch Logs Latency
|
||||
|
||||
- Logs typically appear in CloudWatch within 1-3 seconds
|
||||
- The scripts implement retry logic (up to 10 attempts with 1-second delays)
|
||||
- Initial 2-second wait before querying logs
|
||||
|
||||
## Event Payload
|
||||
|
||||
### Using the Default Payload
|
||||
|
||||
If no `--event-file` is specified, the scripts use a simple string payload: `"Performance test"`
|
||||
|
||||
### Using a Custom Event File
|
||||
|
||||
Create a JSON file with your desired payload structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Hello World",
|
||||
"timestamp": "2024-01-01T00:00:00Z",
|
||||
"userId": "user-123",
|
||||
"data": {
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then reference it with `--event-file`:
|
||||
|
||||
```bash
|
||||
./measure-cold-start.sh \
|
||||
--zip-file ./my-function.zip \
|
||||
--role-arn arn:aws:iam::123456789012:role/lambda-role \
|
||||
--event-file ./my-event.json
|
||||
```
|
||||
|
||||
### Example Event File
|
||||
|
||||
An `example-event.json` file is provided as a template:
|
||||
|
||||
```json
|
||||
{
|
||||
"_comment": "Example event payload for Lambda function invocation",
|
||||
"_usage": "Use this file with --event-file parameter or modify for your specific Lambda function",
|
||||
"message": "Hello World",
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
lambda-measurement/
|
||||
├── measure-cold-start.sh # Cold start measurement script
|
||||
├── measure-warm-start.sh # Warm start measurement script
|
||||
├── shared-utils.sh # Shared utility functions
|
||||
├── example-event.json # Example event payload
|
||||
├── README.md # This documentation
|
||||
└── test/ # Test directory (optional)
|
||||
├── test_statistics.bats # Unit tests
|
||||
└── test_properties.py # Property-based tests
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### AWS CLI Not Found
|
||||
|
||||
**Error**: `ERROR: AWS CLI is not installed or not in PATH`
|
||||
|
||||
**Solution**: Install AWS CLI v2 from https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
|
||||
|
||||
### Missing Dependencies
|
||||
|
||||
**Error**: `ERROR: jq is not installed or not in PATH` or `ERROR: bc is not installed or not in PATH`
|
||||
|
||||
**Solution**: Install the missing tool:
|
||||
- macOS: `brew install jq bc`
|
||||
- Linux: `apt-get install jq bc` or `yum install jq bc`
|
||||
|
||||
### ZIP File Not Found
|
||||
|
||||
**Error**: `ERROR: ZIP file not found: ./my-function.zip`
|
||||
|
||||
**Solution**: Verify the path to your ZIP file is correct and the file exists
|
||||
|
||||
### IAM Role Permissions
|
||||
|
||||
**Error**: Function deployment fails with permission errors
|
||||
|
||||
**Solution**: Ensure your IAM role has the following permissions:
|
||||
- `lambda:CreateFunction`
|
||||
- `lambda:UpdateFunctionCode`
|
||||
- `lambda:UpdateFunctionConfiguration`
|
||||
- `lambda:InvokeFunction`
|
||||
- `lambda:GetFunction`
|
||||
- `logs:FilterLogEvents`
|
||||
|
||||
### CloudWatch Logs Not Available
|
||||
|
||||
**Error**: `ERROR: Could not retrieve logs for request <id>`
|
||||
|
||||
**Solution**:
|
||||
- Logs may take longer than expected to appear
|
||||
- Check that your Lambda function has CloudWatch Logs permissions
|
||||
- Verify the log group `/aws/lambda/<function-name>` exists
|
||||
- Increase the retry attempts in `get_duration_from_logs()` if needed
|
||||
|
||||
### Failed Invocations
|
||||
|
||||
**Warning**: `WARNING: Failed to get duration for iteration N, skipping`
|
||||
|
||||
**Solution**:
|
||||
- Check Lambda function logs for errors
|
||||
- Verify your function code is working correctly
|
||||
- Ensure the event payload is compatible with your function
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Run Multiple Iterations** - Use at least 10 iterations for statistically valid results
|
||||
2. **Consistent Configuration** - Use the same parameters when comparing measurements
|
||||
3. **Warm Up First** - For warm start measurements, the first invocation may be slower
|
||||
4. **Clean Environment** - Delete test functions after measurements to avoid costs
|
||||
5. **Monitor Costs** - Lambda invocations and CloudWatch Logs queries incur AWS charges
|
||||
6. **Test Realistic Payloads** - Use event payloads that match your production workload
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Minimal Error Handling** - Scripts follow the KISS principle with basic error handling
|
||||
- **Sequential Execution** - Measurements are performed sequentially, not in parallel
|
||||
- **CloudWatch Latency** - Log retrieval adds 2-3 seconds per measurement
|
||||
- **Environment Variable Limit** - Cold start forcing via environment variables has AWS limits
|
||||
- **No Concurrent Invocations** - Scripts don't test concurrent execution scenarios
|
||||
|
||||
## Contributing
|
||||
|
||||
These scripts are designed to be simple and easy to modify. Feel free to:
|
||||
|
||||
- Adjust retry logic in `get_duration_from_logs()`
|
||||
- Modify default values in `parse_arguments()`
|
||||
- Add additional statistics calculations
|
||||
- Implement alternative cold start forcing mechanisms
|
||||
|
||||
## License
|
||||
|
||||
This project is provided as-is for measuring AWS Lambda performance.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [AWS Lambda Documentation](https://docs.aws.amazon.com/lambda/)
|
||||
- [AWS Lambda Cold Starts](https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/)
|
||||
- [CloudWatch Logs Documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/)
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## This source file is part of the SwiftAWSLambdaRuntime open source project
|
||||
##
|
||||
## Copyright SwiftAWSLambdaRuntime project authors
|
||||
## Copyright (c) Amazon.com, Inc. or its affiliates.
|
||||
## Licensed under Apache License v2.0
|
||||
##
|
||||
## See LICENSE.txt for license information
|
||||
## See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
|
||||
##
|
||||
## SPDX-License-Identifier: Apache-2.0
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
|
||||
set -e
|
||||
|
||||
# Cold Start Measurement Script
|
||||
# Measures Lambda cold start times (Init Duration) by forcing new execution environments
|
||||
|
||||
# Source shared utility functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=scripts/lambda-performance/shared-utils.sh
|
||||
source "$SCRIPT_DIR/shared-utils.sh"
|
||||
|
||||
# Parse command-line arguments
|
||||
parse_arguments "$@"
|
||||
|
||||
# Validate required parameters and dependencies
|
||||
if ! validate_parameters; then
|
||||
echo ""
|
||||
echo "Usage: $0 --zip-file <path> --role-arn <arn> [options]"
|
||||
echo ""
|
||||
echo "Required:"
|
||||
echo " --zip-file <path> Path to Lambda deployment package (ZIP file)"
|
||||
echo " --role-arn <arn> IAM role ARN for Lambda execution"
|
||||
echo ""
|
||||
echo "Optional:"
|
||||
echo " --runtime <runtime> Lambda runtime (default: provided.al2023)"
|
||||
echo " --iterations <n> Number of measurements (default: 10)"
|
||||
echo " --event-file <path> Path to JSON event payload file"
|
||||
echo " --function-name <name> Lambda function name (default: lambda-perf-test)"
|
||||
echo " --handler <handler> Lambda handler (default: bootstrap)"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Cold Start Measurement ==="
|
||||
echo "Function: $FUNCTION_NAME"
|
||||
echo "Runtime: $RUNTIME"
|
||||
echo "Iterations: $ITERATIONS"
|
||||
echo ""
|
||||
|
||||
# Deploy Lambda function
|
||||
deploy_function "$FUNCTION_NAME" "$ZIP_FILE" "$RUNTIME" "$HANDLER" "$ROLE_ARN"
|
||||
|
||||
echo ""
|
||||
echo "=== Starting Measurements ==="
|
||||
echo ""
|
||||
|
||||
# Array to store measurements
|
||||
measurements=()
|
||||
|
||||
# Measurement loop for N iterations
|
||||
for i in $(seq 1 "$ITERATIONS"); do
|
||||
echo "Iteration $i/$ITERATIONS"
|
||||
|
||||
# Invoke function with metric_type="cold" to get Init Duration
|
||||
duration=$(invoke_and_get_duration "$FUNCTION_NAME" "$EVENT_PAYLOAD" "cold")
|
||||
|
||||
if [ "$duration" == "0" ] || [ -z "$duration" ]; then
|
||||
echo " WARNING: Failed to get Init Duration, skipping measurement" >&2
|
||||
else
|
||||
echo " Init Duration: ${duration}ms"
|
||||
measurements+=("$duration")
|
||||
fi
|
||||
|
||||
# Force cold start for next iteration (except on last iteration)
|
||||
if [ "$i" -lt "$ITERATIONS" ]; then
|
||||
echo " Forcing cold start..."
|
||||
force_cold_start "$FUNCTION_NAME"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
# Calculate and display statistics
|
||||
echo "=== Cold Start Results ==="
|
||||
calculate_statistics "${measurements[@]}"
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## This source file is part of the SwiftAWSLambdaRuntime open source project
|
||||
##
|
||||
## Copyright SwiftAWSLambdaRuntime project authors
|
||||
## Copyright (c) Amazon.com, Inc. or its affiliates.
|
||||
## Licensed under Apache License v2.0
|
||||
##
|
||||
## See LICENSE.txt for license information
|
||||
## See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
|
||||
##
|
||||
## SPDX-License-Identifier: Apache-2.0
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
|
||||
set -e
|
||||
|
||||
# Warm Start Measurement Script
|
||||
# Measures Lambda warm start times (Duration) by reusing the same execution environment
|
||||
|
||||
# Source shared utility functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=scripts/lambda-performance/shared-utils.sh
|
||||
source "$SCRIPT_DIR/shared-utils.sh"
|
||||
|
||||
# Parse command-line arguments
|
||||
parse_arguments "$@"
|
||||
|
||||
# Validate required parameters and dependencies
|
||||
if ! validate_parameters; then
|
||||
echo ""
|
||||
echo "Usage: $0 --zip-file <path> --role-arn <arn> [options]"
|
||||
echo ""
|
||||
echo "Required:"
|
||||
echo " --zip-file <path> Path to Lambda deployment package (ZIP file)"
|
||||
echo " --role-arn <arn> IAM role ARN for Lambda execution"
|
||||
echo ""
|
||||
echo "Optional:"
|
||||
echo " --runtime <runtime> Lambda runtime (default: provided.al2023)"
|
||||
echo " --iterations <n> Number of measurements (default: 10)"
|
||||
echo " --event-file <path> Path to JSON event payload file"
|
||||
echo " --function-name <name> Lambda function name (default: lambda-perf-test)"
|
||||
echo " --handler <handler> Lambda handler (default: bootstrap)"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Warm Start Measurement ==="
|
||||
echo "Function: $FUNCTION_NAME"
|
||||
echo "Runtime: $RUNTIME"
|
||||
echo "Iterations: $ITERATIONS"
|
||||
echo ""
|
||||
|
||||
# Deploy Lambda function
|
||||
deploy_function "$FUNCTION_NAME" "$ZIP_FILE" "$RUNTIME" "$HANDLER" "$ROLE_ARN"
|
||||
|
||||
echo ""
|
||||
echo "=== Warming Up Function ==="
|
||||
echo "Making initial invocation to warm up execution environment..."
|
||||
echo ""
|
||||
|
||||
# Make initial warm-up call (this will be a cold start, so we discard it)
|
||||
warmup_duration=$(invoke_and_get_duration "$FUNCTION_NAME" "$EVENT_PAYLOAD" "warm")
|
||||
if [ "$warmup_duration" != "0" ] && [ -n "$warmup_duration" ]; then
|
||||
echo "Warm-up complete (Duration: ${warmup_duration}ms)"
|
||||
else
|
||||
echo "WARNING: Warm-up invocation completed but duration not captured"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Starting Measurements ==="
|
||||
echo ""
|
||||
|
||||
# Array to store measurements
|
||||
measurements=()
|
||||
|
||||
# Measurement loop for N iterations (all should be warm starts now)
|
||||
for i in $(seq 1 "$ITERATIONS"); do
|
||||
echo "Iteration $i/$ITERATIONS"
|
||||
|
||||
# Invoke function with metric_type="warm" to get Duration (no cold start forcing)
|
||||
duration=$(invoke_and_get_duration "$FUNCTION_NAME" "$EVENT_PAYLOAD" "warm")
|
||||
|
||||
if [ "$duration" == "0" ] || [ -z "$duration" ]; then
|
||||
echo " WARNING: Failed to get Duration, skipping measurement" >&2
|
||||
else
|
||||
echo " Duration: ${duration}ms"
|
||||
measurements+=("$duration")
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
# Calculate and display statistics
|
||||
echo "=== Warm Start Results ==="
|
||||
calculate_statistics "${measurements[@]}"
|
||||
Executable
+253
@@ -0,0 +1,253 @@
|
||||
#!/bin/bash
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## This source file is part of the SwiftAWSLambdaRuntime open source project
|
||||
##
|
||||
## Copyright SwiftAWSLambdaRuntime project authors
|
||||
## Copyright (c) Amazon.com, Inc. or its affiliates.
|
||||
## Licensed under Apache License v2.0
|
||||
##
|
||||
## See LICENSE.txt for license information
|
||||
## See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
|
||||
##
|
||||
## SPDX-License-Identifier: Apache-2.0
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
|
||||
# Shared utility functions for Lambda measurement scripts
|
||||
|
||||
# Deploy or update a Lambda function
|
||||
# Arguments: function_name, zip_file, runtime, handler, role_arn
|
||||
deploy_function() {
|
||||
local function_name=$1
|
||||
local zip_file=$2
|
||||
local runtime=$3
|
||||
local handler=$4
|
||||
local role_arn=$5
|
||||
|
||||
echo "Deploying function: $function_name"
|
||||
|
||||
# Check if function exists
|
||||
if aws lambda get-function --function-name "$function_name" &>/dev/null; then
|
||||
echo "Function exists, updating code..."
|
||||
# Update existing function
|
||||
aws lambda update-function-code \
|
||||
--function-name "$function_name" \
|
||||
--zip-file "fileb://$zip_file" \
|
||||
> /dev/null
|
||||
else
|
||||
echo "Function does not exist, creating..."
|
||||
# Create new function with arm64 architecture
|
||||
aws lambda create-function \
|
||||
--function-name "$function_name" \
|
||||
--runtime "$runtime" \
|
||||
--role "$role_arn" \
|
||||
--handler "$handler" \
|
||||
--architectures arm64 \
|
||||
--zip-file "fileb://$zip_file" \
|
||||
> /dev/null
|
||||
fi
|
||||
|
||||
# Wait for function to be active
|
||||
echo "Waiting for function to be active..."
|
||||
aws lambda wait function-active --function-name "$function_name"
|
||||
echo "Function deployed successfully"
|
||||
}
|
||||
|
||||
# Invoke Lambda function and extract duration from LogResult
|
||||
# Arguments: function_name, event_payload, metric_type ("cold" or "warm")
|
||||
# Returns: duration in milliseconds
|
||||
invoke_and_get_duration() {
|
||||
local function_name=$1
|
||||
local event_payload=$2
|
||||
local metric_type=$3 # "cold" or "warm"
|
||||
local output_file="/tmp/lambda-response-$.json"
|
||||
|
||||
# Invoke function with --log-type Tail to get logs in response
|
||||
local encoded_payload
|
||||
encoded_payload=$(echo "$event_payload" | base64)
|
||||
aws lambda invoke \
|
||||
--function-name "$function_name" \
|
||||
--payload "$encoded_payload" \
|
||||
--log-type Tail \
|
||||
"$output_file" > /tmp/invoke-response-$.json 2>/dev/null
|
||||
|
||||
# Extract and decode LogResult (base64-encoded logs)
|
||||
local log_result
|
||||
log_result=$(jq -r '.LogResult // empty' /tmp/invoke-response-$.json 2>/dev/null | base64 -d 2>/dev/null)
|
||||
|
||||
# Parse the REPORT line to extract timing using sed (BSD-compatible)
|
||||
local duration=""
|
||||
if [ "$metric_type" == "cold" ]; then
|
||||
# Extract Init Duration for cold starts
|
||||
duration=$(echo "$log_result" | grep "^REPORT" | sed -n 's/.*Init Duration: \([0-9.]*\) ms.*/\1/p' | head -1)
|
||||
else
|
||||
# Extract Duration for warm starts (match first Duration, not Init Duration)
|
||||
duration=$(echo "$log_result" | grep "^REPORT" | sed -n 's/.*Duration: \([0-9.]*\) ms.*/\1/p' | head -1)
|
||||
fi
|
||||
|
||||
# Clean up temp files
|
||||
rm -f "$output_file" /tmp/invoke-response-$.json
|
||||
|
||||
# Return duration or 0 if not found
|
||||
if [ -z "$duration" ]; then
|
||||
echo "0"
|
||||
else
|
||||
echo "$duration"
|
||||
fi
|
||||
}
|
||||
|
||||
# Force a cold start by updating environment variable
|
||||
# Arguments: function_name
|
||||
force_cold_start() {
|
||||
local function_name=$1
|
||||
local timestamp
|
||||
timestamp=$(date +%s)
|
||||
|
||||
# Update environment variable to force new execution environment
|
||||
aws lambda update-function-configuration \
|
||||
--function-name "$function_name" \
|
||||
--environment "Variables={FORCE_COLD_START=$timestamp}" \
|
||||
> /dev/null 2>&1
|
||||
|
||||
# Wait for update to complete
|
||||
aws lambda wait function-updated --function-name "$function_name"
|
||||
|
||||
# Additional wait to ensure environment is recycled
|
||||
sleep 2
|
||||
}
|
||||
|
||||
# Calculate and display statistics for measurements
|
||||
# Arguments: array of duration measurements
|
||||
calculate_statistics() {
|
||||
local measurements=("$@")
|
||||
local count=${#measurements[@]}
|
||||
|
||||
if [ "$count" -eq 0 ]; then
|
||||
echo "No measurements to calculate"
|
||||
return
|
||||
fi
|
||||
|
||||
local sum=0
|
||||
local min=${measurements[0]}
|
||||
local max=${measurements[0]}
|
||||
|
||||
for duration in "${measurements[@]}"; do
|
||||
sum=$(echo "$sum + $duration" | bc)
|
||||
if (( $(echo "$duration < $min" | bc -l) )); then
|
||||
min=$duration
|
||||
fi
|
||||
if (( $(echo "$duration > $max" | bc -l) )); then
|
||||
max=$duration
|
||||
fi
|
||||
done
|
||||
|
||||
local avg
|
||||
avg=$(echo "scale=2; $sum / $count" | bc)
|
||||
|
||||
echo ""
|
||||
echo "=== Statistics ==="
|
||||
echo " Count: $count"
|
||||
echo " Average: ${avg}ms"
|
||||
echo " Min: ${min}ms"
|
||||
echo " Max: ${max}ms"
|
||||
}
|
||||
|
||||
# Parse command-line arguments
|
||||
# Sets global variables for script configuration
|
||||
parse_arguments() {
|
||||
# Default values
|
||||
RUNTIME="${RUNTIME:-provided.al2023}"
|
||||
ITERATIONS="${ITERATIONS:-10}"
|
||||
FUNCTION_NAME="${FUNCTION_NAME:-lambda-perf-test}"
|
||||
HANDLER="${HANDLER:-bootstrap}"
|
||||
# a simple String payload to use with the HelloWorld function
|
||||
EVENT_PAYLOAD="${EVENT_PAYLOAD:-\"Performance test\"}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--zip-file)
|
||||
ZIP_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--runtime)
|
||||
RUNTIME="$2"
|
||||
shift 2
|
||||
;;
|
||||
--iterations)
|
||||
ITERATIONS="$2"
|
||||
shift 2
|
||||
;;
|
||||
--event-file)
|
||||
EVENT_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--function-name)
|
||||
FUNCTION_NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
--handler)
|
||||
HANDLER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--role-arn)
|
||||
ROLE_ARN="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Validate required parameters and dependencies
|
||||
validate_parameters() {
|
||||
local errors=0
|
||||
|
||||
# Check required parameters
|
||||
if [ -z "$ZIP_FILE" ]; then
|
||||
echo "ERROR: --zip-file is required" >&2
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
if [ -z "$ROLE_ARN" ]; then
|
||||
echo "ERROR: --role-arn is required" >&2
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
# Validate ZIP file exists
|
||||
if [ -n "$ZIP_FILE" ] && [ ! -f "$ZIP_FILE" ]; then
|
||||
echo "ERROR: ZIP file not found: $ZIP_FILE" >&2
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
# Handle event payload from file or use default
|
||||
if [ -n "$EVENT_FILE" ]; then
|
||||
if [ ! -f "$EVENT_FILE" ]; then
|
||||
echo "ERROR: Event file not found: $EVENT_FILE" >&2
|
||||
errors=$((errors + 1))
|
||||
else
|
||||
EVENT_PAYLOAD=$(cat "$EVENT_FILE")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for required tools
|
||||
if ! command -v aws &> /dev/null; then
|
||||
echo "ERROR: AWS CLI is not installed or not in PATH" >&2
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "ERROR: jq is not installed or not in PATH" >&2
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
if ! command -v bc &> /dev/null; then
|
||||
echo "ERROR: bc is not installed or not in PATH" >&2
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
return $errors
|
||||
}
|
||||
Reference in New Issue
Block a user