Author: rob
Date: 2008-12-12 14:47:29 -0500 (Fri, 12 Dec 2008)
New Revision: 21768

Added:
   scripts/listen_local.py
   scripts/pyprof2calltree.py
Log:
add listen_local script (for testing mail delivery to listen w/o setting up
an MTA) and pyprof2calltree (for converting python's hotshot profiler output
into something that can be used by kcachegrind)


Added: scripts/listen_local.py
===================================================================
--- scripts/listen_local.py                             (rev 0)
+++ scripts/listen_local.py     2008-12-12 19:47:29 UTC (rev 21768)
@@ -0,0 +1,14 @@
+import sys
+from urllib import quote
+file_name = sys.argv[1]
+print file_name
+f = open(file_name)
+data = f.read()
+f.close()
+quoted_data = quote(data)
+f = open('txt_quoted.msg', 'w')
+f.write('Mail=' + quoted_data)
+f.close()
+
+# then:
+print 'wget --post-file=txt_quoted.msg 
http://localhost:PORT/openplans/send_listen_mail'


Property changes on: scripts/listen_local.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: scripts/pyprof2calltree.py
===================================================================
--- scripts/pyprof2calltree.py                          (rev 0)
+++ scripts/pyprof2calltree.py  2008-12-12 19:47:29 UTC (rev 21768)
@@ -0,0 +1,283 @@
+# Copyright (c) 2006-2008, David Allouche, Jp Calderone, Itamar 
Shtull-Trauring,
+# Johan Dahlin, Olivier Grisel <[email protected]>
+#
+# All rights reserved.
+#
+# 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 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.
+"""pyprof2calltree: profiling output which is readable by kcachegrind
+
+This script can either take raw cProfile.Profile.getstats() log entries or
+take a previously recorded instance of the pstats.Stats class.
+"""
+
+import cProfile
+import pstats
+import optparse
+import os
+import sys
+import tempfile
+
+__all__ = ['convert', 'visualize', 'CalltreeConverter']
+
+class Code(object):
+    pass
+
+class Entry(object):
+    pass
+
+def pstats2entries(data):
+    """Helper to convert serialized pstats back to a list of raw entries
+
+    Converse opperation of cProfile.Profile.snapshot_stats()
+    """
+    entries = dict()
+    allcallers = dict()
+
+    # first pass over stats to build the list of entry instances
+    for code_info, call_info in data.stats.items():
+        # build a fake code object
+        code = Code()
+        code.co_filename, code.co_firstlineno, code.co_name = code_info
+
+        # build a fake entry object
+        cc, nc, tt, ct, callers = call_info
+        entry = Entry()
+        entry.code = code
+        entry.callcount = cc
+        entry.reccallcount = nc - cc
+        entry.inlinetime = tt
+        entry.totaltime = ct
+
+        # to be filled during the second pass over stats
+        entry.calls = list()
+
+        # collect the new entry
+        entries[code_info] = entry
+        allcallers[code_info] = callers.items()
+
+    # second pass of stats to plug callees into callers
+    for entry in entries.itervalues():
+        entry_label = cProfile.label(entry.code)
+        entry_callers = allcallers.get(entry_label, [])
+        for entry_caller, call_info in entry_callers:
+            entries[entry_caller].calls.append((entry, call_info))
+
+    return entries.values()
+
+class CalltreeConverter(object):
+    """Convert raw cProfile or pstats data to the calltree format"""
+
+    kcachegrind_command = "kcachegrind %s"
+
+    def __init__(self, profiling_data):
+        if isinstance(profiling_data, basestring):
+            # treat profiling_data as a filename of pstats serialized data
+            self.entries = pstats2entries(pstats.Stats(profiling_data))
+        elif isinstance(profiling_data, pstats.Stats):
+            # convert pstats data to cProfile list of entries
+            self.entries = pstats2entries(profiling_data)
+        else:
+            # assume this are direct cProfile entries
+            self.entries = profiling_data
+        self.out_file = None
+
+    def output(self, out_file):
+        """Write the converted entries to out_file"""
+        self.out_file = out_file
+        print >> out_file, 'events: Ticks'
+        self._print_summary()
+        for entry in self.entries:
+            self._entry(entry)
+
+    def visualize(self):
+        """Launch kcachegrind on the converted entries
+
+        kcachegrind must be present in the system path
+        """
+
+        if self.out_file is None:
+            _, outfile = tempfile.mkstemp(".log", "pyprof2calltree")
+            f = file(outfile, "wb")
+            self.output(f)
+            use_temp_file = True
+        else:
+            use_temp_file = False
+
+        try:
+            os.system(self.kcachegrind_command % self.out_file.name)
+        finally:
+            # clean the temporary file
+            if use_temp_file:
+                f.close()
+                os.remove(outfile)
+                self.out_file = None
+
+    def _print_summary(self):
+        max_cost = 0
+        for entry in self.entries:
+            totaltime = int(entry.totaltime * 1000)
+            max_cost = max(max_cost, totaltime)
+        print >> self.out_file, 'summary: %d' % (max_cost,)
+
+    def _entry(self, entry):
+        out_file = self.out_file
+
+        code = entry.code
+        #print >> out_file, 'ob=%s' % (code.co_filename,)
+
+        co_filename, co_firstlineno, co_name = cProfile.label(code)
+        print >> out_file, 'fi=%s' % (co_filename,)
+        print >> out_file, 'fn=%s %s:%d' % (
+            co_name, co_filename, co_firstlineno)
+
+        inlinetime = int(entry.inlinetime * 1000)
+        if isinstance(code, str):
+            print >> out_file, '0 ', inlinetime
+        else:
+            print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime)
+
+        # recursive calls are counted in entry.calls
+        if entry.calls:
+            calls = entry.calls
+        else:
+            calls = []
+
+        if isinstance(code, str):
+            lineno = 0
+        else:
+            lineno = code.co_firstlineno
+
+        for subentry, call_info in calls:
+            self._subentry(lineno, subentry, call_info)
+        print >> out_file
+
+    def _subentry(self, lineno, subentry, call_info):
+        out_file = self.out_file
+        code = subentry.code
+        #print >> out_file, 'cob=%s' % (code.co_filename,)
+        co_filename, co_firstlineno, co_name = cProfile.label(code)
+        print >> out_file, 'cfn=%s %s:%d' % (
+            co_name, co_filename, co_firstlineno)
+        print >> out_file, 'cfi=%s' % (co_filename,)
+        print >> out_file, 'calls=%d %d' % (call_info[0], co_firstlineno)
+
+        totaltime = int(call_info[3] * 1000)
+        print >> out_file, '%d %d' % (lineno, totaltime)
+
+def main():
+    """Execute the converter using parameters provided on the command line"""
+
+    usage = "%s [-k] [-o output_file_path] [-i input_file_path] [-r scriptfile 
[args]]"
+    parser = optparse.OptionParser(usage=usage % sys.argv[0])
+    parser.allow_interspersed_args = False
+    parser.add_option('-o', '--outfile', dest="outfile",
+                      help="Save calltree stats to <outfile>", default=None)
+    parser.add_option('-i', '--infile', dest="infile",
+                      help="Read python stats from <infile>", default=None)
+    parser.add_option('-r', '--run-script', dest="script",
+                      help="Name of the python script to run to collect"
+                      " profiling data", default=None)
+    parser.add_option('-k', '--kcachegrind', dest="kcachegrind",
+                      help="Run the kcachegrind tool on the converted data",
+                      action="store_true")
+    options, args = parser.parse_args()
+
+
+    outfile = options.outfile
+
+    if options.script is not None:
+        # collect profiling data by running the given script
+
+        sys.argv[:] = [options.script] + args
+        if not options.outfile:
+            outfile = '%s.log' % os.path.basename(options.script)
+
+        prof = cProfile.Profile()
+        try:
+            try:
+                prof = prof.run('execfile(%r)' % (sys.argv[0],))
+            except SystemExit:
+                pass
+        finally:
+            kg = CalltreeConverter(prof.getstats())
+
+    elif options.infile is not None:
+        # use the profiling data from some input file
+        if not options.outfile:
+            outfile = '%s.log' % os.path.basename(options.infile)
+
+        if options.infile == outfile:
+            # prevent name collisions by appending another extension
+            outfile += ".log"
+
+        kg = CalltreeConverter(pstats.Stats(options.infile))
+
+    else:
+        # at least an input file or a script to run is required
+        parser.print_usage()
+        sys.exit(2)
+
+    if options.outfile is not None or not options.kcachegrind:
+        # user either explicitely required output file or requested by not
+        # explicitely asking to launch kcachegrind
+        print "writing converted data to: " + outfile
+        kg.output(file(outfile, 'wb'))
+
+    if options.kcachegrind:
+        print "launching kcachegrind"
+        kg.visualize()
+
+
+def visualize(profiling_data):
+    """launch the kcachegrind on `profiling_data`
+
+    `profiling_data` can either be:
+        - a pstats.Stats instance
+        - the filename of a pstats.Stats dump
+        - the result of a call to cProfile.Profile.getstats()
+    """
+    converter = CalltreeConverter(profiling_data)
+    converter.visualize()
+
+def convert(profiling_data, outputfile):
+    """convert `profiling_data` to calltree format and dump it to `outputfile`
+
+    `profiling_data` can either be:
+        - a pstats.Stats instance
+        - the filename of a pstats.Stats dump
+        - the result of a call to cProfile.Profile.getstats()
+
+    `outputfile` can either be:
+        - a file() instance open in write mode
+        - a filename
+    """
+    converter = CalltreeConverter(profiling_data)
+    if isinstance(outputfile, basestring):
+        f = file(outputfile, "wb")
+        try:
+            converter.output(f)
+        finally:
+            f.close()
+    else:
+        converter.output(outputfile)
+
+
+if __name__ == '__main__':
+    sys.exit(main())
+


Property changes on: scripts/pyprof2calltree.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native



--
To unsubscribe send an email with subject "unsubscribe" to 
[email protected].  Please contact 
[email protected] for questions.

Reply via email to