From 45135b4e8f85bb62088c8477867366729595f5e9 Mon Sep 17 00:00:00 2001 From: Sebastien Stormacq Date: Wed, 19 Nov 2025 20:16:35 +0100 Subject: [PATCH] add scripts to measure cold start performance --- scripts/lambda-performance/README.md | 470 ++++++++++++++++++ .../lambda-performance/measure-cold-start.sh | 90 ++++ .../lambda-performance/measure-warm-start.sh | 97 ++++ scripts/lambda-performance/shared-utils.sh | 253 ++++++++++ ...ance_test.sh => local_performance_test.sh} | 0 5 files changed, 910 insertions(+) create mode 100644 scripts/lambda-performance/README.md create mode 100755 scripts/lambda-performance/measure-cold-start.sh create mode 100755 scripts/lambda-performance/measure-warm-start.sh create mode 100755 scripts/lambda-performance/shared-utils.sh rename scripts/{performance_test.sh => local_performance_test.sh} (100%) diff --git a/scripts/lambda-performance/README.md b/scripts/lambda-performance/README.md new file mode 100644 index 00000000..a17050f5 --- /dev/null +++ b/scripts/lambda-performance/README.md @@ -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 to Lambda deployment package (ZIP file) | `--zip-file ./my-function.zip` | +| `--role-arn ` | IAM role ARN for Lambda execution | `--role-arn arn:aws:iam::123456789012:role/lambda-role` | + +### Optional Parameters + +| Parameter | Description | Default Value | +|-----------|-------------|---------------| +| `--runtime ` | Lambda runtime environment | `provided.al2023` | +| `--iterations ` | Number of measurements to collect | `10` | +| `--event-file ` | Path to JSON event payload file | Simple string: "Performance test" | +| `--function-name ` | Lambda function name | `lambda-perf-test` | +| `--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 ` + +**Solution**: +- Logs may take longer than expected to appear +- Check that your Lambda function has CloudWatch Logs permissions +- Verify the log group `/aws/lambda/` 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/) diff --git a/scripts/lambda-performance/measure-cold-start.sh b/scripts/lambda-performance/measure-cold-start.sh new file mode 100755 index 00000000..f79e08c0 --- /dev/null +++ b/scripts/lambda-performance/measure-cold-start.sh @@ -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 --role-arn [options]" + echo "" + echo "Required:" + echo " --zip-file Path to Lambda deployment package (ZIP file)" + echo " --role-arn IAM role ARN for Lambda execution" + echo "" + echo "Optional:" + echo " --runtime Lambda runtime (default: provided.al2023)" + echo " --iterations Number of measurements (default: 10)" + echo " --event-file Path to JSON event payload file" + echo " --function-name Lambda function name (default: lambda-perf-test)" + echo " --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[@]}" diff --git a/scripts/lambda-performance/measure-warm-start.sh b/scripts/lambda-performance/measure-warm-start.sh new file mode 100755 index 00000000..f3a7a738 --- /dev/null +++ b/scripts/lambda-performance/measure-warm-start.sh @@ -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 --role-arn [options]" + echo "" + echo "Required:" + echo " --zip-file Path to Lambda deployment package (ZIP file)" + echo " --role-arn IAM role ARN for Lambda execution" + echo "" + echo "Optional:" + echo " --runtime Lambda runtime (default: provided.al2023)" + echo " --iterations Number of measurements (default: 10)" + echo " --event-file Path to JSON event payload file" + echo " --function-name Lambda function name (default: lambda-perf-test)" + echo " --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[@]}" diff --git a/scripts/lambda-performance/shared-utils.sh b/scripts/lambda-performance/shared-utils.sh new file mode 100755 index 00000000..debec209 --- /dev/null +++ b/scripts/lambda-performance/shared-utils.sh @@ -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 +} diff --git a/scripts/performance_test.sh b/scripts/local_performance_test.sh similarity index 100% rename from scripts/performance_test.sh rename to scripts/local_performance_test.sh