-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(web): Provide
Server-Timing
Header
Allows users to debug/see for themselves where/why requests are (potentially) slow. Closes #37.
- Loading branch information
Showing
4 changed files
with
138 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
from dataclasses import dataclass, field | ||
from datetime import timedelta | ||
from time import perf_counter | ||
from typing import Optional | ||
|
||
|
||
@dataclass | ||
class Stopwatch: | ||
"""A simple stopwatch for timing execution. | ||
Call it with a segment name, and it will start timing that segment, stopping when it | ||
is called again with the next segment or explicitly with `stop()`. | ||
The results are available in the `timings` attribute. | ||
""" | ||
|
||
timings: dict[str, timedelta] = field(default_factory=dict) | ||
_start: Optional[float] = field(repr=False, default=None) | ||
_current_segment: Optional[str] = field(repr=False, default=None) | ||
_finished: bool = field(repr=False, default=False) | ||
|
||
def __getitem__(self, key: str) -> timedelta: | ||
return self.timings[key] | ||
|
||
def __call__(self, segment: str) -> None: | ||
stop = perf_counter() | ||
|
||
if segment in self.timings or segment == self._current_segment: | ||
raise ValueError(f"Segment '{segment}' already exists.") | ||
|
||
if self._current_segment is not None and self._start is not None: | ||
self.timings[self._current_segment] = timedelta(seconds=stop - self._start) | ||
|
||
if self._finished: | ||
self._start = None | ||
self._current_segment = None | ||
self._finished = False | ||
else: | ||
self._start = perf_counter() | ||
self._current_segment = segment | ||
|
||
def stop(self) -> None: | ||
"""Stops the current segment and adds it to the timings. | ||
Calling the stopwatch again with a new segment will restart it. | ||
""" | ||
self._finished = True | ||
self(segment="__final_segment__") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import time | ||
from datetime import timedelta | ||
|
||
import pytest | ||
|
||
from ancv.timing import Stopwatch | ||
|
||
|
||
def test_stopwatch_disallows_same_segments(): | ||
stopwatch = Stopwatch() | ||
with pytest.raises(ValueError): | ||
stopwatch("segment1") | ||
stopwatch("segment1") | ||
|
||
|
||
def sleep(seconds: float) -> None: | ||
"""Sleep for the given number of seconds. | ||
This is a replacement for the built-in `time.sleep()` function which will send | ||
control back to the OS, introducing an uncertainty depending on the OS. | ||
To keep our tests fast, we want to sleep for brief periods, but that will yield a | ||
large relative error from the approx. constant OS thread sleep uncertainties. | ||
""" | ||
now = time.time() | ||
while time.time() <= (now + seconds): | ||
time.sleep(0.001) | ||
|
||
|
||
def test_stopwatch_basics(): | ||
stopwatch = Stopwatch() | ||
stopwatch("segment1") | ||
sleep(0.1) | ||
stopwatch("segment2") | ||
sleep(0.1) | ||
stopwatch("segment3") | ||
sleep(0.2) | ||
stopwatch.stop() | ||
sleep(0.1) | ||
stopwatch("segment4") | ||
sleep(0.5) | ||
stopwatch.stop() | ||
|
||
expected = { | ||
"segment1": timedelta(seconds=0.1), | ||
"segment2": timedelta(seconds=0.1), | ||
"segment3": timedelta(seconds=0.2), | ||
"segment4": timedelta(seconds=0.5), | ||
} | ||
for real, expected in zip(stopwatch.timings.values(), expected.values()): | ||
# https://stackoverflow.com/a/1133888/11477374 : | ||
os_thread_sleep_uncertainty_microseconds = 20_000 | ||
assert ( | ||
pytest.approx( | ||
real.microseconds, abs=os_thread_sleep_uncertainty_microseconds | ||
) | ||
== expected.microseconds | ||
) |