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"