Helper class for tracking accumulated time (in seconds). Every value passed to :meth:`add` is also fed into an internal :class:`DistributionTracker` (a KLL sketch with bounded memory) so :meth:`percentile` can return an approximate p-th percentile at any time. The sketch uses O(k lo
| 161 | |
| 162 | |
| 163 | class Timer: |
| 164 | """Helper class for tracking accumulated time (in seconds). |
| 165 | |
| 166 | Every value passed to :meth:`add` is also fed into an internal |
| 167 | :class:`DistributionTracker` (a KLL sketch with bounded memory) so |
| 168 | :meth:`percentile` can return an approximate p-th percentile at any |
| 169 | time. The sketch uses O(k log(n/k)) memory (k=200 by default), so it |
| 170 | stays a few kilobytes regardless of how many samples are added — |
| 171 | safe for long-running production jobs. |
| 172 | |
| 173 | Percentile accuracy is the KLL guarantee — roughly 1.65% rank error |
| 174 | at the default k=200. When the optional ``datasketches`` dependency |
| 175 | is not installed, :meth:`percentile` returns 0 (the other stats are |
| 176 | unaffected). |
| 177 | """ |
| 178 | |
| 179 | def __init__(self): |
| 180 | self._total: float = 0 |
| 181 | self._min: float = float("inf") |
| 182 | self._max: float = 0 |
| 183 | self._total_count: float = 0 |
| 184 | # Bounded-memory percentile backend. add() forwards every value |
| 185 | # to ``add_sample`` and ``percentile`` reads from it. |
| 186 | self._distribution: DistributionTracker = DistributionTracker() |
| 187 | |
| 188 | @contextmanager |
| 189 | def timer(self) -> None: |
| 190 | time_start = time.perf_counter() |
| 191 | try: |
| 192 | yield |
| 193 | finally: |
| 194 | self.add(time.perf_counter() - time_start) |
| 195 | |
| 196 | def add(self, value: float) -> None: |
| 197 | self._total += value |
| 198 | if value < self._min: |
| 199 | self._min = value |
| 200 | if value > self._max: |
| 201 | self._max = value |
| 202 | self._total_count += 1 |
| 203 | self._distribution.add_sample(value) |
| 204 | |
| 205 | def get(self) -> float: |
| 206 | return self._total |
| 207 | |
| 208 | def min(self) -> float: |
| 209 | return self._min |
| 210 | |
| 211 | def max(self) -> float: |
| 212 | return self._max |
| 213 | |
| 214 | def avg(self) -> float: |
| 215 | return self._total / self._total_count if self._total_count else float("inf") |
| 216 | |
| 217 | def percentile(self, p: float) -> float: |
| 218 | """Approximate ``p``-th percentile in seconds. |
| 219 | |
| 220 | Backed by the internal :class:`DistributionTracker`'s KLL |
no outgoing calls
searching dependent graphs…