Spaces:
Build error
Build error
Download headroom/memory/tracker.py from minhtudragon/headroom_3: direct link, hf CLI and curl.
- Browser
- Download file 12.2 kB
-
https://huggingface.co/spaces/minhtudragon/headroom_3/resolve/aac850be3e24202a5f857fc294cd291db6f4d3e0/headroom/memory/tracker.py
- Command line
-
hf download hf://spaces/minhtudragon/headroom_3@aac850be3e24202a5f857fc294cd291db6f4d3e0/headroom/memory/tracker.py
-
curl -L -o tracker.py https://huggingface.co/spaces/minhtudragon/headroom_3/resolve/aac850be3e24202a5f857fc294cd291db6f4d3e0/headroom/memory/tracker.py
12.2 kB
| """Memory tracking infrastructure for headroom. | |
| This module provides centralized memory tracking across all components, | |
| enabling observability into memory usage patterns and budget enforcement. | |
| """ | |
| from __future__ import annotations | |
| import sys | |
| import threading | |
| import time | |
| from collections.abc import Callable | |
| from dataclasses import dataclass, field | |
| from typing import Any | |
| # Try to import psutil for process memory tracking | |
| try: | |
| import psutil | |
| PSUTIL_AVAILABLE = True | |
| except ImportError: | |
| PSUTIL_AVAILABLE = False | |
| class ComponentStats: | |
| """Statistics for a single memory component.""" | |
| name: str | |
| entry_count: int | |
| size_bytes: int | |
| budget_bytes: int | None = None | |
| hits: int = 0 | |
| misses: int = 0 | |
| evictions: int = 0 | |
| last_updated: float = field(default_factory=time.time) | |
| def size_mb(self) -> float: | |
| """Size in megabytes.""" | |
| return self.size_bytes / (1024 * 1024) | |
| def budget_mb(self) -> float | None: | |
| """Budget in megabytes.""" | |
| return self.budget_bytes / (1024 * 1024) if self.budget_bytes else None | |
| def budget_used_percent(self) -> float | None: | |
| """Percentage of budget used.""" | |
| if self.budget_bytes and self.budget_bytes > 0: | |
| return (self.size_bytes / self.budget_bytes) * 100 | |
| return None | |
| def hit_rate(self) -> float | None: | |
| """Cache hit rate as percentage.""" | |
| total = self.hits + self.misses | |
| if total > 0: | |
| return (self.hits / total) * 100 | |
| return None | |
| def to_dict(self) -> dict[str, Any]: | |
| """Convert to dictionary for JSON serialization.""" | |
| return { | |
| "name": self.name, | |
| "entry_count": self.entry_count, | |
| "size_bytes": self.size_bytes, | |
| "size_mb": round(self.size_mb, 2), | |
| "budget_bytes": self.budget_bytes, | |
| "budget_mb": round(self.budget_mb, 2) if self.budget_mb else None, | |
| "budget_used_percent": round(self.budget_used_percent, 2) | |
| if self.budget_used_percent | |
| else None, | |
| "hits": self.hits, | |
| "misses": self.misses, | |
| "evictions": self.evictions, | |
| "hit_rate": round(self.hit_rate, 2) if self.hit_rate else None, | |
| "last_updated": self.last_updated, | |
| } | |
| class ProcessStats: | |
| """Process-level memory statistics.""" | |
| rss_bytes: int | |
| vms_bytes: int | |
| percent: float | |
| available_bytes: int | |
| total_bytes: int | |
| def rss_mb(self) -> float: | |
| """Resident set size in MB.""" | |
| return self.rss_bytes / (1024 * 1024) | |
| def vms_mb(self) -> float: | |
| """Virtual memory size in MB.""" | |
| return self.vms_bytes / (1024 * 1024) | |
| def available_mb(self) -> float: | |
| """Available system memory in MB.""" | |
| return self.available_bytes / (1024 * 1024) | |
| def total_mb(self) -> float: | |
| """Total system memory in MB.""" | |
| return self.total_bytes / (1024 * 1024) | |
| def to_dict(self) -> dict[str, Any]: | |
| """Convert to dictionary for JSON serialization.""" | |
| return { | |
| "rss_bytes": self.rss_bytes, | |
| "rss_mb": round(self.rss_mb, 2), | |
| "vms_bytes": self.vms_bytes, | |
| "vms_mb": round(self.vms_mb, 2), | |
| "percent": round(self.percent, 2), | |
| "available_mb": round(self.available_mb, 2), | |
| "total_mb": round(self.total_mb, 2), | |
| } | |
| class MemoryReport: | |
| """Complete memory report including process and component stats.""" | |
| process: ProcessStats | |
| components: dict[str, ComponentStats] | |
| total_tracked_bytes: int | |
| target_budget_bytes: int | None | |
| timestamp: float = field(default_factory=time.time) | |
| def total_tracked_mb(self) -> float: | |
| """Total tracked memory in MB.""" | |
| return self.total_tracked_bytes / (1024 * 1024) | |
| def target_budget_mb(self) -> float | None: | |
| """Target budget in MB.""" | |
| return self.target_budget_bytes / (1024 * 1024) if self.target_budget_bytes else None | |
| def is_over_budget(self) -> bool: | |
| """Check if tracked memory exceeds target budget.""" | |
| if self.target_budget_bytes: | |
| return self.total_tracked_bytes > self.target_budget_bytes | |
| return False | |
| def to_dict(self) -> dict[str, Any]: | |
| """Convert to dictionary for JSON serialization.""" | |
| return { | |
| "process": self.process.to_dict(), | |
| "components": {name: stats.to_dict() for name, stats in self.components.items()}, | |
| "total_tracked_bytes": self.total_tracked_bytes, | |
| "total_tracked_mb": round(self.total_tracked_mb, 2), | |
| "target_budget_bytes": self.target_budget_bytes, | |
| "target_budget_mb": round(self.target_budget_mb, 2) if self.target_budget_mb else None, | |
| "is_over_budget": self.is_over_budget, | |
| "timestamp": self.timestamp, | |
| } | |
| class MemoryTracker: | |
| """Singleton that tracks memory usage across all components. | |
| Usage: | |
| # Register a component | |
| tracker = MemoryTracker.get() | |
| tracker.register("my_store", my_store.get_memory_stats) | |
| # Get all stats | |
| report = tracker.get_report() | |
| # Get specific component | |
| stats = tracker.get_component_stats("my_store") | |
| """ | |
| _instance: MemoryTracker | None = None | |
| _lock: threading.Lock = threading.Lock() | |
| def __init__(self, target_budget_mb: float | None = None): | |
| """Initialize the tracker. | |
| Args: | |
| target_budget_mb: Target memory budget in MB for all tracked components. | |
| """ | |
| self._components: dict[str, Callable[[], ComponentStats]] = {} | |
| self._target_budget_bytes: int | None = ( | |
| int(target_budget_mb * 1024 * 1024) if target_budget_mb else None | |
| ) | |
| self._component_lock = threading.Lock() | |
| def get(cls, target_budget_mb: float | None = None) -> MemoryTracker: | |
| """Get or create the singleton instance. | |
| Args: | |
| target_budget_mb: Target memory budget (only used on first call). | |
| Returns: | |
| The singleton MemoryTracker instance. | |
| """ | |
| if cls._instance is None: | |
| with cls._lock: | |
| if cls._instance is None: | |
| cls._instance = cls(target_budget_mb=target_budget_mb) | |
| return cls._instance | |
| def reset(cls) -> None: | |
| """Reset the singleton instance. Useful for testing.""" | |
| with cls._lock: | |
| cls._instance = None | |
| def set_target_budget(self, budget_mb: float) -> None: | |
| """Set the target memory budget. | |
| Args: | |
| budget_mb: Target budget in megabytes. | |
| """ | |
| self._target_budget_bytes = int(budget_mb * 1024 * 1024) | |
| def register(self, name: str, stats_fn: Callable[[], ComponentStats]) -> None: | |
| """Register a component's stats function. | |
| Args: | |
| name: Unique name for the component. | |
| stats_fn: Function that returns ComponentStats for this component. | |
| """ | |
| with self._component_lock: | |
| self._components[name] = stats_fn | |
| def unregister(self, name: str) -> bool: | |
| """Unregister a component. | |
| Args: | |
| name: Name of the component to unregister. | |
| Returns: | |
| True if component was unregistered, False if not found. | |
| """ | |
| with self._component_lock: | |
| if name in self._components: | |
| del self._components[name] | |
| return True | |
| return False | |
| def get_component_stats(self, name: str) -> ComponentStats | None: | |
| """Get stats for a specific component. | |
| Args: | |
| name: Name of the component. | |
| Returns: | |
| ComponentStats or None if component not found. | |
| """ | |
| with self._component_lock: | |
| if name in self._components: | |
| try: | |
| return self._components[name]() | |
| except Exception: | |
| return None | |
| return None | |
| def get_all_component_stats(self) -> dict[str, ComponentStats]: | |
| """Get stats for all registered components. | |
| Returns: | |
| Dictionary mapping component names to their stats. | |
| """ | |
| stats: dict[str, ComponentStats] = {} | |
| with self._component_lock: | |
| for name, fn in self._components.items(): | |
| try: | |
| stats[name] = fn() | |
| except Exception: | |
| # Skip components that fail to report stats | |
| pass | |
| return stats | |
| def get_process_stats(self) -> ProcessStats: | |
| """Get process-level memory statistics. | |
| Returns: | |
| ProcessStats with current memory usage. | |
| """ | |
| if PSUTIL_AVAILABLE: | |
| process = psutil.Process() | |
| mem_info = process.memory_info() | |
| sys_mem = psutil.virtual_memory() | |
| return ProcessStats( | |
| rss_bytes=mem_info.rss, | |
| vms_bytes=mem_info.vms, | |
| percent=process.memory_percent(), | |
| available_bytes=sys_mem.available, | |
| total_bytes=sys_mem.total, | |
| ) | |
| else: | |
| # Fallback when psutil not available | |
| return ProcessStats( | |
| rss_bytes=0, | |
| vms_bytes=0, | |
| percent=0.0, | |
| available_bytes=0, | |
| total_bytes=0, | |
| ) | |
| def get_total_tracked_bytes(self) -> int: | |
| """Get total memory used by all tracked components. | |
| Returns: | |
| Total bytes used by tracked components. | |
| """ | |
| stats = self.get_all_component_stats() | |
| return sum(s.size_bytes for s in stats.values()) | |
| def get_report(self) -> MemoryReport: | |
| """Get a complete memory report. | |
| Returns: | |
| MemoryReport with process and component statistics. | |
| """ | |
| process_stats = self.get_process_stats() | |
| component_stats = self.get_all_component_stats() | |
| total_tracked = sum(s.size_bytes for s in component_stats.values()) | |
| return MemoryReport( | |
| process=process_stats, | |
| components=component_stats, | |
| total_tracked_bytes=total_tracked, | |
| target_budget_bytes=self._target_budget_bytes, | |
| ) | |
| def registered_components(self) -> list[str]: | |
| """Get list of registered component names.""" | |
| with self._component_lock: | |
| return list(self._components.keys()) | |
| def target_budget_mb(self) -> float | None: | |
| """Get target budget in MB.""" | |
| return self._target_budget_bytes / (1024 * 1024) if self._target_budget_bytes else None | |
| def estimate_object_size(obj: Any, seen: set | None = None) -> int: | |
| """Estimate the memory size of a Python object recursively. | |
| This provides a rough estimate by traversing the object graph. | |
| For more accurate measurements, use tracemalloc or memory_profiler. | |
| Args: | |
| obj: Object to measure. | |
| seen: Set of already-seen object ids (for cycle detection). | |
| Returns: | |
| Estimated size in bytes. | |
| """ | |
| if seen is None: | |
| seen = set() | |
| obj_id = id(obj) | |
| if obj_id in seen: | |
| return 0 | |
| seen.add(obj_id) | |
| size = sys.getsizeof(obj) | |
| if isinstance(obj, dict): | |
| size += sum( | |
| estimate_object_size(k, seen) + estimate_object_size(v, seen) for k, v in obj.items() | |
| ) | |
| elif isinstance(obj, (list, tuple, set, frozenset)): | |
| size += sum(estimate_object_size(item, seen) for item in obj) | |
| elif hasattr(obj, "__dict__"): | |
| size += estimate_object_size(obj.__dict__, seen) | |
| elif hasattr(obj, "__slots__"): | |
| size += sum( | |
| estimate_object_size(getattr(obj, slot, None), seen) | |
| for slot in obj.__slots__ | |
| if hasattr(obj, slot) | |
| ) | |
| return size | |