PyXR

c:\python24\lib \ pstats.py



0001 """Class for printing reports on profiled python code."""
0002 
0003 # Class for printing reports on profiled python code. rev 1.0  4/1/94
0004 #
0005 # Based on prior profile module by Sjoerd Mullender...
0006 #   which was hacked somewhat by: Guido van Rossum
0007 #
0008 # see profile.doc and profile.py for more info.
0009 
0010 # Copyright 1994, by InfoSeek Corporation, all rights reserved.
0011 # Written by James Roskind
0012 #
0013 # Permission to use, copy, modify, and distribute this Python software
0014 # and its associated documentation for any purpose (subject to the
0015 # restriction in the following sentence) without fee is hereby granted,
0016 # provided that the above copyright notice appears in all copies, and
0017 # that both that copyright notice and this permission notice appear in
0018 # supporting documentation, and that the name of InfoSeek not be used in
0019 # advertising or publicity pertaining to distribution of the software
0020 # without specific, written prior permission.  This permission is
0021 # explicitly restricted to the copying and modification of the software
0022 # to remain in Python, compiled Python, or other languages (such as C)
0023 # wherein the modified or derived code is exclusively imported into a
0024 # Python module.
0025 #
0026 # INFOSEEK CORPORATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
0027 # SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
0028 # FITNESS. IN NO EVENT SHALL INFOSEEK CORPORATION BE LIABLE FOR ANY
0029 # SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
0030 # RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
0031 # CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
0032 # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
0033 
0034 
0035 import os
0036 import time
0037 import marshal
0038 import re
0039 
0040 __all__ = ["Stats"]
0041 
0042 class Stats:
0043     """This class is used for creating reports from data generated by the
0044     Profile class.  It is a "friend" of that class, and imports data either
0045     by direct access to members of Profile class, or by reading in a dictionary
0046     that was emitted (via marshal) from the Profile class.
0047 
0048     The big change from the previous Profiler (in terms of raw functionality)
0049     is that an "add()" method has been provided to combine Stats from
0050     several distinct profile runs.  Both the constructor and the add()
0051     method now take arbitrarily many file names as arguments.
0052 
0053     All the print methods now take an argument that indicates how many lines
0054     to print.  If the arg is a floating point number between 0 and 1.0, then
0055     it is taken as a decimal percentage of the available lines to be printed
0056     (e.g., .1 means print 10% of all available lines).  If it is an integer,
0057     it is taken to mean the number of lines of data that you wish to have
0058     printed.
0059 
0060     The sort_stats() method now processes some additional options (i.e., in
0061     addition to the old -1, 0, 1, or 2).  It takes an arbitrary number of quoted
0062     strings to select the sort order.  For example sort_stats('time', 'name')
0063     sorts on the major key of "internal function time", and on the minor
0064     key of 'the name of the function'.  Look at the two tables in sort_stats()
0065     and get_sort_arg_defs(self) for more examples.
0066 
0067     All methods now return "self",  so you can string together commands like:
0068         Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
0069                             print_stats(5).print_callers(5)
0070     """
0071 
0072     def __init__(self, *args):
0073         if not len(args):
0074             arg = None
0075         else:
0076             arg = args[0]
0077             args = args[1:]
0078         self.init(arg)
0079         self.add(*args)
0080 
0081     def init(self, arg):
0082         self.all_callees = None  # calc only if needed
0083         self.files = []
0084         self.fcn_list = None
0085         self.total_tt = 0
0086         self.total_calls = 0
0087         self.prim_calls = 0
0088         self.max_name_len = 0
0089         self.top_level = {}
0090         self.stats = {}
0091         self.sort_arg_dict = {}
0092         self.load_stats(arg)
0093         trouble = 1
0094         try:
0095             self.get_top_level_stats()
0096             trouble = 0
0097         finally:
0098             if trouble:
0099                 print "Invalid timing data",
0100                 if self.files: print self.files[-1],
0101                 print
0102 
0103     def load_stats(self, arg):
0104         if not arg:  self.stats = {}
0105         elif type(arg) == type(""):
0106             f = open(arg, 'rb')
0107             self.stats = marshal.load(f)
0108             f.close()
0109             try:
0110                 file_stats = os.stat(arg)
0111                 arg = time.ctime(file_stats.st_mtime) + "    " + arg
0112             except:  # in case this is not unix
0113                 pass
0114             self.files = [ arg ]
0115         elif hasattr(arg, 'create_stats'):
0116             arg.create_stats()
0117             self.stats = arg.stats
0118             arg.stats = {}
0119         if not self.stats:
0120             raise TypeError,  "Cannot create or construct a %r object from '%r''" % (
0121                               self.__class__, arg)
0122         return
0123 
0124     def get_top_level_stats(self):
0125         for func, (cc, nc, tt, ct, callers) in self.stats.items():
0126             self.total_calls += nc
0127             self.prim_calls  += cc
0128             self.total_tt    += tt
0129             if callers.has_key(("jprofile", 0, "profiler")):
0130                 self.top_level[func] = None
0131             if len(func_std_string(func)) > self.max_name_len:
0132                 self.max_name_len = len(func_std_string(func))
0133 
0134     def add(self, *arg_list):
0135         if not arg_list: return self
0136         if len(arg_list) > 1: self.add(*arg_list[1:])
0137         other = arg_list[0]
0138         if type(self) != type(other) or self.__class__ != other.__class__:
0139             other = Stats(other)
0140         self.files += other.files
0141         self.total_calls += other.total_calls
0142         self.prim_calls += other.prim_calls
0143         self.total_tt += other.total_tt
0144         for func in other.top_level:
0145             self.top_level[func] = None
0146 
0147         if self.max_name_len < other.max_name_len:
0148             self.max_name_len = other.max_name_len
0149 
0150         self.fcn_list = None
0151 
0152         for func, stat in other.stats.iteritems():
0153             if func in self.stats:
0154                 old_func_stat = self.stats[func]
0155             else:
0156                 old_func_stat = (0, 0, 0, 0, {},)
0157             self.stats[func] = add_func_stats(old_func_stat, stat)
0158         return self
0159 
0160     def dump_stats(self, filename):
0161         """Write the profile data to a file we know how to load back."""
0162         f = file(filename, 'wb')
0163         try:
0164             marshal.dump(self.stats, f)
0165         finally:
0166             f.close()
0167 
0168     # list the tuple indices and directions for sorting,
0169     # along with some printable description
0170     sort_arg_dict_default = {
0171               "calls"     : (((1,-1),              ), "call count"),
0172               "cumulative": (((3,-1),              ), "cumulative time"),
0173               "file"      : (((4, 1),              ), "file name"),
0174               "line"      : (((5, 1),              ), "line number"),
0175               "module"    : (((4, 1),              ), "file name"),
0176               "name"      : (((6, 1),              ), "function name"),
0177               "nfl"       : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
0178               "pcalls"    : (((0,-1),              ), "call count"),
0179               "stdname"   : (((7, 1),              ), "standard name"),
0180               "time"      : (((2,-1),              ), "internal time"),
0181               }
0182 
0183     def get_sort_arg_defs(self):
0184         """Expand all abbreviations that are unique."""
0185         if not self.sort_arg_dict:
0186             self.sort_arg_dict = dict = {}
0187             bad_list = {}
0188             for word, tup in self.sort_arg_dict_default.iteritems():
0189                 fragment = word
0190                 while fragment:
0191                     if not fragment:
0192                         break
0193                     if fragment in dict:
0194                         bad_list[fragment] = 0
0195                         break
0196                     dict[fragment] = tup
0197                     fragment = fragment[:-1]
0198             for word in bad_list:
0199                 del dict[word]
0200         return self.sort_arg_dict
0201 
0202     def sort_stats(self, *field):
0203         if not field:
0204             self.fcn_list = 0
0205             return self
0206         if len(field) == 1 and type(field[0]) == type(1):
0207             # Be compatible with old profiler
0208             field = [ {-1: "stdname",
0209                       0:"calls",
0210                       1:"time",
0211                       2: "cumulative" }  [ field[0] ] ]
0212 
0213         sort_arg_defs = self.get_sort_arg_defs()
0214         sort_tuple = ()
0215         self.sort_type = ""
0216         connector = ""
0217         for word in field:
0218             sort_tuple = sort_tuple + sort_arg_defs[word][0]
0219             self.sort_type += connector + sort_arg_defs[word][1]
0220             connector = ", "
0221 
0222         stats_list = []
0223         for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
0224             stats_list.append((cc, nc, tt, ct) + func +
0225                               (func_std_string(func), func))
0226 
0227         stats_list.sort(TupleComp(sort_tuple).compare)
0228 
0229         self.fcn_list = fcn_list = []
0230         for tuple in stats_list:
0231             fcn_list.append(tuple[-1])
0232         return self
0233 
0234     def reverse_order(self):
0235         if self.fcn_list:
0236             self.fcn_list.reverse()
0237         return self
0238 
0239     def strip_dirs(self):
0240         oldstats = self.stats
0241         self.stats = newstats = {}
0242         max_name_len = 0
0243         for func, (cc, nc, tt, ct, callers) in oldstats.iteritems():
0244             newfunc = func_strip_path(func)
0245             if len(func_std_string(newfunc)) > max_name_len:
0246                 max_name_len = len(func_std_string(newfunc))
0247             newcallers = {}
0248             for func2, caller in callers.iteritems():
0249                 newcallers[func_strip_path(func2)] = caller
0250 
0251             if newfunc in newstats:
0252                 newstats[newfunc] = add_func_stats(
0253                                         newstats[newfunc],
0254                                         (cc, nc, tt, ct, newcallers))
0255             else:
0256                 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
0257         old_top = self.top_level
0258         self.top_level = new_top = {}
0259         for func in old_top:
0260             new_top[func_strip_path(func)] = None
0261 
0262         self.max_name_len = max_name_len
0263 
0264         self.fcn_list = None
0265         self.all_callees = None
0266         return self
0267 
0268     def calc_callees(self):
0269         if self.all_callees: return
0270         self.all_callees = all_callees = {}
0271         for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
0272             if not func in all_callees:
0273                 all_callees[func] = {}
0274             for func2, caller in callers.iteritems():
0275                 if not func2 in all_callees:
0276                     all_callees[func2] = {}
0277                 all_callees[func2][func]  = caller
0278         return
0279 
0280     #******************************************************************
0281     # The following functions support actual printing of reports
0282     #******************************************************************
0283 
0284     # Optional "amount" is either a line count, or a percentage of lines.
0285 
0286     def eval_print_amount(self, sel, list, msg):
0287         new_list = list
0288         if type(sel) == type(""):
0289             new_list = []
0290             for func in list:
0291                 if re.search(sel, func_std_string(func)):
0292                     new_list.append(func)
0293         else:
0294             count = len(list)
0295             if type(sel) == type(1.0) and 0.0 <= sel < 1.0:
0296                 count = int(count * sel + .5)
0297                 new_list = list[:count]
0298             elif type(sel) == type(1) and 0 <= sel < count:
0299                 count = sel
0300                 new_list = list[:count]
0301         if len(list) != len(new_list):
0302             msg = msg + "   List reduced from %r to %r due to restriction <%r>\n" % (
0303                          len(list), len(new_list), sel)
0304 
0305         return new_list, msg
0306 
0307     def get_print_list(self, sel_list):
0308         width = self.max_name_len
0309         if self.fcn_list:
0310             list = self.fcn_list[:]
0311             msg = "   Ordered by: " + self.sort_type + '\n'
0312         else:
0313             list = self.stats.keys()
0314             msg = "   Random listing order was used\n"
0315 
0316         for selection in sel_list:
0317             list, msg = self.eval_print_amount(selection, list, msg)
0318 
0319         count = len(list)
0320 
0321         if not list:
0322             return 0, list
0323         print msg
0324         if count < len(self.stats):
0325             width = 0
0326             for func in list:
0327                 if  len(func_std_string(func)) > width:
0328                     width = len(func_std_string(func))
0329         return width+2, list
0330 
0331     def print_stats(self, *amount):
0332         for filename in self.files:
0333             print filename
0334         if self.files: print
0335         indent = ' ' * 8
0336         for func in self.top_level:
0337             print indent, func_get_function_name(func)
0338 
0339         print indent, self.total_calls, "function calls",
0340         if self.total_calls != self.prim_calls:
0341             print "(%d primitive calls)" % self.prim_calls,
0342         print "in %.3f CPU seconds" % self.total_tt
0343         print
0344         width, list = self.get_print_list(amount)
0345         if list:
0346             self.print_title()
0347             for func in list:
0348                 self.print_line(func)
0349             print
0350             print
0351         return self
0352 
0353     def print_callees(self, *amount):
0354         width, list = self.get_print_list(amount)
0355         if list:
0356             self.calc_callees()
0357 
0358             self.print_call_heading(width, "called...")
0359             for func in list:
0360                 if func in self.all_callees:
0361                     self.print_call_line(width, func, self.all_callees[func])
0362                 else:
0363                     self.print_call_line(width, func, {})
0364             print
0365             print
0366         return self
0367 
0368     def print_callers(self, *amount):
0369         width, list = self.get_print_list(amount)
0370         if list:
0371             self.print_call_heading(width, "was called by...")
0372             for func in list:
0373                 cc, nc, tt, ct, callers = self.stats[func]
0374                 self.print_call_line(width, func, callers)
0375             print
0376             print
0377         return self
0378 
0379     def print_call_heading(self, name_size, column_title):
0380         print "Function ".ljust(name_size) + column_title
0381 
0382     def print_call_line(self, name_size, source, call_dict):
0383         print func_std_string(source).ljust(name_size),
0384         if not call_dict:
0385             print "--"
0386             return
0387         clist = call_dict.keys()
0388         clist.sort()
0389         name_size = name_size + 1
0390         indent = ""
0391         for func in clist:
0392             name = func_std_string(func)
0393             print indent*name_size + name + '(%r)' % (call_dict[func],), \
0394                       f8(self.stats[func][3])
0395             indent = " "
0396 
0397     def print_title(self):
0398         print '   ncalls  tottime  percall  cumtime  percall', \
0399               'filename:lineno(function)'
0400 
0401     def print_line(self, func):  # hack : should print percentages
0402         cc, nc, tt, ct, callers = self.stats[func]
0403         c = str(nc)
0404         if nc != cc:
0405             c = c + '/' + str(cc)
0406         print c.rjust(9),
0407         print f8(tt),
0408         if nc == 0:
0409             print ' '*8,
0410         else:
0411             print f8(tt/nc),
0412         print f8(ct),
0413         if cc == 0:
0414             print ' '*8,
0415         else:
0416             print f8(ct/cc),
0417         print func_std_string(func)
0418 
0419     def ignore(self):
0420         # Deprecated since 1.5.1 -- see the docs.
0421         pass # has no return value, so use at end of line :-)
0422 
0423 class TupleComp:
0424     """This class provides a generic function for comparing any two tuples.
0425     Each instance records a list of tuple-indices (from most significant
0426     to least significant), and sort direction (ascending or decending) for
0427     each tuple-index.  The compare functions can then be used as the function
0428     argument to the system sort() function when a list of tuples need to be
0429     sorted in the instances order."""
0430 
0431     def __init__(self, comp_select_list):
0432         self.comp_select_list = comp_select_list
0433 
0434     def compare (self, left, right):
0435         for index, direction in self.comp_select_list:
0436             l = left[index]
0437             r = right[index]
0438             if l < r:
0439                 return -direction
0440             if l > r:
0441                 return direction
0442         return 0
0443 
0444 #**************************************************************************
0445 # func_name is a triple (file:string, line:int, name:string)
0446 
0447 def func_strip_path(func_name):
0448     filename, line, name = func_name
0449     return os.path.basename(filename), line, name
0450 
0451 def func_get_function_name(func):
0452     return func[2]
0453 
0454 def func_std_string(func_name): # match what old profile produced
0455     return "%s:%d(%s)" % func_name
0456 
0457 #**************************************************************************
0458 # The following functions combine statists for pairs functions.
0459 # The bulk of the processing involves correctly handling "call" lists,
0460 # such as callers and callees.
0461 #**************************************************************************
0462 
0463 def add_func_stats(target, source):
0464     """Add together all the stats for two profile entries."""
0465     cc, nc, tt, ct, callers = source
0466     t_cc, t_nc, t_tt, t_ct, t_callers = target
0467     return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
0468               add_callers(t_callers, callers))
0469 
0470 def add_callers(target, source):
0471     """Combine two caller lists in a single list."""
0472     new_callers = {}
0473     for func, caller in target.iteritems():
0474         new_callers[func] = caller
0475     for func, caller in source.iteritems():
0476         if func in new_callers:
0477             new_callers[func] = caller + new_callers[func]
0478         else:
0479             new_callers[func] = caller
0480     return new_callers
0481 
0482 def count_calls(callers):
0483     """Sum the caller statistics to get total number of calls received."""
0484     nc = 0
0485     for calls in callers.itervalues():
0486         nc += calls
0487     return nc
0488 
0489 #**************************************************************************
0490 # The following functions support printing of reports
0491 #**************************************************************************
0492 
0493 def f8(x):
0494     return "%8.3f" % x
0495 
0496 #**************************************************************************
0497 # Statistics browser added by ESR, April 2001
0498 #**************************************************************************
0499 
0500 if __name__ == '__main__':
0501     import cmd
0502     try:
0503         import readline
0504     except ImportError:
0505         pass
0506 
0507     class ProfileBrowser(cmd.Cmd):
0508         def __init__(self, profile=None):
0509             cmd.Cmd.__init__(self)
0510             self.prompt = "% "
0511             if profile is not None:
0512                 self.stats = Stats(profile)
0513             else:
0514                 self.stats = None
0515 
0516         def generic(self, fn, line):
0517             args = line.split()
0518             processed = []
0519             for term in args:
0520                 try:
0521                     processed.append(int(term))
0522                     continue
0523                 except ValueError:
0524                     pass
0525                 try:
0526                     frac = float(term)
0527                     if frac > 1 or frac < 0:
0528                         print "Fraction argument mus be in [0, 1]"
0529                         continue
0530                     processed.append(frac)
0531                     continue
0532                 except ValueError:
0533                     pass
0534                 processed.append(term)
0535             if self.stats:
0536                 getattr(self.stats, fn)(*processed)
0537             else:
0538                 print "No statistics object is loaded."
0539             return 0
0540         def generic_help(self):
0541             print "Arguments may be:"
0542             print "* An integer maximum number of entries to print."
0543             print "* A decimal fractional number between 0 and 1, controlling"
0544             print "  what fraction of selected entries to print."
0545             print "* A regular expression; only entries with function names"
0546             print "  that match it are printed."
0547 
0548         def do_add(self, line):
0549             self.stats.add(line)
0550             return 0
0551         def help_add(self):
0552             print "Add profile info from given file to current statistics object."
0553 
0554         def do_callees(self, line):
0555             return self.generic('print_callees', line)
0556         def help_callees(self):
0557             print "Print callees statistics from the current stat object."
0558             self.generic_help()
0559 
0560         def do_callers(self, line):
0561             return self.generic('print_callers', line)
0562         def help_callers(self):
0563             print "Print callers statistics from the current stat object."
0564             self.generic_help()
0565 
0566         def do_EOF(self, line):
0567             print ""
0568             return 1
0569         def help_EOF(self):
0570             print "Leave the profile brower."
0571 
0572         def do_quit(self, line):
0573             return 1
0574         def help_quit(self):
0575             print "Leave the profile brower."
0576 
0577         def do_read(self, line):
0578             if line:
0579                 try:
0580                     self.stats = Stats(line)
0581                 except IOError, args:
0582                     print args[1]
0583                     return
0584                 self.prompt = line + "% "
0585             elif len(self.prompt) > 2:
0586                 line = self.prompt[-2:]
0587             else:
0588                 print "No statistics object is current -- cannot reload."
0589             return 0
0590         def help_read(self):
0591             print "Read in profile data from a specified file."
0592 
0593         def do_reverse(self, line):
0594             self.stats.reverse_order()
0595             return 0
0596         def help_reverse(self):
0597             print "Reverse the sort order of the profiling report."
0598 
0599         def do_sort(self, line):
0600             abbrevs = self.stats.get_sort_arg_defs()
0601             if line and not filter(lambda x,a=abbrevs: x not in a,line.split()):
0602                 self.stats.sort_stats(*line.split())
0603             else:
0604                 print "Valid sort keys (unique prefixes are accepted):"
0605                 for (key, value) in Stats.sort_arg_dict_default.iteritems():
0606                     print "%s -- %s" % (key, value[1])
0607             return 0
0608         def help_sort(self):
0609             print "Sort profile data according to specified keys."
0610             print "(Typing `sort' without arguments lists valid keys.)"
0611         def complete_sort(self, text, *args):
0612             return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
0613 
0614         def do_stats(self, line):
0615             return self.generic('print_stats', line)
0616         def help_stats(self):
0617             print "Print statistics from the current stat object."
0618             self.generic_help()
0619 
0620         def do_strip(self, line):
0621             self.stats.strip_dirs()
0622             return 0
0623         def help_strip(self):
0624             print "Strip leading path information from filenames in the report."
0625 
0626         def postcmd(self, stop, line):
0627             if stop:
0628                 return stop
0629             return None
0630 
0631     import sys
0632     print "Welcome to the profile statistics browser."
0633     if len(sys.argv) > 1:
0634         initprofile = sys.argv[1]
0635     else:
0636         initprofile = None
0637     try:
0638         ProfileBrowser(initprofile).cmdloop()
0639         print "Goodbye."
0640     except KeyboardInterrupt:
0641         pass
0642 
0643 # That's all, folks.
0644 

Generated by PyXR 0.9.4
SourceForge.net Logo