blob: 930cc6d17a2791771a07ef89fa82c297ff1ec453 [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:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000120 raise TypeError, "Cannot create or construct a %r object from '%r''" % (
121 self.__class__, arg)
Tim Peters2344fae2001-01-15 00:50:52 +0000122 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000123
Tim Peters2344fae2001-01-15 00:50:52 +0000124 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000125 for func, (cc, nc, tt, ct, callers) in self.stats.items():
126 self.total_calls += nc
127 self.prim_calls += cc
128 self.total_tt += tt
Tim Peters2344fae2001-01-15 00:50:52 +0000129 if callers.has_key(("jprofile", 0, "profiler")):
130 self.top_level[func] = None
131 if len(func_std_string(func)) > self.max_name_len:
132 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000133
Tim Peters2344fae2001-01-15 00:50:52 +0000134 def add(self, *arg_list):
135 if not arg_list: return self
Guido van Rossum68468eb2003-02-27 20:14:51 +0000136 if len(arg_list) > 1: self.add(*arg_list[1:])
Tim Peters2344fae2001-01-15 00:50:52 +0000137 other = arg_list[0]
Tim Peters7d016852001-10-08 06:13:19 +0000138 if type(self) != type(other) or self.__class__ != other.__class__:
Tim Peters2344fae2001-01-15 00:50:52 +0000139 other = Stats(other)
Tim Peters7d016852001-10-08 06:13:19 +0000140 self.files += other.files
141 self.total_calls += other.total_calls
142 self.prim_calls += other.prim_calls
143 self.total_tt += other.total_tt
Raymond Hettingere0d49722002-06-02 18:55:56 +0000144 for func in other.top_level:
Tim Peters2344fae2001-01-15 00:50:52 +0000145 self.top_level[func] = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000146
Tim Peters2344fae2001-01-15 00:50:52 +0000147 if self.max_name_len < other.max_name_len:
148 self.max_name_len = other.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000149
Tim Peters2344fae2001-01-15 00:50:52 +0000150 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000151
Raymond Hettingere0d49722002-06-02 18:55:56 +0000152 for func, stat in other.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000153 if func in self.stats:
Tim Peters2344fae2001-01-15 00:50:52 +0000154 old_func_stat = self.stats[func]
155 else:
156 old_func_stat = (0, 0, 0, 0, {},)
Raymond Hettingere0d49722002-06-02 18:55:56 +0000157 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000158 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000159
Fred Drake9c439102003-05-14 14:28:09 +0000160 def dump_stats(self, filename):
161 """Write the profile data to a file we know how to load back."""
162 f = file(filename, 'wb')
163 try:
164 marshal.dump(self.stats, f)
165 finally:
166 f.close()
167
Tim Peters2344fae2001-01-15 00:50:52 +0000168 # list the tuple indices and directions for sorting,
169 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000170 sort_arg_dict_default = {
171 "calls" : (((1,-1), ), "call count"),
172 "cumulative": (((3,-1), ), "cumulative time"),
173 "file" : (((4, 1), ), "file name"),
174 "line" : (((5, 1), ), "line number"),
175 "module" : (((4, 1), ), "file name"),
176 "name" : (((6, 1), ), "function name"),
177 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
178 "pcalls" : (((0,-1), ), "call count"),
179 "stdname" : (((7, 1), ), "standard name"),
180 "time" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000181 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000182
Tim Peters2344fae2001-01-15 00:50:52 +0000183 def get_sort_arg_defs(self):
184 """Expand all abbreviations that are unique."""
185 if not self.sort_arg_dict:
186 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000187 bad_list = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000188 for word, tup in self.sort_arg_dict_default.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000189 fragment = word
190 while fragment:
191 if not fragment:
192 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000193 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000194 bad_list[fragment] = 0
195 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000196 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000197 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000198 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000199 del dict[word]
200 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000201
Tim Peters2344fae2001-01-15 00:50:52 +0000202 def sort_stats(self, *field):
203 if not field:
204 self.fcn_list = 0
205 return self
206 if len(field) == 1 and type(field[0]) == type(1):
207 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000208 field = [ {-1: "stdname",
209 0:"calls",
210 1:"time",
Tim Peters2344fae2001-01-15 00:50:52 +0000211 2: "cumulative" } [ field[0] ] ]
212
213 sort_arg_defs = self.get_sort_arg_defs()
214 sort_tuple = ()
215 self.sort_type = ""
216 connector = ""
217 for word in field:
218 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000219 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000220 connector = ", "
221
222 stats_list = []
Raymond Hettingere0d49722002-06-02 18:55:56 +0000223 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Tim Peters7d016852001-10-08 06:13:19 +0000224 stats_list.append((cc, nc, tt, ct) + func +
225 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000226
227 stats_list.sort(TupleComp(sort_tuple).compare)
228
229 self.fcn_list = fcn_list = []
230 for tuple in stats_list:
231 fcn_list.append(tuple[-1])
232 return self
233
Tim Peters2344fae2001-01-15 00:50:52 +0000234 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000235 if self.fcn_list:
236 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000237 return self
238
239 def strip_dirs(self):
240 oldstats = self.stats
241 self.stats = newstats = {}
242 max_name_len = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000243 for func, (cc, nc, tt, ct, callers) in oldstats.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000244 newfunc = func_strip_path(func)
245 if len(func_std_string(newfunc)) > max_name_len:
246 max_name_len = len(func_std_string(newfunc))
247 newcallers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000248 for func2, caller in callers.iteritems():
249 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000250
Raymond Hettinger54f02222002-06-01 14:18:47 +0000251 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000252 newstats[newfunc] = add_func_stats(
253 newstats[newfunc],
254 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000255 else:
256 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
257 old_top = self.top_level
258 self.top_level = new_top = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000259 for func in old_top:
Tim Peters2344fae2001-01-15 00:50:52 +0000260 new_top[func_strip_path(func)] = None
261
262 self.max_name_len = max_name_len
263
264 self.fcn_list = None
265 self.all_callees = None
266 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000267
Tim Peters2344fae2001-01-15 00:50:52 +0000268 def calc_callees(self):
269 if self.all_callees: return
270 self.all_callees = all_callees = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000271 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000272 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000273 all_callees[func] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000274 for func2, caller in callers.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000275 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000276 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000277 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000278 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000279
Tim Peters2344fae2001-01-15 00:50:52 +0000280 #******************************************************************
281 # The following functions support actual printing of reports
282 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000283
Tim Peters2344fae2001-01-15 00:50:52 +0000284 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000285
Tim Peters2344fae2001-01-15 00:50:52 +0000286 def eval_print_amount(self, sel, list, msg):
287 new_list = list
288 if type(sel) == type(""):
289 new_list = []
290 for func in list:
291 if re.search(sel, func_std_string(func)):
292 new_list.append(func)
293 else:
294 count = len(list)
295 if type(sel) == type(1.0) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000296 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000297 new_list = list[:count]
298 elif type(sel) == type(1) and 0 <= sel < count:
299 count = sel
300 new_list = list[:count]
301 if len(list) != len(new_list):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000302 msg = msg + " List reduced from %r to %r due to restriction <%r>\n" % (
303 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000304
Tim Peters2344fae2001-01-15 00:50:52 +0000305 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000306
Tim Peters2344fae2001-01-15 00:50:52 +0000307 def get_print_list(self, sel_list):
308 width = self.max_name_len
309 if self.fcn_list:
310 list = self.fcn_list[:]
311 msg = " Ordered by: " + self.sort_type + '\n'
312 else:
313 list = self.stats.keys()
314 msg = " Random listing order was used\n"
315
316 for selection in sel_list:
Tim Peters7d016852001-10-08 06:13:19 +0000317 list, msg = self.eval_print_amount(selection, list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000318
319 count = len(list)
320
321 if not list:
322 return 0, list
323 print msg
324 if count < len(self.stats):
325 width = 0
326 for func in list:
327 if len(func_std_string(func)) > width:
328 width = len(func_std_string(func))
329 return width+2, list
330
331 def print_stats(self, *amount):
332 for filename in self.files:
333 print filename
334 if self.files: print
Tim Peters7d016852001-10-08 06:13:19 +0000335 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000336 for func in self.top_level:
Tim Peters2344fae2001-01-15 00:50:52 +0000337 print indent, func_get_function_name(func)
338
Tim Peters7d016852001-10-08 06:13:19 +0000339 print indent, self.total_calls, "function calls",
Tim Peters2344fae2001-01-15 00:50:52 +0000340 if self.total_calls != self.prim_calls:
Tim Peters7d016852001-10-08 06:13:19 +0000341 print "(%d primitive calls)" % self.prim_calls,
342 print "in %.3f CPU seconds" % self.total_tt
Tim Peters2344fae2001-01-15 00:50:52 +0000343 print
344 width, list = self.get_print_list(amount)
345 if list:
346 self.print_title()
347 for func in list:
348 self.print_line(func)
349 print
350 print
351 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000352
Tim Peters2344fae2001-01-15 00:50:52 +0000353 def print_callees(self, *amount):
354 width, list = self.get_print_list(amount)
355 if list:
356 self.calc_callees()
357
358 self.print_call_heading(width, "called...")
359 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000360 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000361 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000362 else:
363 self.print_call_line(width, func, {})
364 print
365 print
366 return self
367
368 def print_callers(self, *amount):
369 width, list = self.get_print_list(amount)
370 if list:
371 self.print_call_heading(width, "was called by...")
372 for func in list:
373 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000374 self.print_call_line(width, func, callers, "<-")
Tim Peters2344fae2001-01-15 00:50:52 +0000375 print
376 print
377 return self
378
379 def print_call_heading(self, name_size, column_title):
Eric S. Raymond373c55e2001-02-09 08:25:29 +0000380 print "Function ".ljust(name_size) + column_title
Armin Rigoa871ef22006-02-08 12:53:56 +0000381 # print sub-header only if we have new-style callers
382 subheader = False
383 for cc, nc, tt, ct, callers in self.stats.itervalues():
384 if callers:
385 value = callers.itervalues().next()
386 subheader = isinstance(value, tuple)
387 break
388 if subheader:
389 print " "*name_size + " ncalls tottime cumtime"
Guido van Rossumadb31051994-06-23 11:42:52 +0000390
Armin Rigoa871ef22006-02-08 12:53:56 +0000391 def print_call_line(self, name_size, source, call_dict, arrow="->"):
392 print func_std_string(source).ljust(name_size) + arrow,
Tim Peters2344fae2001-01-15 00:50:52 +0000393 if not call_dict:
Armin Rigoa871ef22006-02-08 12:53:56 +0000394 print
Tim Peters2344fae2001-01-15 00:50:52 +0000395 return
396 clist = call_dict.keys()
397 clist.sort()
Tim Peters2344fae2001-01-15 00:50:52 +0000398 indent = ""
399 for func in clist:
400 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000401 value = call_dict[func]
402 if isinstance(value, tuple):
403 nc, cc, tt, ct = value
404 if nc != cc:
405 substats = '%d/%d' % (nc, cc)
406 else:
407 substats = '%d' % (nc,)
408 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
409 f8(tt), f8(ct), name)
410 left_width = name_size + 1
411 else:
412 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
413 left_width = name_size + 3
414 print indent*left_width + substats
Tim Peters2344fae2001-01-15 00:50:52 +0000415 indent = " "
416
Tim Peters2344fae2001-01-15 00:50:52 +0000417 def print_title(self):
Tim Peters7d016852001-10-08 06:13:19 +0000418 print ' ncalls tottime percall cumtime percall', \
419 'filename:lineno(function)'
Tim Peters2344fae2001-01-15 00:50:52 +0000420
421 def print_line(self, func): # hack : should print percentages
422 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000423 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000424 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000425 c = c + '/' + str(cc)
Eric S. Raymond373c55e2001-02-09 08:25:29 +0000426 print c.rjust(9),
Tim Peters2344fae2001-01-15 00:50:52 +0000427 print f8(tt),
428 if nc == 0:
429 print ' '*8,
430 else:
431 print f8(tt/nc),
432 print f8(ct),
433 if cc == 0:
434 print ' '*8,
435 else:
436 print f8(ct/cc),
437 print func_std_string(func)
438
Guido van Rossumadb31051994-06-23 11:42:52 +0000439class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000440 """This class provides a generic function for comparing any two tuples.
441 Each instance records a list of tuple-indices (from most significant
442 to least significant), and sort direction (ascending or decending) for
443 each tuple-index. The compare functions can then be used as the function
444 argument to the system sort() function when a list of tuples need to be
445 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000446
Tim Peters2344fae2001-01-15 00:50:52 +0000447 def __init__(self, comp_select_list):
448 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000449
Tim Peters2344fae2001-01-15 00:50:52 +0000450 def compare (self, left, right):
451 for index, direction in self.comp_select_list:
452 l = left[index]
453 r = right[index]
454 if l < r:
455 return -direction
456 if l > r:
457 return direction
458 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000459
Guido van Rossumadb31051994-06-23 11:42:52 +0000460#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000461# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000462
463def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000464 filename, line, name = func_name
465 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000466
467def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000468 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000469
470def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000471 if func_name[:2] == ('~', 0):
472 # special case for built-in functions
473 name = func_name[2]
474 if name.startswith('<') and name.endswith('>'):
475 return '{%s}' % name[1:-1]
476 else:
477 return name
478 else:
479 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000480
481#**************************************************************************
482# The following functions combine statists for pairs functions.
483# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000484# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000485#**************************************************************************
486
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000487def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000488 """Add together all the stats for two profile entries."""
489 cc, nc, tt, ct, callers = source
490 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000491 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000492 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000493
Guido van Rossumadb31051994-06-23 11:42:52 +0000494def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000495 """Combine two caller lists in a single list."""
496 new_callers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000497 for func, caller in target.iteritems():
498 new_callers[func] = caller
499 for func, caller in source.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000500 if func in new_callers:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000501 new_callers[func] = caller + new_callers[func]
Tim Peters2344fae2001-01-15 00:50:52 +0000502 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000503 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000504 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000505
Guido van Rossumadb31051994-06-23 11:42:52 +0000506def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000507 """Sum the caller statistics to get total number of calls received."""
508 nc = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000509 for calls in callers.itervalues():
510 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000511 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000512
513#**************************************************************************
514# The following functions support printing of reports
515#**************************************************************************
516
517def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000518 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000519
520#**************************************************************************
521# Statistics browser added by ESR, April 2001
522#**************************************************************************
523
524if __name__ == '__main__':
525 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000526 try:
527 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000528 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000529 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000530
531 class ProfileBrowser(cmd.Cmd):
532 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000533 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000534 self.prompt = "% "
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000535 if profile is not None:
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000536 self.stats = Stats(profile)
537 else:
538 self.stats = None
539
540 def generic(self, fn, line):
541 args = line.split()
542 processed = []
543 for term in args:
544 try:
545 processed.append(int(term))
546 continue
547 except ValueError:
548 pass
549 try:
550 frac = float(term)
551 if frac > 1 or frac < 0:
552 print "Fraction argument mus be in [0, 1]"
553 continue
554 processed.append(frac)
555 continue
556 except ValueError:
557 pass
558 processed.append(term)
559 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000560 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000561 else:
562 print "No statistics object is loaded."
563 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000564 def generic_help(self):
565 print "Arguments may be:"
566 print "* An integer maximum number of entries to print."
567 print "* A decimal fractional number between 0 and 1, controlling"
568 print " what fraction of selected entries to print."
569 print "* A regular expression; only entries with function names"
570 print " that match it are printed."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000571
572 def do_add(self, line):
573 self.stats.add(line)
574 return 0
575 def help_add(self):
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000576 print "Add profile info from given file to current statistics object."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000577
578 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000579 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000580 def help_callees(self):
581 print "Print callees statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000582 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000583
584 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000585 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000586 def help_callers(self):
587 print "Print callers statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000588 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000589
590 def do_EOF(self, line):
591 print ""
592 return 1
593 def help_EOF(self):
594 print "Leave the profile brower."
595
596 def do_quit(self, line):
597 return 1
598 def help_quit(self):
599 print "Leave the profile brower."
600
601 def do_read(self, line):
602 if line:
603 try:
604 self.stats = Stats(line)
605 except IOError, args:
606 print args[1]
607 return
608 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000609 elif len(self.prompt) > 2:
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000610 line = self.prompt[-2:]
611 else:
612 print "No statistics object is current -- cannot reload."
613 return 0
614 def help_read(self):
615 print "Read in profile data from a specified file."
616
617 def do_reverse(self, line):
618 self.stats.reverse_order()
619 return 0
620 def help_reverse(self):
621 print "Reverse the sort order of the profiling report."
622
623 def do_sort(self, line):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000624 abbrevs = self.stats.get_sort_arg_defs()
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000625 if line and not filter(lambda x,a=abbrevs: x not in a,line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000626 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000627 else:
628 print "Valid sort keys (unique prefixes are accepted):"
Raymond Hettingere0d49722002-06-02 18:55:56 +0000629 for (key, value) in Stats.sort_arg_dict_default.iteritems():
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000630 print "%s -- %s" % (key, value[1])
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000631 return 0
632 def help_sort(self):
633 print "Sort profile data according to specified keys."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000634 print "(Typing `sort' without arguments lists valid keys.)"
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000635 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000636 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000637
638 def do_stats(self, line):
639 return self.generic('print_stats', line)
640 def help_stats(self):
641 print "Print statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000642 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000643
644 def do_strip(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000645 self.stats.strip_dirs()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000646 return 0
647 def help_strip(self):
648 print "Strip leading path information from filenames in the report."
649
650 def postcmd(self, stop, line):
651 if stop:
652 return stop
653 return None
654
655 import sys
656 print "Welcome to the profile statistics browser."
657 if len(sys.argv) > 1:
658 initprofile = sys.argv[1]
659 else:
660 initprofile = None
661 try:
662 ProfileBrowser(initprofile).cmdloop()
663 print "Goodbye."
664 except KeyboardInterrupt:
665 pass
666
667# That's all, folks.