##// END OF EJS Templates
progress: add a changedelay to prevent parallel topics from flapping (issue2698)...
progress: add a changedelay to prevent parallel topics from flapping (issue2698) When combined with the earlier change to make the progress object truly a singleton, this prevents the progress bar swapping on 'hg clone --pull' on a local filesystem. Thanks to timeless for lots of debugging help at the Copenhagen sprint to isolate the root cause of this and a first draft an idea that would fix it.

File last commit:

r14838:5d261fd0 default
r14838:5d261fd0 default
Show More
progress.py
302 lines | 11.0 KiB | text/x-python | PythonLexer
Augie Fackler
Progress bar extension
r10434 # progress.py show progress bars for some actions
#
# Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""show progress bars for some actions
timeless
progress: fix description
r10450 This extension uses the progress information logged by hg commands
to draw progress bars that are as informative as possible. Some progress
Augie Fackler
Progress bar extension
r10434 bars only offer indeterminate information, while others have a definite
end point.
The following settings are available::
[progress]
delay = 3 # number of seconds (float) before showing the progress bar
Augie Fackler
progress: add a changedelay to prevent parallel topics from flapping (issue2698)...
r14838 changedelay = 1 # changedelay: minimum delay before showing a new topic.
# If set to less than 3 * refresh, that value will
# be used instead.
Augie Fackler
Progress bar extension
r10434 refresh = 0.1 # time in seconds between refreshes of the progress bar
Augie Fackler
progress: include time estimate as part of the default progress format
r13148 format = topic bar number estimate # format of the progress bar
Augie Fackler
Progress bar extension
r10434 width = <none> # if set, the maximum width of the progress information
# (that is, min(width, term width) will be used)
clear-complete = True # clear the progress bar after it's done
Augie Fackler
progress: document progress.disable config option
r10656 disable = False # if true, don't show a progress bar
Augie Fackler
progress: use stderr instead of stdout; check stderr.isatty()...
r10788 assume-tty = False # if true, ALWAYS show a progress bar, unless
# disable is given
Augie Fackler
Progress bar extension
r10434
Augie Fackler
progress: only show time estimate when progress format contains 'estimate'
r13147 Valid entries for the format field are topic, bar, number, unit,
Martin Geisler
progress: add speed format...
r14280 estimate, speed, and item. item defaults to the last 20 characters of
the item, but this can be changed by adding either ``-<num>`` which
would take the last num characters, or ``+<num>`` for the first num
Augie Fackler
progress: only show time estimate when progress format contains 'estimate'
r13147 characters.
Augie Fackler
Progress bar extension
r10434 """
import sys
import time
Idan Kamara
util: add helper function isatty(fd) to check for tty-ness
r14515 from mercurial import util
timeless
progress: Add estimated time remaining for long tasks...
r13131 from mercurial.i18n import _
Augie Fackler
Progress bar extension
r10434
def spacejoin(*args):
Brodie Rao
progress: simplify spacejoin()
r10452 return ' '.join(s for s in args if s)
Augie Fackler
Progress bar extension
r10434
Augie Fackler
progress: check stderr.isatty() before each print...
r11458 def shouldprint(ui):
Augie Fackler
progress: remove superfluous parens
r14836 return util.isatty(sys.stderr) or ui.configbool('progress', 'assume-tty')
Augie Fackler
progress: check stderr.isatty() before each print...
r11458
Augie Fackler
progress: refactor for readability and show XXs instead of 0mXXs.
r13132 def fmtremaining(seconds):
if seconds < 60:
Martin Geisler
progress: explain format strings to translators
r13139 # i18n: format XX seconds as "XXs"
Augie Fackler
progress: refactor for readability and show XXs instead of 0mXXs.
r13132 return _("%02ds") % (seconds)
minutes = seconds // 60
if minutes < 60:
seconds -= minutes * 60
Martin Geisler
progress: explain format strings to translators
r13139 # i18n: format X minutes and YY seconds as "XmYYs"
Augie Fackler
progress: refactor for readability and show XXs instead of 0mXXs.
r13132 return _("%dm%02ds") % (minutes, seconds)
# we're going to ignore seconds in this case
minutes += 1
hours = minutes // 60
minutes -= hours * 60
timeless
progress: handle days, weeks and years...
r13236 if hours < 30:
# i18n: format X hours and YY minutes as "XhYYm"
return _("%dh%02dm") % (hours, minutes)
# we're going to ignore minutes in this case
hours += 1
days = hours // 24
hours -= days * 24
if days < 15:
# i18n: format X days and YY hours as "XdYYh"
return _("%dd%02dh") % (days, hours)
# we're going to ignore hours in this case
days += 1
weeks = days // 7
days -= weeks * 7
if weeks < 55:
# i18n: format X weeks and YY days as "XwYYd"
return _("%dw%02dd") % (weeks, days)
# we're going to ignore days and treat a year as 52 weeks
weeks += 1
years = weeks // 52
weeks -= years * 52
# i18n: format X years and YY weeks as "XyYYw"
return _("%dy%02dw") % (years, weeks)
Augie Fackler
progress: refactor for readability and show XXs instead of 0mXXs.
r13132
Augie Fackler
Progress bar extension
r10434 class progbar(object):
def __init__(self, ui):
self.ui = ui
self.resetstate()
def resetstate(self):
self.topics = []
Augie Fackler
progress: react more reasonably to nested progress topics...
r13130 self.topicstates = {}
timeless
progress: Add estimated time remaining for long tasks...
r13131 self.starttimes = {}
self.startvals = {}
Augie Fackler
Progress bar extension
r10434 self.printed = False
self.lastprint = time.time() + float(self.ui.config(
'progress', 'delay', default=3))
Augie Fackler
progress: add a changedelay to prevent parallel topics from flapping (issue2698)...
r14838 self.lasttopic = None
Augie Fackler
Progress bar extension
r10434 self.indetcount = 0
self.refresh = float(self.ui.config(
'progress', 'refresh', default=0.1))
Augie Fackler
progress: add a changedelay to prevent parallel topics from flapping (issue2698)...
r14838 self.changedelay = max(3 * self.refresh,
float(self.ui.config(
'progress', 'changedelay', default=1)))
Augie Fackler
Progress bar extension
r10434 self.order = self.ui.configlist(
'progress', 'format',
Augie Fackler
progress: include time estimate as part of the default progress format
r13148 default=['topic', 'bar', 'number', 'estimate'])
Augie Fackler
Progress bar extension
r10434
timeless
progress: Add estimated time remaining for long tasks...
r13131 def show(self, now, topic, pos, item, unit, total):
Augie Fackler
progress: check stderr.isatty() before each print...
r11458 if not shouldprint(self.ui):
return
Augie Fackler
Progress bar extension
r10434 termwidth = self.width()
self.printed = True
head = ''
needprogress = False
tail = ''
for indicator in self.order:
add = ''
if indicator == 'topic':
add = topic
elif indicator == 'number':
if total:
add = ('% ' + str(len(str(total))) +
's/%s') % (pos, total)
else:
add = str(pos)
elif indicator.startswith('item') and item:
slice = 'end'
if '-' in indicator:
wid = int(indicator.split('-')[1])
elif '+' in indicator:
slice = 'beginning'
wid = int(indicator.split('+')[1])
else:
wid = 20
if slice == 'end':
add = item[-wid:]
else:
add = item[:wid]
add += (wid - len(add)) * ' '
elif indicator == 'bar':
add = ''
needprogress = True
elif indicator == 'unit' and unit:
add = unit
Augie Fackler
progress: only show time estimate when progress format contains 'estimate'
r13147 elif indicator == 'estimate':
add = self.estimate(topic, pos, total, now)
Martin Geisler
progress: add speed format...
r14280 elif indicator == 'speed':
add = self.speed(topic, pos, unit, now)
Augie Fackler
Progress bar extension
r10434 if not needprogress:
head = spacejoin(head, add)
else:
Augie Fackler
progress: fix adding format elements after the progress bar...
r13146 tail = spacejoin(tail, add)
Augie Fackler
Progress bar extension
r10434 if needprogress:
used = 0
if head:
used += len(head) + 1
if tail:
used += len(tail) + 1
progwidth = termwidth - used - 3
Augie Fackler
progress: fall back to indeterminate progress if position is >= total
r10891 if total and pos <= total:
Augie Fackler
Progress bar extension
r10434 amt = pos * progwidth // total
Brodie Rao
progress: make determinate bar more like wget progress bar...
r10453 bar = '=' * (amt - 1)
if amt > 0:
bar += '>'
bar += ' ' * (progwidth - amt)
Augie Fackler
Progress bar extension
r10434 else:
progwidth -= 3
self.indetcount += 1
# mod the count by twice the width so we can make the
# cursor bounce between the right and left sides
amt = self.indetcount % (2 * progwidth)
amt -= progwidth
bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
' ' * int(abs(amt)))
prog = ''.join(('[', bar , ']'))
out = spacejoin(head, prog, tail)
else:
out = spacejoin(head, tail)
Augie Fackler
progress: use stderr instead of stdout; check stderr.isatty()...
r10788 sys.stderr.write('\r' + out[:termwidth])
Augie Fackler
progress: add a changedelay to prevent parallel topics from flapping (issue2698)...
r14838 self.lasttopic = topic
Augie Fackler
progress: use stderr instead of stdout; check stderr.isatty()...
r10788 sys.stderr.flush()
Augie Fackler
Progress bar extension
r10434
def clear(self):
Augie Fackler
progress: check stderr.isatty() before each print...
r11458 if not shouldprint(self.ui):
return
Augie Fackler
progress: use stderr instead of stdout; check stderr.isatty()...
r10788 sys.stderr.write('\r%s\r' % (' ' * self.width()))
Augie Fackler
Progress bar extension
r10434
Benoit Boissinot
progress: correctly handle empty progress topic
r10439 def complete(self):
Augie Fackler
progress: check stderr.isatty() before each print...
r11458 if not shouldprint(self.ui):
return
Benoit Boissinot
progress: correctly handle empty progress topic
r10439 if self.ui.configbool('progress', 'clear-complete', default=True):
self.clear()
else:
Augie Fackler
progress: use stderr instead of stdout; check stderr.isatty()...
r10788 sys.stderr.write('\n')
sys.stderr.flush()
Benoit Boissinot
progress: correctly handle empty progress topic
r10439
Augie Fackler
Progress bar extension
r10434 def width(self):
Augie Fackler
termwidth: move to ui.ui from util
r12689 tw = self.ui.termwidth()
Augie Fackler
Progress bar extension
r10434 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
Augie Fackler
progress: only show time estimate when progress format contains 'estimate'
r13147 def estimate(self, topic, pos, total, now):
Augie Fackler
progress: don't compute estimate without a total...
r13154 if total is None:
return ''
Augie Fackler
progress: only show time estimate when progress format contains 'estimate'
r13147 initialpos = self.startvals[topic]
target = total - initialpos
delta = pos - initialpos
if delta > 0:
elapsed = now - self.starttimes[topic]
if elapsed > float(
self.ui.config('progress', 'estimate', default=2)):
seconds = (elapsed * (target - delta)) // delta + 1
return fmtremaining(seconds)
return ''
Martin Geisler
progress: add speed format...
r14280 def speed(self, topic, pos, unit, now):
initialpos = self.startvals[topic]
delta = pos - initialpos
elapsed = now - self.starttimes[topic]
if elapsed > float(
self.ui.config('progress', 'estimate', default=2)):
return _('%d %s/sec') % (delta / elapsed, unit)
return ''
Brodie Rao
color/progress: subclass ui instead of using wrapfunction (issue2096)...
r11555 def progress(self, topic, pos, item='', unit='', total=None):
timeless
progress: Add estimated time remaining for long tasks...
r13131 now = time.time()
Benoit Boissinot
progress: correctly handle empty progress topic
r10439 if pos is None:
timeless
progress: Add estimated time remaining for long tasks...
r13131 self.starttimes.pop(topic, None)
self.startvals.pop(topic, None)
Augie Fackler
progress: react more reasonably to nested progress topics...
r13130 self.topicstates.pop(topic, None)
# reset the progress bar if this is the outermost topic
if self.topics and self.topics[0] == topic and self.printed:
Benoit Boissinot
progress: correctly handle empty progress topic
r10439 self.complete()
Augie Fackler
progress: only reset state if finishing progress for the current topic...
r10441 self.resetstate()
Augie Fackler
progress: react more reasonably to nested progress topics...
r13130 # truncate the list of topics assuming all topics within
# this one are also closed
if topic in self.topics:
self.topics = self.topics[:self.topics.index(topic)]
Benoit Boissinot
progress: correctly handle empty progress topic
r10439 else:
if topic not in self.topics:
timeless
progress: Add estimated time remaining for long tasks...
r13131 self.starttimes[topic] = now
self.startvals[topic] = pos
Benoit Boissinot
progress: correctly handle empty progress topic
r10439 self.topics.append(topic)
Augie Fackler
progress: react more reasonably to nested progress topics...
r13130 self.topicstates[topic] = pos, item, unit, total
if now - self.lastprint >= self.refresh and self.topics:
Augie Fackler
progress: add a changedelay to prevent parallel topics from flapping (issue2698)...
r14838 if (self.lasttopic is None # first time we printed
# not a topic change
or topic == self.lasttopic
# it's been long enough we should print anyway
or now - self.lastprint >= self.changedelay):
self.lastprint = now
self.show(now, topic, *self.topicstates[topic])
Augie Fackler
Progress bar extension
r10434
Augie Fackler
progress: make progress bar a singleton to avoid double-progress ui bugs...
r14837 _singleton = None
Augie Fackler
Progress bar extension
r10434 def uisetup(ui):
Augie Fackler
progress: make progress bar a singleton to avoid double-progress ui bugs...
r14837 global _singleton
Brodie Rao
color/progress: subclass ui instead of using wrapfunction (issue2096)...
r11555 class progressui(ui.__class__):
_progbar = None
def progress(self, *args, **opts):
self._progbar.progress(*args, **opts)
return super(progressui, self).progress(*args, **opts)
def write(self, *args, **opts):
if self._progbar.printed:
self._progbar.clear()
return super(progressui, self).write(*args, **opts)
def write_err(self, *args, **opts):
if self._progbar.printed:
self._progbar.clear()
return super(progressui, self).write_err(*args, **opts)
Steve Borho
progress: provide an explicit disable method for developers...
r10540 # Apps that derive a class from ui.ui() can use
# setconfig('progress', 'disable', 'True') to disable this extension
if ui.configbool('progress', 'disable'):
return
Augie Fackler
progress: check stderr.isatty() before each print...
r11458 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
Brodie Rao
color/progress: subclass ui instead of using wrapfunction (issue2096)...
r11555 ui.__class__ = progressui
Augie Fackler
Progress bar extension
r10434 # we instantiate one globally shared progress bar to avoid
# competing progress bars when multiple UI objects get created
Brodie Rao
color/progress: subclass ui instead of using wrapfunction (issue2096)...
r11555 if not progressui._progbar:
Augie Fackler
progress: make progress bar a singleton to avoid double-progress ui bugs...
r14837 if _singleton is None:
_singleton = progbar(ui)
progressui._progbar = _singleton
Augie Fackler
Progress bar extension
r10434
def reposetup(ui, repo):
uisetup(repo.ui)