- loadtest: у каждого запроса своя сессия, режим --mode v2 (задача + опрос), X-API-Key; замер v1/v2 в docs/Deployment.md. - make push ver=... — образы robotex-api и robotex-worker в docker.sokolab.xyz; prod-compose и make release ссылаются на образы реестра. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1a4bd8d672
commit
11ba83140d
4 files changed
+86
-23
No files matched your search
+42
-11
@@ -1,8 +1,9 @@
|
||||
"""Нагрузочный тест /v1/solve: N параллельных запросов на каждом уровне + пиковое потребление
|
||||
контейнера по `docker stats`.
|
||||
"""Нагрузочный тест: N параллельных запросов на каждом уровне (каждый — в своей сессии) + пиковое
|
||||
потребление контейнера воркера по `docker stats`.
|
||||
|
||||
docker compose --profile loadtest up -d
|
||||
python tests/loadtest.py --levels 1 5 10 15
|
||||
python tests/loadtest.py --api-key rbx_... --levels 1 5 10 15 # /v1/solve
|
||||
python tests/loadtest.py --api-key rbx_... --mode v2 --levels 4 8 # /v2/tasks + опрос
|
||||
|
||||
Помимо status ответа, неуспех раскладывается по причинам: нет слотов, застряли на Cloudflare,
|
||||
упало действие, исключение до действий.
|
||||
@@ -82,12 +83,43 @@ class StatsSampler(threading.Thread):
|
||||
self.stop_event.wait(1)
|
||||
|
||||
|
||||
async def one(client: httpx.AsyncClient, api: str, payload: dict) -> tuple[str, float]:
|
||||
class TaskResult:
|
||||
"""Ответ /v2 в виде, понятном classify(): status_code и json()."""
|
||||
|
||||
def __init__(self, status_code: int, data: dict):
|
||||
self.status_code, self._data = status_code, data
|
||||
|
||||
def json(self) -> dict:
|
||||
return self._data
|
||||
|
||||
|
||||
async def solve_v2(client: httpx.AsyncClient, api: str, payload: dict):
|
||||
created = await client.post(f"{api}/v2/tasks", json=payload)
|
||||
if created.status_code != 202:
|
||||
return created
|
||||
url = f"{api}{created.json()['url']}"
|
||||
while True:
|
||||
await asyncio.sleep(0.5)
|
||||
task = (await client.get(url)).json()
|
||||
if task["status"] == "done":
|
||||
return TaskResult(200, task["result"])
|
||||
if task["status"] not in ("queued", "running"):
|
||||
return TaskResult(200, {"status": "err", "code": task["status"], "error": task.get("error")})
|
||||
|
||||
|
||||
async def one(args, payload: dict) -> tuple[str, float]:
|
||||
# у каждого запроса своя сессия: общий клиент запомнил бы cookie session_id, и задачи одной
|
||||
# сессии выполнялись бы по очереди
|
||||
headers = {"X-API-Key": args.api_key} if args.api_key else {}
|
||||
t0 = time.monotonic()
|
||||
try:
|
||||
resp = await client.post(f"{api}/v1/solve", json=payload)
|
||||
except Exception as e:
|
||||
resp = e
|
||||
async with httpx.AsyncClient(timeout=args.http_timeout, trust_env=False, headers=headers) as client:
|
||||
try:
|
||||
if args.mode == "v2":
|
||||
resp = await solve_v2(client, args.api, payload)
|
||||
else:
|
||||
resp = await client.post(f"{args.api}/v1/solve", json=payload)
|
||||
except Exception as e:
|
||||
resp = e
|
||||
return classify(resp), time.monotonic() - t0
|
||||
|
||||
|
||||
@@ -95,9 +127,7 @@ async def run_level(n: int, args) -> None:
|
||||
sampler = StatsSampler(args.container)
|
||||
sampler.start()
|
||||
t0 = time.monotonic()
|
||||
headers = {"X-API-Key": args.api_key} if args.api_key else {}
|
||||
async with httpx.AsyncClient(timeout=args.http_timeout, trust_env=False, headers=headers) as client:
|
||||
results = await asyncio.gather(*(one(client, args.api, build_payload(args)) for _ in range(n)))
|
||||
results = await asyncio.gather(*(one(args, build_payload(args)) for _ in range(n)))
|
||||
wall = time.monotonic() - t0
|
||||
sampler.stop_event.set()
|
||||
sampler.join()
|
||||
@@ -122,6 +152,7 @@ async def main():
|
||||
p.add_argument("--selector", default='a[href*="/project/"]')
|
||||
p.add_argument("--proxy", default="http://proxy:8080")
|
||||
p.add_argument("--captcha-type", default="cloudflare")
|
||||
p.add_argument("--mode", choices=["v1", "v2"], default="v1", help="v1 — /v1/solve, v2 — /v2/tasks с опросом")
|
||||
p.add_argument("--api-key", default=os.getenv("ROBOTEX_API_KEY"), help="X-API-Key (или ROBOTEX_API_KEY)")
|
||||
# Chrome работает в воркере — его docker stats и снимаем
|
||||
p.add_argument("--container", default="robotex-worker-1")
|
||||
|
||||
Reference in new issue
Block a user