clean up NRWT logging/metering, be less verbose
[WebKit-https.git] / Tools / Scripts / webkitpy / layout_tests / views / metered_stream.py
index b5c233b..0d5e66c 100644 (file)
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-# Copyright (C) 2010 Google Inc. All rights reserved.
+# Copyright (C) 2010, 2012 Google Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-"""
-Package that implements a stream wrapper that has 'meters' as well as
-regular output. A 'meter' is a single line of text that can be erased
-and rewritten repeatedly, without producing multiple lines of output. It
-can be used to produce effects like progress bars.
-
-This package should only be called by the printing module in the layout_tests
-package.
-"""
-
-
-class MeteredStream:
-    """This class is a wrapper around a stream that allows you to implement
-    meters (progress bars, etc.).
-
-    It can be used directly as a stream, by calling write(), but also provides
-    a method called update() that will overwite prior updates().
-   """
-
-    def __init__(self, stream):
-        """
-        Args:
-          stream: output stream to write to
-        """
-        self._stream = stream
-        self._dirty = False
-        self._last_update = ""
-
-    def write(self, txt):
-        """Write to the stream, overwriting and resetting the meter."""
-
-        # This routine is called by the logging infrastructure, and
-        # must not call back into logging. It is not a public function.
-        self._overwrite(txt)
-        self._reset()
-
-    def update(self, txt):
-        """Write a message that will be overwritten by subsequent update() or write() calls."""
-        self._overwrite(txt)
-
-    def _overwrite(self, txt):
-        # Print the necessary number of backspaces to erase the previous
-        # message.
-        if len(self._last_update):
-            self._stream.write("\b" * len(self._last_update) +
-                               " " * len(self._last_update) +
-                               "\b" * len(self._last_update))
-        self._stream.write(txt)
-        last_newline = txt.rfind("\n")
-        self._last_update = txt[(last_newline + 1):]
-        self._dirty = True
-
-    def _reset(self):
-        self._dirty = False
-        self._last_update = ''
+import logging
+import os
+import sys
+import time
+
+
+LOG_HANDLER_NAME = 'MeteredStreamLogHandler'
+
+
+class MeteredStream(object):
+    """
+    This class implements a stream wrapper that has 'meters' as well as
+    regular output. A 'meter' is a single line of text that can be erased
+    and rewritten repeatedly, without producing multiple lines of output. It
+    can be used to produce effects like progress bars.
+    """
+
+    @staticmethod
+    def _erasure(txt):
+        num_chars = len(txt)
+        return '\b' * num_chars + ' ' * num_chars + '\b' * num_chars
+
+    @staticmethod
+    def _ensure_newline(txt):
+        return txt if txt.endswith('\n') else txt + '\n'
+
+    def __init__(self, stream=None, verbose=False, logger=None, time_fn=None, pid=None):
+        self._stream = stream or sys.stderr
+        self._verbose = verbose
+        self._time_fn = time_fn or time.time
+        self._pid = pid or os.getpid()
+
+        self._isatty = self._stream.isatty()
+        self._erasing = self._isatty and not verbose
+        self._last_partial_line = ''
+        self._last_write_time = 0.0
+        self._throttle_delay_in_secs = 0.066 if self._erasing else 10.0
+
+        self._logger = logger
+        self._log_handler = None
+        if self._logger:
+            log_level = logging.DEBUG if verbose else logging.INFO
+            self._log_handler = _LogHandler(self)
+            self._log_handler.setLevel(log_level)
+            self._logger.addHandler(self._log_handler)
+
+    def __del__(self):
+        self.cleanup()
+
+    def cleanup(self):
+        if self._logger:
+            self._logger.removeHandler(self._log_handler)
+            self._log_handler = None
+
+    def write_throttled_update(self, txt):
+        now = self._time_fn()
+        if now - self._last_write_time >= self._throttle_delay_in_secs:
+            self.write_update(txt, now)
+
+    def write_update(self, txt, now=None):
+        self.write(txt, now)
+        if self._erasing:
+            self._last_partial_line = txt[txt.rfind('\n') + 1:]
+
+    def write(self, txt, now=None):
+        now = now or self._time_fn()
+        self._last_write_time = now
+        if self._last_partial_line:
+            self._erase_last_partial_line()
+        if self._verbose:
+            now_tuple = time.localtime(now)
+            msg = '%02d:%02d:%02d.%03d %d %s' % (now_tuple.tm_hour, now_tuple.tm_min, now_tuple.tm_sec, int((now * 1000) % 1000), self._pid, self._ensure_newline(txt))
+        elif self._isatty:
+            msg = txt
+        else:
+            msg = self._ensure_newline(txt)
+
+        self._stream.write(msg)
+
+    def writeln(self, txt, now=None):
+        self.write(self._ensure_newline(txt), now)
+
+    def _erase_last_partial_line(self):
+        num_chars = len(self._last_partial_line)
+        self._stream.write(self._erasure(self._last_partial_line))
+        self._last_partial_line = ''
+
+
+class _LogHandler(logging.Handler):
+    def __init__(self, meter):
+        logging.Handler.__init__(self)
+        self._meter = meter
+        self.name = LOG_HANDLER_NAME
+
+    def emit(self, record):
+        self._meter.writeln(record.msg, record.created)