blob: 1c7a6e28ffde76c33ad0fcc83f7262bf720ae079 [file] [log] [blame]
Guido van Rossum81762581992-04-21 15:36:23 +00001#
2# Class for profiling python code.
3# Author: Sjoerd Mullender
4#
5# See the accompanying document profile.doc for more information.
6
7import sys
8import codehack
9import posix
10
11class Profile():
12
13 def init(self):
14 self.timings = {}
15 self.debug = None
16 self.call_level = 0
17 self.profile_func = None
18 self.profiling = 0
19 return self
20
21 def profile(self, funcname):
22 if not self.profile_func:
23 self.profile_func = {}
24 self.profile_func[funcname] = 1
25
26 def trace_dispatch(self, frame, event, arg):
27 if event == 'call':
28 funcname = codehack.getcodename(frame.f_code)
29 if self.profile_func and not self.profiling:
30 if self.profile_func.has_key(funcname):
31 return
32 self.profiling = 1
33 t = posix.times()
34 t = t[0] + t[1]
35 lineno = codehack.getlineno(frame.f_code)
36 filename = frame.f_code.co_filename
37 key = filename + ':' + `lineno` + '(' + funcname + ')'
38 self.call_level = depth(frame)
39 self.cur_frame = frame
40 pframe = frame.f_back
41 if self.debug:
42 s0 = 'call: ' + key + ' depth: ' + `self.call_level` + ' time: ' + `t`
43 if pframe:
44 pkey = pframe.f_code.co_filename + ':' + \
45 `codehack.getlineno(pframe.f_code)` + '(' + \
46 codehack.getcodename(pframe.f_code) + ')'
47 if self.debug:
48 s1 = 'parent: ' + pkey
49 if pframe.f_locals.has_key('__start_time'):
50 st = pframe.f_locals['__start_time']
51 nc, tt, ct, callers, callees = self.timings[pkey]
52 if self.debug:
53 s1 = s1+' before: st='+`st`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct`
54 if callers.has_key(key):
55 callers[key] = callers[key] + 1
56 else:
57 callers[key] = 1
58 if self.debug:
59 s1 = s1+' after: st='+`st`+' nc='+`nc`+' tt='+`tt+(t-st)`+' ct='+`ct`
60 self.timings[pkey] = nc, tt + (t - st), ct, callers, callees
61 if self.timings.has_key(key):
62 nc, tt, ct, callers, callees = self.timings[key]
63 else:
64 nc, tt, ct, callers, callees = 0, 0, 0, {}, {}
65 if self.debug:
66 s0 = s0+' before: nc='+`nc`+' tt='+`tt`+' ct='+`ct`
67 s0 = s0+' after: nc='+`nc+1`+' tt='+`tt`+' ct='+`ct`
68 if pframe:
69 if callees.has_key(pkey):
70 callees[pkey] = callees[pkey] + 1
71 else:
72 callees[pkey] = 1
73 self.timings[key] = nc + 1, tt, ct, callers, callees
74 frame.f_locals['__start_time'] = t
75 if self.debug:
76 print s0
77 print s1
78 return
79 if event == 'return':
80 if self.profile_func:
81 if not self.profiling:
82 return
83 if self.profile_func.has_key(codehack.getcodename(frame.f_code)):
84 self.profiling = 0
85 self.call_level = depth(frame)
86 self.cur_frame = frame
87 pframe = frame.f_back
88 if self.debug:
89 s0 = 'return: '
90 else:
91 s0 = None
92 self.handle_return(pframe, frame, s0)
93 return
94 if event == 'exception':
95 if self.profile_func and not self.profiling:
96 return
97 call_level = depth(frame)
98 if call_level < self.call_level:
99 if call_level <> self.call_level - 1:
100 print 'heh!',call_level,self.call_level
101 if self.debug:
102 s0 = 'exception: '
103 else:
104 s0 = None
105 self.handle_return(self.cur_frame, frame, s0)
106 self.call_level = call_level
107 self.cur_frame = frame
108 return
109 print 'profile.Profile.dispatch: unknown debugging event:', `event`
110 return
111
112 def handle_return(self, pframe, frame, s0):
113 t = posix.times()
114 t = t[0] + t[1]
115 funcname = codehack.getcodename(frame.f_code)
116 lineno = codehack.getlineno(frame.f_code)
117 filename = frame.f_code.co_filename
118 key = filename + ':' + `lineno` + '(' + funcname + ')'
119 if self.debug:
120 s0 = s0 + key + ' depth: ' + `self.call_level` + ' time: ' + `t`
121 if pframe:
122 funcname = codehack.getcodename(frame.f_code)
123 lineno = codehack.getlineno(frame.f_code)
124 filename = frame.f_code.co_filename
125 pkey = filename + ':' + `lineno` + '(' + funcname + ')'
126 if self.debug:
127 s1 = 'parent: '+pkey
128 if pframe.f_locals.has_key('__start_time') and \
129 self.timings.has_key(pkey):
130 st = pframe.f_locals['__start_time']
131 nc, tt, ct, callers, callees = self.timings[pkey]
132 if self.debug:
133 s1 = s1+' before: st='+`st`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct`
134 s1 = s1+' after: st='+`t`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct+(t-st)`
135 self.timings[pkey] = nc, tt, ct + (t - st), callers, callees
136 pframe.f_locals['__start_time'] = t
137 if self.timings.has_key(key):
138 nc, tt, ct, callers, callees = self.timings[key]
139 else:
140 nc, tt, ct, callers, callees = 0, 0, 0, {}, {}
141 if frame.f_locals.has_key('__start_time'):
142 st = frame.f_locals['__start_time']
143 else:
144 st = t
145 if self.debug:
146 s0 = s0+' before: st='+`st`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct`
147 s0 = s0+' after: nc='+`nc`+' tt='+`tt+(t-st)`+' ct='+`ct+(t-st)`
148 print s0
149 print s1
150 self.timings[key] = nc, tt + (t - st), ct + (t - st), callers, callees
151
152 def print_stats(self):
153 import string
154 s = string.rjust('# calls', 8)
155 s = s + ' ' + string.rjust('tot time', 8)
156 s = s + ' ' + string.rjust('per call', 8)
157 s = s + ' ' + string.rjust('cum time', 8)
158 s = s + ' ' + string.rjust('per call', 8)
159 print s + ' filename(function)'
160 for key in self.timings.keys():
161 nc, tt, ct, callers, callees = self.timings[key]
162 if nc == 0:
163 continue
164 s = string.rjust(`nc`, 8)
165 s = s + ' ' + string.rjust(`tt`, 8)
166 s = s + ' ' + string.rjust(`tt/nc`, 8)
167 s = s + ' ' + string.rjust(`ct`, 8)
168 s = s + ' ' + string.rjust(`ct/nc`, 8)
169 print s + ' ' + key
170
171 def dump_stats(self, file):
172 import marshal
173 f = open(file, 'w')
174 marshal.dump(self.timings, f)
175 f.close()
176
177 # The following two functions can be called by clients to use
178 # a debugger to debug a statement, given as a string.
179
180 def run(self, cmd):
181 import __main__
182 dict = __main__.__dict__
183 self.runctx(cmd, dict, dict)
184
185 def runctx(self, cmd, globals, locals):
186## self.reset()
187 sys.setprofile(self.trace_dispatch)
188 try:
189## try:
190 exec(cmd + '\n', globals, locals)
191## except ProfQuit:
192## pass
193 finally:
194## self.quitting = 1
195 sys.setprofile(None)
196 # XXX What to do if the command finishes normally?
197
198def depth(frame):
199 d = 0
200 while frame:
201 d = d + 1
202 frame = frame.f_back
203 return d
204
205def run(statement, *args):
206 prof = Profile().init()
207 try:
208 prof.run(statement)
209 except SystemExit:
210 pass
211 if len(args) == 0:
212 prof.print_stats()
213 else:
214 prof.dump_stats(args[0])
215
216def cv_float(val, width):
217 import string
218 s = `val`
219 try:
220 e = string.index(s, 'e')
221 exp = s[e+1:]
222 s = s[:e]
223 width = width - len(exp) - 1
224 except string.index_error:
225 exp = ''
226 try:
227 d = string.index(s, '.')
228 frac = s[d+1:]
229 s = s[:d]
230 width = width - len(s) - 1
231 except string.index_error:
232 if exp <> '':
233 return s + 'e' + exp
234 else:
235 return s
236 if width < 0:
237 width = 0
238 while width < len(frac):
239 c = frac[width]
240 frac = frac[:width]
241 if ord(c) >= ord('5'):
242 carry = 1
243 for i in range(width-1, -1, -1):
244 if frac[i] == '9':
245 frac = frac[:i]
246 # keep if trailing zeroes are wanted
247 # + '0' + frac[i+1:width]
248 else:
249 frac = frac[:i] + chr(ord(frac[i])+1) + frac[i+1:width]
250 carry = 0
251 break
252 if carry:
253 for i in range(len(s)-1, -1, -1):
254 if s[i] == '9':
255 s = s[:i] + '0' + s[i+1:]
256 if i == 0:
257 # gets one wider, so
258 # should shorten
259 # fraction by one
260 s = '1' + s
261 if width > 0:
262 width = width - 1
263 else:
264 s = s[:i] + chr(ord(s[i])+1) + s[i+1:]
265 break
266 # delete trailing zeroes
267 for i in range(len(frac)-1, -1, -1):
268 if frac[i] == '0':
269 frac = frac[:i]
270 else:
271 break
272 # build up the number
273 if width > 0 and len(frac) > 0:
274 s = s + '.' + frac[:width]
275 if exp <> '':
276 s = s + 'e' + exp
277 return s
278
279def print_line(nc, tt, ct, callers, callees, key):
280 import string
281 s = string.rjust(cv_float(nc,8), 8)
282 s = s + ' ' + string.rjust(cv_float(tt,8), 8)
283 if nc == 0:
284 s = s + ' '*9
285 else:
286 s = s + ' ' + string.rjust(cv_float(tt/nc,8), 8)
287 s = s + ' ' + string.rjust(cv_float(ct,8), 8)
288 if nc == 0:
289 s = s + ' '*9
290 else:
291 s = s + ' ' + string.rjust(cv_float(ct/nc,8), 8)
292 print s + ' ' + key
293
294class Stats():
295 def init(self, file):
296 import marshal
297 f = open(file, 'r')
298 self.stats = marshal.load(f)
299 f.close()
300 self.stats_list = None
301 return self
302
303 def print_stats(self):
304 import string
305 s = string.rjust('# calls', 8)
306 s = s + ' ' + string.rjust('tot time', 8)
307 s = s + ' ' + string.rjust('per call', 8)
308 s = s + ' ' + string.rjust('cum time', 8)
309 s = s + ' ' + string.rjust('per call', 8)
310 print s + ' filename(function)'
311 if self.stats_list:
312 for i in range(len(self.stats_list)):
313 nc, tt, ct, callers, callees, key = self.stats_list[i]
314 print_line(nc, tt, ct, callers, callees, key)
315 else:
316 for key in self.stats.keys():
317 nc, tt, ct, callers, callees = self.stats[key]
318 print_line(nc, tt, ct, callers, callees, key)
319
320 def print_callers(self):
321 if self.stats_list:
322 for i in range(len(self.stats_list)):
323 nc, tt, ct, callers, callees, key = self.stats_list[i]
324 print key,
325 for func in callers.keys():
326 print func+'('+`callers[func]`+')',
327 print
328 else:
329 for key in self.stats.keys():
330 nc, tt, ct, callers, callees = self.stats[key]
331 print key,
332 for func in callers.keys():
333 print func+'('+`callers[func]`+')',
334 print
335
336 def print_callees(self):
337 if self.stats_list:
338 for i in range(len(self.stats_list)):
339 nc, tt, ct, callers, callees, key = self.stats_list[i]
340 print key,
341 for func in callees.keys():
342 print func+'('+`callees[func]`+')',
343 print
344 else:
345 for key in self.stats.keys():
346 nc, tt, ct, callers, callees = self.stats[key]
347 print key,
348 for func in callees.keys():
349 print func+'('+`callees[func]`+')',
350 print
351
352 def sort_stats(self, field):
353 stats_list = []
354 for key in self.stats.keys():
355 t = self.stats[key]
356 nt = ()
357 for i in range(len(t)):
358 if i == field:
359 nt = (t[i],) + nt[:]
360 else:
361 nt = nt[:] + (t[i],)
362 if field == -1:
363 nt = (key,) + nt
364 else:
365 nt = nt + (key,)
366 stats_list.append(nt)
367 stats_list.sort()
368 self.stats_list = []
369 for i in range(len(stats_list)):
370 t = stats_list[i]
371 if field == -1:
372 nt = t[1:] + t[0:1]
373 else:
374 nt = t[1:]
375 nt = nt[:field] + t[0:1] + nt[field:]
376 self.stats_list.append(nt)
377
378 def strip_dirs(self):
379 import os
380 newstats = {}
381 for key in self.stats.keys():
382 nc, tt, ct, callers, callees = self.stats[key]
383 newkey = os.path.basename(key)
384 newcallers = {}
385 for c in callers.keys():
386 newcallers[os.path.basename(c)] = callers[c]
387 newcallees = {}
388 for c in callees.keys():
389 newcallees[os.path.basename(c)] = callees[c]
390 newstats[newkey] = nc, tt, ct, newcallers, newcallees
391 self.stats = newstats
392 self.stats_list = None
393
394# test command
395def debug():
396 prof = Profile().init()
397 prof.debug = 1
398 try:
399 prof.run('import x; x.main()')
400 except SystemExit:
401 pass
402 prof.print_stats()
403
404def test():
405 run('import x; x.main()')