blob: d861413d4195f726ae6e67542c5479ee12f9f966 [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):
Giampaolo Rodola'2f50aaf2013-02-12 02:04:27 +010096 with open(arg, 'rb') as f:
97 self.stats = marshal.load(f)
Tim Peters2344fae2001-01-15 00:50:52 +000098 try:
99 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000100 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000101 except: # in case this is not unix
102 pass
Georg Brandl83938432010-10-22 06:28:01 +0000103 self.files = [arg]
Tim Peters2344fae2001-01-15 00:50:52 +0000104 elif hasattr(arg, 'create_stats'):
105 arg.create_stats()
106 self.stats = arg.stats
107 arg.stats = {}
108 if not self.stats:
Georg Brandl83938432010-10-22 06:28:01 +0000109 raise TypeError("Cannot create or construct a %r object from %r"
Collin Winterce36ad82007-08-30 01:19:48 +0000110 % (self.__class__, arg))
Tim Peters2344fae2001-01-15 00:50:52 +0000111 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000112
Tim Peters2344fae2001-01-15 00:50:52 +0000113 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000114 for func, (cc, nc, tt, ct, callers) in self.stats.items():
115 self.total_calls += nc
116 self.prim_calls += cc
117 self.total_tt += tt
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000118 if ("jprofile", 0, "profiler") in callers:
Georg Brandleb7e5692010-10-22 06:29:21 +0000119 self.top_level.add(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000120 if len(func_std_string(func)) > self.max_name_len:
121 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000122
Tim Peters2344fae2001-01-15 00:50:52 +0000123 def add(self, *arg_list):
Georg Brandl83938432010-10-22 06:28:01 +0000124 if not arg_list:
125 return self
126 for item in reversed(arg_list):
127 if type(self) != type(item):
128 item = Stats(item)
129 self.files += item.files
130 self.total_calls += item.total_calls
131 self.prim_calls += item.prim_calls
132 self.total_tt += item.total_tt
133 for func in item.top_level:
Georg Brandleb7e5692010-10-22 06:29:21 +0000134 self.top_level.add(func)
Guido van Rossumadb31051994-06-23 11:42:52 +0000135
Georg Brandl83938432010-10-22 06:28:01 +0000136 if self.max_name_len < item.max_name_len:
137 self.max_name_len = item.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000138
Georg Brandl83938432010-10-22 06:28:01 +0000139 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000140
Georg Brandl83938432010-10-22 06:28:01 +0000141 for func, stat in item.stats.items():
142 if func in self.stats:
143 old_func_stat = self.stats[func]
144 else:
145 old_func_stat = (0, 0, 0, 0, {},)
146 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000147 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000148
Fred Drake9c439102003-05-14 14:28:09 +0000149 def dump_stats(self, filename):
150 """Write the profile data to a file we know how to load back."""
Giampaolo Rodola'2f50aaf2013-02-12 02:04:27 +0100151 with open(filename, 'wb') as f:
Fred Drake9c439102003-05-14 14:28:09 +0000152 marshal.dump(self.stats, f)
Fred Drake9c439102003-05-14 14:28:09 +0000153
Tim Peters2344fae2001-01-15 00:50:52 +0000154 # list the tuple indices and directions for sorting,
155 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000156 sort_arg_dict_default = {
157 "calls" : (((1,-1), ), "call count"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300158 "ncalls" : (((1,-1), ), "call count"),
159 "cumtime" : (((3,-1), ), "cumulative time"),
Tim Peters7d016852001-10-08 06:13:19 +0000160 "cumulative": (((3,-1), ), "cumulative time"),
161 "file" : (((4, 1), ), "file name"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300162 "filename" : (((4, 1), ), "file name"),
Tim Peters7d016852001-10-08 06:13:19 +0000163 "line" : (((5, 1), ), "line number"),
164 "module" : (((4, 1), ), "file name"),
165 "name" : (((6, 1), ), "function name"),
166 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
Andrew Svetlov2ef45842012-10-07 18:58:42 +0300167 "pcalls" : (((0,-1), ), "primitive call count"),
Tim Peters7d016852001-10-08 06:13:19 +0000168 "stdname" : (((7, 1), ), "standard name"),
169 "time" : (((2,-1), ), "internal time"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300170 "tottime" : (((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
Brett Cannoncd171c82013-07-04 17:43:24 -0400531 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:
Stefan Krahe12a68b2016-08-02 22:30:24 +0200577 try:
578 self.stats.add(line)
579 except IOError as e:
580 print("Failed to load statistics for %s: %s" % (line, e), file=self.stream)
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000581 else:
582 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000583 return 0
584 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000585 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000586
587 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000588 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000589 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000590 print("Print callees statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000591 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000592
593 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000594 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000595 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000596 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000597 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000598
599 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000600 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000601 return 1
602 def help_EOF(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000603 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000604
605 def do_quit(self, line):
606 return 1
607 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000608 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000609
610 def do_read(self, line):
611 if line:
612 try:
613 self.stats = Stats(line)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200614 except OSError as err:
Georg Brandl50da60c2008-01-06 21:38:54 +0000615 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000616 return
Georg Brandlf02e7362010-08-01 07:57:47 +0000617 except Exception as err:
618 print(err.__class__.__name__ + ':', err, file=self.stream)
619 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000620 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000621 elif len(self.prompt) > 2:
Georg Brandlf02e7362010-08-01 07:57:47 +0000622 line = self.prompt[:-2]
623 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000624 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000625 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000626 return 0
627 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000628 print("Read in profile data from a specified file.", file=self.stream)
Georg Brandlf02e7362010-08-01 07:57:47 +0000629 print("Without argument, reload the current file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000630
631 def do_reverse(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000632 if self.stats:
633 self.stats.reverse_order()
634 else:
635 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000636 return 0
637 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000638 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000639
640 def do_sort(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000641 if not self.stats:
642 print("No statistics object is loaded.", file=self.stream)
643 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000644 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchling0a628232010-04-02 17:02:57 +0000645 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000646 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000647 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000648 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000649 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000650 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000651 return 0
652 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000653 print("Sort profile data according to specified keys.", file=self.stream)
654 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000655 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000656 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000657
658 def do_stats(self, line):
659 return self.generic('print_stats', line)
660 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000661 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000662 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000663
664 def do_strip(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000665 if self.stats:
666 self.stats.strip_dirs()
667 else:
668 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000669 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000670 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000671
Georg Brandlf02e7362010-08-01 07:57:47 +0000672 def help_help(self):
673 print("Show help for a given command.", file=self.stream)
674
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000675 def postcmd(self, stop, line):
676 if stop:
677 return stop
678 return None
679
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000680 if len(sys.argv) > 1:
681 initprofile = sys.argv[1]
682 else:
683 initprofile = None
684 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000685 browser = ProfileBrowser(initprofile)
Antoine Pitrou9d8c1862012-03-14 17:47:11 +0100686 for profile in sys.argv[2:]:
687 browser.do_add(profile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000688 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000689 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000690 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000691 except KeyboardInterrupt:
692 pass
693
694# That's all, folks.