Files
Click/test_minimal.py
T
2026-05-14 16:15:58 +07:00

409 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Точный тест потребления памяти с отслеживанием ВСЕХ процессов.
Измеряет память 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)