Mercurial > hg
view hgext/progress.py @ 21810:4b2ebd3187a1
dirstate.status: assign members one by one instead of unpacking the tuple
With this patch, hg status and hg diff regain their previous speed.
The following tests are run against a working copy with over 270,000 files.
Here, 'before' means without this or the previous patch applied.
Note that in this case `hg perfstatus` isn't representative since it doesn't
take dirstate parsing time into account.
$ time hg status # best of 5
before: 2.03s user 1.25s system 99% cpu 3.290 total
after: 2.01s user 1.25s system 99% cpu 3.261 total
$ time hg diff # best of 5
before: 1.32s user 0.78s system 99% cpu 2.105 total
after: 1.27s user 0.79s system 99% cpu 2.066 total
author | Siddharth Agarwal <sid0@fb.com> |
---|---|
date | Tue, 27 May 2014 21:02:16 -0700 |
parents | 4694ccd5d994 |
children | be4270d27a7e |
line wrap: on
line source
# progress.py show progress bars for some actions # # Copyright (C) 2010 Augie Fackler <durin42@gmail.com> # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. """show progress bars for some actions This extension uses the progress information logged by hg commands to draw progress bars that are as informative as possible. Some progress 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 changedelay = 1 # changedelay: minimum delay before showing a new topic. # If set to less than 3 * refresh, that value will # be used instead. refresh = 0.1 # time in seconds between refreshes of the progress bar format = topic bar number estimate # format of the progress bar 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 disable = False # if true, don't show a progress bar assume-tty = False # if true, ALWAYS show a progress bar, unless # disable is given Valid entries for the format field are topic, bar, number, unit, 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 characters. """ import sys import time from mercurial.i18n import _ testedwith = 'internal' def spacejoin(*args): return ' '.join(s for s in args if s) def shouldprint(ui): return not ui.plain() and (ui._isatty(sys.stderr) or ui.configbool('progress', 'assume-tty')) def fmtremaining(seconds): if seconds < 60: # i18n: format XX seconds as "XXs" return _("%02ds") % (seconds) minutes = seconds // 60 if minutes < 60: seconds -= minutes * 60 # i18n: format X minutes and YY seconds as "XmYYs" return _("%dm%02ds") % (minutes, seconds) # we're going to ignore seconds in this case minutes += 1 hours = minutes // 60 minutes -= hours * 60 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) class progbar(object): def __init__(self, ui): self.ui = ui self.resetstate() def resetstate(self): self.topics = [] self.topicstates = {} self.starttimes = {} self.startvals = {} self.printed = False self.lastprint = time.time() + float(self.ui.config( 'progress', 'delay', default=3)) self.lasttopic = None self.indetcount = 0 self.refresh = float(self.ui.config( 'progress', 'refresh', default=0.1)) self.changedelay = max(3 * self.refresh, float(self.ui.config( 'progress', 'changedelay', default=1))) self.order = self.ui.configlist( 'progress', 'format', default=['topic', 'bar', 'number', 'estimate']) def show(self, now, topic, pos, item, unit, total): if not shouldprint(self.ui): return 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 elif indicator == 'estimate': add = self.estimate(topic, pos, total, now) elif indicator == 'speed': add = self.speed(topic, pos, unit, now) if not needprogress: head = spacejoin(head, add) else: tail = spacejoin(tail, add) if needprogress: used = 0 if head: used += len(head) + 1 if tail: used += len(tail) + 1 progwidth = termwidth - used - 3 if total and pos <= total: amt = pos * progwidth // total bar = '=' * (amt - 1) if amt > 0: bar += '>' bar += ' ' * (progwidth - amt) 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) sys.stderr.write('\r' + out[:termwidth]) self.lasttopic = topic sys.stderr.flush() def clear(self): if not shouldprint(self.ui): return sys.stderr.write('\r%s\r' % (' ' * self.width())) def complete(self): if not shouldprint(self.ui): return if self.ui.configbool('progress', 'clear-complete', default=True): self.clear() else: sys.stderr.write('\n') sys.stderr.flush() def width(self): tw = self.ui.termwidth() return min(int(self.ui.config('progress', 'width', default=tw)), tw) def estimate(self, topic, pos, total, now): if total is None: return '' 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 '' 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 '' def progress(self, topic, pos, item='', unit='', total=None): now = time.time() if pos is None: self.starttimes.pop(topic, None) self.startvals.pop(topic, None) 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: self.complete() self.resetstate() # 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)] # reset the last topic to the one we just unwound to, # so that higher-level topics will be stickier than # lower-level topics if self.topics: self.lasttopic = self.topics[-1] else: self.lasttopic = None else: if topic not in self.topics: self.starttimes[topic] = now self.startvals[topic] = pos self.topics.append(topic) self.topicstates[topic] = pos, item, unit, total if now - self.lastprint >= self.refresh and self.topics: 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]) _singleton = None def uisetup(ui): global _singleton class progressui(ui.__class__): _progbar = None def _quiet(self): return self.debugflag or self.quiet def progress(self, *args, **opts): if not self._quiet(): self._progbar.progress(*args, **opts) return super(progressui, self).progress(*args, **opts) def write(self, *args, **opts): if not self._quiet() and self._progbar.printed: self._progbar.clear() return super(progressui, self).write(*args, **opts) def write_err(self, *args, **opts): if not self._quiet() and self._progbar.printed: self._progbar.clear() return super(progressui, self).write_err(*args, **opts) # Apps that derive a class from ui.ui() can use # setconfig('progress', 'disable', 'True') to disable this extension if ui.configbool('progress', 'disable'): return if shouldprint(ui) and not ui.debugflag and not ui.quiet: ui.__class__ = progressui # we instantiate one globally shared progress bar to avoid # competing progress bars when multiple UI objects get created if not progressui._progbar: if _singleton is None: _singleton = progbar(ui) progressui._progbar = _singleton def reposetup(ui, repo): uisetup(repo.ui)