blob: b7a20542a3994bef0fccb4b8b888d79d78a01afa [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
Matthias Bussonnier8fb1f6e2017-02-20 21:30:00 -080051 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
Jon Dufresne39726282017-05-18 07:35:54 -0700503 new_callers[func] = tuple(i[0] + i[1] for i in zip(caller, new_callers[func]))
Georg Brandl2d3c4e72010-08-02 17:24:49 +0000504 else:
505 # format used by profile
506 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000507 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000508 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000509 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000510
Guido van Rossumadb31051994-06-23 11:42:52 +0000511def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000512 """Sum the caller statistics to get total number of calls received."""
513 nc = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000514 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000515 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000516 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000517
518#**************************************************************************
519# The following functions support printing of reports
520#**************************************************************************
521
522def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000523 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000524
525#**************************************************************************
526# Statistics browser added by ESR, April 2001
527#**************************************************************************
528
529if __name__ == '__main__':
530 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000531 try:
532 import readline
Brett Cannoncd171c82013-07-04 17:43:24 -0400533 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000534 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000535
536 class ProfileBrowser(cmd.Cmd):
537 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000538 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000539 self.prompt = "% "
Georg Brandlb1a97af2010-08-02 12:06:18 +0000540 self.stats = None
541 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000542 if profile is not None:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000543 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000544
545 def generic(self, fn, line):
546 args = line.split()
547 processed = []
548 for term in args:
549 try:
550 processed.append(int(term))
551 continue
552 except ValueError:
553 pass
554 try:
555 frac = float(term)
556 if frac > 1 or frac < 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000557 print("Fraction argument must be in [0, 1]", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000558 continue
559 processed.append(frac)
560 continue
561 except ValueError:
562 pass
563 processed.append(term)
564 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000565 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000566 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000567 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000568 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000569 def generic_help(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000570 print("Arguments may be:", file=self.stream)
571 print("* An integer maximum number of entries to print.", file=self.stream)
572 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
573 print(" what fraction of selected entries to print.", file=self.stream)
574 print("* A regular expression; only entries with function names", file=self.stream)
575 print(" that match it are printed.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000576
577 def do_add(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000578 if self.stats:
Stefan Krahe12a68b2016-08-02 22:30:24 +0200579 try:
580 self.stats.add(line)
Serhiy Storchaka55fe1ae2017-04-16 10:46:38 +0300581 except OSError as e:
Stefan Krahe12a68b2016-08-02 22:30:24 +0200582 print("Failed to load statistics for %s: %s" % (line, e), file=self.stream)
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000583 else:
584 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000585 return 0
586 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000587 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000588
589 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000590 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000591 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000592 print("Print callees statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000593 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000594
595 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000596 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000597 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000598 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000599 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000600
601 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000602 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000603 return 1
604 def help_EOF(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_quit(self, line):
608 return 1
609 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000610 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000611
612 def do_read(self, line):
613 if line:
614 try:
615 self.stats = Stats(line)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200616 except OSError as err:
Georg Brandl50da60c2008-01-06 21:38:54 +0000617 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000618 return
Georg Brandlf02e7362010-08-01 07:57:47 +0000619 except Exception as err:
620 print(err.__class__.__name__ + ':', err, file=self.stream)
621 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000622 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000623 elif len(self.prompt) > 2:
Georg Brandlf02e7362010-08-01 07:57:47 +0000624 line = self.prompt[:-2]
625 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000626 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000627 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000628 return 0
629 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000630 print("Read in profile data from a specified file.", file=self.stream)
Georg Brandlf02e7362010-08-01 07:57:47 +0000631 print("Without argument, reload the current file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000632
633 def do_reverse(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000634 if self.stats:
635 self.stats.reverse_order()
636 else:
637 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000638 return 0
639 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000640 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000641
642 def do_sort(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000643 if not self.stats:
644 print("No statistics object is loaded.", file=self.stream)
645 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000646 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchling0a628232010-04-02 17:02:57 +0000647 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000648 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000649 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000650 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000651 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000652 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000653 return 0
654 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000655 print("Sort profile data according to specified keys.", file=self.stream)
656 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000657 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000658 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000659
660 def do_stats(self, line):
661 return self.generic('print_stats', line)
662 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000663 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000664 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000665
666 def do_strip(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000667 if self.stats:
668 self.stats.strip_dirs()
669 else:
670 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000671 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000672 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000673
Georg Brandlf02e7362010-08-01 07:57:47 +0000674 def help_help(self):
675 print("Show help for a given command.", file=self.stream)
676
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000677 def postcmd(self, stop, line):
678 if stop:
679 return stop
680 return None
681
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000682 if len(sys.argv) > 1:
683 initprofile = sys.argv[1]
684 else:
685 initprofile = None
686 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000687 browser = ProfileBrowser(initprofile)
Antoine Pitrou9d8c1862012-03-14 17:47:11 +0100688 for profile in sys.argv[2:]:
689 browser.do_add(profile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000690 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000691 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000692 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000693 except KeyboardInterrupt:
694 pass
695
696# That's all, folks.