""" Точный тест потребления памяти с отслеживанием ВСЕХ процессов. Измеряет память Python + браузерных процессов отдельно. Запуск: python -u test_memory_processes.py """ import asyncio import sys import os import time import traceback from datetime import datetime import gc sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) def print_flush(*args, **kwargs): print(*args, **kwargs, flush=True) import psutil from config.settings import settings from managers.proxy_manager import ProxyManager from services.browser_service import BrowserService print_flush("=" * 70) print_flush("🧠 ТОЧНЫЙ ТЕСТ ПАМЯТИ (ВСЕ ПРОЦЕССЫ)") print_flush("=" * 70) print_flush(f"Время: {datetime.now().strftime('%H:%M:%S')}") print_flush() def get_process_tree_memory() -> dict: """ Получает память текущего процесса и всех его дочерних процессов. Возвращает детальную информацию. """ current = psutil.Process() total_rss = current.memory_info().rss children = [] try: for child in current.children(recursive=True): try: child_rss = child.memory_info().rss children.append({ "pid": child.pid, "name": child.name()[:50], "rss_mb": child_rss / (1024 * 1024), "status": child.status() }) total_rss += child_rss except (psutil.NoSuchProcess, psutil.AccessDenied): pass except (psutil.NoSuchProcess, psutil.AccessDenied): pass return { "python_rss_mb": current.memory_info().rss / (1024 * 1024), "children_count": len(children), "children_rss_mb": sum(c["rss_mb"] for c in children), "total_rss_mb": total_rss / (1024 * 1024), "children": children } def get_browser_processes() -> list: """ Ищет все браузерные процессы (chromium, chrome, firefox). """ browser_names = ["chromium", "chrome", "firefox", "camoufox", "playwright"] browsers = [] for proc in psutil.process_iter(['pid', 'name', 'memory_info']): try: name = proc.info['name'].lower() if proc.info['name'] else "" if any(bn in name for bn in browser_names): browsers.append({ "pid": proc.info['pid'], "name": proc.info['name'], "rss_mb": proc.info['memory_info'].rss / (1024 * 1024) }) except (psutil.NoSuchProcess, psutil.AccessDenied): pass return browsers def get_system_memory() -> dict: """Получает общую информацию о системной памяти.""" mem = psutil.virtual_memory() swap = psutil.swap_memory() return { "total_gb": mem.total / (1024**3), "available_gb": mem.available / (1024**3), "used_percent": mem.percent, "used_gb": mem.used / (1024**3), "swap_total_gb": swap.total / (1024**3) if swap.total > 0 else 0, "swap_used_gb": swap.used / (1024**3) if swap.total > 0 else 0, } async def monitor_memory_during_operation(duration: int, interval: float = 0.5): """ Мониторит память во время операции. Возвращает макс, мин, среднее и временной ряд. """ samples = [] start = time.time() while time.time() - start < duration: mem_info = get_process_tree_memory() system_mem = get_system_memory() browser_procs = get_browser_processes() samples.append({ "timestamp": time.time() - start, "python_mb": mem_info["python_rss_mb"], "children_mb": mem_info["children_rss_mb"], "total_mb": mem_info["total_rss_mb"], "browser_processes": len(browser_procs), "browser_rss_mb": sum(b["rss_mb"] for b in browser_procs), "system_used_percent": system_mem["used_percent"], "children_count": mem_info["children_count"] }) await asyncio.sleep(interval) return samples async def test_single_visit_memory(): """Тест памяти при одном посещении.""" print_flush("\n" + "=" * 70) print_flush("🧪 ТЕСТ: ОДИНОЧНОЕ ПОСЕЩЕНИЕ (ПОЛНЫЙ ЦИКЛ)") print_flush("=" * 70) # Информация о системе sys_mem = get_system_memory() print_flush(f"\n💻 Система:") print_flush(f" RAM всего: {sys_mem['total_gb']:.1f} GB") print_flush(f" RAM доступно: {sys_mem['available_gb']:.1f} GB") print_flush(f" RAM используется: {sys_mem['used_percent']:.1f}%") print_flush(f" Swap: {sys_mem['swap_total_gb']:.1f} GB") # Память до mem_before = get_process_tree_memory() browsers_before = get_browser_processes() print_flush(f"\n📊 До запуска:") print_flush(f" Python процесс: {mem_before['python_rss_mb']:.1f} MB") print_flush(f" Дочерние процессы: {mem_before['children_count']} шт, " f"{mem_before['children_rss_mb']:.1f} MB") print_flush(f" ВСЕГО: {mem_before['total_rss_mb']:.1f} MB") print_flush(f" Браузерных процессов в системе: {len(browsers_before)}") if browsers_before: for b in browsers_before: print_flush(f" - {b['name']} (PID {b['pid']}): {b['rss_mb']:.1f} MB") # Запускаем мониторинг print_flush(f"\n🔄 Запуск браузера и посещение...") monitor_task = asyncio.create_task( monitor_memory_during_operation(duration=60, interval=0.3) ) proxy_manager = ProxyManager() browser_service = BrowserService(proxy_manager) url = "https://httpbin.org/headers" reading_time = 15 print_flush(f" URL: {url}") print_flush(f" Время чтения: {reading_time}с") try: result = await browser_service.visit_page(url, reading_time) print_flush(f" Статус: {'✅' if result.success else '❌'}") except Exception as e: print_flush(f" ❌ Ошибка: {e}") # Ждем завершения мониторинга await asyncio.sleep(3) monitor_task.cancel() try: samples = await monitor_task except asyncio.CancelledError: samples = [] # Память после await asyncio.sleep(2) gc.collect() await asyncio.sleep(1) mem_after = get_process_tree_memory() browsers_after = get_browser_processes() print_flush(f"\n📊 После посещения:") print_flush(f" Python процесс: {mem_after['python_rss_mb']:.1f} MB") print_flush(f" Дочерние процессы: {mem_after['children_count']} шт, " f"{mem_after['children_rss_mb']:.1f} MB") print_flush(f" ВСЕГО: {mem_after['total_rss_mb']:.1f} MB") print_flush(f" Браузерных процессов в системе: {len(browsers_after)}") # Анализ семплов if samples: total_samples = [s["total_mb"] for s in samples] print_flush(f"\n📈 Во время посещения ({len(samples)} замеров):") print_flush(f" ПИК всего: {max(total_samples):.1f} MB") print_flush(f" Мин всего: {min(total_samples):.1f} MB") print_flush(f" Среднее всего: {sum(total_samples)/len(total_samples):.1f} MB") browser_proc_count = [s["browser_processes"] for s in samples] if max(browser_proc_count) > 0: print_flush(f" Макс браузерных процессов: {max(browser_proc_count)}") # Дельта delta_total = mem_after["total_rss_mb"] - mem_before["total_rss_mb"] peak_total = max(s["total_mb"] for s in samples) if samples else mem_after["total_rss_mb"] peak_delta = peak_total - mem_before["total_rss_mb"] print_flush(f"\n📊 ИТОГО:") print_flush(f" Прирост TOTAL (до→после): {delta_total:+.1f} MB") print_flush(f" ПИКОВЫЙ прирост: {peak_delta:+.1f} MB") print_flush(f" Системная память: {get_system_memory()['used_percent']:.1f}%") return { "mem_before": mem_before, "mem_after": mem_after, "samples": samples, "peak_delta": peak_delta, "delta_total": delta_total } async def test_multiple_sequential(count: int = 5): """Тест последовательных посещений с отслеживанием пиков.""" print_flush("\n" + "=" * 70) print_flush(f"🧪 ТЕСТ: {count} ПОСЛЕДОВАТЕЛЬНЫХ ПОСЕЩЕНИЙ") print_flush("=" * 70) proxy_manager = ProxyManager() browser_service = BrowserService(proxy_manager) results = [] peak_memory = 0 for i in range(count): gc.collect() await asyncio.sleep(0.5) mem_before = get_process_tree_memory() system_before = get_system_memory() url = f"https://httpbin.org/headers?seq={i}" # Мониторим во время визита monitor_task = asyncio.create_task( monitor_memory_during_operation(duration=30, interval=0.3) ) try: result = await browser_service.visit_page(url, reading_time=8) success = result.success except Exception as e: success = False print_flush(f" ❌ Ошибка: {e}") await asyncio.sleep(1) monitor_task.cancel() try: samples = await monitor_task except asyncio.CancelledError: samples = [] mem_after = get_process_tree_memory() system_after = get_system_memory() visit_peak = max(s["total_mb"] for s in samples) if samples else mem_after["total_rss_mb"] if visit_peak > peak_memory: peak_memory = visit_peak delta = mem_after["total_rss_mb"] - mem_before["total_rss_mb"] sys_delta = system_after["used_percent"] - system_before["used_percent"] results.append({ "visit": i+1, "mem_before": mem_before["total_rss_mb"], "mem_after": mem_after["total_rss_mb"], "peak": visit_peak, "delta": delta, "system_delta": sys_delta, "success": success }) status = "✅" if success else "❌" print_flush(f" Визит {i+1}: {mem_before['total_rss_mb']:.1f} → " f"{mem_after['total_rss_mb']:.1f} MB (Δ={delta:+.1f} MB, " f"пик={visit_peak:.1f} MB) {status}") await asyncio.sleep(random.randint(3, 7)) # Итоги print_flush(f"\n📊 Итого за {count} посещений:") print_flush(f" ПИКОВАЯ память за все время: {peak_memory:.1f} MB") avg_peak = sum(r["peak"] for r in results) / len(results) print_flush(f" Средний пик: {avg_peak:.1f} MB") return results, peak_memory async def main(): """Главная функция.""" # Тест 1: одиночное посещение single_result = await test_single_visit_memory() # Тест 2: последовательные seq_results, seq_peak = await test_multiple_sequential(5) # Тест 3: сборка мусора и финальный замер print_flush("\n" + "=" * 70) print_flush("🧹 ОЧИСТКА ПОСЛЕ ФИНАЛЬНОГО ТЕСТА") print_flush("=" * 70) gc.collect() await asyncio.sleep(3) mem_final = get_process_tree_memory() system_final = get_system_memory() print_flush(f" Python: {mem_final['python_rss_mb']:.1f} MB") print_flush(f" Дочерние: {mem_final['children_count']} шт, {mem_final['children_rss_mb']:.1f} MB") print_flush(f" ВСЕГО: {mem_final['total_rss_mb']:.1f} MB") print_flush(f" Система: {system_final['used_percent']:.1f}%") # ИТОГОВЫЕ ОЦЕНКИ print_flush("\n" + "=" * 70) print_flush("📊 ИТОГОВЫЕ ОЦЕНКИ ДЛЯ МАСШТАБИРОВАНИЯ") print_flush("=" * 70) peak_per_visit = single_result["peak_delta"] print_flush(f"\n📈 На основе ПИКОВОГО потребления одного браузера:") print_flush(f" Пиковый прирост: {peak_per_visit:.1f} MB") # Если пиковый прирост маленький, используем абсолютные значения if peak_per_visit < 50: # Браузер уже был в памяти, используем системную дельту estimate_per_browser = 200 # Консервативная оценка MB на браузер print_flush(f" ⚠️ Прирост маленький (браузер уже в памяти)") print_flush(f" Используем консервативную оценку: {estimate_per_browser} MB/браузер") else: estimate_per_browser = peak_per_visit print_flush(f"\n📊 ОЦЕНКИ ДЛЯ РАЗНОГО КОЛИЧЕСТВА ЗАДАЧ:") print_flush(f" {'Задач':<10} {'RAM (MB)':<15} {'RAM (GB)':<15} {'+50% запас (GB)':<20}") print_flush(f" {'-'*60}") for n in [5, 10, 20, 30, 40, 50]: ram_mb = estimate_per_browser * n ram_gb = ram_mb / 1024 ram_safe = ram_mb * 1.5 / 1024 print_flush(f" {n:<10} {ram_mb:<15.0f} {ram_gb:<15.2f} {ram_safe:<20.2f}") # Системные ограничения sys_mem = get_system_memory() usable_ram = sys_mem["total_gb"] * 0.8 # 80% от всей RAM print_flush(f"\n💻 ТЕКУЩАЯ СИСТЕМА:") print_flush(f" Всего RAM: {sys_mem['total_gb']:.1f} GB") print_flush(f" Можно использовать (80%): {usable_ram:.1f} GB") print_flush(f" Максимум задач на этой машине: {int(usable_ram * 1024 / estimate_per_browser)}") print_flush(f"\n🎯 РЕКОМЕНДАЦИЯ:") ram_for_50 = estimate_per_browser * 50 * 1.5 / 1024 if ram_for_50 < 4: print_flush(f" Сервер: 8 GB RAM, 4 vCPU (бюджет)") elif ram_for_50 < 8: print_flush(f" Сервер: 16 GB RAM, 8 vCPU") elif ram_for_50 < 16: print_flush(f" Сервер: 32 GB RAM, 16 vCPU") else: print_flush(f" Сервер: 64 GB RAM, 32 vCPU") print_flush(f" Диск: 100+ GB SSD") print_flush(f" Сеть: 100+ Mbps") print_flush("\n" + "=" * 70) print_flush("✅ ВСЕ ТЕСТЫ ЗАВЕРШЕНЫ") print_flush("=" * 70) if __name__ == "__main__": import random try: asyncio.run(main()) except KeyboardInterrupt: print_flush("\n⚠️ Прервано") except Exception as e: print_flush(f"\n❌ Ошибка: {e}") traceback.print_exc() finally: print_flush("\nНажмите Enter...", end="") try: input() except: time.sleep(3)