Lasttest mit locust und uv
Dieses Skill führt ein Projekt von „Wie viel hält es aus?“ zu einem Bericht mit Zahlen, auf die das Team reagieren kann. Führe die Schritte der Reihe nach aus, denn jeder Schritt baut auf dem vorherigen auf. Der Benutzer trifft zwei Entscheidungen (die Teststufe und das Verkehrsszenario), und zwar erst, nachdem er gesehen hat, was du im Code gefunden hast.
$SKILL ist der Ordner dieses Skills, also das Verzeichnis, aus dem du diese SKILL.md liest. Setze es einmal in der Shell (SKILL=/pfad/zu/locust-load-test). Der Lasttest selbst befindet sich im Ziel-Repository, in loadtest/ oder wo immer der Benutzer es wünscht.
1 Entdecken -> loadtest/endpoints.json jeder Endpunkt, mit seinen Aufrufern und Kostenhinweisen
2 Klassifizieren -> loadtest/PLAN.md Kern / Unterstützung / Peripherie Tabelle; dem Benutzer zeigen
3 Fragen -> Stufen + Szenario + Umgebung + Einschränkungen
4 Erstellen -> loadtest/ (ein uv-Projekt) harness.py, payloads/, Startskripte
5 Ausführen -> loadtest/results/<run>/ zuerst Rauchtest, dann die gewählten Stufen, die riskanteste zuletzt
6 Sammeln -> results/<run>/metrics, profiles serverseitige Ansicht, wenn erreichbar
7 Berichten -> loadtest/report.html + Erkenntnisse in einfachen Worten
1. Entdecken der Endpunkte
uv run $SKILL/scripts/discover_endpoints.py . -o loadtest/endpoints.json --markdown loadtest/endpoints.md
Der Scanner liest den Code und schreibt nichts anderes. Er findet Routendeklarationen in den gängigen Stacks: Python, Node, JVM, Go, Ruby, .NET und PHP Frameworks, sowie OpenAPI-Dateien, GraphQL-Schemata, gRPC-Protos, Websockets und SSE. Er löst Router-Präfixe und Mounts, wo möglich. Er zählt auch die Client-Aufrufstellen (fetch, axios, httpx, requests, generierte Clients, Tests und die Links, Formulare und htmx-Attribute von serverseitig gerenderten Vorlagen) für jeden Endpunkt, damit du sehen kannst, was die eigene UI und die Dienste des Produkts verwenden. Jeder Eintrag enthält die Methode, den Pfad, datei:zeile, den Handler, Authentifizierungshinweise, Body-Hinweise, einen Streaming-Hinweis, Kostenhinweise, die Aufrufe, die der Handler tätigt, und wer ihn aufruft.
Die eigene Routenliste eines Frameworks schlägt jeden statischen Scan, da das Framework bereits alle Präfixe, Mounts und generierten Routen aufgelöst hat. Wenn die App eine solche produzieren kann, verwende sie als Quelle der Wahrheit. Das bedeutet ein OpenAPI-Dokument von einem laufenden Dev-Server (/openapi.json), flask routes, bin/rails routes, php artisan route:list --json oder Spring's /actuator/mappings:
uv run $SKILL/scripts/discover_endpoints.py . --route-dump http://localhost:8000/openapi.json -o loadtest/endpoints.json
Lies dann die Ausgabe kritisch, bevor du ihr vertraust:
unmatched_client_callssind entweder Routen, die der Scan übersehen hat, oder Aufrufe anderer Dienste.prefix_resolved: falseundnotesmarkieren die Schätzungen.shared_handler: truebedeutet, dass eine Funktion mehrere Methoden bedient und der Scan nicht sagen konnte, zu welcher Methode ihre Kostenhinweise gehören; lies diesen Handler.existing_load_testslistet bereits im Repository vorhandene Locustfiles oder k6-Skripte auf. Verwende ihre Payloads wieder.- Überprüfe stichprobenartig fünf Einträge anhand der Quelldateien.
Lies references/discovery.md, wenn das Inventar dünn aussieht, wenn ein Stack nicht abgedeckt ist oder wenn du den Befehl für den Routen-Dump eines Frameworks benötigst.
2. Klassifizieren: Kern, Unterstützung, Peripherie
Sortiere die Endpunkte nach deinem Urteil. Sortiere sie nicht nach Stichworten: Derselbe /search ist in einem Shop Kern und in einer Admin-Konsole Unterstützung.
| Stufe | Was es ist | Typische Beispiele |
|---|---|---|
| Kern | Wofür Benutzer kommen, und wo normalerweise die schwere Arbeit passiert: Berechnungen, Modellaufrufe, Datenverarbeitung, der Hauptschreibpfad | Bezahlvorgang in einem Shop; Konvertierung oder Extraktion in einem Dokumentdienst; Vervollständigungen in einem Chat-API |
| Unterstützung | Was den Kern nutzbar macht | Login, Sitzungen, Einstellungen, Listen und Suche über die eigenen Daten des Benutzers, Uploads, die den Kern speisen, Admin-CRUD |
| Peripherie | Wofür das Produkt nicht existiert | Health, Metriken, Doku, statische Dateien, Debug-Routen, Webhooks von Diensten, die es nicht besitzt |
Abzuwägende Beweise:
- was die README und die Dokumentation als Zweck des Produkts nennen;
- was die UI am häufigsten aufruft (
client_calls) und was die Tests üben (test_calls); - was der Handler kostet: Folge den
callsin die Service-Schicht, dacost_hintsnur den Handler-Body sieht. Modellaufrufe, DB-Schreibvorgänge, externe Aufrufe, CPU-Arbeit (Passwort-Hashing, Komprimierung, Bilder:cpu), Dateiverarbeitung und Queue-Publishes sind das, was einen Endpunkt teuer macht; - Benennung;
- was die eigene Überwachung oder die Logs des Teams als am stärksten ausgelastet ausweisen, falls du sie sehen kannst.
Schreibe loadtest/PLAN.md mit dieser Tabelle, einer Zeile pro Endpunkt, den du belasten willst, und einer kurzen Liste der Endpunkte, die du weggelassen hast, mit Begründung:
| Endpunkt | Stufe | Warum | Kostenhinweise | Payload-Quelle |
|---|---|---|---|---|
| POST /api/orders/{id}/checkout | Kern | der Kaufpfad; UI ruft ihn von 3 Stellen auf | DB-Schreibvorgänge, Zahlungs-API-Aufruf | tests/fixtures/order.json |
Zeige diese Tabelle dem Benutzer bevor du die Fragen stellst, und bitte um Korrekturen. Eine falsche Stufe lässt jede spätere Zahl die falsche Frage beantworten, und der Benutzer kennt sein Produkt besser als jeder Scan.
3. Den Benutzer fragen
Verwende das AskUserQuestion-Tool. Falls es nicht verfügbar ist, stelle dieselben Fragen als einfache nummerierte Liste und warte auf die Antworten. Frage erst, nachdem der Benutzer die Plantabelle gesehen hat.
- F1 Kernstufe. Stufe 1, 2 oder 3.
- F2 Unterstützungsstufe. Stufe 1, 2 oder 3, oder Unterstützung weglassen.
- F3 Benutzerlast-Szenario. Zwei bis vier Optionen, abgeleitet von diesem Projekt (siehe unten), wobei die von dir empfohlene zuerst kommt, gekennzeichnet mit „(Empfohlen)", und einer Zeile, warum sie passt.
- F4 Zielumgebung. Welches Deployment belastet werden soll (Staging, ein lokaler Stack, eine Lasttest-Kopie). Belaste niemals ein Produktivsystem, es sei denn, der Benutzer stimmt dem explizit zu. Last schwappt auf gemeinsame Datenbanken, Mandanten und Rechnungen über.
| Stufe | Läufe | Antworten | Kosten |
|---|---|---|---|
| 1 Rauchtest / Baseline | niedrige konstante Last (1 bis wenige Benutzer) für einige Minuten | Funktioniert jeder Pfad, und wie hoch ist die Latenz ohne Konkurrenz? | Minuten; vernachlässigbares Risiko |
| 2 Erwartete Last | schrittweiser Anstieg auf die erwartete Spitze, halten, dann ein kurzer Soak auf diesem Niveau | Erreicht es das Latenzziel bei seiner höchsten Auslastung, und mit welchem Spielraum? | etwa eine Stunde; das Ziel läuft mit seiner tatsächlichen Spitze |
| 3 Stress / Kapazität | schrittweiser Anstieg, bis eine Stoppregel auslöst (der Knickpunkt und der Bruchpunkt), plus ein Spike | Wo bricht es, wie, und erholt es sich? | ein bis zwei Stunden; das Ziel wird gesättigt und kann umfallen, braucht also ein Zeitfenster und jemanden, der zusieht |
Leite die Optionen für F3 aus dem ab, was das Projekt über seinen Verkehr sagt: README und Doku (wer es nutzt, Benutzeranzahlen, Mandanten), Konfiguration (Ratenbegrenzungen, Worker- und Replikatanzahlen, Autoscaler-Grenzen, Warteschlangengrößen, Verbindungspools), Cron- und Batch-Jobs sowie die Timeouts und Wiederholungen der Aufrufer selbst. Übliche Formen sind ein geschäftstäglicher schrittweiser Anstieg, ein Morgen-Spike, ein stetiger Soak, ein Integrations-Burst von einem anderen Dienst, ein Batch-Import und gemischter interaktiver plus Hintergrundverkehr. Jede Option nennt ihre Zahlen: Benutzer, Generierungsrate, Bedenkzeit, Dauer und Anfragemischung. Wenn das Projekt nichts über seinen Verkehr sagt, sage es, nenne die Annahme, auf der jede Zahl basiert, und frage in F3 nach der erwarteten Spitze.
references/tiers-and-scenarios.md zeigt, wie man die Konfiguration in diese Zahlen umwandelt. Lies es, bevor du fragst.
Nach den Antworten kläre zwei Dinge im Klartext:
- Anmeldeinformationen. Sie stammen aus einer Datei oder einer Umgebungsvariable, die die Testumgebung liest. Sie werden niemals in den Chat eingefügt.
- Einschränkungen. Liste die gefundenen auf und frage nach den restlichen: Ratenbegrenzungen, Lizenz- oder Aktivierungsfenster, was ein Neustart kostet, gemeinsame Mandanten und die Kosten pro Anfrage (Modell-Tokens, bezahlte APIs). Ein Neustart innerhalb eines Lizenzfensters kann einen Dienst für einen Tag sperren, und ein paar parallele schwere Jobs mit einer riesigen Eingabe können ein Ziel lahmlegen. Frage vor der Last, nicht danach.
- Datenzustand. Wie viele Zeilen die Listen und Suchen in der realen Nutzung enthalten, ob die Daten zwischen den Läufen zurückgesetzt werden können und welche Zeilen ein Testbenutzer bearbeiten oder löschen darf. Eine leere Tabelle macht eine Listenseite kostenlos, und Zeilen, die ein Lauf hinzufügt, sorgen dafür, dass jeder spätere Schritt mehr liest als der vorherige.
4. Die Testumgebung erstellen
mkdir -p loadtest && cd loadtest
cp $SKILL/templates/pyproject.toml . && uv sync # eigener .venv und uv.lock; locust festgelegt, Python 3.11+
cp $SKILL/templates/{harness.py,run_local.sh,run_plan.sh,collect.sh,seed.py,loadtest.env.example} .
cp $SKILL/templates/run_docker.sh . # wenn der Generator im Docker-Netzwerk des Ziels läuft
cp -r $SKILL/templates/{run_k8s.sh,k8s} . # wenn es im Cluster des Ziels läuft
git -C .. status --short # nur loadtest/ darf erscheinen
Führe hier kein uv init aus. In einem Repository, das selbst ein uv-Projekt ist, fügt es loadtest zum [tool.uv.workspace] der App hinzu, installiert locust in den .venv der App und schreibt ein app-weites uv.lock. Falls das passiert ist, mache es rückgängig: git checkout -- pyproject.toml in der App, lösche das neue uv.lock und führe dort uv sync aus. Fülle dann die Vorlage aus:
REQUESTS. Fülle die Tabelle inharness.pyaus PLAN.md: Stufe, Name, Gewicht, Mischung, Body und erwarteter Status. Nimm Payloads aus den Tests, Fixtures und Schemas des Projekts und lege sie inloadtest/payloads/ab. Verwende niemals Produktionsdaten. Eine serverseitig gerenderte App antwortet auch mit 200 für ihre Login-Seite und ihre Fehlerseiten, also überprüfe, was nur die richtige Seite hat:expect_textundexpect_location, wo eine Weiterleitung die Antwort ist. Eine Zeile, die weitergeleitet wird, ohne es zu sagen, schlägt fehl.REQ_TIMEOUT. Setze es auf das Timeout des echten Aufrufers: das Gateway, der Abbruch des Frontends, das Client-SDK oder das Lese-Timeout des Proxys. Die Testumgebung hält jede Anfrage daran als Frist für die gesamte Antwort fest, sodass eine spätere Antwort als „Frist überschritten“ fehlschlägt, anstatt als Erfolg zu zählen, den das Produkt bereits als Fehler gemeldet hätte.MIX. Gib jeder Verkehrsmischung mit unterschiedlichen Kosten ihr eigenes Tag und führe jede Mischung als eigenes Szenario aus (kurze Chat-große Eingaben, dokumentgroße Eingaben, eine Option, die extra kostet). Ihre Kapazitäten können sich um ein Vielfaches unterscheiden, und ein gemischter Lauf versteckt die teure.- Auth. Setze
AUTHso, dass sich jeder simulierte Benutzer einmal anmeldet, nicht einmal pro Anfrage. Ein Formular-Login mit einem Session-Cookie benötigt auchLOGIN_EXPECT_LOCATIONoderLOGIN_EXPECT_TEXT, da ein falsches Passwort ebenfalls mit 200 antwortet (sieheloadtest.env.example). Anmeldungen erhalten die Stufeloginund bleiben außerhalb der Schrittnummern und Stoppregeln: Jede landet am Start des Schritts, der ihren Benutzer hinzugefügt hat, und ein langsamer Passwort-Hash würde den Lauf mit Kosten beenden, die die konstante Last nie zahlt. Um den Login selbst zu belasten, mache ihn zu einer Zeile. - Daten. Fülle
seed.pyfür die App und führepython3 seed.py up --users N --rows Maus: Über die API der App erhält jedes Testkonto seine eigenen Zeilen, dieSEED_MANIFESTan seinen Benutzer weitergibt; die Zeilenschlüsselcaptureundconsumeverfolgen, was ein Benutzer erstellt und löscht. Paare Erstellungen mit Löschungen oder reseede zwischen den Läufen. - Journeys. Mehrstufige Abläufe (hochladen dann abfragen, erstellen dann lesen) sind eine Methode einer Benutzerklasse; die Vorlage enthält ein Beispiel.
references/harness.md deckt jedes Feld, die Formen, die Auth-Modi, Streaming, Nicht-HTTP-Protokolle und die drei Platzierungen ab. Lies es während der Erstellung.
5. Sicher ausführen
- Zuerst Rauchtest. Führe
RUN_NAME=smoke SHAPE=smoke ./run_local.shaus: Ein Benutzer pro Benutzerklasse sendet jede Zeile einmal, in Tabellenreihenfolge (setze die Zeile, die eine ID erstellt, vor die Zeilen, die sie verwenden), dann Leerlauf. Das Log endet mitNOTE smoke: every row was sentoder nennt die Zeilen, die nicht gesendet wurden. Überprüfe null Fehler, plausible Latenz und korrekte Bodies. Es beweist die Verkabelung, bevor etwas Last kostet. - Überprüfe, ob die Maschinen ruhig sind. Vor einem Lauf, der die Kapazität misst, führe
uptimeundvmstat 1 5auf dem Generator und auf dem Host des Ziels aus. Andere Jobs auf denselben Kernen machen jede Zahl zu einem Maß für die Konkurrenz. Die Testumgebung tastet ihren eigenen Host vor jedem Lauf inrun.jsonab (host_checkund ein Hinweis); trage die Zahlen des Zielhosts inNOTESein, wenn es eine andere Maschine ist. Der Bericht setzt ein Banner auf einen Lauf, dessen Host- oder Generator-CPU während seiner Schritte 90 % erreicht hat, und schränkt seinen Knickpunkt ein. Wenn eine Maschine beschäftigt ist, warte, binde sie an freie Kerne (taskset) oder verschiebe sie, und sage es. - Dann die gewählten Stufen, die sicherste zuerst. Führe erwartete Last, dann Soak, dann Stress, dann Spike aus. Riskante Eingaben (die größten Payloads, die teuren Optionen) kommen zuletzt, damit, wenn sie das Ziel verletzen, nichts davor verloren ist.
run_plan.shführt die Liste der Reihe nach aus und startet jeden Lauf erst, wenn der Health-Check des Ziels bestanden ist. - Platziere den Generator neben den echten Aufrufern, wenn der Netzwerkpfad wichtig ist: derselbe Cluster, dasselbe VNet oder Compose-Netzwerk (
run_k8s.sh,run_docker.sh). Ein Laptop-Lauf misst das Internet genauso wie den Dienst. Jeder Lauf zeichnetGENERATORauf, und die eigene CPU des Generators geht nachmetrics/client.csv; bei über etwa 90 % CPU beschreiben die Zahlen den Generator. - Respektiere die Stoppregeln. Die Testumgebung beendet einen Lauf, wenn die Fehlerquote
STOP_FAIL_RATIOüberschreitet, wenn p95STOP_P95_MSüberschreitet, wenn mehr als die Hälfte der letzten 20 s fehlgeschlagen sind oder wenn die meisten Anmeldungen fehlgeschlagen sind. Viele Dienste reihen ein, anstatt Last abzuwerfen: null Fehler und Latenz, die nur wächst. Setze also die p95-Obergrenze auf das Timeout des Aufrufers oder das SLO. Überschreite eine Stoppregel nicht „um zu sehen". - Beobachte die Gesundheit zwischen den Läufen und lasse Warteschlangen abfließen (
COOLDOWNinrun_plan.sh). - Räume alles auf, was du erstellt hast:
./run_k8s.sh down(Pod, ConfigMaps, Secret), gestoppte Container, Testbenutzer und Testdaten. Liste für den Benutzer alles auf, was nicht entfernt werden kann, wie z. B. soft-gelöschte Zeilen oder Audit-Einträge.
Lies references/safety.md vor dem ersten Lauf, der über den Rauchtest hinausgeht, und vor jedem Lauf gegen ein gemeinsames System.
6. Serverseitig sammeln
Client-Zahlen sagen, was passiert ist. Server-Zahlen sagen, warum. Starte die Sammler vor locust, stoppe sie danach und schreibe in dasselbe Laufverzeichnis:
uv run $SKILL/scripts/collect_server.sh loadtest/results/<run> <target-host> <target-port>
...