Blob Blame History Raw
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