1 """Class for printing reports on profiled python code."""
43 """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.
48 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.
53 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
60 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.
67 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)
99 print "Invalid timing data",
104 if not arg: self.
stats = {}
105 elif type(arg) == type(
""):
107 self.
stats = marshal.load(f)
110 file_stats = os.stat(arg)
111 arg = time.ctime(file_stats[8]) +
" " + arg
115 elif hasattr(arg,
'create_stats'):
117 self.
stats = arg.stats
120 raise TypeError,
"Cannot create or construct a " \
122 +
" object from '" + `arg` +
"'"
126 for func, (cc, nc, tt, ct, callers)
in self.stats.items():
130 if callers.has_key((
"jprofile", 0,
"profiler")):
136 if not arg_list:
return self
137 if len(arg_list) > 1: apply(self.
add, arg_list[1:])
139 if type(self) != type(other)
or self.__class__ != other.__class__:
141 self.
files += other.files
145 for func
in other.top_level.keys():
153 for func
in other.stats.keys():
154 if self.stats.has_key(func):
155 old_func_stat = self.
stats[func]
157 old_func_stat = (0, 0, 0, 0, {},)
163 sort_arg_dict_default = {
164 "calls" : (((1,-1), ),
"call count"),
165 "cumulative": (((3,-1), ),
"cumulative time"),
166 "file" : (((4, 1), ),
"file name"),
167 "line" : (((5, 1), ),
"line number"),
168 "module" : (((4, 1), ),
"file name"),
169 "name" : (((6, 1), ),
"function name"),
170 "nfl" : (((6, 1),(4, 1),(5, 1),),
"name/file/line"),
171 "pcalls" : (((0,-1), ),
"call count"),
172 "stdname" : (((7, 1), ),
"standard name"),
173 "time" : (((2,-1), ),
"internal time"),
177 """Expand all abbreviations that are unique."""
181 for word
in self.sort_arg_dict_default.keys():
186 if dict.has_key(fragment):
187 bad_list[fragment] = 0
190 fragment = fragment[:-1]
191 for word
in bad_list.keys():
199 if len(field) == 1
and type(field[0]) == type(1):
201 field = [ {-1:
"stdname",
204 2:
"cumulative" } [ field[0] ] ]
211 sort_tuple = sort_tuple + sort_arg_defs[word][0]
212 self.
sort_type += connector + sort_arg_defs[word][1]
216 for func
in self.stats.keys():
217 cc, nc, tt, ct, callers = self.
stats[func]
218 stats_list.append((cc, nc, tt, ct) + func +
221 stats_list.sort(
TupleComp(sort_tuple).compare)
224 for tuple
in stats_list:
225 fcn_list.append(tuple[-1])
230 self.fcn_list.reverse()
234 oldstats = self.
stats
235 self.
stats = newstats = {}
237 for func
in oldstats.keys():
238 cc, nc, tt, ct, callers = oldstats[func]
243 for func2
in callers.keys():
246 if newstats.has_key(newfunc):
249 (cc, nc, tt, ct, newcallers))
251 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
254 for func
in old_top.keys():
266 for func
in self.stats.keys():
267 if not all_callees.has_key(func):
268 all_callees[func] = {}
269 cc, nc, tt, ct, callers = self.
stats[func]
270 for func2
in callers.keys():
271 if not all_callees.has_key(func2):
272 all_callees[func2] = {}
273 all_callees[func2][func] = callers[func2]
284 if type(sel) == type(
""):
288 new_list.append(func)
291 if type(sel) == type(1.0)
and 0.0 <= sel < 1.0:
292 count = int(count * sel + .5)
293 new_list = list[:count]
294 elif type(sel) == type(1)
and 0 <= sel < count:
296 new_list = list[:count]
297 if len(list) != len(new_list):
298 msg = msg +
" List reduced from " + `len(list)` \
299 +
" to " + `len(new_list)` + \
300 " due to restriction <" + `sel` +
">\n"
308 msg =
" Ordered by: " + self.
sort_type +
'\n'
310 list = self.stats.keys()
311 msg =
" Random listing order was used\n"
313 for selection
in sel_list:
321 if count < len(self.
stats):
329 for filename
in self.
files:
333 for func
in self.top_level.keys():
338 print "(%d primitive calls)" % self.
prim_calls,
339 print "in %.3f CPU seconds" % self.
total_tt
357 if self.all_callees.has_key(func):
370 cc, nc, tt, ct, callers = self.
stats[func]
377 print "Function ".
ljust(name_size) + column_title
384 clist = call_dict.keys()
386 name_size = name_size + 1
390 print indent*name_size + name +
'(' \
391 + `call_dict[func]`+
')', \
396 print ' ncalls tottime percall cumtime percall', \
397 'filename:lineno(function)'
400 cc, nc, tt, ct, callers = self.
stats[func]
403 c = c +
'/' +
str(cc)
422 """This class provides a generic function for comparing any two tuples.
423 Each instance records a list of tuple-indices (from most significant
424 to least significant), and sort direction (ascending or decending) for
425 each tuple-index. The compare functions can then be used as the function
426 argument to the system sort() function when a list of tuples need to be
427 sorted in the instances order."""
446 file, line, name = func_name
447 return os.path.basename(file), line, name
453 return "%s:%d(%s)" % func_name
462 """Add together all the stats for two profile entries."""
463 cc, nc, tt, ct, callers = source
464 t_cc, t_nc, t_tt, t_ct, t_callers = target
465 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
469 """Combine two caller lists in a single list."""
471 for func
in target.keys():
472 new_callers[func] = target[func]
473 for func
in source.keys():
474 if new_callers.has_key(func):
475 new_callers[func] = source[func] + new_callers[func]
477 new_callers[func] = source[func]
481 """Sum the caller statistics to get total number of calls received."""
483 for func
in callers.keys():
498 if __name__ ==
'__main__':
519 processed.append(int(term))
525 if frac > 1
or frac < 0:
526 print "Fraction argument mus be in [0, 1]"
528 processed.append(frac)
532 processed.append(term)
534 apply(getattr(self.
stats, fn), processed)
536 print "No statistics object is loaded."
539 print "Arguments may be:"
540 print "* An integer maximum number of entries to print."
541 print "* A decimal fractional number between 0 and 1, controlling"
542 print " what fraction of selected entries to print."
543 print "* A regular expression; only entries with function names"
544 print " that match it are printed."
550 print "Add profile info from given file to current statistics object."
553 return self.
generic(
'print_callees', line)
555 print "Print callees statistics from the current stat object."
559 return self.
generic(
'print_callers', line)
561 print "Print callers statistics from the current stat object."
568 print "Leave the profile brower."
573 print "Leave the profile brower."
579 except IOError, args:
583 elif len(self.
prompt) > 2:
586 print "No statistics object is current -- cannot reload."
589 print "Read in profile data from a specified file."
592 self.stats.reverse_order()
595 print "Reverse the sort order of the profiling report."
598 abbrevs = self.stats.get_sort_arg_defs().
keys()
599 if line
and not filter(
lambda x,a=abbrevs: x
not in a,line.split()):
600 apply(self.stats.sort_stats, line.split())
602 print "Valid sort keys (unique prefixes are accepted):"
603 for (key, value)
in Stats.sort_arg_dict_default.items():
604 print "%s -- %s" % (key, value[1])
607 print "Sort profile data according to specified keys."
608 print "(Typing `sort' without arguments lists valid keys.)"
610 return [a
for a
in Stats.sort_arg_dict_default.keys()
if a.startswith(text)]
613 return self.
generic(
'print_stats', line)
615 print "Print statistics from the current stat object."
619 self.stats.strip_dirs()
622 print "Strip leading path information from filenames in the report."
630 print "Welcome to the profile statistics browser."
631 if len(sys.argv) > 1:
632 initprofile = sys.argv[1]
638 except KeyboardInterrupt: