home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / C / Applications / Python 1.3.3 / Python 133 PPC / Lib / pstats.py < prev    next >
Encoding:
Text File  |  1996-05-19  |  16.0 KB  |  395 lines  |  [TEXT/Pyth]

  1. #
  2. # Class for printing reports on profiled python code. rev 1.0  4/1/94
  3. #
  4. # Based on prior profile module by Sjoerd Mullender...
  5. #   which was hacked somewhat by: Guido van Rossum
  6. #
  7. # see jprofile.doc and jprofile.py for more info.
  8.  
  9. # Copyright 1994, by InfoSeek Corporation, all rights reserved.
  10. # Written by James Roskind
  11. # Permission to use, copy, modify, and distribute this Python software
  12. # and its associated documentation for any purpose (subject to the
  13. # restriction in the following sentence) without fee is hereby granted,
  14. # provided that the above copyright notice appears in all copies, and
  15. # that both that copyright notice and this permission notice appear in
  16. # supporting documentation, and that the name of InfoSeek not be used in
  17. # advertising or publicity pertaining to distribution of the software
  18. # without specific, written prior permission.  This permission is
  19. # explicitly restricted to the copying and modification of the software
  20. # to remain in Python, compiled Python, or other languages (such as C)
  21. # wherein the modified or derived code is exclusively imported into a
  22. # Python module.
  23. # INFOSEEK CORPORATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
  24. # SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  25. # FITNESS. IN NO EVENT SHALL INFOSEEK CORPORATION BE LIABLE FOR ANY
  26. # SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  27. # RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
  28. # CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  29. # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  30.  
  31.  
  32. import os
  33. import time
  34. import string
  35. import marshal
  36. import regex
  37.  
  38. #**************************************************************************
  39. # Class Stats documentation
  40. #**************************************************************************
  41. # This class is used for creating reports from data generated by the
  42. # Profile class.  It is a "friend" of that class, and imports data either
  43. # by direct access to members of Profile class, or by reading in a dictionary
  44. # that was emitted (via marshal) from the Profile class.
  45. #
  46. # The big change from the previous Profiler (in terms of raw functionality)
  47. # is that an "add()" method has been provided to combine Stats from
  48. # several distinct profile runs.  Both the constructor and the add()
  49. # method now take arbitrarilly many file names as arguments.
  50. #
  51. # All the print methods now take an argument that indicats how many lines
  52. # to print.  If the arg is a floating point number between 0 and 1.0, then
  53. # it is taken as a decimal percentage of the availabel lines to be printed
  54. # (e.g., .1 means print 10% of all available lines).  If it is an integer,
  55. # it is taken to mean the number of lines of data that you wish to have
  56. # printed.
  57. #
  58. # The sort_stats() method now processes some additionaly options (i.e., in
  59. # addition to the old -1, 0, 1, or 2).  It takes an arbitrary number of quoted
  60. # strings to select the sort order.  For example sort_stats('time', 'name')
  61. # sorts on the major key of "internal function time", and on the minor
  62. # key of 'the name of the function'.  Look at the two tables in sort_stats()
  63. # and get_sort_arg_defs(self) for more examples.
  64. #
  65. # All methods now return "self",  so you can string together commands like:
  66. #    Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
  67. #                               print_stats(5).print_callers(5)
  68. #**************************************************************************
  69. import fpformat
  70.  
  71. class Stats:
  72.     def __init__(self, *args):
  73.         if not len(args):
  74.             arg = None
  75.         else:
  76.             arg = args[0]
  77.             args = args[1:]
  78.         self.init(arg)
  79.         apply(self.add, args).ignore()
  80.             
  81.     def init(self, arg):
  82.         self.all_callees = None  # calc only if needed
  83.         self.files = []
  84.         self.fcn_list = None
  85.         self.total_tt = 0
  86.         self.total_calls = 0
  87.         self.prim_calls = 0
  88.         self.max_name_len = 0
  89.         self.top_level = {}
  90.         self.stats = {}
  91.         self.sort_arg_dict = {}
  92.         self.load_stats(arg)
  93.         trouble = 1
  94.         try:
  95.             self.get_top_level_stats()
  96.             trouble = 0
  97.         finally:
  98.             if trouble:
  99.                 print "Invalid timing data",
  100.                 if self.files: print self.files[-1],
  101.                 print
  102.  
  103.  
  104.     def load_stats(self, arg):
  105.         if not arg:  self.stats = {}
  106.         elif type(arg) == type(""):
  107.             f = open(arg, 'r')
  108.             self.stats = marshal.load(f)
  109.             f.close()
  110.             try:
  111.                 file_stats = os.stat(arg)
  112.                 arg = time.ctime(file_stats[8]) + "    " + arg
  113.             except:  # in case this is not unix
  114.                 pass
  115.             self.files = [ arg ]
  116.         elif hasattr(arg, 'create_stats'):
  117.             arg.create_stats()
  118.             self.stats = arg.stats
  119.             arg.stats = {}
  120.         if not self.stats:
  121.             raise TypeError,  "Cannot create or construct a " \
  122.                   + `self.__class__` \
  123.                   + " object from '" + `arg` + "'"
  124.         return
  125.  
  126.     def get_top_level_stats(self):
  127.         for func in self.stats.keys():
  128.             cc, nc, tt, ct, callers = self.stats[func]
  129.             self.total_calls = self.total_calls + nc
  130.             self.prim_calls  = self.prim_calls  + cc
  131.             self.total_tt    = self.total_tt    + tt
  132.             if callers.has_key(("jprofile", 0, "profiler")):
  133.                 self.top_level[func] = None
  134.             if len(func_std_string(func)) > self.max_name_len:
  135.                 self.max_name_len = len(func_std_string(func))
  136.                     
  137.     def add(self, *arg_list):
  138.         if not arg_list: return self
  139.         if len(arg_list) > 1: apply(self.add, arg_list[1:])
  140.         other = arg_list[0]
  141.         if type(self) != type(other) or \
  142.               self.__class__ != other.__class__:
  143.             other = Stats(other)
  144.         self.files = self.files + other.files
  145.         self.total_calls = self.total_calls + other.total_calls
  146.         self.prim_calls = self.prim_calls + other.prim_calls
  147.         self.total_tt = self.total_tt + other.total_tt
  148.         for func in other.top_level.keys():
  149.             self.top_level[func] = None
  150.  
  151.         if self.max_name_len < other.max_name_len:
  152.             self.max_name_len = other.max_name_len
  153.  
  154.         self.fcn_list = None
  155.  
  156.         for func in other.stats.keys():
  157.             if self.stats.has_key(func):
  158.                 old_func_stat = self.stats[func]
  159.             else:
  160.                 old_func_stat = (0, 0, 0, 0, {},)
  161.             self.stats[func] = add_func_stats(old_func_stat, \
  162.                   other.stats[func])
  163.         return self
  164.             
  165.  
  166.  
  167.     # list the tuple indicies and directions for sorting,
  168.     # along with some printable description
  169.     sort_arg_dict_default (sel) == type(1) and 0 <= sel < count:
  170.                 count = sel
  171.                 new_list = list[:count]
  172.         if len(list) != len(new_list):
  173.             msg = msg + "   List reduced from " + `len(list)` \
  174.                   + " to " + `len(new_list)` + \
  175.                   " due to restriction <" + `sel` + ">\n"
  176.             
  177.         return new_list, msg
  178.  
  179.  
  180.  
  181.     def get_print_list(self, sel_list):
  182.         width = self.max_name_len
  183.         if self.fcn_list:
  184.             list = self.fcn_list[:]
  185.             msg = "   Ordered by: " + self.sort_type + '\n'
  186.         else:
  187.             list = self.stats.keys()
  188.             msg = "   Random listing order was used\n"
  189.  
  190.         for selection in sel_list:
  191.             list,msg = self.eval_print_amount(selection, list, msg)
  192.  
  193.         count = len(list)
  194.  
  195.         if not list:
  196.             return 0, list
  197.         print msg
  198.         if count < len(self.stats):
  199.             width = 0
  200.             for func in list:
  201.                 if  len(func_std_string(func)) > width:
  202.                     width = len(func_std_string(func))
  203.         return width+2, list
  204.         
  205.     def print_stats(self, *amount):
  206.         for filename in self.files:
  207.             print filename
  208.         if self.files: print
  209.         indent = "        "
  210.         for func in self.top_level.keys():
  211.             print indent, func_get_function_name(func)
  212.         
  213.         print  indent, self.total_calls, "function calls",
  214.         if self.total_calls != self.prim_calls:
  215.             print "(" + `self.prim_calls`, "primitive calls)", 
  216.         print "in", fpformat.fix(self.total_tt, 3), "CPU seconds"
  217.         print
  218.         width, list = self.get_print_list(amount)
  219.         if list:
  220.             self.print_title()
  221.             for func in list:
  222.                 self.print_line(func)
  223.             print 
  224.             print
  225.         return self
  226.  
  227.             
  228.     def print_callees(self, *amount):
  229.         width, list = self.get_print_list(amount)
  230.         if list:
  231.             self.calc_callees()
  232.  
  233.             self.print_call_heading(width, "called...")
  234.             for func in list:
  235.                 if self.all_callees.has_key(func):
  236.                     self.print_call_line(width, \
  237.                           func, self.all_callees[func])
  238.                 else:
  239.                     self.print_call_line(width, func, {})
  240.             print
  241.             print
  242.         return self
  243.  
  244.     def print_callers(self, *amount):
  245.         width, list = self.get_print_list(amount)
  246.         if list:
  247.             self.print_call_heading(width, "was called by...")
  248.             for func in list:
  249.                 cc, nc, tt, ct, callers = self.stats[func]
  250.                 self.print_call_line(width, func, callers)
  251.             print
  252.             print
  253.         return self
  254.  
  255.     def print_call_heading(self, name_size, column_title):
  256.         print string.ljust("Function ", name_size) + column_title
  257.  
  258.  
  259.     def print_call_line(self, name_size, source, call_dict):
  260.         print string.ljust(func_std_string(source), name_size),
  261.         if not call_dict:
  262.             print "--"
  263.             return
  264.         clist = call_dict.keys()
  265.         clist.sort()
  266.         name_size = name_size + 1
  267.         indent = ""
  268.         for func in clist:
  269.             name = func_std_string(func)
  270.             print indent*name_size + name + '(' \
  271.                   + `call_dict[func]`+')', \
  272.                   f8(self.stats[func][3])
  273.             indent = " "
  274.  
  275.  
  276.  
  277.     def print_title(self):
  278.         print string.rjust('ncalls', 9),
  279.         print string.rjust('tottime', 8),
  280.         print string.rjust('percall', 8),
  281.         print string.rjust('cumtime', 8),
  282.         print string.rjust('percall', 8),
  283.         print 'filename:lineno(function)'
  284.  
  285.  
  286.     def print_line(self, func):  # hack : should print percentages
  287.         cc, nc, tt, ct, callers = self.stats[func]
  288.         c = `nc`
  289.         if nc != cc:
  290.             c = c + '/' + `cc`
  291.         print string.rjust(c, 9),
  292.         print f8(tt),
  293.         if nc == 0:
  294.             print ' '*8,
  295.         else:
  296.             print f8(tt/nc),
  297.         print f8(ct),
  298.         if cc == 0:
  299.             print ' '*8,
  300.         else:
  301.             print f8(ct/cc),
  302.         print func_std_string(func)
  303.  
  304.  
  305.     def ignore(self):
  306.         pass # has no return value, so use at end of line :-)
  307.  
  308.  
  309. #**************************************************************************
  310. # class TupleComp Documentation
  311. #**************************************************************************
  312. # This class provides a generic function for comparing any two tuples.
  313. # Each instance records a list of tuple-indicies (from most significant
  314. # to least significant), and sort direction (ascending or decending) for
  315. # each tuple-index.  The compare functions can then be used as the function
  316. # argument to the system sort() function when a list of tuples need to be
  317. # sorted in the instances order.
  318. #**************************************************************************
  319. class TupleComp:
  320.     def __init__(self, comp_select_list):
  321.         self.comp_select_list = comp_select_list
  322.  
  323.     def compare (self, left, right):
  324.         for index, direction in self.comp_select_list:
  325.             l = left[index]
  326.             r = right[index]
  327.             if l < r:
  328.                 return -direction
  329.             if l > r:
  330.                 return direction
  331.         return 0
  332.  
  333.         
  334.  
  335. #**************************************************************************
  336.  
  337. def func_strip_path(func_name):
  338.     file, line, name = func_name
  339.      return os.path.basename(file), line, name
  340.  
  341. def func_get_function_name(func):
  342.     return func[2]
  343.  
  344. def func_std_string(func_name): # match what old profile produced
  345.     file, line, name = func_name
  346.     return file + ":" + `line` + "(" + name + ")"
  347.  
  348. def func_split(func_name):
  349.     return func_name
  350.  
  351. #**************************************************************************
  352. # The following functions combine statists for pairs functions.
  353. # The bulk of the processing involves correctly handling "call" lists,
  354. # such as callers and callees. 
  355. #**************************************************************************
  356.  
  357.     # Add together all the stats for two profile entries
  358. def add_func_stats(target, source):                
  359.     cc, nc, tt, ct, callers = source
  360.     t_cc, t_nc, t_tt, t_ct, t_callers = target
  361.     return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct, \
  362.           add_callers(t_callers, callers))
  363.  
  364.  
  365.     # Combine two caller lists in a single list.
  366. def add_callers(target, source):
  367.     new_callers = {}
  368.     for func in target.keys():
  369.         new_callers[func] = target[func]
  370.     for func in source.keys():
  371.         if new_callers.has_key(func):
  372.             new_callers[func] = source[func] + new_callers[func]
  373.         else:
  374.             new_callers[func] = source[func]
  375.     return new_callers
  376.  
  377.      # Sum the caller statistics to get total number of calls recieved
  378. def count_calls(callers):
  379.     nc = 0
  380.     for func in callers.keys():
  381.         nc = nc + callers[func]
  382.     return nc
  383.  
  384. #**************************************************************************
  385. # The following functions support printing of reports
  386. #**************************************************************************
  387.  
  388. def f8(x):
  389.     return string.rjust(fpformat.fix(x, 3), 8)
  390.  
  391.  
  392.