[PATCH] Add profiling cmdline option

Brian C. Lane bcl at redhat.com
Fri Feb 14 18:17:57 UTC 2014


This adds a --profile (or inst.profile) command that uses the cProfile
module to gather information about Anaconda at runtime. Thread profiles
are dumped in /tmp/thread-*.prof with a unique filename using the thread
id and a counter for duplicates. The id can be matched to the id in
anaconda.log (note that the id can be reused, so count the number of
ThreadDone entries).

Sending a USR2 will include a profile dump in /tmp/anaconda.prof
---
 anaconda                  | 12 ++++++++++++
 data/anaconda_options.txt |  7 +++++++
 pyanaconda/anaconda.py    |  9 +++++++++
 pyanaconda/flags.py       |  1 +
 pyanaconda/threads.py     | 19 ++++++++++++++++++-
 5 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/anaconda b/anaconda
index 994497f..fde89fe 100755
--- a/anaconda
+++ b/anaconda
@@ -42,6 +42,9 @@ def exitHandler(rebootData, storage, exitCode=None):
     if exitCode:
         anaconda.intf.shutdown()
 
+    if anaconda.profiler:
+        anaconda.dumpProfile()
+
     if "nokill" in flags.cmdline:
         iutil.vtActivate(1)
         print "anaconda halting due to nokill flag."
@@ -314,6 +317,8 @@ def parseOptions(argv=None, cmdline=None):
     op.add_option("--extlinux", action="store_true", default=False)
     op.add_option("--dnf", action="store_true", default=False)
     op.add_option("--mpathfriendlynames", action="store_true", default=True)
+    op.add_option("--profile", action="store_true", default=False,
+                  help=help_parser.help_text("profile"))
 
     # some defaults change based on cmdline flags
     if cmdline is not None:
@@ -814,6 +819,13 @@ if __name__ == "__main__":
 
     anaconda.opts = opts
 
+    if opts.profile or flags.profile:
+        flags.profile = True
+        import cProfile
+        anaconda.profiler = cProfile.Profile()
+        anaconda.profiler.enable()
+        log.debug("profiling enabled")
+
     # check memory, just the text mode for now:
     check_memory(anaconda, opts, 't')
 
diff --git a/data/anaconda_options.txt b/data/anaconda_options.txt
index 6dc90c4..2772256 100644
--- a/data/anaconda_options.txt
+++ b/data/anaconda_options.txt
@@ -15,3 +15,10 @@ This option may be used multiple times to specify multiple disk
 images. It is an error to specify the same <path> twice or to use
 duplicate names. The --image and --dirinstall options are
 mutually exclusive.
+
+profile
+Use Python's cProfile module to profile Anaconda. Stats will be
+dumped to /tmp/anaconda.prof when SIGUSR2 is sent to the process.
+Thread profiles will be dumped to /tmp/thread*prof files. Note
+that thread ID may be reused so a counter is added to keep them
+unique.
diff --git a/pyanaconda/anaconda.py b/pyanaconda/anaconda.py
index 88a9a4d..ccea2cf 100644
--- a/pyanaconda/anaconda.py
+++ b/pyanaconda/anaconda.py
@@ -72,6 +72,7 @@ class Anaconda(object):
         self._storage = None
         self.updateSrc = None
         self.mehConfig = None
+        self.profiler = None
 
         # *sigh* we still need to be able to write this out
         self.xdriver = None
@@ -156,12 +157,20 @@ class Anaconda(object):
 
         return self._storage
 
+    def dumpProfile(self):
+        if self.profiler:
+            self.profiler.create_stats()
+            self.profiler.dump_stats("/tmp/anaconda.prof")
+            self.profiler.enable()
+
     def dumpState(self):
         from meh import ExceptionInfo
         from meh.dump import ReverseExceptionDump
         from inspect import stack as _stack
         from traceback import format_stack
 
+        self.dumpProfile()
+
         # Skip the frames for dumpState and the signal handler.
         stack = _stack()[2:]
         stack.reverse()
diff --git a/pyanaconda/flags.py b/pyanaconda/flags.py
index e81972d..b31eb2e 100644
--- a/pyanaconda/flags.py
+++ b/pyanaconda/flags.py
@@ -69,6 +69,7 @@ class Flags(object):
         self.testing = False
         self.dnf = False
         self.mpathFriendlyNames = True
+        self.profile = False
         # ksprompt is whether or not to prompt for missing ksdata
         self.ksprompt = True
         # parse the boot commandline
diff --git a/pyanaconda/threads.py b/pyanaconda/threads.py
index 5b7a80b..394ac3e 100644
--- a/pyanaconda/threads.py
+++ b/pyanaconda/threads.py
@@ -19,6 +19,9 @@
 #
 # Author(s):  Chris Lumens <clumens at redhat.com>
 #
+import os
+
+from pyanaconda.flags import flags
 import logging
 log = logging.getLogger("anaconda")
 
@@ -223,13 +226,27 @@ class AnacondaThread(threading.Thread):
 
         log.info("Running Thread: %s (%s)", self.name, self.ident)
         try:
-            threading.Thread.run(self, *args, **kwargs)
+            if not flags.profile:
+                threading.Thread.run(self, *args, **kwargs)
+            else:
+                import cProfile
+                profiler = cProfile.Profile()
+                profiler.runcall(threading.Thread.run, self, *args, **kwargs)
         # pylint: disable-msg=W0702
         except:
             threadMgr.set_error(self.name, *sys.exc_info())
             if self._fatal:
                 sys.excepthook(*sys.exc_info())
         finally:
+            if flags.profile:
+                # ident could be recycled, make a unique filename
+                dump_template = "/tmp/thread-%d-%d.prof"
+                for i in xrange(0, 99999):
+                    dump_path = dump_template % (self.ident, i)
+                    if not os.path.exists(dump_path):
+                        break
+                profiler.dump_stats(dump_path)
+
             threadMgr.remove(self.name)
             log.info("Thread Done: %s (%s)", self.name, self.ident)
 
-- 
1.8.5.3



More information about the anaconda-patches mailing list