310 lines
11 KiB
Python
310 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
System configuration optimizations for improving concurrent performance.
|
|
"""
|
|
|
|
import os
|
|
import asyncio
|
|
import multiprocessing
|
|
import threading
|
|
import time
|
|
import resource
|
|
import logging
|
|
from typing import Dict, Any, Optional
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
# Configure logging
|
|
logger = logging.getLogger('app')
|
|
|
|
|
|
class SystemOptimizer:
|
|
"""System optimizer.
|
|
|
|
Adjusts system parameters to improve concurrent performance.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.original_settings = {}
|
|
self.optimized = False
|
|
|
|
def optimize_system_settings(self):
|
|
"""Apply optimized system settings."""
|
|
if self.optimized:
|
|
return
|
|
|
|
# Back up original settings
|
|
self._backup_original_settings()
|
|
|
|
# 1. Optimize file descriptor limits.
|
|
try:
|
|
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
new_limit = min(65536, hard) # Increase to 65536 or the hard limit
|
|
resource.setrlimit(resource.RLIMIT_NOFILE, (new_limit, hard))
|
|
self.original_settings['RLIMIT_NOFILE'] = (soft, hard)
|
|
logger.info(f"File descriptor limit increased from {soft} to {new_limit}")
|
|
except (ValueError, OSError) as e:
|
|
logger.error(f"Failed to set file descriptor limit: {e}")
|
|
|
|
# 2. Optimize thread stack size.
|
|
try:
|
|
soft, hard = resource.getrlimit(resource.RLIMIT_STACK)
|
|
new_stack = min(8 * 1024 * 1024, hard) # 8 MB stack size
|
|
resource.setrlimit(resource.RLIMIT_STACK, (new_stack, hard))
|
|
self.original_settings['RLIMIT_STACK'] = (soft, hard)
|
|
logger.info(f"Thread stack size set to {new_stack // (1024*1024)}MB")
|
|
except (ValueError, OSError) as e:
|
|
logger.error(f"Failed to set thread stack size: {e}")
|
|
|
|
# 3. Optimize environment variables.
|
|
env_vars = {
|
|
# Python optimizations
|
|
'PYTHONUNBUFFERED': '1', # Disable output buffering
|
|
'PYTHONDONTWRITEBYTECODE': '1', # Do not write .pyc files
|
|
|
|
# Tokenizer optimizations - allow moderate parallelism
|
|
'TOKENIZERS_PARALLELISM': 'true', # Set to true to improve concurrency
|
|
'TOKENIZERS_FAST': '1', # Enable fast tokenizer
|
|
|
|
# OpenMP optimizations
|
|
'OMP_NUM_THREADS': str(min(8, multiprocessing.cpu_count())), # Limit OpenMP threads
|
|
'OMP_WAIT_POLICY': 'PASSIVE', # Passive wait policy
|
|
|
|
# Memory optimizations
|
|
'MALLOC_TRIM_THRESHOLD_': '100000', # Memory trimming threshold
|
|
|
|
# Network optimizations
|
|
'TCP_NODELAY': '1', # Disable Nagle's algorithm
|
|
|
|
# CUDA optimizations if GPU is used
|
|
'CUDA_LAUNCH_BLOCKING': '0', # Asynchronous CUDA launch
|
|
|
|
# asyncio optimizations
|
|
'UVLOOP_ENABLED': '1', # Enable uvloop when available
|
|
}
|
|
|
|
for key, value in env_vars.items():
|
|
if key not in os.environ:
|
|
os.environ[key] = value
|
|
logger.info(f"Set environment variable: {key}={value}")
|
|
|
|
self.optimized = True
|
|
logger.info("System optimization completed")
|
|
|
|
def _backup_original_settings(self):
|
|
"""Back up original settings."""
|
|
try:
|
|
self.original_settings['RLIMIT_NOFILE'] = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
self.original_settings['RLIMIT_STACK'] = resource.getrlimit(resource.RLIMIT_STACK)
|
|
except:
|
|
pass
|
|
|
|
# Back up important environment variables.
|
|
env_keys = [
|
|
'TOKENIZERS_PARALLELISM',
|
|
'PYTHONUNBUFFERED',
|
|
'PYTHONDONTWRITEBYTECODE',
|
|
'OMP_NUM_THREADS'
|
|
]
|
|
|
|
for key in env_keys:
|
|
if key in os.environ:
|
|
self.original_settings[key] = os.environ[key]
|
|
|
|
def restore_original_settings(self):
|
|
"""Restore original settings."""
|
|
if not self.original_settings:
|
|
return
|
|
|
|
logger.info("Restoring original system settings...")
|
|
|
|
# Restore resource limits.
|
|
if 'RLIMIT_NOFILE' in self.original_settings:
|
|
try:
|
|
resource.setrlimit(resource.RLIMIT_NOFILE, self.original_settings['RLIMIT_NOFILE'])
|
|
logger.info(f"Restored file descriptor limit")
|
|
except:
|
|
pass
|
|
|
|
if 'RLIMIT_STACK' in self.original_settings:
|
|
try:
|
|
resource.setrlimit(resource.RLIMIT_STACK, self.original_settings['RLIMIT_STACK'])
|
|
logger.info(f"Restored thread stack size")
|
|
except:
|
|
pass
|
|
|
|
# Restore environment variables.
|
|
for key, value in self.original_settings.items():
|
|
if key.startswith('TOKENIZERS_') or key in ['PYTHONUNBUFFERED', 'PYTHONDONTWRITEBYTECODE']:
|
|
if key in os.environ:
|
|
del os.environ[key]
|
|
logger.info(f"Removed environment variable: {key}")
|
|
elif key in ['OMP_NUM_THREADS'] and value is not None:
|
|
os.environ[key] = value
|
|
logger.info(f"Restored environment variable: {key}={value}")
|
|
|
|
self.optimized = False
|
|
logger.info("System settings restored")
|
|
|
|
|
|
class AsyncioOptimizer:
|
|
"""asyncio optimizer."""
|
|
|
|
@staticmethod
|
|
def setup_event_loop_policy():
|
|
"""Set an optimized event loop policy."""
|
|
try:
|
|
# Try to use uvloop if available.
|
|
import uvloop
|
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
|
logger.info("Using uvloop event loop policy")
|
|
except ImportError:
|
|
logger.info("Using default event loop policy")
|
|
|
|
# Set the thread pool size recommendation.
|
|
cpu_count = multiprocessing.cpu_count()
|
|
thread_pool_size = min(32, cpu_count * 4) # 4 threads per CPU core, up to 32
|
|
|
|
# Note: the default thread pool executor cannot be set here because
|
|
# no event loop is running yet. This should be configured during app startup.
|
|
logger.info(f"Recommended thread pool size: {thread_pool_size}")
|
|
|
|
@staticmethod
|
|
def optimize_gunicorn_settings() -> Dict[str, Any]:
|
|
"""Get optimized Gunicorn settings."""
|
|
cpu_count = multiprocessing.cpu_count()
|
|
|
|
return {
|
|
# Worker configuration
|
|
'workers': min(8, cpu_count + 1), # Number of worker processes
|
|
'worker_class': 'uvicorn.workers.UvicornWorker', # Use Uvicorn worker
|
|
'worker_connections': 2000, # Connections per worker
|
|
'max_requests': 5000, # Restart worker after max requests
|
|
'max_requests_jitter': 500, # Random jitter
|
|
'preload_app': True, # Preload application
|
|
|
|
# Timeout settings
|
|
'timeout': 120, # Worker timeout
|
|
'keepalive': 5, # Keep-Alive timeout
|
|
'graceful_timeout': 30, # Graceful shutdown timeout
|
|
|
|
# Performance optimizations
|
|
'worker_tmp_dir': '/dev/shm', # Use memory-backed filesystem
|
|
|
|
# Logging settings
|
|
'accesslog': '-', # Standard output
|
|
'errorlog': '-', # Standard error output
|
|
'loglevel': 'info',
|
|
}
|
|
|
|
|
|
def setup_system_optimizations():
|
|
"""Set up system optimizations."""
|
|
# 1. System-level optimizations
|
|
system_optimizer = SystemOptimizer()
|
|
system_optimizer.optimize_system_settings()
|
|
|
|
# 2. asyncio optimizations
|
|
asyncio_optimizer = AsyncioOptimizer()
|
|
asyncio_optimizer.setup_event_loop_policy()
|
|
|
|
return system_optimizer
|
|
|
|
|
|
def create_performance_monitor() -> Dict[str, Any]:
|
|
"""Create performance monitoring configuration."""
|
|
return {
|
|
'monitor_interval': 60, # Monitoring interval in seconds
|
|
'metrics': {
|
|
'memory_usage': True,
|
|
'cpu_usage': True,
|
|
'disk_io': True,
|
|
'network_io': True,
|
|
'active_connections': True,
|
|
'request_latency': True,
|
|
'cache_hit_rate': True,
|
|
'error_rate': True,
|
|
},
|
|
'alerts': {
|
|
'memory_threshold': 0.9, # Alert at 90% memory usage
|
|
'cpu_threshold': 0.8, # Alert at 80% CPU usage
|
|
'disk_threshold': 0.9, # Alert at 90% disk usage
|
|
'error_threshold': 0.05, # Alert at 5% error rate
|
|
'latency_threshold': 5.0, # Alert at 5 seconds latency
|
|
}
|
|
}
|
|
|
|
|
|
def get_optimized_worker_config() -> Dict[str, Any]:
|
|
"""Get optimized worker configuration."""
|
|
cpu_count = multiprocessing.cpu_count()
|
|
memory_gb = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') / (1024.0 ** 3)
|
|
|
|
# Configuration constrained by available system resources.
|
|
max_workers = min(
|
|
16, # Maximum worker count
|
|
max(2, cpu_count), # At least 2 workers, at most the CPU core count
|
|
int(memory_gb / 2) # Memory-based worker limit, 2 GB per worker
|
|
)
|
|
|
|
return {
|
|
'max_workers': max_workers,
|
|
'worker_connections': 1000, # Connections per worker
|
|
'connection_pool_size': 100, # Connection pool size
|
|
'buffer_size': 8192, # Buffer size
|
|
'timeout': 120, # Timeout in seconds
|
|
'keepalive_timeout': 30, # Keep-Alive timeout
|
|
}
|
|
|
|
|
|
# Predefined optimization profiles
|
|
OPTIMIZATION_CONFIGS = {
|
|
'low_memory': {
|
|
'max_workers': 2,
|
|
'worker_connections': 500,
|
|
'buffer_size': 4096,
|
|
'cache_size': 500,
|
|
},
|
|
'balanced': {
|
|
'max_workers': 4,
|
|
'worker_connections': 1000,
|
|
'buffer_size': 8192,
|
|
'cache_size': 1000,
|
|
},
|
|
'high_performance': {
|
|
'max_workers': 8,
|
|
'worker_connections': 2000,
|
|
'buffer_size': 16384,
|
|
'cache_size': 2000,
|
|
}
|
|
}
|
|
|
|
|
|
def apply_optimization_profile(profile_name: str) -> Dict[str, Any]:
|
|
"""Apply an optimization profile."""
|
|
if profile_name not in OPTIMIZATION_CONFIGS:
|
|
raise ValueError(f"Unknown optimization profile: {profile_name}")
|
|
|
|
config = OPTIMIZATION_CONFIGS[profile_name].copy()
|
|
|
|
# Add system-specific configuration.
|
|
config.update({
|
|
'profile_name': profile_name,
|
|
'applied_at': time.time(),
|
|
'cpu_count': multiprocessing.cpu_count(),
|
|
'memory_gb': os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') / (1024.0 ** 3)
|
|
})
|
|
|
|
return config
|
|
|
|
|
|
# Global system optimizer instance
|
|
_global_system_optimizer: Optional[SystemOptimizer] = None
|
|
|
|
|
|
def get_global_system_optimizer() -> SystemOptimizer:
|
|
"""Get the global system optimizer."""
|
|
global _global_system_optimizer
|
|
if _global_system_optimizer is None:
|
|
_global_system_optimizer = SystemOptimizer()
|
|
return _global_system_optimizer
|