[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