blob: 3f0add2c65e6ea567d0f4232f0336634268f7b8f [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"),
162 "cumulative": (((3,-1), ), "cumulative time"),
163 "file" : (((4, 1), ), "file name"),
164 "line" : (((5, 1), ), "line number"),
165 "module" : (((4, 1), ), "file name"),
166 "name" : (((6, 1), ), "function name"),
167 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
168 "pcalls" : (((0,-1), ), "call count"),
169 "stdname" : (((7, 1), ), "standard name"),
170 "time" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000171 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000172
Tim Peters2344fae2001-01-15 00:50:52 +0000173 def get_sort_arg_defs(self):
174 """Expand all abbreviations that are unique."""
175 if not self.sort_arg_dict:
176 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000177 bad_list = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000178 for word, tup in self.sort_arg_dict_default.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000179 fragment = word
180 while fragment:
181 if not fragment:
182 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000183 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000184 bad_list[fragment] = 0
185 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000186 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000187 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000188 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000189 del dict[word]
190 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000191
Tim Peters2344fae2001-01-15 00:50:52 +0000192 def sort_stats(self, *field):
193 if not field:
194 self.fcn_list = 0
195 return self
Georg Brandlb1a97af2010-08-02 12:06:18 +0000196 if len(field) == 1 and isinstance(field[0], int):
Tim Peters2344fae2001-01-15 00:50:52 +0000197 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000198 field = [ {-1: "stdname",
Georg Brandlb1a97af2010-08-02 12:06:18 +0000199 0: "calls",
200 1: "time",
201 2: "cumulative"}[field[0]] ]
Tim Peters2344fae2001-01-15 00:50:52 +0000202
203 sort_arg_defs = self.get_sort_arg_defs()
204 sort_tuple = ()
205 self.sort_type = ""
206 connector = ""
207 for word in field:
208 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000209 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000210 connector = ", "
211
212 stats_list = []
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000213 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Tim Peters7d016852001-10-08 06:13:19 +0000214 stats_list.append((cc, nc, tt, ct) + func +
215 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000216
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000217 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000218
219 self.fcn_list = fcn_list = []
220 for tuple in stats_list:
221 fcn_list.append(tuple[-1])
222 return self
223
Tim Peters2344fae2001-01-15 00:50:52 +0000224 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000225 if self.fcn_list:
226 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000227 return self
228
229 def strip_dirs(self):
230 oldstats = self.stats
231 self.stats = newstats = {}
232 max_name_len = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000233 for func, (cc, nc, tt, ct, callers) in oldstats.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000234 newfunc = func_strip_path(func)
235 if len(func_std_string(newfunc)) > max_name_len:
236 max_name_len = len(func_std_string(newfunc))
237 newcallers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000238 for func2, caller in callers.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000239 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000240
Raymond Hettinger54f02222002-06-01 14:18:47 +0000241 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000242 newstats[newfunc] = add_func_stats(
243 newstats[newfunc],
244 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000245 else:
246 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
247 old_top = self.top_level
Georg Brandleb7e5692010-10-22 06:29:21 +0000248 self.top_level = new_top = set()
Raymond Hettingere0d49722002-06-02 18:55:56 +0000249 for func in old_top:
Georg Brandleb7e5692010-10-22 06:29:21 +0000250 new_top.add(func_strip_path(func))
Tim Peters2344fae2001-01-15 00:50:52 +0000251
252 self.max_name_len = max_name_len
253
254 self.fcn_list = None
255 self.all_callees = None
256 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000257
Tim Peters2344fae2001-01-15 00:50:52 +0000258 def calc_callees(self):
Georg Brandl9a8439d2010-10-22 06:35:59 +0000259 if self.all_callees:
260 return
Tim Peters2344fae2001-01-15 00:50:52 +0000261 self.all_callees = all_callees = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000262 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000263 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000264 all_callees[func] = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000265 for func2, caller in callers.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000266 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000267 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000268 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000269 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000270
Tim Peters2344fae2001-01-15 00:50:52 +0000271 #******************************************************************
272 # The following functions support actual printing of reports
273 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000274
Tim Peters2344fae2001-01-15 00:50:52 +0000275 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000276
Tim Peters2344fae2001-01-15 00:50:52 +0000277 def eval_print_amount(self, sel, list, msg):
278 new_list = list
Georg Brandlb1a97af2010-08-02 12:06:18 +0000279 if isinstance(sel, str):
280 try:
281 rex = re.compile(sel)
282 except re.error:
283 msg += " <Invalid regular expression %r>\n" % sel
284 return new_list, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000285 new_list = []
286 for func in list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000287 if rex.search(func_std_string(func)):
Tim Peters2344fae2001-01-15 00:50:52 +0000288 new_list.append(func)
289 else:
290 count = len(list)
Georg Brandlb1a97af2010-08-02 12:06:18 +0000291 if isinstance(sel, float) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000292 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000293 new_list = list[:count]
Georg Brandlb1a97af2010-08-02 12:06:18 +0000294 elif isinstance(sel, int) and 0 <= sel < count:
Tim Peters2344fae2001-01-15 00:50:52 +0000295 count = sel
296 new_list = list[:count]
297 if len(list) != len(new_list):
Georg Brandlb1a97af2010-08-02 12:06:18 +0000298 msg += " List reduced from %r to %r due to restriction <%r>\n" % (
299 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000300
Tim Peters2344fae2001-01-15 00:50:52 +0000301 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000302
Tim Peters2344fae2001-01-15 00:50:52 +0000303 def get_print_list(self, sel_list):
304 width = self.max_name_len
305 if self.fcn_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000306 stat_list = self.fcn_list[:]
Tim Peters2344fae2001-01-15 00:50:52 +0000307 msg = " Ordered by: " + self.sort_type + '\n'
308 else:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000309 stat_list = list(self.stats.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000310 msg = " Random listing order was used\n"
311
312 for selection in sel_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000313 stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000314
Georg Brandlb1a97af2010-08-02 12:06:18 +0000315 count = len(stat_list)
Tim Peters2344fae2001-01-15 00:50:52 +0000316
Georg Brandlb1a97af2010-08-02 12:06:18 +0000317 if not stat_list:
318 return 0, stat_list
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000319 print(msg, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000320 if count < len(self.stats):
321 width = 0
Georg Brandlb1a97af2010-08-02 12:06:18 +0000322 for func in stat_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000323 if len(func_std_string(func)) > width:
324 width = len(func_std_string(func))
Georg Brandlb1a97af2010-08-02 12:06:18 +0000325 return width+2, stat_list
Tim Peters2344fae2001-01-15 00:50:52 +0000326
327 def print_stats(self, *amount):
328 for filename in self.files:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000329 print(filename, file=self.stream)
Georg Brandl9a8439d2010-10-22 06:35:59 +0000330 if self.files:
331 print(file=self.stream)
Tim Peters7d016852001-10-08 06:13:19 +0000332 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000333 for func in self.top_level:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000334 print(indent, func_get_function_name(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000335
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000336 print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000337 if self.total_calls != self.prim_calls:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000338 print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
Senthil Kumaran5e703cf2010-11-20 17:02:50 +0000339 print("in %.3f seconds" % self.total_tt, file=self.stream)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000340 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000341 width, list = self.get_print_list(amount)
342 if list:
343 self.print_title()
344 for func in list:
345 self.print_line(func)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000346 print(file=self.stream)
347 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000348 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000349
Tim Peters2344fae2001-01-15 00:50:52 +0000350 def print_callees(self, *amount):
351 width, list = self.get_print_list(amount)
352 if list:
353 self.calc_callees()
354
355 self.print_call_heading(width, "called...")
356 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000357 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000358 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000359 else:
360 self.print_call_line(width, func, {})
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000361 print(file=self.stream)
362 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000363 return self
364
365 def print_callers(self, *amount):
366 width, list = self.get_print_list(amount)
367 if list:
368 self.print_call_heading(width, "was called by...")
369 for func in list:
370 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000371 self.print_call_line(width, func, callers, "<-")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000372 print(file=self.stream)
373 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000374 return self
375
376 def print_call_heading(self, name_size, column_title):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000377 print("Function ".ljust(name_size) + column_title, file=self.stream)
Armin Rigoa871ef22006-02-08 12:53:56 +0000378 # print sub-header only if we have new-style callers
379 subheader = False
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000380 for cc, nc, tt, ct, callers in self.stats.values():
Armin Rigoa871ef22006-02-08 12:53:56 +0000381 if callers:
Georg Brandla18af4e2007-04-21 15:47:16 +0000382 value = next(iter(callers.values()))
Armin Rigoa871ef22006-02-08 12:53:56 +0000383 subheader = isinstance(value, tuple)
384 break
385 if subheader:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000386 print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000387
Armin Rigoa871ef22006-02-08 12:53:56 +0000388 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000389 print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000390 if not call_dict:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000391 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000392 return
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000393 clist = sorted(call_dict.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000394 indent = ""
395 for func in clist:
396 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000397 value = call_dict[func]
398 if isinstance(value, tuple):
399 nc, cc, tt, ct = value
400 if nc != cc:
401 substats = '%d/%d' % (nc, cc)
402 else:
403 substats = '%d' % (nc,)
404 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
405 f8(tt), f8(ct), name)
406 left_width = name_size + 1
407 else:
408 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
409 left_width = name_size + 3
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000410 print(indent*left_width + substats, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000411 indent = " "
412
Tim Peters2344fae2001-01-15 00:50:52 +0000413 def print_title(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000414 print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
415 print('filename:lineno(function)', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000416
Georg Brandl9a8439d2010-10-22 06:35:59 +0000417 def print_line(self, func): # hack: should print percentages
Tim Peters2344fae2001-01-15 00:50:52 +0000418 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000419 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000420 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000421 c = c + '/' + str(cc)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000422 print(c.rjust(9), end=' ', file=self.stream)
423 print(f8(tt), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000424 if nc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000425 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000426 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000427 print(f8(tt/nc), end=' ', file=self.stream)
428 print(f8(ct), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000429 if cc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000430 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000431 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000432 print(f8(ct/cc), end=' ', file=self.stream)
433 print(func_std_string(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000434
Guido van Rossumadb31051994-06-23 11:42:52 +0000435class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000436 """This class provides a generic function for comparing any two tuples.
437 Each instance records a list of tuple-indices (from most significant
438 to least significant), and sort direction (ascending or decending) for
439 each tuple-index. The compare functions can then be used as the function
440 argument to the system sort() function when a list of tuples need to be
441 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000442
Tim Peters2344fae2001-01-15 00:50:52 +0000443 def __init__(self, comp_select_list):
444 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000445
Tim Peters2344fae2001-01-15 00:50:52 +0000446 def compare (self, left, right):
447 for index, direction in self.comp_select_list:
448 l = left[index]
449 r = right[index]
450 if l < r:
451 return -direction
452 if l > r:
453 return direction
454 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000455
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000456
Guido van Rossumadb31051994-06-23 11:42:52 +0000457#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000458# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000459
460def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000461 filename, line, name = func_name
462 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000463
464def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000465 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000466
467def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000468 if func_name[:2] == ('~', 0):
469 # special case for built-in functions
470 name = func_name[2]
471 if name.startswith('<') and name.endswith('>'):
472 return '{%s}' % name[1:-1]
473 else:
474 return name
475 else:
476 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000477
478#**************************************************************************
479# The following functions combine statists for pairs functions.
480# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000481# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000482#**************************************************************************
483
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000484def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000485 """Add together all the stats for two profile entries."""
486 cc, nc, tt, ct, callers = source
487 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000488 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000489 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000490
Guido van Rossumadb31051994-06-23 11:42:52 +0000491def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000492 """Combine two caller lists in a single list."""
493 new_callers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000494 for func, caller in target.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000495 new_callers[func] = caller
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000496 for func, caller in source.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000497 if func in new_callers:
Georg Brandl2d3c4e72010-08-02 17:24:49 +0000498 if isinstance(caller, tuple):
499 # format used by cProfile
500 new_callers[func] = tuple([i[0] + i[1] for i in
501 zip(caller, new_callers[func])])
502 else:
503 # format used by profile
504 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000505 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000506 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000507 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000508
Guido van Rossumadb31051994-06-23 11:42:52 +0000509def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000510 """Sum the caller statistics to get total number of calls received."""
511 nc = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000512 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000513 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000514 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000515
516#**************************************************************************
517# The following functions support printing of reports
518#**************************************************************************
519
520def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000521 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000522
523#**************************************************************************
524# Statistics browser added by ESR, April 2001
525#**************************************************************************
526
527if __name__ == '__main__':
528 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000529 try:
530 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000531 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000532 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000533
534 class ProfileBrowser(cmd.Cmd):
535 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000536 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000537 self.prompt = "% "
Georg Brandlb1a97af2010-08-02 12:06:18 +0000538 self.stats = None
539 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000540 if profile is not None:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000541 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000542
543 def generic(self, fn, line):
544 args = line.split()
545 processed = []
546 for term in args:
547 try:
548 processed.append(int(term))
549 continue
550 except ValueError:
551 pass
552 try:
553 frac = float(term)
554 if frac > 1 or frac < 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000555 print("Fraction argument must be in [0, 1]", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000556 continue
557 processed.append(frac)
558 continue
559 except ValueError:
560 pass
561 processed.append(term)
562 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000563 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000564 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000565 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000566 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000567 def generic_help(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000568 print("Arguments may be:", file=self.stream)
569 print("* An integer maximum number of entries to print.", file=self.stream)
570 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
571 print(" what fraction of selected entries to print.", file=self.stream)
572 print("* A regular expression; only entries with function names", file=self.stream)
573 print(" that match it are printed.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000574
575 def do_add(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000576 if self.stats:
577 self.stats.add(line)
578 else:
579 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000580 return 0
581 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000582 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000583
584 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000585 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000586 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000587 print("Print callees statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000588 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000589
590 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000591 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000592 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000593 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000594 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000595
596 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000597 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000598 return 1
599 def help_EOF(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000600 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000601
602 def do_quit(self, line):
603 return 1
604 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000605 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000606
607 def do_read(self, line):
608 if line:
609 try:
610 self.stats = Stats(line)
Georg Brandl50da60c2008-01-06 21:38:54 +0000611 except IOError as err:
612 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000613 return
Georg Brandlf02e7362010-08-01 07:57:47 +0000614 except Exception as err:
615 print(err.__class__.__name__ + ':', err, file=self.stream)
616 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000617 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000618 elif len(self.prompt) > 2:
Georg Brandlf02e7362010-08-01 07:57:47 +0000619 line = self.prompt[:-2]
620 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000621 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000622 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000623 return 0
624 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000625 print("Read in profile data from a specified file.", file=self.stream)
Georg Brandlf02e7362010-08-01 07:57:47 +0000626 print("Without argument, reload the current file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000627
628 def do_reverse(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000629 if self.stats:
630 self.stats.reverse_order()
631 else:
632 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000633 return 0
634 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000635 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000636
637 def do_sort(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000638 if not self.stats:
639 print("No statistics object is loaded.", file=self.stream)
640 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000641 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchling0a628232010-04-02 17:02:57 +0000642 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000643 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000644 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000645 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000646 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000647 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000648 return 0
649 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000650 print("Sort profile data according to specified keys.", file=self.stream)
651 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000652 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000653 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000654
655 def do_stats(self, line):
656 return self.generic('print_stats', line)
657 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000658 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000659 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000660
661 def do_strip(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000662 if self.stats:
663 self.stats.strip_dirs()
664 else:
665 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000666 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000667 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000668
Georg Brandlf02e7362010-08-01 07:57:47 +0000669 def help_help(self):
670 print("Show help for a given command.", file=self.stream)
671
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000672 def postcmd(self, stop, line):
673 if stop:
674 return stop
675 return None
676
677 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000678 if len(sys.argv) > 1:
679 initprofile = sys.argv[1]
680 else:
681 initprofile = None
682 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000683 browser = ProfileBrowser(initprofile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000684 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000685 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000686 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000687 except KeyboardInterrupt:
688 pass
689
690# That's all, folks.