-
Notifications
You must be signed in to change notification settings - Fork 187
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
reorg checkboxes, add load testing (#929)
* reorg checkboxes, add load testing * integrate with CI * back to 10k checkboxes * remove stray load test files from WIP * module-style, not script-style * relative import of constants * ignore locustfile in pytest * absolute import for constants in locustfile
- Loading branch information
1 parent
d31377e
commit a0c804c
Showing
6 changed files
with
154 additions
and
5 deletions.
There are no files selected for viewing
95 changes: 95 additions & 0 deletions
95
07_web_endpoints/fasthtml-checkboxes/checkboxes-load-test.py
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,95 @@ | ||
import os | ||
from datetime import datetime | ||
from pathlib import Path | ||
|
||
import modal | ||
|
||
if modal.is_local(): | ||
workspace = modal.config._profile | ||
else: | ||
workspace = os.environ["MODAL_WORKSPACE"] | ||
|
||
|
||
image = ( | ||
modal.Image.debian_slim(python_version="3.12") | ||
.pip_install("locust~=2.29.1") | ||
.env({"MODAL_WORKSPACE": workspace}) | ||
.copy_local_file( | ||
Path(__file__).parent / "checkboxes-locustfile.py", | ||
remote_path="/root/locustfile.py", | ||
) | ||
.copy_local_file( | ||
Path(__file__).parent / "constants.py", | ||
remote_path="/root/constants.py", | ||
) | ||
) | ||
volume = modal.Volume.from_name( | ||
"loadtest-checkboxes-results", create_if_missing=True | ||
) | ||
remote_path = Path("/root") / "loadtests" | ||
OUT_DIRECTORY = ( | ||
remote_path / datetime.utcnow().replace(microsecond=0).isoformat() | ||
) | ||
|
||
app = modal.App("loadtest-checkbox", image=image, volumes={remote_path: volume}) | ||
|
||
workers = 8 | ||
host = f"https://{workspace}--example-checkboxes-web.modal.run" | ||
csv_file = OUT_DIRECTORY / "stats.csv" | ||
default_args = [ | ||
"-H", | ||
host, | ||
"--processes", | ||
str(workers), | ||
"--csv", | ||
csv_file, | ||
] | ||
|
||
MINUTES = 60 # seconds | ||
|
||
|
||
@app.function(allow_concurrent_inputs=1000, cpu=workers) | ||
@modal.web_server(port=8089) | ||
def serve(): | ||
run_locust.local(default_args) | ||
|
||
|
||
@app.function(cpu=workers, timeout=60 * MINUTES) | ||
def run_locust(args: list, wait=False): | ||
import subprocess | ||
|
||
process = subprocess.Popen(["locust"] + args) | ||
if wait: | ||
process.wait() | ||
return process.returncode | ||
|
||
|
||
@app.local_entrypoint() | ||
def main( | ||
r: float = 1.0, | ||
u: int = 36, | ||
t: str = "1m", # no more than the timeout of run_locust, one hour | ||
): | ||
args = default_args + [ | ||
"--spawn-rate", | ||
str(r), | ||
"--users", | ||
str(u), | ||
"--run-time", | ||
t, | ||
] | ||
|
||
html_report_file = OUT_DIRECTORY / "report.html" | ||
args += [ | ||
"--headless", # run without browser UI | ||
"--autostart", # start test immediately | ||
"--autoquit", # stop once finished... | ||
"10", # ...but wait ten seconds | ||
"--html", # output an HTML-formatted report | ||
html_report_file, # to this location | ||
] | ||
|
||
if exit_code := run_locust.remote(args, wait=True): | ||
SystemExit(exit_code) | ||
else: | ||
print("finished successfully") |
50 changes: 50 additions & 0 deletions
50
07_web_endpoints/fasthtml-checkboxes/checkboxes-locustfile.py
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,50 @@ | ||
# --- | ||
# lambda-test: false | ||
# pytest: false | ||
# --- | ||
import random | ||
|
||
from constants import N_CHECKBOXES | ||
from locust import HttpUser, between, task | ||
|
||
|
||
class CheckboxesUser(HttpUser): | ||
wait_time = between(0.01, 0.1) # Simulates a wait time between requests | ||
|
||
def load_homepage(self): | ||
""" | ||
Simulates a user loading the homepage and fetching the state of the checkboxes. | ||
""" | ||
self.client.get("/") | ||
|
||
@task(10) | ||
def toggle_random_checkboxes(self): | ||
""" | ||
Simulates a user toggling a random checkbox. | ||
""" | ||
n_checkboxes = random.binomialvariate( # approximately poisson at 5 | ||
n=100, | ||
p=0.1, | ||
) | ||
for _ in range(min(n_checkboxes, 1)): | ||
checkbox_id = int( | ||
N_CHECKBOXES * random.random() ** 2 | ||
) # Choose a random checkbox between 0 and 9999, more likely to be closer to 0 | ||
self.client.post( | ||
f"/checkbox/toggle/{checkbox_id}/{self.id}", | ||
name="/checkbox/toggle", | ||
) | ||
|
||
@task(1) | ||
def poll_for_diffs(self): | ||
""" | ||
Simulates a user polling for any outstanding diffs. | ||
""" | ||
self.client.get(f"/diffs/{self.id}", name="/diffs") | ||
|
||
def on_start(self): | ||
""" | ||
Called when a simulated user starts, typically used to initialize or login a user. | ||
""" | ||
self.id = str(random.randint(1, 9999)) | ||
self.load_homepage() |
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,4 @@ | ||
# --- | ||
# lambda-test: false | ||
# --- | ||
N_CHECKBOXES = 10_000 # feel free to increase, if you dare! |
File renamed without changes
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
File renamed without changes.