3 # Class for profiling python code. rev 1.0 6/2/94
5 # Based on prior profile module by Sjoerd Mullender...
6 # which was hacked somewhat by: Guido van Rossum
8 # See profile.doc for more information
10 """Class for profiling Python code."""
12 # Copyright 1994, by InfoSeek Corporation, all rights reserved.
13 # Written by James Roskind
15 # Permission to use, copy, modify, and distribute this Python software
16 # and its associated documentation for any purpose (subject to the
17 # restriction in the following sentence) without fee is hereby granted,
18 # provided that the above copyright notice appears in all copies, and
19 # that both that copyright notice and this permission notice appear in
20 # supporting documentation, and that the name of InfoSeek not be used in
21 # advertising or publicity pertaining to distribution of the software
22 # without specific, written prior permission. This permission is
23 # explicitly restricted to the copying and modification of the software
24 # to remain in Python, compiled Python, or other languages (such as C)
25 # wherein the modified or derived code is exclusively imported into a
28 # INFOSEEK CORPORATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
29 # SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
30 # FITNESS. IN NO EVENT SHALL INFOSEEK CORPORATION BE LIABLE FOR ANY
31 # SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
32 # RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
33 # CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
34 # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
43 __all__
= ["run","help","Profile"]
45 # Sample timer for use with
49 # i_count = i_count + 1
51 #itimes = integer_timer # replace with C coded timer returning integers
53 #**************************************************************************
54 # The following are the static member functions for the profiler class
55 # Note that an instance of Profile() is *not* needed to call them.
56 #**************************************************************************
58 def run(statement
, filename
=None):
59 """Run statement under profiler optionally saving results in filename
61 This function takes a single argument that can be passed to the
62 "exec" statement, and an optional file name. In all cases this
63 routine attempts to "exec" its first argument and gather profiling
64 statistics from the execution. If no file name is present, then this
65 function automatically prints a simple profiling report, sorted by the
66 standard name string (file/line/function-name) that is presented in
71 prof
= prof
.run(statement
)
74 if filename
is not None:
75 prof
.dump_stats(filename
)
77 return prof
.print_stats()
81 for dirname
in sys
.path
:
82 fullname
= os
.path
.join(dirname
, 'profile.doc')
83 if os
.path
.exists(fullname
):
84 sts
= os
.system('${PAGER-more} '+fullname
)
85 if sts
: print '*** Pager exit status:', sts
88 print 'Sorry, can\'t find the help file "profile.doc"',
89 print 'along the Python search path'
94 def _get_time_mac(timer
=MacOS
.GetTicks
):
97 if hasattr(os
, "times"):
98 def _get_time_times(timer
=os
.times
):
106 self.cur is always a tuple. Each such tuple corresponds to a stack
107 frame that is currently active (self.cur[-2]). The following are the
108 definitions of its members. We use this external "parallel stack" to
109 avoid contaminating the program that we are profiling. (old profiler
110 used to write into the frames local dictionary!!) Derived classes
111 can change the definition of some entries, as long as they leave
114 [ 0] = Time that needs to be charged to the parent frame's function.
115 It is used so that a function call will not have to access the
116 timing data for the parent frame.
117 [ 1] = Total time spent in this frame's function, excluding time in
119 [ 2] = Cumulative time spent in this frame's function, including time in
120 all subfunctions to this frame.
121 [-3] = Name of the function that corresponds to this frame.
122 [-2] = Actual frame that we correspond to (used to sync exception handling)
123 [-1] = Our parent 6-tuple (corresponds to frame.f_back)
125 Timing data for each function is stored as a 5-tuple in the dictionary
126 self.timings[]. The index is always the name stored in self.cur[4].
127 The following are the definitions of the members:
129 [0] = The number of times this function was called, not counting direct
130 or indirect recursion,
131 [1] = Number of times this function appears on the stack, minus one
132 [2] = Total time spent internal to this function
133 [3] = Cumulative time that this function was present on the stack. In
134 non-recursive functions, this is the total execution time from start
135 to finish of each invocation of a function, including time spent in
137 [5] = A dictionary indicating for each function name, the number of times
141 def __init__(self
, timer
=None):
148 self
.timer
= MacOS
.GetTicks
149 self
.dispatcher
= self
.trace_dispatch_mac
150 self
.get_time
= _get_time_mac
151 elif hasattr(time
, 'clock'):
152 self
.timer
= self
.get_time
= time
.clock
153 self
.dispatcher
= self
.trace_dispatch_i
154 elif hasattr(os
, 'times'):
155 self
.timer
= os
.times
156 self
.dispatcher
= self
.trace_dispatch
157 self
.get_time
= _get_time_times
159 self
.timer
= self
.get_time
= time
.time
160 self
.dispatcher
= self
.trace_dispatch_i
163 t
= self
.timer() # test out timer function
167 self
.get_time
= timer
168 self
.dispatcher
= self
.trace_dispatch_i
171 self
.dispatcher
= self
.trace_dispatch
173 self
.dispatcher
= self
.trace_dispatch_l
174 # This get_time() implementation needs to be defined
175 # here to capture the passed-in timer in the parameter
176 # list (for performance). Note that we can't assume
177 # the timer() result contains two values in all
180 def get_time_timer(timer
=timer
,
181 reduce=reduce, reducer
=operator
.add
):
182 return reduce(reducer
, timer(), 0)
183 self
.get_time
= get_time_timer
184 self
.t
= self
.get_time()
185 self
.simulate_call('profiler')
187 # Heavily optimized dispatch routine for os.times() timer
189 def trace_dispatch(self
, frame
, event
, arg
):
192 t
= t
[0] + t
[1] - self
.t
# No Calibration constant
193 # t = t[0] + t[1] - self.t - .00053 # Calibration constant
195 if self
.dispatch
[event
](self
, frame
,t
):
200 self
.t
= r
[0] + r
[1] - t
# put back unrecorded delta
205 # Dispatch routine for best timer program (return = scalar integer)
207 def trace_dispatch_i(self
, frame
, event
, arg
):
209 t
= timer() - self
.t
# - 1 # Integer calibration constant
210 if self
.dispatch
[event
](self
, frame
,t
):
213 self
.t
= timer() - t
# put back unrecorded delta
216 # Dispatch routine for macintosh (timer returns time in ticks of
219 def trace_dispatch_mac(self
, frame
, event
, arg
):
221 t
= timer()/60.0 - self
.t
# - 1 # Integer calibration constant
222 if self
.dispatch
[event
](self
, frame
,t
):
223 self
.t
= timer()/60.0
225 self
.t
= timer()/60.0 - t
# put back unrecorded delta
229 # SLOW generic dispatch routine for timer returning lists of numbers
231 def trace_dispatch_l(self
, frame
, event
, arg
):
232 get_time
= self
.get_time
233 t
= get_time() - self
.t
235 if self
.dispatch
[event
](self
, frame
,t
):
238 self
.t
= get_time() - t
# put back unrecorded delta
242 def trace_dispatch_exception(self
, frame
, t
):
243 rt
, rtt
, rct
, rfn
, rframe
, rcur
= self
.cur
244 if (not rframe
is frame
) and rcur
:
245 return self
.trace_dispatch_return(rframe
, t
)
249 def trace_dispatch_call(self
, frame
, t
):
251 fn
= (fcode
.co_filename
, fcode
.co_firstlineno
, fcode
.co_name
)
252 self
.cur
= (t
, 0, 0, fn
, frame
, self
.cur
)
253 timings
= self
.timings
254 if timings
.has_key(fn
):
255 cc
, ns
, tt
, ct
, callers
= timings
[fn
]
256 timings
[fn
] = cc
, ns
+ 1, tt
, ct
, callers
258 timings
[fn
] = 0, 0, 0, 0, {}
261 def trace_dispatch_return(self
, frame
, t
):
262 # if not frame is self.cur[-2]: raise "Bad return", self.cur[3]
264 # Prefix "r" means part of the Returning or exiting frame
265 # Prefix "p" means part of the Previous or older frame
267 rt
, rtt
, rct
, rfn
, frame
, rcur
= self
.cur
271 pt
, ptt
, pct
, pfn
, pframe
, pcur
= rcur
272 self
.cur
= pt
, ptt
+rt
, pct
+sft
, pfn
, pframe
, pcur
274 timings
= self
.timings
275 cc
, ns
, tt
, ct
, callers
= timings
[rfn
]
279 if callers
.has_key(pfn
):
280 callers
[pfn
] = callers
[pfn
] + 1 # hack: gather more
281 # stats such as the amount of time added to ct courtesy
282 # of this specific call, and the contribution to cc
283 # courtesy of this call.
286 timings
[rfn
] = cc
, ns
- 1, tt
+rtt
, ct
, callers
292 "call": trace_dispatch_call
,
293 "exception": trace_dispatch_exception
,
294 "return": trace_dispatch_return
,
298 # The next few function play with self.cmd. By carefully preloading
299 # our parallel stack, we can force the profiled result to include
300 # an arbitrary string as the name of the calling function.
301 # We use self.cmd as that string, and the resulting stats look
304 def set_cmd(self
, cmd
):
305 if self
.cur
[-1]: return # already set
307 self
.simulate_call(cmd
)
310 def __init__(self
, filename
, line
, name
):
311 self
.co_filename
= filename
314 self
.co_firstlineno
= 0
317 return repr((self
.co_filename
, self
.co_line
, self
.co_name
))
320 def __init__(self
, code
, prior
):
324 def simulate_call(self
, name
):
325 code
= self
.fake_code('profile', 0, name
)
327 pframe
= self
.cur
[-2]
330 frame
= self
.fake_frame(code
, pframe
)
331 a
= self
.dispatch
['call'](self
, frame
, 0)
334 # collect stats from pending stack, including getting final
335 # timings for self.cmd frame.
337 def simulate_cmd_complete(self
):
338 get_time
= self
.get_time
339 t
= get_time() - self
.t
341 # We *can* cause assertion errors here if
342 # dispatch_trace_return checks for a frame match!
343 a
= self
.dispatch
['return'](self
, self
.cur
[-2], t
)
345 self
.t
= get_time() - t
348 def print_stats(self
):
350 pstats
.Stats(self
).strip_dirs().sort_stats(-1). \
353 def dump_stats(self
, file):
356 marshal
.dump(self
.stats
, f
)
359 def create_stats(self
):
360 self
.simulate_cmd_complete()
361 self
.snapshot_stats()
363 def snapshot_stats(self
):
365 for func
in self
.timings
.keys():
366 cc
, ns
, tt
, ct
, callers
= self
.timings
[func
]
367 callers
= callers
.copy()
369 for func_caller
in callers
.keys():
370 nc
= nc
+ callers
[func_caller
]
371 self
.stats
[func
] = cc
, nc
, tt
, ct
, callers
374 # The following two methods can be called by clients to use
375 # a profiler to profile a statement, given as a string.
379 dict = __main__
.__dict
__
380 return self
.runctx(cmd
, dict, dict)
382 def runctx(self
, cmd
, globals, locals):
384 sys
.setprofile(self
.dispatcher
)
386 exec cmd
in globals, locals
391 # This method is more useful to profile a single function call.
392 def runcall(self
, func
, *args
, **kw
):
394 sys
.setprofile(self
.dispatcher
)
396 return apply(func
, args
, kw
)
401 #******************************************************************
402 # The following calculates the overhead for using a profiler. The
403 # problem is that it takes a fair amount of time for the profiler
404 # to stop the stopwatch (from the time it receives an event).
405 # Similarly, there is a delay from the time that the profiler
406 # re-starts the stopwatch before the user's code really gets to
407 # continue. The following code tries to measure the difference on
408 # a per-event basis. The result can the be placed in the
409 # Profile.dispatch_event() routine for the given platform. Note
410 # that this difference is only significant if there are a lot of
411 # events, and relatively little user code per event. For example,
412 # code with small functions will typically benefit from having the
413 # profiler calibrated for the current platform. This *could* be
414 # done on the fly during init() time, but it is not worth the
415 # effort. Also note that if too large a value specified, then
416 # execution time on some functions will actually appear as a
417 # negative number. It is *normal* for some functions (with very
418 # low call counts) to have such negative stats, even if the
419 # calibration figure is "correct."
421 # One alternative to profile-time calibration adjustments (i.e.,
422 # adding in the magic little delta during each event) is to track
423 # more carefully the number of events (and cumulatively, the number
424 # of events during sub functions) that are seen. If this were
425 # done, then the arithmetic could be done after the fact (i.e., at
426 # display time). Currently, we track only call/return events.
427 # These values can be deduced by examining the callees and callers
428 # vectors for each functions. Hence we *can* almost correct the
429 # internal time figure at print time (note that we currently don't
430 # track exception event processing counts). Unfortunately, there
431 # is currently no similar information for cumulative sub-function
432 # time. It would not be hard to "get all this info" at profiler
433 # time. Specifically, we would have to extend the tuples to keep
434 # counts of this in each frame, and then extend the defs of timing
435 # tuples to include the significant two figures. I'm a bit fearful
436 # that this additional feature will slow the heavily optimized
437 # event/time ratio (i.e., the profiler would run slower, fur a very
438 # low "value added" feature.)
440 # Plugging in the calibration constant doesn't slow down the
441 # profiler very much, and the accuracy goes way up.
442 #**************************************************************
444 def calibrate(self
, m
):
445 # Modified by Tim Peters
446 get_time
= self
.get_time
454 #print "Simple =", my_simple,
463 # print "Instrumented =", my_inst
464 avg_cost
= (my_inst
- my_simple
)/m
465 #print "Delta/call =", avg_cost, "(profiler fixup constant)"
468 # simulate a program with no profiler activity
473 # simulate a program with call/return event processing
474 def instrumented(self
):
476 self
.profiler_simulation(a
, a
, a
)
478 # simulate an event processing activity (from user's perspective)
479 def profiler_simulation(self
, x
, y
, z
):
486 class OldProfile(Profile
):
487 """A derived profiler that simulates the old style profile, providing
488 errant results on recursive functions. The reason for the usefulness of
489 this profiler is that it runs faster (i.e., less overhead). It still
490 creates all the caller stats, and is quite useful when there is *no*
491 recursion in the user's code.
493 This code also shows how easy it is to create a modified profiler.
496 def trace_dispatch_exception(self
, frame
, t
):
497 rt
, rtt
, rct
, rfn
, rframe
, rcur
= self
.cur
498 if rcur
and not rframe
is frame
:
499 return self
.trace_dispatch_return(rframe
, t
)
502 def trace_dispatch_call(self
, frame
, t
):
505 self
.cur
= (t
, 0, 0, fn
, frame
, self
.cur
)
506 if self
.timings
.has_key(fn
):
507 tt
, ct
, callers
= self
.timings
[fn
]
508 self
.timings
[fn
] = tt
, ct
, callers
510 self
.timings
[fn
] = 0, 0, {}
513 def trace_dispatch_return(self
, frame
, t
):
514 rt
, rtt
, rct
, rfn
, frame
, rcur
= self
.cur
518 pt
, ptt
, pct
, pfn
, pframe
, pcur
= rcur
519 self
.cur
= pt
, ptt
+rt
, pct
+sft
, pfn
, pframe
, pcur
521 tt
, ct
, callers
= self
.timings
[rfn
]
522 if callers
.has_key(pfn
):
523 callers
[pfn
] = callers
[pfn
] + 1
526 self
.timings
[rfn
] = tt
+rtt
, ct
+ sft
, callers
532 "call": trace_dispatch_call
,
533 "exception": trace_dispatch_exception
,
534 "return": trace_dispatch_return
,
538 def snapshot_stats(self
):
540 for func
in self
.timings
.keys():
541 tt
, ct
, callers
= self
.timings
[func
]
542 callers
= callers
.copy()
544 for func_caller
in callers
.keys():
545 nc
= nc
+ callers
[func_caller
]
546 self
.stats
[func
] = nc
, nc
, tt
, ct
, callers
550 class HotProfile(Profile
):
551 """The fastest derived profile example. It does not calculate
552 caller-callee relationships, and does not calculate cumulative
553 time under a function. It only calculates time spent in a
554 function, so it runs very quickly due to its very low overhead.
557 def trace_dispatch_exception(self
, frame
, t
):
558 rt
, rtt
, rfn
, rframe
, rcur
= self
.cur
559 if rcur
and not rframe
is frame
:
560 return self
.trace_dispatch_return(rframe
, t
)
563 def trace_dispatch_call(self
, frame
, t
):
564 self
.cur
= (t
, 0, frame
, self
.cur
)
567 def trace_dispatch_return(self
, frame
, t
):
568 rt
, rtt
, frame
, rcur
= self
.cur
572 pt
, ptt
, pframe
, pcur
= rcur
573 self
.cur
= pt
, ptt
+rt
, pframe
, pcur
575 if self
.timings
.has_key(rfn
):
576 nc
, tt
= self
.timings
[rfn
]
577 self
.timings
[rfn
] = nc
+ 1, rt
+ rtt
+ tt
579 self
.timings
[rfn
] = 1, rt
+ rtt
585 "call": trace_dispatch_call
,
586 "exception": trace_dispatch_exception
,
587 "return": trace_dispatch_return
,
591 def snapshot_stats(self
):
593 for func
in self
.timings
.keys():
594 nc
, tt
= self
.timings
[func
]
595 self
.stats
[func
] = nc
, nc
, tt
, 0, {}
599 #****************************************************************************
601 print 'Report generating functions are in the "pstats" module\a'
604 # When invoked as main program, invoke the profiler on a script
605 if __name__
== '__main__':
607 print "usage: profile.py scriptfile [arg] ..."
610 filename
= sys
.argv
[1] # Get script filename
612 del sys
.argv
[0] # Hide "profile.py" from argument list
614 # Insert script directory in front of module search path
615 sys
.path
.insert(0, os
.path
.dirname(filename
))
617 run('execfile(' + `filename`
+ ')')