Some improvements to ratemeter.

threading.lock to ensure consistency
time.monotonic instead of time.time
Docstring wrapping
Remove math.ceil and round, not sure why I did that
This commit is contained in:
voussoir 2022-01-26 21:23:39 -08:00
parent f550c03c15
commit ecd663b7e9
No known key found for this signature in database
GPG key ID: 5F7554F8C26DACCB

View file

@ -1,5 +1,5 @@
import collections import collections
import math import threading
import time import time
class RateMeter: class RateMeter:
@ -9,17 +9,18 @@ class RateMeter:
units per second over `span` seconds. units per second over `span` seconds.
Set `span` to None to calculate unit/s over the lifetime of the object Set `span` to None to calculate unit/s over the lifetime of the object
after the first digest, rather than over a span. after the first digest, rather than over a span. This saves the effort
This saves the effort of tracking timestamps. Don't just use a large number! of tracking timestamps; so don't just use a large number!
''' '''
self.sum = 0 self.sum = 0
self.span = span self.span = span
self.lock = threading.Lock()
self.tracking = collections.deque() self.tracking = collections.deque()
self.first_digest = None self.first_digest = None
def digest(self, value): def _digest(self, value):
now = time.time() now = time.monotonic()
self.sum += value self.sum += value
if self.span is None: if self.span is None:
@ -27,8 +28,8 @@ class RateMeter:
self.first_digest = now self.first_digest = now
return return
earlier = now - self.span expire_cutoff = now - self.span
while len(self.tracking) > 0 and self.tracking[0][0] < earlier: while len(self.tracking) > 0 and self.tracking[0][0] < expire_cutoff:
(timestamp, pop_value) = self.tracking.popleft() (timestamp, pop_value) = self.tracking.popleft()
self.sum -= pop_value self.sum -= pop_value
@ -37,19 +38,16 @@ class RateMeter:
else: else:
self.tracking[-1][1] += value self.tracking[-1][1] += value
def report(self): def digest(self, value):
''' with self.lock:
Return a tuple containing the running sum, the time span return self._digest(value)
over which the rate is being calculated, and the rate in
units per second.
(sum, time_interval, rate) def _report(self):
'''
# Flush the old values, ensure self.first_digest exists. # Flush the old values, ensure self.first_digest exists.
self.digest(0) self._digest(0)
if self.span is None: if self.span is None:
now = math.ceil(time.time()) now = time.monotonic()
time_interval = now - self.first_digest time_interval = now - self.first_digest
else: else:
# No risk of IndexError because the digest(0) ensures we have # No risk of IndexError because the digest(0) ensures we have
@ -58,7 +56,16 @@ class RateMeter:
if time_interval == 0: if time_interval == 0:
return (self.sum, 0, self.sum) return (self.sum, 0, self.sum)
rate = self.sum / time_interval rate = self.sum / time_interval
time_interval = round(time_interval, 3)
rate = round(rate, 3)
return (self.sum, time_interval, rate) return (self.sum, time_interval, rate)
def report(self):
'''
Return a tuple containing the running sum, the time span over which the
rate has been calculated, and the rate in units per second.
(sum, time_interval, rate)
'''
with self.lock:
return self._report()