Skip to content

landingai.timer

Timer class for timing code execution and reporting results.

Timer dataclass

Bases: ContextDecorator

Time your code using a class, context manager, or decorator. See below examples for usage. As a class:

t = Timer(name="class")
t.start()
# Do something
t.stop()

As a context manager:

with Timer(name="context manager"):
    # Do something

As a decorator:

@Timer(name="decorator")
def stuff():
    # Do something

All the time values are stored in a global dictionary, accessible via the stats attribute. See the TimerStats class for more information.

Source code in landingai/timer.py
@dataclass
class Timer(ContextDecorator):
    """Time your code using a class, context manager, or decorator.
    See below examples for usage.
    As a class:
    ```
    t = Timer(name="class")
    t.start()
    # Do something
    t.stop()
    ```

    As a context manager:
    ```
    with Timer(name="context manager"):
        # Do something
    ```

    As a decorator:
    ```
    @Timer(name="decorator")
    def stuff():
        # Do something
    ```

    All the time values are stored in a global dictionary, accessible via the `stats` attribute.
    See the `TimerStats` class for more information.
    """

    # Global stats of all the timers
    stats: ClassVar[TimerStats] = TimerStats()
    # Instance attributes
    name: str = "default"
    text: str = (
        "Timer '{name}' finished. Elapsed time: {color}{:0.3f}{color_reset} seconds."
    )
    log_fn: Callable[[str], None] = logging.getLogger(__name__).info
    _elapsed_time: float = field(default=math.nan, init=False, repr=False)
    # Keep track of the start time for each thread and each timer (i.e. each function)
    _thread_local_data: threading.local = field(
        default=threading.local(), init=False, repr=False
    )

    def start(self) -> None:
        """Start a new timer."""
        if not hasattr(self._thread_local_data, "_start_time"):
            self._thread_local_data._start_time = {}
        if self.name in self._thread_local_data._start_time:
            raise ValueError("Timer is running. Use .stop() to stop it")
        self._thread_local_data._start_time[self.name] = time.perf_counter()

    def stop(self) -> float:
        """Stop the timer, and report the elapsed time."""
        if (
            not hasattr(self._thread_local_data, "_start_time")
            or self.name not in self._thread_local_data._start_time
        ):
            raise ValueError("Timer is not running. Use .start() to start it")

        # Calculate elapsed time
        self._elapsed_time = (
            time.perf_counter() - self._thread_local_data._start_time[self.name]
        )
        # Report elapsed time
        attributes = {
            "name": self.name,
            "milliseconds": self._elapsed_time * 1000,
            "seconds": self._elapsed_time,
            "minutes": self._elapsed_time / 60,
            "color": TextColor.BOLD_RED.value,
            "color_reset": TextColor.RESET.value,
        }
        text = self.text.format(self._elapsed_time, **attributes)
        self.log_fn(str(text))
        # Save stats
        Timer.stats.add(self.name, self._elapsed_time)

        del self._thread_local_data._start_time[self.name]

        return self._elapsed_time

    def __enter__(self) -> "Timer":
        """Start a new timer as a context manager."""
        self.start()
        return self

    def __exit__(self, *exc_info: Any) -> None:
        """Stop the context manager timer."""
        self.stop()

__enter__()

Start a new timer as a context manager.

Source code in landingai/timer.py
def __enter__(self) -> "Timer":
    """Start a new timer as a context manager."""
    self.start()
    return self

__exit__(*exc_info)

Stop the context manager timer.

Source code in landingai/timer.py
def __exit__(self, *exc_info: Any) -> None:
    """Stop the context manager timer."""
    self.stop()

start()

Start a new timer.

Source code in landingai/timer.py
def start(self) -> None:
    """Start a new timer."""
    if not hasattr(self._thread_local_data, "_start_time"):
        self._thread_local_data._start_time = {}
    if self.name in self._thread_local_data._start_time:
        raise ValueError("Timer is running. Use .stop() to stop it")
    self._thread_local_data._start_time[self.name] = time.perf_counter()

stop()

Stop the timer, and report the elapsed time.

Source code in landingai/timer.py
def stop(self) -> float:
    """Stop the timer, and report the elapsed time."""
    if (
        not hasattr(self._thread_local_data, "_start_time")
        or self.name not in self._thread_local_data._start_time
    ):
        raise ValueError("Timer is not running. Use .start() to start it")

    # Calculate elapsed time
    self._elapsed_time = (
        time.perf_counter() - self._thread_local_data._start_time[self.name]
    )
    # Report elapsed time
    attributes = {
        "name": self.name,
        "milliseconds": self._elapsed_time * 1000,
        "seconds": self._elapsed_time,
        "minutes": self._elapsed_time / 60,
        "color": TextColor.BOLD_RED.value,
        "color_reset": TextColor.RESET.value,
    }
    text = self.text.format(self._elapsed_time, **attributes)
    self.log_fn(str(text))
    # Save stats
    Timer.stats.add(self.name, self._elapsed_time)

    del self._thread_local_data._start_time[self.name]

    return self._elapsed_time

TimerStats

Custom dictionary that stores time values of different timers. For each timer, it stores a sequence of time duration values. Each sequence is limited to a maximum size to avoid memory issues.

Source code in landingai/timer.py
class TimerStats:
    """Custom dictionary that stores time values of different timers.
    For each timer, it stores a sequence of time duration values. Each sequence is limited to a maximum size to avoid memory issues.

    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        """Add a private dictionary keeping track of all timings"""
        super().__init__(*args, **kwargs)
        self._timings: Dict[str, MutableSequence[float]] = defaultdict(
            partial(deque, maxlen=_MAX_SIZE)
        )

    def add(self, name: str, value: float) -> None:
        """Add a timing value to the given timer."""
        self._timings[name].append(value)

    def clear(self) -> None:
        """Clear timers."""
        self._timings.clear()

    def apply(self, func: Callable[[Sequence[float]], float], name: str) -> float:
        """Apply a function to the results of one named timer."""
        if name in self._timings:
            return func(self._timings[name])
        raise KeyError(name)

    def count(self, name: str) -> float:
        """Number of timings."""
        return self.apply(len, name=name)

    def total(self, name: str) -> float:
        """Total time for timers."""
        return self.apply(sum, name=name)

    def min(self, name: str) -> float:
        """Minimal value of timings."""
        return self.apply(lambda values: min(values or [0]), name=name)

    def max(self, name: str) -> float:
        """Maximal value of timings."""
        return self.apply(lambda values: max(values or [0]), name=name)

    def mean(self, name: str) -> float:
        """Mean value of timings."""
        return self.apply(lambda values: statistics.mean(values or [0]), name=name)

    def median(self, name: str) -> float:
        """Median value of timings."""
        return self.apply(lambda values: statistics.median(values or [0]), name=name)

    def p95(self, name: str) -> float:
        return self.apply(
            lambda values: statistics.quantiles(
                values or [0], n=100, method="inclusive"
            )[95],
            name=name,
        )

    def stdev(self, name: str) -> float:
        """Standard deviation of timings."""
        if name in self._timings:
            value = self._timings[name]
            return statistics.stdev(value) if len(value) >= 2 else math.nan
        raise KeyError(name)

    def stats(self, name: str) -> Dict[str, float]:
        """All the stats for a given timer."""
        return {
            "count": self.count(name),
            "min": self.min(name),
            "max": self.max(name),
            "mean": self.mean(name),
            "median": self.median(name),
            "p95": self.p95(name),
            "stdev": self.stdev(name),
            "sum_total": self.total(name),
        }

    def __repr__(self) -> str:
        """Return a string representation of the TimerStats."""
        stats_repr = {k: self.stats(k) for k in self._timings.keys()}
        return f"{self.__class__.__name__}({pprint.pformat(stats_repr)})"

__init__(*args, **kwargs)

Add a private dictionary keeping track of all timings

Source code in landingai/timer.py
def __init__(self, *args: Any, **kwargs: Any) -> None:
    """Add a private dictionary keeping track of all timings"""
    super().__init__(*args, **kwargs)
    self._timings: Dict[str, MutableSequence[float]] = defaultdict(
        partial(deque, maxlen=_MAX_SIZE)
    )

__repr__()

Return a string representation of the TimerStats.

Source code in landingai/timer.py
def __repr__(self) -> str:
    """Return a string representation of the TimerStats."""
    stats_repr = {k: self.stats(k) for k in self._timings.keys()}
    return f"{self.__class__.__name__}({pprint.pformat(stats_repr)})"

add(name, value)

Add a timing value to the given timer.

Source code in landingai/timer.py
def add(self, name: str, value: float) -> None:
    """Add a timing value to the given timer."""
    self._timings[name].append(value)

apply(func, name)

Apply a function to the results of one named timer.

Source code in landingai/timer.py
def apply(self, func: Callable[[Sequence[float]], float], name: str) -> float:
    """Apply a function to the results of one named timer."""
    if name in self._timings:
        return func(self._timings[name])
    raise KeyError(name)

clear()

Clear timers.

Source code in landingai/timer.py
def clear(self) -> None:
    """Clear timers."""
    self._timings.clear()

count(name)

Number of timings.

Source code in landingai/timer.py
def count(self, name: str) -> float:
    """Number of timings."""
    return self.apply(len, name=name)

max(name)

Maximal value of timings.

Source code in landingai/timer.py
def max(self, name: str) -> float:
    """Maximal value of timings."""
    return self.apply(lambda values: max(values or [0]), name=name)

mean(name)

Mean value of timings.

Source code in landingai/timer.py
def mean(self, name: str) -> float:
    """Mean value of timings."""
    return self.apply(lambda values: statistics.mean(values or [0]), name=name)

median(name)

Median value of timings.

Source code in landingai/timer.py
def median(self, name: str) -> float:
    """Median value of timings."""
    return self.apply(lambda values: statistics.median(values or [0]), name=name)

min(name)

Minimal value of timings.

Source code in landingai/timer.py
def min(self, name: str) -> float:
    """Minimal value of timings."""
    return self.apply(lambda values: min(values or [0]), name=name)

stats(name)

All the stats for a given timer.

Source code in landingai/timer.py
def stats(self, name: str) -> Dict[str, float]:
    """All the stats for a given timer."""
    return {
        "count": self.count(name),
        "min": self.min(name),
        "max": self.max(name),
        "mean": self.mean(name),
        "median": self.median(name),
        "p95": self.p95(name),
        "stdev": self.stdev(name),
        "sum_total": self.total(name),
    }

stdev(name)

Standard deviation of timings.

Source code in landingai/timer.py
def stdev(self, name: str) -> float:
    """Standard deviation of timings."""
    if name in self._timings:
        value = self._timings[name]
        return statistics.stdev(value) if len(value) >= 2 else math.nan
    raise KeyError(name)

total(name)

Total time for timers.

Source code in landingai/timer.py
def total(self, name: str) -> float:
    """Total time for timers."""
    return self.apply(sum, name=name)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy