diff options
author | Chris Larson <chris_larson@mentor.com> | 2010-11-18 21:15:07 -0700 |
---|---|---|
committer | Richard Purdie <rpurdie@linux.intel.com> | 2011-01-04 14:46:42 +0000 |
commit | 9ffbd9fe27e25a458b09631c503f4ef96632e334 (patch) | |
tree | f0d8f9291aaa1afe9c2f31ffa629dd27372b2678 | |
parent | 32ea7668712a50d8f8b67d5e4558039e5092a485 (diff) | |
download | openembedded-core-9ffbd9fe27e25a458b09631c503f4ef96632e334.tar.gz openembedded-core-9ffbd9fe27e25a458b09631c503f4ef96632e334.tar.bz2 openembedded-core-9ffbd9fe27e25a458b09631c503f4ef96632e334.tar.xz openembedded-core-9ffbd9fe27e25a458b09631c503f4ef96632e334.zip |
Experimental usage of the 'progressbar' module
(Bitbake rev: 64feb03bc2accecb49033df65e0a939ef5ab5986)
Signed-off-by: Chris Larson <chris_larson@mentor.com>
Signed-off-by: Richard Purdie <rpurdie@linux.intel.com>
-rw-r--r-- | bitbake/lib/bb/ui/knotty.py | 20 | ||||
-rw-r--r-- | bitbake/lib/progressbar.py | 384 |
2 files changed, 396 insertions, 8 deletions
diff --git a/bitbake/lib/bb/ui/knotty.py b/bitbake/lib/bb/ui/knotty.py index 177a12609..a34991bb6 100644 --- a/bitbake/lib/bb/ui/knotty.py +++ b/bitbake/lib/bb/ui/knotty.py @@ -25,11 +25,13 @@ import sys import itertools import xmlrpclib import logging +import progressbar from bb import ui from bb.ui import uihelper logger = logging.getLogger("BitBake") -parsespin = itertools.cycle( r'|/-\\' ) +widgets = ['Parsing recipes: ', progressbar.Percentage(), ' ', + progressbar.Bar(), ' ', progressbar.ETA()] class BBLogFormatter(logging.Formatter): """Formatter which ensures that our 'plain' messages (logging.INFO + 1) are used as is""" @@ -75,6 +77,7 @@ def init(server, eventHandler): print("XMLRPC Fault getting commandline:\n %s" % x) return 1 + pbar = None shutdown = 0 return_value = 0 while True: @@ -130,19 +133,20 @@ def init(server, eventHandler): logger.info(event._message) continue if isinstance(event, bb.event.ParseProgress): - x = event.sofar - y = event.total + current, total = event.sofar, event.total if os.isatty(sys.stdout.fileno()): - sys.stdout.write("\rNOTE: Handling BitBake files: %s (%04d/%04d) [%2d %%]" % ( next(parsespin), x, y, x*100//y ) ) - sys.stdout.flush() + if not pbar: + pbar = progressbar.ProgressBar(widgets=widgets, + maxval=total).start() + pbar.update(current) else: - if x == 1: + if current == 1: sys.stdout.write("Parsing .bb files, please wait...") sys.stdout.flush() - if x == y: + if current == total: sys.stdout.write("done.") sys.stdout.flush() - if x == y: + if current == total: print(("\nParsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors." % ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors))) continue diff --git a/bitbake/lib/progressbar.py b/bitbake/lib/progressbar.py new file mode 100644 index 000000000..b668647a3 --- /dev/null +++ b/bitbake/lib/progressbar.py @@ -0,0 +1,384 @@ +#!/usr/bin/python +# -*- coding: iso-8859-1 -*- +# +# progressbar - Text progressbar library for python. +# Copyright (c) 2005 Nilton Volpato +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +"""Text progressbar library for python. + +This library provides a text mode progressbar. This is typically used +to display the progress of a long running operation, providing a +visual clue that processing is underway. + +The ProgressBar class manages the progress, and the format of the line +is given by a number of widgets. A widget is an object that may +display diferently depending on the state of the progress. There are +three types of widget: +- a string, which always shows itself; +- a ProgressBarWidget, which may return a diferent value every time +it's update method is called; and +- a ProgressBarWidgetHFill, which is like ProgressBarWidget, except it +expands to fill the remaining width of the line. + +The progressbar module is very easy to use, yet very powerful. And +automatically supports features like auto-resizing when available. +""" + +from __future__ import division + +__author__ = "Nilton Volpato" +__author_email__ = "first-name dot last-name @ gmail.com" +__date__ = "2006-05-07" +__version__ = "2.3-dev" + +import sys, time, os +from array import array +try: + from fcntl import ioctl + import termios +except ImportError: + pass +import signal +try: + basestring +except NameError: + basestring = (str,) + +class ProgressBarWidget(object): + """This is an element of ProgressBar formatting. + + The ProgressBar object will call it's update value when an update + is needed. It's size may change between call, but the results will + not be good if the size changes drastically and repeatedly. + """ + def update(self, pbar): + """Returns the string representing the widget. + + The parameter pbar is a reference to the calling ProgressBar, + where one can access attributes of the class for knowing how + the update must be made. + + At least this function must be overriden.""" + pass + +class ProgressBarWidgetHFill(object): + """This is a variable width element of ProgressBar formatting. + + The ProgressBar object will call it's update value, informing the + width this object must the made. This is like TeX \\hfill, it will + expand to fill the line. You can use more than one in the same + line, and they will all have the same width, and together will + fill the line. + """ + def update(self, pbar, width): + """Returns the string representing the widget. + + The parameter pbar is a reference to the calling ProgressBar, + where one can access attributes of the class for knowing how + the update must be made. The parameter width is the total + horizontal width the widget must have. + + At least this function must be overriden.""" + pass + + +class ETA(ProgressBarWidget): + "Widget for the Estimated Time of Arrival" + def format_time(self, seconds): + return time.strftime('%H:%M:%S', time.gmtime(seconds)) + def update(self, pbar): + if pbar.currval == 0: + return 'ETA: --:--:--' + elif pbar.finished: + return 'Time: %s' % self.format_time(pbar.seconds_elapsed) + else: + elapsed = pbar.seconds_elapsed + eta = elapsed * pbar.maxval / pbar.currval - elapsed + return 'ETA: %s' % self.format_time(eta) + +class FileTransferSpeed(ProgressBarWidget): + "Widget for showing the transfer speed (useful for file transfers)." + def __init__(self, unit='B'): + self.unit = unit + self.fmt = '%6.2f %s' + self.prefixes = ['', 'K', 'M', 'G', 'T', 'P'] + def update(self, pbar): + if pbar.seconds_elapsed < 2e-6:#== 0: + bps = 0.0 + else: + bps = pbar.currval / pbar.seconds_elapsed + spd = bps + for u in self.prefixes: + if spd < 1000: + break + spd /= 1000 + return self.fmt % (spd, u + self.unit + '/s') + +class RotatingMarker(ProgressBarWidget): + "A rotating marker for filling the bar of progress." + def __init__(self, markers='|/-\\'): + self.markers = markers + self.curmark = -1 + def update(self, pbar): + if pbar.finished: + return self.markers[0] + self.curmark = (self.curmark + 1) % len(self.markers) + return self.markers[self.curmark] + +class Percentage(ProgressBarWidget): + "Just the percentage done." + def update(self, pbar): + return '%3d%%' % pbar.percentage() + +class SimpleProgress(ProgressBarWidget): + "Returns what is already done and the total, e.g.: '5 of 47'" + def __init__(self, sep=' of '): + self.sep = sep + def update(self, pbar): + return '%d%s%d' % (pbar.currval, self.sep, pbar.maxval) + +class Bar(ProgressBarWidgetHFill): + "The bar of progress. It will stretch to fill the line." + def __init__(self, marker='#', left='|', right='|'): + self.marker = marker + self.left = left + self.right = right + def _format_marker(self, pbar): + if isinstance(self.marker, basestring): + return self.marker + else: + return self.marker.update(pbar) + def update(self, pbar, width): + percent = pbar.percentage() + cwidth = width - len(self.left) - len(self.right) + marked_width = int(percent * cwidth // 100) + m = self._format_marker(pbar) + bar = (self.left + (m * marked_width).ljust(cwidth) + self.right) + return bar + +class ReverseBar(Bar): + "The reverse bar of progress, or bar of regress. :)" + def update(self, pbar, width): + percent = pbar.percentage() + cwidth = width - len(self.left) - len(self.right) + marked_width = int(percent * cwidth // 100) + m = self._format_marker(pbar) + bar = (self.left + (m*marked_width).rjust(cwidth) + self.right) + return bar + +default_widgets = [Percentage(), ' ', Bar()] +class ProgressBar(object): + """This is the ProgressBar class, it updates and prints the bar. + + A common way of using it is like: + >>> pbar = ProgressBar().start() + >>> for i in xrange(100): + ... # do something + ... pbar.update(i+1) + ... + >>> pbar.finish() + + You can also use a progressbar as an iterator: + >>> progress = ProgressBar() + >>> for i in progress(some_iterable): + ... # do something + ... + + But anything you want to do is possible (well, almost anything). + You can supply different widgets of any type in any order. And you + can even write your own widgets! There are many widgets already + shipped and you should experiment with them. + + The term_width parameter must be an integer or None. In the latter case + it will try to guess it, if it fails it will default to 80 columns. + + When implementing a widget update method you may access any + attribute or function of the ProgressBar object calling the + widget's update method. The most important attributes you would + like to access are: + - currval: current value of the progress, 0 <= currval <= maxval + - maxval: maximum (and final) value of the progress + - finished: True if the bar has finished (reached 100%), False o/w + - start_time: the time when start() method of ProgressBar was called + - seconds_elapsed: seconds elapsed since start_time + - percentage(): percentage of the progress [0..100]. This is a method. + + The attributes above are unlikely to change between different versions, + the other ones may change or cease to exist without notice, so try to rely + only on the ones documented above if you are extending the progress bar. + """ + + __slots__ = ('currval', 'fd', 'finished', 'last_update_time', 'maxval', + 'next_update', 'num_intervals', 'seconds_elapsed', + 'signal_set', 'start_time', 'term_width', 'update_interval', + 'widgets', '_iterable') + + _DEFAULT_MAXVAL = 100 + + def __init__(self, maxval=None, widgets=default_widgets, term_width=None, + fd=sys.stderr): + self.maxval = maxval + self.widgets = widgets + self.fd = fd + self.signal_set = False + if term_width is not None: + self.term_width = term_width + else: + try: + self._handle_resize(None, None) + signal.signal(signal.SIGWINCH, self._handle_resize) + self.signal_set = True + except (SystemExit, KeyboardInterrupt): + raise + except: + self.term_width = int(os.environ.get('COLUMNS', 80)) - 1 + + self.currval = 0 + self.finished = False + self.start_time = None + self.last_update_time = None + self.seconds_elapsed = 0 + self._iterable = None + + def __call__(self, iterable): + try: + self.maxval = len(iterable) + except TypeError: + # If the iterable has no length, then rely on the value provided + # by the user, otherwise fail. + if not (isinstance(self.maxval, (int, long)) and self.maxval > 0): + raise RuntimeError('Could not determine maxval from iterable. ' + 'You must explicitly provide a maxval.') + self._iterable = iter(iterable) + self.start() + return self + + def __iter__(self): + return self + + def next(self): + try: + next = self._iterable.next() + self.update(self.currval + 1) + return next + except StopIteration: + self.finish() + raise + + def _handle_resize(self, signum, frame): + h, w = array('h', ioctl(self.fd, termios.TIOCGWINSZ, '\0' * 8))[:2] + self.term_width = w + + def percentage(self): + "Returns the percentage of the progress." + return self.currval * 100.0 / self.maxval + + def _format_widgets(self): + r = [] + hfill_inds = [] + num_hfill = 0 + currwidth = 0 + for i, w in enumerate(self.widgets): + if isinstance(w, ProgressBarWidgetHFill): + r.append(w) + hfill_inds.append(i) + num_hfill += 1 + elif isinstance(w, basestring): + r.append(w) + currwidth += len(w) + else: + weval = w.update(self) + currwidth += len(weval) + r.append(weval) + for iw in hfill_inds: + widget_width = int((self.term_width - currwidth) // num_hfill) + r[iw] = r[iw].update(self, widget_width) + return r + + def _format_line(self): + return ''.join(self._format_widgets()).ljust(self.term_width) + + def _next_update(self): + return int((int(self.num_intervals * + (self.currval / self.maxval)) + 1) * + self.update_interval) + + def _need_update(self): + """Returns true when the progressbar should print an updated line. + + You can override this method if you want finer grained control over + updates. + + The current implementation is optimized to be as fast as possible and + as economical as possible in the number of updates. However, depending + on your usage you may want to do more updates. For instance, if your + progressbar stays in the same percentage for a long time, and you want + to update other widgets, like ETA, then you could return True after + some time has passed with no updates. + + Ideally you could call self._format_line() and see if it's different + from the previous _format_line() call, but calling _format_line() takes + around 20 times more time than calling this implementation of + _need_update(). + """ + return self.currval >= self.next_update + + def update(self, value): + "Updates the progress bar to a new value." + assert 0 <= value <= self.maxval, '0 <= %d <= %d' % (value, self.maxval) + self.currval = value + if not self._need_update(): + return + if self.start_time is None: + raise RuntimeError('You must call start() before calling update()') + now = time.time() + self.seconds_elapsed = now - self.start_time + self.next_update = self._next_update() + self.fd.write(self._format_line() + '\r') + self.last_update_time = now + + def start(self): + """Starts measuring time, and prints the bar at 0%. + + It returns self so you can use it like this: + >>> pbar = ProgressBar().start() + >>> for i in xrange(100): + ... # do something + ... pbar.update(i+1) + ... + >>> pbar.finish() + """ + if self.maxval is None: + self.maxval = self._DEFAULT_MAXVAL + assert self.maxval > 0 + + self.num_intervals = max(100, self.term_width) + self.update_interval = self.maxval / self.num_intervals + self.next_update = 0 + + self.start_time = self.last_update_time = time.time() + self.update(0) + return self + + def finish(self): + """Used to tell the progress is finished.""" + self.finished = True + self.update(self.maxval) + self.fd.write('\n') + if self.signal_set: + signal.signal(signal.SIGWINCH, signal.SIG_DFL) |