blob: 2c5bf981b85cf85ade1d10889f0a8b1325db3dd6 [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
Berker Peksagb067a5e2017-02-22 04:55:33 +030051 addition to the old -1, 0, 1, or 2 that are respectively interpreted as
52 'stdname', 'calls', 'time', and 'cumulative'). It takes an arbitrary number
53 of quoted strings to select the sort order.
54
55 For example sort_stats('time', 'name') sorts on the major key of 'internal
56 function time', and on the minor key of 'the name of the function'. Look at
57 the two tables in sort_stats() and get_sort_arg_defs(self) for more
58 examples.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000059
Georg Brandlb1a97af2010-08-02 12:06:18 +000060 All methods return self, so you can string together commands like:
Tim Peters2344fae2001-01-15 00:50:52 +000061 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
62 print_stats(5).print_callers(5)
63 """
64
Georg Brandl7837a962009-09-02 20:33:30 +000065 def __init__(self, *args, stream=None):
66 self.stream = stream or sys.stdout
Tim Peters2344fae2001-01-15 00:50:52 +000067 if not len(args):
68 arg = None
69 else:
70 arg = args[0]
71 args = args[1:]
72 self.init(arg)
Guido van Rossum68468eb2003-02-27 20:14:51 +000073 self.add(*args)
Tim Peters2344fae2001-01-15 00:50:52 +000074
75 def init(self, arg):
76 self.all_callees = None # calc only if needed
77 self.files = []
78 self.fcn_list = None
79 self.total_tt = 0
80 self.total_calls = 0
81 self.prim_calls = 0
82 self.max_name_len = 0
Georg Brandleb7e5692010-10-22 06:29:21 +000083 self.top_level = set()
Tim Peters2344fae2001-01-15 00:50:52 +000084 self.stats = {}
85 self.sort_arg_dict = {}
86 self.load_stats(arg)
Tim Peters2344fae2001-01-15 00:50:52 +000087 try:
88 self.get_top_level_stats()
Georg Brandl9a8439d2010-10-22 06:35:59 +000089 except Exception:
90 print("Invalid timing data %s" %
91 (self.files[-1] if self.files else ''), file=self.stream)
92 raise
Guido van Rossumadb31051994-06-23 11:42:52 +000093
Tim Peters2344fae2001-01-15 00:50:52 +000094 def load_stats(self, arg):
Georg Brandl83938432010-10-22 06:28:01 +000095 if arg is None:
96 self.stats = {}
97 return
Guido van Rossum3172c5d2007-10-16 18:12:55 +000098 elif isinstance(arg, str):
Giampaolo Rodola'2f50aaf2013-02-12 02:04:27 +010099 with open(arg, 'rb') as f:
100 self.stats = marshal.load(f)
Tim Peters2344fae2001-01-15 00:50:52 +0000101 try:
102 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000103 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000104 except: # in case this is not unix
105 pass
Georg Brandl83938432010-10-22 06:28:01 +0000106 self.files = [arg]
Tim Peters2344fae2001-01-15 00:50:52 +0000107 elif hasattr(arg, 'create_stats'):
108 arg.create_stats()
109 self.stats = arg.stats
110 arg.stats = {}
111 if not self.stats:
Georg Brandl83938432010-10-22 06:28:01 +0000112 raise TypeError("Cannot create or construct a %r object from %r"
Collin Winterce36ad82007-08-30 01:19:48 +0000113 % (self.__class__, arg))
Tim Peters2344fae2001-01-15 00:50:52 +0000114 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000115
Tim Peters2344fae2001-01-15 00:50:52 +0000116 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000117 for func, (cc, nc, tt, ct, callers) in self.stats.items():
118 self.total_calls += nc
119 self.prim_calls += cc
120 self.total_tt += tt
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000121 if ("jprofile", 0, "profiler") in callers:
Georg Brandleb7e5692010-10-22 06:29:21 +0000122 self.top_level.add(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000123 if len(func_std_string(func)) > self.max_name_len:
124 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000125
Tim Peters2344fae2001-01-15 00:50:52 +0000126 def add(self, *arg_list):
Georg Brandl83938432010-10-22 06:28:01 +0000127 if not arg_list:
128 return self
129 for item in reversed(arg_list):
130 if type(self) != type(item):
131 item = Stats(item)
132 self.files += item.files
133 self.total_calls += item.total_calls
134 self.prim_calls += item.prim_calls
135 self.total_tt += item.total_tt
136 for func in item.top_level:
Georg Brandleb7e5692010-10-22 06:29:21 +0000137 self.top_level.add(func)
Guido van Rossumadb31051994-06-23 11:42:52 +0000138
Georg Brandl83938432010-10-22 06:28:01 +0000139 if self.max_name_len < item.max_name_len:
140 self.max_name_len = item.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000141
Georg Brandl83938432010-10-22 06:28:01 +0000142 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000143
Georg Brandl83938432010-10-22 06:28:01 +0000144 for func, stat in item.stats.items():
145 if func in self.stats:
146 old_func_stat = self.stats[func]
147 else:
148 old_func_stat = (0, 0, 0, 0, {},)
149 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000150 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000151
Fred Drake9c439102003-05-14 14:28:09 +0000152 def dump_stats(self, filename):
153 """Write the profile data to a file we know how to load back."""
Giampaolo Rodola'2f50aaf2013-02-12 02:04:27 +0100154 with open(filename, 'wb') as f:
Fred Drake9c439102003-05-14 14:28:09 +0000155 marshal.dump(self.stats, f)
Fred Drake9c439102003-05-14 14:28:09 +0000156
Tim Peters2344fae2001-01-15 00:50:52 +0000157 # list the tuple indices and directions for sorting,
158 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000159 sort_arg_dict_default = {
160 "calls" : (((1,-1), ), "call count"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300161 "ncalls" : (((1,-1), ), "call count"),
162 "cumtime" : (((3,-1), ), "cumulative time"),
Tim Peters7d016852001-10-08 06:13:19 +0000163 "cumulative": (((3,-1), ), "cumulative time"),
164 "file" : (((4, 1), ), "file name"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300165 "filename" : (((4, 1), ), "file name"),
Tim Peters7d016852001-10-08 06:13:19 +0000166 "line" : (((5, 1), ), "line number"),
167 "module" : (((4, 1), ), "file name"),
168 "name" : (((6, 1), ), "function name"),
169 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
Andrew Svetlov2ef45842012-10-07 18:58:42 +0300170 "pcalls" : (((0,-1), ), "primitive call count"),
Tim Peters7d016852001-10-08 06:13:19 +0000171 "stdname" : (((7, 1), ), "standard name"),
172 "time" : (((2,-1), ), "internal time"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300173 "tottime" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000174 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000175
Tim Peters2344fae2001-01-15 00:50:52 +0000176 def get_sort_arg_defs(self):
177 """Expand all abbreviations that are unique."""
178 if not self.sort_arg_dict:
179 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000180 bad_list = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000181 for word, tup in self.sort_arg_dict_default.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000182 fragment = word
183 while fragment:
184 if not fragment:
185 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000186 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000187 bad_list[fragment] = 0
188 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000189 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000190 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000191 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000192 del dict[word]
193 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000194
Tim Peters2344fae2001-01-15 00:50:52 +0000195 def sort_stats(self, *field):
196 if not field:
197 self.fcn_list = 0
198 return self
Georg Brandlb1a97af2010-08-02 12:06:18 +0000199 if len(field) == 1 and isinstance(field[0], int):
Tim Peters2344fae2001-01-15 00:50:52 +0000200 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000201 field = [ {-1: "stdname",
Georg Brandlb1a97af2010-08-02 12:06:18 +0000202 0: "calls",
203 1: "time",
204 2: "cumulative"}[field[0]] ]
Tim Peters2344fae2001-01-15 00:50:52 +0000205
206 sort_arg_defs = self.get_sort_arg_defs()
207 sort_tuple = ()
208 self.sort_type = ""
209 connector = ""
210 for word in field:
211 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000212 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000213 connector = ", "
214
215 stats_list = []
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000216 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Tim Peters7d016852001-10-08 06:13:19 +0000217 stats_list.append((cc, nc, tt, ct) + func +
218 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000219
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000220 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000221
222 self.fcn_list = fcn_list = []
223 for tuple in stats_list:
224 fcn_list.append(tuple[-1])
225 return self
226
Tim Peters2344fae2001-01-15 00:50:52 +0000227 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000228 if self.fcn_list:
229 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000230 return self
231
232 def strip_dirs(self):
233 oldstats = self.stats
234 self.stats = newstats = {}
235 max_name_len = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000236 for func, (cc, nc, tt, ct, callers) in oldstats.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000237 newfunc = func_strip_path(func)
238 if len(func_std_string(newfunc)) > max_name_len:
239 max_name_len = len(func_std_string(newfunc))
240 newcallers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000241 for func2, caller in callers.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000242 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000243
Raymond Hettinger54f02222002-06-01 14:18:47 +0000244 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000245 newstats[newfunc] = add_func_stats(
246 newstats[newfunc],
247 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000248 else:
249 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
250 old_top = self.top_level
Georg Brandleb7e5692010-10-22 06:29:21 +0000251 self.top_level = new_top = set()
Raymond Hettingere0d49722002-06-02 18:55:56 +0000252 for func in old_top:
Georg Brandleb7e5692010-10-22 06:29:21 +0000253 new_top.add(func_strip_path(func))
Tim Peters2344fae2001-01-15 00:50:52 +0000254
255 self.max_name_len = max_name_len
256
257 self.fcn_list = None
258 self.all_callees = None
259 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000260
Tim Peters2344fae2001-01-15 00:50:52 +0000261 def calc_callees(self):
Georg Brandl9a8439d2010-10-22 06:35:59 +0000262 if self.all_callees:
263 return
Tim Peters2344fae2001-01-15 00:50:52 +0000264 self.all_callees = all_callees = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000265 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000266 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000267 all_callees[func] = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000268 for func2, caller in callers.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000269 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000270 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000271 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000272 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000273
Tim Peters2344fae2001-01-15 00:50:52 +0000274 #******************************************************************
275 # The following functions support actual printing of reports
276 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000277
Tim Peters2344fae2001-01-15 00:50:52 +0000278 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000279
Tim Peters2344fae2001-01-15 00:50:52 +0000280 def eval_print_amount(self, sel, list, msg):
281 new_list = list
Georg Brandlb1a97af2010-08-02 12:06:18 +0000282 if isinstance(sel, str):
283 try:
284 rex = re.compile(sel)
285 except re.error:
286 msg += " <Invalid regular expression %r>\n" % sel
287 return new_list, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000288 new_list = []
289 for func in list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000290 if rex.search(func_std_string(func)):
Tim Peters2344fae2001-01-15 00:50:52 +0000291 new_list.append(func)
292 else:
293 count = len(list)
Georg Brandlb1a97af2010-08-02 12:06:18 +0000294 if isinstance(sel, float) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000295 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000296 new_list = list[:count]
Georg Brandlb1a97af2010-08-02 12:06:18 +0000297 elif isinstance(sel, int) and 0 <= sel < count:
Tim Peters2344fae2001-01-15 00:50:52 +0000298 count = sel
299 new_list = list[:count]
300 if len(list) != len(new_list):
Georg Brandlb1a97af2010-08-02 12:06:18 +0000301 msg += " List reduced from %r to %r due to restriction <%r>\n" % (
302 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000303
Tim Peters2344fae2001-01-15 00:50:52 +0000304 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000305
Tim Peters2344fae2001-01-15 00:50:52 +0000306 def get_print_list(self, sel_list):
307 width = self.max_name_len
308 if self.fcn_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000309 stat_list = self.fcn_list[:]
Tim Peters2344fae2001-01-15 00:50:52 +0000310 msg = " Ordered by: " + self.sort_type + '\n'
311 else:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000312 stat_list = list(self.stats.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000313 msg = " Random listing order was used\n"
314
315 for selection in sel_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000316 stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000317
Georg Brandlb1a97af2010-08-02 12:06:18 +0000318 count = len(stat_list)
Tim Peters2344fae2001-01-15 00:50:52 +0000319
Georg Brandlb1a97af2010-08-02 12:06:18 +0000320 if not stat_list:
321 return 0, stat_list
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000322 print(msg, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000323 if count < len(self.stats):
324 width = 0
Georg Brandlb1a97af2010-08-02 12:06:18 +0000325 for func in stat_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000326 if len(func_std_string(func)) > width:
327 width = len(func_std_string(func))
Georg Brandlb1a97af2010-08-02 12:06:18 +0000328 return width+2, stat_list
Tim Peters2344fae2001-01-15 00:50:52 +0000329
330 def print_stats(self, *amount):
331 for filename in self.files:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000332 print(filename, file=self.stream)
Georg Brandl9a8439d2010-10-22 06:35:59 +0000333 if self.files:
334 print(file=self.stream)
Tim Peters7d016852001-10-08 06:13:19 +0000335 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000336 for func in self.top_level:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000337 print(indent, func_get_function_name(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000338
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000339 print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000340 if self.total_calls != self.prim_calls:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000341 print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
Senthil Kumaran5e703cf2010-11-20 17:02:50 +0000342 print("in %.3f seconds" % self.total_tt, file=self.stream)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000343 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000344 width, list = self.get_print_list(amount)
345 if list:
346 self.print_title()
347 for func in list:
348 self.print_line(func)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000349 print(file=self.stream)
350 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000351 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000352
Tim Peters2344fae2001-01-15 00:50:52 +0000353 def print_callees(self, *amount):
354 width, list = self.get_print_list(amount)
355 if list:
356 self.calc_callees()
357
358 self.print_call_heading(width, "called...")
359 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000360 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000361 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000362 else:
363 self.print_call_line(width, func, {})
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000364 print(file=self.stream)
365 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000366 return self
367
368 def print_callers(self, *amount):
369 width, list = self.get_print_list(amount)
370 if list:
371 self.print_call_heading(width, "was called by...")
372 for func in list:
373 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000374 self.print_call_line(width, func, callers, "<-")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000375 print(file=self.stream)
376 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000377 return self
378
379 def print_call_heading(self, name_size, column_title):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000380 print("Function ".ljust(name_size) + column_title, file=self.stream)
Armin Rigoa871ef22006-02-08 12:53:56 +0000381 # print sub-header only if we have new-style callers
382 subheader = False
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000383 for cc, nc, tt, ct, callers in self.stats.values():
Armin Rigoa871ef22006-02-08 12:53:56 +0000384 if callers:
Georg Brandla18af4e2007-04-21 15:47:16 +0000385 value = next(iter(callers.values()))
Armin Rigoa871ef22006-02-08 12:53:56 +0000386 subheader = isinstance(value, tuple)
387 break
388 if subheader:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000389 print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000390
Armin Rigoa871ef22006-02-08 12:53:56 +0000391 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000392 print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000393 if not call_dict:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000394 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000395 return
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000396 clist = sorted(call_dict.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000397 indent = ""
398 for func in clist:
399 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000400 value = call_dict[func]
401 if isinstance(value, tuple):
402 nc, cc, tt, ct = value
403 if nc != cc:
404 substats = '%d/%d' % (nc, cc)
405 else:
406 substats = '%d' % (nc,)
407 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
408 f8(tt), f8(ct), name)
409 left_width = name_size + 1
410 else:
411 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
412 left_width = name_size + 3
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000413 print(indent*left_width + substats, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000414 indent = " "
415
Tim Peters2344fae2001-01-15 00:50:52 +0000416 def print_title(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000417 print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
418 print('filename:lineno(function)', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000419
Georg Brandl9a8439d2010-10-22 06:35:59 +0000420 def print_line(self, func): # hack: should print percentages
Tim Peters2344fae2001-01-15 00:50:52 +0000421 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000422 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000423 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000424 c = c + '/' + str(cc)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000425 print(c.rjust(9), end=' ', file=self.stream)
426 print(f8(tt), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000427 if nc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000428 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000429 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000430 print(f8(tt/nc), end=' ', file=self.stream)
431 print(f8(ct), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000432 if cc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000433 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000434 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000435 print(f8(ct/cc), end=' ', file=self.stream)
436 print(func_std_string(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000437
Guido van Rossumadb31051994-06-23 11:42:52 +0000438class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000439 """This class provides a generic function for comparing any two tuples.
440 Each instance records a list of tuple-indices (from most significant
441 to least significant), and sort direction (ascending or decending) for
442 each tuple-index. The compare functions can then be used as the function
443 argument to the system sort() function when a list of tuples need to be
444 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000445
Tim Peters2344fae2001-01-15 00:50:52 +0000446 def __init__(self, comp_select_list):
447 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000448
Tim Peters2344fae2001-01-15 00:50:52 +0000449 def compare (self, left, right):
450 for index, direction in self.comp_select_list:
451 l = left[index]
452 r = right[index]
453 if l < r:
454 return -direction
455 if l > r:
456 return direction
457 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000458
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000459
Guido van Rossumadb31051994-06-23 11:42:52 +0000460#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000461# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000462
463def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000464 filename, line, name = func_name
465 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000466
467def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000468 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000469
470def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000471 if func_name[:2] == ('~', 0):
472 # special case for built-in functions
473 name = func_name[2]
474 if name.startswith('<') and name.endswith('>'):
475 return '{%s}' % name[1:-1]
476 else:
477 return name
478 else:
479 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000480
481#**************************************************************************
482# The following functions combine statists for pairs functions.
483# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000484# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000485#**************************************************************************
486
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000487def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000488 """Add together all the stats for two profile entries."""
489 cc, nc, tt, ct, callers = source
490 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000491 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000492 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000493
Guido van Rossumadb31051994-06-23 11:42:52 +0000494def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000495 """Combine two caller lists in a single list."""
496 new_callers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000497 for func, caller in target.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000498 new_callers[func] = caller
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000499 for func, caller in source.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000500 if func in new_callers:
Georg Brandl2d3c4e72010-08-02 17:24:49 +0000501 if isinstance(caller, tuple):
502 # format used by cProfile
503 new_callers[func] = tuple([i[0] + i[1] for i in
504 zip(caller, new_callers[func])])
505 else:
506 # format used by profile
507 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000508 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000509 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000510 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000511
Guido van Rossumadb31051994-06-23 11:42:52 +0000512def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000513 """Sum the caller statistics to get total number of calls received."""
514 nc = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000515 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000516 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000517 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000518
519#**************************************************************************
520# The following functions support printing of reports
521#**************************************************************************
522
523def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000524 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000525
526#**************************************************************************
527# Statistics browser added by ESR, April 2001
528#**************************************************************************
529
530if __name__ == '__main__':
531 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000532 try:
533 import readline
Brett Cannoncd171c82013-07-04 17:43:24 -0400534 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000535 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000536
537 class ProfileBrowser(cmd.Cmd):
538 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000539 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000540 self.prompt = "% "
Georg Brandlb1a97af2010-08-02 12:06:18 +0000541 self.stats = None
542 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000543 if profile is not None:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000544 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000545
546 def generic(self, fn, line):
547 args = line.split()
548 processed = []
549 for term in args:
550 try:
551 processed.append(int(term))
552 continue
553 except ValueError:
554 pass
555 try:
556 frac = float(term)
557 if frac > 1 or frac < 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000558 print("Fraction argument must be in [0, 1]", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000559 continue
560 processed.append(frac)
561 continue
562 except ValueError:
563 pass
564 processed.append(term)
565 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000566 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000567 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000568 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000569 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000570 def generic_help(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000571 print("Arguments may be:", file=self.stream)
572 print("* An integer maximum number of entries to print.", file=self.stream)
573 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
574 print(" what fraction of selected entries to print.", file=self.stream)
575 print("* A regular expression; only entries with function names", file=self.stream)
576 print(" that match it are printed.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000577
578 def do_add(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000579 if self.stats:
Stefan Krahe12a68b2016-08-02 22:30:24 +0200580 try:
581 self.stats.add(line)
582 except IOError as e:
583 print("Failed to load statistics for %s: %s" % (line, e), file=self.stream)
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000584 else:
585 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000586 return 0
587 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000588 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000589
590 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000591 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000592 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000593 print("Print callees 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_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000597 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000598 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000599 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000600 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000601
602 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000603 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000604 return 1
605 def help_EOF(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000606 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000607
608 def do_quit(self, line):
609 return 1
610 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000611 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000612
613 def do_read(self, line):
614 if line:
615 try:
616 self.stats = Stats(line)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200617 except OSError as err:
Georg Brandl50da60c2008-01-06 21:38:54 +0000618 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000619 return
Georg Brandlf02e7362010-08-01 07:57:47 +0000620 except Exception as err:
621 print(err.__class__.__name__ + ':', err, file=self.stream)
622 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000623 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000624 elif len(self.prompt) > 2:
Georg Brandlf02e7362010-08-01 07:57:47 +0000625 line = self.prompt[:-2]
626 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000627 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000628 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000629 return 0
630 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000631 print("Read in profile data from a specified file.", file=self.stream)
Georg Brandlf02e7362010-08-01 07:57:47 +0000632 print("Without argument, reload the current file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000633
634 def do_reverse(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000635 if self.stats:
636 self.stats.reverse_order()
637 else:
638 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000639 return 0
640 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000641 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000642
643 def do_sort(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000644 if not self.stats:
645 print("No statistics object is loaded.", file=self.stream)
646 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000647 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchling0a628232010-04-02 17:02:57 +0000648 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000649 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000650 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000651 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000652 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000653 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000654 return 0
655 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000656 print("Sort profile data according to specified keys.", file=self.stream)
657 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000658 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000659 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000660
661 def do_stats(self, line):
662 return self.generic('print_stats', line)
663 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000664 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000665 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000666
667 def do_strip(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000668 if self.stats:
669 self.stats.strip_dirs()
670 else:
671 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000672 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000673 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000674
Georg Brandlf02e7362010-08-01 07:57:47 +0000675 def help_help(self):
676 print("Show help for a given command.", file=self.stream)
677
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000678 def postcmd(self, stop, line):
679 if stop:
680 return stop
681 return None
682
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000683 if len(sys.argv) > 1:
684 initprofile = sys.argv[1]
685 else:
686 initprofile = None
687 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000688 browser = ProfileBrowser(initprofile)
Antoine Pitrou9d8c1862012-03-14 17:47:11 +0100689 for profile in sys.argv[2:]:
690 browser.do_add(profile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000691 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000692 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000693 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000694 except KeyboardInterrupt:
695 pass
696
697# That's all, folks.