From 4691a1efdcd8cbe4bc66d586307daf6505f21db4 Mon Sep 17 00:00:00 2001
From: Pavel Raiskup <praiskup@redhat.com>
Date: Mon, 18 Jan 2016 19:05:25 +0100
Subject: [PATCH 2/2] make the progress bar more stable
Add new sma_delta parameter. That parameter represents time delta
(in seconds) for which _one item_ in deque() is valid until we
create new one. Practically, one item represents number of items
processed during 'sma_delta' time (by default 0.3s).
Taking into account sma_delta and sma_window values, progress bar
now returns statistics for the last (sma_delta*sma_window)
seconds, which is by default 3s.
Document a bit.
This resolves verigak/progress/pull/24.
---
progress/__init__.py | 43 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 41 insertions(+), 2 deletions(-)
diff --git a/progress/__init__.py b/progress/__init__.py
index 3130fe1..37b4547 100644
--- a/progress/__init__.py
+++ b/progress/__init__.py
@@ -24,9 +24,35 @@ from time import time
__version__ = '1.2'
+"""
+The moving average is calculated by this formula:
+
+ _in_window / (time() - oldest_timestamp_in_window)
+
+Because the frequency of calling next() callback might be rather very high,
+doing the naive next()/avg() implementation leads to issues (PR 23):
+
+ def next(N):
+ t = time()
+ delta = N / (t - last_timestamp) # very small number
+ _dt.append(delta)
+ _avg = sum(_dt)/len(_dt)
+ last_timestamp = t
+
+.. Even if 'len(_dt)' was 1000, the time frame can be very small (e.g. file
+download for next(N=8k in bytes) on 1Gbit network. With the mentioned formula
+the window size is limited by *time*, not by the next() callback frequency.
+
+The moving average is calculated -at most- for the last 3 seconds by default:
+
+ sma_window x sma_delta = 10 x 0.3 = 3s
+
+Users can change this default window size, when needed.
+"""
class Infinite(object):
file = stderr
- sma_window = 10
+ sma_window = 10 # Size of the window -- (max) number of sma_delta items.
+ sma_delta = 0.3 # Time-length of one item in window, in seconds.
def __init__(self, *args, **kwargs):
self.index = 0
@@ -67,7 +93,20 @@ class Infinite(object):
def next(self, n=1):
self._ts = time()
- self._dt.append({'t': self._ts, 'n': n})
+
+ item = {'t': self._ts, 'n': 0}
+ if len(self._dt):
+ old_item = self._dt.pop()
+ if self._ts > old_item['t'] + self.sma_delta:
+ # Already reached timeout, we are not going to
+ # touch this item. Return it back.
+ self._dt.append(old_item)
+ else:
+ item = old_item
+
+ item['n'] = item['n'] + n
+
+ self._dt.append(item)
self._in_window = self._in_window + n
if len(self._dt) > self.sma_window:
--
2.7.4