22856fa
Fix fast upload avg calculation
22856fa
22856fa
Upstream verigak/progress is broken for very fast uploads; e.g.
22856fa
with 'copr build' command, the next() call is called so often that
22856fa
the avg() calculation probably suffers from some small
22856fa
floating-point numbers problems:
22856fa
22856fa
  With 15MB/s => next() called for each 8096B => ~2000 calls/s
22856fa
22856fa
Since the upstream default window size is only of size 10 items
22856fa
(by default), it calculates the average speed only for the last
22856fa
~0.005s.  We could enlarge the size of window (sma_window param),
22856fa
but the algorithm is so naive that it would decrease the
22856fa
performance.
22856fa
22856fa
This has been discussed very extensively with upstream (PR 24 and
22856fa
friends) but I neither was not able to explain the problem, nor I
22856fa
was able to convince upstream to accept my patches.
22856fa
22856fa
This downstream patch - while it keeps the backward API
22856fa
compatibility - changes the algorithm so the average speed is
22856fa
calculation is fast enough, and much more stable (by default it
22856fa
calculates speed for window of 2 seconds).
22856fa
e39309c
We also don't seem to have the monotonic() mess, since we don't seem
e39309c
to suffer from the same issues.
e39309c
22856fa
Fork with this patch backported is maintained in
22856fa
https://github.com/python-progress/python-progress
22856fa
e39309c
diff --git a/MANIFEST.in b/MANIFEST.in
e39309c
index ef7c4cb..0c73842 100644
e39309c
--- a/MANIFEST.in
e39309c
+++ b/MANIFEST.in
e39309c
@@ -1,2 +1 @@
e39309c
 include README.rst LICENSE
e39309c
-include test_*.py
22856fa
diff --git a/progress/__init__.py b/progress/__init__.py
e39309c
index e434c25..1cbdce6 100644
22856fa
--- a/progress/__init__.py
22856fa
+++ b/progress/__init__.py
e39309c
@@ -18,10 +18,7 @@ from collections import deque
e39309c
 from datetime import timedelta
e39309c
 from math import ceil
e39309c
 from sys import stderr
e39309c
-try:
e39309c
-    from time import monotonic
e39309c
-except ImportError:
e39309c
-    from time import time as monotonic
e39309c
+from time import time
e39309c
 
e39309c
 
e39309c
 __version__ = '1.5'
e39309c
@@ -30,19 +27,55 @@ HIDE_CURSOR = '\x1b[?25l'
e39309c
 SHOW_CURSOR = '\x1b[?25h'
22856fa
 
22856fa
 
22856fa
+class _Window(object):
22856fa
+    max_seconds = 2
22856fa
+    max_items = None
22856fa
+
22856fa
+    def __init__(self, max_seconds=2, max_items=None):
22856fa
+        self.max_seconds = max_seconds
22856fa
+        self.max_items = max_items
22856fa
+
22856fa
+        stamp = time()
22856fa
+        self.last = stamp - 0.001
22856fa
+        self.counter = 0
22856fa
+        self.deque = deque()
22856fa
+        self.next(0, stamp)
22856fa
+
22856fa
+    def pop(self):
22856fa
+        item = self.deque.popleft()
22856fa
+        self.counter -= item[1]
22856fa
+
22856fa
+    def clean(self):
22856fa
+        if self.max_items:
22856fa
+            while len(self.deque) > self.max_items:
22856fa
+                self.pop()
22856fa
+        while len(self.deque) > 2 and self.last - self.deque[0][0] > float(self.max_seconds):
22856fa
+            self.pop()
22856fa
+
22856fa
+    def next(self, n, t):
22856fa
+        self.clean()
22856fa
+        self.deque.append((self.last, n))
22856fa
+        self.last = t
22856fa
+        self.counter += n
22856fa
+
22856fa
+    @property
22856fa
+    def avg(self):
22856fa
+        return self.counter / (self.last - self.deque[0][0])
22856fa
+
22856fa
+
22856fa
 class Infinite(object):
22856fa
     file = stderr
22856fa
-    sma_window = 10         # Simple Moving Average window
22856fa
+    # Maximum number of next() calls to be held in Simple Moving Average
22856fa
+    # window structure (in memory), default is unlimited.
22856fa
+    sma_window_seconds = 2
22856fa
+    sma_window = None
e39309c
     check_tty = True
e39309c
     hide_cursor = True
22856fa
 
e39309c
     def __init__(self, message='', **kwargs):
22856fa
         self.index = 0
e39309c
-        self.start_ts = monotonic()
22856fa
-        self.avg = 0
e39309c
-        self._avg_update_ts = self.start_ts
22856fa
-        self._ts = self.start_ts
22856fa
-        self._xput = deque(maxlen=self.sma_window)
e39309c
+        self.start_ts = time()
22856fa
+        self.window = _Window(self.sma_window_seconds, self.sma_window)
22856fa
         for key, val in kwargs.items():
22856fa
             setattr(self, key, val)
22856fa
 
e39309c
@@ -62,23 +95,19 @@ class Infinite(object):
22856fa
 
e39309c
     @property
e39309c
     def elapsed(self):
e39309c
-        return int(monotonic() - self.start_ts)
e39309c
+        return int(time() - self.start_ts)
e39309c
+
22856fa
+    @property
22856fa
+    def avg(self):
22856fa
+        speed = self.window.avg
22856fa
+        if speed:
22856fa
+            return 1/speed
22856fa
+        return 3600 # better constant?
e39309c
 
22856fa
     @property
22856fa
     def elapsed_td(self):
22856fa
         return timedelta(seconds=self.elapsed)
22856fa
 
22856fa
-    def update_avg(self, n, dt):
22856fa
-        if n > 0:
e39309c
-            xput_len = len(self._xput)
22856fa
-            self._xput.append(dt / n)
e39309c
-            now = monotonic()
e39309c
-            # update when we're still filling _xput, then after every second
e39309c
-            if (xput_len < self.sma_window or
e39309c
-                    now - self._avg_update_ts > 1):
e39309c
-                self.avg = sum(self._xput) / len(self._xput)
e39309c
-                self._avg_update_ts = now
22856fa
-
22856fa
     def update(self):
22856fa
         pass
22856fa
 
e39309c
@@ -112,10 +141,7 @@ class Infinite(object):
e39309c
         return self.file.isatty() if self.check_tty else True
22856fa
 
22856fa
     def next(self, n=1):
e39309c
-        now = monotonic()
22856fa
-        dt = now - self._ts
22856fa
-        self.update_avg(n, dt)
22856fa
-        self._ts = now
22856fa
+        self.window.next(n, time())
22856fa
         self.index = self.index + n
22856fa
         self.update()
22856fa