blob: db16628adc73567d9bba02fdc2048f1e563414ab [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#
Georg Brandl4009c9e2010-10-06 08:26:09 +00008# see 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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Georg Brandl4009c9e2010-10-06 08:26:09 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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])
Collin Winterce36ad82007-08-30 01:19:48 +000086 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:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000113 print("Invalid timing data", end=' ', file=self.stream)
114 if self.files: print(self.files[-1], end=' ', file=self.stream)
115 print(file=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 = {}
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000119 elif isinstance(arg, str):
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:
Collin Winterce36ad82007-08-30 01:19:48 +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
Guido van Rossume2b70bc2006-08-18 22:13:04 +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]
Georg Brandl4009c9e2010-10-06 08:26:09 +0000152 if type(self) != type(other):
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
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000166 for func, stat in other.stats.items():
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."""
Alex Martelli01c77c62006-08-24 02:58:11 +0000176 f = open(filename, 'wb')
Fred Drake9c439102003-05-14 14:28:09 +0000177 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 = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000202 for word, tup in self.sort_arg_dict_default.items():
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
Georg Brandl4009c9e2010-10-06 08:26:09 +0000220 if len(field) == 1 and isinstance(field[0], int):
Tim Peters2344fae2001-01-15 00:50:52 +0000221 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000222 field = [ {-1: "stdname",
Georg Brandl4009c9e2010-10-06 08:26:09 +0000223 0: "calls",
224 1: "time",
225 2: "cumulative"}[field[0]] ]
Tim Peters2344fae2001-01-15 00:50:52 +0000226
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 = []
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000237 for func, (cc, nc, tt, ct, callers) in self.stats.items():
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
Raymond Hettinger70b64fc2008-01-30 20:15:17 +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
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000257 for func, (cc, nc, tt, ct, callers) in oldstats.items():
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 = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000262 for func2, caller in callers.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000263 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 = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000285 for func, (cc, nc, tt, ct, callers) in self.stats.items():
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] = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000288 for func2, caller in callers.items():
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
Georg Brandl4009c9e2010-10-06 08:26:09 +0000302 if isinstance(sel, str):
303 try:
304 rex = re.compile(sel)
305 except re.error:
306 msg += " <Invalid regular expression %r>\n" % sel
307 return new_list, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000308 new_list = []
309 for func in list:
Georg Brandl4009c9e2010-10-06 08:26:09 +0000310 if rex.search(func_std_string(func)):
Tim Peters2344fae2001-01-15 00:50:52 +0000311 new_list.append(func)
312 else:
313 count = len(list)
Georg Brandl4009c9e2010-10-06 08:26:09 +0000314 if isinstance(sel, float) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000315 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000316 new_list = list[:count]
Georg Brandl4009c9e2010-10-06 08:26:09 +0000317 elif isinstance(sel, int) and 0 <= sel < count:
Tim Peters2344fae2001-01-15 00:50:52 +0000318 count = sel
319 new_list = list[:count]
320 if len(list) != len(new_list):
Georg Brandl4009c9e2010-10-06 08:26:09 +0000321 msg += " List reduced from %r to %r due to restriction <%r>\n" % (
322 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000323
Tim Peters2344fae2001-01-15 00:50:52 +0000324 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000325
Tim Peters2344fae2001-01-15 00:50:52 +0000326 def get_print_list(self, sel_list):
327 width = self.max_name_len
328 if self.fcn_list:
Georg Brandl4009c9e2010-10-06 08:26:09 +0000329 stat_list = self.fcn_list[:]
Tim Peters2344fae2001-01-15 00:50:52 +0000330 msg = " Ordered by: " + self.sort_type + '\n'
331 else:
Georg Brandl4009c9e2010-10-06 08:26:09 +0000332 stat_list = list(self.stats.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000333 msg = " Random listing order was used\n"
334
335 for selection in sel_list:
Georg Brandl4009c9e2010-10-06 08:26:09 +0000336 stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000337
Georg Brandl4009c9e2010-10-06 08:26:09 +0000338 count = len(stat_list)
Tim Peters2344fae2001-01-15 00:50:52 +0000339
Georg Brandl4009c9e2010-10-06 08:26:09 +0000340 if not stat_list:
341 return 0, stat_list
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000342 print(msg, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000343 if count < len(self.stats):
344 width = 0
Georg Brandl4009c9e2010-10-06 08:26:09 +0000345 for func in stat_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000346 if len(func_std_string(func)) > width:
347 width = len(func_std_string(func))
Georg Brandl4009c9e2010-10-06 08:26:09 +0000348 return width+2, stat_list
Tim Peters2344fae2001-01-15 00:50:52 +0000349
350 def print_stats(self, *amount):
351 for filename in self.files:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000352 print(filename, file=self.stream)
353 if self.files: print(file=self.stream)
Tim Peters7d016852001-10-08 06:13:19 +0000354 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000355 for func in self.top_level:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000356 print(indent, func_get_function_name(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000357
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000358 print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000359 if self.total_calls != self.prim_calls:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000360 print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
Senthil Kumarane062ba42010-11-20 17:08:19 +0000361 print("in %.3f seconds" % self.total_tt, file=self.stream)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000362 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000363 width, list = self.get_print_list(amount)
364 if list:
365 self.print_title()
366 for func in list:
367 self.print_line(func)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000368 print(file=self.stream)
369 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000370 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000371
Tim Peters2344fae2001-01-15 00:50:52 +0000372 def print_callees(self, *amount):
373 width, list = self.get_print_list(amount)
374 if list:
375 self.calc_callees()
376
377 self.print_call_heading(width, "called...")
378 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000379 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000380 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000381 else:
382 self.print_call_line(width, func, {})
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000383 print(file=self.stream)
384 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000385 return self
386
387 def print_callers(self, *amount):
388 width, list = self.get_print_list(amount)
389 if list:
390 self.print_call_heading(width, "was called by...")
391 for func in list:
392 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000393 self.print_call_line(width, func, callers, "<-")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000394 print(file=self.stream)
395 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000396 return self
397
398 def print_call_heading(self, name_size, column_title):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000399 print("Function ".ljust(name_size) + column_title, file=self.stream)
Armin Rigoa871ef22006-02-08 12:53:56 +0000400 # print sub-header only if we have new-style callers
401 subheader = False
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000402 for cc, nc, tt, ct, callers in self.stats.values():
Armin Rigoa871ef22006-02-08 12:53:56 +0000403 if callers:
Georg Brandla18af4e2007-04-21 15:47:16 +0000404 value = next(iter(callers.values()))
Armin Rigoa871ef22006-02-08 12:53:56 +0000405 subheader = isinstance(value, tuple)
406 break
407 if subheader:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000408 print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000409
Armin Rigoa871ef22006-02-08 12:53:56 +0000410 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000411 print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000412 if not call_dict:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000413 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000414 return
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000415 clist = sorted(call_dict.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000416 indent = ""
417 for func in clist:
418 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000419 value = call_dict[func]
420 if isinstance(value, tuple):
421 nc, cc, tt, ct = value
422 if nc != cc:
423 substats = '%d/%d' % (nc, cc)
424 else:
425 substats = '%d' % (nc,)
426 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
427 f8(tt), f8(ct), name)
428 left_width = name_size + 1
429 else:
430 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
431 left_width = name_size + 3
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000432 print(indent*left_width + substats, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000433 indent = " "
434
Tim Peters2344fae2001-01-15 00:50:52 +0000435 def print_title(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000436 print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
437 print('filename:lineno(function)', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000438
439 def print_line(self, func): # hack : should print percentages
440 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000441 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000442 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000443 c = c + '/' + str(cc)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000444 print(c.rjust(9), end=' ', file=self.stream)
445 print(f8(tt), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000446 if nc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000447 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000448 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000449 print(f8(tt/nc), end=' ', file=self.stream)
450 print(f8(ct), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000451 if cc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000452 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000453 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000454 print(f8(ct/cc), end=' ', file=self.stream)
455 print(func_std_string(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000456
Guido van Rossumadb31051994-06-23 11:42:52 +0000457class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000458 """This class provides a generic function for comparing any two tuples.
459 Each instance records a list of tuple-indices (from most significant
460 to least significant), and sort direction (ascending or decending) for
461 each tuple-index. The compare functions can then be used as the function
462 argument to the system sort() function when a list of tuples need to be
463 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000464
Tim Peters2344fae2001-01-15 00:50:52 +0000465 def __init__(self, comp_select_list):
466 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000467
Tim Peters2344fae2001-01-15 00:50:52 +0000468 def compare (self, left, right):
469 for index, direction in self.comp_select_list:
470 l = left[index]
471 r = right[index]
472 if l < r:
473 return -direction
474 if l > r:
475 return direction
476 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000477
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000478def CmpToKey(mycmp):
479 'Convert a cmp= function into a key= function'
480 class K(object):
481 def __init__(self, obj):
482 self.obj = obj
483 def __lt__(self, other):
484 return mycmp(self.obj, other.obj) == -1
485 return K
486
487
Guido van Rossumadb31051994-06-23 11:42:52 +0000488#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000489# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000490
491def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000492 filename, line, name = func_name
493 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000494
495def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000496 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000497
498def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000499 if func_name[:2] == ('~', 0):
500 # special case for built-in functions
501 name = func_name[2]
502 if name.startswith('<') and name.endswith('>'):
503 return '{%s}' % name[1:-1]
504 else:
505 return name
506 else:
507 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000508
509#**************************************************************************
510# The following functions combine statists for pairs functions.
511# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000512# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000513#**************************************************************************
514
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000515def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000516 """Add together all the stats for two profile entries."""
517 cc, nc, tt, ct, callers = source
518 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000519 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000520 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000521
Guido van Rossumadb31051994-06-23 11:42:52 +0000522def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000523 """Combine two caller lists in a single list."""
524 new_callers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000525 for func, caller in target.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000526 new_callers[func] = caller
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000527 for func, caller in source.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000528 if func in new_callers:
Georg Brandle90bce72010-08-02 17:36:05 +0000529 if isinstance(caller, tuple):
530 # format used by cProfile
531 new_callers[func] = tuple([i[0] + i[1] for i in
532 zip(caller, new_callers[func])])
533 else:
534 # format used by profile
535 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000536 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000537 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000538 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000539
Guido van Rossumadb31051994-06-23 11:42:52 +0000540def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000541 """Sum the caller statistics to get total number of calls received."""
542 nc = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000543 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000544 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000545 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000546
547#**************************************************************************
548# The following functions support printing of reports
549#**************************************************************************
550
551def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000552 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000553
554#**************************************************************************
555# Statistics browser added by ESR, April 2001
556#**************************************************************************
557
558if __name__ == '__main__':
559 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000560 try:
561 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000562 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000563 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000564
565 class ProfileBrowser(cmd.Cmd):
566 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000567 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000568 self.prompt = "% "
Georg Brandl4009c9e2010-10-06 08:26:09 +0000569 self.stats = None
570 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000571 if profile is not None:
Georg Brandl4009c9e2010-10-06 08:26:09 +0000572 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000573
574 def generic(self, fn, line):
575 args = line.split()
576 processed = []
577 for term in args:
578 try:
579 processed.append(int(term))
580 continue
581 except ValueError:
582 pass
583 try:
584 frac = float(term)
585 if frac > 1 or frac < 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000586 print("Fraction argument must be in [0, 1]", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000587 continue
588 processed.append(frac)
589 continue
590 except ValueError:
591 pass
592 processed.append(term)
593 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000594 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000595 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000596 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000597 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000598 def generic_help(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000599 print("Arguments may be:", file=self.stream)
600 print("* An integer maximum number of entries to print.", file=self.stream)
601 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
602 print(" what fraction of selected entries to print.", file=self.stream)
603 print("* A regular expression; only entries with function names", file=self.stream)
604 print(" that match it are printed.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000605
606 def do_add(self, line):
Georg Brandl44c58232010-08-01 19:04:55 +0000607 if self.stats:
608 self.stats.add(line)
609 else:
610 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000611 return 0
612 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000613 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000614
615 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000616 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000617 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000618 print("Print callees statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000619 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000620
621 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000622 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000623 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000624 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000625 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000626
627 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000628 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000629 return 1
630 def help_EOF(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000631 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000632
633 def do_quit(self, line):
634 return 1
635 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000636 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000637
638 def do_read(self, line):
639 if line:
640 try:
641 self.stats = Stats(line)
Georg Brandl50da60c2008-01-06 21:38:54 +0000642 except IOError as err:
643 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000644 return
Georg Brandl44c58232010-08-01 19:04:55 +0000645 except Exception as err:
646 print(err.__class__.__name__ + ':', err, file=self.stream)
647 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000648 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000649 elif len(self.prompt) > 2:
Georg Brandl44c58232010-08-01 19:04:55 +0000650 line = self.prompt[:-2]
651 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000652 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000653 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000654 return 0
655 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000656 print("Read in profile data from a specified file.", file=self.stream)
Georg Brandl44c58232010-08-01 19:04:55 +0000657 print("Without argument, reload the current file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000658
659 def do_reverse(self, line):
Georg Brandl44c58232010-08-01 19:04:55 +0000660 if self.stats:
661 self.stats.reverse_order()
662 else:
663 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000664 return 0
665 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000666 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000667
668 def do_sort(self, line):
Georg Brandl44c58232010-08-01 19:04:55 +0000669 if not self.stats:
670 print("No statistics object is loaded.", file=self.stream)
671 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000672 abbrevs = self.stats.get_sort_arg_defs()
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000673 if line and not filter(lambda x,a=abbrevs: x not in a,line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000674 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000675 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000676 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000677 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000678 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000679 return 0
680 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000681 print("Sort profile data according to specified keys.", file=self.stream)
682 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000683 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000684 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000685
686 def do_stats(self, line):
687 return self.generic('print_stats', line)
688 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000689 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000690 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000691
692 def do_strip(self, line):
Georg Brandl44c58232010-08-01 19:04:55 +0000693 if self.stats:
694 self.stats.strip_dirs()
695 else:
696 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000697 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000698 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000699
Georg Brandl44c58232010-08-01 19:04:55 +0000700 def help_help(self):
701 print("Show help for a given command.", file=self.stream)
702
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000703 def postcmd(self, stop, line):
704 if stop:
705 return stop
706 return None
707
708 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000709 if len(sys.argv) > 1:
710 initprofile = sys.argv[1]
711 else:
712 initprofile = None
713 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000714 browser = ProfileBrowser(initprofile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000715 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000716 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000717 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000718 except KeyboardInterrupt:
719 pass
720
721# That's all, folks.