Index: gclient_utils.py |
diff --git a/gclient_utils.py b/gclient_utils.py |
index 3517946435a6efe346af97bb539f15f46f624166..f89b601f3a201146e8446aee570cd3bc30f7762a 100644 |
--- a/gclient_utils.py |
+++ b/gclient_utils.py |
@@ -6,6 +6,7 @@ |
import codecs |
import cStringIO |
+import datetime |
import logging |
import os |
import pipes |
@@ -25,6 +26,7 @@ import subprocess2 |
RETRY_MAX = 3 |
RETRY_INITIAL_SLEEP = 0.5 |
+START = datetime.datetime.now() |
_WARNINGS = [] |
@@ -47,6 +49,12 @@ class Error(Exception): |
super(Error, self).__init__(msg, *args, **kwargs) |
+def Elapsed(until=None): |
+ if until is None: |
+ until = datetime.datetime.now() |
+ return str(until - START).partition('.')[0] |
+ |
+ |
def PrintWarnings(): |
"""Prints any accumulated warnings.""" |
if _WARNINGS: |
@@ -483,12 +491,8 @@ def CheckCallAndFilter(args, stdout=None, filter_fn=None, |
output.write(in_byte) |
if print_stdout: |
stdout.write(in_byte) |
- if in_byte != '\r': |
- if in_byte != '\n': |
- in_line += in_byte |
- else: |
- filter_fn(in_line) |
- in_line = '' |
+ if in_byte not in ['\r', '\n']: |
+ in_line += in_byte |
else: |
filter_fn(in_line) |
in_line = '' |
@@ -525,9 +529,9 @@ class GitFilter(object): |
Allows a custom function to skip certain lines (predicate), and will throttle |
the output of percentage completed lines to only output every X seconds. |
""" |
- PERCENT_RE = re.compile('.* ([0-9]{1,2})% .*') |
+ PERCENT_RE = re.compile('(.*) ([0-9]{1,3})% .*') |
- def __init__(self, time_throttle=0, predicate=None): |
+ def __init__(self, time_throttle=0, predicate=None, out_fh=None): |
""" |
Args: |
time_throttle (int): GitFilter will throttle 'noisy' output (such as the |
@@ -535,10 +539,13 @@ class GitFilter(object): |
seconds apart. |
predicate (f(line)): An optional function which is invoked for every line. |
The line will be skipped if predicate(line) returns False. |
+ out_fh: File handle to write output to. |
""" |
self.last_time = 0 |
self.time_throttle = time_throttle |
self.predicate = predicate |
+ self.out_fh = out_fh or sys.stdout |
+ self.progress_prefix = None |
def __call__(self, line): |
# git uses an escape sequence to clear the line; elide it. |
@@ -549,11 +556,14 @@ class GitFilter(object): |
return |
now = time.time() |
match = self.PERCENT_RE.match(line) |
- if not match: |
- self.last_time = 0 |
- if (now - self.last_time) >= self.time_throttle: |
- self.last_time = now |
- print line |
+ if match: |
+ if match.group(1) != self.progress_prefix: |
+ self.progress_prefix = match.group(1) |
+ elif now - self.last_time < self.time_throttle: |
+ return |
+ self.last_time = now |
+ self.out_fh.write('[%s] ' % Elapsed()) |
+ print >> self.out_fh, line |
def FindGclientRoot(from_dir, filename='.gclient'): |
@@ -683,6 +693,8 @@ class WorkItem(object): |
def __init__(self, name): |
# A unique string representing this work item. |
self._name = name |
+ self.outbuf = cStringIO.StringIO() |
+ self.start = self.finish = None |
def run(self, work_queue): |
"""work_queue is passed as keyword argument so it should be |
@@ -704,7 +716,7 @@ class ExecutionQueue(object): |
Methods of this class are thread safe. |
""" |
- def __init__(self, jobs, progress, ignore_requirements): |
+ def __init__(self, jobs, progress, ignore_requirements, verbose=False): |
"""jobs specifies the number of concurrent tasks to allow. progress is a |
Progress instance.""" |
# Set when a thread is done or a new item is enqueued. |
@@ -725,6 +737,9 @@ class ExecutionQueue(object): |
self.progress.update(0) |
self.ignore_requirements = ignore_requirements |
+ self.verbose = verbose |
+ self.last_join = None |
+ self.last_subproc_output = None |
def enqueue(self, d): |
"""Enqueue one Dependency to be executed later once its requirements are |
@@ -743,9 +758,30 @@ class ExecutionQueue(object): |
finally: |
self.ready_cond.release() |
+ def out_cb(self, _): |
+ self.last_subproc_output = datetime.datetime.now() |
+ return True |
+ |
+ @staticmethod |
+ def format_task_output(task, comment=''): |
+ if comment: |
+ comment = ' (%s)' % comment |
+ if task.start and task.finish: |
+ elapsed = ' (Elapsed: %s)' % ( |
+ str(task.finish - task.start).partition('.')[0]) |
+ else: |
+ elapsed = '' |
+ return """ |
+%s%s%s |
+---------------------------------------- |
+%s |
+----------------------------------------""" % ( |
+ task.name, comment, task.outbuf.getvalue().strip(), elapsed) |
+ |
def flush(self, *args, **kwargs): |
"""Runs all enqueued items until all are executed.""" |
kwargs['work_queue'] = self |
+ self.last_subproc_output = self.last_join = datetime.datetime.now() |
self.ready_cond.acquire() |
try: |
while True: |
@@ -778,6 +814,17 @@ class ExecutionQueue(object): |
# We need to poll here otherwise Ctrl-C isn't processed. |
try: |
self.ready_cond.wait(10) |
+ # If we haven't printed to terminal for a while, but we have received |
+ # spew from a suprocess, let the user know we're still progressing. |
+ now = datetime.datetime.now() |
+ if (now - self.last_join > datetime.timedelta(seconds=60) and |
+ self.last_subproc_output > self.last_join): |
+ if self.progress: |
+ print >> sys.stdout, '' |
+ elapsed = Elapsed() |
+ print >> sys.stdout, '[%s] Still working on:' % elapsed |
+ for task in self.running: |
+ print >> sys.stdout, '[%s] %s' % (elapsed, task.item.name) |
except KeyboardInterrupt: |
# Help debugging by printing some information: |
print >> sys.stderr, ( |
@@ -788,7 +835,10 @@ class ExecutionQueue(object): |
', '.join(self.ran), |
len(self.running))) |
for i in self.queued: |
- print >> sys.stderr, '%s: %s' % (i.name, ', '.join(i.requirements)) |
+ print >> sys.stderr, '%s (not started): %s' % ( |
+ i.name, ', '.join(i.requirements)) |
+ for i in self.running: |
+ print >> sys.stderr, self.format_task_output(i.item, 'interrupted') |
raise |
# Something happened: self.enqueue() or a thread terminated. Loop again. |
finally: |
@@ -796,11 +846,14 @@ class ExecutionQueue(object): |
assert not self.running, 'Now guaranteed to be single-threaded' |
if not self.exceptions.empty(): |
+ if self.progress: |
+ print >> sys.stdout, '' |
# To get back the stack location correctly, the raise a, b, c form must be |
# used, passing a tuple as the first argument doesn't work. |
- e = self.exceptions.get() |
+ e, task = self.exceptions.get() |
+ print >> sys.stderr, self.format_task_output(task.item, 'ERROR') |
raise e[0], e[1], e[2] |
- if self.progress: |
+ elif self.progress: |
self.progress.end() |
def _flush_terminated_threads(self): |
@@ -812,7 +865,10 @@ class ExecutionQueue(object): |
self.running.append(t) |
else: |
t.join() |
+ self.last_join = datetime.datetime.now() |
sys.stdout.flush() |
+ if self.verbose: |
+ print >> sys.stdout, self.format_task_output(t.item) |
if self.progress: |
self.progress.update(1, t.item.name) |
if t.item.name in self.ran: |
@@ -832,10 +888,26 @@ class ExecutionQueue(object): |
else: |
# Run the 'thread' inside the main thread. Don't try to catch any |
# exception. |
- task_item.run(*args, **kwargs) |
- self.ran.append(task_item.name) |
- if self.progress: |
- self.progress.update(1, ', '.join(t.item.name for t in self.running)) |
+ try: |
+ task_item.start = datetime.datetime.now() |
+ print >> task_item.outbuf, '[%s] Started.' % Elapsed(task_item.start) |
+ task_item.run(*args, **kwargs) |
+ task_item.finish = datetime.datetime.now() |
+ print >> task_item.outbuf, '[%s] Finished.' % Elapsed(task_item.finish) |
+ self.ran.append(task_item.name) |
+ if self.verbose: |
+ if self.progress: |
+ print >> sys.stdout, '' |
+ print >> sys.stdout, self.format_task_output(task_item) |
+ if self.progress: |
+ self.progress.update(1, ', '.join(t.item.name for t in self.running)) |
+ except KeyboardInterrupt: |
+ print >> sys.stderr, self.format_task_output(task_item, 'interrupted') |
+ raise |
+ except Exception: |
+ print >> sys.stderr, self.format_task_output(task_item, 'ERROR') |
+ raise |
+ |
class _Worker(threading.Thread): |
"""One thread to execute one WorkItem.""" |
@@ -853,17 +925,21 @@ class ExecutionQueue(object): |
logging.debug('_Worker.run(%s)' % self.item.name) |
work_queue = self.kwargs['work_queue'] |
try: |
+ self.item.start = datetime.datetime.now() |
+ print >> self.item.outbuf, '[%s] Started.' % Elapsed(self.item.start) |
self.item.run(*self.args, **self.kwargs) |
+ self.item.finish = datetime.datetime.now() |
+ print >> self.item.outbuf, '[%s] Finished.' % Elapsed(self.item.finish) |
except KeyboardInterrupt: |
logging.info('Caught KeyboardInterrupt in thread %s', self.item.name) |
logging.info(str(sys.exc_info())) |
- work_queue.exceptions.put(sys.exc_info()) |
+ work_queue.exceptions.put((sys.exc_info(), self)) |
raise |
except Exception: |
# Catch exception location. |
logging.info('Caught exception in thread %s', self.item.name) |
logging.info(str(sys.exc_info())) |
- work_queue.exceptions.put(sys.exc_info()) |
+ work_queue.exceptions.put((sys.exc_info(), self)) |
finally: |
logging.info('_Worker.run(%s) done', self.item.name) |
work_queue.ready_cond.acquire() |