# HG changeset patch # User Jun Wu # Date 2017-09-27 22:14:59 # Node ID a667f0ca1d5fa8b80006fc4030ded936b47d3e83 # Parent f428c347d32b2f110856c6fb7fec00a5f4cc0c0d progress: make ETA only consider progress made in the last minute This patch limits the estimate time interval to roughly the last minute (configurable by `estimateinterval`) to be more practical. See the test change for why this is better. .. feature:: Estimated time is more accurate with non-linear progress Differential Revision: https://phab.mercurial-scm.org/D820 diff --git a/mercurial/configitems.py b/mercurial/configitems.py --- a/mercurial/configitems.py +++ b/mercurial/configitems.py @@ -359,6 +359,9 @@ coreconfigitem('progress', 'delay', coreconfigitem('progress', 'disable', default=False, ) +coreconfigitem('progress', 'estimateinterval', + default=60.0, +) coreconfigitem('progress', 'refresh', default=0.1, ) diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt --- a/mercurial/help/config.txt +++ b/mercurial/help/config.txt @@ -1613,6 +1613,10 @@ have a definite end point. Minimum delay before showing a new topic. When set to less than 3 * refresh, that value will be used instead. (default: 1) +``estimateinterval`` + Maximum sampling interval in seconds for speed and estimated time + calculation. (default: 60) + ``refresh`` Time in seconds between refreshes of the progress bar. (default: 0.1) diff --git a/mercurial/progress.py b/mercurial/progress.py --- a/mercurial/progress.py +++ b/mercurial/progress.py @@ -104,6 +104,8 @@ class progbar(object): self.order = self.ui.configlist( 'progress', 'format', default=['topic', 'bar', 'number', 'estimate']) + self.estimateinterval = self.ui.configwith( + float, 'progress', 'estimateinterval') def show(self, now, topic, pos, item, unit, total): if not shouldprint(self.ui): @@ -238,6 +240,32 @@ class progbar(object): else: return False + def _calibrateestimate(self, topic, now, pos): + '''Adjust starttimes and startvals for topic so ETA works better + + If progress is non-linear (ex. get much slower in the last minute), + it's more friendly to only use a recent time span for ETA and speed + calculation. + + [======================================> ] + ^^^^^^^ + estimateinterval, only use this for estimation + ''' + interval = self.estimateinterval + if interval <= 0: + return + elapsed = now - self.starttimes[topic] + if elapsed > interval: + delta = pos - self.startvals[topic] + newdelta = delta * interval / elapsed + # If a stall happens temporarily, ETA could change dramatically + # frequently. This is to avoid such dramatical change and make ETA + # smoother. + if newdelta < 0.1: + return + self.startvals[topic] = pos - newdelta + self.starttimes[topic] = now - interval + def progress(self, topic, pos, item='', unit='', total=None): now = time.time() self._refreshlock.acquire() @@ -268,6 +296,7 @@ class progbar(object): self.topics.append(topic) self.topicstates[topic] = pos, item, unit, total self.curtopic = topic + self._calibrateestimate(topic, now, pos) if now - self.lastprint >= self.refresh and self.topics: if self._oktoprint(now): self.lastprint = now diff --git a/tests/test-progress.t b/tests/test-progress.t --- a/tests/test-progress.t +++ b/tests/test-progress.t @@ -260,17 +260,17 @@ Non-linear progress: loop [===========> ] 6/20 4m41s\r (no-eol) (esc) loop [=============> ] 7/20 4m21s\r (no-eol) (esc) loop [===============> ] 8/20 4m01s\r (no-eol) (esc) - loop [================> ] 9/20 13m27s\r (no-eol) (esc) - loop [==================> ] 10/20 19m21s\r (no-eol) (esc) - loop [====================> ] 11/20 22m39s\r (no-eol) (esc) - loop [======================> ] 12/20 24m01s\r (no-eol) (esc) - loop [========================> ] 13/20 23m53s\r (no-eol) (esc) - loop [==========================> ] 14/20 19m09s\r (no-eol) (esc) - loop [============================> ] 15/20 15m01s\r (no-eol) (esc) - loop [==============================> ] 16/20 11m21s\r (no-eol) (esc) - loop [=================================> ] 17/20 8m04s\r (no-eol) (esc) - loop [===================================> ] 18/20 5m07s\r (no-eol) (esc) - loop [=====================================> ] 19/20 2m27s\r (no-eol) (esc) + loop [================> ] 9/20 25m40s\r (no-eol) (esc) + loop [===================> ] 10/20 1h06m\r (no-eol) (esc) + loop [=====================> ] 11/20 1h13m\r (no-eol) (esc) + loop [=======================> ] 12/20 1h07m\r (no-eol) (esc) + loop [========================> ] 13/20 58m19s\r (no-eol) (esc) + loop [===========================> ] 14/20 7m09s\r (no-eol) (esc) + loop [=============================> ] 15/20 3m38s\r (no-eol) (esc) + loop [===============================> ] 16/20 2m15s\r (no-eol) (esc) + loop [=================================> ] 17/20 1m27s\r (no-eol) (esc) + loop [====================================> ] 18/20 52s\r (no-eol) (esc) + loop [======================================> ] 19/20 25s\r (no-eol) (esc) \r (no-eol) (esc) Time estimates should not fail when there's no end point: