potential performance upgrade
Build and Push Docker Images / build-and-push (push) Successful in 21s
Build and Push Docker Images / build-and-push (push) Successful in 21s
This commit is contained in:
@@ -12,42 +12,49 @@ async def wait_for_network_idle(page):
|
|||||||
await page.wait_for_load_state('networkidle')
|
await page.wait_for_load_state('networkidle')
|
||||||
|
|
||||||
async def safe_browser_operation(url, operation_func):
|
async def safe_browser_operation(url, operation_func):
|
||||||
"""Safely perform browser operations with proper cleanup"""
|
"""Safely perform browser operations with proper cleanup and timeouts"""
|
||||||
browser = None
|
browser = None
|
||||||
context = None
|
context = None
|
||||||
page = None
|
page = None
|
||||||
playwright = None
|
playwright = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get or create playwright instance
|
# Get or create playwright instance with timeout
|
||||||
playwright = await async_playwright().start()
|
playwright = await asyncio.wait_for(async_playwright().start(), timeout=30.0)
|
||||||
|
|
||||||
browser = await playwright.chromium.launch(
|
browser = await asyncio.wait_for(playwright.chromium.launch(
|
||||||
headless=True,
|
headless=True,
|
||||||
args=['--no-sandbox', '--disable-setuid-sandbox'],
|
args=['--no-sandbox', '--disable-setuid-sandbox', '--max_old_space_size=256'],
|
||||||
)
|
), timeout=30.0)
|
||||||
|
|
||||||
# Create context and page
|
# Create context and page with timeout
|
||||||
context = await browser.new_context(
|
context = await asyncio.wait_for(browser.new_context(
|
||||||
user_agent=CUSTOM_USER_AGENT,
|
user_agent=CUSTOM_USER_AGENT,
|
||||||
viewport={'width': 1920, 'height': 1080},
|
viewport={'width': 1920, 'height': 1080},
|
||||||
ignore_https_errors=True,
|
ignore_https_errors=True,
|
||||||
)
|
), timeout=10.0)
|
||||||
|
|
||||||
page = await context.new_page()
|
page = await asyncio.wait_for(context.new_page(), timeout=10.0)
|
||||||
page.set_default_timeout(30000)
|
page.set_default_timeout(30000) # 30 second timeout
|
||||||
|
|
||||||
# Track page creation time for force cleanup
|
# Track page creation time for force cleanup
|
||||||
page_creation_times[page] = time.time()
|
page_creation_times[page] = time.time()
|
||||||
active_pages.add(page)
|
active_pages.add(page)
|
||||||
|
|
||||||
# Call the operation function that uses the page
|
# Call the operation function that uses the page with timeout
|
||||||
result = await operation_func(page)
|
result = await asyncio.wait_for(operation_func(page), timeout=60.0)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
except asyncio.TimeoutError as e:
|
||||||
|
print(f"Browser operation timeout for {url}: {e}")
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error": f"Operation timeout: {str(e)}",
|
||||||
|
"url": url
|
||||||
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error during browser operation: {e}")
|
print(f"Error during browser operation for {url}: {e}")
|
||||||
# Return error result instead of re-raising to allow graceful handling
|
# Return error result instead of re-raising to allow graceful handling
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
@@ -55,7 +62,7 @@ async def safe_browser_operation(url, operation_func):
|
|||||||
"url": url
|
"url": url
|
||||||
}
|
}
|
||||||
finally:
|
finally:
|
||||||
# Cleanup page tracking
|
# Cleanup page tracking with timeout
|
||||||
if page:
|
if page:
|
||||||
try:
|
try:
|
||||||
# Remove from tracking
|
# Remove from tracking
|
||||||
@@ -63,29 +70,38 @@ async def safe_browser_operation(url, operation_func):
|
|||||||
active_pages.remove(page)
|
active_pages.remove(page)
|
||||||
if page in page_creation_times:
|
if page in page_creation_times:
|
||||||
del page_creation_times[page]
|
del page_creation_times[page]
|
||||||
await page.close()
|
await asyncio.wait_for(page.close(), timeout=5.0)
|
||||||
except Exception as e:
|
except (asyncio.TimeoutError, Exception) as e:
|
||||||
print(f"Error closing page: {e}")
|
print(f"Error closing page: {e}")
|
||||||
|
try:
|
||||||
|
await page.close(force=True)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
# Ensure context is closed properly
|
# Ensure context is closed properly with timeout
|
||||||
if context:
|
if context:
|
||||||
try:
|
try:
|
||||||
await context.close()
|
await asyncio.wait_for(context.close(), timeout=5.0)
|
||||||
except Exception as e:
|
except (asyncio.TimeoutError, Exception) as e:
|
||||||
print(f"Error closing context: {e}")
|
print(f"Error closing context: {e}")
|
||||||
|
|
||||||
# Ensure browser is closed properly
|
# Ensure browser is closed properly with timeout
|
||||||
if browser:
|
if browser:
|
||||||
try:
|
try:
|
||||||
await browser.close()
|
await asyncio.wait_for(browser.close(), timeout=10.0)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
try:
|
||||||
|
await browser.close(force=True)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error closing browser: {e}")
|
print(f"Error closing browser: {e}")
|
||||||
|
|
||||||
# Ensure playwright is closed properly - THIS IS THE KEY FIX
|
# Ensure playwright is closed properly with timeout - THIS IS THE KEY FIX
|
||||||
if playwright:
|
if playwright:
|
||||||
try:
|
try:
|
||||||
await playwright.stop()
|
await asyncio.wait_for(playwright.stop(), timeout=10.0)
|
||||||
except Exception as e:
|
except (asyncio.TimeoutError, Exception) as e:
|
||||||
print(f"Error closing playwright: {e}")
|
print(f"Error closing playwright: {e}")
|
||||||
|
|
||||||
async def force_cleanup_old_pages():
|
async def force_cleanup_old_pages():
|
||||||
|
|||||||
@@ -273,73 +273,101 @@ class CloudflareBypass:
|
|||||||
""")
|
""")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def handle_cloudflare_challenge(page: Page, max_retries: int = 3) -> bool:
|
async def handle_cloudflare_challenge(page: Page, max_retries: int = 2) -> bool:
|
||||||
"""Handle Cloudflare challenges and wait for them to complete"""
|
"""Handle Cloudflare challenge with improved efficiency"""
|
||||||
for attempt in range(max_retries):
|
for attempt in range(max_retries):
|
||||||
try:
|
try:
|
||||||
# Check for Cloudflare challenge
|
print(f"Handling Cloudflare challenge (attempt {attempt + 1}/{max_retries})")
|
||||||
cloudflare_selectors = [
|
|
||||||
'#challenge-form',
|
|
||||||
'#cf-please-wait',
|
|
||||||
'.cf-browser-verification',
|
|
||||||
'#cf-wrapper',
|
|
||||||
'iframe[src*="cloudflare"]'
|
|
||||||
]
|
|
||||||
|
|
||||||
for selector in cloudflare_selectors:
|
# Wait for challenge form with shorter timeout
|
||||||
try:
|
try:
|
||||||
element = await page.wait_for_selector(selector, timeout=5000)
|
await page.wait_for_selector('#challenge-form', timeout=10000) # 10 seconds
|
||||||
if element:
|
except:
|
||||||
print(f"Cloudflare challenge detected on attempt {attempt + 1}")
|
# If no challenge form, might already be bypassed
|
||||||
# Wait for challenge to complete
|
|
||||||
await asyncio.sleep(5 + random.uniform(2, 8))
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Wait for page to load completely
|
|
||||||
await page.wait_for_load_state('networkidle', timeout=30000)
|
|
||||||
|
|
||||||
# Check if we're past the challenge
|
|
||||||
title = await page.title()
|
|
||||||
if 'Cloudflare' not in title and 'challenge' not in title.lower():
|
|
||||||
print("Successfully bypassed Cloudflare challenge")
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Add some random delay
|
# Wait for the challenge to complete with shorter timeout
|
||||||
await asyncio.sleep(random.uniform(3, 8))
|
try:
|
||||||
|
await page.wait_for_function('''
|
||||||
|
() => {
|
||||||
|
return !document.querySelector('#challenge-form') &&
|
||||||
|
!document.querySelector('#cf-wrapper') &&
|
||||||
|
!document.querySelector('#cf-please-wait') &&
|
||||||
|
!document.querySelector('.cf-browser-verification');
|
||||||
|
}
|
||||||
|
''', timeout=15000) # 15 seconds
|
||||||
|
|
||||||
|
print("Cloudflare challenge completed")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Challenge completion timeout (attempt {attempt + 1}): {e}")
|
||||||
|
|
||||||
|
# Try to click any "Verify" buttons
|
||||||
|
try:
|
||||||
|
verify_buttons = await page.query_selector_all('button[type="submit"], input[type="submit"]')
|
||||||
|
for button in verify_buttons:
|
||||||
|
try:
|
||||||
|
await button.click(timeout=5000)
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Short delay before retry
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error handling Cloudflare challenge (attempt {attempt + 1}): {e}")
|
print(f"Error handling Cloudflare challenge (attempt {attempt + 1}): {e}")
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
await asyncio.sleep(random.uniform(5, 15))
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Failed to bypass Cloudflare challenge after all attempts")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def navigate_with_stealth(page: Page, url: str) -> bool:
|
async def navigate_with_stealth(page: Page, url: str) -> bool:
|
||||||
"""Navigate to URL with stealth measures and handle Cloudflare"""
|
"""Navigate to URL with stealth measures and improved performance"""
|
||||||
try:
|
try:
|
||||||
# Add random delay before navigation
|
# Set shorter timeouts for better performance
|
||||||
await asyncio.sleep(random.uniform(1, 3))
|
page.set_default_timeout(30000) # 30 seconds
|
||||||
|
page.set_default_navigation_timeout(30000) # 30 seconds
|
||||||
|
|
||||||
# Navigate to the URL
|
# Navigate with stealth measures
|
||||||
await page.goto(url, wait_until='domcontentloaded')
|
response = await page.goto(url, wait_until='domcontentloaded') # Changed from 'networkidle' to 'domcontentloaded' for speed
|
||||||
|
|
||||||
# Handle Cloudflare challenge
|
if not response:
|
||||||
success = await CloudflareBypass.handle_cloudflare_challenge(page)
|
|
||||||
|
|
||||||
if not success:
|
|
||||||
print("Failed to bypass Cloudflare challenge")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Add human-like scrolling
|
# Quick check for Cloudflare challenge
|
||||||
await CloudflareBypass._simulate_human_scrolling(page)
|
if response.status == 403 or response.status == 503:
|
||||||
|
# Handle Cloudflare challenge
|
||||||
|
return await CloudflareBypass.handle_cloudflare_challenge(page, max_retries=2) # Reduced retries
|
||||||
|
|
||||||
|
# Check for Cloudflare indicators in the page
|
||||||
|
cloudflare_detected = await page.evaluate('''() => {
|
||||||
|
return document.title.toLowerCase().includes('cloudflare') ||
|
||||||
|
!!document.querySelector('#challenge-form') ||
|
||||||
|
!!document.querySelector('#cf-wrapper') ||
|
||||||
|
!!document.querySelector('#cf-please-wait') ||
|
||||||
|
!!document.querySelector('.cf-browser-verification');
|
||||||
|
}''')
|
||||||
|
|
||||||
|
if cloudflare_detected:
|
||||||
|
return await CloudflareBypass.handle_cloudflare_challenge(page, max_retries=2) # Reduced retries
|
||||||
|
|
||||||
|
# Wait for page to be ready (shorter wait)
|
||||||
|
try:
|
||||||
|
await page.wait_for_load_state('domcontentloaded', timeout=10000) # 10 second timeout
|
||||||
|
except:
|
||||||
|
pass # Continue even if load state timeout
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error during stealth navigation: {e}")
|
print(f"Error during stealth navigation to {url}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
+320
-127
@@ -22,6 +22,9 @@ from contextlib import asynccontextmanager
|
|||||||
from app.utils.browser_utils import force_cleanup_old_pages
|
from app.utils.browser_utils import force_cleanup_old_pages
|
||||||
from app.utils.cloudflare_bypass import CloudflareBypass
|
from app.utils.cloudflare_bypass import CloudflareBypass
|
||||||
|
|
||||||
|
# Add memory monitoring
|
||||||
|
import gc
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
# Get API key from environment variable
|
# Get API key from environment variable
|
||||||
@@ -39,9 +42,11 @@ CLEANUP_CRON = os.getenv('CLEANUP_CRON', '0 3 * * *')
|
|||||||
CUSTOM_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'
|
CUSTOM_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'
|
||||||
|
|
||||||
# Browser pool configuration - Reduced for better resource management
|
# Browser pool configuration - Reduced for better resource management
|
||||||
MAX_BROWSERS = int(os.getenv('MAX_BROWSERS', '3')) # Reduced from 5 to 3
|
MAX_BROWSERS = int(os.getenv('MAX_BROWSERS', '2')) # Reduced from 3 to 2 for better resource management
|
||||||
BROWSER_TTL = int(os.getenv('BROWSER_TTL', '1800')) # Reduced from 3600 to 1800 seconds (30 minutes)
|
BROWSER_TTL = int(os.getenv('BROWSER_TTL', '900')) # Reduced from 1800 to 900 seconds (15 minutes)
|
||||||
MAX_CONCURRENT_OPERATIONS = int(os.getenv('MAX_CONCURRENT_OPERATIONS', '5')) # New: limit concurrent operations
|
MAX_CONCURRENT_OPERATIONS = int(os.getenv('MAX_CONCURRENT_OPERATIONS', '3')) # Reduced from 5 to 3
|
||||||
|
BROWSER_OPERATION_TIMEOUT = int(os.getenv('BROWSER_OPERATION_TIMEOUT', '60')) # New: timeout for browser operations
|
||||||
|
PAGE_TIMEOUT = int(os.getenv('PAGE_TIMEOUT', '30')) # New: timeout for page operations
|
||||||
|
|
||||||
# Browser pool management
|
# Browser pool management
|
||||||
browser_pool = Queue(maxsize=MAX_BROWSERS) # Add maxsize to prevent unbounded growth
|
browser_pool = Queue(maxsize=MAX_BROWSERS) # Add maxsize to prevent unbounded growth
|
||||||
@@ -52,6 +57,7 @@ active_browsers = set() # Track active browsers
|
|||||||
active_pages = set() # Track active pages for force cleanup
|
active_pages = set() # Track active pages for force cleanup
|
||||||
operation_semaphore = Semaphore(MAX_CONCURRENT_OPERATIONS) # Limit concurrent operations
|
operation_semaphore = Semaphore(MAX_CONCURRENT_OPERATIONS) # Limit concurrent operations
|
||||||
playwright_instance = None # Global playwright instance
|
playwright_instance = None # Global playwright instance
|
||||||
|
browser_health_check_task = None # Track health check task
|
||||||
|
|
||||||
# Rate limiting configuration
|
# Rate limiting configuration
|
||||||
RATE_LIMIT_MINUTE = int(os.getenv('RATE_LIMIT_MINUTE', '60')) # requests per minute
|
RATE_LIMIT_MINUTE = int(os.getenv('RATE_LIMIT_MINUTE', '60')) # requests per minute
|
||||||
@@ -111,9 +117,48 @@ async def create_browser():
|
|||||||
proxy_url = os.getenv('PROXY_URL')
|
proxy_url = os.getenv('PROXY_URL')
|
||||||
proxy_config = CloudflareBypass.get_proxy_config(proxy_url)
|
proxy_config = CloudflareBypass.get_proxy_config(proxy_url)
|
||||||
|
|
||||||
|
# Add memory and performance optimizations
|
||||||
|
browser_args = CloudflareBypass.get_stealth_args() + [
|
||||||
|
'--memory-pressure-off',
|
||||||
|
'--max_old_space_size=256', # Reduced memory usage
|
||||||
|
'--disable-dev-shm-usage',
|
||||||
|
'--disable-gpu-sandbox',
|
||||||
|
'--disable-software-rasterizer',
|
||||||
|
'--disable-background-timer-throttling',
|
||||||
|
'--disable-backgrounding-occluded-windows',
|
||||||
|
'--disable-renderer-backgrounding',
|
||||||
|
'--disable-features=TranslateUI',
|
||||||
|
'--disable-ipc-flooding-protection',
|
||||||
|
'--disable-hang-monitor',
|
||||||
|
'--disable-prompt-on-repost',
|
||||||
|
'--disable-sync',
|
||||||
|
'--no-first-run',
|
||||||
|
'--no-default-browser-check',
|
||||||
|
'--no-pings',
|
||||||
|
'--no-zygote',
|
||||||
|
'--single-process',
|
||||||
|
'--disable-background-networking',
|
||||||
|
'--disable-background-timer-throttling',
|
||||||
|
'--disable-backgrounding-occluded-windows',
|
||||||
|
'--disable-breakpad',
|
||||||
|
'--disable-component-extensions-with-background-pages',
|
||||||
|
'--disable-dev-shm-usage',
|
||||||
|
'--disable-features=TranslateUI',
|
||||||
|
'--disable-ipc-flooding-protection',
|
||||||
|
'--disable-renderer-backgrounding',
|
||||||
|
'--disable-sync',
|
||||||
|
'--force-color-profile=srgb',
|
||||||
|
'--metrics-recording-only',
|
||||||
|
'--no-first-run',
|
||||||
|
'--safebrowsing-disable-auto-update',
|
||||||
|
'--enable-automation',
|
||||||
|
'--password-store=basic',
|
||||||
|
'--use-mock-keychain',
|
||||||
|
]
|
||||||
|
|
||||||
browser = await playwright_instance.chromium.launch(
|
browser = await playwright_instance.chromium.launch(
|
||||||
headless=True,
|
headless=True,
|
||||||
args=CloudflareBypass.get_stealth_args(),
|
args=browser_args,
|
||||||
ignore_default_args=['--enable-automation'],
|
ignore_default_args=['--enable-automation'],
|
||||||
**proxy_config
|
**proxy_config
|
||||||
)
|
)
|
||||||
@@ -124,10 +169,17 @@ async def create_browser():
|
|||||||
return browser
|
return browser
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error creating browser: {e}")
|
print(f"Error creating browser: {e}")
|
||||||
|
# Clean up playwright instance if browser creation fails
|
||||||
|
if playwright_instance:
|
||||||
|
try:
|
||||||
|
await playwright_instance.stop()
|
||||||
|
playwright_instance = None
|
||||||
|
except:
|
||||||
|
pass
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def cleanup_browser(browser):
|
async def cleanup_browser(browser):
|
||||||
"""Safely cleanup a browser instance"""
|
"""Safely cleanup a browser instance with improved error handling"""
|
||||||
try:
|
try:
|
||||||
if browser in active_browsers:
|
if browser in active_browsers:
|
||||||
active_browsers.remove(browser)
|
active_browsers.remove(browser)
|
||||||
@@ -136,82 +188,117 @@ async def cleanup_browser(browser):
|
|||||||
del browser_creation_times[browser]
|
del browser_creation_times[browser]
|
||||||
|
|
||||||
# Close all pages first and clean up page tracking
|
# Close all pages first and clean up page tracking
|
||||||
pages = browser.contexts[0].pages if browser.contexts else []
|
try:
|
||||||
for page in pages:
|
contexts = browser.contexts
|
||||||
|
for context in contexts:
|
||||||
|
pages = context.pages
|
||||||
|
for page in pages:
|
||||||
|
try:
|
||||||
|
if page in active_pages:
|
||||||
|
active_pages.remove(page)
|
||||||
|
if page in page_creation_times:
|
||||||
|
del page_creation_times[page]
|
||||||
|
await asyncio.wait_for(page.close(), timeout=5.0)
|
||||||
|
except (asyncio.TimeoutError, Exception) as e:
|
||||||
|
print(f"Error closing page: {e}")
|
||||||
|
# Force close if normal close fails
|
||||||
|
try:
|
||||||
|
await page.close(force=True)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Close context
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(context.close(), timeout=5.0)
|
||||||
|
except (asyncio.TimeoutError, Exception) as e:
|
||||||
|
print(f"Error closing context: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error during context/page cleanup: {e}")
|
||||||
|
|
||||||
|
# Close browser with timeout
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(browser.close(), timeout=10.0)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
print("Browser close timeout, forcing close")
|
||||||
try:
|
try:
|
||||||
if page in active_pages:
|
await browser.close(force=True)
|
||||||
active_pages.remove(page)
|
except:
|
||||||
if page in page_creation_times:
|
pass
|
||||||
del page_creation_times[page]
|
except Exception as e:
|
||||||
await page.close()
|
print(f"Error during browser cleanup: {e}")
|
||||||
except Exception as e:
|
|
||||||
print(f"Error closing page: {e}")
|
|
||||||
|
|
||||||
# Close browser
|
|
||||||
await browser.close()
|
|
||||||
print(f"Browser cleaned up. Total active browsers: {len(active_browsers)}")
|
print(f"Browser cleaned up. Total active browsers: {len(active_browsers)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error during browser cleanup: {e}")
|
print(f"Error during browser cleanup: {e}")
|
||||||
|
|
||||||
async def check_browser_health():
|
async def check_browser_health():
|
||||||
"""Check browser health and recycle if needed"""
|
"""Check browser health and recycle if needed with improved efficiency"""
|
||||||
|
global browser_health_check_task
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
# Sleep for 2 minutes between checks (reduced from 5 minutes)
|
# Sleep for 1 minute between checks (reduced from 2 minutes)
|
||||||
await asyncio.sleep(120)
|
await asyncio.sleep(60)
|
||||||
|
|
||||||
|
# Monitor memory usage
|
||||||
|
await monitor_memory_usage()
|
||||||
|
|
||||||
# First, force cleanup old instances
|
# First, force cleanup old instances
|
||||||
await force_cleanup_old_instances()
|
await force_cleanup_old_instances()
|
||||||
|
|
||||||
async with browser_lock:
|
# Use a timeout for the entire health check operation
|
||||||
# Get all browsers from the pool
|
async with asyncio.timeout(30): # 30 second timeout for health check
|
||||||
browsers = []
|
async with browser_lock:
|
||||||
while not browser_pool.empty():
|
# Get all browsers from the pool
|
||||||
try:
|
browsers = []
|
||||||
browsers.append(await browser_pool.get_nowait())
|
while not browser_pool.empty():
|
||||||
except asyncio.QueueEmpty:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Check each browser
|
|
||||||
for browser in browsers:
|
|
||||||
try:
|
|
||||||
# Check if browser is too old
|
|
||||||
if time.time() - browser_creation_times.get(browser, 0) > BROWSER_TTL:
|
|
||||||
print(f"Recycling old browser (age: {time.time() - browser_creation_times.get(browser, 0):.0f}s)")
|
|
||||||
await cleanup_browser(browser)
|
|
||||||
browser = await create_browser()
|
|
||||||
else:
|
|
||||||
# Quick health check
|
|
||||||
contexts = browser.contexts
|
|
||||||
if contexts:
|
|
||||||
pages = contexts[0].pages
|
|
||||||
|
|
||||||
# Put back in pool if healthy
|
|
||||||
if not browser_pool.full():
|
|
||||||
await browser_pool.put(browser)
|
|
||||||
else:
|
|
||||||
# Pool is full, cleanup this browser
|
|
||||||
await cleanup_browser(browser)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error checking browser health: {e}")
|
|
||||||
# Cleanup the problematic browser
|
|
||||||
try:
|
try:
|
||||||
await cleanup_browser(browser)
|
browsers.append(await browser_pool.get_nowait())
|
||||||
except:
|
except asyncio.QueueEmpty:
|
||||||
pass
|
break
|
||||||
|
|
||||||
# Create new browsers if pool is empty
|
# Check each browser
|
||||||
while browser_pool.qsize() < MAX_BROWSERS:
|
for browser in browsers:
|
||||||
try:
|
try:
|
||||||
browser = await create_browser()
|
# Check if browser is too old
|
||||||
await browser_pool.put(browser)
|
if time.time() - browser_creation_times.get(browser, 0) > BROWSER_TTL:
|
||||||
except Exception as e:
|
print(f"Recycling old browser (age: {time.time() - browser_creation_times.get(browser, 0):.0f}s)")
|
||||||
print(f"Error creating browser for pool: {e}")
|
await cleanup_browser(browser)
|
||||||
break
|
browser = await create_browser()
|
||||||
|
else:
|
||||||
|
# Quick health check - just verify browser is still responsive
|
||||||
|
try:
|
||||||
|
contexts = browser.contexts
|
||||||
|
if not contexts:
|
||||||
|
# Browser has no contexts, might be dead
|
||||||
|
print("Browser has no contexts, recycling")
|
||||||
|
await cleanup_browser(browser)
|
||||||
|
browser = await create_browser()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Browser health check failed: {e}")
|
||||||
|
await cleanup_browser(browser)
|
||||||
|
browser = await create_browser()
|
||||||
|
|
||||||
|
# Put back in pool if healthy
|
||||||
|
if not browser_pool.full():
|
||||||
|
await browser_pool.put(browser)
|
||||||
|
else:
|
||||||
|
# Pool is full, cleanup this browser
|
||||||
|
await cleanup_browser(browser)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error checking browser health: {e}")
|
||||||
|
# Cleanup the problematic browser
|
||||||
|
try:
|
||||||
|
await cleanup_browser(browser)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
print("Browser health check timed out, continuing...")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in browser health check: {e}")
|
print(f"Error in browser health check: {e}")
|
||||||
await asyncio.sleep(60) # Wait before retrying
|
# Continue the loop even if there's an error
|
||||||
|
await asyncio.sleep(30) # Wait a bit before retrying
|
||||||
|
|
||||||
async def force_cleanup_old_instances():
|
async def force_cleanup_old_instances():
|
||||||
"""Force cleanup old browser and page instances based on timeout"""
|
"""Force cleanup old browser and page instances based on timeout"""
|
||||||
@@ -266,58 +353,95 @@ async def force_cleanup_old_instances():
|
|||||||
print(f"Force cleanup completed: {cleaned_browsers} browsers, {cleaned_pages} pages cleaned")
|
print(f"Force cleanup completed: {cleaned_browsers} browsers, {cleaned_pages} pages cleaned")
|
||||||
|
|
||||||
async def force_cleanup_all_browsers():
|
async def force_cleanup_all_browsers():
|
||||||
"""Force cleanup all browsers - useful for emergency situations"""
|
"""Force cleanup all browsers - useful for emergency situations with improved efficiency"""
|
||||||
print("Force cleaning up all browsers...")
|
print("Force cleaning up all browsers...")
|
||||||
|
|
||||||
async with browser_lock:
|
try:
|
||||||
# Clean up browsers in pool
|
async with asyncio.timeout(20): # 20 second timeout for force cleanup
|
||||||
while not browser_pool.empty():
|
async with browser_lock:
|
||||||
try:
|
# Clean up browsers in pool
|
||||||
browser = await browser_pool.get_nowait()
|
while not browser_pool.empty():
|
||||||
await cleanup_browser(browser)
|
try:
|
||||||
except asyncio.QueueEmpty:
|
browser = await asyncio.wait_for(browser_pool.get_nowait(), timeout=1.0)
|
||||||
break
|
await asyncio.wait_for(cleanup_browser(browser), timeout=5.0)
|
||||||
|
except (asyncio.TimeoutError, asyncio.QueueEmpty):
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error cleaning up browser from pool: {e}")
|
||||||
|
|
||||||
# Clean up any remaining active browsers
|
# Clean up any remaining active browsers
|
||||||
for browser in list(active_browsers):
|
for browser in list(active_browsers):
|
||||||
try:
|
try:
|
||||||
await cleanup_browser(browser)
|
await asyncio.wait_for(cleanup_browser(browser), timeout=5.0)
|
||||||
except Exception as e:
|
except (asyncio.TimeoutError, Exception) as e:
|
||||||
print(f"Error force cleaning browser: {e}")
|
print(f"Error force cleaning browser: {e}")
|
||||||
|
|
||||||
print("Force cleanup completed")
|
print("Force cleanup completed")
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
print("Force cleanup timed out")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error during force cleanup: {e}")
|
||||||
|
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
async def init_browser_pool():
|
async def init_browser_pool():
|
||||||
"""Initialize the browser pool on startup"""
|
"""Initialize the browser pool on startup with improved error handling"""
|
||||||
print("Initializing browser pool...")
|
print("Initializing browser pool...")
|
||||||
|
|
||||||
# Start browser health check task
|
try:
|
||||||
asyncio.create_task(check_browser_health())
|
# Start browser health check task
|
||||||
|
global browser_health_check_task
|
||||||
|
browser_health_check_task = asyncio.create_task(check_browser_health())
|
||||||
|
|
||||||
# Pre-populate pool with initial browsers
|
# Pre-populate pool with initial browsers (reduced from 2 to 1)
|
||||||
for _ in range(min(2, MAX_BROWSERS)):
|
for _ in range(min(1, MAX_BROWSERS)):
|
||||||
try:
|
try:
|
||||||
browser = await create_browser()
|
browser = await asyncio.wait_for(create_browser(), timeout=30.0)
|
||||||
await browser_pool.put(browser)
|
await asyncio.wait_for(browser_pool.put(browser), timeout=5.0)
|
||||||
except Exception as e:
|
print(f"Created initial browser {_ + 1}")
|
||||||
print(f"Error creating initial browser: {e}")
|
except (asyncio.TimeoutError, Exception) as e:
|
||||||
|
print(f"Error creating initial browser {_ + 1}: {e}")
|
||||||
|
break
|
||||||
|
|
||||||
print(f"Browser pool initialized with {browser_pool.qsize()} browsers")
|
print(f"Browser pool initialized with {browser_pool.qsize()} browsers")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error during browser pool initialization: {e}")
|
||||||
|
# Don't fail startup, but log the error
|
||||||
|
|
||||||
@app.on_event("shutdown")
|
@app.on_event("shutdown")
|
||||||
async def cleanup_browser_pool():
|
async def cleanup_browser_pool():
|
||||||
"""Cleanup browser pool on shutdown"""
|
"""Cleanup browser pool on shutdown with improved error handling"""
|
||||||
print("Cleaning up browser pool...")
|
print("Cleaning up browser pool...")
|
||||||
await force_cleanup_all_browsers()
|
|
||||||
|
|
||||||
# Stop playwright instance
|
try:
|
||||||
global playwright_instance
|
# Cancel health check task
|
||||||
if playwright_instance:
|
global browser_health_check_task
|
||||||
await playwright_instance.stop()
|
if browser_health_check_task and not browser_health_check_task.done():
|
||||||
playwright_instance = None
|
browser_health_check_task.cancel()
|
||||||
|
try:
|
||||||
|
await browser_health_check_task
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
|
||||||
print("Browser pool cleanup completed")
|
# Force cleanup all browsers with timeout
|
||||||
|
await asyncio.wait_for(force_cleanup_all_browsers(), timeout=30.0)
|
||||||
|
|
||||||
|
# Stop playwright instance
|
||||||
|
global playwright_instance
|
||||||
|
if playwright_instance:
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(playwright_instance.stop(), timeout=10.0)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
print("Playwright stop timeout")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error stopping playwright: {e}")
|
||||||
|
finally:
|
||||||
|
playwright_instance = None
|
||||||
|
|
||||||
|
print("Browser pool cleanup completed")
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
print("Browser pool cleanup timed out")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error during browser pool cleanup: {e}")
|
||||||
|
|
||||||
def signal_handler(signum, frame):
|
def signal_handler(signum, frame):
|
||||||
"""Handle shutdown signals"""
|
"""Handle shutdown signals"""
|
||||||
@@ -329,6 +453,21 @@ def signal_handler(signum, frame):
|
|||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
|
|
||||||
|
# Add memory monitoring
|
||||||
|
async def monitor_memory_usage():
|
||||||
|
"""Monitor memory usage and trigger cleanup if needed"""
|
||||||
|
try:
|
||||||
|
memory = psutil.virtual_memory()
|
||||||
|
if memory.percent > 85: # If memory usage is above 85%
|
||||||
|
print(f"High memory usage detected: {memory.percent}%")
|
||||||
|
# Force garbage collection
|
||||||
|
gc.collect()
|
||||||
|
# Force cleanup old instances
|
||||||
|
await force_cleanup_old_instances()
|
||||||
|
print("Memory cleanup completed")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error monitoring memory: {e}")
|
||||||
|
|
||||||
def init_db():
|
def init_db():
|
||||||
"""Initialize the SQLite database"""
|
"""Initialize the SQLite database"""
|
||||||
conn = sqlite3.connect('/db/cache.db')
|
conn = sqlite3.connect('/db/cache.db')
|
||||||
@@ -363,9 +502,9 @@ def init_db():
|
|||||||
print("Database initialized")
|
print("Database initialized")
|
||||||
|
|
||||||
def get_cached_data(url, route):
|
def get_cached_data(url, route):
|
||||||
"""Get cached data for a URL and route"""
|
"""Get cached data for a URL and route with improved error handling"""
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect('/db/cache.db')
|
conn = sqlite3.connect('/db/cache.db', timeout=10.0) # Add timeout
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
@@ -384,24 +523,36 @@ def get_cached_data(url, route):
|
|||||||
if datetime.now() - created_time < timedelta(hours=CACHE_EXPIRY_HOURS):
|
if datetime.now() - created_time < timedelta(hours=CACHE_EXPIRY_HOURS):
|
||||||
return json.loads(data)
|
return json.loads(data)
|
||||||
|
|
||||||
|
return None
|
||||||
|
except sqlite3.OperationalError as e:
|
||||||
|
print(f"Database operational error getting cached data: {e}")
|
||||||
return None
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error getting cached data: {e}")
|
print(f"Error getting cached data: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def save_to_cache(url, route, data):
|
def save_to_cache(url, route, data):
|
||||||
"""Save data to cache"""
|
"""Save data to cache with improved error handling"""
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect('/db/cache.db')
|
conn = sqlite3.connect('/db/cache.db', timeout=10.0) # Add timeout
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Limit data size to prevent memory issues
|
||||||
|
data_str = json.dumps(data)
|
||||||
|
if len(data_str) > 10 * 1024 * 1024: # 10MB limit
|
||||||
|
print(f"Data too large for cache: {len(data_str)} bytes")
|
||||||
|
conn.close()
|
||||||
|
return
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT OR REPLACE INTO cache (url, route, data, created_at)
|
INSERT OR REPLACE INTO cache (url, route, data, created_at)
|
||||||
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
''', (url, route, json.dumps(data)))
|
''', (url, route, data_str))
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
except sqlite3.OperationalError as e:
|
||||||
|
print(f"Database operational error saving to cache: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error saving to cache: {e}")
|
print(f"Error saving to cache: {e}")
|
||||||
|
|
||||||
@@ -458,46 +609,60 @@ async def health_check():
|
|||||||
return {"status": "healthy"}
|
return {"status": "healthy"}
|
||||||
|
|
||||||
async def safe_browser_operation(url, operation_func):
|
async def safe_browser_operation(url, operation_func):
|
||||||
"""Safely perform a browser operation with proper resource management"""
|
"""Safely perform a browser operation with proper resource management and timeouts"""
|
||||||
async with operation_semaphore:
|
async with operation_semaphore:
|
||||||
browser = None
|
browser = None
|
||||||
context = None
|
context = None
|
||||||
page = None
|
page = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get browser from pool or create new one
|
# Get browser from pool or create new one with timeout
|
||||||
try:
|
try:
|
||||||
browser = await asyncio.wait_for(browser_pool.get(), timeout=10.0)
|
browser = await asyncio.wait_for(browser_pool.get(), timeout=5.0)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
print("Timeout getting browser from pool, creating new one")
|
print("Timeout getting browser from pool, creating new one")
|
||||||
browser = await create_browser()
|
browser = await asyncio.wait_for(create_browser(), timeout=30.0)
|
||||||
|
|
||||||
# Create stealth context with Cloudflare bypass
|
# Create stealth context with Cloudflare bypass
|
||||||
context = await CloudflareBypass.setup_stealth_context(browser)
|
context = await asyncio.wait_for(
|
||||||
|
CloudflareBypass.setup_stealth_context(browser),
|
||||||
|
timeout=10.0
|
||||||
|
)
|
||||||
|
|
||||||
page = await context.new_page()
|
page = await asyncio.wait_for(context.new_page(), timeout=10.0)
|
||||||
|
page.set_default_timeout(PAGE_TIMEOUT * 1000) # Convert to milliseconds
|
||||||
|
|
||||||
# Track page creation time for force cleanup
|
# Track page creation time for force cleanup
|
||||||
page_creation_times[page] = time.time()
|
page_creation_times[page] = time.time()
|
||||||
active_pages.add(page)
|
active_pages.add(page)
|
||||||
|
|
||||||
# Setup stealth page with additional measures
|
# Setup stealth page with additional measures
|
||||||
await CloudflareBypass.setup_stealth_page(page)
|
await asyncio.wait_for(
|
||||||
|
CloudflareBypass.setup_stealth_page(page),
|
||||||
|
timeout=5.0
|
||||||
|
)
|
||||||
|
|
||||||
# Set up request interception for better performance (but allow essential resources)
|
# Set up optimized request interception for better performance
|
||||||
await page.route("**/*", lambda route: route.abort()
|
await page.route("**/*", lambda route: route.abort()
|
||||||
if route.request.resource_type in ['image', 'stylesheet', 'font', 'media']
|
if route.request.resource_type in ['image', 'stylesheet', 'font', 'media', 'script']
|
||||||
|
and not route.request.url.startswith('data:')
|
||||||
else route.continue_())
|
else route.continue_())
|
||||||
|
|
||||||
# Perform the operation
|
# Perform the operation with timeout
|
||||||
result = await operation_func(page)
|
result = await asyncio.wait_for(
|
||||||
|
operation_func(page),
|
||||||
|
timeout=BROWSER_OPERATION_TIMEOUT
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
except asyncio.TimeoutError as e:
|
||||||
|
print(f"Browser operation timeout for {url}: {e}")
|
||||||
|
raise HTTPException(status_code=408, detail=f"Operation timeout: {str(e)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in browser operation: {e}")
|
print(f"Error in browser operation for {url}: {e}")
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
# Cleanup
|
# Cleanup with timeouts
|
||||||
if page:
|
if page:
|
||||||
try:
|
try:
|
||||||
# Remove from tracking
|
# Remove from tracking
|
||||||
@@ -505,23 +670,38 @@ async def safe_browser_operation(url, operation_func):
|
|||||||
active_pages.remove(page)
|
active_pages.remove(page)
|
||||||
if page in page_creation_times:
|
if page in page_creation_times:
|
||||||
del page_creation_times[page]
|
del page_creation_times[page]
|
||||||
await page.close()
|
await asyncio.wait_for(page.close(), timeout=5.0)
|
||||||
except:
|
except (asyncio.TimeoutError, Exception):
|
||||||
pass
|
try:
|
||||||
|
await page.close(force=True)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
if context:
|
if context:
|
||||||
try:
|
try:
|
||||||
await context.close()
|
await asyncio.wait_for(context.close(), timeout=5.0)
|
||||||
except:
|
except (asyncio.TimeoutError, Exception):
|
||||||
pass
|
pass
|
||||||
if browser:
|
if browser:
|
||||||
try:
|
try:
|
||||||
# Return browser to pool if it's still healthy
|
# Return browser to pool if it's still healthy and pool isn't full
|
||||||
if not browser_pool.full():
|
if not browser_pool.full():
|
||||||
await browser_pool.put(browser)
|
# Quick health check before returning to pool
|
||||||
|
try:
|
||||||
|
contexts = browser.contexts
|
||||||
|
if contexts:
|
||||||
|
await asyncio.wait_for(browser_pool.put(browser), timeout=2.0)
|
||||||
|
else:
|
||||||
|
await cleanup_browser(browser)
|
||||||
|
except (asyncio.TimeoutError, Exception):
|
||||||
|
await cleanup_browser(browser)
|
||||||
else:
|
else:
|
||||||
await cleanup_browser(browser)
|
await cleanup_browser(browser)
|
||||||
except:
|
except Exception as e:
|
||||||
pass
|
print(f"Error returning browser to pool: {e}")
|
||||||
|
try:
|
||||||
|
await cleanup_browser(browser)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def visit_url(url: str, x_api_key: Optional[str] = Header(None)):
|
async def visit_url(url: str, x_api_key: Optional[str] = Header(None)):
|
||||||
@@ -832,8 +1012,13 @@ async def system_status(x_api_key: Optional[str] = Header(None)):
|
|||||||
"cpu_percent": cpu_percent,
|
"cpu_percent": cpu_percent,
|
||||||
"memory_percent": memory.percent,
|
"memory_percent": memory.percent,
|
||||||
"memory_available_gb": round(memory.available / (1024**3), 2),
|
"memory_available_gb": round(memory.available / (1024**3), 2),
|
||||||
|
"memory_used_gb": round(memory.used / (1024**3), 2),
|
||||||
|
"memory_free_gb": round(memory.free / (1024**3), 2),
|
||||||
|
"memory_total_gb": round(memory.total / (1024**3), 2),
|
||||||
"disk_percent": disk.percent,
|
"disk_percent": disk.percent,
|
||||||
"disk_free_gb": round(disk.free / (1024**3), 2)
|
"disk_free_gb": round(disk.free / (1024**3), 2),
|
||||||
|
"disk_used_gb": round(disk.used / (1024**3), 2),
|
||||||
|
"disk_total_gb": round(disk.total / (1024**3), 2)
|
||||||
},
|
},
|
||||||
"browser_pool": {
|
"browser_pool": {
|
||||||
"pool_size": pool_size,
|
"pool_size": pool_size,
|
||||||
@@ -841,7 +1026,11 @@ async def system_status(x_api_key: Optional[str] = Header(None)):
|
|||||||
"active_browsers": active_browser_count,
|
"active_browsers": active_browser_count,
|
||||||
"active_pages": active_page_count,
|
"active_pages": active_page_count,
|
||||||
"browser_ttl_seconds": BROWSER_TTL,
|
"browser_ttl_seconds": BROWSER_TTL,
|
||||||
"instance_timeout_minutes": BROWSER_INSTANCE_TIMEOUT_MINUTES
|
"instance_timeout_minutes": BROWSER_INSTANCE_TIMEOUT_MINUTES,
|
||||||
|
"max_concurrent_operations": MAX_CONCURRENT_OPERATIONS,
|
||||||
|
"browser_operation_timeout_seconds": BROWSER_OPERATION_TIMEOUT,
|
||||||
|
"page_timeout_seconds": PAGE_TIMEOUT,
|
||||||
|
"pool_health": "healthy" if pool_size > 0 and active_browser_count <= MAX_BROWSERS else "warning"
|
||||||
},
|
},
|
||||||
"cache": {
|
"cache": {
|
||||||
"total_entries": cache_count,
|
"total_entries": cache_count,
|
||||||
@@ -850,6 +1039,10 @@ async def system_status(x_api_key: Optional[str] = Header(None)):
|
|||||||
"rate_limiting": {
|
"rate_limiting": {
|
||||||
"requests_per_minute": RATE_LIMIT_MINUTE,
|
"requests_per_minute": RATE_LIMIT_MINUTE,
|
||||||
"window_seconds": RATE_LIMIT_WINDOW
|
"window_seconds": RATE_LIMIT_WINDOW
|
||||||
|
},
|
||||||
|
"performance": {
|
||||||
|
"memory_pressure": "high" if memory.percent > 85 else "normal" if memory.percent > 70 else "low",
|
||||||
|
"cpu_pressure": "high" if cpu_percent > 80 else "normal" if cpu_percent > 50 else "low"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user