blob: 8744235ad0047dd1e8db438a8f2742bdcb8f330c [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 Brandlb1a97af2010-08-02 12:06:18 +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
Raymond Hettingerc50846a2010-04-05 18:56:31 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Georg Brandlb1a97af2010-08-02 12:06:18 +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
Georg Brandl7837a962009-09-02 20:33:30 +000074 def __init__(self, *args, stream=None):
75 self.stream = stream or sys.stdout
Tim Peters2344fae2001-01-15 00:50:52 +000076 if not len(args):
77 arg = None
78 else:
79 arg = args[0]
80 args = args[1:]
81 self.init(arg)
Guido van Rossum68468eb2003-02-27 20:14:51 +000082 self.add(*args)
Tim Peters2344fae2001-01-15 00:50:52 +000083
84 def init(self, arg):
85 self.all_callees = None # calc only if needed
86 self.files = []
87 self.fcn_list = None
88 self.total_tt = 0
89 self.total_calls = 0
90 self.prim_calls = 0
91 self.max_name_len = 0
92 self.top_level = {}
93 self.stats = {}
94 self.sort_arg_dict = {}
95 self.load_stats(arg)
96 trouble = 1
97 try:
98 self.get_top_level_stats()
99 trouble = 0
100 finally:
101 if trouble:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000102 print("Invalid timing data", end=' ', file=self.stream)
103 if self.files: print(self.files[-1], end=' ', file=self.stream)
104 print(file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000105
Tim Peters2344fae2001-01-15 00:50:52 +0000106 def load_stats(self, arg):
Georg Brandl83938432010-10-22 06:28:01 +0000107 if arg is None:
108 self.stats = {}
109 return
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000110 elif isinstance(arg, str):
Tim Peters2344fae2001-01-15 00:50:52 +0000111 f = open(arg, 'rb')
112 self.stats = marshal.load(f)
113 f.close()
114 try:
115 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000116 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000117 except: # in case this is not unix
118 pass
Georg Brandl83938432010-10-22 06:28:01 +0000119 self.files = [arg]
Tim Peters2344fae2001-01-15 00:50:52 +0000120 elif hasattr(arg, 'create_stats'):
121 arg.create_stats()
122 self.stats = arg.stats
123 arg.stats = {}
124 if not self.stats:
Georg Brandl83938432010-10-22 06:28:01 +0000125 raise TypeError("Cannot create or construct a %r object from %r"
Collin Winterce36ad82007-08-30 01:19:48 +0000126 % (self.__class__, arg))
Tim Peters2344fae2001-01-15 00:50:52 +0000127 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000128
Tim Peters2344fae2001-01-15 00:50:52 +0000129 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000130 for func, (cc, nc, tt, ct, callers) in self.stats.items():
131 self.total_calls += nc
132 self.prim_calls += cc
133 self.total_tt += tt
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000134 if ("jprofile", 0, "profiler") in callers:
Tim Peters2344fae2001-01-15 00:50:52 +0000135 self.top_level[func] = None
136 if len(func_std_string(func)) > self.max_name_len:
137 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000138
Tim Peters2344fae2001-01-15 00:50:52 +0000139 def add(self, *arg_list):
Georg Brandl83938432010-10-22 06:28:01 +0000140 if not arg_list:
141 return self
142 for item in reversed(arg_list):
143 if type(self) != type(item):
144 item = Stats(item)
145 self.files += item.files
146 self.total_calls += item.total_calls
147 self.prim_calls += item.prim_calls
148 self.total_tt += item.total_tt
149 for func in item.top_level:
150 self.top_level[func] = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000151
Georg Brandl83938432010-10-22 06:28:01 +0000152 if self.max_name_len < item.max_name_len:
153 self.max_name_len = item.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000154
Georg Brandl83938432010-10-22 06:28:01 +0000155 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000156
Georg Brandl83938432010-10-22 06:28:01 +0000157 for func, stat in item.stats.items():
158 if func in self.stats:
159 old_func_stat = self.stats[func]
160 else:
161 old_func_stat = (0, 0, 0, 0, {},)
162 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000163 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000164
Fred Drake9c439102003-05-14 14:28:09 +0000165 def dump_stats(self, filename):
166 """Write the profile data to a file we know how to load back."""
Alex Martelli01c77c62006-08-24 02:58:11 +0000167 f = open(filename, 'wb')
Fred Drake9c439102003-05-14 14:28:09 +0000168 try:
169 marshal.dump(self.stats, f)
170 finally:
171 f.close()
172
Tim Peters2344fae2001-01-15 00:50:52 +0000173 # list the tuple indices and directions for sorting,
174 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000175 sort_arg_dict_default = {
176 "calls" : (((1,-1), ), "call count"),
177 "cumulative": (((3,-1), ), "cumulative time"),
178 "file" : (((4, 1), ), "file name"),
179 "line" : (((5, 1), ), "line number"),
180 "module" : (((4, 1), ), "file name"),
181 "name" : (((6, 1), ), "function name"),
182 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
183 "pcalls" : (((0,-1), ), "call count"),
184 "stdname" : (((7, 1), ), "standard name"),
185 "time" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000186 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000187
Tim Peters2344fae2001-01-15 00:50:52 +0000188 def get_sort_arg_defs(self):
189 """Expand all abbreviations that are unique."""
190 if not self.sort_arg_dict:
191 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000192 bad_list = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000193 for word, tup in self.sort_arg_dict_default.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000194 fragment = word
195 while fragment:
196 if not fragment:
197 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000198 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000199 bad_list[fragment] = 0
200 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000201 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000202 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000203 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000204 del dict[word]
205 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000206
Tim Peters2344fae2001-01-15 00:50:52 +0000207 def sort_stats(self, *field):
208 if not field:
209 self.fcn_list = 0
210 return self
Georg Brandlb1a97af2010-08-02 12:06:18 +0000211 if len(field) == 1 and isinstance(field[0], int):
Tim Peters2344fae2001-01-15 00:50:52 +0000212 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000213 field = [ {-1: "stdname",
Georg Brandlb1a97af2010-08-02 12:06:18 +0000214 0: "calls",
215 1: "time",
216 2: "cumulative"}[field[0]] ]
Tim Peters2344fae2001-01-15 00:50:52 +0000217
218 sort_arg_defs = self.get_sort_arg_defs()
219 sort_tuple = ()
220 self.sort_type = ""
221 connector = ""
222 for word in field:
223 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000224 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000225 connector = ", "
226
227 stats_list = []
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000228 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Tim Peters7d016852001-10-08 06:13:19 +0000229 stats_list.append((cc, nc, tt, ct) + func +
230 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000231
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000232 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000233
234 self.fcn_list = fcn_list = []
235 for tuple in stats_list:
236 fcn_list.append(tuple[-1])
237 return self
238
Tim Peters2344fae2001-01-15 00:50:52 +0000239 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000240 if self.fcn_list:
241 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000242 return self
243
244 def strip_dirs(self):
245 oldstats = self.stats
246 self.stats = newstats = {}
247 max_name_len = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000248 for func, (cc, nc, tt, ct, callers) in oldstats.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000249 newfunc = func_strip_path(func)
250 if len(func_std_string(newfunc)) > max_name_len:
251 max_name_len = len(func_std_string(newfunc))
252 newcallers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000253 for func2, caller in callers.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000254 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000255
Raymond Hettinger54f02222002-06-01 14:18:47 +0000256 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000257 newstats[newfunc] = add_func_stats(
258 newstats[newfunc],
259 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000260 else:
261 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
262 old_top = self.top_level
263 self.top_level = new_top = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000264 for func in old_top:
Tim Peters2344fae2001-01-15 00:50:52 +0000265 new_top[func_strip_path(func)] = None
266
267 self.max_name_len = max_name_len
268
269 self.fcn_list = None
270 self.all_callees = None
271 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000272
Tim Peters2344fae2001-01-15 00:50:52 +0000273 def calc_callees(self):
274 if self.all_callees: return
275 self.all_callees = all_callees = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000276 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000277 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000278 all_callees[func] = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000279 for func2, caller in callers.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000280 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000281 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000282 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000283 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000284
Tim Peters2344fae2001-01-15 00:50:52 +0000285 #******************************************************************
286 # The following functions support actual printing of reports
287 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000288
Tim Peters2344fae2001-01-15 00:50:52 +0000289 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000290
Tim Peters2344fae2001-01-15 00:50:52 +0000291 def eval_print_amount(self, sel, list, msg):
292 new_list = list
Georg Brandlb1a97af2010-08-02 12:06:18 +0000293 if isinstance(sel, str):
294 try:
295 rex = re.compile(sel)
296 except re.error:
297 msg += " <Invalid regular expression %r>\n" % sel
298 return new_list, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000299 new_list = []
300 for func in list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000301 if rex.search(func_std_string(func)):
Tim Peters2344fae2001-01-15 00:50:52 +0000302 new_list.append(func)
303 else:
304 count = len(list)
Georg Brandlb1a97af2010-08-02 12:06:18 +0000305 if isinstance(sel, float) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000306 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000307 new_list = list[:count]
Georg Brandlb1a97af2010-08-02 12:06:18 +0000308 elif isinstance(sel, int) and 0 <= sel < count:
Tim Peters2344fae2001-01-15 00:50:52 +0000309 count = sel
310 new_list = list[:count]
311 if len(list) != len(new_list):
Georg Brandlb1a97af2010-08-02 12:06:18 +0000312 msg += " List reduced from %r to %r due to restriction <%r>\n" % (
313 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000314
Tim Peters2344fae2001-01-15 00:50:52 +0000315 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000316
Tim Peters2344fae2001-01-15 00:50:52 +0000317 def get_print_list(self, sel_list):
318 width = self.max_name_len
319 if self.fcn_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000320 stat_list = self.fcn_list[:]
Tim Peters2344fae2001-01-15 00:50:52 +0000321 msg = " Ordered by: " + self.sort_type + '\n'
322 else:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000323 stat_list = list(self.stats.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000324 msg = " Random listing order was used\n"
325
326 for selection in sel_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000327 stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000328
Georg Brandlb1a97af2010-08-02 12:06:18 +0000329 count = len(stat_list)
Tim Peters2344fae2001-01-15 00:50:52 +0000330
Georg Brandlb1a97af2010-08-02 12:06:18 +0000331 if not stat_list:
332 return 0, stat_list
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000333 print(msg, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000334 if count < len(self.stats):
335 width = 0
Georg Brandlb1a97af2010-08-02 12:06:18 +0000336 for func in stat_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000337 if len(func_std_string(func)) > width:
338 width = len(func_std_string(func))
Georg Brandlb1a97af2010-08-02 12:06:18 +0000339 return width+2, stat_list
Tim Peters2344fae2001-01-15 00:50:52 +0000340
341 def print_stats(self, *amount):
342 for filename in self.files:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000343 print(filename, file=self.stream)
344 if self.files: print(file=self.stream)
Tim Peters7d016852001-10-08 06:13:19 +0000345 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000346 for func in self.top_level:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000347 print(indent, func_get_function_name(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000348
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000349 print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000350 if self.total_calls != self.prim_calls:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000351 print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
352 print("in %.3f CPU seconds" % self.total_tt, file=self.stream)
353 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000354 width, list = self.get_print_list(amount)
355 if list:
356 self.print_title()
357 for func in list:
358 self.print_line(func)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000359 print(file=self.stream)
360 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000361 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000362
Tim Peters2344fae2001-01-15 00:50:52 +0000363 def print_callees(self, *amount):
364 width, list = self.get_print_list(amount)
365 if list:
366 self.calc_callees()
367
368 self.print_call_heading(width, "called...")
369 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000370 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000371 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000372 else:
373 self.print_call_line(width, func, {})
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000374 print(file=self.stream)
375 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000376 return self
377
378 def print_callers(self, *amount):
379 width, list = self.get_print_list(amount)
380 if list:
381 self.print_call_heading(width, "was called by...")
382 for func in list:
383 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000384 self.print_call_line(width, func, callers, "<-")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000385 print(file=self.stream)
386 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000387 return self
388
389 def print_call_heading(self, name_size, column_title):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000390 print("Function ".ljust(name_size) + column_title, file=self.stream)
Armin Rigoa871ef22006-02-08 12:53:56 +0000391 # print sub-header only if we have new-style callers
392 subheader = False
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000393 for cc, nc, tt, ct, callers in self.stats.values():
Armin Rigoa871ef22006-02-08 12:53:56 +0000394 if callers:
Georg Brandla18af4e2007-04-21 15:47:16 +0000395 value = next(iter(callers.values()))
Armin Rigoa871ef22006-02-08 12:53:56 +0000396 subheader = isinstance(value, tuple)
397 break
398 if subheader:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000399 print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000400
Armin Rigoa871ef22006-02-08 12:53:56 +0000401 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000402 print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000403 if not call_dict:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000404 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000405 return
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000406 clist = sorted(call_dict.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000407 indent = ""
408 for func in clist:
409 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000410 value = call_dict[func]
411 if isinstance(value, tuple):
412 nc, cc, tt, ct = value
413 if nc != cc:
414 substats = '%d/%d' % (nc, cc)
415 else:
416 substats = '%d' % (nc,)
417 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
418 f8(tt), f8(ct), name)
419 left_width = name_size + 1
420 else:
421 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
422 left_width = name_size + 3
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000423 print(indent*left_width + substats, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000424 indent = " "
425
Tim Peters2344fae2001-01-15 00:50:52 +0000426 def print_title(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000427 print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
428 print('filename:lineno(function)', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000429
430 def print_line(self, func): # hack : should print percentages
431 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000432 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000433 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000434 c = c + '/' + str(cc)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000435 print(c.rjust(9), end=' ', file=self.stream)
436 print(f8(tt), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000437 if nc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000438 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000439 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000440 print(f8(tt/nc), end=' ', file=self.stream)
441 print(f8(ct), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000442 if cc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000443 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000444 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000445 print(f8(ct/cc), end=' ', file=self.stream)
446 print(func_std_string(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000447
Guido van Rossumadb31051994-06-23 11:42:52 +0000448class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000449 """This class provides a generic function for comparing any two tuples.
450 Each instance records a list of tuple-indices (from most significant
451 to least significant), and sort direction (ascending or decending) for
452 each tuple-index. The compare functions can then be used as the function
453 argument to the system sort() function when a list of tuples need to be
454 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000455
Tim Peters2344fae2001-01-15 00:50:52 +0000456 def __init__(self, comp_select_list):
457 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000458
Tim Peters2344fae2001-01-15 00:50:52 +0000459 def compare (self, left, right):
460 for index, direction in self.comp_select_list:
461 l = left[index]
462 r = right[index]
463 if l < r:
464 return -direction
465 if l > r:
466 return direction
467 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000468
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000469
Guido van Rossumadb31051994-06-23 11:42:52 +0000470#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000471# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000472
473def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000474 filename, line, name = func_name
475 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000476
477def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000478 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000479
480def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000481 if func_name[:2] == ('~', 0):
482 # special case for built-in functions
483 name = func_name[2]
484 if name.startswith('<') and name.endswith('>'):
485 return '{%s}' % name[1:-1]
486 else:
487 return name
488 else:
489 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000490
491#**************************************************************************
492# The following functions combine statists for pairs functions.
493# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000494# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000495#**************************************************************************
496
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000497def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000498 """Add together all the stats for two profile entries."""
499 cc, nc, tt, ct, callers = source
500 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000501 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000502 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000503
Guido van Rossumadb31051994-06-23 11:42:52 +0000504def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000505 """Combine two caller lists in a single list."""
506 new_callers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000507 for func, caller in target.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000508 new_callers[func] = caller
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000509 for func, caller in source.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000510 if func in new_callers:
Georg Brandl2d3c4e72010-08-02 17:24:49 +0000511 if isinstance(caller, tuple):
512 # format used by cProfile
513 new_callers[func] = tuple([i[0] + i[1] for i in
514 zip(caller, new_callers[func])])
515 else:
516 # format used by profile
517 new_callers[func] += caller
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
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000525 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000526 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 = "% "
Georg Brandlb1a97af2010-08-02 12:06:18 +0000551 self.stats = None
552 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000553 if profile is not None:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000554 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000555
556 def generic(self, fn, line):
557 args = line.split()
558 processed = []
559 for term in args:
560 try:
561 processed.append(int(term))
562 continue
563 except ValueError:
564 pass
565 try:
566 frac = float(term)
567 if frac > 1 or frac < 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000568 print("Fraction argument must be in [0, 1]", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000569 continue
570 processed.append(frac)
571 continue
572 except ValueError:
573 pass
574 processed.append(term)
575 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000576 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000577 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000578 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000579 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000580 def generic_help(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000581 print("Arguments may be:", file=self.stream)
582 print("* An integer maximum number of entries to print.", file=self.stream)
583 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
584 print(" what fraction of selected entries to print.", file=self.stream)
585 print("* A regular expression; only entries with function names", file=self.stream)
586 print(" that match it are printed.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000587
588 def do_add(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000589 if self.stats:
590 self.stats.add(line)
591 else:
592 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000593 return 0
594 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000595 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000596
597 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000598 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000599 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000600 print("Print callees statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000601 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000602
603 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000604 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000605 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000606 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000607 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000608
609 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000610 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000611 return 1
612 def help_EOF(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000613 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000614
615 def do_quit(self, line):
616 return 1
617 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000618 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000619
620 def do_read(self, line):
621 if line:
622 try:
623 self.stats = Stats(line)
Georg Brandl50da60c2008-01-06 21:38:54 +0000624 except IOError as err:
625 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000626 return
Georg Brandlf02e7362010-08-01 07:57:47 +0000627 except Exception as err:
628 print(err.__class__.__name__ + ':', err, file=self.stream)
629 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000630 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000631 elif len(self.prompt) > 2:
Georg Brandlf02e7362010-08-01 07:57:47 +0000632 line = self.prompt[:-2]
633 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000634 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000635 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000636 return 0
637 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000638 print("Read in profile data from a specified file.", file=self.stream)
Georg Brandlf02e7362010-08-01 07:57:47 +0000639 print("Without argument, reload the current file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000640
641 def do_reverse(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000642 if self.stats:
643 self.stats.reverse_order()
644 else:
645 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000646 return 0
647 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000648 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000649
650 def do_sort(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000651 if not self.stats:
652 print("No statistics object is loaded.", file=self.stream)
653 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000654 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchling0a628232010-04-02 17:02:57 +0000655 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000656 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000657 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000658 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000659 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000660 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000661 return 0
662 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000663 print("Sort profile data according to specified keys.", file=self.stream)
664 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000665 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000666 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000667
668 def do_stats(self, line):
669 return self.generic('print_stats', line)
670 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000671 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000672 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000673
674 def do_strip(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000675 if self.stats:
676 self.stats.strip_dirs()
677 else:
678 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000679 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000680 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000681
Georg Brandlf02e7362010-08-01 07:57:47 +0000682 def help_help(self):
683 print("Show help for a given command.", file=self.stream)
684
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000685 def postcmd(self, stop, line):
686 if stop:
687 return stop
688 return None
689
690 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000691 if len(sys.argv) > 1:
692 initprofile = sys.argv[1]
693 else:
694 initprofile = None
695 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000696 browser = ProfileBrowser(initprofile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000697 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000698 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000699 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000700 except KeyboardInterrupt:
701 pass
702
703# That's all, folks.