diff --git a/07_web_endpoints/fasthtml-checkboxes/checkboxes-load-test.py b/07_web_endpoints/fasthtml-checkboxes/checkboxes-load-test.py
new file mode 100644
index 000000000..ea295a4a7
--- /dev/null
+++ b/07_web_endpoints/fasthtml-checkboxes/checkboxes-load-test.py
@@ -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")
diff --git a/07_web_endpoints/fasthtml-checkboxes/checkboxes-locustfile.py b/07_web_endpoints/fasthtml-checkboxes/checkboxes-locustfile.py
new file mode 100644
index 000000000..5486600e8
--- /dev/null
+++ b/07_web_endpoints/fasthtml-checkboxes/checkboxes-locustfile.py
@@ -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()
diff --git a/07_web_endpoints/fasthtml-checkboxes/constants.py b/07_web_endpoints/fasthtml-checkboxes/constants.py
new file mode 100644
index 000000000..5597c2867
--- /dev/null
+++ b/07_web_endpoints/fasthtml-checkboxes/constants.py
@@ -0,0 +1,4 @@
+# ---
+# lambda-test: false
+# ---
+N_CHECKBOXES = 10_000 # feel free to increase, if you dare!
diff --git a/07_web_endpoints/fasthtml-checkboxes-ui.png b/07_web_endpoints/fasthtml-checkboxes/fasthtml-checkboxes-ui.png
similarity index 100%
rename from 07_web_endpoints/fasthtml-checkboxes-ui.png
rename to 07_web_endpoints/fasthtml-checkboxes/fasthtml-checkboxes-ui.png
diff --git a/07_web_endpoints/fasthtml_checkboxes.py b/07_web_endpoints/fasthtml-checkboxes/fasthtml_checkboxes.py
similarity index 95%
rename from 07_web_endpoints/fasthtml_checkboxes.py
rename to 07_web_endpoints/fasthtml-checkboxes/fasthtml_checkboxes.py
index 141f5cb16..e5018ee6e 100644
--- a/07_web_endpoints/fasthtml_checkboxes.py
+++ b/07_web_endpoints/fasthtml-checkboxes/fasthtml_checkboxes.py
@@ -1,6 +1,6 @@
# ---
# deploy: true
-# cmd: ["modal", "serve", "07_web_endpoints/fasthtml_checkboxes.py"]
+# cmd: ["modal", "serve", "07_web_endpoints.fasthtml-checkboxes.fasthtml_checkboxes"]
# mypy: ignore-errors
# ---
@@ -24,13 +24,13 @@
import modal
+from .constants import N_CHECKBOXES
+
app = modal.App("example-checkboxes")
db = modal.Dict.from_name("example-checkboxes-db", create_if_missing=True)
-css_path_local = Path(__file__).parent / "fasthtml_checkboxes.css"
-css_path_remote = Path("/assets/fasthtml_checkboxes.css")
-
-N_CHECKBOXES = 10_000 # feel free to increase, if you dare!
+css_path_local = Path(__file__).parent / "styles.css"
+css_path_remote = Path("/assets/styles.css")
@app.function(
diff --git a/07_web_endpoints/fasthtml_checkboxes.css b/07_web_endpoints/fasthtml-checkboxes/styles.css
similarity index 100%
rename from 07_web_endpoints/fasthtml_checkboxes.css
rename to 07_web_endpoints/fasthtml-checkboxes/styles.css