blob: 03ed98d4b3912b239b75ecaf20a5c4486cca148a [file] [log] [blame] [edit]
# Copyright 2021 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Timing utility."""
import contextlib
import datetime
import functools
import logging
import time
from typing import Any, Callable, Optional
from chromite.utils import pformat
class Timer(object):
"""Simple timer class to make timing blocks of code easy.
It does not have the features of timeit, but can be added anywhere, e.g. to
time a specific section of a script. The Timer class implements __add__ and
__truediv__ to allow averaging multiple timers, but the collection must be
done manually.
Examples:
with Timer():
code_to_be_timed()
with Timer() as t: -> str(t) == "{formatted_delta}"
with Timer('name') as t: -> str(t) == "name: {formatted_delta}"
To get an average:
timers = []
for _ in range(10):
with Timer() as t:
code_to_be_timed()
timers.append(t)
avg = sum(timers, start=Timer('Average')) / len(times)
avg.output() -> prints "Average: {formatted_delta}"
"""
def __init__(self, name: Optional[str] = None):
"""Init.
Args:
name: A string to identify the timer, especially when using multiple.
"""
self.name = name
self.start = 0.0
self.end = 0.0
self.delta = 0.0
def __add__(self, other):
if not isinstance(other, Timer):
raise NotImplementedError(f'Cannot add {type(other)} to Timer')
result = Timer(self.name)
result.delta = self.delta + other.delta
return result
def __truediv__(self, other):
if not isinstance(other, int):
raise NotImplementedError(f'Only int is supported, given {type(other)}')
result = Timer(self.name)
result.delta = self.delta / other
return result
def __enter__(self):
self.start = time.perf_counter()
return self
def __exit__(self, *args):
self.end = time.perf_counter()
self.delta = self.end - self.start
def __str__(self):
name = f'{self.name}: ' if self.name else ''
return f'{name}{pformat.timedelta(datetime.timedelta(seconds=self.delta))}'
def timed(name: Optional[str] = None,
output: Callable[[str], Any] = logging.info):
"""Timed decorator to add a timer to a function."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
with timer(name or func.__name__, output):
return func(*args, **kwargs)
return wrapper
return decorator
@contextlib.contextmanager
def timer(name: Optional[str] = None,
output: Callable[[str], Any] = logging.info):
"""Timer context manager to automatically output results."""
try:
with Timer(name) as t:
yield t
finally:
output(str(t))