diff --git a/src/calng/utils.py b/src/calng/utils.py index 0bd0a25a6c5af8273812dd9f6bbe2ae7a988fd62..291c2d976ae4458495d351337e20f95fdcf7ebce 100644 --- a/src/calng/utils.py +++ b/src/calng/utils.py @@ -3,7 +3,7 @@ import functools import inspect import threading import time -import timeit +from timeit import default_timer import numpy as np @@ -164,11 +164,9 @@ class RepeatingTimer: self, interval, callback, - timer=timeit.default_timer, start_now=True, daemon=True, ): - self.timer = timer self.stopped = True self.interval = interval self.callback = callback @@ -178,19 +176,19 @@ class RepeatingTimer: def start(self): self.stopped = False - self.wakeup_time = self.timer() + self.interval + self.wakeup_time = default_timer() + self.interval def runner(): while not self.stopped: - now = self.timer() + now = default_timer() while now < self.wakeup_time: diff = self.wakeup_time - now time.sleep(diff) if self.stopped: return - now = self.timer() + now = default_timer() self.callback() - self.wakeup_time = self.timer() + self.interval + self.wakeup_time = default_timer() + self.interval self.thread = threading.Thread(target=runner, daemon=self.daemonize) self.thread.start() @@ -217,26 +215,72 @@ class ExponentialMovingAverage: class WindowRateTracker: - def __init__(self, buffer_size=20, time_window=20, timer=timeit.default_timer): + def __init__(self, buffer_size=20, time_window=10): self.time_window = time_window self.buffer_size = buffer_size self.deque = collections.deque(maxlen=self.buffer_size) - self.timer = timer def update(self): - now = self.timer() - self.deque.append(now) + self.deque.append(default_timer()) def get(self): - now = self.timer() + now = default_timer() cutoff = now - self.time_window try: while self.deque[0] < cutoff: self.deque.popleft() except IndexError: return 0 + if len(self.deque) < 2: + return 0 if len(self.deque) < self.buffer_size: + # TODO: estimator avoiding ramp-up of when starting anew return len(self.deque) / self.time_window else: # if going faster than buffer size per time window, look at timestamps - return len(self.deque) / (self.deque[-1] - self.deque[0]) + oldest, newest = self.deque[0], self.deque[-1] + buffer_span = newest - oldest + period = buffer_span / (self.buffer_size - 1) + if (now - newest) < period: + # no new estimate yet, expecting new event after period + return 1 / period + else: + return self.buffer_size / (now - oldest) + + +class Stopwatch: + """Context manager measuring time spent in context. + + Keyword arguments: + name: if not None, will appear in string representation + also, if not None, will automatically print self when done + """ + + def __init__(self, name=None): + self.stop_time = None + self.name = name + + def __enter__(self): + self.start_time = default_timer() + return self + + def __exit__(self, t, v, tb): # type, value and traceback irrelevant + self.stop_time = default_timer() + if self.name is not None: + print(repr(self)) + + @property + def elapsed(self): + if self.stop_time is not None: + return self.stop_time - self.start_time + else: + return default_timer() - self.start_time + + def __str__(self): + return self.__repr__() + + def __repr__(self): + if self.name is None: + return f"{self.elapsed():.3f} s" + else: + return f"{self.name}: {self.elapsed():.3f} s"