blob: 7cf000ffe09963cf5e214369f2482c3a2abf214b [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""Class for printing reports on profiled python code."""
2
Benjamin Peterson8d770692011-06-27 09:14:34 -05003# Written by James Roskind
Guido van Rossumadb31051994-06-23 11:42:52 +00004# Based on prior profile module by Sjoerd Mullender...
5# which was hacked somewhat by: Guido van Rossum
Guido van Rossumadb31051994-06-23 11:42:52 +00006
Benjamin Peterson8d770692011-06-27 09:14:34 -05007# Copyright Disney Enterprises, Inc. All Rights Reserved.
8# Licensed to PSF under a Contributor Agreement
Benjamin Peterson0f93d3d2011-06-27 09:18:46 -05009#
Benjamin Peterson8d770692011-06-27 09:14:34 -050010# Licensed under the Apache License, Version 2.0 (the "License");
11# you may not use this file except in compliance with the License.
12# You may obtain a copy of the License at
Benjamin Peterson0f93d3d2011-06-27 09:18:46 -050013#
Benjamin Peterson8d770692011-06-27 09:14:34 -050014# http://www.apache.org/licenses/LICENSE-2.0
Benjamin Peterson0f93d3d2011-06-27 09:18:46 -050015#
Benjamin Peterson8d770692011-06-27 09:14:34 -050016# Unless required by applicable law or agreed to in writing, software
17# distributed under the License is distributed on an "AS IS" BASIS,
18# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
19# either express or implied. See the License for the specific language
20# governing permissions and limitations under the License.
Guido van Rossumadb31051994-06-23 11:42:52 +000021
22
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000023import sys
Guido van Rossumadb31051994-06-23 11:42:52 +000024import os
25import time
Guido van Rossumadb31051994-06-23 11:42:52 +000026import marshal
Guido van Rossum9694fca1997-10-22 21:00:49 +000027import re
Raymond Hettingerc50846a2010-04-05 18:56:31 +000028from functools import cmp_to_key
Guido van Rossumadb31051994-06-23 11:42:52 +000029
Skip Montanaroc62c81e2001-02-12 02:00:42 +000030__all__ = ["Stats"]
31
Guido van Rossumadb31051994-06-23 11:42:52 +000032class Stats:
Tim Peters2344fae2001-01-15 00:50:52 +000033 """This class is used for creating reports from data generated by the
34 Profile class. It is a "friend" of that class, and imports data either
35 by direct access to members of Profile class, or by reading in a dictionary
36 that was emitted (via marshal) from the Profile class.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000037
Tim Peters2344fae2001-01-15 00:50:52 +000038 The big change from the previous Profiler (in terms of raw functionality)
39 is that an "add()" method has been provided to combine Stats from
40 several distinct profile runs. Both the constructor and the add()
41 method now take arbitrarily many file names as arguments.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000042
Tim Peters2344fae2001-01-15 00:50:52 +000043 All the print methods now take an argument that indicates how many lines
44 to print. If the arg is a floating point number between 0 and 1.0, then
45 it is taken as a decimal percentage of the available lines to be printed
46 (e.g., .1 means print 10% of all available lines). If it is an integer,
47 it is taken to mean the number of lines of data that you wish to have
48 printed.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000049
Tim Peters2344fae2001-01-15 00:50:52 +000050 The sort_stats() method now processes some additional options (i.e., in
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000051 addition to the old -1, 0, 1, or 2). It takes an arbitrary number of
52 quoted strings to select the sort order. For example sort_stats('time',
53 'name') sorts on the major key of 'internal function time', and on the
54 minor key of 'the name of the function'. Look at the two tables in
55 sort_stats() and get_sort_arg_defs(self) for more examples.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000056
Georg Brandlb1a97af2010-08-02 12:06:18 +000057 All methods return self, so you can string together commands like:
Tim Peters2344fae2001-01-15 00:50:52 +000058 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
59 print_stats(5).print_callers(5)
60 """
61
Georg Brandl7837a962009-09-02 20:33:30 +000062 def __init__(self, *args, stream=None):
63 self.stream = stream or sys.stdout
Tim Peters2344fae2001-01-15 00:50:52 +000064 if not len(args):
65 arg = None
66 else:
67 arg = args[0]
68 args = args[1:]
69 self.init(arg)
Guido van Rossum68468eb2003-02-27 20:14:51 +000070 self.add(*args)
Tim Peters2344fae2001-01-15 00:50:52 +000071
72 def init(self, arg):
73 self.all_callees = None # calc only if needed
74 self.files = []
75 self.fcn_list = None
76 self.total_tt = 0
77 self.total_calls = 0
78 self.prim_calls = 0
79 self.max_name_len = 0
Georg Brandleb7e5692010-10-22 06:29:21 +000080 self.top_level = set()
Tim Peters2344fae2001-01-15 00:50:52 +000081 self.stats = {}
82 self.sort_arg_dict = {}
83 self.load_stats(arg)
Tim Peters2344fae2001-01-15 00:50:52 +000084 try:
85 self.get_top_level_stats()
Georg Brandl9a8439d2010-10-22 06:35:59 +000086 except Exception:
87 print("Invalid timing data %s" %
88 (self.files[-1] if self.files else ''), file=self.stream)
89 raise
Guido van Rossumadb31051994-06-23 11:42:52 +000090
Tim Peters2344fae2001-01-15 00:50:52 +000091 def load_stats(self, arg):
Georg Brandl83938432010-10-22 06:28:01 +000092 if arg is None:
93 self.stats = {}
94 return
Guido van Rossum3172c5d2007-10-16 18:12:55 +000095 elif isinstance(arg, str):
Tim Peters2344fae2001-01-15 00:50:52 +000096 f = open(arg, 'rb')
97 self.stats = marshal.load(f)
98 f.close()
99 try:
100 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000101 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000102 except: # in case this is not unix
103 pass
Georg Brandl83938432010-10-22 06:28:01 +0000104 self.files = [arg]
Tim Peters2344fae2001-01-15 00:50:52 +0000105 elif hasattr(arg, 'create_stats'):
106 arg.create_stats()
107 self.stats = arg.stats
108 arg.stats = {}
109 if not self.stats:
Georg Brandl83938432010-10-22 06:28:01 +0000110 raise TypeError("Cannot create or construct a %r object from %r"
Collin Winterce36ad82007-08-30 01:19:48 +0000111 % (self.__class__, arg))
Tim Peters2344fae2001-01-15 00:50:52 +0000112 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000113
Tim Peters2344fae2001-01-15 00:50:52 +0000114 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000115 for func, (cc, nc, tt, ct, callers) in self.stats.items():
116 self.total_calls += nc
117 self.prim_calls += cc
118 self.total_tt += tt
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000119 if ("jprofile", 0, "profiler") in callers:
Georg Brandleb7e5692010-10-22 06:29:21 +0000120 self.top_level.add(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000121 if len(func_std_string(func)) > self.max_name_len:
122 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000123
Tim Peters2344fae2001-01-15 00:50:52 +0000124 def add(self, *arg_list):
Georg Brandl83938432010-10-22 06:28:01 +0000125 if not arg_list:
126 return self
127 for item in reversed(arg_list):
128 if type(self) != type(item):
129 item = Stats(item)
130 self.files += item.files
131 self.total_calls += item.total_calls
132 self.prim_calls += item.prim_calls
133 self.total_tt += item.total_tt
134 for func in item.top_level:
Georg Brandleb7e5692010-10-22 06:29:21 +0000135 self.top_level.add(func)
Guido van Rossumadb31051994-06-23 11:42:52 +0000136
Georg Brandl83938432010-10-22 06:28:01 +0000137 if self.max_name_len < item.max_name_len:
138 self.max_name_len = item.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000139
Georg Brandl83938432010-10-22 06:28:01 +0000140 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000141
Georg Brandl83938432010-10-22 06:28:01 +0000142 for func, stat in item.stats.items():
143 if func in self.stats:
144 old_func_stat = self.stats[func]
145 else:
146 old_func_stat = (0, 0, 0, 0, {},)
147 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000148 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000149
Fred Drake9c439102003-05-14 14:28:09 +0000150 def dump_stats(self, filename):
151 """Write the profile data to a file we know how to load back."""
Alex Martelli01c77c62006-08-24 02:58:11 +0000152 f = open(filename, 'wb')
Fred Drake9c439102003-05-14 14:28:09 +0000153 try:
154 marshal.dump(self.stats, f)
155 finally:
156 f.close()
157
Tim Peters2344fae2001-01-15 00:50:52 +0000158 # list the tuple indices and directions for sorting,
159 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000160 sort_arg_dict_default = {
161 "calls" : (((1,-1), ), "call count"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300162 "ncalls" : (((1,-1), ), "call count"),
163 "cumtime" : (((3,-1), ), "cumulative time"),
Tim Peters7d016852001-10-08 06:13:19 +0000164 "cumulative": (((3,-1), ), "cumulative time"),
165 "file" : (((4, 1), ), "file name"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300166 "filename" : (((4, 1), ), "file name"),
Tim Peters7d016852001-10-08 06:13:19 +0000167 "line" : (((5, 1), ), "line number"),
168 "module" : (((4, 1), ), "file name"),
169 "name" : (((6, 1), ), "function name"),
170 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
Andrew Svetlov2ef45842012-10-07 18:58:42 +0300171 "pcalls" : (((0,-1), ), "primitive call count"),
Tim Peters7d016852001-10-08 06:13:19 +0000172 "stdname" : (((7, 1), ), "standard name"),
173 "time" : (((2,-1), ), "internal time"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300174 "tottime" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000175 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000176
Tim Peters2344fae2001-01-15 00:50:52 +0000177 def get_sort_arg_defs(self):
178 """Expand all abbreviations that are unique."""
179 if not self.sort_arg_dict:
180 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000181 bad_list = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000182 for word, tup in self.sort_arg_dict_default.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000183 fragment = word
184 while fragment:
185 if not fragment:
186 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000187 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000188 bad_list[fragment] = 0
189 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000190 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000191 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000192 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000193 del dict[word]
194 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000195
Tim Peters2344fae2001-01-15 00:50:52 +0000196 def sort_stats(self, *field):
197 if not field:
198 self.fcn_list = 0
199 return self
Georg Brandlb1a97af2010-08-02 12:06:18 +0000200 if len(field) == 1 and isinstance(field[0], int):
Tim Peters2344fae2001-01-15 00:50:52 +0000201 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000202 field = [ {-1: "stdname",
Georg Brandlb1a97af2010-08-02 12:06:18 +0000203 0: "calls",
204 1: "time",
205 2: "cumulative"}[field[0]] ]
Tim Peters2344fae2001-01-15 00:50:52 +0000206
207 sort_arg_defs = self.get_sort_arg_defs()
208 sort_tuple = ()
209 self.sort_type = ""
210 connector = ""
211 for word in field:
212 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000213 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000214 connector = ", "
215
216 stats_list = []
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000217 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Tim Peters7d016852001-10-08 06:13:19 +0000218 stats_list.append((cc, nc, tt, ct) + func +
219 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000220
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000221 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000222
223 self.fcn_list = fcn_list = []
224 for tuple in stats_list:
225 fcn_list.append(tuple[-1])
226 return self
227
Tim Peters2344fae2001-01-15 00:50:52 +0000228 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000229 if self.fcn_list:
230 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000231 return self
232
233 def strip_dirs(self):
234 oldstats = self.stats
235 self.stats = newstats = {}
236 max_name_len = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000237 for func, (cc, nc, tt, ct, callers) in oldstats.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000238 newfunc = func_strip_path(func)
239 if len(func_std_string(newfunc)) > max_name_len:
240 max_name_len = len(func_std_string(newfunc))
241 newcallers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000242 for func2, caller in callers.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000243 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000244
Raymond Hettinger54f02222002-06-01 14:18:47 +0000245 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000246 newstats[newfunc] = add_func_stats(
247 newstats[newfunc],
248 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000249 else:
250 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
251 old_top = self.top_level
Georg Brandleb7e5692010-10-22 06:29:21 +0000252 self.top_level = new_top = set()
Raymond Hettingere0d49722002-06-02 18:55:56 +0000253 for func in old_top:
Georg Brandleb7e5692010-10-22 06:29:21 +0000254 new_top.add(func_strip_path(func))
Tim Peters2344fae2001-01-15 00:50:52 +0000255
256 self.max_name_len = max_name_len
257
258 self.fcn_list = None
259 self.all_callees = None
260 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000261
Tim Peters2344fae2001-01-15 00:50:52 +0000262 def calc_callees(self):
Georg Brandl9a8439d2010-10-22 06:35:59 +0000263 if self.all_callees:
264 return
Tim Peters2344fae2001-01-15 00:50:52 +0000265 self.all_callees = all_callees = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000266 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000267 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000268 all_callees[func] = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000269 for func2, caller in callers.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000270 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000271 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000272 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000273 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000274
Tim Peters2344fae2001-01-15 00:50:52 +0000275 #******************************************************************
276 # The following functions support actual printing of reports
277 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000278
Tim Peters2344fae2001-01-15 00:50:52 +0000279 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000280
Tim Peters2344fae2001-01-15 00:50:52 +0000281 def eval_print_amount(self, sel, list, msg):
282 new_list = list
Georg Brandlb1a97af2010-08-02 12:06:18 +0000283 if isinstance(sel, str):
284 try:
285 rex = re.compile(sel)
286 except re.error:
287 msg += " <Invalid regular expression %r>\n" % sel
288 return new_list, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000289 new_list = []
290 for func in list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000291 if rex.search(func_std_string(func)):
Tim Peters2344fae2001-01-15 00:50:52 +0000292 new_list.append(func)
293 else:
294 count = len(list)
Georg Brandlb1a97af2010-08-02 12:06:18 +0000295 if isinstance(sel, float) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000296 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000297 new_list = list[:count]
Georg Brandlb1a97af2010-08-02 12:06:18 +0000298 elif isinstance(sel, int) and 0 <= sel < count:
Tim Peters2344fae2001-01-15 00:50:52 +0000299 count = sel
300 new_list = list[:count]
301 if len(list) != len(new_list):
Georg Brandlb1a97af2010-08-02 12:06:18 +0000302 msg += " List reduced from %r to %r due to restriction <%r>\n" % (
303 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000304
Tim Peters2344fae2001-01-15 00:50:52 +0000305 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000306
Tim Peters2344fae2001-01-15 00:50:52 +0000307 def get_print_list(self, sel_list):
308 width = self.max_name_len
309 if self.fcn_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000310 stat_list = self.fcn_list[:]
Tim Peters2344fae2001-01-15 00:50:52 +0000311 msg = " Ordered by: " + self.sort_type + '\n'
312 else:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000313 stat_list = list(self.stats.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000314 msg = " Random listing order was used\n"
315
316 for selection in sel_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000317 stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000318
Georg Brandlb1a97af2010-08-02 12:06:18 +0000319 count = len(stat_list)
Tim Peters2344fae2001-01-15 00:50:52 +0000320
Georg Brandlb1a97af2010-08-02 12:06:18 +0000321 if not stat_list:
322 return 0, stat_list
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000323 print(msg, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000324 if count < len(self.stats):
325 width = 0
Georg Brandlb1a97af2010-08-02 12:06:18 +0000326 for func in stat_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000327 if len(func_std_string(func)) > width:
328 width = len(func_std_string(func))
Georg Brandlb1a97af2010-08-02 12:06:18 +0000329 return width+2, stat_list
Tim Peters2344fae2001-01-15 00:50:52 +0000330
331 def print_stats(self, *amount):
332 for filename in self.files:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000333 print(filename, file=self.stream)
Georg Brandl9a8439d2010-10-22 06:35:59 +0000334 if self.files:
335 print(file=self.stream)
Tim Peters7d016852001-10-08 06:13:19 +0000336 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000337 for func in self.top_level:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000338 print(indent, func_get_function_name(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000339
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000340 print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000341 if self.total_calls != self.prim_calls:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000342 print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
Senthil Kumaran5e703cf2010-11-20 17:02:50 +0000343 print("in %.3f seconds" % self.total_tt, file=self.stream)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000344 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000345 width, list = self.get_print_list(amount)
346 if list:
347 self.print_title()
348 for func in list:
349 self.print_line(func)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000350 print(file=self.stream)
351 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000352 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000353
Tim Peters2344fae2001-01-15 00:50:52 +0000354 def print_callees(self, *amount):
355 width, list = self.get_print_list(amount)
356 if list:
357 self.calc_callees()
358
359 self.print_call_heading(width, "called...")
360 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000361 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000362 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000363 else:
364 self.print_call_line(width, func, {})
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000365 print(file=self.stream)
366 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000367 return self
368
369 def print_callers(self, *amount):
370 width, list = self.get_print_list(amount)
371 if list:
372 self.print_call_heading(width, "was called by...")
373 for func in list:
374 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000375 self.print_call_line(width, func, callers, "<-")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000376 print(file=self.stream)
377 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000378 return self
379
380 def print_call_heading(self, name_size, column_title):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000381 print("Function ".ljust(name_size) + column_title, file=self.stream)
Armin Rigoa871ef22006-02-08 12:53:56 +0000382 # print sub-header only if we have new-style callers
383 subheader = False
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000384 for cc, nc, tt, ct, callers in self.stats.values():
Armin Rigoa871ef22006-02-08 12:53:56 +0000385 if callers:
Georg Brandla18af4e2007-04-21 15:47:16 +0000386 value = next(iter(callers.values()))
Armin Rigoa871ef22006-02-08 12:53:56 +0000387 subheader = isinstance(value, tuple)
388 break
389 if subheader:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000390 print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000391
Armin Rigoa871ef22006-02-08 12:53:56 +0000392 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000393 print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000394 if not call_dict:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000395 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000396 return
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000397 clist = sorted(call_dict.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000398 indent = ""
399 for func in clist:
400 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000401 value = call_dict[func]
402 if isinstance(value, tuple):
403 nc, cc, tt, ct = value
404 if nc != cc:
405 substats = '%d/%d' % (nc, cc)
406 else:
407 substats = '%d' % (nc,)
408 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
409 f8(tt), f8(ct), name)
410 left_width = name_size + 1
411 else:
412 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
413 left_width = name_size + 3
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000414 print(indent*left_width + substats, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000415 indent = " "
416
Tim Peters2344fae2001-01-15 00:50:52 +0000417 def print_title(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000418 print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
419 print('filename:lineno(function)', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000420
Georg Brandl9a8439d2010-10-22 06:35:59 +0000421 def print_line(self, func): # hack: should print percentages
Tim Peters2344fae2001-01-15 00:50:52 +0000422 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000423 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000424 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000425 c = c + '/' + str(cc)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000426 print(c.rjust(9), end=' ', file=self.stream)
427 print(f8(tt), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000428 if nc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000429 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000430 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000431 print(f8(tt/nc), end=' ', file=self.stream)
432 print(f8(ct), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000433 if cc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000434 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000435 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000436 print(f8(ct/cc), end=' ', file=self.stream)
437 print(func_std_string(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000438
Guido van Rossumadb31051994-06-23 11:42:52 +0000439class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000440 """This class provides a generic function for comparing any two tuples.
441 Each instance records a list of tuple-indices (from most significant
442 to least significant), and sort direction (ascending or decending) for
443 each tuple-index. The compare functions can then be used as the function
444 argument to the system sort() function when a list of tuples need to be
445 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000446
Tim Peters2344fae2001-01-15 00:50:52 +0000447 def __init__(self, comp_select_list):
448 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000449
Tim Peters2344fae2001-01-15 00:50:52 +0000450 def compare (self, left, right):
451 for index, direction in self.comp_select_list:
452 l = left[index]
453 r = right[index]
454 if l < r:
455 return -direction
456 if l > r:
457 return direction
458 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000459
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000460
Guido van Rossumadb31051994-06-23 11:42:52 +0000461#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000462# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000463
464def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000465 filename, line, name = func_name
466 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000467
468def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000469 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000470
471def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000472 if func_name[:2] == ('~', 0):
473 # special case for built-in functions
474 name = func_name[2]
475 if name.startswith('<') and name.endswith('>'):
476 return '{%s}' % name[1:-1]
477 else:
478 return name
479 else:
480 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000481
482#**************************************************************************
483# The following functions combine statists for pairs functions.
484# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000485# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000486#**************************************************************************
487
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000488def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000489 """Add together all the stats for two profile entries."""
490 cc, nc, tt, ct, callers = source
491 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000492 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000493 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000494
Guido van Rossumadb31051994-06-23 11:42:52 +0000495def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000496 """Combine two caller lists in a single list."""
497 new_callers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000498 for func, caller in target.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000499 new_callers[func] = caller
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000500 for func, caller in source.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000501 if func in new_callers:
Georg Brandl2d3c4e72010-08-02 17:24:49 +0000502 if isinstance(caller, tuple):
503 # format used by cProfile
504 new_callers[func] = tuple([i[0] + i[1] for i in
505 zip(caller, new_callers[func])])
506 else:
507 # format used by profile
508 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000509 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000510 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000511 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000512
Guido van Rossumadb31051994-06-23 11:42:52 +0000513def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000514 """Sum the caller statistics to get total number of calls received."""
515 nc = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000516 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000517 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000518 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000519
520#**************************************************************************
521# The following functions support printing of reports
522#**************************************************************************
523
524def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000525 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000526
527#**************************************************************************
528# Statistics browser added by ESR, April 2001
529#**************************************************************************
530
531if __name__ == '__main__':
532 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000533 try:
534 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000535 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000536 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000537
538 class ProfileBrowser(cmd.Cmd):
539 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000540 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000541 self.prompt = "% "
Georg Brandlb1a97af2010-08-02 12:06:18 +0000542 self.stats = None
543 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000544 if profile is not None:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000545 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000546
547 def generic(self, fn, line):
548 args = line.split()
549 processed = []
550 for term in args:
551 try:
552 processed.append(int(term))
553 continue
554 except ValueError:
555 pass
556 try:
557 frac = float(term)
558 if frac > 1 or frac < 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000559 print("Fraction argument must be in [0, 1]", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000560 continue
561 processed.append(frac)
562 continue
563 except ValueError:
564 pass
565 processed.append(term)
566 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000567 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000568 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000569 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000570 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000571 def generic_help(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000572 print("Arguments may be:", file=self.stream)
573 print("* An integer maximum number of entries to print.", file=self.stream)
574 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
575 print(" what fraction of selected entries to print.", file=self.stream)
576 print("* A regular expression; only entries with function names", file=self.stream)
577 print(" that match it are printed.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000578
579 def do_add(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000580 if self.stats:
581 self.stats.add(line)
582 else:
583 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000584 return 0
585 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000586 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000587
588 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000589 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000590 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000591 print("Print callees statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000592 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000593
594 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000595 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000596 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000597 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000598 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000599
600 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000601 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000602 return 1
603 def help_EOF(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000604 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000605
606 def do_quit(self, line):
607 return 1
608 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000609 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000610
611 def do_read(self, line):
612 if line:
613 try:
614 self.stats = Stats(line)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200615 except OSError as err:
Georg Brandl50da60c2008-01-06 21:38:54 +0000616 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000617 return
Georg Brandlf02e7362010-08-01 07:57:47 +0000618 except Exception as err:
619 print(err.__class__.__name__ + ':', err, file=self.stream)
620 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000621 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000622 elif len(self.prompt) > 2:
Georg Brandlf02e7362010-08-01 07:57:47 +0000623 line = self.prompt[:-2]
624 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000625 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000626 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000627 return 0
628 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000629 print("Read in profile data from a specified file.", file=self.stream)
Georg Brandlf02e7362010-08-01 07:57:47 +0000630 print("Without argument, reload the current file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000631
632 def do_reverse(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000633 if self.stats:
634 self.stats.reverse_order()
635 else:
636 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000637 return 0
638 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000639 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000640
641 def do_sort(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000642 if not self.stats:
643 print("No statistics object is loaded.", file=self.stream)
644 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000645 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchling0a628232010-04-02 17:02:57 +0000646 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000647 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000648 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000649 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000650 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000651 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000652 return 0
653 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000654 print("Sort profile data according to specified keys.", file=self.stream)
655 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000656 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000657 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000658
659 def do_stats(self, line):
660 return self.generic('print_stats', line)
661 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000662 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000663 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000664
665 def do_strip(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000666 if self.stats:
667 self.stats.strip_dirs()
668 else:
669 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000670 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000671 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000672
Georg Brandlf02e7362010-08-01 07:57:47 +0000673 def help_help(self):
674 print("Show help for a given command.", file=self.stream)
675
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000676 def postcmd(self, stop, line):
677 if stop:
678 return stop
679 return None
680
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000681 if len(sys.argv) > 1:
682 initprofile = sys.argv[1]
683 else:
684 initprofile = None
685 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000686 browser = ProfileBrowser(initprofile)
Antoine Pitrou9d8c1862012-03-14 17:47:11 +0100687 for profile in sys.argv[2:]:
688 browser.do_add(profile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000689 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000690 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000691 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000692 except KeyboardInterrupt:
693 pass
694
695# That's all, folks.