home *** CD-ROM | disk | FTP | other *** search
/ linuxmafia.com 2016 / linuxmafia.com.tar / linuxmafia.com / pub / palmos / pippy-0.6beta-src.tar.gz / pippy-0.6beta-src.tar / pippy-0.6beta-src / src / Lib / profile.py < prev    next >
Text File  |  2000-12-21  |  18KB  |  572 lines

  1. #! /usr/bin/env python
  2. #
  3. # Class for profiling python code. rev 1.0  6/2/94
  4. #
  5. # Based on prior profile module by Sjoerd Mullender...
  6. #   which was hacked somewhat by: Guido van Rossum
  7. #
  8. # See profile.doc for more information
  9.  
  10. """Class for profiling Python code."""
  11.  
  12. # Copyright 1994, by InfoSeek Corporation, all rights reserved.
  13. # Written by James Roskind
  14. # Permission to use, copy, modify, and distribute this Python software
  15. # and its associated documentation for any purpose (subject to the
  16. # restriction in the following sentence) without fee is hereby granted,
  17. # provided that the above copyright notice appears in all copies, and
  18. # that both that copyright notice and this permission notice appear in
  19. # supporting documentation, and that the name of InfoSeek not be used in
  20. # advertising or publicity pertaining to distribution of the software
  21. # without specific, written prior permission.  This permission is
  22. # explicitly restricted to the copying and modification of the software
  23. # to remain in Python, compiled Python, or other languages (such as C)
  24. # wherein the modified or derived code is exclusively imported into a
  25. # Python module.
  26. # INFOSEEK CORPORATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
  27. # SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  28. # FITNESS. IN NO EVENT SHALL INFOSEEK CORPORATION BE LIABLE FOR ANY
  29. # SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  30. # RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
  31. # CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  32. # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  33.  
  34.  
  35.  
  36. import sys
  37. import os
  38. import time
  39. import marshal
  40.  
  41.  
  42. # Sample timer for use with 
  43. #i_count = 0
  44. #def integer_timer():
  45. #    global i_count
  46. #    i_count = i_count + 1
  47. #    return i_count
  48. #itimes = integer_timer # replace with C coded timer returning integers
  49.  
  50. #**************************************************************************
  51. # The following are the static member functions for the profiler class
  52. # Note that an instance of Profile() is *not* needed to call them.
  53. #**************************************************************************
  54.  
  55.  
  56. # simplified user interface
  57. def run(statement, *args):
  58.     prof = Profile()
  59.     try:
  60.         prof = prof.run(statement)
  61.     except SystemExit:
  62.         pass
  63.     if args:
  64.         prof.dump_stats(args[0])
  65.     else:
  66.         return prof.print_stats()
  67.  
  68. # print help
  69. def help():
  70.     for dirname in sys.path:
  71.         fullname = os.path.join(dirname, 'profile.doc')
  72.         if os.path.exists(fullname):
  73.             sts = os.system('${PAGER-more} '+fullname)
  74.             if sts: print '*** Pager exit status:', sts
  75.             break
  76.     else:
  77.         print 'Sorry, can\'t find the help file "profile.doc"',
  78.         print 'along the Python search path'
  79.  
  80.  
  81. class Profile:
  82.     """Profiler class.
  83.     
  84.     self.cur is always a tuple.  Each such tuple corresponds to a stack
  85.     frame that is currently active (self.cur[-2]).  The following are the
  86.     definitions of its members.  We use this external "parallel stack" to
  87.     avoid contaminating the program that we are profiling. (old profiler
  88.     used to write into the frames local dictionary!!) Derived classes
  89.     can change the definition of some entries, as long as they leave
  90.     [-2:] intact.
  91.  
  92.     [ 0] = Time that needs to be charged to the parent frame's function.
  93.            It is used so that a function call will not have to access the
  94.            timing data for the parent frame.
  95.     [ 1] = Total time spent in this frame's function, excluding time in
  96.            subfunctions
  97.     [ 2] = Cumulative time spent in this frame's function, including time in
  98.            all subfunctions to this frame.
  99.     [-3] = Name of the function that corresonds to this frame.  
  100.     [-2] = Actual frame that we correspond to (used to sync exception handling)
  101.     [-1] = Our parent 6-tuple (corresonds to frame.f_back)
  102.  
  103.     Timing data for each function is stored as a 5-tuple in the dictionary
  104.     self.timings[].  The index is always the name stored in self.cur[4].
  105.     The following are the definitions of the members:
  106.  
  107.     [0] = The number of times this function was called, not counting direct
  108.           or indirect recursion,
  109.     [1] = Number of times this function appears on the stack, minus one
  110.     [2] = Total time spent internal to this function
  111.     [3] = Cumulative time that this function was present on the stack.  In
  112.           non-recursive functions, this is the total execution time from start
  113.           to finish of each invocation of a function, including time spent in
  114.           all subfunctions.
  115.     [5] = A dictionary indicating for each function name, the number of times
  116.           it was called by us.
  117.     """
  118.  
  119.     def __init__(self, timer=None):
  120.         self.timings = {}
  121.         self.cur = None
  122.         self.cmd = ""
  123.  
  124.         self.dispatch = {  \
  125.               'call'     : self.trace_dispatch_call, \
  126.               'return'   : self.trace_dispatch_return, \
  127.               'exception': self.trace_dispatch_exception, \
  128.               }
  129.  
  130.         if not timer:
  131.             if os.name == 'mac':
  132.                 import MacOS
  133.                 self.timer = MacOS.GetTicks
  134.                 self.dispatcher = self.trace_dispatch_mac
  135.                 self.get_time = self.get_time_mac
  136.             elif hasattr(time, 'clock'):
  137.                 self.timer = time.clock
  138.                 self.dispatcher = self.trace_dispatch_i
  139.             elif hasattr(os, 'times'):
  140.                 self.timer = os.times
  141.                 self.dispatcher = self.trace_dispatch
  142.             else:
  143.                 self.timer = time.time
  144.                 self.dispatcher = self.trace_dispatch_i
  145.         else:
  146.             self.timer = timer
  147.             t = self.timer() # test out timer function
  148.             try:
  149.                 if len(t) == 2:
  150.                     self.dispatcher = self.trace_dispatch
  151.                 else:
  152.                     self.dispatcher = self.trace_dispatch_l
  153.             except TypeError:
  154.                 self.dispatcher = self.trace_dispatch_i
  155.         self.t = self.get_time()
  156.         self.simulate_call('profiler')
  157.  
  158.  
  159.     def get_time(self): # slow simulation of method to acquire time
  160.         t = self.timer()
  161.         if type(t) == type(()) or type(t) == type([]):
  162.             t = reduce(lambda x,y: x+y, t, 0)
  163.         return t
  164.         
  165.     def get_time_mac(self):
  166.         return self.timer()/60.0
  167.  
  168.     # Heavily optimized dispatch routine for os.times() timer
  169.  
  170.     def trace_dispatch(self, frame, event, arg):
  171.         t = self.timer()
  172.         t = t[0] + t[1] - self.t        # No Calibration constant
  173.         # t = t[0] + t[1] - self.t - .00053 # Calibration constant
  174.  
  175.         if self.dispatch[event](frame,t):
  176.             t = self.timer()
  177.             self.t = t[0] + t[1]
  178.         else:
  179.             r = self.timer()
  180.             self.t = r[0] + r[1] - t # put back unrecorded delta
  181.         return
  182.  
  183.  
  184.  
  185.     # Dispatch routine for best timer program (return = scalar integer)
  186.  
  187.     def trace_dispatch_i(self, frame, event, arg):
  188.         t = self.timer() - self.t # - 1 # Integer calibration constant
  189.         if self.dispatch[event](frame,t):
  190.             self.t = self.timer()
  191.         else:
  192.             self.t = self.timer() - t  # put back unrecorded delta
  193.         return
  194.     
  195.     # Dispatch routine for macintosh (timer returns time in ticks of 1/60th second)
  196.  
  197.     def trace_dispatch_mac(self, frame, event, arg):
  198.         t = self.timer()/60.0 - self.t # - 1 # Integer calibration constant
  199.         if self.dispatch[event](frame,t):
  200.             self.t = self.timer()/60.0
  201.         else:
  202.             self.t = self.timer()/60.0 - t  # put back unrecorded delta
  203.         return
  204.  
  205.  
  206.     # SLOW generic dispatch rountine for timer returning lists of numbers
  207.  
  208.     def trace_dispatch_l(self, frame, event, arg):
  209.         t = self.get_time() - self.t
  210.  
  211.         if self.dispatch[event](frame,t):
  212.             self.t = self.get_time()
  213.         else:
  214.             self.t = self.get_time()-t # put back unrecorded delta
  215.         return
  216.  
  217.  
  218.     def trace_dispatch_exception(self, frame, t):
  219.         rt, rtt, rct, rfn, rframe, rcur = self.cur
  220.         if (not rframe is frame) and rcur:
  221.             return self.trace_dispatch_return(rframe, t)
  222.         return 0
  223.  
  224.  
  225.     def trace_dispatch_call(self, frame, t):
  226.         fcode = frame.f_code
  227.         fn = (fcode.co_filename, fcode.co_firstlineno, fcode.co_name)
  228.         self.cur = (t, 0, 0, fn, frame, self.cur)
  229.         if self.timings.has_key(fn):
  230.             cc, ns, tt, ct, callers = self.timings[fn]
  231.             self.timings[fn] = cc, ns + 1, tt, ct, callers
  232.         else:
  233.             self.timings[fn] = 0, 0, 0, 0, {}
  234.         return 1
  235.  
  236.     def trace_dispatch_return(self, frame, t):
  237.         # if not frame is self.cur[-2]: raise "Bad return", self.cur[3]
  238.  
  239.         # Prefix "r" means part of the Returning or exiting frame
  240.         # Prefix "p" means part of the Previous or older frame
  241.  
  242.         rt, rtt, rct, rfn, frame, rcur = self.cur
  243.         rtt = rtt + t
  244.         sft = rtt + rct
  245.  
  246.         pt, ptt, pct, pfn, pframe, pcur = rcur
  247.         self.cur = pt, ptt+rt, pct+sft, pfn, pframe, pcur
  248.  
  249.         cc, ns, tt, ct, callers = self.timings[rfn]
  250.         if not ns:
  251.             ct = ct + sft
  252.             cc = cc + 1
  253.         if callers.has_key(pfn):
  254.             callers[pfn] = callers[pfn] + 1  # hack: gather more
  255.             # stats such as the amount of time added to ct courtesy
  256.             # of this specific call, and the contribution to cc
  257.             # courtesy of this call.
  258.         else:
  259.             callers[pfn] = 1
  260.         self.timings[rfn] = cc, ns - 1, tt+rtt, ct, callers
  261.  
  262.         return 1
  263.  
  264.     # The next few function play with self.cmd. By carefully preloading
  265.     # our parallel stack, we can force the profiled result to include
  266.     # an arbitrary string as the name of the calling function.
  267.     # We use self.cmd as that string, and the resulting stats look
  268.     # very nice :-).
  269.  
  270.     def set_cmd(self, cmd):
  271.         if self.cur[-1]: return   # already set
  272.         self.cmd = cmd
  273.         self.simulate_call(cmd)
  274.  
  275.     class fake_code:
  276.         def __init__(self, filename, line, name):
  277.             self.co_filename = filename
  278.             self.co_line = line
  279.             self.co_name = name
  280.             self.co_firstlineno = 0
  281.  
  282.         def __repr__(self):
  283.             return repr((self.co_filename, self.co_line, self.co_name))
  284.  
  285.     class fake_frame:
  286.         def __init__(self, code, prior):
  287.             self.f_code = code
  288.             self.f_back = prior
  289.             
  290.     def simulate_call(self, name):
  291.         code = self.fake_code('profile', 0, name)
  292.         if self.cur:
  293.             pframe = self.cur[-2]
  294.         else:
  295.             pframe = None
  296.         frame = self.fake_frame(code, pframe)
  297.         a = self.dispatch['call'](frame, 0)
  298.         return
  299.  
  300.     # collect stats from pending stack, including getting final
  301.     # timings for self.cmd frame.
  302.     
  303.     def simulate_cmd_complete(self):   
  304.         t = self.get_time() - self.t
  305.         while self.cur[-1]:
  306.             # We *can* cause assertion errors here if
  307.             # dispatch_trace_return checks for a frame match!
  308.             a = self.dispatch['return'](self.cur[-2], t)
  309.             t = 0
  310.         self.t = self.get_time() - t
  311.  
  312.     
  313.     def print_stats(self):
  314.         import pstats
  315.         pstats.Stats(self).strip_dirs().sort_stats(-1). \
  316.               print_stats()
  317.  
  318.     def dump_stats(self, file):
  319.         f = open(file, 'wb')
  320.         self.create_stats()
  321.         marshal.dump(self.stats, f)
  322.         f.close()
  323.  
  324.     def create_stats(self):
  325.         self.simulate_cmd_complete()
  326.         self.snapshot_stats()
  327.  
  328.     def snapshot_stats(self):
  329.         self.stats = {}
  330.         for func in self.timings.keys():
  331.             cc, ns, tt, ct, callers = self.timings[func]
  332.             callers = callers.copy()
  333.             nc = 0
  334.             for func_caller in callers.keys():
  335.                 nc = nc + callers[func_caller]
  336.             self.stats[func] = cc, nc, tt, ct, callers
  337.  
  338.  
  339.     # The following two methods can be called by clients to use
  340.     # a profiler to profile a statement, given as a string.
  341.     
  342.     def run(self, cmd):
  343.         import __main__
  344.         dict = __main__.__dict__
  345.         return self.runctx(cmd, dict, dict)
  346.     
  347.     def runctx(self, cmd, globals, locals):
  348.         self.set_cmd(cmd)
  349.         sys.setprofile(self.dispatcher)
  350.         try:
  351.             exec cmd in globals, locals
  352.         finally:
  353.             sys.setprofile(None)
  354.         return self
  355.  
  356.     # This method is more useful to profile a single function call.
  357.     def runcall(self, func, *args):
  358.         self.set_cmd(`func`)
  359.         sys.setprofile(self.dispatcher)
  360.         try:
  361.             return apply(func, args)
  362.         finally:
  363.             sys.setprofile(None)
  364.  
  365.  
  366.     #******************************************************************
  367.     # The following calculates the overhead for using a profiler.  The
  368.     # problem is that it takes a fair amount of time for the profiler
  369.     # to stop the stopwatch (from the time it recieves an event).
  370.     # Similarly, there is a delay from the time that the profiler
  371.     # re-starts the stopwatch before the user's code really gets to
  372.     # continue.  The following code tries to measure the difference on
  373.     # a per-event basis. The result can the be placed in the
  374.     # Profile.dispatch_event() routine for the given platform.  Note
  375.     # that this difference is only significant if there are a lot of
  376.     # events, and relatively little user code per event.  For example,
  377.     # code with small functions will typically benefit from having the
  378.     # profiler calibrated for the current platform.  This *could* be
  379.     # done on the fly during init() time, but it is not worth the
  380.     # effort.  Also note that if too large a value specified, then
  381.     # execution time on some functions will actually appear as a
  382.     # negative number.  It is *normal* for some functions (with very
  383.     # low call counts) to have such negative stats, even if the
  384.     # calibration figure is "correct." 
  385.     #
  386.     # One alternative to profile-time calibration adjustments (i.e.,
  387.     # adding in the magic little delta during each event) is to track
  388.     # more carefully the number of events (and cumulatively, the number
  389.     # of events during sub functions) that are seen.  If this were
  390.     # done, then the arithmetic could be done after the fact (i.e., at
  391.     # display time).  Currintly, we track only call/return events.
  392.     # These values can be deduced by examining the callees and callers
  393.     # vectors for each functions.  Hence we *can* almost correct the
  394.     # internal time figure at print time (note that we currently don't
  395.     # track exception event processing counts).  Unfortunately, there
  396.     # is currently no similar information for cumulative sub-function
  397.     # time.  It would not be hard to "get all this info" at profiler
  398.     # time.  Specifically, we would have to extend the tuples to keep
  399.     # counts of this in each frame, and then extend the defs of timing
  400.     # tuples to include the significant two figures. I'm a bit fearful
  401.     # that this additional feature will slow the heavily optimized
  402.     # event/time ratio (i.e., the profiler would run slower, fur a very
  403.     # low "value added" feature.) 
  404.     #
  405.     # Plugging in the calibration constant doesn't slow down the
  406.     # profiler very much, and the accuracy goes way up.
  407.     #**************************************************************
  408.     
  409.     def calibrate(self, m):
  410.         # Modified by Tim Peters
  411.         n = m
  412.         s = self.get_time()
  413.         while n:
  414.             self.simple()
  415.             n = n - 1
  416.         f = self.get_time()
  417.         my_simple = f - s
  418.         #print "Simple =", my_simple,
  419.  
  420.         n = m
  421.         s = self.get_time()
  422.         while n:
  423.             self.instrumented()
  424.             n = n - 1
  425.         f = self.get_time()
  426.         my_inst = f - s
  427.         # print "Instrumented =", my_inst
  428.         avg_cost = (my_inst - my_simple)/m
  429.         #print "Delta/call =", avg_cost, "(profiler fixup constant)"
  430.         return avg_cost
  431.  
  432.     # simulate a program with no profiler activity
  433.     def simple(self):
  434.         a = 1
  435.         pass
  436.  
  437.     # simulate a program with call/return event processing
  438.     def instrumented(self):
  439.         a = 1
  440.         self.profiler_simulation(a, a, a)
  441.  
  442.     # simulate an event processing activity (from user's perspective)
  443.     def profiler_simulation(self, x, y, z):  
  444.         t = self.timer()
  445.         ## t = t[0] + t[1]
  446.         self.ut = t
  447.  
  448.  
  449.  
  450. class OldProfile(Profile):
  451.     """A derived profiler that simulates the old style profile, providing
  452.     errant results on recursive functions. The reason for the usefulness of
  453.     this profiler is that it runs faster (i.e., less overhead).  It still
  454.     creates all the caller stats, and is quite useful when there is *no*
  455.     recursion in the user's code.
  456.     
  457.     This code also shows how easy it is to create a modified profiler.
  458.     """
  459.  
  460.     def trace_dispatch_exception(self, frame, t):
  461.         rt, rtt, rct, rfn, rframe, rcur = self.cur
  462.         if rcur and not rframe is frame:
  463.             return self.trace_dispatch_return(rframe, t)
  464.         return 0
  465.  
  466.     def trace_dispatch_call(self, frame, t):
  467.         fn = `frame.f_code`
  468.         
  469.         self.cur = (t, 0, 0, fn, frame, self.cur)
  470.         if self.timings.has_key(fn):
  471.             tt, ct, callers = self.timings[fn]
  472.             self.timings[fn] = tt, ct, callers
  473.         else:
  474.             self.timings[fn] = 0, 0, {}
  475.         return 1
  476.  
  477.     def trace_dispatch_return(self, frame, t):
  478.         rt, rtt, rct, rfn, frame, rcur = self.cur
  479.         rtt = rtt + t
  480.         sft = rtt + rct
  481.  
  482.         pt, ptt, pct, pfn, pframe, pcur = rcur
  483.         self.cur = pt, ptt+rt, pct+sft, pfn, pframe, pcur
  484.  
  485.         tt, ct, callers = self.timings[rfn]
  486.         if callers.has_key(pfn):
  487.             callers[pfn] = callers[pfn] + 1
  488.         else:
  489.             callers[pfn] = 1
  490.         self.timings[rfn] = tt+rtt, ct + sft, callers
  491.  
  492.         return 1
  493.  
  494.  
  495.     def snapshot_stats(self):
  496.         self.stats = {}
  497.         for func in self.timings.keys():
  498.             tt, ct, callers = self.timings[func]
  499.             callers = callers.copy()
  500.             nc = 0
  501.             for func_caller in callers.keys():
  502.                 nc = nc + callers[func_caller]
  503.             self.stats[func] = nc, nc, tt, ct, callers
  504.  
  505.         
  506.  
  507. class HotProfile(Profile):
  508.     """The fastest derived profile example.  It does not calculate
  509.     caller-callee relationships, and does not calculate cumulative
  510.     time under a function.  It only calculates time spent in a
  511.     function, so it runs very quickly due to its very low overhead.
  512.     """
  513.  
  514.     def trace_dispatch_exception(self, frame, t):
  515.         rt, rtt, rfn, rframe, rcur = self.cur
  516.         if rcur and not rframe is frame:
  517.             return self.trace_dispatch_return(rframe, t)
  518.         return 0
  519.  
  520.     def trace_dispatch_call(self, frame, t):
  521.         self.cur = (t, 0, frame, self.cur)
  522.         return 1
  523.  
  524.     def trace_dispatch_return(self, frame, t):
  525.         rt, rtt, frame, rcur = self.cur
  526.  
  527.         rfn = `frame.f_code`
  528.  
  529.         pt, ptt, pframe, pcur = rcur
  530.         self.cur = pt, ptt+rt, pframe, pcur
  531.  
  532.         if self.timings.has_key(rfn):
  533.             nc, tt = self.timings[rfn]
  534.             self.timings[rfn] = nc + 1, rt + rtt + tt
  535.         else:
  536.             self.timings[rfn] =      1, rt + rtt
  537.  
  538.         return 1
  539.  
  540.  
  541.     def snapshot_stats(self):
  542.         self.stats = {}
  543.         for func in self.timings.keys():
  544.             nc, tt = self.timings[func]
  545.             self.stats[func] = nc, nc, tt, 0, {}
  546.  
  547.         
  548.  
  549. #****************************************************************************
  550. def Stats(*args):
  551.     print 'Report generating functions are in the "pstats" module\a'
  552.  
  553.  
  554. # When invoked as main program, invoke the profiler on a script
  555. if __name__ == '__main__':
  556.     import sys
  557.     import os
  558.     if not sys.argv[1:]:
  559.         print "usage: profile.py scriptfile [arg] ..."
  560.         sys.exit(2)
  561.  
  562.     filename = sys.argv[1]    # Get script filename
  563.  
  564.     del sys.argv[0]        # Hide "profile.py" from argument list
  565.  
  566.     # Insert script directory in front of module search path
  567.     sys.path.insert(0, os.path.dirname(filename))
  568.  
  569.     run('execfile(' + `filename` + ')')
  570.