stac-api-load-testing 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stac_api_load_testing-0.2.0 → stac_api_load_testing-0.3.0}/PKG-INFO +49 -15
- stac_api_load_testing-0.2.0/stac_api_load_testing.egg-info/PKG-INFO → stac_api_load_testing-0.3.0/README.md +37 -22
- stac_api_load_testing-0.3.0/pyproject.toml +35 -0
- stac_api_load_testing-0.3.0/stac_api_load_testing/__init__.py +0 -0
- stac_api_load_testing-0.3.0/stac_api_load_testing/cli.py +308 -0
- stac_api_load_testing-0.3.0/stac_api_load_testing/config_files/locustfile.py +402 -0
- stac_api_load_testing-0.3.0/stac_api_load_testing/config_files/taurus_locust.yml +14 -0
- stac_api_load_testing-0.3.0/stac_api_load_testing/data_loader/__init__.py +1 -0
- stac_api_load_testing-0.3.0/stac_api_load_testing/data_loader/data_loader.py +60 -0
- stac_api_load_testing-0.3.0/stac_api_load_testing/data_loader/setup_data/collection.json +194 -0
- stac_api_load_testing-0.3.0/stac_api_load_testing/data_loader/setup_data/sentinel-s2-l2a-cogs_0_100.json +1 -0
- stac_api_load_testing-0.3.0/stac_api_load_testing.egg-info/PKG-INFO +114 -0
- stac_api_load_testing-0.3.0/stac_api_load_testing.egg-info/SOURCES.txt +17 -0
- stac_api_load_testing-0.3.0/stac_api_load_testing.egg-info/requires.txt +8 -0
- stac_api_load_testing-0.2.0/README.md +0 -61
- stac_api_load_testing-0.2.0/setup.py +0 -35
- stac_api_load_testing-0.2.0/stac_api_load_testing/cli.py +0 -143
- stac_api_load_testing-0.2.0/stac_api_load_testing.egg-info/SOURCES.txt +0 -10
- stac_api_load_testing-0.2.0/stac_api_load_testing.egg-info/requires.txt +0 -8
- {stac_api_load_testing-0.2.0 → stac_api_load_testing-0.3.0}/LICENSE +0 -0
- {stac_api_load_testing-0.2.0 → stac_api_load_testing-0.3.0}/setup.cfg +0 -0
- {stac_api_load_testing-0.2.0 → stac_api_load_testing-0.3.0}/stac_api_load_testing.egg-info/dependency_links.txt +0 -0
- {stac_api_load_testing-0.2.0 → stac_api_load_testing-0.3.0}/stac_api_load_testing.egg-info/entry_points.txt +0 -0
- {stac_api_load_testing-0.2.0 → stac_api_load_testing-0.3.0}/stac_api_load_testing.egg-info/top_level.txt +0 -0
|
@@ -1,31 +1,44 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
2
|
-
Name:
|
|
3
|
-
Version: 0.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stac-api-load-testing
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Load testing for stac compliant apis
|
|
5
|
-
|
|
6
|
-
Author: Jonathan Healy
|
|
7
|
-
Author-email: jonathan.d.healy@gmail.com
|
|
5
|
+
Author-email: Jonathan Healy <jonathan.d.healy@gmail.com>
|
|
8
6
|
License: MIT
|
|
9
|
-
|
|
7
|
+
Project-URL: Homepage, https://github.com/Healy-Hyperspatial/stac-api-load-testing
|
|
8
|
+
Requires-Python: >=3.11
|
|
10
9
|
Description-Content-Type: text/markdown
|
|
11
10
|
License-File: LICENSE
|
|
12
|
-
Requires-Dist: click>=
|
|
13
|
-
Requires-Dist: locust
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist:
|
|
16
|
-
Requires-Dist: bzt
|
|
11
|
+
Requires-Dist: click>=8.1
|
|
12
|
+
Requires-Dist: locust<3,>=2.32
|
|
13
|
+
Requires-Dist: requests>=2.31
|
|
14
|
+
Requires-Dist: pyyaml>=6.0
|
|
15
|
+
Requires-Dist: bzt>=1.17.0
|
|
17
16
|
Provides-Extra: test
|
|
18
17
|
Requires-Dist: pytest; extra == "test"
|
|
18
|
+
Dynamic: license-file
|
|
19
19
|
|
|
20
20
|
# stac-api-load-testing
|
|
21
21
|
Taurus and Locust load testing tools for stac-api backends.
|
|
22
22
|
|
|
23
23
|

|
|
24
24
|
|
|
25
|
+
Requires Python >= 3.11.
|
|
25
26
|
|
|
26
|
-
## Run stac-fastapi backends locally (elasticsearch|opensearch|pgstac|mongo)
|
|
27
|
-
```$ docker
|
|
28
|
-
```$ docker
|
|
27
|
+
## Run stac-fastapi backends locally (elasticsearch|opensearch|pgstac|mongo|stac-os-rs)
|
|
28
|
+
```$ docker compose up elasticsearch```
|
|
29
|
+
```$ docker compose up app-elasticsearch```
|
|
30
|
+
|
|
31
|
+
Backend ports: pgstac `:8083`, elasticsearch `:8084`, opensearch `:8085`, mongo `:8086`, stac-os-rs `:8087`
|
|
32
|
+
|
|
33
|
+
| backend | app image | database image |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| pgstac | stac-fastapi.pgstac 7.2.0 (built locally) | ghcr.io/stac-utils/pgstac:v0.10.0 |
|
|
36
|
+
| elasticsearch | ghcr.io/stac-utils/stac-fastapi-es:v8.1.1 | elasticsearch 9.3.2 |
|
|
37
|
+
| opensearch | ghcr.io/stac-utils/stac-fastapi-os:v8.1.1 | opensearch 3.8.0 |
|
|
38
|
+
| mongo | stac-fastapi.mongo 3.2.0 (built locally) | mongo 7.0 |
|
|
39
|
+
| stac-os-rs | ghcr.io/jonhealy1/stac-os-rs:0.2.0 | opensearch 3.8.0 |
|
|
40
|
+
|
|
41
|
+
All datastores are given a ~2GB memory budget for comparable benchmarks: ES/OpenSearch heaps `-Xms2g -Xmx2g` with `bootstrap.memory_lock`, pgstac `shared_buffers=2GB`, mongo `--wiredTigerCacheSizeGB 2`. Ingest the same data into each backend before comparing (`stac-api-load-testing --ingest --api-address <url>`).
|
|
29
42
|
|
|
30
43
|
## Install locally for development
|
|
31
44
|
```$ pip install -e .```
|
|
@@ -62,6 +75,16 @@ Options:
|
|
|
62
75
|
-r, --ramp-up TEXT Ramp-up time for Taurus option.
|
|
63
76
|
-n, --iterations INTEGER Number of iterations for Taurus option.
|
|
64
77
|
-a, --api-address TEXT Specify the STAC API URL to test against.
|
|
78
|
+
Accepts a comma-separated list for --benchmark.
|
|
79
|
+
-b, --benchmark Run a headless locust benchmark sweep against the
|
|
80
|
+
STAC API.
|
|
81
|
+
-u, --users TEXT Comma-separated concurrency levels for
|
|
82
|
+
--benchmark.
|
|
83
|
+
--spawn-rate INTEGER Users spawned per second for --benchmark.
|
|
84
|
+
--run-time TEXT Duration of each --benchmark run, in locust
|
|
85
|
+
notation (e.g. '30s', '1m').
|
|
86
|
+
-o, --output PATH Write aggregated --benchmark results to a CSV
|
|
87
|
+
file.
|
|
65
88
|
--version Show the version and exit.
|
|
66
89
|
--help Show this message and exit.
|
|
67
90
|
```
|
|
@@ -72,9 +95,20 @@ Options:
|
|
|
72
95
|
## Run Locust Load Testing Ouside of Taurus Wrapper
|
|
73
96
|
```$ stac-api-load-testing --locust --api-address http://localhost:8084```
|
|
74
97
|
- go to ```http://localhost:8089``` and start with desired settings
|
|
98
|
+
- or run headless: ```$ locust --locustfile stac_api_load_testing/config_files/locustfile.py --host http://localhost:8084 --headless -u 10 -r 1 -t 1m```
|
|
75
99
|
|
|
76
100
|
## Inside of Taurus Wrapper
|
|
77
101
|
```$ stac-api-load-testing --taurus --api-address http://localhost:8084```
|
|
78
102
|
|
|
103
|
+
## Benchmark / compare backends
|
|
104
|
+
Runs headless locust sweeps at each concurrency level and prints a comparison
|
|
105
|
+
table. Ingest into each backend first, then run them one at a time:
|
|
106
|
+
|
|
107
|
+
```$ stac-api-load-testing --benchmark --api-address http://localhost:8085 --users 10,50,100 --run-time 1m```
|
|
108
|
+
|
|
109
|
+
Compare multiple backends in one command (comma-separated addresses):
|
|
110
|
+
|
|
111
|
+
```$ stac-api-load-testing --benchmark --api-address http://localhost:8085,http://localhost:8087 --users 10,50,100 --run-time 1m -o results.csv```
|
|
112
|
+
|
|
79
113
|
## Debugging Locust tasks using a single simulated user
|
|
80
114
|
```$ python stac_api_load_testing/config_files/locustfile.py```
|
|
@@ -1,31 +1,25 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: stac_api_load_testing
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Load testing for stac compliant apis
|
|
5
|
-
Home-page: https://github.com/Healy-Hyperspatial/stac-api-load-testing
|
|
6
|
-
Author: Jonathan Healy
|
|
7
|
-
Author-email: jonathan.d.healy@gmail.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Requires-Python: >=3.7
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Dist: click>=7.1.2
|
|
13
|
-
Requires-Dist: locust
|
|
14
|
-
Requires-Dist: setuptools
|
|
15
|
-
Requires-Dist: Cython
|
|
16
|
-
Requires-Dist: bzt
|
|
17
|
-
Provides-Extra: test
|
|
18
|
-
Requires-Dist: pytest; extra == "test"
|
|
19
|
-
|
|
20
1
|
# stac-api-load-testing
|
|
21
2
|
Taurus and Locust load testing tools for stac-api backends.
|
|
22
3
|
|
|
23
4
|

|
|
24
5
|
|
|
6
|
+
Requires Python >= 3.11.
|
|
7
|
+
|
|
8
|
+
## Run stac-fastapi backends locally (elasticsearch|opensearch|pgstac|mongo|stac-os-rs)
|
|
9
|
+
```$ docker compose up elasticsearch```
|
|
10
|
+
```$ docker compose up app-elasticsearch```
|
|
11
|
+
|
|
12
|
+
Backend ports: pgstac `:8083`, elasticsearch `:8084`, opensearch `:8085`, mongo `:8086`, stac-os-rs `:8087`
|
|
13
|
+
|
|
14
|
+
| backend | app image | database image |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| pgstac | stac-fastapi.pgstac 7.2.0 (built locally) | ghcr.io/stac-utils/pgstac:v0.10.0 |
|
|
17
|
+
| elasticsearch | ghcr.io/stac-utils/stac-fastapi-es:v8.1.1 | elasticsearch 9.3.2 |
|
|
18
|
+
| opensearch | ghcr.io/stac-utils/stac-fastapi-os:v8.1.1 | opensearch 3.8.0 |
|
|
19
|
+
| mongo | stac-fastapi.mongo 3.2.0 (built locally) | mongo 7.0 |
|
|
20
|
+
| stac-os-rs | ghcr.io/jonhealy1/stac-os-rs:0.2.0 | opensearch 3.8.0 |
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
```$ docker-compose up elasticsearch```
|
|
28
|
-
```$ docker-compose up app-elasticsearch```
|
|
22
|
+
All datastores are given a ~2GB memory budget for comparable benchmarks: ES/OpenSearch heaps `-Xms2g -Xmx2g` with `bootstrap.memory_lock`, pgstac `shared_buffers=2GB`, mongo `--wiredTigerCacheSizeGB 2`. Ingest the same data into each backend before comparing (`stac-api-load-testing --ingest --api-address <url>`).
|
|
29
23
|
|
|
30
24
|
## Install locally for development
|
|
31
25
|
```$ pip install -e .```
|
|
@@ -62,6 +56,16 @@ Options:
|
|
|
62
56
|
-r, --ramp-up TEXT Ramp-up time for Taurus option.
|
|
63
57
|
-n, --iterations INTEGER Number of iterations for Taurus option.
|
|
64
58
|
-a, --api-address TEXT Specify the STAC API URL to test against.
|
|
59
|
+
Accepts a comma-separated list for --benchmark.
|
|
60
|
+
-b, --benchmark Run a headless locust benchmark sweep against the
|
|
61
|
+
STAC API.
|
|
62
|
+
-u, --users TEXT Comma-separated concurrency levels for
|
|
63
|
+
--benchmark.
|
|
64
|
+
--spawn-rate INTEGER Users spawned per second for --benchmark.
|
|
65
|
+
--run-time TEXT Duration of each --benchmark run, in locust
|
|
66
|
+
notation (e.g. '30s', '1m').
|
|
67
|
+
-o, --output PATH Write aggregated --benchmark results to a CSV
|
|
68
|
+
file.
|
|
65
69
|
--version Show the version and exit.
|
|
66
70
|
--help Show this message and exit.
|
|
67
71
|
```
|
|
@@ -72,9 +76,20 @@ Options:
|
|
|
72
76
|
## Run Locust Load Testing Ouside of Taurus Wrapper
|
|
73
77
|
```$ stac-api-load-testing --locust --api-address http://localhost:8084```
|
|
74
78
|
- go to ```http://localhost:8089``` and start with desired settings
|
|
79
|
+
- or run headless: ```$ locust --locustfile stac_api_load_testing/config_files/locustfile.py --host http://localhost:8084 --headless -u 10 -r 1 -t 1m```
|
|
75
80
|
|
|
76
81
|
## Inside of Taurus Wrapper
|
|
77
82
|
```$ stac-api-load-testing --taurus --api-address http://localhost:8084```
|
|
78
83
|
|
|
84
|
+
## Benchmark / compare backends
|
|
85
|
+
Runs headless locust sweeps at each concurrency level and prints a comparison
|
|
86
|
+
table. Ingest into each backend first, then run them one at a time:
|
|
87
|
+
|
|
88
|
+
```$ stac-api-load-testing --benchmark --api-address http://localhost:8085 --users 10,50,100 --run-time 1m```
|
|
89
|
+
|
|
90
|
+
Compare multiple backends in one command (comma-separated addresses):
|
|
91
|
+
|
|
92
|
+
```$ stac-api-load-testing --benchmark --api-address http://localhost:8085,http://localhost:8087 --users 10,50,100 --run-time 1m -o results.csv```
|
|
93
|
+
|
|
79
94
|
## Debugging Locust tasks using a single simulated user
|
|
80
95
|
```$ python stac_api_load_testing/config_files/locustfile.py```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "stac-api-load-testing"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Load testing for stac compliant apis"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Jonathan Healy", email = "jonathan.d.healy@gmail.com" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"click>=8.1",
|
|
15
|
+
"locust>=2.32,<3",
|
|
16
|
+
"requests>=2.31",
|
|
17
|
+
"pyyaml>=6.0",
|
|
18
|
+
"bzt>=1.17.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
test = ["pytest"]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/Healy-Hyperspatial/stac-api-load-testing"
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
stac-api-load-testing = "stac_api_load_testing.cli:main"
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
include = ["stac_api_load_testing*"]
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.package-data]
|
|
34
|
+
stac_api_load_testing = ["config_files/*"]
|
|
35
|
+
"stac_api_load_testing.data_loader" = ["setup_data/*"]
|
|
File without changes
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""stac-api-load-testing cli tool."""
|
|
2
|
+
|
|
3
|
+
import csv
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
from importlib import resources
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
import yaml
|
|
15
|
+
|
|
16
|
+
from .data_loader import data_loader
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _package_resource(*parts: str) -> str:
|
|
20
|
+
"""Return the filesystem path to a resource bundled in this package."""
|
|
21
|
+
return str(resources.files("stac_api_load_testing").joinpath(*parts))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _tool_path(name: str) -> str:
|
|
25
|
+
"""Resolve a console script on PATH, falling back to this env's bin dir."""
|
|
26
|
+
return shutil.which(name) or str(Path(sys.executable).parent / name)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def generate_taurus_config(api_url: str, concurrency: int, ramp_up, iterations) -> str:
|
|
30
|
+
"""
|
|
31
|
+
Generate a custom Taurus configuration file based on the specified settings.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
api_url (str): The base URL for the STAC API to be tested.
|
|
35
|
+
concurrency (int): The number of concurrent users to simulate.
|
|
36
|
+
ramp_up (str): The duration over which to ramp up the load test.
|
|
37
|
+
iterations (int): The total number of iterations to perform.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
str: The path to the generated Taurus configuration file.
|
|
41
|
+
"""
|
|
42
|
+
template_path = _package_resource("config_files", "taurus_locust.yml")
|
|
43
|
+
locustfile_path = _package_resource("config_files", "locustfile.py")
|
|
44
|
+
|
|
45
|
+
safe_api_url = re.sub(r"[^a-zA-Z0-9]+", "_", api_url)
|
|
46
|
+
output_path = f"taurus_config_{safe_api_url}.yml"
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
with open(template_path, "r") as file:
|
|
50
|
+
config = yaml.safe_load(file)
|
|
51
|
+
|
|
52
|
+
# Setting the script path directly to where `locustfile.py` is expected to be within the package
|
|
53
|
+
config["execution"][0]["concurrency"] = concurrency
|
|
54
|
+
config["execution"][0]["ramp-up"] = ramp_up
|
|
55
|
+
config["execution"][0]["iterations"] = iterations
|
|
56
|
+
config["scenarios"]["default"]["script"] = locustfile_path
|
|
57
|
+
config["scenarios"]["default"]["default-address"] = api_url
|
|
58
|
+
|
|
59
|
+
with open(output_path, "w") as file:
|
|
60
|
+
yaml.safe_dump(config, file)
|
|
61
|
+
|
|
62
|
+
return output_path
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print(f"Error creating Taurus configuration file: {e}")
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _read_aggregated_stats(csv_path: str) -> dict:
|
|
69
|
+
"""Return the 'Aggregated' row from a locust *_stats.csv file."""
|
|
70
|
+
with open(csv_path, newline="") as file:
|
|
71
|
+
for row in csv.DictReader(file):
|
|
72
|
+
if row.get("Name") == "Aggregated":
|
|
73
|
+
return row
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def run_benchmark(
|
|
78
|
+
api_addresses: list,
|
|
79
|
+
concurrency_levels: list,
|
|
80
|
+
spawn_rate: int,
|
|
81
|
+
run_time: str,
|
|
82
|
+
output: str,
|
|
83
|
+
):
|
|
84
|
+
"""
|
|
85
|
+
Run headless Locust benchmark sweeps against one or more STAC APIs.
|
|
86
|
+
|
|
87
|
+
For each API address, a headless locust run is executed at every
|
|
88
|
+
concurrency level. Aggregated statistics are collected from locust's
|
|
89
|
+
CSV output and printed as a comparison table.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
api_addresses (list): STAC API base URLs to benchmark, in order.
|
|
93
|
+
concurrency_levels (list): Concurrent user counts to sweep.
|
|
94
|
+
spawn_rate (int): Users spawned per second in each run.
|
|
95
|
+
run_time (str): Duration of each run, in locust notation (e.g. '30s').
|
|
96
|
+
output (str): Optional path to write aggregated results as CSV.
|
|
97
|
+
"""
|
|
98
|
+
locustfile_path = _package_resource("config_files", "locustfile.py")
|
|
99
|
+
results = []
|
|
100
|
+
|
|
101
|
+
for address in api_addresses:
|
|
102
|
+
for users in concurrency_levels:
|
|
103
|
+
click.secho(f"\n>>> {address} @ {users} users for {run_time}", fg="cyan")
|
|
104
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
105
|
+
csv_prefix = os.path.join(tmpdir, "locust")
|
|
106
|
+
stats_csv = f"{csv_prefix}_stats.csv"
|
|
107
|
+
# locust exits non-zero when requests fail; under load that is
|
|
108
|
+
# data (backend errors), not a benchmark tool error.
|
|
109
|
+
subprocess.run(
|
|
110
|
+
[
|
|
111
|
+
_tool_path("locust"),
|
|
112
|
+
"--locustfile",
|
|
113
|
+
locustfile_path,
|
|
114
|
+
"--host",
|
|
115
|
+
address,
|
|
116
|
+
"--headless",
|
|
117
|
+
"-u",
|
|
118
|
+
str(users),
|
|
119
|
+
"-r",
|
|
120
|
+
str(spawn_rate),
|
|
121
|
+
"-t",
|
|
122
|
+
run_time,
|
|
123
|
+
"--csv",
|
|
124
|
+
csv_prefix,
|
|
125
|
+
"--only-summary",
|
|
126
|
+
]
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if not os.path.exists(stats_csv):
|
|
130
|
+
click.secho(
|
|
131
|
+
f"no stats produced for {address} @ {users} users",
|
|
132
|
+
fg="red",
|
|
133
|
+
)
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
stats = _read_aggregated_stats(stats_csv)
|
|
137
|
+
if stats:
|
|
138
|
+
results.append(
|
|
139
|
+
{
|
|
140
|
+
"api": address,
|
|
141
|
+
"users": users,
|
|
142
|
+
"requests": stats.get("Request Count"),
|
|
143
|
+
"failures": stats.get("Failure Count"),
|
|
144
|
+
"median_ms": stats.get("Median Response Time"),
|
|
145
|
+
"p95_ms": stats.get("95%"),
|
|
146
|
+
"p99_ms": stats.get("99%"),
|
|
147
|
+
"rps": round(float(stats.get("Requests/s") or 0), 1),
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if not results:
|
|
152
|
+
click.secho("No benchmark results collected.", fg="red")
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
header = f"{'api':<26}{'users':>7}{'reqs':>9}{'fails':>7}{'med':>8}{'p95':>8}{'p99':>8}{'rps':>9}"
|
|
156
|
+
click.echo("\n" + header)
|
|
157
|
+
click.echo("-" * len(header))
|
|
158
|
+
for row in results:
|
|
159
|
+
click.echo(
|
|
160
|
+
f"{row['api']:<26}{row['users']:>7}{row['requests']:>9}"
|
|
161
|
+
f"{row['failures']:>7}{row['median_ms']:>8}{row['p95_ms']:>8}"
|
|
162
|
+
f"{row['p99_ms']:>8}{row['rps']:>9}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if output:
|
|
166
|
+
with open(output, "w", newline="") as file:
|
|
167
|
+
writer = csv.DictWriter(file, fieldnames=list(results[0].keys()))
|
|
168
|
+
writer.writeheader()
|
|
169
|
+
writer.writerows(results)
|
|
170
|
+
click.secho(f"\nResults written to {output}", fg="green")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@click.command()
|
|
174
|
+
@click.option(
|
|
175
|
+
"-i", "--ingest", is_flag=True, help="Ingest sample data into the STAC API."
|
|
176
|
+
)
|
|
177
|
+
@click.option(
|
|
178
|
+
"-l", "--locust", is_flag=True, help="Run Locust load tests against the STAC API."
|
|
179
|
+
)
|
|
180
|
+
@click.option(
|
|
181
|
+
"-t",
|
|
182
|
+
"--taurus",
|
|
183
|
+
is_flag=True,
|
|
184
|
+
help="Run the Taurus wrapper for performance testing against the STAC API.",
|
|
185
|
+
)
|
|
186
|
+
@click.option(
|
|
187
|
+
"-c",
|
|
188
|
+
"--concurrency",
|
|
189
|
+
default=10,
|
|
190
|
+
help="Number of concurrent users for Taurus option.",
|
|
191
|
+
type=int,
|
|
192
|
+
)
|
|
193
|
+
@click.option("-r", "--ramp-up", default="1m", help="Ramp-up time for Taurus option.")
|
|
194
|
+
@click.option(
|
|
195
|
+
"-n",
|
|
196
|
+
"--iterations",
|
|
197
|
+
default=100,
|
|
198
|
+
help="Number of iterations for Taurus option.",
|
|
199
|
+
type=int,
|
|
200
|
+
) # Changed the flag to -n to avoid conflict
|
|
201
|
+
@click.option(
|
|
202
|
+
"-a",
|
|
203
|
+
"--api-address",
|
|
204
|
+
default="http://localhost:8080",
|
|
205
|
+
help="Specify the STAC API URL to test against. Accepts a comma-separated list for --benchmark.",
|
|
206
|
+
)
|
|
207
|
+
@click.option(
|
|
208
|
+
"-b",
|
|
209
|
+
"--benchmark",
|
|
210
|
+
is_flag=True,
|
|
211
|
+
help="Run a headless locust benchmark sweep against the STAC API.",
|
|
212
|
+
)
|
|
213
|
+
@click.option(
|
|
214
|
+
"-u",
|
|
215
|
+
"--users",
|
|
216
|
+
default="10,50,100",
|
|
217
|
+
help="Comma-separated concurrency levels for --benchmark.",
|
|
218
|
+
)
|
|
219
|
+
@click.option(
|
|
220
|
+
"--spawn-rate",
|
|
221
|
+
default=10,
|
|
222
|
+
help="Users spawned per second for --benchmark.",
|
|
223
|
+
type=int,
|
|
224
|
+
)
|
|
225
|
+
@click.option(
|
|
226
|
+
"--run-time",
|
|
227
|
+
default="30s",
|
|
228
|
+
help="Duration of each --benchmark run, in locust notation (e.g. '30s', '1m').",
|
|
229
|
+
)
|
|
230
|
+
@click.option(
|
|
231
|
+
"-o",
|
|
232
|
+
"--output",
|
|
233
|
+
default=None,
|
|
234
|
+
help="Write aggregated --benchmark results to a CSV file.",
|
|
235
|
+
type=click.Path(),
|
|
236
|
+
)
|
|
237
|
+
@click.version_option(version="0.3.0")
|
|
238
|
+
def main(
|
|
239
|
+
ingest: bool,
|
|
240
|
+
locust: bool,
|
|
241
|
+
taurus: bool,
|
|
242
|
+
api_address: str,
|
|
243
|
+
concurrency: int,
|
|
244
|
+
ramp_up: str,
|
|
245
|
+
iterations: int,
|
|
246
|
+
benchmark: bool,
|
|
247
|
+
users: str,
|
|
248
|
+
spawn_rate: int,
|
|
249
|
+
run_time: str,
|
|
250
|
+
output: str,
|
|
251
|
+
):
|
|
252
|
+
"""
|
|
253
|
+
Entry point for the stac-api-load-testing CLI tool.
|
|
254
|
+
|
|
255
|
+
This tool facilitates data ingestion, Locust load testing, and Taurus performance testing
|
|
256
|
+
against a specified STAC API endpoint.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
ingest (bool): If True, ingest sample data into the specified STAC API.
|
|
260
|
+
locust (bool): If True, execute Locust load tests against the specified STAC API.
|
|
261
|
+
taurus (bool): If True, perform Taurus performance testing with custom settings against the specified STAC API.
|
|
262
|
+
concurrency (int): Specifies the number of concurrent users for Taurus testing. Default is 10.
|
|
263
|
+
ramp_up (str): Specifies the ramp-up period for Taurus testing, in Taurus notation (e.g., '1m' for 1 minute). Default is '1m'.
|
|
264
|
+
iterations (int): Specifies the number of iterations each virtual user will execute in Taurus testing. Default is 100.
|
|
265
|
+
api_address (str): The base URL of the STAC API to be tested. Comma-separated list allowed for benchmark.
|
|
266
|
+
benchmark (bool): If True, run headless locust runs sweeping --users levels and print a comparison table.
|
|
267
|
+
users (str): Comma-separated concurrency levels for benchmark. Default '10,50,100'.
|
|
268
|
+
spawn_rate (int): Users spawned per second in each benchmark run. Default 10.
|
|
269
|
+
run_time (str): Duration of each benchmark run in locust notation. Default '30s'.
|
|
270
|
+
output (str): Optional CSV file path for aggregated benchmark results.
|
|
271
|
+
"""
|
|
272
|
+
os.environ["LOCUST_HOST"] = api_address
|
|
273
|
+
|
|
274
|
+
if ingest:
|
|
275
|
+
# Load data into the STAC API
|
|
276
|
+
data_loader.load_items(stac_api_base_url=api_address)
|
|
277
|
+
elif locust:
|
|
278
|
+
# Execute Locust load tests
|
|
279
|
+
locust_file_path = _package_resource("config_files", "locustfile.py")
|
|
280
|
+
subprocess.run(
|
|
281
|
+
[
|
|
282
|
+
_tool_path("locust"),
|
|
283
|
+
"--locustfile",
|
|
284
|
+
locust_file_path,
|
|
285
|
+
"--host",
|
|
286
|
+
api_address,
|
|
287
|
+
],
|
|
288
|
+
check=True,
|
|
289
|
+
)
|
|
290
|
+
elif taurus:
|
|
291
|
+
# Generate and run a custom Taurus configuration for performance testing
|
|
292
|
+
config_file_path = generate_taurus_config(
|
|
293
|
+
api_address, concurrency, ramp_up, iterations
|
|
294
|
+
)
|
|
295
|
+
if config_file_path:
|
|
296
|
+
try:
|
|
297
|
+
subprocess.run([_tool_path("bzt"), config_file_path], check=True)
|
|
298
|
+
finally:
|
|
299
|
+
if os.path.exists(config_file_path):
|
|
300
|
+
os.remove(config_file_path) # Cleanup after running
|
|
301
|
+
elif benchmark:
|
|
302
|
+
concurrency_levels = [int(x) for x in users.split(",")]
|
|
303
|
+
api_addresses = [a.strip() for a in api_address.split(",")]
|
|
304
|
+
run_benchmark(api_addresses, concurrency_levels, spawn_rate, run_time, output)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
if __name__ == "__main__":
|
|
308
|
+
main()
|