-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 248 add load test to GitHub actions (#256)
Added a github action to run the load tests periodically. Added load test scripts.
- Loading branch information
Showing
4 changed files
with
169 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
name: Schedule Load Test every other Friday | ||
|
||
on: | ||
schedule: | ||
- cron: "0 0 * * 5/2" | ||
workflow_dispatch: # Supports manual triggering | ||
|
||
env: | ||
API_BASE_URL: "api-qa.mobilitydatabase.org" | ||
# locust parameters. Refer to https://docs.locust.io/en/stable/configuration.html for explanation | ||
LOCUST_USERS: 100 | ||
LOCUST_RATE: 10 | ||
LOCUST_DURATION: 180 | ||
|
||
jobs: | ||
load-test: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v2 | ||
|
||
- name: Set up Python | ||
uses: actions/setup-python@v2 | ||
with: | ||
python-version: "3.x" | ||
|
||
- name: Install dependencies | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install locust | ||
- name: Get an access token | ||
id: getAccessToken | ||
run: | | ||
set +e # Do not exit if error. Handle error messages here. | ||
REPLY=`curl --location "https://${API_BASE_URL}/v1/tokens" \ | ||
--header 'Content-Type: application/json' \ | ||
--data '{ "refresh_token": "${{ secrets.QA_API_TEST_REFRESH_TOKEN }}" }'` | ||
[ $? -ne 0 ] && { echo "Error: Cannot obtain access token Reply = \"$REPLY\""; exit 1; } | ||
ACCESS_TOKEN=`echo $REPLY | jq -r .access_token` | ||
[ $? -ne 0 ] && { echo "Error: Cannot extract access token from reply \"$REPLY\""; exit 1; } | ||
[ -z "$ACCESS_TOKEN" ] && { echo "Error: Access token is empty extracted from $REPLY"; exit 1; } | ||
echo "ACCESS_TOKEN=$ACCESS_TOKEN" >> $GITHUB_ENV | ||
- name: Run the load tests | ||
run: | | ||
export FEEDS_AUTH_TOKEN="${{ env.ACCESS_TOKEN }}" # The locust script uses this variable | ||
locust -f ./load-test/gtfs_user_test.py --host=https://${API_BASE_URL} \ | ||
-u ${LOCUST_USERS} -r ${LOCUST_RATE} --headless -t ${LOCUST_DURATION} --only-summary --csv locust_results | ||
- name: Upload load test results as artifacts | ||
uses: actions/upload-artifact@v3 | ||
with: | ||
name: load_test_results | ||
path: locust_results_* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# API load tests | ||
|
||
This script `gtfs_user_test.py`, defines a set of tasks for load testing the GTFS API. Each task represents a different API endpoint that will be hit during the load test. | ||
|
||
## Setup | ||
|
||
### Install Locust | ||
|
||
Locust is a Python library, so you can install it with pip. Run the following command in your terminal: | ||
|
||
``` | ||
pip install locust | ||
``` | ||
## Authorization | ||
|
||
Set the `FEEDS_AUTH_TOKEN` environment variable to the access token you wish to use for connecting to the rest API. | ||
All requests sent during the load test will include this token. | ||
|
||
## Start a Load Test | ||
|
||
To start a load test on QA environment, run the following command in your terminal: | ||
``` | ||
locust -f gtfs_user_test.py --host=https://api-qa.mobilitydatabase.org -u 100 -r 10 | ||
``` | ||
The -u option specifies the total number of users to simulate, and the -r option specifies the hatch rate (number of users to start per second) | ||
|
||
### Tasks | ||
|
||
### `feeds` | ||
|
||
This task hits the `/v1/feeds` endpoint, which returns a list of all feeds. | ||
|
||
### `feed_byId` | ||
|
||
This task hits the `/v1/feeds/mdb-10` endpoint, which returns the feed with the ID `mdb-10`. | ||
|
||
### `gtfs_feeds` | ||
|
||
This task hits the `/v1/gtfs_feeds` endpoint, which returns a list of all GTFS feeds. | ||
|
||
### `gtfs_feed_byId` | ||
|
||
This task hits the `/v1/gtfs_feeds/mdb-10` endpoint, which returns the GTFS feed with the ID `mdb-10`. | ||
|
||
### `gtfs_realtime_feeds` | ||
|
||
This task hits the `/v1/gtfs_rt_feeds` endpoint, which returns a list of all GTFS realtime feeds. | ||
|
||
### `gtfs_realtime_feed_byId` | ||
|
||
This task hits the `/v1/gtfs_rt_feeds/mdb-1852` endpoint, which returns the GTFS realtime feed with the ID `mdb-1852`. | ||
|
||
### `gtfs_feeds_datasets` | ||
|
||
This task hits the `/v1/gtfs_feeds/mdb-10/datasets` endpoint, which returns a list of all datasets for the GTFS feed with the ID `mdb-10`. | ||
|
||
### `gtfs_dataset` | ||
|
||
This task hits the `/v1/datasets/gtfs/mdb-10` endpoint, which returns the dataset for the GTFS feed with the ID `mdb-10`. | ||
|
||
## Wait Time | ||
|
||
The `wait_time` is set to a random duration between 5 and 15 seconds. This means that after each task is executed, the script will wait for a duration between 5 and 15 seconds before executing the next task. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,72 @@ | ||
import sys | ||
import json | ||
|
||
from locust import HttpUser, TaskSet, task, between | ||
import os | ||
|
||
class gtfs_user(HttpUser): | ||
wait_time = between(5, 15) | ||
|
||
wait_time = between(.1, 1) | ||
|
||
def print_response(self, response, indent): | ||
print(indent, "Contents of response:") | ||
print(indent, " text: ", response.text) | ||
print(indent, " status code:", response.status_code) | ||
print(indent, " headers:", response.headers) | ||
print(indent, " URL:", response.url) | ||
print(indent, " content:", response.content) | ||
|
||
def get_valid(self, endpoint, allow404=False): | ||
try: | ||
response = self.client.get(endpoint, allow_redirects=False) | ||
if allow404 and response.status_code == 404: | ||
return | ||
if response.status_code >= 300: | ||
print("Error in response.") | ||
self.print_response(response, "") | ||
sys.exit(1) | ||
json_response = response.json() # Try to parse response content as JSON | ||
except json.JSONDecodeError: | ||
print("Error: Response not json.") | ||
self.print_response(response, "") | ||
sys.exit(1) | ||
|
||
def on_start(self): | ||
self.client.headers = {'Authorization': os.getenv('FEEDS_AUTH_TOKEN')} | ||
access_token = os.environ.get('FEEDS_AUTH_TOKEN') | ||
if access_token is None or access_token == "": | ||
print("Error: FEEDS_AUTH_TOKEN is not defined or empty") | ||
sys.exit(1) | ||
self.client.headers = {'Authorization': "Bearer " + access_token} | ||
|
||
@task | ||
def feeds(self): | ||
self.client.get("/v1/feeds") | ||
self.get_valid("/v1/feeds?limit=10") | ||
|
||
@task | ||
def feed_byId(self): | ||
self.client.get("/v1/feeds/mdb-10") | ||
|
||
# Allow error 404 since we are not sure the feed ID exists | ||
self.get_valid("/v1/feeds/mdb-10", allow404=True) | ||
|
||
@task | ||
def gtfs_feeds(self): | ||
self.client.get("/v1/gtfs_feeds") | ||
self.get_valid("/v1/gtfs_feeds?limit=1000") | ||
|
||
@task | ||
def gtfs_feed_byId(self): | ||
self.client.get("/v1/gtfs_feeds/mdb-10") | ||
self.get_valid("/v1/gtfs_feeds/mdb-10", allow404=True) | ||
|
||
@task | ||
def gtfs_realtime_feeds(self): | ||
self.client.get("/v1/gtfs_rt_feeds") | ||
self.get_valid("/v1/gtfs_rt_feeds?limit=1000") | ||
|
||
@task | ||
def gtfs_realtime_feed_byId(self): | ||
self.client.get("/v1/gtfs_rt_feeds/mdb-1852") | ||
self.get_valid("/v1/gtfs_rt_feeds/mdb-1333", allow404=True) | ||
|
||
@task | ||
def gtfs_feeds_datasets(self): | ||
self.client.get("/v1/gtfs_feeds/mdb-10/datasets") | ||
self.get_valid("/v1/gtfs_feeds/mdb-10/datasets", allow404=True) | ||
|
||
@task | ||
def gtfs_dataset(self): | ||
self.client.get("/v1/datasets/gtfs/mdb-10") | ||
|
||
|
||
self.get_valid("/v1/datasets/gtfs/mdb-10-202402071805", allow404=True) |