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:
parent
f550c03c15
commit
ecd663b7e9
1 changed files with 25 additions and 18 deletions
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue