blob: df36c176a54fb6eed490fa76bffbc105e21604d9 [file] [log] [blame]
Guido van Rossumf06ee5f1996-11-27 19:52:01 +00001#! /usr/bin/env python
Guido van Rossum81762581992-04-21 15:36:23 +00002#
Guido van Rossumb6775db1994-08-01 11:34:53 +00003# Class for profiling python code. rev 1.0 6/2/94
Guido van Rossum81762581992-04-21 15:36:23 +00004#
Guido van Rossumb6775db1994-08-01 11:34:53 +00005# 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
11# Copyright 1994, by InfoSeek Corporation, all rights reserved.
12# Written by James Roskind
13#
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#
27# INFOSEEK CORPORATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
28# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
29# FITNESS. IN NO EVENT SHALL INFOSEEK CORPORATION BE LIABLE FOR ANY
30# SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
31# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
32# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
33# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
34
35
Guido van Rossum81762581992-04-21 15:36:23 +000036
37import sys
Guido van Rossum4e160981992-09-02 20:43:20 +000038import os
Guido van Rossumb6775db1994-08-01 11:34:53 +000039import time
Guido van Rossum4e160981992-09-02 20:43:20 +000040import string
Guido van Rossum4e160981992-09-02 20:43:20 +000041import marshal
Guido van Rossum81762581992-04-21 15:36:23 +000042
Guido van Rossum81762581992-04-21 15:36:23 +000043
Guido van Rossumb6775db1994-08-01 11:34:53 +000044# Global variables
45func_norm_dict = {}
46func_norm_counter = 0
Guido van Rossum4f399fb1995-09-30 16:48:54 +000047if hasattr(os, 'getpid'):
48 pid_string = `os.getpid()`
49else:
50 pid_string = ''
Guido van Rossum81762581992-04-21 15:36:23 +000051
Guido van Rossum81762581992-04-21 15:36:23 +000052
Guido van Rossumb6775db1994-08-01 11:34:53 +000053# Sample timer for use with
54#i_count = 0
55#def integer_timer():
56# global i_count
57# i_count = i_count + 1
58# return i_count
59#itimes = integer_timer # replace with C coded timer returning integers
Guido van Rossum81762581992-04-21 15:36:23 +000060
Guido van Rossumb6775db1994-08-01 11:34:53 +000061#**************************************************************************
62# The following are the static member functions for the profiler class
63# Note that an instance of Profile() is *not* needed to call them.
64#**************************************************************************
Guido van Rossum81762581992-04-21 15:36:23 +000065
Guido van Rossum4e160981992-09-02 20:43:20 +000066
67# simplified user interface
68def run(statement, *args):
Guido van Rossum7bc817d1993-12-17 15:25:27 +000069 prof = Profile()
Guido van Rossum4e160981992-09-02 20:43:20 +000070 try:
Guido van Rossumb6775db1994-08-01 11:34:53 +000071 prof = prof.run(statement)
Guido van Rossum4e160981992-09-02 20:43:20 +000072 except SystemExit:
73 pass
Guido van Rossumb6775db1994-08-01 11:34:53 +000074 if args:
Guido van Rossum4e160981992-09-02 20:43:20 +000075 prof.dump_stats(args[0])
Guido van Rossumb6775db1994-08-01 11:34:53 +000076 else:
77 return prof.print_stats()
Guido van Rossume61fa0a1993-10-22 13:56:35 +000078
79# print help
80def help():
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
86 break
87 else:
88 print 'Sorry, can\'t find the help file "profile.doc"',
89 print 'along the Python search path'
Guido van Rossumb6775db1994-08-01 11:34:53 +000090
91
92#**************************************************************************
93# class Profile documentation:
94#**************************************************************************
95# self.cur is always a tuple. Each such tuple corresponds to a stack
96# frame that is currently active (self.cur[-2]). The following are the
97# definitions of its members. We use this external "parallel stack" to
98# avoid contaminating the program that we are profiling. (old profiler
99# used to write into the frames local dictionary!!) Derived classes
100# can change the definition of some entries, as long as they leave
101# [-2:] intact.
102#
103# [ 0] = Time that needs to be charged to the parent frame's function. It is
104# used so that a function call will not have to access the timing data
105# for the parents frame.
106# [ 1] = Total time spent in this frame's function, excluding time in
107# subfunctions
108# [ 2] = Cumulative time spent in this frame's function, including time in
109# all subfunctions to this frame.
110# [-3] = Name of the function that corresonds to this frame.
111# [-2] = Actual frame that we correspond to (used to sync exception handling)
112# [-1] = Our parent 6-tuple (corresonds to frame.f_back)
113#**************************************************************************
114# Timing data for each function is stored as a 5-tuple in the dictionary
115# self.timings[]. The index is always the name stored in self.cur[4].
116# The following are the definitions of the members:
117#
118# [0] = The number of times this function was called, not counting direct
119# or indirect recursion,
120# [1] = Number of times this function appears on the stack, minus one
121# [2] = Total time spent internal to this function
122# [3] = Cumulative time that this function was present on the stack. In
123# non-recursive functions, this is the total execution time from start
124# to finish of each invocation of a function, including time spent in
125# all subfunctions.
126# [5] = A dictionary indicating for each function name, the number of times
127# it was called by us.
128#**************************************************************************
129# We produce function names via a repr() call on the f_code object during
130# profiling. This save a *lot* of CPU time. This results in a string that
131# always looks like:
132# <code object main at 87090, file "/a/lib/python-local/myfib.py", line 76>
133# After we "normalize it, it is a tuple of filename, line, function-name.
134# We wait till we are done profiling to do the normalization.
135# *IF* this repr format changes, then only the normalization routine should
136# need to be fixed.
137#**************************************************************************
138class Profile:
139
Guido van Rossum4f399fb1995-09-30 16:48:54 +0000140 def __init__(self, timer=None):
Guido van Rossumb6775db1994-08-01 11:34:53 +0000141 self.timings = {}
142 self.cur = None
143 self.cmd = ""
144
145 self.dispatch = { \
146 'call' : self.trace_dispatch_call, \
147 'return' : self.trace_dispatch_return, \
148 'exception': self.trace_dispatch_exception, \
149 }
150
Guido van Rossum4f399fb1995-09-30 16:48:54 +0000151 if not timer:
Guido van Rossum96c07fe1998-03-17 14:46:43 +0000152 if os.name == 'mac':
Guido van Rossumcbf3dd51997-10-08 15:23:02 +0000153 import MacOS
154 self.timer = MacOS.GetTicks
155 self.dispatcher = self.trace_dispatch_mac
156 self.get_time = self.get_time_mac
Guido van Rossum96c07fe1998-03-17 14:46:43 +0000157 elif hasattr(time, 'clock'):
158 self.timer = time.clock
159 self.dispatcher = self.trace_dispatch_i
160 elif hasattr(os, 'times'):
161 self.timer = os.times
162 self.dispatcher = self.trace_dispatch
Guido van Rossum4f399fb1995-09-30 16:48:54 +0000163 else:
164 self.timer = time.time
165 self.dispatcher = self.trace_dispatch_i
Guido van Rossumb6775db1994-08-01 11:34:53 +0000166 else:
Guido van Rossum4f399fb1995-09-30 16:48:54 +0000167 self.timer = timer
Guido van Rossumb6775db1994-08-01 11:34:53 +0000168 t = self.timer() # test out timer function
169 try:
170 if len(t) == 2:
171 self.dispatcher = self.trace_dispatch
172 else:
Guido van Rossum4f399fb1995-09-30 16:48:54 +0000173 self.dispatcher = self.trace_dispatch_l
174 except TypeError:
Guido van Rossumb6775db1994-08-01 11:34:53 +0000175 self.dispatcher = self.trace_dispatch_i
176 self.t = self.get_time()
177 self.simulate_call('profiler')
178
179
180 def get_time(self): # slow simulation of method to acquire time
181 t = self.timer()
182 if type(t) == type(()) or type(t) == type([]):
183 t = reduce(lambda x,y: x+y, t, 0)
184 return t
185
Guido van Rossumcbf3dd51997-10-08 15:23:02 +0000186 def get_time_mac(self):
187 return self.timer()/60.0
Guido van Rossumb6775db1994-08-01 11:34:53 +0000188
189 # Heavily optimized dispatch routine for os.times() timer
190
191 def trace_dispatch(self, frame, event, arg):
192 t = self.timer()
193 t = t[0] + t[1] - self.t # No Calibration constant
194 # t = t[0] + t[1] - self.t - .00053 # Calibration constant
195
196 if self.dispatch[event](frame,t):
197 t = self.timer()
198 self.t = t[0] + t[1]
199 else:
200 r = self.timer()
201 self.t = r[0] + r[1] - t # put back unrecorded delta
202 return
203
204
205
206 # Dispatch routine for best timer program (return = scalar integer)
207
208 def trace_dispatch_i(self, frame, event, arg):
209 t = self.timer() - self.t # - 1 # Integer calibration constant
210 if self.dispatch[event](frame,t):
211 self.t = self.timer()
212 else:
213 self.t = self.timer() - t # put back unrecorded delta
214 return
Guido van Rossumcbf3dd51997-10-08 15:23:02 +0000215
216 # Dispatch routine for macintosh (timer returns time in ticks of 1/60th second)
217
218 def trace_dispatch_mac(self, frame, event, arg):
219 t = self.timer()/60.0 - self.t # - 1 # Integer calibration constant
220 if self.dispatch[event](frame,t):
221 self.t = self.timer()/60.0
222 else:
223 self.t = self.timer()/60.0 - t # put back unrecorded delta
224 return
Guido van Rossumb6775db1994-08-01 11:34:53 +0000225
226
227 # SLOW generic dispatch rountine for timer returning lists of numbers
228
229 def trace_dispatch_l(self, frame, event, arg):
230 t = self.get_time() - self.t
231
232 if self.dispatch[event](frame,t):
233 self.t = self.get_time()
234 else:
235 self.t = self.get_time()-t # put back unrecorded delta
236 return
237
238
239 def trace_dispatch_exception(self, frame, t):
240 rt, rtt, rct, rfn, rframe, rcur = self.cur
241 if (not rframe is frame) and rcur:
242 return self.trace_dispatch_return(rframe, t)
243 return 0
244
245
246 def trace_dispatch_call(self, frame, t):
247 fn = `frame.f_code`
248
249 # The following should be about the best approach, but
250 # we would need a function that maps from id() back to
251 # the actual code object.
252 # fn = id(frame.f_code)
253 # Note we would really use our own function, which would
254 # return the code address, *and* bump the ref count. We
255 # would then fix up the normalize function to do the
256 # actualy repr(fn) call.
257
258 # The following is an interesting alternative
259 # It doesn't do as good a job, and it doesn't run as
260 # fast 'cause repr() is written in C, and this is Python.
261 #fcode = frame.f_code
262 #code = fcode.co_code
263 #if ord(code[0]) == 127: # == SET_LINENO
264 # # see "opcode.h" in the Python source
265 # fn = (fcode.co_filename, ord(code[1]) | \
266 # ord(code[2]) << 8, fcode.co_name)
267 #else:
268 # fn = (fcode.co_filename, 0, fcode.co_name)
269
270 self.cur = (t, 0, 0, fn, frame, self.cur)
271 if self.timings.has_key(fn):
272 cc, ns, tt, ct, callers = self.timings[fn]
273 self.timings[fn] = cc, ns + 1, tt, ct, callers
274 else:
275 self.timings[fn] = 0, 0, 0, 0, {}
276 return 1
277
278 def trace_dispatch_return(self, frame, t):
279 # if not frame is self.cur[-2]: raise "Bad return", self.cur[3]
280
281 # Prefix "r" means part of the Returning or exiting frame
282 # Prefix "p" means part of the Previous or older frame
283
284 rt, rtt, rct, rfn, frame, rcur = self.cur
285 rtt = rtt + t
286 sft = rtt + rct
287
288 pt, ptt, pct, pfn, pframe, pcur = rcur
289 self.cur = pt, ptt+rt, pct+sft, pfn, pframe, pcur
290
291 cc, ns, tt, ct, callers = self.timings[rfn]
292 if not ns:
293 ct = ct + sft
294 cc = cc + 1
295 if callers.has_key(pfn):
296 callers[pfn] = callers[pfn] + 1 # hack: gather more
297 # stats such as the amount of time added to ct courtesy
298 # of this specific call, and the contribution to cc
299 # courtesy of this call.
300 else:
301 callers[pfn] = 1
302 self.timings[rfn] = cc, ns - 1, tt+rtt, ct, callers
303
304 return 1
305
306 # The next few function play with self.cmd. By carefully preloading
307 # our paralell stack, we can force the profiled result to include
308 # an arbitrary string as the name of the calling function.
309 # We use self.cmd as that string, and the resulting stats look
310 # very nice :-).
311
312 def set_cmd(self, cmd):
313 if self.cur[-1]: return # already set
314 self.cmd = cmd
315 self.simulate_call(cmd)
316
317 class fake_code:
318 def __init__(self, filename, line, name):
319 self.co_filename = filename
320 self.co_line = line
321 self.co_name = name
322 self.co_code = '\0' # anything but 127
323
324 def __repr__(self):
325 return (self.co_filename, self.co_line, self.co_name)
326
327 class fake_frame:
328 def __init__(self, code, prior):
329 self.f_code = code
330 self.f_back = prior
331
332 def simulate_call(self, name):
333 code = self.fake_code('profile', 0, name)
334 if self.cur:
335 pframe = self.cur[-2]
336 else:
337 pframe = None
338 frame = self.fake_frame(code, pframe)
339 a = self.dispatch['call'](frame, 0)
340 return
341
342 # collect stats from pending stack, including getting final
343 # timings for self.cmd frame.
344
345 def simulate_cmd_complete(self):
346 t = self.get_time() - self.t
347 while self.cur[-1]:
348 # We *can* cause assertion errors here if
349 # dispatch_trace_return checks for a frame match!
350 a = self.dispatch['return'](self.cur[-2], t)
351 t = 0
352 self.t = self.get_time() - t
353
354
355 def print_stats(self):
356 import pstats
357 pstats.Stats(self).strip_dirs().sort_stats(-1). \
358 print_stats()
359
360 def dump_stats(self, file):
Guido van Rossumcbf3dd51997-10-08 15:23:02 +0000361 f = open(file, 'wb')
Guido van Rossumb6775db1994-08-01 11:34:53 +0000362 self.create_stats()
363 marshal.dump(self.stats, f)
364 f.close()
365
366 def create_stats(self):
367 self.simulate_cmd_complete()
368 self.snapshot_stats()
369
370 def snapshot_stats(self):
371 self.stats = {}
372 for func in self.timings.keys():
373 cc, ns, tt, ct, callers = self.timings[func]
374 nor_func = self.func_normalize(func)
375 nor_callers = {}
376 nc = 0
377 for func_caller in callers.keys():
378 nor_callers[self.func_normalize(func_caller)]=\
379 callers[func_caller]
380 nc = nc + callers[func_caller]
381 self.stats[nor_func] = cc, nc, tt, ct, nor_callers
382
383
384 # Override the following function if you can figure out
385 # a better name for the binary f_code entries. I just normalize
386 # them sequentially in a dictionary. It would be nice if we could
387 # *really* see the name of the underlying C code :-). Sometimes
388 # you can figure out what-is-what by looking at caller and callee
389 # lists (and knowing what your python code does).
390
391 def func_normalize(self, func_name):
392 global func_norm_dict
393 global func_norm_counter
394 global func_sequence_num
395
396 if func_norm_dict.has_key(func_name):
397 return func_norm_dict[func_name]
398 if type(func_name) == type(""):
399 long_name = string.split(func_name)
Guido van Rossum4f399fb1995-09-30 16:48:54 +0000400 file_name = long_name[-3][1:-2]
Guido van Rossumb6775db1994-08-01 11:34:53 +0000401 func = long_name[2]
Guido van Rossum4f399fb1995-09-30 16:48:54 +0000402 lineno = long_name[-1][:-1]
Guido van Rossumb6775db1994-08-01 11:34:53 +0000403 if '?' == func: # Until I find out how to may 'em...
404 file_name = 'python'
405 func_norm_counter = func_norm_counter + 1
406 func = pid_string + ".C." + `func_norm_counter`
407 result = file_name , string.atoi(lineno) , func
408 else:
409 result = func_name
410 func_norm_dict[func_name] = result
411 return result
412
413
414 # The following two methods can be called by clients to use
415 # a profiler to profile a statement, given as a string.
416
417 def run(self, cmd):
418 import __main__
419 dict = __main__.__dict__
Guido van Rossum6cb84f31996-05-28 23:00:42 +0000420 return self.runctx(cmd, dict, dict)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000421
422 def runctx(self, cmd, globals, locals):
423 self.set_cmd(cmd)
Guido van Rossum4f399fb1995-09-30 16:48:54 +0000424 sys.setprofile(self.dispatcher)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000425 try:
Guido van Rossum9c3241d1995-08-10 19:46:50 +0000426 exec cmd in globals, locals
Guido van Rossumb6775db1994-08-01 11:34:53 +0000427 finally:
428 sys.setprofile(None)
Guido van Rossum6cb84f31996-05-28 23:00:42 +0000429 return self
Guido van Rossumb6775db1994-08-01 11:34:53 +0000430
431 # This method is more useful to profile a single function call.
432 def runcall(self, func, *args):
Guido van Rossum8afa8241995-06-22 18:52:35 +0000433 self.set_cmd(`func`)
Guido van Rossum4f399fb1995-09-30 16:48:54 +0000434 sys.setprofile(self.dispatcher)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000435 try:
Guido van Rossum6cb84f31996-05-28 23:00:42 +0000436 return apply(func, args)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000437 finally:
438 sys.setprofile(None)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000439
440
Guido van Rossum8ca84201998-03-26 20:56:10 +0000441 #******************************************************************
Guido van Rossumb6775db1994-08-01 11:34:53 +0000442 # The following calculates the overhead for using a profiler. The
443 # problem is that it takes a fair amount of time for the profiler
444 # to stop the stopwatch (from the time it recieves an event).
445 # Similarly, there is a delay from the time that the profiler
446 # re-starts the stopwatch before the user's code really gets to
447 # continue. The following code tries to measure the difference on
448 # a per-event basis. The result can the be placed in the
449 # Profile.dispatch_event() routine for the given platform. Note
450 # that this difference is only significant if there are a lot of
451 # events, and relatively little user code per event. For example,
452 # code with small functions will typically benefit from having the
453 # profiler calibrated for the current platform. This *could* be
454 # done on the fly during init() time, but it is not worth the
455 # effort. Also note that if too large a value specified, then
456 # execution time on some functions will actually appear as a
457 # negative number. It is *normal* for some functions (with very
458 # low call counts) to have such negative stats, even if the
459 # calibration figure is "correct."
460 #
461 # One alternative to profile-time calibration adjustments (i.e.,
462 # adding in the magic little delta during each event) is to track
463 # more carefully the number of events (and cumulatively, the number
464 # of events during sub functions) that are seen. If this were
465 # done, then the arithmetic could be done after the fact (i.e., at
466 # display time). Currintly, we track only call/return events.
467 # These values can be deduced by examining the callees and callers
468 # vectors for each functions. Hence we *can* almost correct the
469 # internal time figure at print time (note that we currently don't
470 # track exception event processing counts). Unfortunately, there
471 # is currently no similar information for cumulative sub-function
472 # time. It would not be hard to "get all this info" at profiler
473 # time. Specifically, we would have to extend the tuples to keep
474 # counts of this in each frame, and then extend the defs of timing
475 # tuples to include the significant two figures. I'm a bit fearful
476 # that this additional feature will slow the heavily optimized
477 # event/time ratio (i.e., the profiler would run slower, fur a very
478 # low "value added" feature.)
479 #
480 # Plugging in the calibration constant doesn't slow down the
481 # profiler very much, and the accuracy goes way up.
482 #**************************************************************
483
Guido van Rossum8ca84201998-03-26 20:56:10 +0000484 def calibrate(self, m):
485 # Modified by Tim Peters
Guido van Rossumb6775db1994-08-01 11:34:53 +0000486 n = m
Guido van Rossum8ca84201998-03-26 20:56:10 +0000487 s = self.get_time()
Guido van Rossumb6775db1994-08-01 11:34:53 +0000488 while n:
489 self.simple()
490 n = n - 1
Guido van Rossum8ca84201998-03-26 20:56:10 +0000491 f = self.get_time()
492 my_simple = f - s
Guido van Rossumb6775db1994-08-01 11:34:53 +0000493 #print "Simple =", my_simple,
494
495 n = m
Guido van Rossum8ca84201998-03-26 20:56:10 +0000496 s = self.get_time()
Guido van Rossumb6775db1994-08-01 11:34:53 +0000497 while n:
498 self.instrumented()
499 n = n - 1
Guido van Rossum8ca84201998-03-26 20:56:10 +0000500 f = self.get_time()
501 my_inst = f - s
Guido van Rossumb6775db1994-08-01 11:34:53 +0000502 # print "Instrumented =", my_inst
503 avg_cost = (my_inst - my_simple)/m
504 #print "Delta/call =", avg_cost, "(profiler fixup constant)"
505 return avg_cost
506
507 # simulate a program with no profiler activity
Guido van Rossum8ca84201998-03-26 20:56:10 +0000508 def simple(self):
Guido van Rossumb6775db1994-08-01 11:34:53 +0000509 a = 1
510 pass
511
512 # simulate a program with call/return event processing
Guido van Rossum8ca84201998-03-26 20:56:10 +0000513 def instrumented(self):
Guido van Rossumb6775db1994-08-01 11:34:53 +0000514 a = 1
515 self.profiler_simulation(a, a, a)
516
517 # simulate an event processing activity (from user's perspective)
518 def profiler_simulation(self, x, y, z):
519 t = self.timer()
520 t = t[0] + t[1]
521 self.ut = t
522
523
524
525#****************************************************************************
526# OldProfile class documentation
527#****************************************************************************
528#
529# The following derived profiler simulates the old style profile, providing
530# errant results on recursive functions. The reason for the usefulnes of this
531# profiler is that it runs faster (i.e., less overhead). It still creates
532# all the caller stats, and is quite useful when there is *no* recursion
533# in the user's code.
534#
535# This code also shows how easy it is to create a modified profiler.
536#****************************************************************************
537class OldProfile(Profile):
538 def trace_dispatch_exception(self, frame, t):
539 rt, rtt, rct, rfn, rframe, rcur = self.cur
540 if rcur and not rframe is frame:
541 return self.trace_dispatch_return(rframe, t)
542 return 0
543
544 def trace_dispatch_call(self, frame, t):
545 fn = `frame.f_code`
546
547 self.cur = (t, 0, 0, fn, frame, self.cur)
548 if self.timings.has_key(fn):
549 tt, ct, callers = self.timings[fn]
550 self.timings[fn] = tt, ct, callers
551 else:
552 self.timings[fn] = 0, 0, {}
553 return 1
554
555 def trace_dispatch_return(self, frame, t):
556 rt, rtt, rct, rfn, frame, rcur = self.cur
557 rtt = rtt + t
558 sft = rtt + rct
559
560 pt, ptt, pct, pfn, pframe, pcur = rcur
561 self.cur = pt, ptt+rt, pct+sft, pfn, pframe, pcur
562
563 tt, ct, callers = self.timings[rfn]
564 if callers.has_key(pfn):
565 callers[pfn] = callers[pfn] + 1
566 else:
567 callers[pfn] = 1
568 self.timings[rfn] = tt+rtt, ct + sft, callers
569
570 return 1
571
572
573 def snapshot_stats(self):
574 self.stats = {}
575 for func in self.timings.keys():
576 tt, ct, callers = self.timings[func]
577 nor_func = self.func_normalize(func)
578 nor_callers = {}
579 nc = 0
580 for func_caller in callers.keys():
581 nor_callers[self.func_normalize(func_caller)]=\
582 callers[func_caller]
583 nc = nc + callers[func_caller]
584 self.stats[nor_func] = nc, nc, tt, ct, nor_callers
585
586
587
588#****************************************************************************
589# HotProfile class documentation
590#****************************************************************************
591#
592# This profiler is the fastest derived profile example. It does not
593# calculate caller-callee relationships, and does not calculate cumulative
594# time under a function. It only calculates time spent in a function, so
595# it runs very quickly (re: very low overhead)
596#****************************************************************************
597class HotProfile(Profile):
598 def trace_dispatch_exception(self, frame, t):
599 rt, rtt, rfn, rframe, rcur = self.cur
600 if rcur and not rframe is frame:
601 return self.trace_dispatch_return(rframe, t)
602 return 0
603
604 def trace_dispatch_call(self, frame, t):
605 self.cur = (t, 0, frame, self.cur)
606 return 1
607
608 def trace_dispatch_return(self, frame, t):
609 rt, rtt, frame, rcur = self.cur
610
611 rfn = `frame.f_code`
612
613 pt, ptt, pframe, pcur = rcur
614 self.cur = pt, ptt+rt, pframe, pcur
615
616 if self.timings.has_key(rfn):
617 nc, tt = self.timings[rfn]
618 self.timings[rfn] = nc + 1, rt + rtt + tt
619 else:
620 self.timings[rfn] = 1, rt + rtt
621
622 return 1
623
624
625 def snapshot_stats(self):
626 self.stats = {}
627 for func in self.timings.keys():
628 nc, tt = self.timings[func]
629 nor_func = self.func_normalize(func)
630 self.stats[nor_func] = nc, nc, tt, 0, {}
631
632
633
634#****************************************************************************
635def Stats(*args):
636 print 'Report generating functions are in the "pstats" module\a'
Guido van Rossumcc778eb1996-10-01 02:55:54 +0000637
638
639# When invoked as main program, invoke the profiler on a script
640if __name__ == '__main__':
641 import sys
642 import os
643 if not sys.argv[1:]:
644 print "usage: profile.py scriptfile [arg] ..."
645 sys.exit(2)
646
647 filename = sys.argv[1] # Get script filename
648
649 del sys.argv[0] # Hide "profile.py" from argument list
650
651 # Insert script directory in front of module search path
652 sys.path.insert(0, os.path.dirname(filename))
653
654 run('execfile(' + `filename` + ')')