diff --git a/load_test.py b/load_test.py index d9280ff..e6952c7 100644 --- a/load_test.py +++ b/load_test.py @@ -4,6 +4,7 @@ Использование: python load_test.py --n 5 python load_test.py --n 10 --url https://example.com --time 15 + python load_test.py --n 10 --parallel 3 # не более 3 одновременных запусков """ import asyncio @@ -11,14 +12,13 @@ import sys import time import argparse from pathlib import Path -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import List, Optional sys.path.insert(0, str(Path(__file__).parent)) import psutil -from config.settings import settings from core.logger import setup_logger from managers.proxy_manager import ProxyManager from services.browser_service import BrowserService @@ -47,30 +47,35 @@ async def run_one_browser( url: str, visit_time: int, results: List[BrowserResult], + launch_sem: asyncio.Semaphore, ) -> None: - t0 = time.perf_counter() - try: - result = await browser_service.visit_page(url, reading_time=visit_time) - duration = time.perf_counter() - t0 - results.append(BrowserResult( - index=index, - success=result.success, - duration=duration, - error=result.error, - final_url=result.final_url, - )) - status = "OK" if result.success else f"FAIL: {result.error}" - print(f" [Browser {index:>2}] {status} — {duration:.1f}s | RAM: {_mem_mb():.0f} MB") - except Exception as e: - duration = time.perf_counter() - t0 - results.append(BrowserResult(index=index, success=False, duration=duration, error=str(e))) - print(f" [Browser {index:>2}] EXCEPTION: {e} — {duration:.1f}s") + # Семафор ограничивает сколько браузеров стартует одновременно. + # Когда браузер поднялся (AsyncCamoufox.__aenter__ завершён), семафор освобождается + # и следующий браузер может начать запуск. Так избегаем race на ресурсы ОС. + async with launch_sem: + t0 = time.perf_counter() + try: + result = await browser_service.visit_page(url, reading_time=visit_time) + duration = time.perf_counter() - t0 + results.append(BrowserResult( + index=index, + success=result.success, + duration=duration, + error=result.error, + final_url=result.final_url, + )) + status = "OK" if result.success else f"FAIL: {result.error}" + print(f" [Browser {index:>2}] {status} — {duration:.1f}s | RAM: {_mem_mb():.0f} MB") + except Exception as e: + duration = time.perf_counter() - t0 + results.append(BrowserResult(index=index, success=False, duration=duration, error=str(e))) + print(f" [Browser {index:>2}] EXCEPTION: {e} — {duration:.1f}s") -async def run_load_test(n: int, url: str, visit_time: int) -> None: +async def run_load_test(n: int, url: str, visit_time: int, parallel: int) -> None: print(f"\n{'='*60}") print(f" Нагрузочный тест") - print(f" Браузеров: {n} | URL: {url} | Время: {visit_time}s") + print(f" Браузеров: {n} | Одновременный запуск: {parallel} | URL: {url} | Время: {visit_time}s") # Инфо о системе mem = psutil.virtual_memory() @@ -91,13 +96,14 @@ async def run_load_test(n: int, url: str, visit_time: int) -> None: ram_before = _mem_mb() print(f" RAM до старта: {ram_before:.0f} MB") - print(f"\n Запускаем {n} браузеров...\n") + print(f"\n Запускаем {n} браузеров (волнами по {parallel})...\n") + launch_sem = asyncio.Semaphore(parallel) wall_start = time.perf_counter() tasks = [ asyncio.create_task( - run_one_browser(i + 1, browser_service, url, visit_time, results) + run_one_browser(i + 1, browser_service, url, visit_time, results, launch_sem) ) for i in range(n) ] @@ -137,13 +143,15 @@ async def run_load_test(n: int, url: str, visit_time: int) -> None: def main(): parser = argparse.ArgumentParser(description="Load test: N параллельных браузеров") - parser.add_argument("--n", type=int, default=3, help="Число параллельных браузеров (по умолчанию: 3)") + parser.add_argument("--n", type=int, default=3, help="Всего браузеров (по умолчанию: 3)") + parser.add_argument("--parallel", type=int, default=2, help="Макс. одновременных запусков (по умолчанию: 2)") parser.add_argument("--url", type=str, default="https://httpbin.org/headers", help="URL для посещения") parser.add_argument("--time", type=int, default=10, dest="visit_time", help="Время посещения в секундах (по умолчанию: 10)") args = parser.parse_args() + parallel = min(args.parallel, args.n) try: - asyncio.run(run_load_test(args.n, args.url, args.visit_time)) + asyncio.run(run_load_test(args.n, args.url, args.visit_time, parallel)) except KeyboardInterrupt: print("\nПрервано.")