diff options
author | Paul Eggleton <paul.eggleton@linux.intel.com> | 2016-06-23 22:59:05 +1200 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2016-07-08 09:57:26 +0100 |
commit | ac5e720575dd3c8f86514a7a646de82c8cc28e17 (patch) | |
tree | 9c0cec5d764f1b9fb8f33d5f094d87a8fbc89ab5 /bitbake/lib/bb/ui/knotty.py | |
parent | 1cf6e14a6c5ddb4daec1c1e0cce113eea8570545 (diff) | |
download | poky-ac5e720575dd3c8f86514a7a646de82c8cc28e17.tar.gz |
bitbake: lib: implement basic task progress support
For 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].
(Bitbake rev: 0d275fc5b6531957a6189069b04074065bb718a0)
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/bb/ui/knotty.py')
-rw-r--r-- | bitbake/lib/bb/ui/knotty.py | 74 |
1 files changed, 65 insertions, 9 deletions
diff --git a/bitbake/lib/bb/ui/knotty.py b/bitbake/lib/bb/ui/knotty.py index 6a6f6888e3..2513501500 100644 --- a/bitbake/lib/bb/ui/knotty.py +++ b/bitbake/lib/bb/ui/knotty.py | |||
@@ -40,10 +40,13 @@ logger = logging.getLogger("BitBake") | |||
40 | interactive = sys.stdout.isatty() | 40 | interactive = sys.stdout.isatty() |
41 | 41 | ||
42 | class BBProgress(progressbar.ProgressBar): | 42 | class BBProgress(progressbar.ProgressBar): |
43 | def __init__(self, msg, maxval): | 43 | def __init__(self, msg, maxval, widgets=None): |
44 | self.msg = msg | 44 | self.msg = msg |
45 | widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ', | 45 | self.extrapos = -1 |
46 | progressbar.ETA()] | 46 | if not widgets: |
47 | widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ', | ||
48 | progressbar.ETA()] | ||
49 | self.extrapos = 4 | ||
47 | 50 | ||
48 | try: | 51 | try: |
49 | self._resize_default = signal.getsignal(signal.SIGWINCH) | 52 | self._resize_default = signal.getsignal(signal.SIGWINCH) |
@@ -55,11 +58,31 @@ class BBProgress(progressbar.ProgressBar): | |||
55 | progressbar.ProgressBar._handle_resize(self, signum, frame) | 58 | progressbar.ProgressBar._handle_resize(self, signum, frame) |
56 | if self._resize_default: | 59 | if self._resize_default: |
57 | self._resize_default(signum, frame) | 60 | self._resize_default(signum, frame) |
61 | |||
58 | def finish(self): | 62 | def finish(self): |
59 | progressbar.ProgressBar.finish(self) | 63 | progressbar.ProgressBar.finish(self) |
60 | if self._resize_default: | 64 | if self._resize_default: |
61 | signal.signal(signal.SIGWINCH, self._resize_default) | 65 | signal.signal(signal.SIGWINCH, self._resize_default) |
62 | 66 | ||
67 | def setmessage(self, msg): | ||
68 | self.msg = msg | ||
69 | self.widgets[0] = msg | ||
70 | |||
71 | def setextra(self, extra): | ||
72 | if extra: | ||
73 | extrastr = str(extra) | ||
74 | if extrastr[0] != ' ': | ||
75 | extrastr = ' ' + extrastr | ||
76 | if extrastr[-1] != ' ': | ||
77 | extrastr += ' ' | ||
78 | else: | ||
79 | extrastr = ' ' | ||
80 | self.widgets[self.extrapos] = extrastr | ||
81 | |||
82 | def _need_update(self): | ||
83 | # We always want the bar to print when update() is called | ||
84 | return True | ||
85 | |||
63 | class NonInteractiveProgress(object): | 86 | class NonInteractiveProgress(object): |
64 | fobj = sys.stdout | 87 | fobj = sys.stdout |
65 | 88 | ||
@@ -195,15 +218,31 @@ class TerminalFilter(object): | |||
195 | activetasks = self.helper.running_tasks | 218 | activetasks = self.helper.running_tasks |
196 | failedtasks = self.helper.failed_tasks | 219 | failedtasks = self.helper.failed_tasks |
197 | runningpids = self.helper.running_pids | 220 | runningpids = self.helper.running_pids |
198 | if self.footer_present and (self.lastcount == self.helper.tasknumber_current) and (self.lastpids == runningpids): | 221 | if self.footer_present and not self.helper.needUpdate: |
199 | return | 222 | return |
223 | self.helper.needUpdate = False | ||
200 | if self.footer_present: | 224 | if self.footer_present: |
201 | self.clearFooter() | 225 | self.clearFooter() |
202 | if (not self.helper.tasknumber_total or self.helper.tasknumber_current == self.helper.tasknumber_total) and not len(activetasks): | 226 | if (not self.helper.tasknumber_total or self.helper.tasknumber_current == self.helper.tasknumber_total) and not len(activetasks): |
203 | return | 227 | return |
204 | tasks = [] | 228 | tasks = [] |
205 | for t in runningpids: | 229 | for t in runningpids: |
206 | tasks.append("%s (pid %s)" % (activetasks[t]["title"], t)) | 230 | progress = activetasks[t].get("progress", None) |
231 | if progress is not None: | ||
232 | pbar = activetasks[t].get("progressbar", None) | ||
233 | rate = activetasks[t].get("rate", None) | ||
234 | start_time = activetasks[t].get("starttime", None) | ||
235 | if not pbar or pbar.bouncing != (progress < 0): | ||
236 | if progress < 0: | ||
237 | pbar = BBProgress("0: %s (pid %s) " % (activetasks[t]["title"], t), 100, widgets=[progressbar.BouncingSlider()]) | ||
238 | pbar.bouncing = True | ||
239 | else: | ||
240 | pbar = BBProgress("0: %s (pid %s) " % (activetasks[t]["title"], t), 100) | ||
241 | pbar.bouncing = False | ||
242 | activetasks[t]["progressbar"] = pbar | ||
243 | tasks.append((pbar, progress, rate, start_time)) | ||
244 | else: | ||
245 | tasks.append("%s (pid %s)" % (activetasks[t]["title"], t)) | ||
207 | 246 | ||
208 | if self.main.shutdown: | 247 | if self.main.shutdown: |
209 | content = "Waiting for %s running tasks to finish:" % len(activetasks) | 248 | content = "Waiting for %s running tasks to finish:" % len(activetasks) |
@@ -214,8 +253,23 @@ class TerminalFilter(object): | |||
214 | print(content) | 253 | print(content) |
215 | lines = 1 + int(len(content) / (self.columns + 1)) | 254 | lines = 1 + int(len(content) / (self.columns + 1)) |
216 | for tasknum, task in enumerate(tasks[:(self.rows - 2)]): | 255 | for tasknum, task in enumerate(tasks[:(self.rows - 2)]): |
217 | content = "%s: %s" % (tasknum, task) | 256 | if isinstance(task, tuple): |
218 | print(content) | 257 | pbar, progress, rate, start_time = task |
258 | if not pbar.start_time: | ||
259 | pbar.start(False) | ||
260 | if start_time: | ||
261 | pbar.start_time = start_time | ||
262 | pbar.setmessage('%s:%s' % (tasknum, pbar.msg.split(':', 1)[1])) | ||
263 | if progress > -1: | ||
264 | pbar.setextra(rate) | ||
265 | output = pbar.update(progress) | ||
266 | else: | ||
267 | output = pbar.update(1) | ||
268 | if not output or (len(output) <= pbar.term_width): | ||
269 | print('') | ||
270 | else: | ||
271 | content = "%s: %s" % (tasknum, task) | ||
272 | print(content) | ||
219 | lines = lines + 1 + int(len(content) / (self.columns + 1)) | 273 | lines = lines + 1 + int(len(content) / (self.columns + 1)) |
220 | self.footer_present = lines | 274 | self.footer_present = lines |
221 | self.lastpids = runningpids[:] | 275 | self.lastpids = runningpids[:] |
@@ -249,7 +303,8 @@ _evt_list = [ "bb.runqueue.runQueueExitWait", "bb.event.LogExecTTY", "logging.Lo | |||
249 | "bb.command.CommandExit", "bb.command.CommandCompleted", "bb.cooker.CookerExit", | 303 | "bb.command.CommandExit", "bb.command.CommandCompleted", "bb.cooker.CookerExit", |
250 | "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted", | 304 | "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted", |
251 | "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed", | 305 | "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed", |
252 | "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent"] | 306 | "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent", |
307 | "bb.build.TaskProgress"] | ||
253 | 308 | ||
254 | def main(server, eventHandler, params, tf = TerminalFilter): | 309 | def main(server, eventHandler, params, tf = TerminalFilter): |
255 | 310 | ||
@@ -535,7 +590,8 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
535 | bb.event.OperationStarted, | 590 | bb.event.OperationStarted, |
536 | bb.event.OperationCompleted, | 591 | bb.event.OperationCompleted, |
537 | bb.event.OperationProgress, | 592 | bb.event.OperationProgress, |
538 | bb.event.DiskFull)): | 593 | bb.event.DiskFull, |
594 | bb.build.TaskProgress)): | ||
539 | continue | 595 | continue |
540 | 596 | ||
541 | logger.error("Unknown event: %s", event) | 597 | logger.error("Unknown event: %s", event) |