blob: 9e202e91bf922a268a35148034ad0c11029533f4 [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
Skip Montanaroc62c81e2001-02-12 02:00:42 +000040__all__ = ["Stats"]
41
Guido van Rossumadb31051994-06-23 11:42:52 +000042class 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)
Guido van Rossum68468eb2003-02-27 20:14:51 +000079 self.add(*args)
Tim Peters2344fae2001-01-15 00:50:52 +000080
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
Tim Peters2344fae2001-01-15 00:50:52 +0000103 def load_stats(self, arg):
104 if not arg: self.stats = {}
105 elif type(arg) == type(""):
106 f = open(arg, 'rb')
107 self.stats = marshal.load(f)
108 f.close()
109 try:
110 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000111 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000112 except: # in case this is not unix
113 pass
114 self.files = [ arg ]
115 elif hasattr(arg, 'create_stats'):
116 arg.create_stats()
117 self.stats = arg.stats
118 arg.stats = {}
119 if not self.stats:
120 raise TypeError, "Cannot create or construct a " \
121 + `self.__class__` \
122 + " object from '" + `arg` + "'"
123 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000124
Tim Peters2344fae2001-01-15 00:50:52 +0000125 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000126 for func, (cc, nc, tt, ct, callers) in self.stats.items():
127 self.total_calls += nc
128 self.prim_calls += cc
129 self.total_tt += tt
Tim Peters2344fae2001-01-15 00:50:52 +0000130 if callers.has_key(("jprofile", 0, "profiler")):
131 self.top_level[func] = None
132 if len(func_std_string(func)) > self.max_name_len:
133 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000134
Tim Peters2344fae2001-01-15 00:50:52 +0000135 def add(self, *arg_list):
136 if not arg_list: return self
Guido van Rossum68468eb2003-02-27 20:14:51 +0000137 if len(arg_list) > 1: self.add(*arg_list[1:])
Tim Peters2344fae2001-01-15 00:50:52 +0000138 other = arg_list[0]
Tim Peters7d016852001-10-08 06:13:19 +0000139 if type(self) != type(other) or self.__class__ != other.__class__:
Tim Peters2344fae2001-01-15 00:50:52 +0000140 other = Stats(other)
Tim Peters7d016852001-10-08 06:13:19 +0000141 self.files += other.files
142 self.total_calls += other.total_calls
143 self.prim_calls += other.prim_calls
144 self.total_tt += other.total_tt
Raymond Hettingere0d49722002-06-02 18:55:56 +0000145 for func in other.top_level:
Tim Peters2344fae2001-01-15 00:50:52 +0000146 self.top_level[func] = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000147
Tim Peters2344fae2001-01-15 00:50:52 +0000148 if self.max_name_len < other.max_name_len:
149 self.max_name_len = other.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000150
Tim Peters2344fae2001-01-15 00:50:52 +0000151 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000152
Raymond Hettingere0d49722002-06-02 18:55:56 +0000153 for func, stat in other.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000154 if func in self.stats:
Tim Peters2344fae2001-01-15 00:50:52 +0000155 old_func_stat = self.stats[func]
156 else:
157 old_func_stat = (0, 0, 0, 0, {},)
Raymond Hettingere0d49722002-06-02 18:55:56 +0000158 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000159 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000160
Fred Drake9c439102003-05-14 14:28:09 +0000161 def dump_stats(self, filename):
162 """Write the profile data to a file we know how to load back."""
163 f = file(filename, 'wb')
164 try:
165 marshal.dump(self.stats, f)
166 finally:
167 f.close()
168
Tim Peters2344fae2001-01-15 00:50:52 +0000169 # list the tuple indices and directions for sorting,
170 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000171 sort_arg_dict_default = {
172 "calls" : (((1,-1), ), "call count"),
173 "cumulative": (((3,-1), ), "cumulative time"),
174 "file" : (((4, 1), ), "file name"),
175 "line" : (((5, 1), ), "line number"),
176 "module" : (((4, 1), ), "file name"),
177 "name" : (((6, 1), ), "function name"),
178 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
179 "pcalls" : (((0,-1), ), "call count"),
180 "stdname" : (((7, 1), ), "standard name"),
181 "time" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000182 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000183
Tim Peters2344fae2001-01-15 00:50:52 +0000184 def get_sort_arg_defs(self):
185 """Expand all abbreviations that are unique."""
186 if not self.sort_arg_dict:
187 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000188 bad_list = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000189 for word, tup in self.sort_arg_dict_default.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000190 fragment = word
191 while fragment:
192 if not fragment:
193 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000194 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000195 bad_list[fragment] = 0
196 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000197 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000198 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000199 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000200 del dict[word]
201 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000202
Tim Peters2344fae2001-01-15 00:50:52 +0000203 def sort_stats(self, *field):
204 if not field:
205 self.fcn_list = 0
206 return self
207 if len(field) == 1 and type(field[0]) == type(1):
208 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000209 field = [ {-1: "stdname",
210 0:"calls",
211 1:"time",
Tim Peters2344fae2001-01-15 00:50:52 +0000212 2: "cumulative" } [ field[0] ] ]
213
214 sort_arg_defs = self.get_sort_arg_defs()
215 sort_tuple = ()
216 self.sort_type = ""
217 connector = ""
218 for word in field:
219 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000220 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000221 connector = ", "
222
223 stats_list = []
Raymond Hettingere0d49722002-06-02 18:55:56 +0000224 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Tim Peters7d016852001-10-08 06:13:19 +0000225 stats_list.append((cc, nc, tt, ct) + func +
226 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000227
228 stats_list.sort(TupleComp(sort_tuple).compare)
229
230 self.fcn_list = fcn_list = []
231 for tuple in stats_list:
232 fcn_list.append(tuple[-1])
233 return self
234
Tim Peters2344fae2001-01-15 00:50:52 +0000235 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000236 if self.fcn_list:
237 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000238 return self
239
240 def strip_dirs(self):
241 oldstats = self.stats
242 self.stats = newstats = {}
243 max_name_len = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000244 for func, (cc, nc, tt, ct, callers) in oldstats.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000245 newfunc = func_strip_path(func)
246 if len(func_std_string(newfunc)) > max_name_len:
247 max_name_len = len(func_std_string(newfunc))
248 newcallers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000249 for func2, caller in callers.iteritems():
250 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000251
Raymond Hettinger54f02222002-06-01 14:18:47 +0000252 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000253 newstats[newfunc] = add_func_stats(
254 newstats[newfunc],
255 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000256 else:
257 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
258 old_top = self.top_level
259 self.top_level = new_top = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000260 for func in old_top:
Tim Peters2344fae2001-01-15 00:50:52 +0000261 new_top[func_strip_path(func)] = None
262
263 self.max_name_len = max_name_len
264
265 self.fcn_list = None
266 self.all_callees = None
267 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000268
Tim Peters2344fae2001-01-15 00:50:52 +0000269 def calc_callees(self):
270 if self.all_callees: return
271 self.all_callees = all_callees = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000272 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000273 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000274 all_callees[func] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000275 for func2, caller in callers.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000276 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000277 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000278 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000279 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000280
Tim Peters2344fae2001-01-15 00:50:52 +0000281 #******************************************************************
282 # The following functions support actual printing of reports
283 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000284
Tim Peters2344fae2001-01-15 00:50:52 +0000285 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000286
Tim Peters2344fae2001-01-15 00:50:52 +0000287 def eval_print_amount(self, sel, list, msg):
288 new_list = list
289 if type(sel) == type(""):
290 new_list = []
291 for func in list:
292 if re.search(sel, func_std_string(func)):
293 new_list.append(func)
294 else:
295 count = len(list)
296 if type(sel) == type(1.0) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000297 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000298 new_list = list[:count]
299 elif type(sel) == type(1) and 0 <= sel < count:
300 count = sel
301 new_list = list[:count]
302 if len(list) != len(new_list):
303 msg = msg + " List reduced from " + `len(list)` \
304 + " to " + `len(new_list)` + \
305 " due to restriction <" + `sel` + ">\n"
Guido van Rossumadb31051994-06-23 11:42:52 +0000306
Tim Peters2344fae2001-01-15 00:50:52 +0000307 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000308
Tim Peters2344fae2001-01-15 00:50:52 +0000309 def get_print_list(self, sel_list):
310 width = self.max_name_len
311 if self.fcn_list:
312 list = self.fcn_list[:]
313 msg = " Ordered by: " + self.sort_type + '\n'
314 else:
315 list = self.stats.keys()
316 msg = " Random listing order was used\n"
317
318 for selection in sel_list:
Tim Peters7d016852001-10-08 06:13:19 +0000319 list, msg = self.eval_print_amount(selection, list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000320
321 count = len(list)
322
323 if not list:
324 return 0, list
325 print msg
326 if count < len(self.stats):
327 width = 0
328 for func in list:
329 if len(func_std_string(func)) > width:
330 width = len(func_std_string(func))
331 return width+2, list
332
333 def print_stats(self, *amount):
334 for filename in self.files:
335 print filename
336 if self.files: print
Tim Peters7d016852001-10-08 06:13:19 +0000337 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000338 for func in self.top_level:
Tim Peters2344fae2001-01-15 00:50:52 +0000339 print indent, func_get_function_name(func)
340
Tim Peters7d016852001-10-08 06:13:19 +0000341 print indent, self.total_calls, "function calls",
Tim Peters2344fae2001-01-15 00:50:52 +0000342 if self.total_calls != self.prim_calls:
Tim Peters7d016852001-10-08 06:13:19 +0000343 print "(%d primitive calls)" % self.prim_calls,
344 print "in %.3f CPU seconds" % self.total_tt
Tim Peters2344fae2001-01-15 00:50:52 +0000345 print
346 width, list = self.get_print_list(amount)
347 if list:
348 self.print_title()
349 for func in list:
350 self.print_line(func)
351 print
352 print
353 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000354
Tim Peters2344fae2001-01-15 00:50:52 +0000355 def print_callees(self, *amount):
356 width, list = self.get_print_list(amount)
357 if list:
358 self.calc_callees()
359
360 self.print_call_heading(width, "called...")
361 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000362 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000363 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000364 else:
365 self.print_call_line(width, func, {})
366 print
367 print
368 return self
369
370 def print_callers(self, *amount):
371 width, list = self.get_print_list(amount)
372 if list:
373 self.print_call_heading(width, "was called by...")
374 for func in list:
375 cc, nc, tt, ct, callers = self.stats[func]
376 self.print_call_line(width, func, callers)
377 print
378 print
379 return self
380
381 def print_call_heading(self, name_size, column_title):
Eric S. Raymond373c55e2001-02-09 08:25:29 +0000382 print "Function ".ljust(name_size) + column_title
Guido van Rossumadb31051994-06-23 11:42:52 +0000383
Tim Peters2344fae2001-01-15 00:50:52 +0000384 def print_call_line(self, name_size, source, call_dict):
Eric S. Raymond373c55e2001-02-09 08:25:29 +0000385 print func_std_string(source).ljust(name_size),
Tim Peters2344fae2001-01-15 00:50:52 +0000386 if not call_dict:
387 print "--"
388 return
389 clist = call_dict.keys()
390 clist.sort()
391 name_size = name_size + 1
392 indent = ""
393 for func in clist:
394 name = func_std_string(func)
395 print indent*name_size + name + '(' \
396 + `call_dict[func]`+')', \
397 f8(self.stats[func][3])
398 indent = " "
399
Tim Peters2344fae2001-01-15 00:50:52 +0000400 def print_title(self):
Tim Peters7d016852001-10-08 06:13:19 +0000401 print ' ncalls tottime percall cumtime percall', \
402 'filename:lineno(function)'
Tim Peters2344fae2001-01-15 00:50:52 +0000403
404 def print_line(self, func): # hack : should print percentages
405 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000406 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000407 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000408 c = c + '/' + str(cc)
Eric S. Raymond373c55e2001-02-09 08:25:29 +0000409 print c.rjust(9),
Tim Peters2344fae2001-01-15 00:50:52 +0000410 print f8(tt),
411 if nc == 0:
412 print ' '*8,
413 else:
414 print f8(tt/nc),
415 print f8(ct),
416 if cc == 0:
417 print ' '*8,
418 else:
419 print f8(ct/cc),
420 print func_std_string(func)
421
Tim Peters281084f2001-10-08 06:28:18 +0000422 def ignore(self):
423 # Deprecated since 1.5.1 -- see the docs.
424 pass # has no return value, so use at end of line :-)
425
Guido van Rossumadb31051994-06-23 11:42:52 +0000426class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000427 """This class provides a generic function for comparing any two tuples.
428 Each instance records a list of tuple-indices (from most significant
429 to least significant), and sort direction (ascending or decending) for
430 each tuple-index. The compare functions can then be used as the function
431 argument to the system sort() function when a list of tuples need to be
432 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000433
Tim Peters2344fae2001-01-15 00:50:52 +0000434 def __init__(self, comp_select_list):
435 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000436
Tim Peters2344fae2001-01-15 00:50:52 +0000437 def compare (self, left, right):
438 for index, direction in self.comp_select_list:
439 l = left[index]
440 r = right[index]
441 if l < r:
442 return -direction
443 if l > r:
444 return direction
445 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000446
Guido van Rossumadb31051994-06-23 11:42:52 +0000447#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000448# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000449
450def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000451 filename, line, name = func_name
452 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000453
454def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000455 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000456
457def func_std_string(func_name): # match what old profile produced
Tim Peters7d016852001-10-08 06:13:19 +0000458 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000459
460#**************************************************************************
461# The following functions combine statists for pairs functions.
462# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000463# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000464#**************************************************************************
465
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000466def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000467 """Add together all the stats for two profile entries."""
468 cc, nc, tt, ct, callers = source
469 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000470 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000471 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000472
Guido van Rossumadb31051994-06-23 11:42:52 +0000473def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000474 """Combine two caller lists in a single list."""
475 new_callers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000476 for func, caller in target.iteritems():
477 new_callers[func] = caller
478 for func, caller in source.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000479 if func in new_callers:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000480 new_callers[func] = caller + new_callers[func]
Tim Peters2344fae2001-01-15 00:50:52 +0000481 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000482 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000483 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000484
Guido van Rossumadb31051994-06-23 11:42:52 +0000485def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000486 """Sum the caller statistics to get total number of calls received."""
487 nc = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000488 for calls in callers.itervalues():
489 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000490 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000491
492#**************************************************************************
493# The following functions support printing of reports
494#**************************************************************************
495
496def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000497 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000498
499#**************************************************************************
500# Statistics browser added by ESR, April 2001
501#**************************************************************************
502
503if __name__ == '__main__':
504 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000505 try:
506 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000507 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000508 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000509
510 class ProfileBrowser(cmd.Cmd):
511 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000512 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000513 self.prompt = "% "
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000514 if profile is not None:
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000515 self.stats = Stats(profile)
516 else:
517 self.stats = None
518
519 def generic(self, fn, line):
520 args = line.split()
521 processed = []
522 for term in args:
523 try:
524 processed.append(int(term))
525 continue
526 except ValueError:
527 pass
528 try:
529 frac = float(term)
530 if frac > 1 or frac < 0:
531 print "Fraction argument mus be in [0, 1]"
532 continue
533 processed.append(frac)
534 continue
535 except ValueError:
536 pass
537 processed.append(term)
538 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000539 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000540 else:
541 print "No statistics object is loaded."
542 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000543 def generic_help(self):
544 print "Arguments may be:"
545 print "* An integer maximum number of entries to print."
546 print "* A decimal fractional number between 0 and 1, controlling"
547 print " what fraction of selected entries to print."
548 print "* A regular expression; only entries with function names"
549 print " that match it are printed."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000550
551 def do_add(self, line):
552 self.stats.add(line)
553 return 0
554 def help_add(self):
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000555 print "Add profile info from given file to current statistics object."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000556
557 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000558 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000559 def help_callees(self):
560 print "Print callees statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000561 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000562
563 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000564 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000565 def help_callers(self):
566 print "Print callers statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000567 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000568
569 def do_EOF(self, line):
570 print ""
571 return 1
572 def help_EOF(self):
573 print "Leave the profile brower."
574
575 def do_quit(self, line):
576 return 1
577 def help_quit(self):
578 print "Leave the profile brower."
579
580 def do_read(self, line):
581 if line:
582 try:
583 self.stats = Stats(line)
584 except IOError, args:
585 print args[1]
586 return
587 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000588 elif len(self.prompt) > 2:
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000589 line = self.prompt[-2:]
590 else:
591 print "No statistics object is current -- cannot reload."
592 return 0
593 def help_read(self):
594 print "Read in profile data from a specified file."
595
596 def do_reverse(self, line):
597 self.stats.reverse_order()
598 return 0
599 def help_reverse(self):
600 print "Reverse the sort order of the profiling report."
601
602 def do_sort(self, line):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000603 abbrevs = self.stats.get_sort_arg_defs()
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000604 if line and not filter(lambda x,a=abbrevs: x not in a,line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000605 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000606 else:
607 print "Valid sort keys (unique prefixes are accepted):"
Raymond Hettingere0d49722002-06-02 18:55:56 +0000608 for (key, value) in Stats.sort_arg_dict_default.iteritems():
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000609 print "%s -- %s" % (key, value[1])
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000610 return 0
611 def help_sort(self):
612 print "Sort profile data according to specified keys."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000613 print "(Typing `sort' without arguments lists valid keys.)"
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000614 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000615 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000616
617 def do_stats(self, line):
618 return self.generic('print_stats', line)
619 def help_stats(self):
620 print "Print statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000621 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000622
623 def do_strip(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000624 self.stats.strip_dirs()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000625 return 0
626 def help_strip(self):
627 print "Strip leading path information from filenames in the report."
628
629 def postcmd(self, stop, line):
630 if stop:
631 return stop
632 return None
633
634 import sys
635 print "Welcome to the profile statistics browser."
636 if len(sys.argv) > 1:
637 initprofile = sys.argv[1]
638 else:
639 initprofile = None
640 try:
641 ProfileBrowser(initprofile).cmdloop()
642 print "Goodbye."
643 except KeyboardInterrupt:
644 pass
645
646# That's all, folks.