refactor: add performance profiling hooks for bottleneck identification
Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Node Failover Simulation / failover-test (push) Has been cancelled
Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Node Failover Simulation / failover-test (push) Has been cancelled
- Create PerformanceProfiler class for tracking function execution times - Add profile_function decorator for automatic function profiling - Add profile_context context manager for profiling code blocks - Add profile_cprofile decorator for detailed cProfile profiling - Implement global profiler instance for application-wide profiling - Provide statistics tracking: total time, call count, avg/max/min times - Add logging for profiling results and statistics - Support enabling/disabling profiling dynamically
This commit is contained in:
249
aitbc/profiling.py
Normal file
249
aitbc/profiling.py
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
"""
|
||||||
|
Performance profiling utilities for AITBC
|
||||||
|
Provides profiling hooks for performance bottleneck identification
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import functools
|
||||||
|
import cProfile
|
||||||
|
import pstats
|
||||||
|
import io
|
||||||
|
from typing import Callable, Optional, Any, Dict
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from .aitbc_logging import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ProfilingResult:
|
||||||
|
"""Result of a profiling operation"""
|
||||||
|
function_name: str
|
||||||
|
total_time: float
|
||||||
|
call_count: int
|
||||||
|
avg_time: float
|
||||||
|
max_time: float
|
||||||
|
min_time: float
|
||||||
|
|
||||||
|
|
||||||
|
class PerformanceProfiler:
|
||||||
|
"""
|
||||||
|
Performance profiler for function execution time tracking.
|
||||||
|
Tracks execution statistics for function calls.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize performance profiler"""
|
||||||
|
self._stats: Dict[str, list] = defaultdict(list)
|
||||||
|
self._enabled = True
|
||||||
|
|
||||||
|
def enable(self) -> None:
|
||||||
|
"""Enable profiling"""
|
||||||
|
self._enabled = True
|
||||||
|
logger.info("Performance profiling enabled")
|
||||||
|
|
||||||
|
def disable(self) -> None:
|
||||||
|
"""Disable profiling"""
|
||||||
|
self._enabled = False
|
||||||
|
logger.info("Performance profiling disabled")
|
||||||
|
|
||||||
|
def record(self, function_name: str, execution_time: float) -> None:
|
||||||
|
"""
|
||||||
|
Record execution time for a function
|
||||||
|
|
||||||
|
Args:
|
||||||
|
function_name: Name of the function
|
||||||
|
execution_time: Execution time in seconds
|
||||||
|
"""
|
||||||
|
if self._enabled:
|
||||||
|
self._stats[function_name].append(execution_time)
|
||||||
|
|
||||||
|
def get_stats(self, function_name: Optional[str] = None) -> ProfilingResult | Dict[str, ProfilingResult]:
|
||||||
|
"""
|
||||||
|
Get profiling statistics for a function or all functions
|
||||||
|
|
||||||
|
Args:
|
||||||
|
function_name: Specific function name, or None for all functions
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ProfilingResult or dictionary of results
|
||||||
|
"""
|
||||||
|
if function_name:
|
||||||
|
times = self._stats.get(function_name, [])
|
||||||
|
if not times:
|
||||||
|
return ProfilingResult(
|
||||||
|
function_name=function_name,
|
||||||
|
total_time=0,
|
||||||
|
call_count=0,
|
||||||
|
avg_time=0,
|
||||||
|
max_time=0,
|
||||||
|
min_time=0
|
||||||
|
)
|
||||||
|
return ProfilingResult(
|
||||||
|
function_name=function_name,
|
||||||
|
total_time=sum(times),
|
||||||
|
call_count=len(times),
|
||||||
|
avg_time=sum(times) / len(times),
|
||||||
|
max_time=max(times),
|
||||||
|
min_time=min(times)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
name: self.get_stats(name)
|
||||||
|
for name in self._stats.keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
def clear_stats(self) -> None:
|
||||||
|
"""Clear all profiling statistics"""
|
||||||
|
self._stats.clear()
|
||||||
|
logger.info("Profiling statistics cleared")
|
||||||
|
|
||||||
|
def print_stats(self, function_name: Optional[str] = None) -> None:
|
||||||
|
"""
|
||||||
|
Print profiling statistics to console
|
||||||
|
|
||||||
|
Args:
|
||||||
|
function_name: Specific function name, or None for all functions
|
||||||
|
"""
|
||||||
|
stats = self.get_stats(function_name)
|
||||||
|
|
||||||
|
if function_name:
|
||||||
|
self._print_single_stat(stats)
|
||||||
|
else:
|
||||||
|
for name, stat in stats.items():
|
||||||
|
logger.info(f"--- {name} ---")
|
||||||
|
self._print_single_stat(stat)
|
||||||
|
|
||||||
|
def _print_single_stat(self, stat: ProfilingResult) -> None:
|
||||||
|
"""Print single profiling result"""
|
||||||
|
logger.info(f" Total time: {stat.total_time:.4f}s")
|
||||||
|
logger.info(f" Call count: {stat.call_count}")
|
||||||
|
logger.info(f" Avg time: {stat.avg_time:.4f}s")
|
||||||
|
logger.info(f" Max time: {stat.max_time:.4f}s")
|
||||||
|
logger.info(f" Min time: {stat.min_time:.4f}s")
|
||||||
|
|
||||||
|
|
||||||
|
# Global profiler instance
|
||||||
|
_global_profiler = PerformanceProfiler()
|
||||||
|
|
||||||
|
|
||||||
|
def profile_function(profiler: Optional[PerformanceProfiler] = None):
|
||||||
|
"""
|
||||||
|
Decorator to profile function execution time
|
||||||
|
|
||||||
|
Args:
|
||||||
|
profiler: Custom profiler instance, or None to use global profiler
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Decorated function with profiling
|
||||||
|
"""
|
||||||
|
def decorator(func: Callable) -> Callable:
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwargs) -> Any:
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
try:
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
return result
|
||||||
|
finally:
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
execution_time = end_time - start_time
|
||||||
|
|
||||||
|
target_profiler = profiler or _global_profiler
|
||||||
|
target_profiler.record(func.__name__, execution_time)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def profile_context(name: str, profiler: Optional[PerformanceProfiler] = None):
|
||||||
|
"""
|
||||||
|
Context manager for profiling code blocks
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name for the profiling context
|
||||||
|
profiler: Custom profiler instance, or None to use global profiler
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
target_profiler = profiler or _global_profiler
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
execution_time = end_time - start_time
|
||||||
|
target_profiler.record(name, execution_time)
|
||||||
|
|
||||||
|
|
||||||
|
def profile_cprofile(func: Callable) -> Callable:
|
||||||
|
"""
|
||||||
|
Decorator to profile function using cProfile
|
||||||
|
|
||||||
|
Args:
|
||||||
|
func: Function to profile
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Decorated function with cProfile profiling
|
||||||
|
"""
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwargs) -> Any:
|
||||||
|
profiler = cProfile.Profile()
|
||||||
|
profiler.enable()
|
||||||
|
try:
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
return result
|
||||||
|
finally:
|
||||||
|
profiler.disable()
|
||||||
|
|
||||||
|
# Print statistics
|
||||||
|
s = io.StringIO()
|
||||||
|
ps = pstats.Stats(profiler, stream=s).sort_stats('cumulative')
|
||||||
|
ps.print_stats(10) # Print top 10 functions
|
||||||
|
logger.info(f"Profile for {func.__name__}:\n{s.getvalue()}")
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def get_global_profiler() -> PerformanceProfiler:
|
||||||
|
"""
|
||||||
|
Get the global performance profiler instance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Global PerformanceProfiler instance
|
||||||
|
"""
|
||||||
|
return _global_profiler
|
||||||
|
|
||||||
|
|
||||||
|
def enable_global_profiling() -> None:
|
||||||
|
"""Enable global performance profiling"""
|
||||||
|
_global_profiler.enable()
|
||||||
|
|
||||||
|
|
||||||
|
def disable_global_profiling() -> None:
|
||||||
|
"""Disable global performance profiling"""
|
||||||
|
_global_profiler.disable()
|
||||||
|
|
||||||
|
|
||||||
|
def get_profiling_summary() -> Dict[str, ProfilingResult]:
|
||||||
|
"""
|
||||||
|
Get summary of all profiling data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary of profiling results
|
||||||
|
"""
|
||||||
|
return _global_profiler.get_stats()
|
||||||
|
|
||||||
|
|
||||||
|
def print_profiling_summary() -> None:
|
||||||
|
"""Print summary of all profiling data"""
|
||||||
|
_global_profiler.print_stats()
|
||||||
|
|
||||||
|
|
||||||
|
def clear_profiling_data() -> None:
|
||||||
|
"""Clear all profiling data"""
|
||||||
|
_global_profiler.clear_stats()
|
||||||
Reference in New Issue
Block a user