blob: 502a4dbfa727f1189e7c0dd0b2809b03b31da5e8 [file] [log] [blame]
Guido van Rossum81762581992-04-21 15:36:23 +00001#
2# Class for profiling python code.
3# Author: Sjoerd Mullender
Guido van Rossum4e160981992-09-02 20:43:20 +00004# Hacked somewhat by: Guido van Rossum
Guido van Rossum81762581992-04-21 15:36:23 +00005#
6# See the accompanying document profile.doc for more information.
7
8import sys
9import codehack
Guido van Rossum4e160981992-09-02 20:43:20 +000010import os
11import string
12import fpformat
13import marshal
Guido van Rossum81762581992-04-21 15:36:23 +000014
Guido van Rossumd3166071993-05-24 14:16:22 +000015class Profile:
Guido van Rossum81762581992-04-21 15:36:23 +000016
Guido van Rossum7bc817d1993-12-17 15:25:27 +000017 def __init__(self):
Guido van Rossum81762581992-04-21 15:36:23 +000018 self.timings = {}
19 self.debug = None
20 self.call_level = 0
21 self.profile_func = None
22 self.profiling = 0
Guido van Rossum81762581992-04-21 15:36:23 +000023
24 def profile(self, funcname):
25 if not self.profile_func:
26 self.profile_func = {}
27 self.profile_func[funcname] = 1
28
29 def trace_dispatch(self, frame, event, arg):
30 if event == 'call':
31 funcname = codehack.getcodename(frame.f_code)
32 if self.profile_func and not self.profiling:
33 if self.profile_func.has_key(funcname):
34 return
35 self.profiling = 1
Guido van Rossum4e160981992-09-02 20:43:20 +000036 t = os.times()
Guido van Rossum81762581992-04-21 15:36:23 +000037 t = t[0] + t[1]
Sjoerd Mullender4fddf331993-08-25 14:09:01 +000038 if frame.f_locals.has_key('__key'):
39 key = frame.f_locals['__key']
40 else:
41 lineno = codehack.getlineno(frame.f_code)
42 filename = frame.f_code.co_filename
43 key = filename + ':' + `lineno` + '(' + funcname + ')'
44 frame.f_locals['__key'] = key
Guido van Rossum81762581992-04-21 15:36:23 +000045 self.call_level = depth(frame)
46 self.cur_frame = frame
47 pframe = frame.f_back
48 if self.debug:
49 s0 = 'call: ' + key + ' depth: ' + `self.call_level` + ' time: ' + `t`
50 if pframe:
Sjoerd Mullender4fddf331993-08-25 14:09:01 +000051 if pframe.f_locals.has_key('__key'):
52 pkey = pframe.f_locals['__key']
53 else:
54 pkey = pframe.f_code.co_filename + \
55 ':' + \
56 `codehack.getlineno(pframe.f_code)` \
57 + '(' + \
58 codehack.getcodename(pframe.f_code) \
59 + ')'
60 pframe.f_locals['__key'] = pkey
Guido van Rossum81762581992-04-21 15:36:23 +000061 if self.debug:
62 s1 = 'parent: ' + pkey
63 if pframe.f_locals.has_key('__start_time'):
64 st = pframe.f_locals['__start_time']
Guido van Rossum4e160981992-09-02 20:43:20 +000065 nc, tt, ct, callers, callees = \
66 self.timings[pkey]
Guido van Rossum81762581992-04-21 15:36:23 +000067 if self.debug:
68 s1 = s1+' before: st='+`st`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct`
69 if callers.has_key(key):
70 callers[key] = callers[key] + 1
71 else:
72 callers[key] = 1
73 if self.debug:
74 s1 = s1+' after: st='+`st`+' nc='+`nc`+' tt='+`tt+(t-st)`+' ct='+`ct`
75 self.timings[pkey] = nc, tt + (t - st), ct, callers, callees
76 if self.timings.has_key(key):
77 nc, tt, ct, callers, callees = self.timings[key]
78 else:
79 nc, tt, ct, callers, callees = 0, 0, 0, {}, {}
80 if self.debug:
81 s0 = s0+' before: nc='+`nc`+' tt='+`tt`+' ct='+`ct`
82 s0 = s0+' after: nc='+`nc+1`+' tt='+`tt`+' ct='+`ct`
83 if pframe:
84 if callees.has_key(pkey):
85 callees[pkey] = callees[pkey] + 1
86 else:
87 callees[pkey] = 1
88 self.timings[key] = nc + 1, tt, ct, callers, callees
89 frame.f_locals['__start_time'] = t
90 if self.debug:
91 print s0
92 print s1
93 return
94 if event == 'return':
95 if self.profile_func:
96 if not self.profiling:
97 return
Guido van Rossum4e160981992-09-02 20:43:20 +000098 if self.profile_func.has_key( \
99 codehack.getcodename(frame.f_code)):
Guido van Rossum81762581992-04-21 15:36:23 +0000100 self.profiling = 0
101 self.call_level = depth(frame)
102 self.cur_frame = frame
103 pframe = frame.f_back
104 if self.debug:
105 s0 = 'return: '
106 else:
107 s0 = None
108 self.handle_return(pframe, frame, s0)
109 return
110 if event == 'exception':
111 if self.profile_func and not self.profiling:
112 return
113 call_level = depth(frame)
114 if call_level < self.call_level:
115 if call_level <> self.call_level - 1:
116 print 'heh!',call_level,self.call_level
117 if self.debug:
118 s0 = 'exception: '
119 else:
120 s0 = None
121 self.handle_return(self.cur_frame, frame, s0)
122 self.call_level = call_level
123 self.cur_frame = frame
124 return
Guido van Rossum4e160981992-09-02 20:43:20 +0000125 print 'profile.Profile.dispatch: unknown debugging event:',
126 print `event`
Guido van Rossum81762581992-04-21 15:36:23 +0000127 return
128
129 def handle_return(self, pframe, frame, s0):
Guido van Rossum4e160981992-09-02 20:43:20 +0000130 t = os.times()
Guido van Rossum81762581992-04-21 15:36:23 +0000131 t = t[0] + t[1]
Sjoerd Mullender4fddf331993-08-25 14:09:01 +0000132 if frame.f_locals.has_key('__key'):
133 key = frame.f_locals['__key']
134 else:
Guido van Rossum81762581992-04-21 15:36:23 +0000135 funcname = codehack.getcodename(frame.f_code)
136 lineno = codehack.getlineno(frame.f_code)
137 filename = frame.f_code.co_filename
Sjoerd Mullender4fddf331993-08-25 14:09:01 +0000138 key = filename + ':' + `lineno` + '(' + funcname + ')'
139 frame.f_locals['__key'] = key
140 if self.debug:
141 s0 = s0 + key + ' depth: ' + `self.call_level` + ' time: ' + `t`
142 if pframe:
143 if pframe.f_locals.has_key('__key'):
144 pkey = pframe.f_locals['__key']
145 else:
146 funcname = codehack.getcodename(frame.f_code)
147 lineno = codehack.getlineno(frame.f_code)
148 filename = frame.f_code.co_filename
149 pkey = filename + ':' + `lineno` + '(' + funcname + ')'
150 pframe.f_locals['__key'] = pkey
Guido van Rossum81762581992-04-21 15:36:23 +0000151 if self.debug:
152 s1 = 'parent: '+pkey
153 if pframe.f_locals.has_key('__start_time') and \
154 self.timings.has_key(pkey):
155 st = pframe.f_locals['__start_time']
Guido van Rossum4e160981992-09-02 20:43:20 +0000156 nc, tt, ct, callers, callees = \
157 self.timings[pkey]
Guido van Rossum81762581992-04-21 15:36:23 +0000158 if self.debug:
159 s1 = s1+' before: st='+`st`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct`
160 s1 = s1+' after: st='+`t`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct+(t-st)`
Guido van Rossum4e160981992-09-02 20:43:20 +0000161 self.timings[pkey] = \
162 nc, tt, ct + (t - st), callers, callees
Guido van Rossum81762581992-04-21 15:36:23 +0000163 pframe.f_locals['__start_time'] = t
164 if self.timings.has_key(key):
165 nc, tt, ct, callers, callees = self.timings[key]
166 else:
167 nc, tt, ct, callers, callees = 0, 0, 0, {}, {}
168 if frame.f_locals.has_key('__start_time'):
169 st = frame.f_locals['__start_time']
170 else:
171 st = t
172 if self.debug:
173 s0 = s0+' before: st='+`st`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct`
174 s0 = s0+' after: nc='+`nc`+' tt='+`tt+(t-st)`+' ct='+`ct+(t-st)`
175 print s0
176 print s1
Guido van Rossum4e160981992-09-02 20:43:20 +0000177 self.timings[key] = \
178 nc, tt + (t - st), ct + (t - st), callers, callees
Guido van Rossum81762581992-04-21 15:36:23 +0000179
180 def print_stats(self):
Guido van Rossum4e160981992-09-02 20:43:20 +0000181 # Print in reverse order by ct
182 print_title()
183 list = []
Guido van Rossum81762581992-04-21 15:36:23 +0000184 for key in self.timings.keys():
185 nc, tt, ct, callers, callees = self.timings[key]
186 if nc == 0:
187 continue
Guido van Rossum4e160981992-09-02 20:43:20 +0000188 list.append(ct, tt, nc, key)
189 list.sort()
190 list.reverse()
191 for ct, tt, nc, key in list:
192 print_line(nc, tt, ct, os.path.basename(key))
Guido van Rossum81762581992-04-21 15:36:23 +0000193
194 def dump_stats(self, file):
Guido van Rossum81762581992-04-21 15:36:23 +0000195 f = open(file, 'w')
196 marshal.dump(self.timings, f)
197 f.close()
198
Guido van Rossum4e160981992-09-02 20:43:20 +0000199 # The following two methods can be called by clients to use
200 # a profiler to profile a statement, given as a string.
Guido van Rossum81762581992-04-21 15:36:23 +0000201
202 def run(self, cmd):
203 import __main__
204 dict = __main__.__dict__
205 self.runctx(cmd, dict, dict)
206
207 def runctx(self, cmd, globals, locals):
Guido van Rossum81762581992-04-21 15:36:23 +0000208 sys.setprofile(self.trace_dispatch)
209 try:
Guido van Rossum4e160981992-09-02 20:43:20 +0000210 exec(cmd + '\n', globals, locals)
Guido van Rossum81762581992-04-21 15:36:23 +0000211 finally:
Guido van Rossum81762581992-04-21 15:36:23 +0000212 sys.setprofile(None)
Guido van Rossum4e160981992-09-02 20:43:20 +0000213
214 # This method is more useful to profile a single function call.
215
216 def runcall(self, func, *args):
217 sys.setprofile(self.trace_dispatch)
218 try:
219 apply(func, args)
220 finally:
221 sys.setprofile(None)
222
Guido van Rossum81762581992-04-21 15:36:23 +0000223
224def depth(frame):
225 d = 0
226 while frame:
227 d = d + 1
228 frame = frame.f_back
229 return d
230
Guido van Rossumd3166071993-05-24 14:16:22 +0000231class Stats:
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000232 def __init__(self, file):
Guido van Rossum81762581992-04-21 15:36:23 +0000233 f = open(file, 'r')
234 self.stats = marshal.load(f)
235 f.close()
236 self.stats_list = None
Guido van Rossum81762581992-04-21 15:36:23 +0000237
238 def print_stats(self):
Guido van Rossum4e160981992-09-02 20:43:20 +0000239 print_title()
Guido van Rossum81762581992-04-21 15:36:23 +0000240 if self.stats_list:
241 for i in range(len(self.stats_list)):
Guido van Rossum4e160981992-09-02 20:43:20 +0000242 nc, tt, ct, callers, callees, key = \
243 self.stats_list[i]
244 print_line(nc, tt, ct, key)
Guido van Rossum81762581992-04-21 15:36:23 +0000245 else:
246 for key in self.stats.keys():
247 nc, tt, ct, callers, callees = self.stats[key]
Guido van Rossum4e160981992-09-02 20:43:20 +0000248 print_line(nc, tt, ct, key)
Guido van Rossum81762581992-04-21 15:36:23 +0000249
250 def print_callers(self):
251 if self.stats_list:
252 for i in range(len(self.stats_list)):
Guido van Rossum4e160981992-09-02 20:43:20 +0000253 nc, tt, ct, callers, callees, key = \
254 self.stats_list[i]
Guido van Rossum81762581992-04-21 15:36:23 +0000255 print key,
256 for func in callers.keys():
257 print func+'('+`callers[func]`+')',
258 print
259 else:
260 for key in self.stats.keys():
261 nc, tt, ct, callers, callees = self.stats[key]
262 print key,
263 for func in callers.keys():
264 print func+'('+`callers[func]`+')',
265 print
266
267 def print_callees(self):
268 if self.stats_list:
269 for i in range(len(self.stats_list)):
Guido van Rossum4e160981992-09-02 20:43:20 +0000270 nc, tt, ct, callers, callees, key = \
271 self.stats_list[i]
Guido van Rossum81762581992-04-21 15:36:23 +0000272 print key,
273 for func in callees.keys():
274 print func+'('+`callees[func]`+')',
275 print
276 else:
277 for key in self.stats.keys():
278 nc, tt, ct, callers, callees = self.stats[key]
279 print key,
280 for func in callees.keys():
281 print func+'('+`callees[func]`+')',
282 print
283
284 def sort_stats(self, field):
285 stats_list = []
286 for key in self.stats.keys():
287 t = self.stats[key]
288 nt = ()
289 for i in range(len(t)):
290 if i == field:
291 nt = (t[i],) + nt[:]
292 else:
293 nt = nt[:] + (t[i],)
294 if field == -1:
295 nt = (key,) + nt
296 else:
297 nt = nt + (key,)
298 stats_list.append(nt)
299 stats_list.sort()
300 self.stats_list = []
301 for i in range(len(stats_list)):
302 t = stats_list[i]
303 if field == -1:
304 nt = t[1:] + t[0:1]
305 else:
306 nt = t[1:]
307 nt = nt[:field] + t[0:1] + nt[field:]
308 self.stats_list.append(nt)
309
Guido van Rossum4e160981992-09-02 20:43:20 +0000310 def reverse_order(self):
311 self.stats_list.reverse()
312
Guido van Rossum81762581992-04-21 15:36:23 +0000313 def strip_dirs(self):
Guido van Rossum81762581992-04-21 15:36:23 +0000314 newstats = {}
315 for key in self.stats.keys():
316 nc, tt, ct, callers, callees = self.stats[key]
317 newkey = os.path.basename(key)
318 newcallers = {}
319 for c in callers.keys():
320 newcallers[os.path.basename(c)] = callers[c]
321 newcallees = {}
322 for c in callees.keys():
323 newcallees[os.path.basename(c)] = callees[c]
324 newstats[newkey] = nc, tt, ct, newcallers, newcallees
325 self.stats = newstats
326 self.stats_list = None
327
Guido van Rossum4e160981992-09-02 20:43:20 +0000328def print_title():
329 print string.rjust('ncalls', 8),
330 print string.rjust('tottime', 8),
331 print string.rjust('percall', 8),
332 print string.rjust('cumtime', 8),
333 print string.rjust('percall', 8),
334 print 'filename:lineno(function)'
335
336def print_line(nc, tt, ct, key):
337 print string.rjust(`nc`, 8),
338 print f8(tt),
339 if nc == 0:
340 print ' '*8,
341 else:
342 print f8(tt/nc),
343 print f8(ct),
344 if nc == 0:
345 print ' '*8,
346 else:
347 print f8(ct/nc),
348 print key
349
350def f8(x):
351 return string.rjust(fpformat.fix(x, 3), 8)
352
353# simplified user interface
354def run(statement, *args):
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000355 prof = Profile()
Guido van Rossum4e160981992-09-02 20:43:20 +0000356 try:
357 prof.run(statement)
358 except SystemExit:
359 pass
360 if len(args) == 0:
361 prof.print_stats()
362 else:
363 prof.dump_stats(args[0])
364
365# test command with debugging
Guido van Rossum81762581992-04-21 15:36:23 +0000366def debug():
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000367 prof = Profile()
Guido van Rossum81762581992-04-21 15:36:23 +0000368 prof.debug = 1
369 try:
370 prof.run('import x; x.main()')
371 except SystemExit:
372 pass
373 prof.print_stats()
374
Guido van Rossum4e160981992-09-02 20:43:20 +0000375# test command
Guido van Rossum81762581992-04-21 15:36:23 +0000376def test():
377 run('import x; x.main()')
Guido van Rossume61fa0a1993-10-22 13:56:35 +0000378
379# print help
380def help():
381 for dirname in sys.path:
382 fullname = os.path.join(dirname, 'profile.doc')
383 if os.path.exists(fullname):
384 sts = os.system('${PAGER-more} '+fullname)
385 if sts: print '*** Pager exit status:', sts
386 break
387 else:
388 print 'Sorry, can\'t find the help file "profile.doc"',
389 print 'along the Python search path'