blob: 619f1cf835723be3afae47ce43ac0be2d26512b0 [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
Skip Montanaro262fb922006-04-21 02:31:07 +000035import sys
Guido van Rossumadb31051994-06-23 11:42:52 +000036import os
37import time
Guido van Rossumadb31051994-06-23 11:42:52 +000038import marshal
Guido van Rossum9694fca1997-10-22 21:00:49 +000039import re
Guido van Rossumadb31051994-06-23 11:42:52 +000040
Skip Montanaroc62c81e2001-02-12 02:00:42 +000041__all__ = ["Stats"]
42
Guido van Rossumadb31051994-06-23 11:42:52 +000043class Stats:
Tim Peters2344fae2001-01-15 00:50:52 +000044 """This class is used for creating reports from data generated by the
45 Profile class. It is a "friend" of that class, and imports data either
46 by direct access to members of Profile class, or by reading in a dictionary
47 that was emitted (via marshal) from the Profile class.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000048
Tim Peters2344fae2001-01-15 00:50:52 +000049 The big change from the previous Profiler (in terms of raw functionality)
50 is that an "add()" method has been provided to combine Stats from
51 several distinct profile runs. Both the constructor and the add()
52 method now take arbitrarily many file names as arguments.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000053
Tim Peters2344fae2001-01-15 00:50:52 +000054 All the print methods now take an argument that indicates how many lines
55 to print. If the arg is a floating point number between 0 and 1.0, then
56 it is taken as a decimal percentage of the available lines to be printed
57 (e.g., .1 means print 10% of all available lines). If it is an integer,
58 it is taken to mean the number of lines of data that you wish to have
59 printed.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000060
Tim Peters2344fae2001-01-15 00:50:52 +000061 The sort_stats() method now processes some additional options (i.e., in
Skip Montanaro262fb922006-04-21 02:31:07 +000062 addition to the old -1, 0, 1, or 2). It takes an arbitrary number of
63 quoted strings to select the sort order. For example sort_stats('time',
64 'name') sorts on the major key of 'internal function time', and on the
65 minor key of 'the name of the function'. Look at the two tables in
66 sort_stats() and get_sort_arg_defs(self) for more examples.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000067
Skip Montanaro262fb922006-04-21 02:31:07 +000068 All methods return self, so you can string together commands like:
Tim Peters2344fae2001-01-15 00:50:52 +000069 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
70 print_stats(5).print_callers(5)
71 """
72
Skip Montanaro262fb922006-04-21 02:31:07 +000073 def __init__(self, *args, **kwds):
74 # I can't figure out how to explictly specify a stream keyword arg
75 # with *args:
76 # def __init__(self, *args, stream=sys.stdout): ...
77 # so I use **kwds and sqauwk if something unexpected is passed in.
78 self.stream = sys.stdout
79 if "stream" in kwds:
80 self.stream = kwds["stream"]
81 del kwds["stream"]
82 if kwds:
83 keys = kwds.keys()
84 keys.sort()
85 extras = ", ".join(["%s=%s" % (k, kwds[k]) for k in keys])
86 raise ValueError, "unrecognized keyword args: %s" % extras
Tim Peters2344fae2001-01-15 00:50:52 +000087 if not len(args):
88 arg = None
89 else:
90 arg = args[0]
91 args = args[1:]
92 self.init(arg)
Guido van Rossum68468eb2003-02-27 20:14:51 +000093 self.add(*args)
Tim Peters2344fae2001-01-15 00:50:52 +000094
95 def init(self, arg):
96 self.all_callees = None # calc only if needed
97 self.files = []
98 self.fcn_list = None
99 self.total_tt = 0
100 self.total_calls = 0
101 self.prim_calls = 0
102 self.max_name_len = 0
103 self.top_level = {}
104 self.stats = {}
105 self.sort_arg_dict = {}
106 self.load_stats(arg)
107 trouble = 1
108 try:
109 self.get_top_level_stats()
110 trouble = 0
111 finally:
112 if trouble:
Skip Montanaro262fb922006-04-21 02:31:07 +0000113 print >> self.stream, "Invalid timing data",
114 if self.files: print >> self.stream, self.files[-1],
115 print >> self.stream
Guido van Rossumadb31051994-06-23 11:42:52 +0000116
Tim Peters2344fae2001-01-15 00:50:52 +0000117 def load_stats(self, arg):
118 if not arg: self.stats = {}
Georg Brandl21d900f2006-11-26 19:27:47 +0000119 elif isinstance(arg, basestring):
Tim Peters2344fae2001-01-15 00:50:52 +0000120 f = open(arg, 'rb')
121 self.stats = marshal.load(f)
122 f.close()
123 try:
124 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000125 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000126 except: # in case this is not unix
127 pass
128 self.files = [ arg ]
129 elif hasattr(arg, 'create_stats'):
130 arg.create_stats()
131 self.stats = arg.stats
132 arg.stats = {}
133 if not self.stats:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000134 raise TypeError, "Cannot create or construct a %r object from '%r''" % (
135 self.__class__, arg)
Tim Peters2344fae2001-01-15 00:50:52 +0000136 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000137
Tim Peters2344fae2001-01-15 00:50:52 +0000138 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000139 for func, (cc, nc, tt, ct, callers) in self.stats.items():
140 self.total_calls += nc
141 self.prim_calls += cc
142 self.total_tt += tt
Brett Cannonc3ce0e52008-08-03 22:52:42 +0000143 if ("jprofile", 0, "profiler") in callers:
Tim Peters2344fae2001-01-15 00:50:52 +0000144 self.top_level[func] = None
145 if len(func_std_string(func)) > self.max_name_len:
146 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000147
Tim Peters2344fae2001-01-15 00:50:52 +0000148 def add(self, *arg_list):
149 if not arg_list: return self
Guido van Rossum68468eb2003-02-27 20:14:51 +0000150 if len(arg_list) > 1: self.add(*arg_list[1:])
Tim Peters2344fae2001-01-15 00:50:52 +0000151 other = arg_list[0]
Tim Peters7d016852001-10-08 06:13:19 +0000152 if type(self) != type(other) or self.__class__ != other.__class__:
Tim Peters2344fae2001-01-15 00:50:52 +0000153 other = Stats(other)
Tim Peters7d016852001-10-08 06:13:19 +0000154 self.files += other.files
155 self.total_calls += other.total_calls
156 self.prim_calls += other.prim_calls
157 self.total_tt += other.total_tt
Raymond Hettingere0d49722002-06-02 18:55:56 +0000158 for func in other.top_level:
Tim Peters2344fae2001-01-15 00:50:52 +0000159 self.top_level[func] = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000160
Tim Peters2344fae2001-01-15 00:50:52 +0000161 if self.max_name_len < other.max_name_len:
162 self.max_name_len = other.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000163
Tim Peters2344fae2001-01-15 00:50:52 +0000164 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000165
Raymond Hettingere0d49722002-06-02 18:55:56 +0000166 for func, stat in other.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000167 if func in self.stats:
Tim Peters2344fae2001-01-15 00:50:52 +0000168 old_func_stat = self.stats[func]
169 else:
170 old_func_stat = (0, 0, 0, 0, {},)
Raymond Hettingere0d49722002-06-02 18:55:56 +0000171 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000172 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000173
Fred Drake9c439102003-05-14 14:28:09 +0000174 def dump_stats(self, filename):
175 """Write the profile data to a file we know how to load back."""
176 f = file(filename, 'wb')
177 try:
178 marshal.dump(self.stats, f)
179 finally:
180 f.close()
181
Tim Peters2344fae2001-01-15 00:50:52 +0000182 # list the tuple indices and directions for sorting,
183 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000184 sort_arg_dict_default = {
185 "calls" : (((1,-1), ), "call count"),
186 "cumulative": (((3,-1), ), "cumulative time"),
187 "file" : (((4, 1), ), "file name"),
188 "line" : (((5, 1), ), "line number"),
189 "module" : (((4, 1), ), "file name"),
190 "name" : (((6, 1), ), "function name"),
191 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
192 "pcalls" : (((0,-1), ), "call count"),
193 "stdname" : (((7, 1), ), "standard name"),
194 "time" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000195 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000196
Tim Peters2344fae2001-01-15 00:50:52 +0000197 def get_sort_arg_defs(self):
198 """Expand all abbreviations that are unique."""
199 if not self.sort_arg_dict:
200 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000201 bad_list = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000202 for word, tup in self.sort_arg_dict_default.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000203 fragment = word
204 while fragment:
205 if not fragment:
206 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000207 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000208 bad_list[fragment] = 0
209 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000210 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000211 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000212 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000213 del dict[word]
214 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000215
Tim Peters2344fae2001-01-15 00:50:52 +0000216 def sort_stats(self, *field):
217 if not field:
218 self.fcn_list = 0
219 return self
220 if len(field) == 1 and type(field[0]) == type(1):
221 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000222 field = [ {-1: "stdname",
223 0:"calls",
224 1:"time",
Tim Peters2344fae2001-01-15 00:50:52 +0000225 2: "cumulative" } [ field[0] ] ]
226
227 sort_arg_defs = self.get_sort_arg_defs()
228 sort_tuple = ()
229 self.sort_type = ""
230 connector = ""
231 for word in field:
232 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000233 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000234 connector = ", "
235
236 stats_list = []
Raymond Hettingere0d49722002-06-02 18:55:56 +0000237 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Tim Peters7d016852001-10-08 06:13:19 +0000238 stats_list.append((cc, nc, tt, ct) + func +
239 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000240
Brett Cannonc3ce0e52008-08-03 22:52:42 +0000241 stats_list.sort(key=CmpToKey(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000242
243 self.fcn_list = fcn_list = []
244 for tuple in stats_list:
245 fcn_list.append(tuple[-1])
246 return self
247
Tim Peters2344fae2001-01-15 00:50:52 +0000248 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000249 if self.fcn_list:
250 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000251 return self
252
253 def strip_dirs(self):
254 oldstats = self.stats
255 self.stats = newstats = {}
256 max_name_len = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000257 for func, (cc, nc, tt, ct, callers) in oldstats.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000258 newfunc = func_strip_path(func)
259 if len(func_std_string(newfunc)) > max_name_len:
260 max_name_len = len(func_std_string(newfunc))
261 newcallers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000262 for func2, caller in callers.iteritems():
263 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000264
Raymond Hettinger54f02222002-06-01 14:18:47 +0000265 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000266 newstats[newfunc] = add_func_stats(
267 newstats[newfunc],
268 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000269 else:
270 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
271 old_top = self.top_level
272 self.top_level = new_top = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000273 for func in old_top:
Tim Peters2344fae2001-01-15 00:50:52 +0000274 new_top[func_strip_path(func)] = None
275
276 self.max_name_len = max_name_len
277
278 self.fcn_list = None
279 self.all_callees = None
280 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000281
Tim Peters2344fae2001-01-15 00:50:52 +0000282 def calc_callees(self):
283 if self.all_callees: return
284 self.all_callees = all_callees = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000285 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000286 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000287 all_callees[func] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000288 for func2, caller in callers.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000289 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000290 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000291 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000292 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000293
Tim Peters2344fae2001-01-15 00:50:52 +0000294 #******************************************************************
295 # The following functions support actual printing of reports
296 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000297
Tim Peters2344fae2001-01-15 00:50:52 +0000298 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000299
Tim Peters2344fae2001-01-15 00:50:52 +0000300 def eval_print_amount(self, sel, list, msg):
301 new_list = list
302 if type(sel) == type(""):
303 new_list = []
304 for func in list:
305 if re.search(sel, func_std_string(func)):
306 new_list.append(func)
307 else:
308 count = len(list)
309 if type(sel) == type(1.0) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000310 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000311 new_list = list[:count]
312 elif type(sel) == type(1) and 0 <= sel < count:
313 count = sel
314 new_list = list[:count]
315 if len(list) != len(new_list):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000316 msg = msg + " List reduced from %r to %r due to restriction <%r>\n" % (
317 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000318
Tim Peters2344fae2001-01-15 00:50:52 +0000319 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000320
Tim Peters2344fae2001-01-15 00:50:52 +0000321 def get_print_list(self, sel_list):
322 width = self.max_name_len
323 if self.fcn_list:
324 list = self.fcn_list[:]
325 msg = " Ordered by: " + self.sort_type + '\n'
326 else:
327 list = self.stats.keys()
328 msg = " Random listing order was used\n"
329
330 for selection in sel_list:
Tim Peters7d016852001-10-08 06:13:19 +0000331 list, msg = self.eval_print_amount(selection, list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000332
333 count = len(list)
334
335 if not list:
336 return 0, list
Skip Montanaro262fb922006-04-21 02:31:07 +0000337 print >> self.stream, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000338 if count < len(self.stats):
339 width = 0
340 for func in list:
341 if len(func_std_string(func)) > width:
342 width = len(func_std_string(func))
343 return width+2, list
344
345 def print_stats(self, *amount):
346 for filename in self.files:
Skip Montanaro262fb922006-04-21 02:31:07 +0000347 print >> self.stream, filename
348 if self.files: print >> self.stream
Tim Peters7d016852001-10-08 06:13:19 +0000349 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000350 for func in self.top_level:
Skip Montanaro262fb922006-04-21 02:31:07 +0000351 print >> self.stream, indent, func_get_function_name(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000352
Skip Montanaro262fb922006-04-21 02:31:07 +0000353 print >> self.stream, indent, self.total_calls, "function calls",
Tim Peters2344fae2001-01-15 00:50:52 +0000354 if self.total_calls != self.prim_calls:
Skip Montanaro262fb922006-04-21 02:31:07 +0000355 print >> self.stream, "(%d primitive calls)" % self.prim_calls,
356 print >> self.stream, "in %.3f CPU seconds" % self.total_tt
357 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000358 width, list = self.get_print_list(amount)
359 if list:
360 self.print_title()
361 for func in list:
362 self.print_line(func)
Skip Montanaro262fb922006-04-21 02:31:07 +0000363 print >> self.stream
364 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000365 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000366
Tim Peters2344fae2001-01-15 00:50:52 +0000367 def print_callees(self, *amount):
368 width, list = self.get_print_list(amount)
369 if list:
370 self.calc_callees()
371
372 self.print_call_heading(width, "called...")
373 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000374 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000375 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000376 else:
377 self.print_call_line(width, func, {})
Skip Montanaro262fb922006-04-21 02:31:07 +0000378 print >> self.stream
379 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000380 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]
Armin Rigoa871ef22006-02-08 12:53:56 +0000388 self.print_call_line(width, func, callers, "<-")
Skip Montanaro262fb922006-04-21 02:31:07 +0000389 print >> self.stream
390 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000391 return self
392
393 def print_call_heading(self, name_size, column_title):
Skip Montanaro262fb922006-04-21 02:31:07 +0000394 print >> self.stream, "Function ".ljust(name_size) + column_title
Armin Rigoa871ef22006-02-08 12:53:56 +0000395 # print sub-header only if we have new-style callers
396 subheader = False
397 for cc, nc, tt, ct, callers in self.stats.itervalues():
398 if callers:
399 value = callers.itervalues().next()
400 subheader = isinstance(value, tuple)
401 break
402 if subheader:
Skip Montanaro262fb922006-04-21 02:31:07 +0000403 print >> self.stream, " "*name_size + " ncalls tottime cumtime"
Guido van Rossumadb31051994-06-23 11:42:52 +0000404
Armin Rigoa871ef22006-02-08 12:53:56 +0000405 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Skip Montanaro262fb922006-04-21 02:31:07 +0000406 print >> self.stream, func_std_string(source).ljust(name_size) + arrow,
Tim Peters2344fae2001-01-15 00:50:52 +0000407 if not call_dict:
Skip Montanaro262fb922006-04-21 02:31:07 +0000408 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000409 return
410 clist = call_dict.keys()
411 clist.sort()
Tim Peters2344fae2001-01-15 00:50:52 +0000412 indent = ""
413 for func in clist:
414 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000415 value = call_dict[func]
416 if isinstance(value, tuple):
417 nc, cc, tt, ct = value
418 if nc != cc:
419 substats = '%d/%d' % (nc, cc)
420 else:
421 substats = '%d' % (nc,)
422 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
423 f8(tt), f8(ct), name)
424 left_width = name_size + 1
425 else:
426 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
427 left_width = name_size + 3
Skip Montanaro262fb922006-04-21 02:31:07 +0000428 print >> self.stream, indent*left_width + substats
Tim Peters2344fae2001-01-15 00:50:52 +0000429 indent = " "
430
Tim Peters2344fae2001-01-15 00:50:52 +0000431 def print_title(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000432 print >> self.stream, ' ncalls tottime percall cumtime percall',
433 print >> self.stream, 'filename:lineno(function)'
Tim Peters2344fae2001-01-15 00:50:52 +0000434
435 def print_line(self, func): # hack : should print percentages
436 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000437 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000438 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000439 c = c + '/' + str(cc)
Skip Montanaro262fb922006-04-21 02:31:07 +0000440 print >> self.stream, c.rjust(9),
441 print >> self.stream, f8(tt),
Tim Peters2344fae2001-01-15 00:50:52 +0000442 if nc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000443 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000444 else:
Ezio Melotti8dc04a42010-08-02 00:24:26 +0000445 print >> self.stream, f8(float(tt)/nc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000446 print >> self.stream, f8(ct),
Tim Peters2344fae2001-01-15 00:50:52 +0000447 if cc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000448 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000449 else:
Ezio Melotti8dc04a42010-08-02 00:24:26 +0000450 print >> self.stream, f8(float(ct)/cc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000451 print >> self.stream, func_std_string(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000452
Guido van Rossumadb31051994-06-23 11:42:52 +0000453class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000454 """This class provides a generic function for comparing any two tuples.
455 Each instance records a list of tuple-indices (from most significant
456 to least significant), and sort direction (ascending or decending) for
457 each tuple-index. The compare functions can then be used as the function
458 argument to the system sort() function when a list of tuples need to be
459 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000460
Tim Peters2344fae2001-01-15 00:50:52 +0000461 def __init__(self, comp_select_list):
462 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000463
Tim Peters2344fae2001-01-15 00:50:52 +0000464 def compare (self, left, right):
465 for index, direction in self.comp_select_list:
466 l = left[index]
467 r = right[index]
468 if l < r:
469 return -direction
470 if l > r:
471 return direction
472 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000473
Brett Cannonc3ce0e52008-08-03 22:52:42 +0000474def CmpToKey(mycmp):
475 """Convert a cmp= function into a key= function"""
476 class K(object):
477 def __init__(self, obj):
478 self.obj = obj
479 def __lt__(self, other):
480 return mycmp(self.obj, other.obj) == -1
481 return K
482
483
Guido van Rossumadb31051994-06-23 11:42:52 +0000484#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000485# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000486
487def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000488 filename, line, name = func_name
489 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000490
491def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000492 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000493
494def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000495 if func_name[:2] == ('~', 0):
496 # special case for built-in functions
497 name = func_name[2]
498 if name.startswith('<') and name.endswith('>'):
499 return '{%s}' % name[1:-1]
500 else:
501 return name
502 else:
503 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000504
505#**************************************************************************
506# The following functions combine statists for pairs functions.
507# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000508# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000509#**************************************************************************
510
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000511def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000512 """Add together all the stats for two profile entries."""
513 cc, nc, tt, ct, callers = source
514 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000515 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000516 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000517
Guido van Rossumadb31051994-06-23 11:42:52 +0000518def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000519 """Combine two caller lists in a single list."""
520 new_callers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000521 for func, caller in target.iteritems():
522 new_callers[func] = caller
523 for func, caller in source.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000524 if func in new_callers:
Georg Brandl800a3542010-08-02 17:40:28 +0000525 if isinstance(caller, tuple):
526 # format used by cProfile
527 new_callers[func] = tuple([i[0] + i[1] for i in
528 zip(caller, new_callers[func])])
529 else:
530 # format used by profile
531 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000532 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000533 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000534 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000535
Guido van Rossumadb31051994-06-23 11:42:52 +0000536def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000537 """Sum the caller statistics to get total number of calls received."""
538 nc = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000539 for calls in callers.itervalues():
540 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000541 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000542
543#**************************************************************************
544# The following functions support printing of reports
545#**************************************************************************
546
547def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000548 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000549
550#**************************************************************************
551# Statistics browser added by ESR, April 2001
552#**************************************************************************
553
554if __name__ == '__main__':
555 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000556 try:
557 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000558 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000559 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000560
561 class ProfileBrowser(cmd.Cmd):
562 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000563 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000564 self.prompt = "% "
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000565 if profile is not None:
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000566 self.stats = Stats(profile)
Neal Norwitze588c2b2006-06-11 07:27:56 +0000567 self.stream = self.stats.stream
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000568 else:
569 self.stats = None
Neal Norwitze588c2b2006-06-11 07:27:56 +0000570 self.stream = sys.stdout
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000571
572 def generic(self, fn, line):
573 args = line.split()
574 processed = []
575 for term in args:
576 try:
577 processed.append(int(term))
578 continue
579 except ValueError:
580 pass
581 try:
582 frac = float(term)
583 if frac > 1 or frac < 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000584 print >> self.stream, "Fraction argument must be in [0, 1]"
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000585 continue
586 processed.append(frac)
587 continue
588 except ValueError:
589 pass
590 processed.append(term)
591 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000592 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000593 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000594 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000595 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000596 def generic_help(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000597 print >> self.stream, "Arguments may be:"
598 print >> self.stream, "* An integer maximum number of entries to print."
599 print >> self.stream, "* A decimal fractional number between 0 and 1, controlling"
600 print >> self.stream, " what fraction of selected entries to print."
601 print >> self.stream, "* A regular expression; only entries with function names"
602 print >> self.stream, " that match it are printed."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000603
604 def do_add(self, line):
Georg Brandld9ede202010-08-01 22:13:33 +0000605 if self.stats:
606 self.stats.add(line)
607 else:
608 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000609 return 0
610 def help_add(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000611 print >> self.stream, "Add profile info from given file to current statistics object."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000612
613 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000614 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000615 def help_callees(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000616 print >> self.stream, "Print callees statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000617 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000618
619 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000620 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000621 def help_callers(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000622 print >> self.stream, "Print callers statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000623 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000624
625 def do_EOF(self, line):
Skip Montanaro262fb922006-04-21 02:31:07 +0000626 print >> self.stream, ""
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000627 return 1
628 def help_EOF(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000629 print >> self.stream, "Leave the profile brower."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000630
631 def do_quit(self, line):
632 return 1
633 def help_quit(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000634 print >> self.stream, "Leave the profile brower."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000635
636 def do_read(self, line):
637 if line:
638 try:
639 self.stats = Stats(line)
640 except IOError, args:
Skip Montanaro262fb922006-04-21 02:31:07 +0000641 print >> self.stream, args[1]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000642 return
Georg Brandld9ede202010-08-01 22:13:33 +0000643 except Exception as err:
644 print >> self.stream, err.__class__.__name__ + ':', err
645 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000646 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000647 elif len(self.prompt) > 2:
Georg Brandld9ede202010-08-01 22:13:33 +0000648 line = self.prompt[:-2]
649 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000650 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000651 print >> self.stream, "No statistics object is current -- cannot reload."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000652 return 0
653 def help_read(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000654 print >> self.stream, "Read in profile data from a specified file."
Georg Brandld9ede202010-08-01 22:13:33 +0000655 print >> self.stream, "Without argument, reload the current file."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000656
657 def do_reverse(self, line):
Georg Brandld9ede202010-08-01 22:13:33 +0000658 if self.stats:
659 self.stats.reverse_order()
660 else:
661 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000662 return 0
663 def help_reverse(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000664 print >> self.stream, "Reverse the sort order of the profiling report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000665
666 def do_sort(self, line):
Georg Brandld9ede202010-08-01 22:13:33 +0000667 if not self.stats:
668 print >> self.stream, "No statistics object is loaded."
669 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000670 abbrevs = self.stats.get_sort_arg_defs()
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000671 if line and not filter(lambda x,a=abbrevs: x not in a,line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000672 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000673 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000674 print >> self.stream, "Valid sort keys (unique prefixes are accepted):"
Raymond Hettingere0d49722002-06-02 18:55:56 +0000675 for (key, value) in Stats.sort_arg_dict_default.iteritems():
Skip Montanaro262fb922006-04-21 02:31:07 +0000676 print >> self.stream, "%s -- %s" % (key, value[1])
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000677 return 0
678 def help_sort(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000679 print >> self.stream, "Sort profile data according to specified keys."
680 print >> self.stream, "(Typing `sort' without arguments lists valid keys.)"
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000681 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000682 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000683
684 def do_stats(self, line):
685 return self.generic('print_stats', line)
686 def help_stats(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000687 print >> self.stream, "Print statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000688 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000689
690 def do_strip(self, line):
Georg Brandld9ede202010-08-01 22:13:33 +0000691 if self.stats:
692 self.stats.strip_dirs()
693 else:
694 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000695 def help_strip(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000696 print >> self.stream, "Strip leading path information from filenames in the report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000697
Georg Brandld9ede202010-08-01 22:13:33 +0000698 def help_help(self):
699 print >> self.stream, "Show help for a given command."
700
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000701 def postcmd(self, stop, line):
702 if stop:
703 return stop
704 return None
705
706 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000707 if len(sys.argv) > 1:
708 initprofile = sys.argv[1]
709 else:
710 initprofile = None
711 try:
Neal Norwitze588c2b2006-06-11 07:27:56 +0000712 browser = ProfileBrowser(initprofile)
713 print >> browser.stream, "Welcome to the profile statistics browser."
714 browser.cmdloop()
715 print >> browser.stream, "Goodbye."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000716 except KeyboardInterrupt:
717 pass
718
719# That's all, folks.