EU-Utility/core/startup_profiler.py

193 lines
5.8 KiB
Python

"""
EU-Utility - Startup Profiler
Profiles application startup time and identifies bottlenecks.
"""
import time
import sys
from functools import wraps
from typing import Dict, List, Callable, Optional
from dataclasses import dataclass, field
from collections import defaultdict
@dataclass
class ProfileResult:
"""Result of profiling a component."""
name: str
start_time: float
end_time: float
duration_ms: float
parent: Optional[str] = None
children: List[str] = field(default_factory=list)
class StartupProfiler:
"""
Profiles application startup time.
Usage:
profiler = StartupProfiler()
with profiler.profile("database_init"):
init_database()
profiler.print_report()
"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self.results: Dict[str, ProfileResult] = {}
self._stack: List[str] = []
self._start_times: Dict[str, float] = {}
self.enabled = True
self.app_start_time = time.perf_counter()
self._initialized = True
def profile(self, name: str):
"""Context manager for profiling a code block."""
return self._ProfileContext(self, name)
class _ProfileContext:
def __init__(self, profiler, name):
self.profiler = profiler
self.name = name
def __enter__(self):
if self.profiler.enabled:
self.profiler._start(self.name)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.profiler.enabled:
self.profiler._end(self.name)
return False
def _start(self, name: str):
"""Start profiling a component."""
self._start_times[name] = time.perf_counter()
parent = self._stack[-1] if self._stack else None
self._stack.append(name)
if parent:
if parent not in self.results:
self.results[parent] = ProfileResult(
name=parent,
start_time=0,
end_time=0,
duration_ms=0,
children=[]
)
self.results[parent].children.append(name)
def _end(self, name: str):
"""End profiling a component."""
end_time = time.perf_counter()
start_time = self._start_times.pop(name, end_time)
duration = (end_time - start_time) * 1000
if name in self._stack:
self._stack.remove(name)
self.results[name] = ProfileResult(
name=name,
start_time=start_time - self.app_start_time,
end_time=end_time - self.app_start_time,
duration_ms=duration,
parent=self._stack[-1] if self._stack else None
)
def print_report(self):
"""Print startup profiling report."""
total_time = (time.perf_counter() - self.app_start_time) * 1000
print("\n" + "=" * 80)
print("🚀 STARTUP PERFORMANCE REPORT")
print("=" * 80)
print(f"{'Component':<40} {'Time (ms)':<12} {'% of Total':<12} {'Status'}")
print("-" * 80)
# Sort by duration
sorted_results = sorted(
self.results.items(),
key=lambda x: x[1].duration_ms,
reverse=True
)
for name, result in sorted_results:
pct = (result.duration_ms / total_time * 100) if total_time > 0 else 0
status = "⚠️ SLOW" if result.duration_ms > 1000 else "✓ OK" if result.duration_ms > 100 else "✓ FAST"
indent = " " * (len(name.split(".")) - 1)
print(f"{indent}{name:<38} {result.duration_ms:>10.2f} {pct:>10.1f}% {status}")
print("-" * 80)
print(f"{'TOTAL STARTUP TIME':<40} {total_time:>10.2f} ms")
print("=" * 80)
# Identify bottlenecks
slow_components = [r for r in self.results.values() if r.duration_ms > 500]
if slow_components:
print("\n⚠️ BOTTLENECKS DETECTED:")
for r in sorted(slow_components, key=lambda x: x.duration_ms, reverse=True):
print(f"{r.name}: {r.duration_ms:.2f} ms")
print()
def get_bottlenecks(self, threshold_ms: float = 500) -> List[ProfileResult]:
"""Get components that took longer than threshold."""
return [
r for r in self.results.values()
if r.duration_ms > threshold_ms
]
def reset(self):
"""Reset profiler."""
self.results.clear()
self._stack.clear()
self._start_times.clear()
self.app_start_time = time.perf_counter()
def profile_startup(name: str):
"""Decorator for profiling function startup time."""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
profiler = StartupProfiler()
with profiler.profile(name):
return func(*args, **kwargs)
return wrapper
return decorator
# Convenience function
def get_startup_profiler() -> StartupProfiler:
"""Get global StartupProfiler instance."""
return StartupProfiler()
if __name__ == "__main__":
# Test profiler
profiler = StartupProfiler()
with profiler.profile("test_component"):
time.sleep(0.1)
with profiler.profile("nested_component"):
time.sleep(0.05)
with profiler.profile("another_component"):
time.sleep(0.2)
profiler.print_report()