blob: 046b70b24abc40aa4e63f6788d674339627ed887 [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
15class Profile():
16
17 def init(self):
18 self.timings = {}
19 self.debug = None
20 self.call_level = 0
21 self.profile_func = None
22 self.profiling = 0
23 return self
24
25 def profile(self, funcname):
26 if not self.profile_func:
27 self.profile_func = {}
28 self.profile_func[funcname] = 1
29
30 def trace_dispatch(self, frame, event, arg):
31 if event == 'call':
32 funcname = codehack.getcodename(frame.f_code)
33 if self.profile_func and not self.profiling:
34 if self.profile_func.has_key(funcname):
35 return
36 self.profiling = 1
Guido van Rossum4e160981992-09-02 20:43:20 +000037 t = os.times()
Guido van Rossum81762581992-04-21 15:36:23 +000038 t = t[0] + t[1]
39 lineno = codehack.getlineno(frame.f_code)
40 filename = frame.f_code.co_filename
41 key = filename + ':' + `lineno` + '(' + funcname + ')'
42 self.call_level = depth(frame)
43 self.cur_frame = frame
44 pframe = frame.f_back
45 if self.debug:
46 s0 = 'call: ' + key + ' depth: ' + `self.call_level` + ' time: ' + `t`
47 if pframe:
48 pkey = pframe.f_code.co_filename + ':' + \
Guido van Rossum4e160981992-09-02 20:43:20 +000049 `codehack.getlineno(pframe.f_code)` \
50 + '(' + \
51 codehack.getcodename(pframe.f_code) \
52 + ')'
Guido van Rossum81762581992-04-21 15:36:23 +000053 if self.debug:
54 s1 = 'parent: ' + pkey
55 if pframe.f_locals.has_key('__start_time'):
56 st = pframe.f_locals['__start_time']
Guido van Rossum4e160981992-09-02 20:43:20 +000057 nc, tt, ct, callers, callees = \
58 self.timings[pkey]
Guido van Rossum81762581992-04-21 15:36:23 +000059 if self.debug:
60 s1 = s1+' before: st='+`st`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct`
61 if callers.has_key(key):
62 callers[key] = callers[key] + 1
63 else:
64 callers[key] = 1
65 if self.debug:
66 s1 = s1+' after: st='+`st`+' nc='+`nc`+' tt='+`tt+(t-st)`+' ct='+`ct`
67 self.timings[pkey] = nc, tt + (t - st), ct, callers, callees
68 if self.timings.has_key(key):
69 nc, tt, ct, callers, callees = self.timings[key]
70 else:
71 nc, tt, ct, callers, callees = 0, 0, 0, {}, {}
72 if self.debug:
73 s0 = s0+' before: nc='+`nc`+' tt='+`tt`+' ct='+`ct`
74 s0 = s0+' after: nc='+`nc+1`+' tt='+`tt`+' ct='+`ct`
75 if pframe:
76 if callees.has_key(pkey):
77 callees[pkey] = callees[pkey] + 1
78 else:
79 callees[pkey] = 1
80 self.timings[key] = nc + 1, tt, ct, callers, callees
81 frame.f_locals['__start_time'] = t
82 if self.debug:
83 print s0
84 print s1
85 return
86 if event == 'return':
87 if self.profile_func:
88 if not self.profiling:
89 return
Guido van Rossum4e160981992-09-02 20:43:20 +000090 if self.profile_func.has_key( \
91 codehack.getcodename(frame.f_code)):
Guido van Rossum81762581992-04-21 15:36:23 +000092 self.profiling = 0
93 self.call_level = depth(frame)
94 self.cur_frame = frame
95 pframe = frame.f_back
96 if self.debug:
97 s0 = 'return: '
98 else:
99 s0 = None
100 self.handle_return(pframe, frame, s0)
101 return
102 if event == 'exception':
103 if self.profile_func and not self.profiling:
104 return
105 call_level = depth(frame)
106 if call_level < self.call_level:
107 if call_level <> self.call_level - 1:
108 print 'heh!',call_level,self.call_level
109 if self.debug:
110 s0 = 'exception: '
111 else:
112 s0 = None
113 self.handle_return(self.cur_frame, frame, s0)
114 self.call_level = call_level
115 self.cur_frame = frame
116 return
Guido van Rossum4e160981992-09-02 20:43:20 +0000117 print 'profile.Profile.dispatch: unknown debugging event:',
118 print `event`
Guido van Rossum81762581992-04-21 15:36:23 +0000119 return
120
121 def handle_return(self, pframe, frame, s0):
Guido van Rossum4e160981992-09-02 20:43:20 +0000122 t = os.times()
Guido van Rossum81762581992-04-21 15:36:23 +0000123 t = t[0] + t[1]
124 funcname = codehack.getcodename(frame.f_code)
125 lineno = codehack.getlineno(frame.f_code)
126 filename = frame.f_code.co_filename
127 key = filename + ':' + `lineno` + '(' + funcname + ')'
128 if self.debug:
129 s0 = s0 + key + ' depth: ' + `self.call_level` + ' time: ' + `t`
130 if pframe:
131 funcname = codehack.getcodename(frame.f_code)
132 lineno = codehack.getlineno(frame.f_code)
133 filename = frame.f_code.co_filename
134 pkey = filename + ':' + `lineno` + '(' + funcname + ')'
135 if self.debug:
136 s1 = 'parent: '+pkey
137 if pframe.f_locals.has_key('__start_time') and \
138 self.timings.has_key(pkey):
139 st = pframe.f_locals['__start_time']
Guido van Rossum4e160981992-09-02 20:43:20 +0000140 nc, tt, ct, callers, callees = \
141 self.timings[pkey]
Guido van Rossum81762581992-04-21 15:36:23 +0000142 if self.debug:
143 s1 = s1+' before: st='+`st`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct`
144 s1 = s1+' after: st='+`t`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct+(t-st)`
Guido van Rossum4e160981992-09-02 20:43:20 +0000145 self.timings[pkey] = \
146 nc, tt, ct + (t - st), callers, callees
Guido van Rossum81762581992-04-21 15:36:23 +0000147 pframe.f_locals['__start_time'] = t
148 if self.timings.has_key(key):
149 nc, tt, ct, callers, callees = self.timings[key]
150 else:
151 nc, tt, ct, callers, callees = 0, 0, 0, {}, {}
152 if frame.f_locals.has_key('__start_time'):
153 st = frame.f_locals['__start_time']
154 else:
155 st = t
156 if self.debug:
157 s0 = s0+' before: st='+`st`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct`
158 s0 = s0+' after: nc='+`nc`+' tt='+`tt+(t-st)`+' ct='+`ct+(t-st)`
159 print s0
160 print s1
Guido van Rossum4e160981992-09-02 20:43:20 +0000161 self.timings[key] = \
162 nc, tt + (t - st), ct + (t - st), callers, callees
Guido van Rossum81762581992-04-21 15:36:23 +0000163
164 def print_stats(self):
Guido van Rossum4e160981992-09-02 20:43:20 +0000165 # Print in reverse order by ct
166 print_title()
167 list = []
Guido van Rossum81762581992-04-21 15:36:23 +0000168 for key in self.timings.keys():
169 nc, tt, ct, callers, callees = self.timings[key]
170 if nc == 0:
171 continue
Guido van Rossum4e160981992-09-02 20:43:20 +0000172 list.append(ct, tt, nc, key)
173 list.sort()
174 list.reverse()
175 for ct, tt, nc, key in list:
176 print_line(nc, tt, ct, os.path.basename(key))
Guido van Rossum81762581992-04-21 15:36:23 +0000177
178 def dump_stats(self, file):
Guido van Rossum81762581992-04-21 15:36:23 +0000179 f = open(file, 'w')
180 marshal.dump(self.timings, f)
181 f.close()
182
Guido van Rossum4e160981992-09-02 20:43:20 +0000183 # The following two methods can be called by clients to use
184 # a profiler to profile a statement, given as a string.
Guido van Rossum81762581992-04-21 15:36:23 +0000185
186 def run(self, cmd):
187 import __main__
188 dict = __main__.__dict__
189 self.runctx(cmd, dict, dict)
190
191 def runctx(self, cmd, globals, locals):
Guido van Rossum81762581992-04-21 15:36:23 +0000192 sys.setprofile(self.trace_dispatch)
193 try:
Guido van Rossum4e160981992-09-02 20:43:20 +0000194 exec(cmd + '\n', globals, locals)
Guido van Rossum81762581992-04-21 15:36:23 +0000195 finally:
Guido van Rossum81762581992-04-21 15:36:23 +0000196 sys.setprofile(None)
Guido van Rossum4e160981992-09-02 20:43:20 +0000197
198 # This method is more useful to profile a single function call.
199
200 def runcall(self, func, *args):
201 sys.setprofile(self.trace_dispatch)
202 try:
203 apply(func, args)
204 finally:
205 sys.setprofile(None)
206
Guido van Rossum81762581992-04-21 15:36:23 +0000207
208def depth(frame):
209 d = 0
210 while frame:
211 d = d + 1
212 frame = frame.f_back
213 return d
214
Guido van Rossum81762581992-04-21 15:36:23 +0000215class Stats():
216 def init(self, file):
Guido van Rossum81762581992-04-21 15:36:23 +0000217 f = open(file, 'r')
218 self.stats = marshal.load(f)
219 f.close()
220 self.stats_list = None
221 return self
222
223 def print_stats(self):
Guido van Rossum4e160981992-09-02 20:43:20 +0000224 print_title()
Guido van Rossum81762581992-04-21 15:36:23 +0000225 if self.stats_list:
226 for i in range(len(self.stats_list)):
Guido van Rossum4e160981992-09-02 20:43:20 +0000227 nc, tt, ct, callers, callees, key = \
228 self.stats_list[i]
229 print_line(nc, tt, ct, key)
Guido van Rossum81762581992-04-21 15:36:23 +0000230 else:
231 for key in self.stats.keys():
232 nc, tt, ct, callers, callees = self.stats[key]
Guido van Rossum4e160981992-09-02 20:43:20 +0000233 print_line(nc, tt, ct, key)
Guido van Rossum81762581992-04-21 15:36:23 +0000234
235 def print_callers(self):
236 if self.stats_list:
237 for i in range(len(self.stats_list)):
Guido van Rossum4e160981992-09-02 20:43:20 +0000238 nc, tt, ct, callers, callees, key = \
239 self.stats_list[i]
Guido van Rossum81762581992-04-21 15:36:23 +0000240 print key,
241 for func in callers.keys():
242 print func+'('+`callers[func]`+')',
243 print
244 else:
245 for key in self.stats.keys():
246 nc, tt, ct, callers, callees = self.stats[key]
247 print key,
248 for func in callers.keys():
249 print func+'('+`callers[func]`+')',
250 print
251
252 def print_callees(self):
253 if self.stats_list:
254 for i in range(len(self.stats_list)):
Guido van Rossum4e160981992-09-02 20:43:20 +0000255 nc, tt, ct, callers, callees, key = \
256 self.stats_list[i]
Guido van Rossum81762581992-04-21 15:36:23 +0000257 print key,
258 for func in callees.keys():
259 print func+'('+`callees[func]`+')',
260 print
261 else:
262 for key in self.stats.keys():
263 nc, tt, ct, callers, callees = self.stats[key]
264 print key,
265 for func in callees.keys():
266 print func+'('+`callees[func]`+')',
267 print
268
269 def sort_stats(self, field):
270 stats_list = []
271 for key in self.stats.keys():
272 t = self.stats[key]
273 nt = ()
274 for i in range(len(t)):
275 if i == field:
276 nt = (t[i],) + nt[:]
277 else:
278 nt = nt[:] + (t[i],)
279 if field == -1:
280 nt = (key,) + nt
281 else:
282 nt = nt + (key,)
283 stats_list.append(nt)
284 stats_list.sort()
285 self.stats_list = []
286 for i in range(len(stats_list)):
287 t = stats_list[i]
288 if field == -1:
289 nt = t[1:] + t[0:1]
290 else:
291 nt = t[1:]
292 nt = nt[:field] + t[0:1] + nt[field:]
293 self.stats_list.append(nt)
294
Guido van Rossum4e160981992-09-02 20:43:20 +0000295 def reverse_order(self):
296 self.stats_list.reverse()
297
Guido van Rossum81762581992-04-21 15:36:23 +0000298 def strip_dirs(self):
Guido van Rossum81762581992-04-21 15:36:23 +0000299 newstats = {}
300 for key in self.stats.keys():
301 nc, tt, ct, callers, callees = self.stats[key]
302 newkey = os.path.basename(key)
303 newcallers = {}
304 for c in callers.keys():
305 newcallers[os.path.basename(c)] = callers[c]
306 newcallees = {}
307 for c in callees.keys():
308 newcallees[os.path.basename(c)] = callees[c]
309 newstats[newkey] = nc, tt, ct, newcallers, newcallees
310 self.stats = newstats
311 self.stats_list = None
312
Guido van Rossum4e160981992-09-02 20:43:20 +0000313def print_title():
314 print string.rjust('ncalls', 8),
315 print string.rjust('tottime', 8),
316 print string.rjust('percall', 8),
317 print string.rjust('cumtime', 8),
318 print string.rjust('percall', 8),
319 print 'filename:lineno(function)'
320
321def print_line(nc, tt, ct, key):
322 print string.rjust(`nc`, 8),
323 print f8(tt),
324 if nc == 0:
325 print ' '*8,
326 else:
327 print f8(tt/nc),
328 print f8(ct),
329 if nc == 0:
330 print ' '*8,
331 else:
332 print f8(ct/nc),
333 print key
334
335def f8(x):
336 return string.rjust(fpformat.fix(x, 3), 8)
337
338# simplified user interface
339def run(statement, *args):
340 prof = Profile().init()
341 try:
342 prof.run(statement)
343 except SystemExit:
344 pass
345 if len(args) == 0:
346 prof.print_stats()
347 else:
348 prof.dump_stats(args[0])
349
350# test command with debugging
Guido van Rossum81762581992-04-21 15:36:23 +0000351def debug():
352 prof = Profile().init()
353 prof.debug = 1
354 try:
355 prof.run('import x; x.main()')
356 except SystemExit:
357 pass
358 prof.print_stats()
359
Guido van Rossum4e160981992-09-02 20:43:20 +0000360# test command
Guido van Rossum81762581992-04-21 15:36:23 +0000361def test():
362 run('import x; x.main()')