Spaces:
Sleeping
Sleeping
File size: 6,678 Bytes
830d137 feb1b1c 830d137 feb1b1c 830d137 feb1b1c 830d137 bb77312 feb1b1c 830d137 bb77312 830d137 bb77312 830d137 bb77312 830d137 bb77312 830d137 bb77312 830d137 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
from __future__ import annotations
import sys
import time
from collections.abc import MutableMapping
from types import TracebackType
from typing import final
__all__: tuple[str, ...] = (
"BudgetExceededError",
"BudgetGuard",
"StageTimer",
"format_duration",
"sample_peak_rss_mb",
)
def _posix_peak_rss_mb() -> float:
"""POSIX peak RSS via ``resource.getrusage`` (Linux/Darwin/BSD only).
``ru_maxrss`` is kibibytes on Linux and bytes on macOS/BSD (Darwin); both
are normalized here so the figure is platform-stable.
"""
import resource
raw_units = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
divisor = 1024.0 * 1024.0 if sys.platform == "darwin" else 1024.0
return raw_units / divisor
def _windows_peak_rss_mb() -> float:
"""Windows peak working-set size via the ``psapi`` ``GetProcessMemoryInfo`` call.
stdlib-only (``ctypes``); no ``pywin32``/``psutil`` dependency. Returns 0.0
on any failure -- a peak-RSS reading is observability, not a behavior-
critical value, so a failed sample must never abort a run.
"""
import ctypes
from ctypes import wintypes
class _ProcessMemoryCounters(ctypes.Structure):
_fields_ = (
("cb", wintypes.DWORD),
("PageFaultCount", wintypes.DWORD),
("PeakWorkingSetSize", ctypes.c_size_t),
("WorkingSetSize", ctypes.c_size_t),
("QuotaPeakPagedPoolUsage", ctypes.c_size_t),
("QuotaPagedPoolUsage", ctypes.c_size_t),
("QuotaPeakNonPagedPoolUsage", ctypes.c_size_t),
("QuotaNonPagedPoolUsage", ctypes.c_size_t),
("PagefileUsage", ctypes.c_size_t),
("PeakPagefileUsage", ctypes.c_size_t),
)
try:
counters = _ProcessMemoryCounters()
counters.cb = ctypes.sizeof(_ProcessMemoryCounters)
kernel32 = ctypes.WinDLL("kernel32") # type: ignore[attr-defined]
psapi = ctypes.WinDLL("psapi") # type: ignore[attr-defined]
handle = kernel32.GetCurrentProcess()
ok = psapi.GetProcessMemoryInfo(handle, ctypes.byref(counters), counters.cb)
if not ok:
return 0.0
return float(counters.PeakWorkingSetSize) / (1024.0 * 1024.0)
except (OSError, AttributeError, ValueError):
return 0.0
def sample_peak_rss_mb() -> float:
"""Return the process's peak RSS so far, normalized to MB, on any platform.
Dispatches to the POSIX (``resource``) or Windows (``ctypes``/``psapi``)
reading so neither ``redstack build`` nor ``redstack rank`` crashes at
import time on Windows (CLAUDE.md §1: cross-platform execution stability) —
the POSIX-only ``resource`` module does not exist there at all.
"""
if sys.platform == "win32":
return _windows_peak_rss_mb()
return _posix_peak_rss_mb()
def format_duration(seconds: float) -> str:
"""Format a non-negative duration in seconds as ``MM:SS.mmm``.
Minutes are not capped at 59 (a multi-hour offline build still renders as
e.g. ``137:04.250`` rather than wrapping), matching the MM:SS.ms telemetry
format used for offline/online pass durations.
"""
total_ms = round(seconds * 1000.0)
minutes, remainder_ms = divmod(total_ms, 60_000)
secs, ms = divmod(remainder_ms, 1000)
return f"{minutes:02d}:{secs:02d}.{ms:03d}"
@final
class StageTimer:
"""Context manager measuring one stage's wall-clock duration in milliseconds.
On ``__exit__`` the elapsed time is written into ``ledger[stage]``, so a
composition root can accumulate every stage's timing into one dict that
feeds ``RunReport.timings`` (Ports §13) without this class holding any
reference back to the report itself.
"""
__slots__ = ("_ledger", "_stage", "_start")
def __init__(self, stage: str, ledger: MutableMapping[str, float]) -> None:
self._stage = stage
self._ledger = ledger
self._start = 0.0
def __enter__(self) -> StageTimer:
self._start = time.perf_counter()
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
elapsed_ms = (time.perf_counter() - self._start) * 1000.0
self._ledger[self._stage] = elapsed_ms
class BudgetExceededError(RuntimeError):
"""The wall-clock or RSS budget for the run was exceeded."""
@final
class BudgetGuard:
"""Hard wall-time + RSS budget guard for one run (CLAUDE.md §1: <=16GB RAM).
Constructed once at the start of a run with the two ceilings; stages call
:meth:`check` at safe boundaries (between rows, between stages) so a
breach is caught promptly rather than only at the very end.
"""
__slots__ = ("_limit_seconds", "_max_rss_mb", "_peak_rss_mb", "_started_at")
def __init__(self, limit_seconds: float, max_rss_mb: float) -> None:
self._limit_seconds = limit_seconds
self._max_rss_mb = max_rss_mb
self._started_at = time.perf_counter()
self._peak_rss_mb = 0.0
def check(self) -> None:
"""Sample elapsed time + RSS; raise :class:`BudgetExceededError` if over.
Raises:
BudgetExceededError: the wall-clock or RSS ceiling has been crossed.
"""
self._peak_rss_mb = max(self._peak_rss_mb, sample_peak_rss_mb())
if self.used_seconds > self._limit_seconds:
raise BudgetExceededError(
f"wall-clock budget exceeded: used {self.used_seconds:.3f}s "
f"> limit {self._limit_seconds:.3f}s"
)
if self._peak_rss_mb > self._max_rss_mb:
raise BudgetExceededError(
f"RSS budget exceeded: peak {self._peak_rss_mb:.1f}MB "
f"> ceiling {self._max_rss_mb:.1f}MB"
)
@property
def limit_seconds(self) -> float:
"""The configured wall-clock ceiling, in seconds."""
return self._limit_seconds
@property
def used_seconds(self) -> float:
"""Elapsed wall-clock time since construction, in seconds."""
return time.perf_counter() - self._started_at
@property
def peak_rss_mb(self) -> float:
"""The highest RSS observed across all :meth:`check` calls, in MB."""
self._peak_rss_mb = max(self._peak_rss_mb, sample_peak_rss_mb())
return self._peak_rss_mb
@property
def within_budget(self) -> bool:
"""Whether both the time and RSS ceilings currently hold."""
return (
self.used_seconds <= self._limit_seconds
and self.peak_rss_mb <= self._max_rss_mb
)
|