blob: 764d89c95eabab9d0e722b892993895452015d01 [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""Class for printing reports on profiled python code."""
2
Guido van Rossumadb31051994-06-23 11:42:52 +00003# Class for printing reports on profiled python code. rev 1.0 4/1/94
4#
5# Based on prior profile module by Sjoerd Mullender...
6# which was hacked somewhat by: Guido van Rossum
7#
Guido van Rossumdabcd001999-04-13 04:24:22 +00008# see profile.doc and profile.py for more info.
Guido van Rossumadb31051994-06-23 11:42:52 +00009
10# Copyright 1994, by InfoSeek Corporation, all rights reserved.
11# Written by James Roskind
Tim Peters2344fae2001-01-15 00:50:52 +000012#
Guido van Rossumadb31051994-06-23 11:42:52 +000013# Permission to use, copy, modify, and distribute this Python software
14# and its associated documentation for any purpose (subject to the
15# restriction in the following sentence) without fee is hereby granted,
16# provided that the above copyright notice appears in all copies, and
17# that both that copyright notice and this permission notice appear in
18# supporting documentation, and that the name of InfoSeek not be used in
19# advertising or publicity pertaining to distribution of the software
20# without specific, written prior permission. This permission is
21# explicitly restricted to the copying and modification of the software
22# to remain in Python, compiled Python, or other languages (such as C)
23# wherein the modified or derived code is exclusively imported into a
24# Python module.
Tim Peters2344fae2001-01-15 00:50:52 +000025#
Guido van Rossumadb31051994-06-23 11:42:52 +000026# INFOSEEK CORPORATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
27# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
28# FITNESS. IN NO EVENT SHALL INFOSEEK CORPORATION BE LIABLE FOR ANY
29# SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
30# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
31# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
32# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
33
34
35import os
36import time
Guido van Rossumadb31051994-06-23 11:42:52 +000037import marshal
Guido van Rossum9694fca1997-10-22 21:00:49 +000038import re
Guido van Rossumadb31051994-06-23 11:42:52 +000039
Guido van Rossumadb31051994-06-23 11:42:52 +000040import fpformat
41
42class Stats:
Tim Peters2344fae2001-01-15 00:50:52 +000043 """This class is used for creating reports from data generated by the
44 Profile class. It is a "friend" of that class, and imports data either
45 by direct access to members of Profile class, or by reading in a dictionary
46 that was emitted (via marshal) from the Profile class.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000047
Tim Peters2344fae2001-01-15 00:50:52 +000048 The big change from the previous Profiler (in terms of raw functionality)
49 is that an "add()" method has been provided to combine Stats from
50 several distinct profile runs. Both the constructor and the add()
51 method now take arbitrarily many file names as arguments.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000052
Tim Peters2344fae2001-01-15 00:50:52 +000053 All the print methods now take an argument that indicates how many lines
54 to print. If the arg is a floating point number between 0 and 1.0, then
55 it is taken as a decimal percentage of the available lines to be printed
56 (e.g., .1 means print 10% of all available lines). If it is an integer,
57 it is taken to mean the number of lines of data that you wish to have
58 printed.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000059
Tim Peters2344fae2001-01-15 00:50:52 +000060 The sort_stats() method now processes some additional options (i.e., in
61 addition to the old -1, 0, 1, or 2). It takes an arbitrary number of quoted
62 strings to select the sort order. For example sort_stats('time', 'name')
63 sorts on the major key of "internal function time", and on the minor
64 key of 'the name of the function'. Look at the two tables in sort_stats()
65 and get_sort_arg_defs(self) for more examples.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000066
Tim Peters2344fae2001-01-15 00:50:52 +000067 All methods now return "self", so you can string together commands like:
68 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
69 print_stats(5).print_callers(5)
70 """
71
72 def __init__(self, *args):
73 if not len(args):
74 arg = None
75 else:
76 arg = args[0]
77 args = args[1:]
78 self.init(arg)
79 apply(self.add, args).ignore()
80
81 def init(self, arg):
82 self.all_callees = None # calc only if needed
83 self.files = []
84 self.fcn_list = None
85 self.total_tt = 0
86 self.total_calls = 0
87 self.prim_calls = 0
88 self.max_name_len = 0
89 self.top_level = {}
90 self.stats = {}
91 self.sort_arg_dict = {}
92 self.load_stats(arg)
93 trouble = 1
94 try:
95 self.get_top_level_stats()
96 trouble = 0
97 finally:
98 if trouble:
99 print "Invalid timing data",
100 if self.files: print self.files[-1],
101 print
Guido van Rossumadb31051994-06-23 11:42:52 +0000102
103
Tim Peters2344fae2001-01-15 00:50:52 +0000104 def load_stats(self, arg):
105 if not arg: self.stats = {}
106 elif type(arg) == type(""):
107 f = open(arg, 'rb')
108 self.stats = marshal.load(f)
109 f.close()
110 try:
111 file_stats = os.stat(arg)
112 arg = time.ctime(file_stats[8]) + " " + arg
113 except: # in case this is not unix
114 pass
115 self.files = [ arg ]
116 elif hasattr(arg, 'create_stats'):
117 arg.create_stats()
118 self.stats = arg.stats
119 arg.stats = {}
120 if not self.stats:
121 raise TypeError, "Cannot create or construct a " \
122 + `self.__class__` \
123 + " object from '" + `arg` + "'"
124 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000125
Tim Peters2344fae2001-01-15 00:50:52 +0000126 def get_top_level_stats(self):
127 for func in self.stats.keys():
128 cc, nc, tt, ct, callers = self.stats[func]
129 self.total_calls = self.total_calls + nc
130 self.prim_calls = self.prim_calls + cc
131 self.total_tt = self.total_tt + tt
132 if callers.has_key(("jprofile", 0, "profiler")):
133 self.top_level[func] = None
134 if len(func_std_string(func)) > self.max_name_len:
135 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000136
Tim Peters2344fae2001-01-15 00:50:52 +0000137 def add(self, *arg_list):
138 if not arg_list: return self
139 if len(arg_list) > 1: apply(self.add, arg_list[1:])
140 other = arg_list[0]
141 if type(self) != type(other) or \
142 self.__class__ != other.__class__:
143 other = Stats(other)
144 self.files = self.files + other.files
145 self.total_calls = self.total_calls + other.total_calls
146 self.prim_calls = self.prim_calls + other.prim_calls
147 self.total_tt = self.total_tt + other.total_tt
148 for func in other.top_level.keys():
149 self.top_level[func] = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000150
Tim Peters2344fae2001-01-15 00:50:52 +0000151 if self.max_name_len < other.max_name_len:
152 self.max_name_len = other.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000153
Tim Peters2344fae2001-01-15 00:50:52 +0000154 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000155
Tim Peters2344fae2001-01-15 00:50:52 +0000156 for func in other.stats.keys():
157 if self.stats.has_key(func):
158 old_func_stat = self.stats[func]
159 else:
160 old_func_stat = (0, 0, 0, 0, {},)
161 self.stats[func] = add_func_stats(old_func_stat, \
162 other.stats[func])
163 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000164
165
166
Tim Peters2344fae2001-01-15 00:50:52 +0000167 # list the tuple indices and directions for sorting,
168 # along with some printable description
169 sort_arg_dict_default = {\
170 "calls" : (((1,-1), ), "call count"),\
171 "cumulative": (((3,-1), ), "cumulative time"),\
172 "file" : (((4, 1), ), "file name"),\
173 "line" : (((5, 1), ), "line number"),\
174 "module" : (((4, 1), ), "file name"),\
175 "name" : (((6, 1), ), "function name"),\
176 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"), \
177 "pcalls" : (((0,-1), ), "call count"),\
178 "stdname" : (((7, 1), ), "standard name"),\
179 "time" : (((2,-1), ), "internal time"),\
180 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000181
Tim Peters2344fae2001-01-15 00:50:52 +0000182 def get_sort_arg_defs(self):
183 """Expand all abbreviations that are unique."""
184 if not self.sort_arg_dict:
185 self.sort_arg_dict = dict = {}
186 std_list = dict.keys()
187 bad_list = {}
188 for word in self.sort_arg_dict_default.keys():
189 fragment = word
190 while fragment:
191 if not fragment:
192 break
193 if dict.has_key(fragment):
194 bad_list[fragment] = 0
195 break
196 dict[fragment] = self. \
197 sort_arg_dict_default[word]
198 fragment = fragment[:-1]
199 for word in bad_list.keys():
200 del dict[word]
201 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000202
Guido van Rossumadb31051994-06-23 11:42:52 +0000203
Tim Peters2344fae2001-01-15 00:50:52 +0000204 def sort_stats(self, *field):
205 if not field:
206 self.fcn_list = 0
207 return self
208 if len(field) == 1 and type(field[0]) == type(1):
209 # Be compatible with old profiler
210 field = [ {-1: "stdname", \
211 0:"calls", \
212 1:"time", \
213 2: "cumulative" } [ field[0] ] ]
214
215 sort_arg_defs = self.get_sort_arg_defs()
216 sort_tuple = ()
217 self.sort_type = ""
218 connector = ""
219 for word in field:
220 sort_tuple = sort_tuple + sort_arg_defs[word][0]
221 self.sort_type = self.sort_type + connector + \
222 sort_arg_defs[word][1]
223 connector = ", "
224
225 stats_list = []
226 for func in self.stats.keys():
227 cc, nc, tt, ct, callers = self.stats[func]
228 stats_list.append((cc, nc, tt, ct) + func_split(func) \
229 + (func_std_string(func), func,) )
230
231 stats_list.sort(TupleComp(sort_tuple).compare)
232
233 self.fcn_list = fcn_list = []
234 for tuple in stats_list:
235 fcn_list.append(tuple[-1])
236 return self
237
238
239 def reverse_order(self):
240 if self.fcn_list: self.fcn_list.reverse()
241 return self
242
243 def strip_dirs(self):
244 oldstats = self.stats
245 self.stats = newstats = {}
246 max_name_len = 0
247 for func in oldstats.keys():
248 cc, nc, tt, ct, callers = oldstats[func]
249 newfunc = func_strip_path(func)
250 if len(func_std_string(newfunc)) > max_name_len:
251 max_name_len = len(func_std_string(newfunc))
252 newcallers = {}
253 for func2 in callers.keys():
254 newcallers[func_strip_path(func2)] = \
255 callers[func2]
256
257 if newstats.has_key(newfunc):
258 newstats[newfunc] = add_func_stats( \
259 newstats[newfunc],\
260 (cc, nc, tt, ct, newcallers))
261 else:
262 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
263 old_top = self.top_level
264 self.top_level = new_top = {}
265 for func in old_top.keys():
266 new_top[func_strip_path(func)] = None
267
268 self.max_name_len = max_name_len
269
270 self.fcn_list = None
271 self.all_callees = None
272 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000273
274
275
Tim Peters2344fae2001-01-15 00:50:52 +0000276 def calc_callees(self):
277 if self.all_callees: return
278 self.all_callees = all_callees = {}
279 for func in self.stats.keys():
280 if not all_callees.has_key(func):
281 all_callees[func] = {}
282 cc, nc, tt, ct, callers = self.stats[func]
283 for func2 in callers.keys():
284 if not all_callees.has_key(func2):
285 all_callees[func2] = {}
286 all_callees[func2][func] = callers[func2]
287 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000288
Tim Peters2344fae2001-01-15 00:50:52 +0000289 #******************************************************************
290 # The following functions support actual printing of reports
291 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000292
Tim Peters2344fae2001-01-15 00:50:52 +0000293 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000294
Tim Peters2344fae2001-01-15 00:50:52 +0000295 def eval_print_amount(self, sel, list, msg):
296 new_list = list
297 if type(sel) == type(""):
298 new_list = []
299 for func in list:
300 if re.search(sel, func_std_string(func)):
301 new_list.append(func)
302 else:
303 count = len(list)
304 if type(sel) == type(1.0) and 0.0 <= sel < 1.0:
305 count = int (count * sel + .5)
306 new_list = list[:count]
307 elif type(sel) == type(1) and 0 <= sel < count:
308 count = sel
309 new_list = list[:count]
310 if len(list) != len(new_list):
311 msg = msg + " List reduced from " + `len(list)` \
312 + " to " + `len(new_list)` + \
313 " due to restriction <" + `sel` + ">\n"
Guido van Rossumadb31051994-06-23 11:42:52 +0000314
Tim Peters2344fae2001-01-15 00:50:52 +0000315 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000316
317
318
Tim Peters2344fae2001-01-15 00:50:52 +0000319 def get_print_list(self, sel_list):
320 width = self.max_name_len
321 if self.fcn_list:
322 list = self.fcn_list[:]
323 msg = " Ordered by: " + self.sort_type + '\n'
324 else:
325 list = self.stats.keys()
326 msg = " Random listing order was used\n"
327
328 for selection in sel_list:
329 list,msg = self.eval_print_amount(selection, list, msg)
330
331 count = len(list)
332
333 if not list:
334 return 0, list
335 print msg
336 if count < len(self.stats):
337 width = 0
338 for func in list:
339 if len(func_std_string(func)) > width:
340 width = len(func_std_string(func))
341 return width+2, list
342
343 def print_stats(self, *amount):
344 for filename in self.files:
345 print filename
346 if self.files: print
347 indent = " "
348 for func in self.top_level.keys():
349 print indent, func_get_function_name(func)
350
351 print indent, self.total_calls, "function calls",
352 if self.total_calls != self.prim_calls:
353 print "(" + `self.prim_calls`, "primitive calls)",
354 print "in", fpformat.fix(self.total_tt, 3), "CPU seconds"
355 print
356 width, list = self.get_print_list(amount)
357 if list:
358 self.print_title()
359 for func in list:
360 self.print_line(func)
361 print
362 print
363 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000364
365
Tim Peters2344fae2001-01-15 00:50:52 +0000366 def print_callees(self, *amount):
367 width, list = self.get_print_list(amount)
368 if list:
369 self.calc_callees()
370
371 self.print_call_heading(width, "called...")
372 for func in list:
373 if self.all_callees.has_key(func):
374 self.print_call_line(width, \
375 func, self.all_callees[func])
376 else:
377 self.print_call_line(width, func, {})
378 print
379 print
380 return self
381
382 def print_callers(self, *amount):
383 width, list = self.get_print_list(amount)
384 if list:
385 self.print_call_heading(width, "was called by...")
386 for func in list:
387 cc, nc, tt, ct, callers = self.stats[func]
388 self.print_call_line(width, func, callers)
389 print
390 print
391 return self
392
393 def print_call_heading(self, name_size, column_title):
Eric S. Raymond373c55e2001-02-09 08:25:29 +0000394 print "Function ".ljust(name_size) + column_title
Guido van Rossumadb31051994-06-23 11:42:52 +0000395
396
Tim Peters2344fae2001-01-15 00:50:52 +0000397 def print_call_line(self, name_size, source, call_dict):
Eric S. Raymond373c55e2001-02-09 08:25:29 +0000398 print func_std_string(source).ljust(name_size),
Tim Peters2344fae2001-01-15 00:50:52 +0000399 if not call_dict:
400 print "--"
401 return
402 clist = call_dict.keys()
403 clist.sort()
404 name_size = name_size + 1
405 indent = ""
406 for func in clist:
407 name = func_std_string(func)
408 print indent*name_size + name + '(' \
409 + `call_dict[func]`+')', \
410 f8(self.stats[func][3])
411 indent = " "
412
413
414
415 def print_title(self):
Eric S. Raymond373c55e2001-02-09 08:25:29 +0000416 print 'ncalls'.rjust(9),
417 print 'tottime'.rjust(8),
418 print 'percall'.rjust(8),
419 print 'cumtime'.rjust(8),
420 print 'percall'.rjust(8),
Tim Peters2344fae2001-01-15 00:50:52 +0000421 print 'filename:lineno(function)'
422
423
424 def print_line(self, func): # hack : should print percentages
425 cc, nc, tt, ct, callers = self.stats[func]
426 c = `nc`
427 if nc != cc:
428 c = c + '/' + `cc`
Eric S. Raymond373c55e2001-02-09 08:25:29 +0000429 print c.rjust(9),
Tim Peters2344fae2001-01-15 00:50:52 +0000430 print f8(tt),
431 if nc == 0:
432 print ' '*8,
433 else:
434 print f8(tt/nc),
435 print f8(ct),
436 if cc == 0:
437 print ' '*8,
438 else:
439 print f8(ct/cc),
440 print func_std_string(func)
441
442
443 def ignore(self):
444 pass # has no return value, so use at end of line :-)
Guido van Rossumadb31051994-06-23 11:42:52 +0000445
446
Guido van Rossumadb31051994-06-23 11:42:52 +0000447class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000448 """This class provides a generic function for comparing any two tuples.
449 Each instance records a list of tuple-indices (from most significant
450 to least significant), and sort direction (ascending or decending) for
451 each tuple-index. The compare functions can then be used as the function
452 argument to the system sort() function when a list of tuples need to be
453 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000454
Tim Peters2344fae2001-01-15 00:50:52 +0000455 def __init__(self, comp_select_list):
456 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000457
Tim Peters2344fae2001-01-15 00:50:52 +0000458 def compare (self, left, right):
459 for index, direction in self.comp_select_list:
460 l = left[index]
461 r = right[index]
462 if l < r:
463 return -direction
464 if l > r:
465 return direction
466 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000467
Tim Peters2344fae2001-01-15 00:50:52 +0000468
Guido van Rossumadb31051994-06-23 11:42:52 +0000469
470#**************************************************************************
471
472def func_strip_path(func_name):
Tim Peters2344fae2001-01-15 00:50:52 +0000473 file, line, name = func_name
474 return os.path.basename(file), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000475
476def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000477 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000478
479def func_std_string(func_name): # match what old profile produced
Tim Peters2344fae2001-01-15 00:50:52 +0000480 file, line, name = func_name
481 return file + ":" + `line` + "(" + name + ")"
Guido van Rossumadb31051994-06-23 11:42:52 +0000482
483def func_split(func_name):
Tim Peters2344fae2001-01-15 00:50:52 +0000484 return func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000485
486#**************************************************************************
487# The following functions combine statists for pairs functions.
488# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000489# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000490#**************************************************************************
491
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000492def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000493 """Add together all the stats for two profile entries."""
494 cc, nc, tt, ct, callers = source
495 t_cc, t_nc, t_tt, t_ct, t_callers = target
496 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct, \
497 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000498
499
Guido van Rossumadb31051994-06-23 11:42:52 +0000500def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000501 """Combine two caller lists in a single list."""
502 new_callers = {}
503 for func in target.keys():
504 new_callers[func] = target[func]
505 for func in source.keys():
506 if new_callers.has_key(func):
507 new_callers[func] = source[func] + new_callers[func]
508 else:
509 new_callers[func] = source[func]
510 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000511
Guido van Rossumadb31051994-06-23 11:42:52 +0000512def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000513 """Sum the caller statistics to get total number of calls received."""
514 nc = 0
515 for func in callers.keys():
516 nc = nc + callers[func]
517 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000518
519#**************************************************************************
520# The following functions support printing of reports
521#**************************************************************************
522
523def f8(x):
Eric S. Raymond373c55e2001-02-09 08:25:29 +0000524 return fpformat.fix(x, 3).rjust(8)