Support progress reporting
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.
The following changes since commit 6d1379c8818400e5cdc442e6142f08a110fd5b95:
lib/bb/main.py: Fix use of BBPOSTCONF and BBPRECONF (2016-06-09 18:00:45 +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 | 266 ++++++++++++++++++++++++++++ lib/bb/runqueue.py | 131 +++++++++++++- 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, 1519 insertions(+), 420 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 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 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 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 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 | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+)
lib/bb/progress.py +107 -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,109 @@ 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 + 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._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 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
@@ -820,7 +820,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 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
@@ -640,6 +640,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 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 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 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 | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-)
lib/bb/runqueue.py +65 -1
@@ -224,6 +224,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()
@@ -1629,8 +1630,35 @@ class RunQueueExecuteTasks(RunQueueExecute): Run the tasks in a queue prepared by rqdata.prepare() """ + if self.rqdata.setscenewhitelist: + # Check tasks that are going to run against the whitelist + unexpected = False + for taskid in range(len(self.rqdata.runq_fnid)): + fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[taskid]] + taskname = self.rqdata.runq_task[taskid] + # Ignore covered tasks + if taskid in self.rq.scenequeue_covered: + continue + # Ignore stamped tasks + if self.rq.check_stamp_task(taskid, taskname, cache=self.stampcache): + continue + # Ignore noexec tasks + taskdep = self.rqdata.dataCache.task_deps[fn] + if 'noexec' in taskdep and taskname in taskdep['noexec']: + continue + + pn = self.rqdata.dataCache.pkg_fn[fn] + if not check_setscene_enforce_whitelist(pn, taskname, self.rqdata.setscenewhitelist): + unexpected = True + if taskid in self.rqdata.runq_setscene: + 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)) + if unexpected: + self.rq.state = runQueueCleanUp + return True + self.rq.read_workers() - if self.stats.total == 0: # nothing to do
@@ -1982,6 +2010,16 @@ class RunQueueExecuteScenequeue(RunQueueExecute): self.scenequeue_covered.add(task) self.scenequeue_updatecounters(task) + def check_taskfail(self, task): + if self.rqdata.setscenewhitelist: + realtask = self.rqdata.runq_setscene[task] + fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[realtask]] + pn = self.rqdata.dataCache.pkg_fn[fn] + taskname = self.rqdata.runq_task[realtask] + 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)
@@ -1992,6 +2030,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[task] = 1
@@ -2001,6 +2040,7 @@ class RunQueueExecuteScenequeue(RunQueueExecute): index = self.rqdata.runq_setscene[task] self.scenequeue_notcovered.add(task) self.scenequeue_updatecounters(task, True) + self.check_taskfail(task) def task_skip(self, task): self.runq_running[task] = 1
@@ -2282,3 +2322,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 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 | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 105 insertions(+), 2 deletions(-)
lib/bb/progress.py +42 -0
@@ -222,3 +222,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 +63 -2
256 lines not on this page · raw message
Signature · 1 line
2.5.5
Re: [PATCH 10/10] runqueue: report progress for "Preparing RunQueue" step
lib/bb/runqueue.py class RunQueue: · line 1161
Quoted 13 lines · Paul Eggleton
lib/bb/runqueue.py +4
-0@@ -1119,14 +1150,19 @@ class RunQueue: if self.state is runQueuePrepare: self.rqexe = RunQueueExecuteDummy(self) + self.rqdata.init_progress_reporter = bb.progress.MultiStageProcessProgressReporter(self.cooker.data, + "Initialising tasks", + [27, 870, 3, 33, 1, 3, 3, 2, 3, 6, 1, 13, 1, 1, 132, 32, 1, 61, 1, 13, 2, 243, 151, 299, 81, 1, 15, 72]) if self.rqdata.prepare() == 0: self.state = runQueueComplete else: self.state = runQueueSceneInit + self.rqdata.init_progress_reporter.next_stage()
I appreciate the difficulty in figuring some of this out and the changes look quite neat on the most part. I am however very worried by the above from a code maintenance standpoint.
If for example I add or remove a step in runqueue, how do I know where in this array to make a change? How do I calculate what the new weighting should be?
I did wonder if next_stage() should pass the number instead, that way at least the call site would be clearer. It doesn't solve the problem of knowing how many steps there are, or what these weights mean or how to recalculate them in the future.
Any thoughts on that?
Cheers,
Richard
Re: [PATCH 10/10] runqueue: report progress for "Preparing RunQueue" step
Quoted 23 lines · On Mon, 13 Jun 2016 12:09:33 Richard Purdie wrote:
Quoted 13 lines · Paul Eggleton
lib/bb/runqueue.py +1
-0@@ -1119,14 +1150,19 @@ class RunQueue: if self.state is runQueuePrepare: self.rqexe = RunQueueExecuteDummy(self) + self.rqdata.init_progress_reporter =bb.progress.MultiStageProcessProgressReporter(self.cooker.data, +
"Initialising tasks",
+ [27, 870, 3,
33, 1, 3, 3, 2, 3, 6, 1, 13, 1, 1, 132, 32, 1, 61, 1, 13, 2, 243, 151, 299, 81, 1, 15, 72])>
ifself.rqdata.prepare()== 0:
self.state = runQueueCompleteQuoted 4 lines
else:
self.state = runQueueSceneInit+
self.rqdata.init_progress_reporter.next_stage()I appreciate the difficulty in figuring some of this out and the changes look quite neat on the most part. I am however very worried by the above from a code maintenance standpoint.
Quoted 3 lines
If for example I add or remove a step in runqueue, how do I know where in this array to make a change? How do I calculate what the new weighting should be?
Actually with the current patchset it's pretty easy. Just temporarily specify debug=True to the reporter when constructing and it will print out the list of weightings (based on timing each stage) when finish() is called, along with a map of the weightings to the lines where next_stage() was called. (Perhaps if we'd want a comment to that effect for anyone editing this code.)
Quoted 4 lines
I did wonder if
next_stage()should pass the number instead, that way at least the call site would be clearer. It doesn't solve the problem of knowing how many steps there are, or what these weights mean or how to recalculate them in the future.
I guess you could do it that way; in the way I'd structured the code earlier it wouldn't have been practical but now that next_stage() must be called before every stage, it could be done. The trouble though is that then if you want to set them all at once (as you often would, since they are relative) you will have to go to each line and update it instead of just changing the list passed to the constructor, so I'm not convinced that's an improvement.
Cheers,
Paul
Signature · 2 lines
Paul Eggleton Intel Open Source Technology Centre
Re: [PATCH 10/10] runqueue: report progress for "Preparing RunQueue" step
Quoted 34 lines · Paul Eggleton
Quoted 28 lines · On Mon, 13 Jun 2016 12:09:33 Richard Purdie wrote:
Quoted 17 lines · Paul Eggleton
lib/bb/runqueue.py +1
-0@@ -1119,14 +1150,19 @@ class RunQueue: if self.state is runQueuePrepare: self.rqexe = RunQueueExecuteDummy(self) + self.rqdata.init_progress_reporter =bb.progress.MultiStageProcessProgressReporter(self.cooker.data, +
"Initialising
tasks",
+ [27,
870, 3,
33, 1, 3, 3, 2, 3, 6, 1, 13, 1, 1, 132, 32, 1, 61, 1, 13, 2, 243, 151,
299, 81, 1, 15, 72])>
ifself.rqdata.prepare()== 0:
self.state = runQueueCompleteQuoted 4 lines
else:
self.state = runQueueSceneInit+
self.rqdata.init_progress_reporter.next_stage()I appreciate the difficulty in figuring some of this out and the changes look quite neat on the most part. I am however very worried by
the above from a code maintenance standpoint.Quoted 4 lines
If for example I add or remove a step in runqueue, how do I know where
in this array to make a change? How do I calculate what the new weighting should be?
Quoted 9 lines
Actually with the current patchset it's pretty easy. Just temporarily specify
debug=True to the reporter when constructing and it will print out the list of
weightings (based on timing each stage) whenfinish()is called, along with a
map of the weightings to the lines wherenext_stage()was called. (Perhaps if
we'd want a comment to that effect for anyone editing this code.)
I think a comment in there about this would be helpful as it isn't clear how to handle this. What does it do in the case you have a next() call without a weight? Will that give a warning?
Cheers,
Richard
Re: [PATCH 10/10] runqueue: report progress for "Preparing RunQueue" step
Quoted 47 lines · On Wed, 15 Jun 2016 22:46:41 Richard Purdie wrote:
Quoted 36 lines · Paul Eggleton
Quoted 30 lines · On Mon, 13 Jun 2016 12:09:33 Richard Purdie wrote:
Quoted 16 lines · Paul Eggleton
lib/bb/runqueue.py +1
-0@@ -1119,14 +1150,19 @@ class RunQueue: if self.state is runQueuePrepare: self.rqexe = RunQueueExecuteDummy(self) + self.rqdata.init_progress_reporter =bb.progress.MultiStageProcessProgressReporter(self.cooker.data, +
"Initialising
tasks",
+ [27,
870, 3,
33, 1, 3, 3, 2, 3, 6, 1, 13, 1, 1, 132, 32, 1, 61, 1, 13, 2, 243, 151,
299, 81, 1, 15, 72])>Quoted 7 lines
if
self.rqdata.prepare()== 0:
self.state = runQueueCompleteelse:
self.state = runQueueSceneInit+
self.rqdata.init_progress_reporter.next_stage()I appreciate the difficulty in figuring some of this out and the changes look quite neat on the most part. I am however very worried by
the above from a code maintenance standpoint.Quoted 4 lines
If for example I add or remove a step in runqueue, how do I know where
in this array to make a change? How do I calculate what the new weighting should be?Quoted 9 lines
Actually with the current patchset it's pretty easy. Just temporarily specify
debug=True to the reporter when constructing and it will print out the list of
weightings (based on timing each stage) whenfinish()is called, along with a
map of the weightings to the lines wherenext_stage()was called. (Perhaps if
we'd want a comment to that effect for anyone editing this code.)
Quoted 2 lines
I think a comment in there about this would be helpful as it isn't clear how to handle this.
OK, I'll add one - I need to rebase this patchset anyway.
Quoted 2 lines
What does it do in the case you have a
next()
call without a weight? Will that give a warning?
It will yes.
Cheers,
Paul
Signature · 2 lines
Paul Eggleton Intel Open Source Technology Centre
Re: [PATCH 05/10] fetch2: implement progress support
In reply to Paul EggletonQuoted 3 lines · Paul Eggleton
+ runfetchcmd(command + ' --progress=dot --show-progress', d,
quiet, log=progresshandler)
wget: unrecognized option '--show-progress'
Turns out wget on Ubuntu 14.04 (wget 1.15) doesn't support this option.
Ross
Re: [PATCH 05/10] fetch2: implement progress support
Quoted 9 lines · Ross
On 13 June 2016 at 03:52, Paul Eggleton <[email protected]>
Quoted 2 lines · wrote:
+ runfetchcmd(command + ' --progress=dot --show-progress', d,
quiet, log=progresshandler)wget: unrecognized option '--show-progress'
Turns out wget on Ubuntu 14.04 (wget 1.15) doesn't support this option.
Turns out it was introduced in 1.16. Sigh...
I will try to figure out what we can do with this. Worst case I suppose we could run wget once to determine the version and just disable progress reporting if it's too old.
Cheers,
Paul
Signature · 2 lines
Paul Eggleton Intel Open Source Technology Centre