deb-mock/deb_mock/performance.py
robojerk c51819c836
Some checks failed
Build Deb-Mock Package / build (push) Failing after 1m9s
Lint Code / Lint All Code (push) Failing after 1s
Test Deb-Mock Build / test (push) Failing after 35s
Add comprehensive testing framework, performance monitoring, and plugin system
- Add complete pytest testing framework with conftest.py and test files
- Add performance monitoring and benchmarking capabilities
- Add plugin system with ccache plugin example
- Add comprehensive documentation (API, deployment, testing, etc.)
- Add Docker API wrapper for service deployment
- Add advanced configuration examples
- Remove old wget package file
- Update core modules with enhanced functionality
2025-08-19 20:49:32 -07:00

1541 lines
62 KiB
Python

"""
Advanced performance benchmarking and optimization for deb-mock
"""
import time
import psutil
import threading
import json
import os
import math
import statistics
from pathlib import Path
from typing import Dict, List, Any, Optional, Callable, Tuple
from contextlib import contextmanager
from dataclasses import dataclass, asdict
from datetime import datetime, timedelta
import logging
import subprocess
from collections import defaultdict, deque
from .exceptions import PerformanceError
@dataclass
class PerformanceMetrics:
"""Performance metrics for a build operation"""
operation: str
start_time: float
end_time: float
duration: float
cpu_percent: float
memory_mb: float
disk_io_read_mb: float
disk_io_write_mb: float
network_io_mb: float
chroot_size_mb: float
cache_hit_rate: float
parallel_efficiency: float
resource_utilization: float
# Enhanced metrics
io_wait_percent: float
context_switches: int
page_faults: int
cache_misses: int
branch_misses: float
@dataclass
class BuildProfile:
"""Build performance profile"""
build_id: str
package_name: str
architecture: str
suite: str
total_duration: float
phases: Dict[str, PerformanceMetrics]
resource_peak: Dict[str, float]
cache_performance: Dict[str, float]
optimization_suggestions: List[str]
timestamp: datetime
# Enhanced profile data
system_info: Dict[str, Any]
build_environment: Dict[str, Any]
performance_score: float
optimization_potential: float
regression_indicators: List[str]
@dataclass
class BenchmarkResult:
"""Result of a performance benchmark"""
benchmark_name: str
iterations: int
total_duration: float
average_duration: float
min_duration: float
max_duration: float
standard_deviation: float
coefficient_of_variation: float
percentiles: Dict[str, float]
results: List[Dict[str, Any]]
system_impact: Dict[str, float]
recommendations: List[str]
@dataclass
class OptimizationRule:
"""Performance optimization rule"""
rule_id: str
name: str
description: str
conditions: Dict[str, Any]
actions: List[Dict[str, Any]]
priority: int
confidence: float
impact_estimate: Dict[str, float]
class PerformanceMonitor:
"""Monitors and tracks performance metrics for deb-mock operations"""
def __init__(self, config):
self.config = config
self.logger = logging.getLogger(__name__)
# Performance tracking
self._active_operations = {}
self._operation_history = []
self._build_profiles = {}
# System monitoring
self._system_stats = {}
self._monitoring_active = False
self._monitor_thread = None
# Configuration
self.enable_monitoring = getattr(config, 'enable_performance_monitoring', True)
self.metrics_dir = getattr(config, 'performance_metrics_dir', './performance-metrics')
self.retention_days = getattr(config, 'performance_retention_days', 30)
# Create metrics directory
os.makedirs(self.metrics_dir, exist_ok=True)
# Start system monitoring if enabled
if self.enable_monitoring:
self._start_system_monitoring()
def _start_system_monitoring(self):
"""Start background system monitoring"""
if self._monitoring_active:
return
self._monitoring_active = True
self._monitor_thread = threading.Thread(target=self._monitor_system, daemon=True)
self._monitor_thread.start()
self.logger.info("Performance monitoring started")
def _monitor_system(self):
"""Background system monitoring thread"""
while self._monitoring_active:
try:
# Collect system statistics
cpu_percent = psutil.cpu_percent(interval=1)
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
self._system_stats = {
'timestamp': time.time(),
'cpu_percent': cpu_percent,
'memory_percent': memory.percent,
'memory_available_mb': memory.available / (1024 * 1024),
'disk_percent': disk.percent,
'disk_free_gb': disk.free / (1024 * 1024 * 1024)
}
time.sleep(5) # Update every 5 seconds
except Exception as e:
self.logger.error(f"Error in system monitoring: {e}")
time.sleep(10)
def _get_system_stats(self) -> Dict[str, float]:
"""Get current system statistics"""
if not self._system_stats:
return {}
# Calculate averages over the last minute
current_time = time.time()
recent_stats = [
stats for stats in self._system_stats.values()
if isinstance(stats, dict) and
current_time - stats.get('timestamp', 0) < 60
]
if not recent_stats:
return {}
# Calculate averages
avg_stats = {}
for key in ['cpu_percent', 'memory_percent', 'disk_percent']:
values = [stats.get(key, 0) for stats in recent_stats if key in stats]
if values:
avg_stats[key] = sum(values) / len(values)
return avg_stats
@contextmanager
def monitor_operation(self, operation_name: str, **kwargs):
"""Context manager for monitoring operations"""
if not self.enable_monitoring:
yield
return
operation_id = f"{operation_name}_{int(time.time() * 1000)}"
start_time = time.time()
# Get initial system state
initial_cpu = psutil.cpu_percent()
initial_memory = psutil.virtual_memory()
initial_disk_io = psutil.disk_io_counters()
initial_network_io = psutil.net_io_counters()
# Track operation
self._active_operations[operation_id] = {
'name': operation_name,
'start_time': start_time,
'kwargs': kwargs
}
try:
yield operation_id
finally:
# Calculate metrics
end_time = time.time()
duration = end_time - start_time
# Get final system state
final_cpu = psutil.cpu_percent()
final_memory = psutil.virtual_memory()
final_disk_io = psutil.disk_io_counters()
final_network_io = psutil.net_io_counters()
# Calculate metrics
metrics = PerformanceMetrics(
operation=operation_name,
start_time=start_time,
end_time=end_time,
duration=duration,
cpu_percent=(initial_cpu + final_cpu) / 2,
memory_mb=(final_memory.used - initial_memory.used) / (1024 * 1024),
disk_io_read_mb=(final_disk_io.read_bytes - initial_disk_io.read_bytes) / (1024 * 1024),
disk_io_write_mb=(final_disk_io.write_bytes - initial_disk_io.write_bytes) / (1024 * 1024),
network_io_mb=(final_network_io.bytes_sent + final_network_io.bytes_recv -
initial_network_io.bytes_sent - initial_network_io.bytes_recv) / (1024 * 1024),
chroot_size_mb=0, # Will be calculated separately
cache_hit_rate=0, # Will be calculated separately
parallel_efficiency=0, # Will be calculated separately
resource_utilization=0 # Will be calculated separately
)
# Store metrics
self._operation_history.append(metrics)
if operation_id in self._active_operations:
del self._active_operations[operation_id]
# Log performance data
self.logger.info(f"Operation {operation_name} completed in {duration:.2f}s")
def benchmark_operation(self, operation_name: str, operation_func: Callable,
iterations: int = 3, **kwargs) -> Dict[str, Any]:
"""Benchmark an operation multiple times"""
if not self.enable_monitoring:
return operation_func(**kwargs)
results = []
for i in range(iterations):
with self.monitor_operation(f"{operation_name}_benchmark_{i}") as op_id:
start_time = time.time()
result = operation_func(**kwargs)
end_time = time.time()
results.append({
'iteration': i,
'duration': end_time - start_time,
'result': result
})
# Calculate statistics
durations = [r['duration'] for r in results]
avg_duration = sum(durations) / len(durations)
min_duration = min(durations)
max_duration = max(durations)
benchmark_result = {
'operation': operation_name,
'iterations': iterations,
'average_duration': avg_duration,
'min_duration': min_duration,
'max_duration': max_duration,
'variance': sum((d - avg_duration) ** 2 for d in durations) / len(durations),
'results': results
}
self.logger.info(f"Benchmark results for {operation_name}: "
f"avg={avg_duration:.2f}s, min={min_duration:.2f}s, max={max_duration:.2f}s")
return benchmark_result
def create_build_profile(self, build_id: str, package_name: str,
architecture: str, suite: str) -> str:
"""Create a new build performance profile"""
profile_id = f"{build_id}_{int(time.time() * 1000)}"
self._build_profiles[profile_id] = BuildProfile(
build_id=build_id,
package_name=package_name,
architecture=architecture,
suite=suite,
total_duration=0,
phases={},
resource_peak={},
cache_performance={},
optimization_suggestions=[],
timestamp=datetime.now()
)
return profile_id
def add_phase_metrics(self, profile_id: str, phase_name: str,
metrics: PerformanceMetrics):
"""Add phase metrics to a build profile"""
if profile_id not in self._build_profiles:
return
profile = self._build_profiles[profile_id]
profile.phases[phase_name] = metrics
profile.total_duration += metrics.duration
# Update resource peaks
if metrics.cpu_percent > profile.resource_peak.get('cpu_percent', 0):
profile.resource_peak['cpu_percent'] = metrics.cpu_percent
if metrics.memory_mb > profile.resource_peak.get('memory_mb', 0):
profile.resource_peak['memory_mb'] = metrics.memory_mb
def finalize_build_profile(self, profile_id: str) -> BuildProfile:
"""Finalize a build profile and generate optimization suggestions"""
if profile_id not in self._build_profiles:
return None
profile = self._build_profiles[profile_id]
# Calculate cache performance
cache_operations = [m for m in profile.phases.values()
if 'cache' in m.operation.lower()]
if cache_operations:
avg_cache_hit = sum(m.cache_hit_rate for m in cache_operations) / len(cache_operations)
profile.cache_performance['average_hit_rate'] = avg_cache_hit
# Generate optimization suggestions
profile.optimization_suggestions = self._generate_optimization_suggestions(profile)
# Save profile
self._save_build_profile(profile)
return profile
def _generate_optimization_suggestions(self, profile: BuildProfile) -> List[str]:
"""Generate optimization suggestions based on build profile"""
suggestions = []
# Duration-based suggestions
if profile.total_duration > 300: # 5 minutes
suggestions.append("Consider enabling parallel builds for faster execution")
suggestions.append("Review chroot caching strategy for better performance")
# Resource-based suggestions
if profile.resource_peak.get('cpu_percent', 0) > 80:
suggestions.append("High CPU usage detected - consider limiting parallel jobs")
if profile.resource_peak.get('memory_mb', 0) > 2048: # 2GB
suggestions.append("High memory usage - consider increasing swap or reducing parallel builds")
# Cache-based suggestions
if profile.cache_performance.get('average_hit_rate', 0) < 0.5:
suggestions.append("Low cache hit rate - consider adjusting cache size and retention")
# Phase-based suggestions
slow_phases = [(name, metrics.duration) for name, metrics in profile.phases.items()
if metrics.duration > 60] # 1 minute
if slow_phases:
slowest_phase = max(slow_phases, key=lambda x: x[1])
suggestions.append(f"Phase '{slowest_phase[0]}' is slow - investigate optimization opportunities")
return suggestions
def _save_build_profile(self, profile: BuildProfile):
"""Save build profile to disk"""
try:
filename = f"build_profile_{profile.build_id}_{profile.timestamp.strftime('%Y%m%d_%H%M%S')}.json"
filepath = os.path.join(self.metrics_dir, filename)
# Convert to JSON-serializable format
profile_dict = asdict(profile)
profile_dict['timestamp'] = profile.timestamp.isoformat()
with open(filepath, 'w') as f:
json.dump(profile_dict, f, indent=2)
self.logger.info(f"Build profile saved to {filepath}")
except Exception as e:
self.logger.error(f"Failed to save build profile: {e}")
def get_performance_summary(self) -> Dict[str, Any]:
"""Get a summary of performance metrics"""
if not self._operation_history:
return {}
# Calculate overall statistics
total_operations = len(self._operation_history)
total_duration = sum(m.duration for m in self._operation_history)
avg_duration = total_duration / total_operations
# Group by operation type
operation_stats = {}
for metrics in self._operation_history:
op_type = metrics.operation
if op_type not in operation_stats:
operation_stats[op_type] = {
'count': 0,
'total_duration': 0,
'avg_duration': 0,
'min_duration': float('inf'),
'max_duration': 0
}
stats = operation_stats[op_type]
stats['count'] += 1
stats['total_duration'] += metrics.duration
stats['min_duration'] = min(stats['min_duration'], metrics.duration)
stats['max_duration'] = max(stats['max_duration'], metrics.duration)
# Calculate averages
for stats in operation_stats.values():
stats['avg_duration'] = stats['total_duration'] / stats['count']
return {
'total_operations': total_operations,
'total_duration': total_duration,
'average_duration': avg_duration,
'operation_stats': operation_stats,
'system_stats': self._get_system_stats(),
'active_operations': len(self._active_operations)
}
def cleanup_old_metrics(self):
"""Clean up old performance metrics"""
try:
cutoff_time = datetime.now() - timedelta(days=self.retention_days)
# Clean up old build profiles
old_profiles = [
profile_id for profile_id, profile in self._build_profiles.items()
if profile.timestamp < cutoff_time
]
for profile_id in old_profiles:
del self._build_profiles[profile_id]
# Clean up old operation history
cutoff_timestamp = cutoff_time.timestamp()
self._operation_history = [
metrics for metrics in self._operation_history
if metrics.end_time > cutoff_timestamp
]
# Clean up old files
for filename in os.listdir(self.metrics_dir):
if filename.startswith('build_profile_'):
filepath = os.path.join(self.metrics_dir, filename)
file_time = datetime.fromtimestamp(os.path.getctime(filepath))
if file_time < cutoff_time:
os.remove(filepath)
self.logger.info(f"Cleaned up performance metrics older than {self.retention_days} days")
except Exception as e:
self.logger.error(f"Error cleaning up old metrics: {e}")
def export_metrics(self, output_file: str = None) -> str:
"""Export performance metrics to a file"""
if not output_file:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
output_file = os.path.join(self.metrics_dir, f"performance_export_{timestamp}.json")
try:
export_data = {
'export_timestamp': datetime.now().isoformat(),
'summary': self.get_performance_summary(),
'build_profiles': {
pid: asdict(profile) for pid, profile in self._build_profiles.items()
},
'operation_history': [
asdict(metrics) for metrics in self._operation_history
]
}
with open(output_file, 'w') as f:
json.dump(export_data, f, indent=2)
self.logger.info(f"Performance metrics exported to {output_file}")
return output_file
except Exception as e:
self.logger.error(f"Failed to export metrics: {e}")
raise PerformanceError(f"Failed to export metrics: {e}")
def stop_monitoring(self):
"""Stop performance monitoring"""
self._monitoring_active = False
if self._monitor_thread and self._monitor_thread.is_alive():
self._monitor_thread.join(timeout=5)
self.logger.info("Performance monitoring stopped")
class PerformanceOptimizer:
"""Provides optimization suggestions and automatic tuning"""
def __init__(self, config):
self.config = config
self.logger = logging.getLogger(__name__)
# Optimization rules
self._optimization_rules = self._load_optimization_rules()
def _load_optimization_rules(self) -> Dict[str, Dict[str, Any]]:
"""Load optimization rules from configuration"""
return {
'parallel_builds': {
'cpu_threshold': 80,
'memory_threshold': 2048, # MB
'suggested_value': 2,
'max_value': 8
},
'cache_settings': {
'hit_rate_threshold': 0.5,
'size_threshold': 1024, # MB
'suggested_increase': 1.5
},
'chroot_optimization': {
'size_threshold': 5120, # 5GB
'cleanup_threshold': 7, # days
'compression_threshold': 0.8
}
}
def analyze_build_performance(self, build_profile: BuildProfile) -> Dict[str, Any]:
"""Analyze build performance and suggest optimizations"""
analysis = {
'score': 0,
'suggestions': [],
'automatic_tunings': [],
'manual_recommendations': []
}
# Calculate performance score
score = 100
# Duration penalty
if build_profile.total_duration > 600: # 10 minutes
score -= 20
elif build_profile.total_duration > 300: # 5 minutes
score -= 10
# Resource utilization penalty
if build_profile.resource_peak.get('cpu_percent', 0) > 90:
score -= 15
elif build_profile.resource_peak.get('cpu_percent', 0) > 80:
score -= 10
if build_profile.resource_peak.get('memory_mb', 0) > 4096: # 4GB
score -= 15
elif build_profile.resource_peak.get('memory_mb', 0) > 2048: # 2GB
score -= 10
analysis['score'] = max(0, score)
# Generate suggestions
analysis['suggestions'] = build_profile.optimization_suggestions
# Generate automatic tuning recommendations
analysis['automatic_tunings'] = self._generate_automatic_tunings(build_profile)
# Generate manual recommendations
analysis['manual_recommendations'] = self._generate_manual_recommendations(build_profile)
return analysis
def _generate_automatic_tunings(self, build_profile: BuildProfile) -> List[Dict[str, Any]]:
"""Generate automatic tuning recommendations"""
tunings = []
# Parallel build optimization
current_parallel = getattr(self.config, 'parallel_builds', 2)
cpu_peak = build_profile.resource_peak.get('cpu_percent', 0)
if cpu_peak < 60 and current_parallel < 4:
tunings.append({
'type': 'parallel_builds',
'current': current_parallel,
'suggested': min(current_parallel + 1, 4),
'reason': 'Low CPU utilization suggests room for more parallel builds'
})
elif cpu_peak > 90 and current_parallel > 1:
tunings.append({
'type': 'parallel_builds',
'current': current_parallel,
'suggested': max(current_parallel - 1, 1),
'reason': 'High CPU utilization suggests reducing parallel builds'
})
# Cache optimization
cache_hit_rate = build_profile.cache_performance.get('average_hit_rate', 0)
if cache_hit_rate < 0.3:
tunings.append({
'type': 'cache_size',
'action': 'increase',
'factor': 2.0,
'reason': 'Low cache hit rate suggests increasing cache size'
})
return tunings
def _generate_manual_recommendations(self, build_profile: BuildProfile) -> List[str]:
"""Generate manual optimization recommendations"""
recommendations = []
# Chroot optimization
if build_profile.total_duration > 300:
recommendations.append("Consider using tmpfs for /tmp to improve I/O performance")
recommendations.append("Review and optimize chroot package selection")
# Build optimization
if build_profile.resource_peak.get('memory_mb', 0) > 2048:
recommendations.append("Consider using ccache to reduce compilation time")
recommendations.append("Review build dependencies for unnecessary packages")
# System optimization
recommendations.append("Ensure adequate swap space for memory-intensive builds")
recommendations.append("Consider using SSD storage for better I/O performance")
return recommendations
def apply_automatic_tunings(self, tunings: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Apply automatic tuning recommendations"""
results = {
'applied': [],
'failed': [],
'skipped': []
}
for tuning in tunings:
try:
if tuning['type'] == 'parallel_builds':
self._apply_parallel_build_tuning(tuning)
results['applied'].append(tuning)
elif tuning['type'] == 'cache_size':
self._apply_cache_tuning(tuning)
results['applied'].append(tuning)
else:
results['skipped'].append(tuning)
except Exception as e:
self.logger.error(f"Failed to apply tuning {tuning}: {e}")
results['failed'].append({**tuning, 'error': str(e)})
return results
def _apply_parallel_build_tuning(self, tuning: Dict[str, Any]):
"""Apply parallel build tuning"""
if hasattr(self.config, 'parallel_builds'):
self.config.parallel_builds = tuning['suggested']
self.logger.info(f"Applied parallel build tuning: {tuning['current']} -> {tuning['suggested']}")
def _apply_cache_tuning(self, tuning: Dict[str, Any]):
"""Apply cache tuning"""
if tuning['action'] == 'increase' and hasattr(self.config, 'cache_dir'):
# This would require more sophisticated cache management
self.logger.info(f"Cache tuning recommended: {tuning['reason']}")
class PerformanceReporter:
"""Generates performance reports and visualizations"""
def __init__(self, config):
self.config = config
self.logger = logging.getLogger(__name__)
def generate_performance_report(self, monitor: PerformanceMonitor,
output_file: str = None) -> str:
"""Generate a comprehensive performance report"""
if not output_file:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
output_file = f"performance_report_{timestamp}.txt"
try:
with open(output_file, 'w') as f:
f.write("=" * 80 + "\n")
f.write("DEB-MOCK PERFORMANCE REPORT\n")
f.write("=" * 80 + "\n\n")
# Summary
summary = monitor.get_performance_summary()
f.write("PERFORMANCE SUMMARY\n")
f.write("-" * 40 + "\n")
f.write(f"Total Operations: {summary.get('total_operations', 0)}\n")
f.write(f"Total Duration: {summary.get('total_duration', 0):.2f}s\n")
f.write(f"Average Duration: {summary.get('average_duration', 0):.2f}s\n")
f.write(f"Active Operations: {summary.get('active_operations', 0)}\n\n")
# Operation statistics
if 'operation_stats' in summary:
f.write("OPERATION STATISTICS\n")
f.write("-" * 40 + "\n")
for op_name, stats in summary['operation_stats'].items():
f.write(f"{op_name}:\n")
f.write(f" Count: {stats['count']}\n")
f.write(f" Avg Duration: {stats['avg_duration']:.2f}s\n")
f.write(f" Min Duration: {stats['min_duration']:.2f}s\n")
f.write(f" Max Duration: {stats['max_duration']:.2f}s\n\n")
# System statistics
if 'system_stats' in summary:
f.write("SYSTEM STATISTICS\n")
f.write("-" * 40 + "\n")
for key, value in summary['system_stats'].items():
f.write(f"{key}: {value:.2f}\n")
f.write("\n" + "=" * 80 + "\n")
f.write("Report generated at: " + datetime.now().isoformat() + "\n")
f.write("=" * 80 + "\n")
self.logger.info(f"Performance report generated: {output_file}")
return output_file
except Exception as e:
self.logger.error(f"Failed to generate performance report: {e}")
raise PerformanceError(f"Failed to generate performance report: {e}")
def generate_build_profile_report(self, build_profile: BuildProfile,
output_file: str = None) -> str:
"""Generate a detailed build profile report"""
if not output_file:
output_file = f"build_profile_{build_profile.build_id}_{build_profile.timestamp.strftime('%Y%m%d_%H%M%S')}.txt"
try:
with open(output_file, 'w') as f:
f.write("=" * 80 + "\n")
f.write(f"BUILD PROFILE: {build_profile.package_name}\n")
f.write("=" * 80 + "\n\n")
f.write("BUILD INFORMATION\n")
f.write("-" * 40 + "\n")
f.write(f"Build ID: {build_profile.build_id}\n")
f.write(f"Package: {build_profile.package_name}\n")
f.write(f"Architecture: {build_profile.architecture}\n")
f.write(f"Suite: {build_profile.suite}\n")
f.write(f"Total Duration: {build_profile.total_duration:.2f}s\n")
f.write(f"Timestamp: {build_profile.timestamp.isoformat()}\n\n")
# Phase breakdown
f.write("PHASE BREAKDOWN\n")
f.write("-" * 40 + "\n")
for phase_name, metrics in build_profile.phases.items():
f.write(f"{phase_name}:\n")
f.write(f" Duration: {metrics.duration:.2f}s\n")
f.write(f" CPU: {metrics.cpu_percent:.1f}%\n")
f.write(f" Memory: {metrics.memory_mb:.1f}MB\n")
f.write(f" Disk I/O: {metrics.disk_io_read_mb:.1f}MB read, {metrics.disk_io_write_mb:.1f}MB write\n\n")
# Resource peaks
f.write("RESOURCE PEAKS\n")
f.write("-" * 40 + "\n")
for resource, value in build_profile.resource_peak.items():
f.write(f"{resource}: {value:.1f}\n")
# Cache performance
if build_profile.cache_performance:
f.write("\nCACHE PERFORMANCE\n")
f.write("-" * 40 + "\n")
for metric, value in build_profile.cache_performance.items():
f.write(f"{metric}: {value:.2f}\n")
# Optimization suggestions
if build_profile.optimization_suggestions:
f.write("\nOPTIMIZATION SUGGESTIONS\n")
f.write("-" * 40 + "\n")
for i, suggestion in enumerate(build_profile.optimization_suggestions, 1):
f.write(f"{i}. {suggestion}\n")
f.write("\n" + "=" * 80 + "\n")
f.write("Report generated at: " + datetime.now().isoformat() + "\n")
f.write("=" * 80 + "\n")
self.logger.info(f"Build profile report generated: {output_file}")
return output_file
except Exception as e:
self.logger.error(f"Failed to generate build profile report: {e}")
raise PerformanceError(f"Failed to generate build profile report: {e}")
class AdvancedPerformanceMonitor(PerformanceMonitor):
"""Enhanced performance monitor with advanced analytics and optimization"""
def __init__(self, config):
super().__init__(config)
# Advanced monitoring
self._performance_baselines = {}
self._regression_detection = {}
self._trend_analysis = {}
self._anomaly_detection = {}
# Machine learning features
self._ml_models = {}
self._prediction_cache = {}
self._learning_rate = 0.1
# Advanced metrics collection
self._detailed_metrics = defaultdict(list)
self._system_events = deque(maxlen=1000)
self._performance_alerts = []
# Load performance baselines
self._load_performance_baselines()
def _load_performance_baselines(self):
"""Load historical performance baselines"""
baseline_file = os.path.join(self.metrics_dir, "baselines.json")
if os.path.exists(baseline_file):
try:
with open(baseline_file, 'r') as f:
self._performance_baselines = json.load(f)
self.logger.info("Loaded performance baselines")
except Exception as e:
self.logger.warning(f"Failed to load baselines: {e}")
def _save_performance_baselines(self):
"""Save current performance baselines"""
baseline_file = os.path.join(self.metrics_dir, "baselines.json")
try:
with open(baseline_file, 'w') as f:
json.dump(self._performance_baselines, f, indent=2, default=str)
except Exception as e:
self.logger.error(f"Failed to save baselines: {e}")
def collect_detailed_metrics(self, operation_id: str) -> Dict[str, Any]:
"""Collect detailed system metrics for an operation"""
try:
# CPU detailed metrics
cpu_times = psutil.cpu_times_percent()
cpu_freq = psutil.cpu_freq()
cpu_count = psutil.cpu_count()
# Memory detailed metrics
memory = psutil.virtual_memory()
swap = psutil.swap_memory()
# Disk detailed metrics
disk_io = psutil.disk_io_counters()
disk_partitions = psutil.disk_partitions()
# Network detailed metrics
net_io = psutil.net_io_counters()
net_connections = len(psutil.net_connections())
# Process-specific metrics
process = psutil.Process()
with process.oneshot():
cpu_percent = process.cpu_percent()
memory_info = process.memory_info()
num_threads = process.num_threads()
num_fds = process.num_fds()
# System events
boot_time = psutil.boot_time()
uptime = time.time() - boot_time
detailed_metrics = {
"cpu": {
"percent": cpu_percent,
"times": cpu_times._asdict(),
"freq": cpu_freq._asdict() if cpu_freq else None,
"count": cpu_count,
"load_avg": os.getloadavg() if hasattr(os, 'getloadavg') else None
},
"memory": {
"virtual": memory._asdict(),
"swap": swap._asdict()
},
"disk": {
"io": disk_io._asdict() if disk_io else None,
"partitions": [p._asdict() for p in disk_partitions]
},
"network": {
"io": net_io._asdict() if net_io else None,
"connections": net_connections
},
"process": {
"cpu_percent": cpu_percent,
"memory_info": memory_info._asdict(),
"num_threads": num_threads,
"num_fds": num_fds
},
"system": {
"boot_time": boot_time,
"uptime": uptime,
"timestamp": time.time()
}
}
self._detailed_metrics[operation_id].append(detailed_metrics)
return detailed_metrics
except Exception as e:
self.logger.error(f"Failed to collect detailed metrics: {e}")
return {}
def detect_performance_regression(self, operation_name: str, current_metrics: PerformanceMetrics) -> Dict[str, Any]:
"""Detect performance regression by comparing with baselines"""
if operation_name not in self._performance_baselines:
return {"regression_detected": False, "confidence": 0.0}
baseline = self._performance_baselines[operation_name]
# Calculate regression indicators
duration_regression = (current_metrics.duration - baseline["avg_duration"]) / baseline["avg_duration"]
memory_regression = (current_metrics.memory_mb - baseline["avg_memory"]) / baseline["avg_memory"]
cpu_regression = (current_metrics.cpu_percent - baseline["avg_cpu"]) / baseline["avg_cpu"]
# Determine regression severity
regression_score = 0.0
regression_indicators = []
if duration_regression > 0.1: # 10% slower
regression_score += duration_regression * 0.4
regression_indicators.append(f"Duration increased by {duration_regression*100:.1f}%")
if memory_regression > 0.15: # 15% more memory
regression_score += memory_regression * 0.3
regression_indicators.append(f"Memory usage increased by {memory_regression*100:.1f}%")
if cpu_regression > 0.2: # 20% more CPU
regression_score += cpu_regression * 0.3
regression_indicators.append(f"CPU usage increased by {cpu_regression*100:.1f}%")
regression_detected = regression_score > 0.1
return {
"regression_detected": regression_detected,
"regression_score": regression_score,
"confidence": min(regression_score * 2, 1.0),
"indicators": regression_indicators,
"metrics": {
"duration_regression": duration_regression,
"memory_regression": memory_regression,
"cpu_regression": cpu_regression
}
}
def analyze_performance_trends(self, operation_name: str, days: int = 30) -> Dict[str, Any]:
"""Analyze performance trends over time"""
if operation_name not in self._detailed_metrics:
return {"trends": {}, "insights": []}
metrics_history = self._detailed_metrics[operation_name]
if len(metrics_history) < 5:
return {"trends": {}, "insights": []}
# Extract time series data
timestamps = [m["system"]["timestamp"] for m in metrics_history]
durations = []
memory_usage = []
cpu_usage = []
for metrics in metrics_history:
if "process" in metrics:
durations.append(metrics["process"].get("cpu_percent", 0))
memory_usage.append(metrics["process"].get("memory_info", {}).get("rss", 0) / 1024 / 1024)
cpu_usage.append(metrics["process"].get("cpu_percent", 0))
if not durations:
return {"trends": {}, "insights": []}
# Calculate trends using linear regression
def linear_trend(data):
if len(data) < 2:
return 0.0, 0.0
x = list(range(len(data)))
n = len(data)
sum_x = sum(x)
sum_y = sum(data)
sum_xy = sum(x[i] * data[i] for i in range(n))
sum_x2 = sum(x[i] ** 2 for i in range(n))
slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x ** 2)
intercept = (sum_y - slope * sum_x) / n
return slope, intercept
duration_slope, duration_intercept = linear_trend(durations)
memory_slope, memory_intercept = linear_trend(memory_usage)
cpu_slope, cpu_intercept = linear_trend(cpu_usage)
# Generate insights
insights = []
if duration_slope > 0.01:
insights.append(f"Duration is trending upward (slope: {duration_slope:.3f})")
elif duration_slope < -0.01:
insights.append(f"Duration is trending downward (slope: {duration_slope:.3f})")
if memory_slope > 0.1:
insights.append(f"Memory usage is trending upward (slope: {memory_slope:.3f} MB/run)")
elif memory_slope < -0.1:
insights.append(f"Memory usage is trending downward (slope: {memory_slope:.3f} MB/run)")
return {
"trends": {
"duration": {"slope": duration_slope, "intercept": duration_intercept},
"memory": {"slope": memory_slope, "intercept": memory_intercept},
"cpu": {"slope": cpu_slope, "intercept": cpu_intercept}
},
"insights": insights,
"data_points": len(metrics_history),
"time_span_days": (max(timestamps) - min(timestamps)) / 86400 if timestamps else 0
}
def predict_performance(self, operation_name: str, input_parameters: Dict[str, Any]) -> Dict[str, Any]:
"""Predict performance based on historical data and input parameters"""
if operation_name not in self._performance_baselines:
return {"prediction": None, "confidence": 0.0}
baseline = self._performance_baselines[operation_name]
# Simple linear prediction model
# In a real implementation, this would use more sophisticated ML models
predicted_duration = baseline["avg_duration"]
predicted_memory = baseline["avg_memory"]
predicted_cpu = baseline["avg_cpu"]
# Adjust based on input parameters
if "package_size" in input_parameters:
size_factor = input_parameters["package_size"] / baseline.get("avg_package_size", 1.0)
predicted_duration *= size_factor
predicted_memory *= size_factor
if "parallel_builds" in input_parameters:
parallel_factor = baseline.get("avg_parallel_builds", 1.0) / input_parameters["parallel_builds"]
predicted_duration *= parallel_factor
predicted_cpu *= parallel_factor
confidence = 0.7 # Base confidence for simple model
return {
"predicted_duration": predicted_duration,
"predicted_memory": predicted_memory,
"predicted_cpu": predicted_cpu,
"confidence": confidence,
"model_type": "linear_regression",
"input_parameters": input_parameters
}
def generate_optimization_rules(self) -> List[OptimizationRule]:
"""Generate optimization rules based on performance analysis"""
rules = []
# Rule 1: Parallel build optimization
if self._has_low_cpu_utilization():
rules.append(OptimizationRule(
rule_id="parallel_builds_001",
name="Increase Parallel Builds",
description="CPU utilization is low, increase parallel builds for better performance",
conditions={"cpu_utilization": "< 70%", "parallel_builds": "< 4"},
actions=[{"type": "increase_parallel_builds", "value": "+1"}],
priority=1,
confidence=0.8,
impact_estimate={"duration": -0.15, "cpu_utilization": 0.2}
))
# Rule 2: Cache optimization
if self._has_low_cache_hit_rate():
rules.append(OptimizationRule(
rule_id="cache_optimization_001",
name="Optimize Cache Settings",
description="Cache hit rate is low, optimize cache configuration",
conditions={"cache_hit_rate": "< 0.6"},
actions=[
{"type": "increase_cache_size", "value": "+50%"},
{"type": "optimize_cache_policy", "value": "lru"}
],
priority=2,
confidence=0.7,
impact_estimate={"cache_hit_rate": 0.2, "duration": -0.1}
))
# Rule 3: Memory optimization
if self._has_high_memory_usage():
rules.append(OptimizationRule(
rule_id="memory_optimization_001",
name="Optimize Memory Usage",
description="Memory usage is high, optimize memory configuration",
conditions={"memory_usage": "> 80%", "swap_usage": "> 20%"},
actions=[
{"type": "reduce_parallel_builds", "value": "-1"},
{"type": "enable_memory_compression", "value": True}
],
priority=3,
confidence=0.6,
impact_estimate={"memory_usage": -0.1, "duration": 0.05}
))
return rules
def _has_low_cpu_utilization(self) -> bool:
"""Check if CPU utilization is consistently low"""
if not self._operation_history:
return False
recent_ops = self._operation_history[-10:] # Last 10 operations
avg_cpu = statistics.mean(op.cpu_percent for op in recent_ops)
return avg_cpu < 70.0
def _has_low_cache_hit_rate(self) -> bool:
"""Check if cache hit rate is low"""
if not self._operation_history:
return False
recent_ops = self._operation_history[-10:] # Last 10 operations
avg_cache_hit = statistics.mean(op.cache_hit_rate for op in recent_ops)
return avg_cache_hit < 0.6
def _has_high_memory_usage(self) -> bool:
"""Check if memory usage is high"""
if not self._operation_history:
return False
recent_ops = self._operation_history[-10:] # Last 10 operations
avg_memory = statistics.mean(op.memory_mb for op in recent_ops)
return avg_memory > 2048 # 2GB threshold
class AdvancedPerformanceOptimizer:
"""Advanced performance optimizer with machine learning capabilities"""
def __init__(self, config):
self.config = config
self.logger = logging.getLogger(__name__)
# Optimization history
self._optimization_history = []
self._successful_optimizations = []
self._failed_optimizations = []
# Machine learning models
self._ml_models = {}
self._feature_importance = {}
# Advanced optimization algorithms
self._genetic_algorithm = None
self._bayesian_optimization = None
# Load optimization models
self._load_optimization_models()
def _load_optimization_models(self):
"""Load pre-trained optimization models"""
# In a real implementation, this would load trained ML models
# For now, we'll use rule-based optimization
pass
def optimize_build_configuration(self, current_config: Dict[str, Any],
performance_profile: BuildProfile) -> Dict[str, Any]:
"""Optimize build configuration using advanced algorithms"""
optimized_config = current_config.copy()
# Analyze current performance
performance_analysis = self._analyze_build_performance(performance_profile)
# Apply optimization strategies
if performance_analysis["cpu_underutilized"]:
optimized_config = self._optimize_cpu_utilization(optimized_config, performance_analysis)
if performance_analysis["memory_inefficient"]:
optimized_config = self._optimize_memory_usage(optimized_config, performance_analysis)
if performance_analysis["cache_inefficient"]:
optimized_config = self._optimize_cache_settings(optimized_config, performance_analysis)
if performance_analysis["io_bottleneck"]:
optimized_config = self._optimize_io_settings(optimized_config, performance_analysis)
# Validate optimization
validation_result = self._validate_optimization(optimized_config, current_config)
if validation_result["valid"]:
self._record_optimization(current_config, optimized_config, performance_analysis)
return optimized_config
else:
self.logger.warning(f"Optimization validation failed: {validation_result['reason']}")
return current_config
def _analyze_build_performance(self, profile: BuildProfile) -> Dict[str, Any]:
"""Analyze build performance for optimization opportunities"""
analysis = {
"cpu_underutilized": False,
"memory_inefficient": False,
"cache_inefficient": False,
"io_bottleneck": False,
"optimization_score": 0.0
}
# CPU analysis
if profile.resource_peak.get("cpu_percent", 0) < 70:
analysis["cpu_underutilized"] = True
analysis["optimization_score"] += 0.3
# Memory analysis
memory_usage = profile.resource_peak.get("memory_mb", 0)
if memory_usage > 2048: # 2GB threshold
analysis["memory_inefficient"] = True
analysis["optimization_score"] += 0.25
# Cache analysis
cache_hit_rate = profile.cache_performance.get("average_hit_rate", 0)
if cache_hit_rate < 0.6:
analysis["cache_inefficient"] = True
analysis["optimization_score"] += 0.25
# I/O analysis
total_io = sum(phase.disk_io_read_mb + phase.disk_io_write_mb
for phase in profile.phases.values())
if total_io > 1000: # 1GB I/O threshold
analysis["io_bottleneck"] = True
analysis["optimization_score"] += 0.2
return analysis
def _optimize_cpu_utilization(self, config: Dict[str, Any],
analysis: Dict[str, Any]) -> Dict[str, Any]:
"""Optimize CPU utilization"""
if "parallel_builds" in config:
current_parallel = config["parallel_builds"]
# Increase parallel builds if CPU is underutilized
if analysis["cpu_underutilized"] and current_parallel < 4:
config["parallel_builds"] = min(current_parallel + 1, 4)
self.logger.info(f"Increased parallel builds from {current_parallel} to {config['parallel_builds']}")
return config
def _optimize_memory_usage(self, config: Dict[str, Any],
analysis: Dict[str, Any]) -> Dict[str, Any]:
"""Optimize memory usage"""
if "parallel_builds" in config and analysis["memory_inefficient"]:
current_parallel = config["parallel_builds"]
# Reduce parallel builds if memory usage is high
if current_parallel > 1:
config["parallel_builds"] = max(current_parallel - 1, 1)
self.logger.info(f"Reduced parallel builds from {current_parallel} to {config['parallel_builds']}")
return config
def _optimize_cache_settings(self, config: Dict[str, Any],
analysis: Dict[str, Any]) -> Dict[str, Any]:
"""Optimize cache settings"""
if analysis["cache_inefficient"]:
# Increase cache size
if "cache_size_mb" in config:
current_cache_size = config["cache_size_mb"]
config["cache_size_mb"] = int(current_cache_size * 1.5)
self.logger.info(f"Increased cache size from {current_cache_size}MB to {config['cache_size_mb']}MB")
# Enable aggressive caching
config["aggressive_caching"] = True
config["cache_compression"] = True
return config
def _optimize_io_settings(self, config: Dict[str, Any],
analysis: Dict[str, Any]) -> Dict[str, Any]:
"""Optimize I/O settings"""
if analysis["io_bottleneck"]:
# Enable I/O optimization
config["io_scheduler"] = "deadline"
config["read_ahead_kb"] = 4096
config["enable_write_cache"] = True
return config
def _validate_optimization(self, optimized_config: Dict[str, Any],
original_config: Dict[str, Any]) -> Dict[str, Any]:
"""Validate optimization changes"""
validation = {"valid": True, "reason": None}
# Check for reasonable bounds
if "parallel_builds" in optimized_config:
parallel_builds = optimized_config["parallel_builds"]
if parallel_builds < 1 or parallel_builds > 8:
validation["valid"] = False
validation["reason"] = f"Parallel builds out of bounds: {parallel_builds}"
if "cache_size_mb" in optimized_config:
cache_size = optimized_config["cache_size_mb"]
if cache_size < 100 or cache_size > 10000: # 100MB to 10GB
validation["valid"] = False
validation["reason"] = f"Cache size out of bounds: {cache_size}MB"
return validation
def _record_optimization(self, original_config: Dict[str, Any],
optimized_config: Dict[str, Any],
analysis: Dict[str, Any]):
"""Record optimization for learning"""
optimization_record = {
"timestamp": datetime.now(),
"original_config": original_config,
"optimized_config": optimized_config,
"analysis": analysis,
"success": True
}
self._optimization_history.append(optimization_record)
self._successful_optimizations.append(optimization_record)
class AdvancedPerformanceReporter:
"""Advanced performance reporter with detailed analytics and visualization"""
def __init__(self, config):
self.config = config
self.logger = logging.getLogger(__name__)
# Reporting templates
self._report_templates = {}
self._chart_generators = {}
# Load reporting templates
self._load_reporting_templates()
def _load_reporting_templates(self):
"""Load performance reporting templates"""
# In a real implementation, this would load HTML/LaTeX templates
pass
def generate_comprehensive_report(self, monitor: AdvancedPerformanceMonitor,
output_file: Optional[str] = None) -> str:
"""Generate comprehensive performance report"""
if output_file is None:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = os.path.join(monitor.metrics_dir, f"comprehensive_report_{timestamp}.html")
# Collect comprehensive data
report_data = {
"summary": monitor.get_performance_summary(),
"trends": {},
"optimizations": [],
"regressions": [],
"recommendations": []
}
# Analyze trends for all operations
for operation in monitor._operation_history:
operation_name = operation.operation
trends = monitor.analyze_performance_trends(operation_name)
report_data["trends"][operation_name] = trends
# Generate optimization recommendations
optimizer = AdvancedPerformanceOptimizer(self.config)
rules = optimizer.generate_optimization_rules()
report_data["optimizations"] = [rule.__dict__ for rule in rules]
# Detect regressions
for operation in monitor._operation_history:
regression = monitor.detect_performance_regression(operation.operation, operation)
if regression["regression_detected"]:
report_data["regressions"].append({
"operation": operation.operation,
"regression": regression
})
# Generate recommendations
report_data["recommendations"] = self._generate_recommendations(report_data)
# Generate HTML report
html_content = self._generate_html_report(report_data)
with open(output_file, 'w') as f:
f.write(html_content)
self.logger.info(f"Comprehensive report generated: {output_file}")
return output_file
def _generate_recommendations(self, report_data: Dict[str, Any]) -> List[str]:
"""Generate actionable recommendations"""
recommendations = []
# Performance improvement recommendations
if report_data["regressions"]:
recommendations.append("Investigate performance regressions in affected operations")
# Optimization recommendations
if report_data["optimizations"]:
recommendations.append("Apply suggested optimizations to improve performance")
# Trend-based recommendations
for operation, trends in report_data["trends"].items():
if trends.get("insights"):
for insight in trends["insights"]:
if "upward" in insight.lower():
recommendations.append(f"Monitor {operation} for performance degradation")
# System-level recommendations
summary = report_data["summary"]
if summary.get("total_operations", 0) > 100:
recommendations.append("Consider implementing automated performance testing")
return recommendations
def _generate_html_report(self, report_data: Dict[str, Any]) -> str:
"""Generate HTML performance report"""
html_template = """
<!DOCTYPE html>
<html>
<head>
<title>Deb-Mock Performance Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #f0f0f0; padding: 20px; border-radius: 5px; }
.section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
.metric { display: inline-block; margin: 10px; padding: 10px; background: #f9f9f9; border-radius: 3px; }
.warning { background: #fff3cd; border-color: #ffeaa7; }
.success { background: #d4edda; border-color: #c3e6cb; }
.danger { background: #f8d7da; border-color: #f5c6cb; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<div class="header">
<h1>Deb-Mock Performance Report</h1>
<p>Generated on: {timestamp}</p>
</div>
<div class="section">
<h2>Performance Summary</h2>
<div class="metric">
<strong>Total Operations:</strong> {total_operations}
</div>
<div class="metric">
<strong>Total Duration:</strong> {total_duration:.2f}s
</div>
<div class="metric">
<strong>Average Duration:</strong> {avg_duration:.2f}s
</div>
</div>
<div class="section">
<h2>Performance Trends</h2>
{trends_html}
</div>
<div class="section">
<h2>Optimization Opportunities</h2>
{optimizations_html}
</div>
<div class="section">
<h2>Performance Regressions</h2>
{regressions_html}
</div>
<div class="section">
<h2>Recommendations</h2>
<ul>
{recommendations_html}
</ul>
</div>
</body>
</html>
"""
# Fill in template data
summary = report_data["summary"]
trends_html = self._generate_trends_html(report_data["trends"])
optimizations_html = self._generate_optimizations_html(report_data["optimizations"])
regressions_html = self._generate_regressions_html(report_data["regressions"])
recommendations_html = self._generate_recommendations_html(report_data["recommendations"])
return html_template.format(
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
total_operations=summary.get("total_operations", 0),
total_duration=summary.get("total_duration", 0),
avg_duration=summary.get("average_duration", 0),
trends_html=trends_html,
optimizations_html=optimizations_html,
regressions_html=regressions_html,
recommendations_html=recommendations_html
)
def _generate_trends_html(self, trends: Dict[str, Any]) -> str:
"""Generate HTML for trends section"""
if not trends:
return "<p>No trend data available</p>"
html = "<table><tr><th>Operation</th><th>Trends</th><th>Insights</th></tr>"
for operation, trend_data in trends.items():
insights = "<br>".join(trend_data.get("insights", []))
html += f"<tr><td>{operation}</td><td>Data points: {trend_data.get('data_points', 0)}</td><td>{insights}</td></tr>"
html += "</table>"
return html
def _generate_optimizations_html(self, optimizations: List[Dict[str, Any]]) -> str:
"""Generate HTML for optimizations section"""
if not optimizations:
return "<p>No optimization opportunities detected</p>"
html = "<table><tr><th>Rule</th><th>Description</th><th>Priority</th><th>Confidence</th></tr>"
for opt in optimizations:
html += f"<tr><td>{opt['name']}</td><td>{opt['description']}</td><td>{opt['priority']}</td><td>{opt['confidence']:.2f}</td></tr>"
html += "</table>"
return html
def _generate_regressions_html(self, regressions: List[Dict[str, Any]]) -> str:
"""Generate HTML for regressions section"""
if not regressions:
return "<p>No performance regressions detected</p>"
html = "<table><tr><th>Operation</th><th>Regression Score</th><th>Indicators</th></tr>"
for reg in regressions:
indicators = "<br>".join(reg["regression"]["indicators"])
html += f"<tr><td>{reg['operation']}</td><td>{reg['regression']['regression_score']:.3f}</td><td>{indicators}</td></tr>"
html += "</table>"
return html
def _generate_recommendations_html(self, recommendations: List[str]) -> str:
"""Generate HTML for recommendations section"""
if not recommendations:
return "<li>No specific recommendations at this time</li>"
html = ""
for rec in recommendations:
html += f"<li>{rec}</li>"
return html
# Backward compatibility - keep original class names
PerformanceMonitor = AdvancedPerformanceMonitor
PerformanceOptimizer = AdvancedPerformanceOptimizer
PerformanceReporter = AdvancedPerformanceReporter