blob: 8b6081040a589a9e7489209243084ee094656af2 [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
Raymond Hettingerbb006cf2010-04-04 21:45:01 +000040from functools import cmp_to_key
Guido van Rossumadb31051994-06-23 11:42:52 +000041
Skip Montanaroc62c81e2001-02-12 02:00:42 +000042__all__ = ["Stats"]
43
Guido van Rossumadb31051994-06-23 11:42:52 +000044class Stats:
Tim Peters2344fae2001-01-15 00:50:52 +000045 """This class is used for creating reports from data generated by the
46 Profile class. It is a "friend" of that class, and imports data either
47 by direct access to members of Profile class, or by reading in a dictionary
48 that was emitted (via marshal) from the Profile class.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000049
Tim Peters2344fae2001-01-15 00:50:52 +000050 The big change from the previous Profiler (in terms of raw functionality)
51 is that an "add()" method has been provided to combine Stats from
52 several distinct profile runs. Both the constructor and the add()
53 method now take arbitrarily many file names as arguments.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000054
Tim Peters2344fae2001-01-15 00:50:52 +000055 All the print methods now take an argument that indicates how many lines
56 to print. If the arg is a floating point number between 0 and 1.0, then
57 it is taken as a decimal percentage of the available lines to be printed
58 (e.g., .1 means print 10% of all available lines). If it is an integer,
59 it is taken to mean the number of lines of data that you wish to have
60 printed.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000061
Tim Peters2344fae2001-01-15 00:50:52 +000062 The sort_stats() method now processes some additional options (i.e., in
Skip Montanaro262fb922006-04-21 02:31:07 +000063 addition to the old -1, 0, 1, or 2). It takes an arbitrary number of
64 quoted strings to select the sort order. For example sort_stats('time',
65 'name') sorts on the major key of 'internal function time', and on the
66 minor key of 'the name of the function'. Look at the two tables in
67 sort_stats() and get_sort_arg_defs(self) for more examples.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000068
Skip Montanaro262fb922006-04-21 02:31:07 +000069 All methods return self, so you can string together commands like:
Tim Peters2344fae2001-01-15 00:50:52 +000070 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
71 print_stats(5).print_callers(5)
72 """
73
Skip Montanaro262fb922006-04-21 02:31:07 +000074 def __init__(self, *args, **kwds):
75 # I can't figure out how to explictly specify a stream keyword arg
76 # with *args:
77 # def __init__(self, *args, stream=sys.stdout): ...
78 # so I use **kwds and sqauwk if something unexpected is passed in.
79 self.stream = sys.stdout
80 if "stream" in kwds:
81 self.stream = kwds["stream"]
82 del kwds["stream"]
83 if kwds:
84 keys = kwds.keys()
85 keys.sort()
86 extras = ", ".join(["%s=%s" % (k, kwds[k]) for k in keys])
87 raise ValueError, "unrecognized keyword args: %s" % extras
Tim Peters2344fae2001-01-15 00:50:52 +000088 if not len(args):
89 arg = None
90 else:
91 arg = args[0]
92 args = args[1:]
93 self.init(arg)
Guido van Rossum68468eb2003-02-27 20:14:51 +000094 self.add(*args)
Tim Peters2344fae2001-01-15 00:50:52 +000095
96 def init(self, arg):
97 self.all_callees = None # calc only if needed
98 self.files = []
99 self.fcn_list = None
100 self.total_tt = 0
101 self.total_calls = 0
102 self.prim_calls = 0
103 self.max_name_len = 0
104 self.top_level = {}
105 self.stats = {}
106 self.sort_arg_dict = {}
107 self.load_stats(arg)
108 trouble = 1
109 try:
110 self.get_top_level_stats()
111 trouble = 0
112 finally:
113 if trouble:
Skip Montanaro262fb922006-04-21 02:31:07 +0000114 print >> self.stream, "Invalid timing data",
115 if self.files: print >> self.stream, self.files[-1],
116 print >> self.stream
Guido van Rossumadb31051994-06-23 11:42:52 +0000117
Tim Peters2344fae2001-01-15 00:50:52 +0000118 def load_stats(self, arg):
119 if not arg: self.stats = {}
Georg Brandl21d900f2006-11-26 19:27:47 +0000120 elif isinstance(arg, basestring):
Tim Peters2344fae2001-01-15 00:50:52 +0000121 f = open(arg, 'rb')
122 self.stats = marshal.load(f)
123 f.close()
124 try:
125 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000126 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000127 except: # in case this is not unix
128 pass
129 self.files = [ arg ]
130 elif hasattr(arg, 'create_stats'):
131 arg.create_stats()
132 self.stats = arg.stats
133 arg.stats = {}
134 if not self.stats:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000135 raise TypeError, "Cannot create or construct a %r object from '%r''" % (
136 self.__class__, arg)
Tim Peters2344fae2001-01-15 00:50:52 +0000137 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000138
Tim Peters2344fae2001-01-15 00:50:52 +0000139 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000140 for func, (cc, nc, tt, ct, callers) in self.stats.items():
141 self.total_calls += nc
142 self.prim_calls += cc
143 self.total_tt += tt
Brett Cannonc3ce0e52008-08-03 22:52:42 +0000144 if ("jprofile", 0, "profiler") in callers:
Tim Peters2344fae2001-01-15 00:50:52 +0000145 self.top_level[func] = None
146 if len(func_std_string(func)) > self.max_name_len:
147 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000148
Tim Peters2344fae2001-01-15 00:50:52 +0000149 def add(self, *arg_list):
150 if not arg_list: return self
Guido van Rossum68468eb2003-02-27 20:14:51 +0000151 if len(arg_list) > 1: self.add(*arg_list[1:])
Tim Peters2344fae2001-01-15 00:50:52 +0000152 other = arg_list[0]
Tim Peters7d016852001-10-08 06:13:19 +0000153 if type(self) != type(other) or self.__class__ != other.__class__:
Tim Peters2344fae2001-01-15 00:50:52 +0000154 other = Stats(other)
Tim Peters7d016852001-10-08 06:13:19 +0000155 self.files += other.files
156 self.total_calls += other.total_calls
157 self.prim_calls += other.prim_calls
158 self.total_tt += other.total_tt
Raymond Hettingere0d49722002-06-02 18:55:56 +0000159 for func in other.top_level:
Tim Peters2344fae2001-01-15 00:50:52 +0000160 self.top_level[func] = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000161
Tim Peters2344fae2001-01-15 00:50:52 +0000162 if self.max_name_len < other.max_name_len:
163 self.max_name_len = other.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000164
Tim Peters2344fae2001-01-15 00:50:52 +0000165 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000166
Raymond Hettingere0d49722002-06-02 18:55:56 +0000167 for func, stat in other.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000168 if func in self.stats:
Tim Peters2344fae2001-01-15 00:50:52 +0000169 old_func_stat = self.stats[func]
170 else:
171 old_func_stat = (0, 0, 0, 0, {},)
Raymond Hettingere0d49722002-06-02 18:55:56 +0000172 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000173 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000174
Fred Drake9c439102003-05-14 14:28:09 +0000175 def dump_stats(self, filename):
176 """Write the profile data to a file we know how to load back."""
177 f = file(filename, 'wb')
178 try:
179 marshal.dump(self.stats, f)
180 finally:
181 f.close()
182
Tim Peters2344fae2001-01-15 00:50:52 +0000183 # list the tuple indices and directions for sorting,
184 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000185 sort_arg_dict_default = {
186 "calls" : (((1,-1), ), "call count"),
187 "cumulative": (((3,-1), ), "cumulative time"),
188 "file" : (((4, 1), ), "file name"),
189 "line" : (((5, 1), ), "line number"),
190 "module" : (((4, 1), ), "file name"),
191 "name" : (((6, 1), ), "function name"),
192 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
193 "pcalls" : (((0,-1), ), "call count"),
194 "stdname" : (((7, 1), ), "standard name"),
195 "time" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000196 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000197
Tim Peters2344fae2001-01-15 00:50:52 +0000198 def get_sort_arg_defs(self):
199 """Expand all abbreviations that are unique."""
200 if not self.sort_arg_dict:
201 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000202 bad_list = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000203 for word, tup in self.sort_arg_dict_default.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000204 fragment = word
205 while fragment:
206 if not fragment:
207 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000208 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000209 bad_list[fragment] = 0
210 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000211 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000212 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000213 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000214 del dict[word]
215 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000216
Tim Peters2344fae2001-01-15 00:50:52 +0000217 def sort_stats(self, *field):
218 if not field:
219 self.fcn_list = 0
220 return self
221 if len(field) == 1 and type(field[0]) == type(1):
222 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000223 field = [ {-1: "stdname",
224 0:"calls",
225 1:"time",
Tim Peters2344fae2001-01-15 00:50:52 +0000226 2: "cumulative" } [ field[0] ] ]
227
228 sort_arg_defs = self.get_sort_arg_defs()
229 sort_tuple = ()
230 self.sort_type = ""
231 connector = ""
232 for word in field:
233 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000234 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000235 connector = ", "
236
237 stats_list = []
Raymond Hettingere0d49722002-06-02 18:55:56 +0000238 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Tim Peters7d016852001-10-08 06:13:19 +0000239 stats_list.append((cc, nc, tt, ct) + func +
240 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000241
Raymond Hettingerbb006cf2010-04-04 21:45:01 +0000242 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000243
244 self.fcn_list = fcn_list = []
245 for tuple in stats_list:
246 fcn_list.append(tuple[-1])
247 return self
248
Tim Peters2344fae2001-01-15 00:50:52 +0000249 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000250 if self.fcn_list:
251 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000252 return self
253
254 def strip_dirs(self):
255 oldstats = self.stats
256 self.stats = newstats = {}
257 max_name_len = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000258 for func, (cc, nc, tt, ct, callers) in oldstats.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000259 newfunc = func_strip_path(func)
260 if len(func_std_string(newfunc)) > max_name_len:
261 max_name_len = len(func_std_string(newfunc))
262 newcallers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000263 for func2, caller in callers.iteritems():
264 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000265
Raymond Hettinger54f02222002-06-01 14:18:47 +0000266 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000267 newstats[newfunc] = add_func_stats(
268 newstats[newfunc],
269 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000270 else:
271 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
272 old_top = self.top_level
273 self.top_level = new_top = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000274 for func in old_top:
Tim Peters2344fae2001-01-15 00:50:52 +0000275 new_top[func_strip_path(func)] = None
276
277 self.max_name_len = max_name_len
278
279 self.fcn_list = None
280 self.all_callees = None
281 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000282
Tim Peters2344fae2001-01-15 00:50:52 +0000283 def calc_callees(self):
284 if self.all_callees: return
285 self.all_callees = all_callees = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000286 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000287 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000288 all_callees[func] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000289 for func2, caller in callers.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000290 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000291 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000292 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000293 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000294
Tim Peters2344fae2001-01-15 00:50:52 +0000295 #******************************************************************
296 # The following functions support actual printing of reports
297 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000298
Tim Peters2344fae2001-01-15 00:50:52 +0000299 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000300
Tim Peters2344fae2001-01-15 00:50:52 +0000301 def eval_print_amount(self, sel, list, msg):
302 new_list = list
303 if type(sel) == type(""):
304 new_list = []
305 for func in list:
306 if re.search(sel, func_std_string(func)):
307 new_list.append(func)
308 else:
309 count = len(list)
310 if type(sel) == type(1.0) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000311 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000312 new_list = list[:count]
313 elif type(sel) == type(1) and 0 <= sel < count:
314 count = sel
315 new_list = list[:count]
316 if len(list) != len(new_list):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000317 msg = msg + " List reduced from %r to %r due to restriction <%r>\n" % (
318 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000319
Tim Peters2344fae2001-01-15 00:50:52 +0000320 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000321
Tim Peters2344fae2001-01-15 00:50:52 +0000322 def get_print_list(self, sel_list):
323 width = self.max_name_len
324 if self.fcn_list:
325 list = self.fcn_list[:]
326 msg = " Ordered by: " + self.sort_type + '\n'
327 else:
328 list = self.stats.keys()
329 msg = " Random listing order was used\n"
330
331 for selection in sel_list:
Tim Peters7d016852001-10-08 06:13:19 +0000332 list, msg = self.eval_print_amount(selection, list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000333
334 count = len(list)
335
336 if not list:
337 return 0, list
Skip Montanaro262fb922006-04-21 02:31:07 +0000338 print >> self.stream, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000339 if count < len(self.stats):
340 width = 0
341 for func in list:
342 if len(func_std_string(func)) > width:
343 width = len(func_std_string(func))
344 return width+2, list
345
346 def print_stats(self, *amount):
347 for filename in self.files:
Skip Montanaro262fb922006-04-21 02:31:07 +0000348 print >> self.stream, filename
349 if self.files: print >> self.stream
Tim Peters7d016852001-10-08 06:13:19 +0000350 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000351 for func in self.top_level:
Skip Montanaro262fb922006-04-21 02:31:07 +0000352 print >> self.stream, indent, func_get_function_name(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000353
Skip Montanaro262fb922006-04-21 02:31:07 +0000354 print >> self.stream, indent, self.total_calls, "function calls",
Tim Peters2344fae2001-01-15 00:50:52 +0000355 if self.total_calls != self.prim_calls:
Skip Montanaro262fb922006-04-21 02:31:07 +0000356 print >> self.stream, "(%d primitive calls)" % self.prim_calls,
357 print >> self.stream, "in %.3f CPU seconds" % self.total_tt
358 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000359 width, list = self.get_print_list(amount)
360 if list:
361 self.print_title()
362 for func in list:
363 self.print_line(func)
Skip Montanaro262fb922006-04-21 02:31:07 +0000364 print >> self.stream
365 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000366 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000367
Tim Peters2344fae2001-01-15 00:50:52 +0000368 def print_callees(self, *amount):
369 width, list = self.get_print_list(amount)
370 if list:
371 self.calc_callees()
372
373 self.print_call_heading(width, "called...")
374 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000375 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000376 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000377 else:
378 self.print_call_line(width, func, {})
Skip Montanaro262fb922006-04-21 02:31:07 +0000379 print >> self.stream
380 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000381 return self
382
383 def print_callers(self, *amount):
384 width, list = self.get_print_list(amount)
385 if list:
386 self.print_call_heading(width, "was called by...")
387 for func in list:
388 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000389 self.print_call_line(width, func, callers, "<-")
Skip Montanaro262fb922006-04-21 02:31:07 +0000390 print >> self.stream
391 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000392 return self
393
394 def print_call_heading(self, name_size, column_title):
Skip Montanaro262fb922006-04-21 02:31:07 +0000395 print >> self.stream, "Function ".ljust(name_size) + column_title
Armin Rigoa871ef22006-02-08 12:53:56 +0000396 # print sub-header only if we have new-style callers
397 subheader = False
398 for cc, nc, tt, ct, callers in self.stats.itervalues():
399 if callers:
400 value = callers.itervalues().next()
401 subheader = isinstance(value, tuple)
402 break
403 if subheader:
Skip Montanaro262fb922006-04-21 02:31:07 +0000404 print >> self.stream, " "*name_size + " ncalls tottime cumtime"
Guido van Rossumadb31051994-06-23 11:42:52 +0000405
Armin Rigoa871ef22006-02-08 12:53:56 +0000406 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Skip Montanaro262fb922006-04-21 02:31:07 +0000407 print >> self.stream, func_std_string(source).ljust(name_size) + arrow,
Tim Peters2344fae2001-01-15 00:50:52 +0000408 if not call_dict:
Skip Montanaro262fb922006-04-21 02:31:07 +0000409 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000410 return
411 clist = call_dict.keys()
412 clist.sort()
Tim Peters2344fae2001-01-15 00:50:52 +0000413 indent = ""
414 for func in clist:
415 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000416 value = call_dict[func]
417 if isinstance(value, tuple):
418 nc, cc, tt, ct = value
419 if nc != cc:
420 substats = '%d/%d' % (nc, cc)
421 else:
422 substats = '%d' % (nc,)
423 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
424 f8(tt), f8(ct), name)
425 left_width = name_size + 1
426 else:
427 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
428 left_width = name_size + 3
Skip Montanaro262fb922006-04-21 02:31:07 +0000429 print >> self.stream, indent*left_width + substats
Tim Peters2344fae2001-01-15 00:50:52 +0000430 indent = " "
431
Tim Peters2344fae2001-01-15 00:50:52 +0000432 def print_title(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000433 print >> self.stream, ' ncalls tottime percall cumtime percall',
434 print >> self.stream, 'filename:lineno(function)'
Tim Peters2344fae2001-01-15 00:50:52 +0000435
436 def print_line(self, func): # hack : should print percentages
437 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000438 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000439 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000440 c = c + '/' + str(cc)
Skip Montanaro262fb922006-04-21 02:31:07 +0000441 print >> self.stream, c.rjust(9),
442 print >> self.stream, f8(tt),
Tim Peters2344fae2001-01-15 00:50:52 +0000443 if nc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000444 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000445 else:
Antoine Pitroub9d49632010-01-04 23:22:44 +0000446 print >> self.stream, f8(float(tt)/nc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000447 print >> self.stream, f8(ct),
Tim Peters2344fae2001-01-15 00:50:52 +0000448 if cc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000449 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000450 else:
Antoine Pitroub9d49632010-01-04 23:22:44 +0000451 print >> self.stream, f8(float(ct)/cc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000452 print >> self.stream, func_std_string(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000453
Guido van Rossumadb31051994-06-23 11:42:52 +0000454class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000455 """This class provides a generic function for comparing any two tuples.
456 Each instance records a list of tuple-indices (from most significant
457 to least significant), and sort direction (ascending or decending) for
458 each tuple-index. The compare functions can then be used as the function
459 argument to the system sort() function when a list of tuples need to be
460 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000461
Tim Peters2344fae2001-01-15 00:50:52 +0000462 def __init__(self, comp_select_list):
463 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000464
Tim Peters2344fae2001-01-15 00:50:52 +0000465 def compare (self, left, right):
466 for index, direction in self.comp_select_list:
467 l = left[index]
468 r = right[index]
469 if l < r:
470 return -direction
471 if l > r:
472 return direction
473 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000474
Guido van Rossumadb31051994-06-23 11:42:52 +0000475#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000476# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000477
478def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000479 filename, line, name = func_name
480 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000481
482def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000483 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000484
485def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000486 if func_name[:2] == ('~', 0):
487 # special case for built-in functions
488 name = func_name[2]
489 if name.startswith('<') and name.endswith('>'):
490 return '{%s}' % name[1:-1]
491 else:
492 return name
493 else:
494 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000495
496#**************************************************************************
497# The following functions combine statists for pairs functions.
498# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000499# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000500#**************************************************************************
501
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000502def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000503 """Add together all the stats for two profile entries."""
504 cc, nc, tt, ct, callers = source
505 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000506 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000507 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000508
Guido van Rossumadb31051994-06-23 11:42:52 +0000509def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000510 """Combine two caller lists in a single list."""
511 new_callers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000512 for func, caller in target.iteritems():
513 new_callers[func] = caller
514 for func, caller in source.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000515 if func in new_callers:
Georg Brandl66e73632008-01-21 10:24:59 +0000516 new_callers[func] = tuple([i[0] + i[1] for i in
517 zip(caller, new_callers[func])])
Tim Peters2344fae2001-01-15 00:50:52 +0000518 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000519 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000520 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000521
Guido van Rossumadb31051994-06-23 11:42:52 +0000522def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000523 """Sum the caller statistics to get total number of calls received."""
524 nc = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000525 for calls in callers.itervalues():
526 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000527 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000528
529#**************************************************************************
530# The following functions support printing of reports
531#**************************************************************************
532
533def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000534 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000535
536#**************************************************************************
537# Statistics browser added by ESR, April 2001
538#**************************************************************************
539
540if __name__ == '__main__':
541 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000542 try:
543 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000544 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000545 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000546
547 class ProfileBrowser(cmd.Cmd):
548 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000549 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000550 self.prompt = "% "
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000551 if profile is not None:
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000552 self.stats = Stats(profile)
Neal Norwitze588c2b2006-06-11 07:27:56 +0000553 self.stream = self.stats.stream
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000554 else:
555 self.stats = None
Neal Norwitze588c2b2006-06-11 07:27:56 +0000556 self.stream = sys.stdout
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000557
558 def generic(self, fn, line):
559 args = line.split()
560 processed = []
561 for term in args:
562 try:
563 processed.append(int(term))
564 continue
565 except ValueError:
566 pass
567 try:
568 frac = float(term)
569 if frac > 1 or frac < 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000570 print >> self.stream, "Fraction argument must be in [0, 1]"
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000571 continue
572 processed.append(frac)
573 continue
574 except ValueError:
575 pass
576 processed.append(term)
577 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000578 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000579 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000580 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000581 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000582 def generic_help(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000583 print >> self.stream, "Arguments may be:"
584 print >> self.stream, "* An integer maximum number of entries to print."
585 print >> self.stream, "* A decimal fractional number between 0 and 1, controlling"
586 print >> self.stream, " what fraction of selected entries to print."
587 print >> self.stream, "* A regular expression; only entries with function names"
588 print >> self.stream, " that match it are printed."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000589
590 def do_add(self, line):
591 self.stats.add(line)
592 return 0
593 def help_add(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000594 print >> self.stream, "Add profile info from given file to current statistics object."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000595
596 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000597 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000598 def help_callees(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000599 print >> self.stream, "Print callees statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000600 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000601
602 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000603 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000604 def help_callers(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000605 print >> self.stream, "Print callers statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000606 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000607
608 def do_EOF(self, line):
Skip Montanaro262fb922006-04-21 02:31:07 +0000609 print >> self.stream, ""
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000610 return 1
611 def help_EOF(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000612 print >> self.stream, "Leave the profile brower."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000613
614 def do_quit(self, line):
615 return 1
616 def help_quit(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000617 print >> self.stream, "Leave the profile brower."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000618
619 def do_read(self, line):
620 if line:
621 try:
622 self.stats = Stats(line)
623 except IOError, args:
Skip Montanaro262fb922006-04-21 02:31:07 +0000624 print >> self.stream, args[1]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000625 return
626 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000627 elif len(self.prompt) > 2:
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000628 line = self.prompt[-2:]
629 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000630 print >> self.stream, "No statistics object is current -- cannot reload."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000631 return 0
632 def help_read(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000633 print >> self.stream, "Read in profile data from a specified file."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000634
635 def do_reverse(self, line):
636 self.stats.reverse_order()
637 return 0
638 def help_reverse(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000639 print >> self.stream, "Reverse the sort order of the profiling report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000640
641 def do_sort(self, line):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000642 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchlingd54e6992010-04-02 16:59:16 +0000643 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000644 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000645 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000646 print >> self.stream, "Valid sort keys (unique prefixes are accepted):"
Raymond Hettingere0d49722002-06-02 18:55:56 +0000647 for (key, value) in Stats.sort_arg_dict_default.iteritems():
Skip Montanaro262fb922006-04-21 02:31:07 +0000648 print >> self.stream, "%s -- %s" % (key, value[1])
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000649 return 0
650 def help_sort(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000651 print >> self.stream, "Sort profile data according to specified keys."
652 print >> self.stream, "(Typing `sort' without arguments lists valid keys.)"
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000653 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000654 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000655
656 def do_stats(self, line):
657 return self.generic('print_stats', line)
658 def help_stats(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000659 print >> self.stream, "Print statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000660 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000661
662 def do_strip(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000663 self.stats.strip_dirs()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000664 return 0
665 def help_strip(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000666 print >> self.stream, "Strip leading path information from filenames in the report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000667
668 def postcmd(self, stop, line):
669 if stop:
670 return stop
671 return None
672
673 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000674 if len(sys.argv) > 1:
675 initprofile = sys.argv[1]
676 else:
677 initprofile = None
678 try:
Neal Norwitze588c2b2006-06-11 07:27:56 +0000679 browser = ProfileBrowser(initprofile)
680 print >> browser.stream, "Welcome to the profile statistics browser."
681 browser.cmdloop()
682 print >> browser.stream, "Goodbye."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000683 except KeyboardInterrupt:
684 pass
685
686# That's all, folks.