diff --git a/.gitignore b/.gitignore index ebec2fc..dd8857c 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ publish/ appsettings.Development.json + +scripts/.env +scripts/.venv \ No newline at end of file diff --git a/docs/load_testing/images/lightsail_cpu_200users_15min.png b/docs/load_testing/images/lightsail_cpu_200users_15min.png new file mode 100644 index 0000000..3dc0f3d Binary files /dev/null and b/docs/load_testing/images/lightsail_cpu_200users_15min.png differ diff --git a/docs/load_testing/images/lightsail_cpu_5min.png b/docs/load_testing/images/lightsail_cpu_5min.png new file mode 100644 index 0000000..e514b22 Binary files /dev/null and b/docs/load_testing/images/lightsail_cpu_5min.png differ diff --git a/docs/load_testing/images/lightsail_cpu_5min_100users.png b/docs/load_testing/images/lightsail_cpu_5min_100users.png new file mode 100644 index 0000000..129b0d3 Binary files /dev/null and b/docs/load_testing/images/lightsail_cpu_5min_100users.png differ diff --git a/docs/load_testing/images/lightsail_cpu_5min_100users_recipe.png b/docs/load_testing/images/lightsail_cpu_5min_100users_recipe.png new file mode 100644 index 0000000..d033d42 Binary files /dev/null and b/docs/load_testing/images/lightsail_cpu_5min_100users_recipe.png differ diff --git a/docs/load_testing/images/lightsail_cpu_5min_recipe.png b/docs/load_testing/images/lightsail_cpu_5min_recipe.png new file mode 100644 index 0000000..73f15b3 Binary files /dev/null and b/docs/load_testing/images/lightsail_cpu_5min_recipe.png differ diff --git a/docs/load_testing/images/total_rqps_15min_200users.png b/docs/load_testing/images/total_rqps_15min_200users.png new file mode 100644 index 0000000..f7e663a Binary files /dev/null and b/docs/load_testing/images/total_rqps_15min_200users.png differ diff --git a/docs/load_testing/images/total_rqps_5_minutes.png b/docs/load_testing/images/total_rqps_5_minutes.png new file mode 100644 index 0000000..dc6be9b Binary files /dev/null and b/docs/load_testing/images/total_rqps_5_minutes.png differ diff --git a/docs/load_testing/images/total_rqps_5min_100.png b/docs/load_testing/images/total_rqps_5min_100.png new file mode 100644 index 0000000..93707f5 Binary files /dev/null and b/docs/load_testing/images/total_rqps_5min_100.png differ diff --git a/docs/load_testing/images/total_rqps_5min_100users_recipe.png b/docs/load_testing/images/total_rqps_5min_100users_recipe.png new file mode 100644 index 0000000..45249a3 Binary files /dev/null and b/docs/load_testing/images/total_rqps_5min_100users_recipe.png differ diff --git a/docs/load_testing/images/total_rqps_5min_recipe.png b/docs/load_testing/images/total_rqps_5min_recipe.png new file mode 100644 index 0000000..ad596be Binary files /dev/null and b/docs/load_testing/images/total_rqps_5min_recipe.png differ diff --git a/docs/load_testing/load_testing.md b/docs/load_testing/load_testing.md new file mode 100644 index 0000000..a5994d0 --- /dev/null +++ b/docs/load_testing/load_testing.md @@ -0,0 +1,37 @@ +# --- 5 Minute Tests --- +## "/" --> "/account" (50 users): +### Lightsail Instance Data: + ![alt text](images/lightsail_cpu_5min.png) +### Locust Data: + ![alt text](images/total_rqps_5_minutes.png) + +## "/" --> "/account" (100 users): +### Lightsail Instance Data: + ![alt text](images/lightsail_cpu_5min_100users.png) +### Locust Data: + ![alt text](images/total_rqps_5min_100.png) + + +# Heavier workloads --- 5 Minute Tests --- +## "/" --> "/recipes" --> "/recipes/view/2" --> "/dashboard" (50 users) +### Lightsail Instance data: + ![alt text](images/lightsail_cpu_5min_recipe.png) +### Locust Data: + ![alt text](images/total_rqps_5min_recipe.png) + +## "/" --> "/recipes" --> "/recipes/view/2" --> "/dashboard" (100 users) + +### Lightsail Instance Data: +![alt text](images/lightsail_cpu_5min_100users_recipe.png) + +### Locust Data: + ![alt text](images/total_rqps_5min_100users_recipe.png) + + +## Pushing the limits --- 200 Users --- 15 minutes --- + +### Lightsail Instance Data: + ![alt text](images/lightsail_cpu_200users_15min.png) + +### Locust Data: + ![alt text](images/total_rqps_15min_200users.png) \ No newline at end of file diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..4046f4e --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,61 @@ +# Getting Started + +## Prerequisites + +- Python 3.8+ +- AWS Cognito credentials for the target environment + +--- + +## Setup + +**1. Create and activate a virtual environment** + +```bash +python3 -m venv venv +source venv/bin/activate +``` + +**2. Install dependencies** + +```bash +pip install -r requirements.txt +``` + +--- + +## Environment Variables + +Create a `.env` file in the `scripts/` directory with the following: + +```env +COGNITO_CLIENT_ID=your_client_id +COGNITO_CLIENT_SECRET=your_client_secret +COGNITO_USERNAME=your_username +COGNITO_PASSWORD=your_password +AWS_REGION=us-east-1 +``` + +These credentials are used to authenticate against AWS Cognito before each load test run. The script calls `InitiateAuth` with `USER_PASSWORD_AUTH` flow and uses the returned `IdToken` as a Bearer token on all requests. + +--- + +## Running Locust + +Start the Locust web UI: + +```bash +locust -f locustfile.py --host=https://culinary-command.com +``` + +Then open [http://localhost:8089](http://localhost:8089) to configure the number of users and spawn rate, and start the test. + +To run headless (no UI): + +```bash +locust -f locustfile.py --host=https://your-app-url.com --headless -u 50 -r 10 --run-time 1m +``` + +- `-u` — number of users +- `-r` — spawn rate (users per second) +- `--run-time` — how long to run the test diff --git a/scripts/locustfile.py b/scripts/locustfile.py new file mode 100644 index 0000000..470d209 --- /dev/null +++ b/scripts/locustfile.py @@ -0,0 +1,56 @@ +from locust import HttpUser, task, between +from dotenv import load_dotenv +import boto3 +import hashlib +import hmac +import base64 +import os + +load_dotenv() + +def get_secret_hash(username): + client_id = os.getenv("COGNITO_CLIENT_ID") + client_secret = os.getenv("COGNITO_CLIENT_SECRET") + message = username + client_id + + dig = hmac.new( + client_secret.encode("utf-8"), + msg=message.encode("utf-8"), + digestmod=hashlib.sha256 + ).digest() + return base64.b64encode(dig).decode() + +def get_cognito_token(): + username = os.getenv("COGNITO_USERNAME") + client = boto3.client("cognito-idp", region_name=os.getenv("AWS_REGION")) + response = client.initiate_auth( + AuthFlow="USER_PASSWORD_AUTH", + AuthParameters={ + "USERNAME": os.getenv("COGNITO_USERNAME"), + "PASSWORD": os.getenv("COGNITO_PASSWORD"), + "SECRET_HASH": get_secret_hash(username) + }, + ClientId=os.getenv("COGNITO_CLIENT_ID"), + ) + return response["AuthenticationResult"]["IdToken"] + +class LocustLoadTesting(HttpUser): + wait_time = between(1, 3) + + def on_start(self): + token = get_cognito_token() + + self.client.headers.update({ + "Authorization": f"Bearer {token}" + }) + + @task(3) + def browse_recipes(self): + self.client.get("/recipes", name="Browse Recipes") + + def view_recipe(self): + recipe_id = 2 + self.client.get(f"/recipes/view/{recipe_id}", name="View Recipe") + + def view_dashboard(self): + self.client.get("/dashboard", name="View Dashboard") \ No newline at end of file diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 0000000..e083641 --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1,3 @@ +locust +boto3 +python-dotenv \ No newline at end of file