mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
158 lines
4.9 KiB
Python
158 lines
4.9 KiB
Python
|
|
"""TrendTracker - Encapsulated trend tracking for live profiling metrics.
|
||
|
|
|
||
|
|
This module provides trend tracking functionality for profiling metrics,
|
||
|
|
calculating direction indicators (up/down/stable) and managing associated
|
||
|
|
visual attributes like colors.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import curses
|
||
|
|
from typing import Dict, Literal, Any
|
||
|
|
|
||
|
|
TrendDirection = Literal["up", "down", "stable"]
|
||
|
|
|
||
|
|
|
||
|
|
class TrendTracker:
|
||
|
|
"""
|
||
|
|
Tracks metric trends over time and provides visual indicators.
|
||
|
|
|
||
|
|
This class encapsulates all logic for:
|
||
|
|
- Tracking previous values of metrics
|
||
|
|
- Calculating trend directions (up/down/stable)
|
||
|
|
- Determining visual attributes (colors) for trends
|
||
|
|
- Managing enable/disable state
|
||
|
|
|
||
|
|
Example:
|
||
|
|
tracker = TrendTracker(colors_dict)
|
||
|
|
tracker.update("func1", "nsamples", 10)
|
||
|
|
trend = tracker.get_trend("func1", "nsamples")
|
||
|
|
color = tracker.get_color("func1", "nsamples")
|
||
|
|
"""
|
||
|
|
|
||
|
|
# Threshold for determining if a value has changed significantly
|
||
|
|
CHANGE_THRESHOLD = 0.001
|
||
|
|
|
||
|
|
def __init__(self, colors: Dict[str, int], enabled: bool = True):
|
||
|
|
"""
|
||
|
|
Initialize the trend tracker.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
colors: Dictionary containing color attributes including
|
||
|
|
'trend_up', 'trend_down', 'trend_stable'
|
||
|
|
enabled: Whether trend tracking is initially enabled
|
||
|
|
"""
|
||
|
|
self._previous_values: Dict[Any, Dict[str, float]] = {}
|
||
|
|
self._enabled = enabled
|
||
|
|
self._colors = colors
|
||
|
|
|
||
|
|
@property
|
||
|
|
def enabled(self) -> bool:
|
||
|
|
"""Whether trend tracking is enabled."""
|
||
|
|
return self._enabled
|
||
|
|
|
||
|
|
def toggle(self) -> bool:
|
||
|
|
"""
|
||
|
|
Toggle trend tracking on/off.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
New enabled state
|
||
|
|
"""
|
||
|
|
self._enabled = not self._enabled
|
||
|
|
return self._enabled
|
||
|
|
|
||
|
|
def set_enabled(self, enabled: bool) -> None:
|
||
|
|
"""Set trend tracking enabled state."""
|
||
|
|
self._enabled = enabled
|
||
|
|
|
||
|
|
def update(self, key: Any, metric: str, value: float) -> TrendDirection:
|
||
|
|
"""
|
||
|
|
Update a metric value and calculate its trend.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
key: Identifier for the entity (e.g., function)
|
||
|
|
metric: Name of the metric (e.g., 'nsamples', 'tottime')
|
||
|
|
value: Current value of the metric
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Trend direction: 'up', 'down', or 'stable'
|
||
|
|
"""
|
||
|
|
# Initialize storage for this key if needed
|
||
|
|
if key not in self._previous_values:
|
||
|
|
self._previous_values[key] = {}
|
||
|
|
|
||
|
|
# Get previous value, defaulting to current if not tracked yet
|
||
|
|
prev_value = self._previous_values[key].get(metric, value)
|
||
|
|
|
||
|
|
# Calculate trend
|
||
|
|
if value > prev_value + self.CHANGE_THRESHOLD:
|
||
|
|
trend = "up"
|
||
|
|
elif value < prev_value - self.CHANGE_THRESHOLD:
|
||
|
|
trend = "down"
|
||
|
|
else:
|
||
|
|
trend = "stable"
|
||
|
|
|
||
|
|
# Update previous value for next iteration
|
||
|
|
self._previous_values[key][metric] = value
|
||
|
|
|
||
|
|
return trend
|
||
|
|
|
||
|
|
def get_trend(self, key: Any, metric: str) -> TrendDirection:
|
||
|
|
"""
|
||
|
|
Get the current trend for a metric without updating.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
key: Identifier for the entity
|
||
|
|
metric: Name of the metric
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Trend direction, or 'stable' if not tracked
|
||
|
|
"""
|
||
|
|
# This would require storing trends separately, which we don't do
|
||
|
|
# For now, return stable if not found
|
||
|
|
return "stable"
|
||
|
|
|
||
|
|
def get_color(self, trend: TrendDirection) -> int:
|
||
|
|
"""
|
||
|
|
Get the color attribute for a trend direction.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
trend: The trend direction
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Curses color attribute (or A_NORMAL if disabled)
|
||
|
|
"""
|
||
|
|
if not self._enabled:
|
||
|
|
return curses.A_NORMAL
|
||
|
|
|
||
|
|
if trend == "up":
|
||
|
|
return self._colors.get("trend_up", curses.A_BOLD)
|
||
|
|
elif trend == "down":
|
||
|
|
return self._colors.get("trend_down", curses.A_BOLD)
|
||
|
|
else: # stable
|
||
|
|
return self._colors.get("trend_stable", curses.A_NORMAL)
|
||
|
|
|
||
|
|
def update_metrics(self, key: Any, metrics: Dict[str, float]) -> Dict[str, TrendDirection]:
|
||
|
|
"""
|
||
|
|
Update multiple metrics at once and get their trends.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
key: Identifier for the entity
|
||
|
|
metrics: Dictionary of metric_name -> value
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dictionary of metric_name -> trend_direction
|
||
|
|
"""
|
||
|
|
trends = {}
|
||
|
|
for metric, value in metrics.items():
|
||
|
|
trends[metric] = self.update(key, metric, value)
|
||
|
|
return trends
|
||
|
|
|
||
|
|
def clear(self) -> None:
|
||
|
|
"""Clear all tracked values (useful on stats reset)."""
|
||
|
|
self._previous_values.clear()
|
||
|
|
|
||
|
|
def __repr__(self) -> str:
|
||
|
|
"""String representation for debugging."""
|
||
|
|
status = "enabled" if self._enabled else "disabled"
|
||
|
|
tracked = len(self._previous_values)
|
||
|
|
return f"TrendTracker({status}, tracking {tracked} entities)"
|