Support progress reporting
v2 cover Support progress reporting
Add code to support progress reporting within OpenEmbedded, as well as a couple of related changes to allow reworking the extensible SDK installation process. (See the corresponding OE-Core series for further details).
Changes since v1:
* Rebase on top of recent runqueue changes
* Fix debug=True in MultiStageProgressReporter printing weightings if finish() was called a second time (as it is in the runqueue).
* Fix a couple of bugs with the BB_SETSCENE_ENFORCE code
* Add comments in the runqueue code explaining how to update the weightings
The following changes since commit 31977e7bb98f676197c6cee66f6ab4c12d4dcbde:
cooker: clean up EvertWriter (2016-06-20 17:23:53 +0100)
are available in the git repository at:
git://git.yoctoproject.org/poky-contrib paule/startup-bb
http://git.yoctoproject.org/cgit.cgi/poky-contrib/log/?h=paule/startup-bb
Paul Eggleton (10):
knotty: provide a symlink to the latest console log
knotty: import latest python-progressbar
lib: implement basic task progress support lib/bb/progress: add MultiStageProgressReporter
fetch2: implement progress support
knotty: add code to support showing progress for sstate object
querying
knotty: show task progress bar
knotty: add quiet output mode
runqueue: add ability to enforce that tasks are setscened
runqueue: report progress for "Preparing RunQueue" step
lib/bb/build.py | 34 ++++ lib/bb/event.py | 27 +++ lib/bb/fetch2/__init__.py | 4 +- lib/bb/fetch2/git.py | 52 +++++- lib/bb/fetch2/wget.py | 26 ++- lib/bb/main.py | 9 + lib/bb/msg.py | 5 +- lib/bb/progress.py | 270 ++++++++++++++++++++++++++++ lib/bb/runqueue.py | 151 +++++++++++++++- lib/bb/ui/knotty.py | 143 ++++++++++++--- lib/bb/ui/uihelper.py | 7 +- lib/progressbar.py | 384 ---------------------------------------- lib/progressbar/LICENSE.txt | 52 ++++++ lib/progressbar/__init__.py | 49 ++++++ lib/progressbar/compat.py | 44 +++++ lib/progressbar/progressbar.py | 315 +++++++++++++++++++++++++++++++++ lib/progressbar/widgets.py | 391 +++++++++++++++++++++++++++++++++++++++++ 17 files changed, 1542 insertions(+), 421 deletions(-) create mode 100644 lib/bb/progress.py delete mode 100644 lib/progressbar.py create mode 100644 lib/progressbar/LICENSE.txt create mode 100644 lib/progressbar/__init__.py create mode 100644 lib/progressbar/compat.py create mode 100644 lib/progressbar/progressbar.py create mode 100644 lib/progressbar/widgets.py
Signature · 1 line
2.5.5
PATCH v2 1/10 knotty: provide a symlink to the latest console log
If you're looking to find the latest console log repeatedly it can be a bit tedious - let's just create a symlink just as we do with other logs to make it easy to find.
--- lib/bb/ui/knotty.py | 6 ++++++ 1 file changed, 6 insertions(+)
lib/bb/ui/knotty.py +6 -0
@@ -287,6 +287,12 @@ def main(server, eventHandler, params, tf = TerminalFilter): bb.msg.addDefaultlogFilter(consolelog) consolelog.setFormatter(conlogformat) logger.addHandler(consolelog) + loglink = os.path.join(os.path.dirname(consolelogfile), 'console-latest.log') + bb.utils.remove(loglink) + try: + os.symlink(os.path.basename(consolelogfile), loglink) + except OSError: + pass llevel, debug_domains = bb.msg.constructLogOptions() server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list])
Signature · 1 line
2.5.5
PATCH v2 2/10 knotty: import latest python-progressbar
In reply to Paul EggletonSince we're going to make some minor extensions to it, it makes sense to bring in the latest version of python-progressbar. Its structure has changed a little but the API hasn't; however we do need to ensure our overridden _needs_update() function's signature in BBProgress() matches properly.
--- lib/bb/ui/knotty.py | 2 +- lib/progressbar.py | 384 ----------------------------------------- lib/progressbar/LICENSE.txt | 52 ++++++ lib/progressbar/__init__.py | 49 ++++++ lib/progressbar/compat.py | 44 +++++ lib/progressbar/progressbar.py | 307 ++++++++++++++++++++++++++++++++ lib/progressbar/widgets.py | 355 +++++++++++++++++++++++++++++++++++++ 7 files changed, 808 insertions(+), 385 deletions(-) delete mode 100644 lib/progressbar.py create mode 100644 lib/progressbar/LICENSE.txt create mode 100644 lib/progressbar/__init__.py create mode 100644 lib/progressbar/compat.py create mode 100644 lib/progressbar/progressbar.py create mode 100644 lib/progressbar/widgets.py
lib/bb/ui/knotty.py +1 -1
@@ -51,7 +51,7 @@ class BBProgress(progressbar.ProgressBar): self._resize_default = None progressbar.ProgressBar.__init__(self, maxval, [self.msg + ": "] + widgets, fd=sys.stdout) - def _handle_resize(self, signum, frame): + def _handle_resize(self, signum=None, frame=None): progressbar.ProgressBar._handle_resize(self, signum, frame) if self._resize_default: self._resize_default(signum, frame)
lib/progressbar.py deleted +0 -384
384 lines not on this page · raw message
lib/progressbar/LICENSE.txt new file +52 -0
52 lines not on this page · raw message
lib/progressbar/__init__.py new file +49 -0
49 lines not on this page · raw message
lib/progressbar/compat.py new file +44 -0
44 lines not on this page · raw message
lib/progressbar/progressbar.py new file +307 -0
307 lines not on this page · raw message
lib/progressbar/widgets.py new file +355 -0
355 lines not on this page · raw message
Signature · 1 line
2.5.5
PATCH v2 3/10 lib: implement basic task progress support
In reply to Paul EggletonFor long-running tasks where we have some output from the task that gives us some idea of the progress of the task (such as a percentage complete), provide the means to scrape the output for that progress information and show it to the user in the default knotty terminal output in the form of a progress bar. This is implemented using a new TaskProgress event as well as some code we can insert to do output scanning/filtering.
Any task can fire TaskProgress events; however, if you have a shell task whose output you wish to scan for progress information, you just need to set the "progress" varflag on the task. This can be set to:
* "percent" to just look for a number followed by a % sign
* "percent:<regex>" to specify your own regex matching a percentage value (must have a single group which matches the percentage number)
* "outof:<regex>" to look for the specified regex matching x out of y items completed (must have two groups - first group needs to be x, second y).
We can potentially extend this in future but this should be a good start.
Part of the implementation for [YOCTO #5383].
--- lib/bb/build.py | 34 +++++++++++++++++ lib/bb/progress.py | 86 ++++++++++++++++++++++++++++++++++++++++++ lib/bb/ui/knotty.py | 74 +++++++++++++++++++++++++++++++----- lib/bb/ui/uihelper.py | 7 +++- lib/progressbar/progressbar.py | 16 ++++++-- lib/progressbar/widgets.py | 36 ++++++++++++++++++ 6 files changed, 239 insertions(+), 14 deletions(-) create mode 100644 lib/bb/progress.py
lib/bb/build.py +34 -0
@@ -35,6 +35,7 @@ import stat import bb import bb.msg import bb.process +import bb.progress from bb import data, event, utils bblogger = logging.getLogger('BitBake')
@@ -137,6 +138,25 @@ class TaskInvalid(TaskBase): super(TaskInvalid, self).__init__(task, None, metadata) self._message = "No such task '%s'" % task +class TaskProgress(event.Event): + """ + Task made some progress that could be reported to the user, usually in + the form of a progress bar or similar. + NOTE: this class does not inherit from TaskBase since it doesn't need + to - it's fired within the task context itself, so we don't have any of + the context information that you do in the case of the other events. + The event PID can be used to determine which task it came from. + The progress value is normally 0-100, but can also be negative + indicating that progress has been made but we aren't able to determine + how much. + The rate is optional, this is simply an extra string to display to the + user if specified. + """ + def __init__(self, progress, rate=None): + self.progress = progress + self.rate = rate + event.Event.__init__(self) + class LogTee(object): def __init__(self, logger, outfile):
@@ -340,6 +360,20 @@ exit $ret else: logfile = sys.stdout + progress = d.getVarFlag(func, 'progress', True) + if progress: + if progress == 'percent': + # Use default regex + logfile = bb.progress.BasicProgressHandler(d, outfile=logfile) + elif progress.startswith('percent:'): + # Use specified regex + logfile = bb.progress.BasicProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile) + elif progress.startswith('outof:'): + # Use specified regex + logfile = bb.progress.OutOfProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile) + else: + bb.warn('%s: invalid task progress varflag value "%s", ignoring' % (func, progress)) + def readfifo(data): lines = data.split(b'\0') for line in lines:
lib/bb/progress.py new file +86 -0
86 lines not on this page · raw message
lib/bb/ui/knotty.py +65 -9
123 lines not on this page · raw message
lib/bb/ui/uihelper.py +6 -1
@@ -18,6 +18,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import bb.build +import time class BBUIHelper: def __init__(self):
@@ -31,7 +32,7 @@ class BBUIHelper: def eventHandler(self, event): if isinstance(event, bb.build.TaskStarted): - self.running_tasks[event.pid] = { 'title' : "%s %s" % (event._package, event._task) } + self.running_tasks[event.pid] = { 'title' : "%s %s" % (event._package, event._task), 'starttime' : time.time() } self.running_pids.append(event.pid) self.needUpdate = True if isinstance(event, bb.build.TaskSucceeded):
@@ -52,6 +53,10 @@ class BBUIHelper: self.tasknumber_current = event.stats.completed + event.stats.active + event.stats.failed + 1 self.tasknumber_total = event.stats.total self.needUpdate = True + if isinstance(event, bb.build.TaskProgress): + self.running_tasks[event.pid]['progress'] = event.progress + self.running_tasks[event.pid]['rate'] = event.rate + self.needUpdate = True def getTasks(self): self.needUpdate = False
lib/progressbar/progressbar.py +12 -4
@@ -3,6 +3,8 @@ # progressbar - Text progress bar library for Python. # Copyright (c) 2005 Nilton Volpato # +# (With some small changes after importing into BitBake) +# # 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
@@ -261,12 +263,14 @@ class ProgressBar(object): now = time.time() self.seconds_elapsed = now - self.start_time self.next_update = self.currval + self.update_interval - self.fd.write(self._format_line() + '\r') + output = self._format_line() + self.fd.write(output + '\r') self.fd.flush() self.last_update_time = now + return output - def start(self): + def start(self, update=True): """Starts measuring time, and prints the bar at 0%. It returns self so you can use it like this:
@@ -289,8 +293,12 @@ class ProgressBar(object): self.update_interval = self.maxval / self.num_intervals - self.start_time = self.last_update_time = time.time() - self.update(0) + self.start_time = time.time() + if update: + self.last_update_time = self.start_time + self.update(0) + else: + self.last_update_time = 0 return self
lib/progressbar/widgets.py +36 -0
@@ -353,3 +353,39 @@ class BouncingBar(Bar): if not self.fill_left: rpad, lpad = lpad, rpad return '%s%s%s%s%s' % (left, lpad, marker, rpad, right) + + +class BouncingSlider(Bar): + """ + A slider that bounces back and forth in response to update() calls + without reference to the actual value. Based on a combination of + BouncingBar from a newer version of this module and RotatingMarker. + """ + def __init__(self, marker='<=>'): + self.curmark = -1 + self.forward = True + Bar.__init__(self, marker=marker) + def update(self, pbar, width): + left, marker, right = (format_updatable(i, pbar) for i in + (self.left, self.marker, self.right)) + + width -= len(left) + len(right) + if width < 0: + return '' + + if pbar.finished: return '%s%s%s' % (left, width * '=', right) + + self.curmark = self.curmark + 1 + position = int(self.curmark % (width * 2 - 1)) + if position + len(marker) > width: + self.forward = not self.forward + self.curmark = 1 + position = 1 + lpad = ' ' * (position - 1) + rpad = ' ' * (width - len(marker) - len(lpad)) + + if not self.forward: + temp = lpad + lpad = rpad + rpad = temp + return '%s%s%s%s%s' % (left, lpad, marker, rpad, right)
Signature · 1 line
2.5.5
PATCH v2 4/10 lib/bb/progress: add MultiStageProgressReporter
In reply to Paul EggletonAdd a class to help report progress in a task that consists of multiple stages, some of which may have internal progress (do_rootfs within OpenEmbedded is one example). Each stage is weighted to try to give a reasonable representation of progress over time.
Part of the implementation for [YOCTO #5383].
--- lib/bb/progress.py | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+)
lib/bb/progress.py +111 -0
@@ -20,6 +20,7 @@ BitBake progress handling code import sys import re import time +import inspect import bb.event import bb.build
@@ -84,3 +85,113 @@ class OutOfProgressHandler(ProgressHandler): progress = (float(nums[-1][0]) / float(nums[-1][1])) * 100 self.update(progress) super(OutOfProgressHandler, self).write(string) + +class MultiStageProgressReporter(object): + """ + Class which allows reporting progress without the caller + having to know where they are in the overall sequence. Useful + for tasks made up of python code spread across multiple + classes / functions - the progress reporter object can + be passed around or stored at the object level and calls + to next_stage() and update() made whereever needed. + """ + def __init__(self, d, stage_weights, debug=False): + """ + Initialise the progress reporter. + + Parameters: + * d: the datastore (needed for firing the events) + * stage_weights: a list of weight values, one for each stage. + The value is scaled internally so you only need to specify + values relative to other values in the list, so if there + are two stages and the first takes 2s and the second takes + 10s you would specify [2, 10] (or [1, 5], it doesn't matter). + * debug: specify True (and ensure you call finish() at the end) + in order to show a printout of the calculated stage weights + based on timing each stage. Use this to determine what the + weights should be when you're not sure. + """ + self._data = d + total = sum(stage_weights) + self._stage_weights = [float(x)/total for x in stage_weights] + self._stage = -1 + self._base_progress = 0 + # Send an initial progress event so the bar gets shown + self._fire_progress(0) + self._debug = debug + self._finished = False + if self._debug: + self._last_time = time.time() + self._stage_times = [] + self._stage_total = None + self._callers = [] + + def _fire_progress(self, taskprogress): + bb.event.fire(bb.build.TaskProgress(taskprogress), self._data) + + def next_stage(self, stage_total=None): + """ + Move to the next stage. + Parameters: + * stage_total: optional total for progress within the stage, + see update() for details + NOTE: you need to call this before the first stage. + """ + self._stage += 1 + self._stage_total = stage_total + if self._stage == 0: + # First stage + if self._debug: + self._last_time = time.time() + else: + if self._stage < len(self._stage_weights): + self._base_progress = sum(self._stage_weights[:self._stage]) * 100 + if self._debug: + currtime = time.time() + self._stage_times.append(currtime - self._last_time) + self._last_time = currtime + self._callers.append(inspect.getouterframes(inspect.currentframe())[1]) + elif not self._debug: + bb.warn('ProgressReporter: current stage beyond declared number of stages') + self._base_progress = 100 + self._fire_progress(self._base_progress) + + def update(self, stage_progress): + """ + Update progress within the current stage. + Parameters: + * stage_progress: progress value within the stage. If stage_total + was specified when next_stage() was last called, then this + value is considered to be out of stage_total, otherwise it should + be a percentage value from 0 to 100. + """ + if self._stage_total: + stage_progress = (float(stage_progress) / self._stage_total) * 100 + if self._stage < 0: + bb.warn('ProgressReporter: update called before first call to next_stage()') + elif self._stage < len(self._stage_weights): + progress = self._base_progress + (stage_progress * self._stage_weights[self._stage]) + else: + progress = self._base_progress + if progress > 100: + progress = 100 + self._fire_progress(progress) + + def finish(self): + if self._finished: + return + self._finished = True + if self._debug: + import math + self._stage_times.append(time.time() - self._last_time) + mintime = max(min(self._stage_times), 0.01) + self._callers.append(None) + stage_weights = [int(math.ceil(x / mintime)) for x in self._stage_times] + bb.warn('Stage weights: %s' % stage_weights) + out = [] + for stage_weight, caller in zip(stage_weights, self._callers): + if caller: + out.append('Up to %s:%d: %d' % (caller[1], caller[2], stage_weight)) + else: + out.append('Up to finish: %d' % stage_weight) + bb.warn('Stage times:\n %s' % '\n '.join(out))
Signature · 1 line
2.5.5
PATCH v2 5/10 fetch2: implement progress support
In reply to Paul EggletonImplement progress reporting support specifically for the fetchers. For fetch tasks we don't necessarily know which fetcher will be used (we might initially be fetching a git:// URI, but if we instead download a mirror tarball we may fetch that over http using wget). These programs also have different abilities as far as reporting progress goes (e.g. wget gives us percentage complete and rate, git gives this some of the time depending on what stage it's at). Additionally we filter out the progress output before it makes it to the logs, in order to prevent the logs filling up with junk.
At the moment this is only implemented for the wget and git fetchers since they are the most commonly used (and svn doesn't seem to support any kind of progress output, at least not without doing a relatively expensive remote file listing first).
Line changes such as the ones you get in git's output as it progresses don't make it to the log files, you only get the final state of the line so the logs aren't filled with progress information that's useless after the fact.
Part of the implementation for [YOCTO #5383].
--- lib/bb/fetch2/__init__.py | 4 ++-- lib/bb/fetch2/git.py | 52 +++++++++++++++++++++++++++++++++++++++++++---- lib/bb/fetch2/wget.py | 26 +++++++++++++++++++++++- lib/bb/progress.py | 31 ++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 7 deletions(-)
lib/bb/fetch2/__init__.py +2 -2
@@ -779,7 +779,7 @@ def localpath(url, d): fetcher = bb.fetch2.Fetch([url], d) return fetcher.localpath(url) -def runfetchcmd(cmd, d, quiet=False, cleanup=None): +def runfetchcmd(cmd, d, quiet=False, cleanup=None, log=None): """ Run cmd returning the command output Raise an error if interrupted or cmd fails
@@ -821,7 +821,7 @@ def runfetchcmd(cmd, d, quiet=False, cleanup=None): error_message = "" try: - (output, errors) = bb.process.run(cmd, shell=True, stderr=subprocess.PIPE) + (output, errors) = bb.process.run(cmd, log=log, shell=True, stderr=subprocess.PIPE) success = True except bb.process.NotFoundError as e: error_message = "Fetch command %s" % (e.command)
lib/bb/fetch2/git.py +48 -4
79 lines not on this page · raw message
lib/bb/fetch2/wget.py +25 -1
@@ -31,6 +31,7 @@ import subprocess import os import logging import bb +import bb.progress import urllib.request, urllib.parse, urllib.error from bb import data from bb.fetch2 import FetchMethod
@@ -41,6 +42,27 @@ from bb.utils import export_proxies from bs4 import BeautifulSoup from bs4 import SoupStrainer +class WgetProgressHandler(bb.progress.LineFilterProgressHandler): + """ + Extract progress information from wget output. + Note: relies on --progress=dot --show-progress being specified on the + wget command line. + """ + def __init__(self, d): + super(WgetProgressHandler, self).__init__(d) + # Send an initial progress event so the bar gets shown + self._fire_progress(0) + + def writeline(self, line): + percs = re.findall(r'(\d+)%\s+([\d.]+[A-Z])', line) + if percs: + progress = int(percs[-1][0]) + rate = percs[-1][1] + '/s' + self.update(progress, rate) + return False + return True + + class Wget(FetchMethod): """Class to fetch urls via 'wget'""" def supports(self, ud, d):
@@ -70,9 +92,11 @@ class Wget(FetchMethod): def _runwget(self, ud, d, command, quiet): + progresshandler = WgetProgressHandler(d) + logger.debug(2, "Fetching %s using command '%s'" % (ud.url, command)) bb.fetch2.check_network_access(d, command) - runfetchcmd(command, d, quiet) + runfetchcmd(command + ' --progress=dot --show-progress', d, quiet, log=progresshandler) def download(self, ud, d): """Fetch urls"""
lib/bb/progress.py +31 -0
@@ -58,6 +58,37 @@ class ProgressHandler(object): self._lastevent = ts self._progress = progress +class LineFilterProgressHandler(ProgressHandler): + """ + A ProgressHandler variant that provides the ability to filter out + the lines if they contain progress information. Additionally, it + filters out anything before the last line feed on a line. This can + be used to keep the logs clean of output that we've only enabled for + getting progress, assuming that that can be done on a per-line + basis. + """ + def __init__(self, d, outfile=None): + self._linebuffer = '' + super(LineFilterProgressHandler, self).__init__(d, outfile) + + def write(self, string): + self._linebuffer += string + while True: + breakpos = self._linebuffer.find('\n') + 1 + if breakpos == 0: + break + line = self._linebuffer[:breakpos] + self._linebuffer = self._linebuffer[breakpos:] + # Drop any line feeds and anything that precedes them + lbreakpos = line.rfind('\r') + 1 + if lbreakpos: + line = line[lbreakpos:] + if self.writeline(line): + super(LineFilterProgressHandler, self).write(line) + + def writeline(self, line): + return True + class BasicProgressHandler(ProgressHandler): def __init__(self, d, regex=r'(\d+)%', outfile=None): super(BasicProgressHandler, self).__init__(d, outfile)
Signature · 1 line
2.5.5
PATCH v2 6/10 knotty: add code to support showing progress for sstate object querying
In reply to Paul EggletonAdd support code on the BitBake side to allow sstate.bbclass in OpenEmbedded to report progress when it is checking for availability of artifacts from shared state mirrors.
Part of the implementation for [YOCTO #5853].
--- lib/bb/event.py | 27 +++++++++++++++++++++++++++ lib/bb/ui/knotty.py | 15 +++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-)
lib/bb/event.py +27 -0
@@ -647,6 +647,33 @@ class MetadataEvent(Event): self.type = eventtype self._localdata = eventdata +class ProcessStarted(Event): + """ + Generic process started event (usually part of the initial startup) + where further progress events will be delivered + """ + def __init__(self, processname, total): + Event.__init__(self) + self.processname = processname + self.total = total + +class ProcessProgress(Event): + """ + Generic process progress event (usually part of the initial startup) + """ + def __init__(self, processname, progress): + Event.__init__(self) + self.processname = processname + self.progress = progress + +class ProcessFinished(Event): + """ + Generic process finished event (usually part of the initial startup) + """ + def __init__(self, processname): + Event.__init__(self) + self.processname = processname + class SanityCheck(Event): """ Event to run sanity checks, either raise errors or generate events as return status.
lib/bb/ui/knotty.py +13 -2
@@ -90,7 +90,7 @@ class NonInteractiveProgress(object): self.msg = msg self.maxval = maxval - def start(self): + def start(self, update=True): self.fobj.write("%s..." % self.msg) self.fobj.flush() return self
@@ -304,7 +304,7 @@ _evt_list = [ "bb.runqueue.runQueueExitWait", "bb.event.LogExecTTY", "logging.Lo "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted", "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed", "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent", - "bb.build.TaskProgress"] + "bb.build.TaskProgress", "bb.event.ProcessStarted", "bb.event.ProcessProgress", "bb.event.ProcessFinished"] def main(server, eventHandler, params, tf = TerminalFilter):
@@ -579,6 +579,17 @@ def main(server, eventHandler, params, tf = TerminalFilter): if isinstance(event, bb.event.DepTreeGenerated): continue + if isinstance(event, bb.event.ProcessStarted): + parseprogress = new_progress(event.processname, event.total) + parseprogress.start(False) + continue + if isinstance(event, bb.event.ProcessProgress): + parseprogress.update(event.progress) + continue + if isinstance(event, bb.event.ProcessFinished): + parseprogress.finish() + continue + # ignore if isinstance(event, (bb.event.BuildBase, bb.event.MetadataEvent,
Signature · 1 line
2.5.5
PATCH v2 7/10 knotty: show task progress bar
In reply to Paul EggletonIn addition to the "currently running n tasks (x of y)" message, show a progress bar for another view on how much of the build is left. We have to take care to reset it when moving from the scenequeue to the runqueue, and explicitly don't include an ETA since not all tasks take equal time and thus it isn't possible to estimate the time remaining with the information available.
--- lib/bb/ui/knotty.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-)
lib/bb/ui/knotty.py +19 -4
@@ -89,6 +89,7 @@ class NonInteractiveProgress(object): def __init__(self, msg, maxval): self.msg = msg self.maxval = maxval + self.finished = False def start(self, update=True): self.fobj.write("%s..." % self.msg)
@@ -99,8 +100,11 @@ class NonInteractiveProgress(object): pass def finish(self): + if self.finished: + return self.fobj.write("done.\n") self.fobj.flush() + self.finished = True def new_progress(msg, maxval): if interactive:
@@ -204,6 +208,8 @@ class TerminalFilter(object): console.addFilter(InteractConsoleLogFilter(self, format)) errconsole.addFilter(InteractConsoleLogFilter(self, format)) + self.main_progress = None + def clearFooter(self): if self.footer_present: lines = self.footer_present
@@ -246,11 +252,20 @@ class TerminalFilter(object): if self.main.shutdown: content = "Waiting for %s running tasks to finish:" % len(activetasks) - elif not len(activetasks): - content = "No currently running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total) + print(content) else: - content = "Currently %s running tasks (%s of %s):" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total) - print(content) + if not len(activetasks): + content = "No currently running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total) + else: + content = "Currently %2s running tasks (%s of %s)" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total) + maxtask = self.helper.tasknumber_total + 1 + if not self.main_progress or self.main_progress.maxval != maxtask: + widgets = [' ', progressbar.Percentage(), ' ', progressbar.Bar()] + self.main_progress = BBProgress("Running tasks", maxtask, widgets=widgets) + self.main_progress.start(False) + self.main_progress.setmessage(content) + self.main_progress.update(self.helper.tasknumber_current) + print('') lines = 1 + int(len(content) / (self.columns + 1)) for tasknum, task in enumerate(tasks[:(self.rows - 2)]): if isinstance(task, tuple):
Signature · 1 line
2.5.5
PATCH v2 8/10 knotty: add quiet output mode
In reply to Paul EggletonQuiet output mode disables printing most messages (below warnings) to the console; however these messages still go to the console log file. This is primarily for cases where bitbake is being launched interactively from some other process, but where full console output is not needed.
Because of the need to keep logging all normal events to the console log, this functionality was implemented within the knotty UI rather than in bb.msg (where verbose mode is implemented). We don't currently have a means of registering command line options from the UI end, thus the option actually has to be registered in main.py regardless of the UI, however I didn't feel like it was worth setting up such a mechanism just for this option.
--- lib/bb/main.py | 9 +++++++++ lib/bb/msg.py | 5 ++++- lib/bb/ui/knotty.py | 57 ++++++++++++++++++++++++++++++----------------------- 3 files changed, 45 insertions(+), 26 deletions(-)
lib/bb/main.py +9 -0
@@ -179,6 +179,9 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters): parser.add_option("-D", "--debug", action="count", dest="debug", default=0, help="Increase the debug level. You can specify this more than once.") + parser.add_option("-q", "--quiet", action="store_true", dest="quiet", default=False, + help="Output less log message data to the terminal.") + parser.add_option("-n", "--dry-run", action="store_true", dest="dry_run", default=False, help="Don't execute, just go through the motions.")
@@ -279,6 +282,12 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters): options, targets = parser.parse_args(argv) + if options.quiet and options.verbose: + parser.error("options --quiet and --verbose are mutually exclusive") + + if options.quiet and options.debug: + parser.error("options --quiet and --debug are mutually exclusive") + # use configuration files from environment variables if "BBPRECONF" in os.environ: options.prefile.append(os.environ["BBPRECONF"])
lib/bb/msg.py +4 -1
@@ -182,9 +182,12 @@ def constructLogOptions(): debug_domains["BitBake.%s" % domainarg] = logging.DEBUG - dlevel + 1 return level, debug_domains -def addDefaultlogFilter(handler, cls = BBLogFilter): +def addDefaultlogFilter(handler, cls = BBLogFilter, forcelevel=None): level, debug_domains = constructLogOptions() + if forcelevel is not None: + level = forcelevel + cls(handler, level, debug_domains) #
lib/bb/ui/knotty.py +32 -25
106 lines not on this page · raw message
Signature · 1 line
2.5.5
PATCH v2 9/10 runqueue: add ability to enforce that tasks are setscened
In reply to Paul EggletonAdd the ability to enter a mode where only a specified whitelist of tasks can be executed outright; everything else must be successfully provided in the form of a setscene task (or covered by a setscene task). Any setscene failure outside of the whitelist will cause the build to fail immediately instead of running the real task, and any real tasks that would execute outside of the whitelist cause an immediate build failure when it comes to executing the runqueue as well.
The mode is enabled by setting BB_SETSCENE_ENFORCE="1", and the whitelist is specified through BB_SETSCENE_ENFORCE_WHITELIST, consisting of pn:taskname pairs. A single % character can be substituted for the pn value to match any target explicitly specified on the bitbake command line. Wildcards * and ? can also be used as per standard unix file name matching for both pn and taskname.
Part of the implementation of [YOCTO #9367].
--- lib/bb/runqueue.py | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-)
lib/bb/runqueue.py +80 -1
@@ -240,6 +240,7 @@ class RunQueueData: self.stampwhitelist = cfgData.getVar("BB_STAMP_WHITELIST", True) or "" self.multi_provider_whitelist = (cfgData.getVar("MULTI_PROVIDER_WHITELIST", True) or "").split() + self.setscenewhitelist = get_setscene_enforce_whitelist(cfgData) self.reset()
@@ -1596,8 +1597,51 @@ class RunQueueExecuteTasks(RunQueueExecute): Run the tasks in a queue prepared by rqdata.prepare() """ - self.rq.read_workers() + if self.rqdata.setscenewhitelist: + # Check tasks that are going to run against the whitelist + def check_norun_task(tid, showerror=False): + fn = fn_from_tid(tid) + taskname = taskname_from_tid(tid) + # Ignore covered tasks + if tid in self.rq.scenequeue_covered: + return False + # Ignore stamped tasks + if self.rq.check_stamp_task(tid, taskname, cache=self.stampcache): + return False + # Ignore noexec tasks + taskdep = self.rqdata.dataCache.task_deps[fn] + if 'noexec' in taskdep and taskname in taskdep['noexec']: + return False + pn = self.rqdata.dataCache.pkg_fn[fn] + if not check_setscene_enforce_whitelist(pn, taskname, self.rqdata.setscenewhitelist): + if showerror: + if tid in self.rqdata.runq_setscene_tids: + logger.error('Task %s.%s attempted to execute unexpectedly and should have been setscened' % (pn, taskname)) + else: + logger.error('Task %s.%s attempted to execute unexpectedly' % (pn, taskname)) + return True + return False + # Look to see if any tasks that we think shouldn't run are going to + unexpected = False + for tid in self.rqdata.runtaskentries: + if check_norun_task(tid): + unexpected = True + break + if unexpected: + # Run through the tasks in the rough order they'd have executed and print errors + # (since the order can be useful - usually missing sstate for the last few tasks + # is the cause of the problem) + task = self.sched.next() + while task is not None: + check_norun_task(task, showerror=True) + self.task_skip(task, 'Setscene enforcement check') + task = self.sched.next() + + self.rq.state = runQueueCleanUp + return True + + self.rq.read_workers() if self.stats.total == 0: # nothing to do
@@ -1940,6 +1984,16 @@ class RunQueueExecuteScenequeue(RunQueueExecute): self.scenequeue_covered.add(task) self.scenequeue_updatecounters(task) + def check_taskfail(self, task): + if self.rqdata.setscenewhitelist: + realtask = task.split('_setscene')[0] + fn = fn_from_tid(realtask) + taskname = taskname_from_tid(realtask) + pn = self.rqdata.dataCache.pkg_fn[fn] + if not check_setscene_enforce_whitelist(pn, taskname, self.rqdata.setscenewhitelist): + logger.error('Task %s.%s failed' % (pn, taskname + "_setscene")) + self.rq.state = runQueueCleanUp + def task_complete(self, task): self.stats.taskCompleted() bb.event.fire(sceneQueueTaskCompleted(task, self.stats, self.rq), self.cfgData)
@@ -1950,6 +2004,7 @@ class RunQueueExecuteScenequeue(RunQueueExecute): bb.event.fire(sceneQueueTaskFailed(task, self.stats, result, self), self.cfgData) self.scenequeue_notcovered.add(task) self.scenequeue_updatecounters(task, True) + self.check_taskfail(task) def task_failoutright(self, task): self.runq_running.add(task)
@@ -2231,3 +2286,27 @@ class runQueuePipe(): if len(self.queue) > 0: print("Warning, worker left partial message: %s" % self.queue) self.input.close() + +def get_setscene_enforce_whitelist(d): + if d.getVar('BB_SETSCENE_ENFORCE', True) != '1': + return None + whitelist = (d.getVar("BB_SETSCENE_ENFORCE_WHITELIST", True) or "").split() + outlist = [] + for item in whitelist[:]: + if item.startswith('%:'): + for target in sys.argv[1:]: + if not target.startswith('-'): + outlist.append(target.split(':')[0] + ':' + item.split(':')[1]) + else: + outlist.append(item) + return outlist + +def check_setscene_enforce_whitelist(pn, taskname, whitelist): + import fnmatch + if whitelist: + item = '%s:%s' % (pn, taskname) + for whitelist_item in whitelist: + if fnmatch.fnmatch(item, whitelist_item): + return True + return False + return True
Signature · 1 line
2.5.5
PATCH v2 10/10 runqueue: report progress for "Preparing RunQueue" step
In reply to Paul EggletonWhen "Preparing RunQueue" shows up you can expect to wait up to 30 seconds while it works - which is a bit long to leave the user waiting without any kind of output. Since the work being carried out during this time is divided into stages such that it's practical to determine internally how it's progressing, replace the message with a progress bar.
Actually what happens during this time is two major steps rather than just one - the runqueue preparation itself, followed by the initialisation prior to running setscene tasks. I elected to have the progress bar cover both as one (there doesn't appear to be much point in doing otherwise from a user perspective). I did however describe it as "initialising tasks".
--- lib/bb/progress.py | 42 ++++++++++++++++++++++++++++++++ lib/bb/runqueue.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 109 insertions(+), 3 deletions(-)
lib/bb/progress.py +42 -0
@@ -226,3 +226,45 @@ class MultiStageProgressReporter(object): else: out.append('Up to finish: %d' % stage_weight) bb.warn('Stage times:\n %s' % '\n '.join(out)) + +class MultiStageProcessProgressReporter(MultiStageProgressReporter): + """ + Version of MultiStageProgressReporter intended for use with + standalone processes (such as preparing the runqueue) + """ + def __init__(self, d, processname, stage_weights, debug=False): + self._processname = processname + MultiStageProgressReporter.__init__(self, d, stage_weights, debug) + + def start(self): + bb.event.fire(bb.event.ProcessStarted(self._processname, 100), self._data) + + def _fire_progress(self, taskprogress): + bb.event.fire(bb.event.ProcessProgress(self._processname, taskprogress), self._data) + + def finish(self): + MultiStageProgressReporter.finish(self) + bb.event.fire(bb.event.ProcessFinished(self._processname), self._data) + +class DummyMultiStageProcessProgressReporter(MultiStageProgressReporter): + """ + MultiStageProcessProgressReporter that takes the calls and does nothing + with them (to avoid a bunch of "if progress_reporter:" checks) + """ + def __init__(self): + MultiStageProcessProgressReporter.__init__(self, "", None, []) + + def _fire_progress(self, taskprogress, rate=None): + pass + + def start(self): + pass + + def next_stage(self, stage_total=None): + pass + + def update(self, stage_progress): + pass + + def finish(self): + pass
lib/bb/runqueue.py +67 -3
249 lines not on this page · raw message
Signature · 1 line
2.5.5
PATCH v3 1/1 fetch2: implement progress support
In reply to Paul EggletonImplement progress reporting support specifically for the fetchers. For fetch tasks we don't necessarily know which fetcher will be used (we might initially be fetching a git:// URI, but if we instead download a mirror tarball we may fetch that over http using wget). These programs also have different abilities as far as reporting progress goes (e.g. wget gives us percentage complete and rate, git gives this some of the time depending on what stage it's at). Additionally we filter out the progress output before it makes it to the logs, in order to prevent the logs filling up with junk.
At the moment this is only implemented for the wget and git fetchers since they are the most commonly used (and svn doesn't seem to support any kind of progress output, at least not without doing a relatively expensive remote file listing first).
Line changes such as the ones you get in git's output as it progresses don't make it to the log files, you only get the final state of the line so the logs aren't filled with progress information that's useless after the fact.
Part of the implementation for [YOCTO #5383].
---
Changes since v2:
* Use -v instead of --show-progress in the wget command line since the latter is only available with wget 1.16 and newer, and we still need to support distros that have older versions than that. This does override the default of -nv if present (which it will be via the FETCHCMD_wget set in bitbake.conf within OE), but the added verbosity is minimal since the progress information is filtered out of the logs by the progress handler.
lib/bb/fetch2/__init__.py | 4 ++-- lib/bb/fetch2/git.py | 52 +++++++++++++++++++++++++++++++++++++++++++---- lib/bb/fetch2/wget.py | 28 +++++++++++++++++++++++-- lib/bb/progress.py | 31 ++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 8 deletions(-)
lib/bb/fetch2/__init__.py +2 -2
@@ -779,7 +779,7 @@ def localpath(url, d): fetcher = bb.fetch2.Fetch([url], d) return fetcher.localpath(url) -def runfetchcmd(cmd, d, quiet=False, cleanup=None): +def runfetchcmd(cmd, d, quiet=False, cleanup=None, log=None): """ Run cmd returning the command output Raise an error if interrupted or cmd fails
@@ -821,7 +821,7 @@ def runfetchcmd(cmd, d, quiet=False, cleanup=None): error_message = "" try: - (output, errors) = bb.process.run(cmd, shell=True, stderr=subprocess.PIPE) + (output, errors) = bb.process.run(cmd, log=log, shell=True, stderr=subprocess.PIPE) success = True except bb.process.NotFoundError as e: error_message = "Fetch command %s" % (e.command)
lib/bb/fetch2/git.py +48 -4
79 lines not on this page · raw message
lib/bb/fetch2/wget.py +26 -2
@@ -31,6 +31,7 @@ import subprocess import os import logging import bb +import bb.progress import urllib.request, urllib.parse, urllib.error from bb import data from bb.fetch2 import FetchMethod
@@ -41,6 +42,27 @@ from bb.utils import export_proxies from bs4 import BeautifulSoup from bs4 import SoupStrainer +class WgetProgressHandler(bb.progress.LineFilterProgressHandler): + """ + Extract progress information from wget output. + Note: relies on --progress=dot (with -v or without -q/-nv) being + specified on the wget command line. + """ + def __init__(self, d): + super(WgetProgressHandler, self).__init__(d) + # Send an initial progress event so the bar gets shown + self._fire_progress(0) + + def writeline(self, line): + percs = re.findall(r'(\d+)%\s+([\d.]+[A-Z])', line) + if percs: + progress = int(percs[-1][0]) + rate = percs[-1][1] + '/s' + self.update(progress, rate) + return False + return True + + class Wget(FetchMethod): """Class to fetch urls via 'wget'""" def supports(self, ud, d):
@@ -66,13 +88,15 @@ class Wget(FetchMethod): if not ud.localfile: ud.localfile = data.expand(urllib.parse.unquote(ud.host + ud.path).replace("/", "."), d) - self.basecmd = d.getVar("FETCHCMD_wget", True) or "/usr/bin/env wget -t 2 -T 30 -nv --passive-ftp --no-check-certificate" + self.basecmd = d.getVar("FETCHCMD_wget", True) or "/usr/bin/env wget -t 2 -T 30 --passive-ftp --no-check-certificate" def _runwget(self, ud, d, command, quiet): + progresshandler = WgetProgressHandler(d) + logger.debug(2, "Fetching %s using command '%s'" % (ud.url, command)) bb.fetch2.check_network_access(d, command) - runfetchcmd(command, d, quiet) + runfetchcmd(command + ' --progress=dot -v', d, quiet, log=progresshandler) def download(self, ud, d): """Fetch urls"""
lib/bb/progress.py +31 -0
@@ -58,6 +58,37 @@ class ProgressHandler(object): self._lastevent = ts self._progress = progress +class LineFilterProgressHandler(ProgressHandler): + """ + A ProgressHandler variant that provides the ability to filter out + the lines if they contain progress information. Additionally, it + filters out anything before the last line feed on a line. This can + be used to keep the logs clean of output that we've only enabled for + getting progress, assuming that that can be done on a per-line + basis. + """ + def __init__(self, d, outfile=None): + self._linebuffer = '' + super(LineFilterProgressHandler, self).__init__(d, outfile) + + def write(self, string): + self._linebuffer += string + while True: + breakpos = self._linebuffer.find('\n') + 1 + if breakpos == 0: + break + line = self._linebuffer[:breakpos] + self._linebuffer = self._linebuffer[breakpos:] + # Drop any line feeds and anything that precedes them + lbreakpos = line.rfind('\r') + 1 + if lbreakpos: + line = line[lbreakpos:] + if self.writeline(line): + super(LineFilterProgressHandler, self).write(line) + + def writeline(self, line): + return True + class BasicProgressHandler(ProgressHandler): def __init__(self, d, regex=r'(\d+)%', outfile=None): super(BasicProgressHandler, self).__init__(d, outfile)
Signature · 1 line
2.5.5
Re: [PATCH v3] fetch2: implement progress support
Quoted 25 lines · On Wed, 06 Jul 2016 16:26:10 Paul Eggleton wrote:
Implement progress reporting support specifically for the fetchers. For fetch tasks we don't necessarily know which fetcher will be used (we might initially be fetching a
git://URI, but if we instead download a mirror tarball we may fetch that over http using wget). These programs also have different abilities as far as reporting progress goes (e.g. wget gives us percentage complete and rate, git gives this some of the time depending on what stage it's at). Additionally we filter out the progress output before it makes it to the logs, in order to prevent the logs filling up with junk.At the moment this is only implemented for the wget and git fetchers since they are the most commonly used (and svn doesn't seem to support any kind of progress output, at least not without doing a relatively expensive remote file listing first).
Line changes such as the ones you get in git's output as it progresses don't make it to the log files, you only get the final state of the line so the logs aren't filled with progress information that's useless after the fact.
Part of the implementation for [YOCTO #5383].
Signed-off-by Paul Eggleton <[email protected]>---
Quoted 8 lines
Changes since v2:
* Use -v instead of --show-progress in the wget command line since the latter is only available with wget 1.16 and newer, and we still need to support distros that have older versions than that. This does override the default of -nv if present (which it will be via theFETCHCMD_wgetset in bitbake.conf within OE), but the added verbosity is minimal since the progress information is filtered out of the logs by the progress handler.
So, you asked me to send this, but unfortunately it looks like you merged the old version - I guess I should send an additional patch now.
Cheers,
Paul
Signature · 2 lines
Paul Eggleton Intel Open Source Technology Centre
Re: [PATCH v3] fetch2: implement progress support
Quoted 36 lines · On Mon, 11 Jul 2016 10:23:30 Paul Eggleton wrote:
Quoted 28 lines · On Wed, 06 Jul 2016 16:26:10 Paul Eggleton wrote:
Implement progress reporting support specifically for the fetchers. For fetch tasks we don't necessarily know which fetcher will be used (we might initially be fetching a
git://URI, but if we instead download a mirror tarball we may fetch that over http using wget). These programs also have different abilities as far as reporting progress goes (e.g. wget gives us percentage complete and rate, git gives this some of the time depending on what stage it's at). Additionally we filter out the progress output before it makes it to the logs, in order to prevent the logs filling up with junk.At the moment this is only implemented for the wget and git fetchers since they are the most commonly used (and svn doesn't seem to support any kind of progress output, at least not without doing a relatively expensive remote file listing first).
Line changes such as the ones you get in git's output as it progresses don't make it to the log files, you only get the final state of the line so the logs aren't filled with progress information that's useless after the fact.
Part of the implementation for [YOCTO #5383].
Signed-off-by Paul Eggleton <[email protected]>---
Changes since v2:
* Use -v instead of --show-progress in the wget command line since theQuoted 6 lines
latter is only available with wget 1.16 and newer, and we still need to support distros that have older versions than that. This does override the default of -nv if present (which it will be via the
FETCHCMD_wgetset in bitbake.conf within OE), but the added verbosity is minimal since the progress information is filtered out of the logs by the progress handler.
Quoted 2 lines
So, you asked me to send this, but unfortunately it looks like you merged the old version - I guess I should send an additional patch now.
Wait a second - you didn't merge it at all, sorry my mistake.
Cheers,
Paul
Signature · 2 lines
Paul Eggleton Intel Open Source Technology Centre