[Piglit] [PATCH] log: Replace console output with a simpler output

Dylan Baker baker.dylan.c at gmail.com
Thu Jan 9 12:02:12 PST 2014


This replaces the console spewer with a simpler console reporting
mechanism inspired by the output of ninja. This reduces code, remove all
singleton instances and speeds up piglit runs. There is a drawback, the
output is much more terse than the previous implementation, giving only
the following output:
[<finished>/<total>] Running Test(s): <test number>
[16008/16011] Running Test(s): 16007 16008 16009 16010

This means that one can't look at the output to readily see that all
tests are skipping or failing. However, since these problems are usually
related to environment setup, and not to piglit doing something wrong,
these should be moved to some kind of prerun check instead of relying on
user attentiveness. Since the test numbers update only when they are
completed and hung test can be spotted.

Performance examples:
Master:
./piglit-run.py -c tets/quick.tests foo 1065.64s user 373.48s system
295% cpu 8:06.35 total

With this patch:
./piglit-run.py -c tests/quick.tests foo  1045.33s user 353.20s system
315% cpu 7:23.56 total

Signed-off-by: Dylan Baker <baker.dylan.c at gmail.com>
---
 framework/core.py     | 23 +++++++------
 framework/log.py      | 78 +++++++++++++++++++++++++++++---------------
 framework/patterns.py | 90 ---------------------------------------------------
 3 files changed, 63 insertions(+), 128 deletions(-)
 delete mode 100644 framework/patterns.py

diff --git a/framework/core.py b/framework/core.py
index 8bcda5b..aa89584 100644
--- a/framework/core.py
+++ b/framework/core.py
@@ -32,7 +32,7 @@ import string
 import sys
 import time
 import traceback
-from log import log
+from log import Log
 from cStringIO import StringIO
 from textwrap import dedent
 from threads import synchronized_self
@@ -464,7 +464,7 @@ class Test:
     def run(self):
         raise NotImplementedError
 
-    def execute(self, env, path, json_writer):
+    def execute(self, env, path, log, json_writer):
         '''
         Run the test.
 
@@ -472,13 +472,12 @@ class Test:
             Fully qualified test name as a string.  For example,
             ``spec/glsl-1.30/preprocessor/compiler/keywords/void.frag``.
         '''
-        def status(msg):
-            log(msg=msg, channel=path)
 
+        log_current = log.get_current()
         # Run the test
         if env.execute:
             try:
-                status("running")
+                log.log()
                 time_start = time.time()
                 result = self.run(env)
                 time_end = time.time()
@@ -499,8 +498,6 @@ class Test:
                 result['traceback'] = \
                     "".join(traceback.format_tb(sys.exc_info()[2]))
 
-            status(result['result'])
-
             if 'subtest' in result and len(result['subtest'].keys()) > 1:
                 for test in result['subtest'].keys():
                     result['result'] = result['subtest'][test]
@@ -508,7 +505,8 @@ class Test:
             else:
                 json_writer.write_dict_item(path, result)
         else:
-            status("dry-run")
+            log.log()
+        log.mark_complete(log_current)
 
 
 class Group(dict):
@@ -565,6 +563,7 @@ class TestProfile:
         '''
 
         self.prepare_test_list(env)
+        log = Log(len(self.test_list))
 
         # If concurrency is set to 'all' run all tests out of a concurrent
         # threadpool, if it's none, then run evey test serially. otherwise mix
@@ -572,24 +571,24 @@ class TestProfile:
         if env.concurrent == "all":
             pool = ThreadPool(multiprocessing.cpu_count())
             for (path, test) in self.test_list.items():
-                pool.add(test.execute, (env, path, json_writer))
+                pool.add(test.execute, (env, path, log, json_writer))
             pool.join()
         elif env.concurrent == "none":
             pool = ThreadPool(1)
             for (path, test) in self.test_list.items():
-                pool.add(test.execute, (env, path, json_writer))
+                pool.add(test.execute, (env, path, log, json_writer))
             pool.join()
         else:
             pool = ThreadPool(multiprocessing.cpu_count())
             for (path, test) in self.test_list.items():
                 if test.runConcurrent:
-                    pool.add(test.execute, (env, path, json_writer))
+                    pool.add(test.execute, (env, path, log, json_writer))
             pool.join()
 
             pool = ThreadPool(1)
             for (path, test) in self.test_list.items():
                 if not test.runConcurrent:
-                    pool.add(test.execute, (env, path, json_writer))
+                    pool.add(test.execute, (env, path, log, json_writer))
             pool.join()
 
     def remove_test(self, test_path):
diff --git a/framework/log.py b/framework/log.py
index 310c552..167e4ea 100644
--- a/framework/log.py
+++ b/framework/log.py
@@ -1,5 +1,4 @@
-#
-# Copyright (c) 2010 Intel Corporation
+# Copyright (c) 2013 Intel Corporation
 #
 # Permission is hereby granted, free of charge, to any person obtaining a
 # copy of this software and associated documentation files (the "Software"),
@@ -21,33 +20,60 @@
 # IN THE SOFTWARE.
 #
 
-import logging
-
+import sys
 from threads import synchronized_self
-from patterns import Singleton
 
 
-class Logger(Singleton):
+class Log(object):
+    """ Print Progress to stdout
+
+    Arguments:
+    total -- The total number of tests to run.
+
+    """
+    def __init__(self, total):
+        self.__total = total
+        self.__complete = 1
+        self.__running = []
+        self.__generator = (x for x in xrange(self.__total))
+        self.__pad = len(str(self.__total))
+
+    def _running(self):
+        return "Running Test(s): {}".format(
+            " ".join([str(x).zfill(self.__pad) for x in self.__running]))
+
+    def _percent(self):
+        return "[{0}/{1}]".format(str(self.__complete).zfill(self.__pad),
+                                  str(self.__total).zfill(self.__pad))
+
+    @synchronized_self
+    def mark_complete(self, value):
+        """ Used to mark a test as complete in the log
+
+        Arguments:
+        value -- the test number to mark complete
+        
+        """
+        # Mark running complete
+        assert value in self.__running
+        self.__running.remove(value)
+
+        # increment the number of completed tests
+        self.__complete += 1
+
     @synchronized_self
-    def __logMessage(self, logfunc, message, **kwargs):
-        [logfunc(line, **kwargs) for line in message.split('\n')]
+    def log(self):
+        """ Print to the screen 
+
+        Works by moving the cursor back to the front of the line and printing
+        over it.
+        
+        """
+        sys.stdout.write("{0} {1} \r".format(self._percent(), self._running()))
 
     @synchronized_self
-    def getLogger(self, channel=None):
-        if 0 == len(logging.root.handlers):
-            logging.basicConfig(format="[%(asctime)s] :: %(message)+8s "
-                                       ":: %(name)s",
-                                datefmt="%c",
-                                level=logging.INFO)
-        if channel is None:
-            channel = "base"
-        logger = logging.getLogger(channel)
-        return logger
-
-    def log(self, type=logging.INFO, msg="", channel=None):
-        self.__logMessage(lambda m,
-                          **kwargs: self.getLogger(channel).log(type,
-                                                                m,
-                                                                **kwargs), msg)
-
-log = Logger().log
+    def get_current(self):
+        """ Returns a new number to know what processes are running """
+        x = self.__generator.next()
+        self.__running.append(x)
+        return x
diff --git a/framework/patterns.py b/framework/patterns.py
deleted file mode 100644
index bcf4e7e..0000000
--- a/framework/patterns.py
+++ /dev/null
@@ -1,90 +0,0 @@
-#
-# Copyright (c) 2010 Intel Corporation
-#
-# Permission is hereby granted, free of charge, to any person obtaining a
-# copy of this software and associated documentation files (the "Software"),
-# to deal in the Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish, distribute, sublicense,
-# and/or sell copies of the Software, and to permit persons to whom the
-# Software is furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice (including the next
-# paragraph) shall be included in all copies or substantial portions of the
-# Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
-# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-# IN THE SOFTWARE.
-#
-
-import threading
-
-
-class Singleton(object):
-    '''
-    Modeled after
-    http://www.python.org/download/releases/2.2.3/descrintro/*__new__
-
-    A thread-safe (mostly -- see NOTE) Singleton class pattern.
-
-    NOTE: deleting a singleton instance (i.e. Singleton::delInstance) does not
-    guarantee that nothing else is currently using it. To reduce this risk, a
-    program should not hold a reference to the instance.  Rather, use the
-    create/construct syntax (see example below) to access the instance.  Yet,
-    this still does not guarantee that this type of usage will result in a
-    desired effect in a multithreaded program.
-    You've been warned so use the singleton pattern wisely!
-
-    Example:
-
-    class MySingletonClass(Singleton):
-            def init(self):
-                    print "in MySingletonClass::init()", self
-
-            def foo(self):
-                    print "in MySingletonClass::foo()", self
-
-    MySingletonClass().foo()
-    MySingletonClass().foo()
-    MySingletonClass().foo()
-
-    ---> output will look something like this:
-    in MySingletonClass::init() <__main__.MySingletonClass object at 0x7ff5b322f3d0>
-    in MySingletonClass::foo() <__main__.MySingletonClass object at 0x7ff5b322f3d0>
-    in MySingletonClass::foo() <__main__.MySingletonClass object at 0x7ff5b322f3d0>
-    in MySingletonClass::foo() <__main__.MySingletonClass object at 0x7ff5b322f3d0>
-    '''
-
-    lock = threading.RLock()
-
-    def __new__(cls, *args, **kwargs):
-        try:
-            cls.lock.acquire()
-            it = cls.__dict__.get('__it__')
-            if it is not None:
-                return it
-            cls.__it__ = it = object.__new__(cls)
-            it.init(*args, **kwargs)
-            return it
-        finally:
-            cls.lock.release()
-
-    def init(self, *args, **kwargs):
-        '''
-        Derived classes should override this method to do its initializations
-        The derived class should not implement a '__init__' method.
-        '''
-        pass
-
-    @classmethod
-    def delInstance(cls):
-        cls.lock.acquire()
-        try:
-            if cls.__dict__.get('__it__') is not None:
-                del cls.__it__
-        finally:
-            cls.lock.release()
-- 
1.8.5.2



More information about the Piglit mailing list