Guest User

Untitled

a guest
Jan 14th, 2010
307
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 20.43 KB | None | 0 0
  1. # Copyright (C) 2005, 2006, 2008, 2009 Canonical Ltd
  2. #
  3. # This program is free software; you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation; either version 2 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software
  15. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  16.  
  17.  
  18. """Progress indicators.
  19.  
  20. The usual way to use this is via bzrlib.ui.ui_factory.nested_progress_bar which
  21. will manage a conceptual stack of nested activities.
  22. """
  23.  
  24.  
  25. import sys
  26. import time
  27. import os
  28.  
  29.  
  30. from bzrlib import (
  31.     errors,
  32.     )
  33. from bzrlib.trace import mutter
  34. from bzrlib.symbol_versioning import (
  35.     deprecated_function,
  36.     deprecated_in,
  37.     deprecated_method,
  38.     )
  39.  
  40.  
  41. def _supports_progress(f):
  42.     """Detect if we can use pretty progress bars on file F.
  43.  
  44.    If this returns true we expect that a human may be looking at that
  45.    output, and that we can repaint a line to update it.
  46.  
  47.    This doesn't check the policy for whether we *should* use them.
  48.    """
  49.     isatty = getattr(f, 'isatty', None)
  50.     if isatty is None:
  51.         return False
  52.     if not isatty():
  53.         return False
  54.     # The following case also handles Win32 - on that platform $TERM is
  55.     # typically never set, so the case None is treated as a smart terminal,
  56.     # not dumb.  <https://bugs.launchpad.net/bugs/334808>  win32 files do have
  57.     # isatty methods that return true.
  58.     if os.environ.get('TERM') == 'dumb':
  59.         # e.g. emacs compile window
  60.         return False
  61.     return True
  62.  
  63.  
  64. class ProgressTask(object):
  65.     """Model component of a progress indicator.
  66.  
  67.    Most code that needs to indicate progress should update one of these,
  68.    and it will in turn update the display, if one is present.
  69.  
  70.    Code updating the task may also set fields as hints about how to display
  71.    it: show_pct, show_spinner, show_eta, show_count, show_bar.  UIs
  72.    will not necessarily respect all these fields.
  73.    
  74.    :ivar update_latency: The interval (in seconds) at which the PB should be
  75.        updated.  Setting this to zero suggests every update should be shown
  76.        synchronously.
  77.  
  78.    :ivar show_transport_activity: If true (default), transport activity
  79.        will be shown when this task is drawn.  Disable it if you're sure
  80.        that only irrelevant or uninteresting transport activity can occur
  81.        during this task.
  82.    """
  83.  
  84.     def __init__(self, parent_task=None, ui_factory=None, progress_view=None):
  85.         """Construct a new progress task.
  86.  
  87.        :param parent_task: Enclosing ProgressTask or None.
  88.  
  89.        :param progress_view: ProgressView to display this ProgressTask.
  90.  
  91.        :param ui_factory: The UI factory that will display updates;
  92.            deprecated in favor of passing progress_view directly.
  93.  
  94.        Normally you should not call this directly but rather through
  95.        `ui_factory.nested_progress_bar`.
  96.        """
  97.         self._parent_task = parent_task
  98.         self._last_update = 0
  99.         self.total_cnt = None
  100.         self.current_cnt = None
  101.         self.msg = ''
  102.         # TODO: deprecate passing ui_factory
  103.         self.ui_factory = ui_factory
  104.         self.progress_view = progress_view
  105.         self.show_pct = False
  106.         self.show_spinner = True
  107.         self.show_eta = False,
  108.         self.show_count = True
  109.         self.show_bar = True
  110.         self.update_latency = 0.1
  111.         self.show_transport_activity = True
  112.  
  113.     def __repr__(self):
  114.         return '%s(%r/%r, msg=%r)' % (
  115.             self.__class__.__name__,
  116.             self.current_cnt,
  117.             self.total_cnt,
  118.             self.msg)
  119.  
  120.     def update(self, msg, current_cnt=None, total_cnt=None):
  121.         self.msg = msg
  122.         self.current_cnt = current_cnt
  123.         if total_cnt:
  124.             self.total_cnt = total_cnt
  125.         if self.progress_view:
  126.             self.progress_view.show_progress(self)
  127.         else:
  128.             self.ui_factory._progress_updated(self)
  129.  
  130.     def tick(self):
  131.         self.update(self.msg)
  132.  
  133.     def finished(self):
  134.         if self.progress_view:
  135.             self.progress_view.task_finished(self)
  136.         else:
  137.             self.ui_factory._progress_finished(self)
  138.  
  139.     def make_sub_task(self):
  140.         return ProgressTask(self, ui_factory=self.ui_factory,
  141.             progress_view=self.progress_view)
  142.  
  143.     def _overall_completion_fraction(self, child_fraction=0.0):
  144.         """Return fractional completion of this task and its parents
  145.  
  146.        Returns None if no completion can be computed."""
  147.         if self.current_cnt is not None and self.total_cnt:
  148.             own_fraction = (float(self.current_cnt) + child_fraction) / self.total_cnt
  149.         else:
  150.             # if this task has no estimation, it just passes on directly
  151.             # whatever the child has measured...
  152.             own_fraction = child_fraction
  153.         if self._parent_task is None:
  154.             return own_fraction
  155.         else:
  156.             if own_fraction is None:
  157.                 own_fraction = 0.0
  158.             return self._parent_task._overall_completion_fraction(own_fraction)
  159.  
  160.     @deprecated_method(deprecated_in((2, 1, 0)))
  161.     def note(self, fmt_string, *args):
  162.         """Record a note without disrupting the progress bar.
  163.        
  164.        Deprecated: use ui_factory.note() instead or bzrlib.trace.  Note that
  165.        ui_factory.note takes just one string as the argument, not a format
  166.        string and arguments.
  167.        """
  168.         if args:
  169.             self.ui_factory.note(fmt_string % args)
  170.         else:
  171.             self.ui_factory.note(fmt_string)
  172.  
  173.     def clear(self):
  174.         # TODO: deprecate this method; the model object shouldn't be concerned
  175.         # with whether it's shown or not.  Most callers use this because they
  176.         # want to write some different non-progress output to the screen, but
  177.         # they should probably instead use a stream that's synchronized with
  178.         # the progress output.  It may be there is a model-level use for
  179.         # saying "this task's not active at the moment" but I don't see it. --
  180.         # mbp 20090623
  181.         if self.progress_view:
  182.             self.progress_view.clear()
  183.         else:
  184.             self.ui_factory.clear_term()
  185.  
  186.  
  187. @deprecated_function(deprecated_in((1, 16, 0)))
  188. def ProgressBar(to_file=None, **kwargs):
  189.     """Construct a progress bar.
  190.  
  191.    Deprecated; ask the ui_factory for a progress task instead.
  192.    """
  193.     if to_file is None:
  194.         to_file = sys.stderr
  195.     requested_bar_type = os.environ.get('BZR_PROGRESS_BAR')
  196.     # An value of '' or not set reverts to standard processing
  197.     if requested_bar_type in (None, ''):
  198.         if _supports_progress(to_file):
  199.             return TTYProgressBar(to_file=to_file, **kwargs)
  200.         else:
  201.             return DummyProgress(to_file=to_file, **kwargs)
  202.     else:
  203.         # Minor sanitation to prevent spurious errors
  204.         requested_bar_type = requested_bar_type.lower().strip()
  205.         # TODO: jam 20060710 Arguably we shouldn't raise an exception
  206.         #       but should instead just disable progress bars if we
  207.         #       don't recognize the type
  208.         if requested_bar_type not in _progress_bar_types:
  209.             raise errors.InvalidProgressBarType(requested_bar_type,
  210.                                                 _progress_bar_types.keys())
  211.         return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
  212.  
  213.  
  214. # NOTE: This is also deprecated; you should provide a ProgressView instead.
  215. class _BaseProgressBar(object):
  216.  
  217.     def __init__(self,
  218.                  to_file=None,
  219.                  show_pct=False,
  220.                  show_spinner=False,
  221.                  show_eta=False,
  222.                  show_bar=True,
  223.                  show_count=True,
  224.                  to_messages_file=None,
  225.                  _stack=None):
  226.         object.__init__(self)
  227.         if to_file is None:
  228.             to_file = sys.stderr
  229.         if to_messages_file is None:
  230.             to_messages_file = sys.stdout
  231.         self.to_file = to_file
  232.         self.to_messages_file = to_messages_file
  233.         self.last_msg = None
  234.         self.last_cnt = None
  235.         self.last_total = None
  236.         self.show_pct = show_pct
  237.         self.show_spinner = show_spinner
  238.         self.show_eta = show_eta
  239.         self.show_bar = show_bar
  240.         self.show_count = show_count
  241.         self._stack = _stack
  242.         # seed throttler
  243.         self.MIN_PAUSE = 0.1 # seconds
  244.         now = time.time()
  245.         # starting now
  246.         self.start_time = now
  247.         # next update should not throttle
  248.         self.last_update = now - self.MIN_PAUSE - 1
  249.  
  250.     def finished(self):
  251.         """Return this bar to its progress stack."""
  252.         self.clear()
  253.         self._stack.return_pb(self)
  254.  
  255.     def note(self, fmt_string, *args, **kwargs):
  256.         """Record a note without disrupting the progress bar."""
  257.         self.clear()
  258.         self.to_messages_file.write(fmt_string % args)
  259.         self.to_messages_file.write('\n')
  260.  
  261.     @deprecated_function(deprecated_in((1, 16, 0)))
  262.     def child_progress(self, **kwargs):
  263.         return ChildProgress(**kwargs)
  264.  
  265.  
  266. class DummyProgress(_BaseProgressBar):
  267.     """Progress-bar standin that does nothing.
  268.  
  269.    This can be used as the default argument for methods that
  270.    take an optional progress indicator."""
  271.  
  272.     def tick(self):
  273.         pass
  274.  
  275.     def update(self, msg=None, current=None, total=None):
  276.         pass
  277.  
  278.     def child_update(self, message, current, total):
  279.         pass
  280.  
  281.     def clear(self):
  282.         pass
  283.  
  284.     def note(self, fmt_string, *args, **kwargs):
  285.         """See _BaseProgressBar.note()."""
  286.  
  287.     def child_progress(self, **kwargs):
  288.         return DummyProgress(**kwargs)
  289.  
  290.  
  291. class DotsProgressBar(_BaseProgressBar):
  292.  
  293.     @deprecated_function(deprecated_in((1, 16, 0)))
  294.     def __init__(self, **kwargs):
  295.         _BaseProgressBar.__init__(self, **kwargs)
  296.         self.last_msg = None
  297.         self.need_nl = False
  298.  
  299.     def tick(self):
  300.         self.update()
  301.  
  302.     def update(self, msg=None, current_cnt=None, total_cnt=None):
  303.         if msg and msg != self.last_msg:
  304.             if self.need_nl:
  305.                 self.to_file.write('\n')
  306.             self.to_file.write(msg + ': ')
  307.             self.last_msg = msg
  308.         self.need_nl = True
  309.         self.to_file.write('.')
  310.  
  311.     def clear(self):
  312.         if self.need_nl:
  313.             self.to_file.write('\n')
  314.         self.need_nl = False
  315.  
  316.     def child_update(self, message, current, total):
  317.         self.tick()
  318.  
  319.  
  320. class TTYProgressBar(_BaseProgressBar):
  321.     """Progress bar display object.
  322.  
  323.    Several options are available to control the display.  These can
  324.    be passed as parameters to the constructor or assigned at any time:
  325.  
  326.    show_pct
  327.        Show percentage complete.
  328.    show_spinner
  329.        Show rotating baton.  This ticks over on every update even
  330.        if the values don't change.
  331.    show_eta
  332.        Show predicted time-to-completion.
  333.    show_bar
  334.        Show bar graph.
  335.    show_count
  336.        Show numerical counts.
  337.  
  338.    The output file should be in line-buffered or unbuffered mode.
  339.    """
  340.     SPIN_CHARS = r'/-\|'
  341.  
  342.     @deprecated_function(deprecated_in((1, 16, 0)))
  343.     def __init__(self, **kwargs):
  344.         from bzrlib.osutils import terminal_width
  345.         _BaseProgressBar.__init__(self, **kwargs)
  346.         self.spin_pos = 0
  347.         self.width = terminal_width()
  348.         self.last_updates = []
  349.         self._max_last_updates = 10
  350.         self.child_fraction = 0
  351.         self._have_output = False
  352.  
  353.     def throttle(self, old_msg):
  354.         """Return True if the bar was updated too recently"""
  355.         # time.time consistently takes 40/4000 ms = 0.01 ms.
  356.         # time.clock() is faster, but gives us CPU time, not wall-clock time
  357.         now = time.time()
  358.         if self.start_time is not None and (now - self.start_time) < 1:
  359.             return True
  360.         if old_msg != self.last_msg:
  361.             return False
  362.         interval = now - self.last_update
  363.         # if interval > 0
  364.         if interval < self.MIN_PAUSE:
  365.             return True
  366.  
  367.         self.last_updates.append(now - self.last_update)
  368.         # Don't let the queue grow without bound
  369.         self.last_updates = self.last_updates[-self._max_last_updates:]
  370.         self.last_update = now
  371.         return False
  372.  
  373.     def tick(self):
  374.         self.update(self.last_msg, self.last_cnt, self.last_total,
  375.                     self.child_fraction)
  376.  
  377.     def child_update(self, message, current, total):
  378.         if current is not None and total != 0:
  379.             child_fraction = float(current) / total
  380.             if self.last_cnt is None:
  381.                 pass
  382.             elif self.last_cnt + child_fraction <= self.last_total:
  383.                 self.child_fraction = child_fraction
  384.         if self.last_msg is None:
  385.             self.last_msg = ''
  386.         self.tick()
  387.  
  388.     def update(self, msg, current_cnt=None, total_cnt=None,
  389.             child_fraction=0):
  390.         """Update and redraw progress bar.
  391.        """
  392.         if msg is None:
  393.             msg = self.last_msg
  394.  
  395.         if total_cnt is None:
  396.             total_cnt = self.last_total
  397.  
  398.         if current_cnt < 0:
  399.             current_cnt = 0
  400.  
  401.         if current_cnt > total_cnt:
  402.             total_cnt = current_cnt
  403.  
  404.         ## # optional corner case optimisation
  405.         ## # currently does not seem to fire so costs more than saved.
  406.         ## # trivial optimal case:
  407.         ## # NB if callers are doing a clear and restore with
  408.         ## # the saved values, this will prevent that:
  409.         ## # in that case add a restore method that calls
  410.         ## # _do_update or some such
  411.         ## if (self.last_msg == msg and
  412.         ##     self.last_cnt == current_cnt and
  413.         ##     self.last_total == total_cnt and
  414.         ##     self.child_fraction == child_fraction):
  415.         ##     return
  416.  
  417.         if msg is None:
  418.             msg = ''
  419.  
  420.         old_msg = self.last_msg
  421.         # save these for the tick() function
  422.         self.last_msg = msg
  423.         self.last_cnt = current_cnt
  424.         self.last_total = total_cnt
  425.         self.child_fraction = child_fraction
  426.  
  427.         # each function call takes 20ms/4000 = 0.005 ms,
  428.         # but multiple that by 4000 calls -> starts to cost.
  429.         # so anything to make this function call faster
  430.         # will improve base 'diff' time by up to 0.1 seconds.
  431.         if self.throttle(old_msg):
  432.             return
  433.  
  434.         if self.show_eta and self.start_time and self.last_total:
  435.             eta = get_eta(self.start_time, self.last_cnt + self.child_fraction,
  436.                     self.last_total, last_updates = self.last_updates)
  437.             eta_str = " " + str_tdelta(eta)
  438.         else:
  439.             eta_str = ""
  440.  
  441.         if self.show_spinner:
  442.             spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '
  443.         else:
  444.             spin_str = ''
  445.  
  446.         # always update this; it's also used for the bar
  447.         self.spin_pos += 1
  448.  
  449.         if self.show_pct and self.last_total and self.last_cnt:
  450.             pct = 100.0 * ((self.last_cnt + self.child_fraction) / self.last_total)
  451.             pct_str = ' (%5.1f%%)' % pct
  452.         else:
  453.             pct_str = ''
  454.  
  455.         if not self.show_count:
  456.             count_str = ''
  457.         elif self.last_cnt is None:
  458.             count_str = ''
  459.         elif self.last_total is None:
  460.             count_str = ' %i' % (self.last_cnt)
  461.         else:
  462.             # make both fields the same size
  463.             t = '%i' % (self.last_total)
  464.             c = '%*i' % (len(t), self.last_cnt)
  465.             count_str = ' ' + c + '/' + t
  466.  
  467.         if self.show_bar:
  468.             # progress bar, if present, soaks up all remaining space
  469.             cols = self.width - 1 - len(self.last_msg) - len(spin_str) - len(pct_str) \
  470.                    - len(eta_str) - len(count_str) - 3
  471.  
  472.             if self.last_total:
  473.                 # number of markers highlighted in bar
  474.                 markers = int(round(float(cols) *
  475.                               (self.last_cnt + self.child_fraction) / self.last_total))
  476.                 bar_str = '[' + ('=' * markers).ljust(cols) + '] '
  477.             elif False:
  478.                 # don't know total, so can't show completion.
  479.                 # so just show an expanded spinning thingy
  480.                 m = self.spin_pos % cols
  481.                 ms = (' ' * m + '*').ljust(cols)
  482.  
  483.                 bar_str = '[' + ms + '] '
  484.             else:
  485.                 bar_str = ''
  486.         else:
  487.             bar_str = ''
  488.  
  489.         m = spin_str + bar_str + self.last_msg + count_str \
  490.             + pct_str + eta_str
  491.         self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
  492.         self._have_output = True
  493.         #self.to_file.flush()
  494.  
  495.     def clear(self):
  496.         if self._have_output:
  497.             self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
  498.         self._have_output = False
  499.         #self.to_file.flush()
  500.  
  501.  
  502.  
  503. # DEPRECATED
  504. class ChildProgress(_BaseProgressBar):
  505.     """A progress indicator that pushes its data to the parent"""
  506.  
  507.     @deprecated_function(deprecated_in((1, 16, 0)))
  508.     def __init__(self, _stack, **kwargs):
  509.         _BaseProgressBar.__init__(self, _stack=_stack, **kwargs)
  510.         self.parent = _stack.top()
  511.         self.current = None
  512.         self.total = None
  513.         self.child_fraction = 0
  514.         self.message = None
  515.  
  516.     def update(self, msg, current_cnt=None, total_cnt=None):
  517.         self.current = current_cnt
  518.         if total_cnt is not None:
  519.             self.total = total_cnt
  520.         self.message = msg
  521.         self.child_fraction = 0
  522.         self.tick()
  523.  
  524.     def child_update(self, message, current, total):
  525.         if current is None or total == 0:
  526.             self.child_fraction = 0
  527.         else:
  528.             self.child_fraction = float(current) / total
  529.         self.tick()
  530.  
  531.     def tick(self):
  532.         if self.current is None:
  533.             count = None
  534.         else:
  535.             count = self.current+self.child_fraction
  536.             if count > self.total:
  537.                 if __debug__:
  538.                     mutter('clamping count of %d to %d' % (count, self.total))
  539.                 count = self.total
  540.         self.parent.child_update(self.message, count, self.total)
  541.  
  542.     def clear(self):
  543.         pass
  544.  
  545.     def note(self, *args, **kwargs):
  546.         self.parent.note(*args, **kwargs)
  547.  
  548.  
  549. def str_tdelta(delt):
  550.     if delt is None:
  551.         return "-:--:--"
  552.     delt = int(round(delt))
  553.     return '%d:%02d:%02d' % (delt/3600,
  554.                              (delt/60) % 60,
  555.                              delt % 60)
  556.  
  557.  
  558. def get_eta(start_time, current, total, enough_samples=3, last_updates=None, n_recent=10):
  559.     if start_time is None:
  560.         return None
  561.  
  562.     if not total:
  563.         return None
  564.  
  565.     if current < enough_samples:
  566.         return None
  567.  
  568.     if current > total:
  569.         return None                     # wtf?
  570.  
  571.     elapsed = time.time() - start_time
  572.  
  573.     if elapsed < 2.0:                   # not enough time to estimate
  574.         return None
  575.  
  576.     total_duration = float(elapsed) * float(total) / float(current)
  577.  
  578.     if last_updates and len(last_updates) >= n_recent:
  579.         avg = sum(last_updates) / float(len(last_updates))
  580.         time_left = avg * (total - current)
  581.  
  582.         old_time_left = total_duration - elapsed
  583.  
  584.         # We could return the average, or some other value here
  585.         return (time_left + old_time_left) / 2
  586.  
  587.     return total_duration - elapsed
  588.  
  589.  
  590. class ProgressPhase(object):
  591.     """Update progress object with the current phase"""
  592.     def __init__(self, message, total, pb):
  593.         object.__init__(self)
  594.         self.pb = pb
  595.         self.message = message
  596.         self.total = total
  597.         self.cur_phase = None
  598.  
  599.     def next_phase(self):
  600.         if self.cur_phase is None:
  601.             self.cur_phase = 0
  602.         else:
  603.             self.cur_phase += 1
  604.         self.pb.update(self.message, self.cur_phase, self.total)
  605.  
  606.  
  607. _progress_bar_types = {}
  608. _progress_bar_types['dummy'] = DummyProgress
  609. _progress_bar_types['none'] = DummyProgress
  610. _progress_bar_types['tty'] = TTYProgressBar
  611. _progress_bar_types['dots'] = DotsProgressBar
Advertisement
Add Comment
Please, Sign In to add comment