blob: 1b57d26b5a5cddf3a4161b8ccbceedd4a9bee94a [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
mwidjaja863b1e42018-01-25 20:49:56 -080028from enum import Enum
Raymond Hettingerc50846a2010-04-05 18:56:31 +000029from functools import cmp_to_key
Guido van Rossumadb31051994-06-23 11:42:52 +000030
mwidjaja863b1e42018-01-25 20:49:56 -080031__all__ = ["Stats", "SortKey"]
32
33
34class SortKey(str, Enum):
35 CALLS = 'calls', 'ncalls'
36 CUMULATIVE = 'cumulative', 'cumtime'
37 FILENAME = 'filename', 'module'
38 LINE = 'line'
39 NAME = 'name'
40 NFL = 'nfl'
41 PCALLS = 'pcalls'
42 STDNAME = 'stdname'
43 TIME = 'time', 'tottime'
44
45 def __new__(cls, *values):
46 obj = str.__new__(cls)
47
48 obj._value_ = values[0]
49 for other_value in values[1:]:
50 cls._value2member_map_[other_value] = obj
51 obj._all_values = values
52 return obj
53
Skip Montanaroc62c81e2001-02-12 02:00:42 +000054
Guido van Rossumadb31051994-06-23 11:42:52 +000055class Stats:
Tim Peters2344fae2001-01-15 00:50:52 +000056 """This class is used for creating reports from data generated by the
57 Profile class. It is a "friend" of that class, and imports data either
58 by direct access to members of Profile class, or by reading in a dictionary
59 that was emitted (via marshal) from the Profile class.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000060
Tim Peters2344fae2001-01-15 00:50:52 +000061 The big change from the previous Profiler (in terms of raw functionality)
62 is that an "add()" method has been provided to combine Stats from
63 several distinct profile runs. Both the constructor and the add()
64 method now take arbitrarily many file names as arguments.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000065
Tim Peters2344fae2001-01-15 00:50:52 +000066 All the print methods now take an argument that indicates how many lines
67 to print. If the arg is a floating point number between 0 and 1.0, then
68 it is taken as a decimal percentage of the available lines to be printed
69 (e.g., .1 means print 10% of all available lines). If it is an integer,
70 it is taken to mean the number of lines of data that you wish to have
71 printed.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000072
Tim Peters2344fae2001-01-15 00:50:52 +000073 The sort_stats() method now processes some additional options (i.e., in
Matthias Bussonnier8fb1f6e2017-02-20 21:30:00 -080074 addition to the old -1, 0, 1, or 2 that are respectively interpreted as
mwidjaja863b1e42018-01-25 20:49:56 -080075 'stdname', 'calls', 'time', and 'cumulative'). It takes either an
76 arbitrary number of quoted strings or SortKey enum to select the sort
77 order.
Matthias Bussonnier8fb1f6e2017-02-20 21:30:00 -080078
mwidjaja863b1e42018-01-25 20:49:56 -080079 For example sort_stats('time', 'name') or sort_stats(SortKey.TIME,
80 SortKey.NAME) sorts on the major key of 'internal function time', and on
81 the minor key of 'the name of the function'. Look at the two tables in
82 sort_stats() and get_sort_arg_defs(self) for more examples.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000083
Georg Brandlb1a97af2010-08-02 12:06:18 +000084 All methods return self, so you can string together commands like:
Tim Peters2344fae2001-01-15 00:50:52 +000085 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
86 print_stats(5).print_callers(5)
87 """
88
Georg Brandl7837a962009-09-02 20:33:30 +000089 def __init__(self, *args, stream=None):
90 self.stream = stream or sys.stdout
Tim Peters2344fae2001-01-15 00:50:52 +000091 if not len(args):
92 arg = None
93 else:
94 arg = args[0]
95 args = args[1:]
96 self.init(arg)
Guido van Rossum68468eb2003-02-27 20:14:51 +000097 self.add(*args)
Tim Peters2344fae2001-01-15 00:50:52 +000098
99 def init(self, arg):
100 self.all_callees = None # calc only if needed
101 self.files = []
102 self.fcn_list = None
103 self.total_tt = 0
104 self.total_calls = 0
105 self.prim_calls = 0
106 self.max_name_len = 0
Georg Brandleb7e5692010-10-22 06:29:21 +0000107 self.top_level = set()
Tim Peters2344fae2001-01-15 00:50:52 +0000108 self.stats = {}
109 self.sort_arg_dict = {}
110 self.load_stats(arg)
Tim Peters2344fae2001-01-15 00:50:52 +0000111 try:
112 self.get_top_level_stats()
Georg Brandl9a8439d2010-10-22 06:35:59 +0000113 except Exception:
114 print("Invalid timing data %s" %
115 (self.files[-1] if self.files else ''), file=self.stream)
116 raise
Guido van Rossumadb31051994-06-23 11:42:52 +0000117
Tim Peters2344fae2001-01-15 00:50:52 +0000118 def load_stats(self, arg):
Georg Brandl83938432010-10-22 06:28:01 +0000119 if arg is None:
120 self.stats = {}
121 return
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000122 elif isinstance(arg, str):
Giampaolo Rodola'2f50aaf2013-02-12 02:04:27 +0100123 with open(arg, 'rb') as f:
124 self.stats = marshal.load(f)
Tim Peters2344fae2001-01-15 00:50:52 +0000125 try:
126 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000127 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000128 except: # in case this is not unix
129 pass
Georg Brandl83938432010-10-22 06:28:01 +0000130 self.files = [arg]
Tim Peters2344fae2001-01-15 00:50:52 +0000131 elif hasattr(arg, 'create_stats'):
132 arg.create_stats()
133 self.stats = arg.stats
134 arg.stats = {}
135 if not self.stats:
Georg Brandl83938432010-10-22 06:28:01 +0000136 raise TypeError("Cannot create or construct a %r object from %r"
Collin Winterce36ad82007-08-30 01:19:48 +0000137 % (self.__class__, arg))
Tim Peters2344fae2001-01-15 00:50:52 +0000138 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000139
Tim Peters2344fae2001-01-15 00:50:52 +0000140 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000141 for func, (cc, nc, tt, ct, callers) in self.stats.items():
142 self.total_calls += nc
143 self.prim_calls += cc
144 self.total_tt += tt
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000145 if ("jprofile", 0, "profiler") in callers:
Georg Brandleb7e5692010-10-22 06:29:21 +0000146 self.top_level.add(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000147 if len(func_std_string(func)) > self.max_name_len:
148 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000149
Tim Peters2344fae2001-01-15 00:50:52 +0000150 def add(self, *arg_list):
Georg Brandl83938432010-10-22 06:28:01 +0000151 if not arg_list:
152 return self
153 for item in reversed(arg_list):
154 if type(self) != type(item):
155 item = Stats(item)
156 self.files += item.files
157 self.total_calls += item.total_calls
158 self.prim_calls += item.prim_calls
159 self.total_tt += item.total_tt
160 for func in item.top_level:
Georg Brandleb7e5692010-10-22 06:29:21 +0000161 self.top_level.add(func)
Guido van Rossumadb31051994-06-23 11:42:52 +0000162
Georg Brandl83938432010-10-22 06:28:01 +0000163 if self.max_name_len < item.max_name_len:
164 self.max_name_len = item.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000165
Georg Brandl83938432010-10-22 06:28:01 +0000166 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000167
Georg Brandl83938432010-10-22 06:28:01 +0000168 for func, stat in item.stats.items():
169 if func in self.stats:
170 old_func_stat = self.stats[func]
171 else:
172 old_func_stat = (0, 0, 0, 0, {},)
173 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000174 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000175
Fred Drake9c439102003-05-14 14:28:09 +0000176 def dump_stats(self, filename):
177 """Write the profile data to a file we know how to load back."""
Giampaolo Rodola'2f50aaf2013-02-12 02:04:27 +0100178 with open(filename, 'wb') as f:
Fred Drake9c439102003-05-14 14:28:09 +0000179 marshal.dump(self.stats, f)
Fred Drake9c439102003-05-14 14:28:09 +0000180
Tim Peters2344fae2001-01-15 00:50:52 +0000181 # list the tuple indices and directions for sorting,
182 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000183 sort_arg_dict_default = {
184 "calls" : (((1,-1), ), "call count"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300185 "ncalls" : (((1,-1), ), "call count"),
186 "cumtime" : (((3,-1), ), "cumulative time"),
Tim Peters7d016852001-10-08 06:13:19 +0000187 "cumulative": (((3,-1), ), "cumulative time"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300188 "filename" : (((4, 1), ), "file name"),
Tim Peters7d016852001-10-08 06:13:19 +0000189 "line" : (((5, 1), ), "line number"),
190 "module" : (((4, 1), ), "file name"),
191 "name" : (((6, 1), ), "function name"),
192 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
Andrew Svetlov2ef45842012-10-07 18:58:42 +0300193 "pcalls" : (((0,-1), ), "primitive call count"),
Tim Peters7d016852001-10-08 06:13:19 +0000194 "stdname" : (((7, 1), ), "standard name"),
195 "time" : (((2,-1), ), "internal time"),
Andrew Svetlovc3e5b102012-10-07 19:18:39 +0300196 "tottime" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000197 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000198
Tim Peters2344fae2001-01-15 00:50:52 +0000199 def get_sort_arg_defs(self):
200 """Expand all abbreviations that are unique."""
201 if not self.sort_arg_dict:
202 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000203 bad_list = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000204 for word, tup in self.sort_arg_dict_default.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000205 fragment = word
206 while fragment:
207 if not fragment:
208 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000209 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000210 bad_list[fragment] = 0
211 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000212 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000213 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000214 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000215 del dict[word]
216 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000217
Tim Peters2344fae2001-01-15 00:50:52 +0000218 def sort_stats(self, *field):
219 if not field:
220 self.fcn_list = 0
221 return self
Georg Brandlb1a97af2010-08-02 12:06:18 +0000222 if len(field) == 1 and isinstance(field[0], int):
Tim Peters2344fae2001-01-15 00:50:52 +0000223 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000224 field = [ {-1: "stdname",
Georg Brandlb1a97af2010-08-02 12:06:18 +0000225 0: "calls",
226 1: "time",
227 2: "cumulative"}[field[0]] ]
mwidjaja863b1e42018-01-25 20:49:56 -0800228 elif len(field) >= 2:
229 for arg in field[1:]:
230 if type(arg) != type(field[0]):
231 raise TypeError("Can't have mixed argument type")
Tim Peters2344fae2001-01-15 00:50:52 +0000232
233 sort_arg_defs = self.get_sort_arg_defs()
mwidjaja863b1e42018-01-25 20:49:56 -0800234
Tim Peters2344fae2001-01-15 00:50:52 +0000235 sort_tuple = ()
236 self.sort_type = ""
237 connector = ""
238 for word in field:
mwidjaja863b1e42018-01-25 20:49:56 -0800239 if isinstance(word, SortKey):
240 word = word.value
Tim Peters2344fae2001-01-15 00:50:52 +0000241 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000242 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000243 connector = ", "
244
245 stats_list = []
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000246 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Tim Peters7d016852001-10-08 06:13:19 +0000247 stats_list.append((cc, nc, tt, ct) + func +
248 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000249
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000250 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000251
252 self.fcn_list = fcn_list = []
253 for tuple in stats_list:
254 fcn_list.append(tuple[-1])
255 return self
256
Tim Peters2344fae2001-01-15 00:50:52 +0000257 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000258 if self.fcn_list:
259 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000260 return self
261
262 def strip_dirs(self):
263 oldstats = self.stats
264 self.stats = newstats = {}
265 max_name_len = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000266 for func, (cc, nc, tt, ct, callers) in oldstats.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000267 newfunc = func_strip_path(func)
268 if len(func_std_string(newfunc)) > max_name_len:
269 max_name_len = len(func_std_string(newfunc))
270 newcallers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000271 for func2, caller in callers.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000272 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000273
Raymond Hettinger54f02222002-06-01 14:18:47 +0000274 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000275 newstats[newfunc] = add_func_stats(
276 newstats[newfunc],
277 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000278 else:
279 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
280 old_top = self.top_level
Georg Brandleb7e5692010-10-22 06:29:21 +0000281 self.top_level = new_top = set()
Raymond Hettingere0d49722002-06-02 18:55:56 +0000282 for func in old_top:
Georg Brandleb7e5692010-10-22 06:29:21 +0000283 new_top.add(func_strip_path(func))
Tim Peters2344fae2001-01-15 00:50:52 +0000284
285 self.max_name_len = max_name_len
286
287 self.fcn_list = None
288 self.all_callees = None
289 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000290
Tim Peters2344fae2001-01-15 00:50:52 +0000291 def calc_callees(self):
Georg Brandl9a8439d2010-10-22 06:35:59 +0000292 if self.all_callees:
293 return
Tim Peters2344fae2001-01-15 00:50:52 +0000294 self.all_callees = all_callees = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000295 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000296 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000297 all_callees[func] = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000298 for func2, caller in callers.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000299 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000300 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000301 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000302 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000303
Tim Peters2344fae2001-01-15 00:50:52 +0000304 #******************************************************************
305 # The following functions support actual printing of reports
306 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000307
Tim Peters2344fae2001-01-15 00:50:52 +0000308 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000309
Tim Peters2344fae2001-01-15 00:50:52 +0000310 def eval_print_amount(self, sel, list, msg):
311 new_list = list
Georg Brandlb1a97af2010-08-02 12:06:18 +0000312 if isinstance(sel, str):
313 try:
314 rex = re.compile(sel)
315 except re.error:
316 msg += " <Invalid regular expression %r>\n" % sel
317 return new_list, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000318 new_list = []
319 for func in list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000320 if rex.search(func_std_string(func)):
Tim Peters2344fae2001-01-15 00:50:52 +0000321 new_list.append(func)
322 else:
323 count = len(list)
Georg Brandlb1a97af2010-08-02 12:06:18 +0000324 if isinstance(sel, float) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000325 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000326 new_list = list[:count]
Georg Brandlb1a97af2010-08-02 12:06:18 +0000327 elif isinstance(sel, int) and 0 <= sel < count:
Tim Peters2344fae2001-01-15 00:50:52 +0000328 count = sel
329 new_list = list[:count]
330 if len(list) != len(new_list):
Georg Brandlb1a97af2010-08-02 12:06:18 +0000331 msg += " List reduced from %r to %r due to restriction <%r>\n" % (
332 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000333
Tim Peters2344fae2001-01-15 00:50:52 +0000334 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000335
Tim Peters2344fae2001-01-15 00:50:52 +0000336 def get_print_list(self, sel_list):
337 width = self.max_name_len
338 if self.fcn_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000339 stat_list = self.fcn_list[:]
Tim Peters2344fae2001-01-15 00:50:52 +0000340 msg = " Ordered by: " + self.sort_type + '\n'
341 else:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000342 stat_list = list(self.stats.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000343 msg = " Random listing order was used\n"
344
345 for selection in sel_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000346 stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000347
Georg Brandlb1a97af2010-08-02 12:06:18 +0000348 count = len(stat_list)
Tim Peters2344fae2001-01-15 00:50:52 +0000349
Georg Brandlb1a97af2010-08-02 12:06:18 +0000350 if not stat_list:
351 return 0, stat_list
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000352 print(msg, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000353 if count < len(self.stats):
354 width = 0
Georg Brandlb1a97af2010-08-02 12:06:18 +0000355 for func in stat_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000356 if len(func_std_string(func)) > width:
357 width = len(func_std_string(func))
Georg Brandlb1a97af2010-08-02 12:06:18 +0000358 return width+2, stat_list
Tim Peters2344fae2001-01-15 00:50:52 +0000359
360 def print_stats(self, *amount):
361 for filename in self.files:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000362 print(filename, file=self.stream)
Georg Brandl9a8439d2010-10-22 06:35:59 +0000363 if self.files:
364 print(file=self.stream)
Tim Peters7d016852001-10-08 06:13:19 +0000365 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000366 for func in self.top_level:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000367 print(indent, func_get_function_name(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000368
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000369 print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000370 if self.total_calls != self.prim_calls:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000371 print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
Senthil Kumaran5e703cf2010-11-20 17:02:50 +0000372 print("in %.3f seconds" % self.total_tt, file=self.stream)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000373 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000374 width, list = self.get_print_list(amount)
375 if list:
376 self.print_title()
377 for func in list:
378 self.print_line(func)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000379 print(file=self.stream)
380 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000381 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000382
Tim Peters2344fae2001-01-15 00:50:52 +0000383 def print_callees(self, *amount):
384 width, list = self.get_print_list(amount)
385 if list:
386 self.calc_callees()
387
388 self.print_call_heading(width, "called...")
389 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000390 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000391 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000392 else:
393 self.print_call_line(width, func, {})
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000394 print(file=self.stream)
395 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000396 return self
397
398 def print_callers(self, *amount):
399 width, list = self.get_print_list(amount)
400 if list:
401 self.print_call_heading(width, "was called by...")
402 for func in list:
403 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000404 self.print_call_line(width, func, callers, "<-")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000405 print(file=self.stream)
406 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000407 return self
408
409 def print_call_heading(self, name_size, column_title):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000410 print("Function ".ljust(name_size) + column_title, file=self.stream)
Armin Rigoa871ef22006-02-08 12:53:56 +0000411 # print sub-header only if we have new-style callers
412 subheader = False
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000413 for cc, nc, tt, ct, callers in self.stats.values():
Armin Rigoa871ef22006-02-08 12:53:56 +0000414 if callers:
Georg Brandla18af4e2007-04-21 15:47:16 +0000415 value = next(iter(callers.values()))
Armin Rigoa871ef22006-02-08 12:53:56 +0000416 subheader = isinstance(value, tuple)
417 break
418 if subheader:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000419 print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000420
Armin Rigoa871ef22006-02-08 12:53:56 +0000421 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000422 print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000423 if not call_dict:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000424 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000425 return
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000426 clist = sorted(call_dict.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000427 indent = ""
428 for func in clist:
429 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000430 value = call_dict[func]
431 if isinstance(value, tuple):
432 nc, cc, tt, ct = value
433 if nc != cc:
434 substats = '%d/%d' % (nc, cc)
435 else:
436 substats = '%d' % (nc,)
437 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
438 f8(tt), f8(ct), name)
439 left_width = name_size + 1
440 else:
441 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
442 left_width = name_size + 3
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000443 print(indent*left_width + substats, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000444 indent = " "
445
Tim Peters2344fae2001-01-15 00:50:52 +0000446 def print_title(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000447 print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
448 print('filename:lineno(function)', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000449
Georg Brandl9a8439d2010-10-22 06:35:59 +0000450 def print_line(self, func): # hack: should print percentages
Tim Peters2344fae2001-01-15 00:50:52 +0000451 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000452 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000453 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000454 c = c + '/' + str(cc)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000455 print(c.rjust(9), end=' ', file=self.stream)
456 print(f8(tt), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000457 if nc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000458 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000459 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000460 print(f8(tt/nc), end=' ', file=self.stream)
461 print(f8(ct), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000462 if cc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000463 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000464 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000465 print(f8(ct/cc), end=' ', file=self.stream)
466 print(func_std_string(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000467
Guido van Rossumadb31051994-06-23 11:42:52 +0000468class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000469 """This class provides a generic function for comparing any two tuples.
470 Each instance records a list of tuple-indices (from most significant
471 to least significant), and sort direction (ascending or decending) for
472 each tuple-index. The compare functions can then be used as the function
473 argument to the system sort() function when a list of tuples need to be
474 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000475
Tim Peters2344fae2001-01-15 00:50:52 +0000476 def __init__(self, comp_select_list):
477 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000478
Tim Peters2344fae2001-01-15 00:50:52 +0000479 def compare (self, left, right):
480 for index, direction in self.comp_select_list:
481 l = left[index]
482 r = right[index]
483 if l < r:
484 return -direction
485 if l > r:
486 return direction
487 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000488
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000489
Guido van Rossumadb31051994-06-23 11:42:52 +0000490#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000491# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000492
493def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000494 filename, line, name = func_name
495 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000496
497def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000498 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000499
500def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000501 if func_name[:2] == ('~', 0):
502 # special case for built-in functions
503 name = func_name[2]
504 if name.startswith('<') and name.endswith('>'):
505 return '{%s}' % name[1:-1]
506 else:
507 return name
508 else:
509 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000510
511#**************************************************************************
512# The following functions combine statists for pairs functions.
513# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000514# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000515#**************************************************************************
516
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000517def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000518 """Add together all the stats for two profile entries."""
519 cc, nc, tt, ct, callers = source
520 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000521 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000522 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000523
Guido van Rossumadb31051994-06-23 11:42:52 +0000524def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000525 """Combine two caller lists in a single list."""
526 new_callers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000527 for func, caller in target.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000528 new_callers[func] = caller
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000529 for func, caller in source.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000530 if func in new_callers:
Georg Brandl2d3c4e72010-08-02 17:24:49 +0000531 if isinstance(caller, tuple):
532 # format used by cProfile
Jon Dufresne39726282017-05-18 07:35:54 -0700533 new_callers[func] = tuple(i[0] + i[1] for i in zip(caller, new_callers[func]))
Georg Brandl2d3c4e72010-08-02 17:24:49 +0000534 else:
535 # format used by profile
536 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000537 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000538 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000539 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000540
Guido van Rossumadb31051994-06-23 11:42:52 +0000541def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000542 """Sum the caller statistics to get total number of calls received."""
543 nc = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000544 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000545 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000546 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000547
548#**************************************************************************
549# The following functions support printing of reports
550#**************************************************************************
551
552def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000553 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000554
555#**************************************************************************
556# Statistics browser added by ESR, April 2001
557#**************************************************************************
558
559if __name__ == '__main__':
560 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000561 try:
562 import readline
Brett Cannoncd171c82013-07-04 17:43:24 -0400563 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000564 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000565
566 class ProfileBrowser(cmd.Cmd):
567 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000568 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000569 self.prompt = "% "
Georg Brandlb1a97af2010-08-02 12:06:18 +0000570 self.stats = None
571 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000572 if profile is not None:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000573 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000574
575 def generic(self, fn, line):
576 args = line.split()
577 processed = []
578 for term in args:
579 try:
580 processed.append(int(term))
581 continue
582 except ValueError:
583 pass
584 try:
585 frac = float(term)
586 if frac > 1 or frac < 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000587 print("Fraction argument must be in [0, 1]", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000588 continue
589 processed.append(frac)
590 continue
591 except ValueError:
592 pass
593 processed.append(term)
594 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000595 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000596 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000597 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000598 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000599 def generic_help(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000600 print("Arguments may be:", file=self.stream)
601 print("* An integer maximum number of entries to print.", file=self.stream)
602 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
603 print(" what fraction of selected entries to print.", file=self.stream)
604 print("* A regular expression; only entries with function names", file=self.stream)
605 print(" that match it are printed.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000606
607 def do_add(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000608 if self.stats:
Stefan Krahe12a68b2016-08-02 22:30:24 +0200609 try:
610 self.stats.add(line)
Serhiy Storchaka55fe1ae2017-04-16 10:46:38 +0300611 except OSError as e:
Stefan Krahe12a68b2016-08-02 22:30:24 +0200612 print("Failed to load statistics for %s: %s" % (line, e), file=self.stream)
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000613 else:
614 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000615 return 0
616 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000617 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000618
619 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000620 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000621 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000622 print("Print callees statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000623 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000624
625 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000626 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000627 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000628 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000629 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000630
631 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000632 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000633 return 1
634 def help_EOF(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000635 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000636
637 def do_quit(self, line):
638 return 1
639 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000640 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000641
642 def do_read(self, line):
643 if line:
644 try:
645 self.stats = Stats(line)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200646 except OSError as err:
Georg Brandl50da60c2008-01-06 21:38:54 +0000647 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000648 return
Georg Brandlf02e7362010-08-01 07:57:47 +0000649 except Exception as err:
650 print(err.__class__.__name__ + ':', err, file=self.stream)
651 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000652 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000653 elif len(self.prompt) > 2:
Georg Brandlf02e7362010-08-01 07:57:47 +0000654 line = self.prompt[:-2]
655 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000656 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000657 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000658 return 0
659 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000660 print("Read in profile data from a specified file.", file=self.stream)
Georg Brandlf02e7362010-08-01 07:57:47 +0000661 print("Without argument, reload the current file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000662
663 def do_reverse(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000664 if self.stats:
665 self.stats.reverse_order()
666 else:
667 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000668 return 0
669 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000670 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000671
672 def do_sort(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000673 if not self.stats:
674 print("No statistics object is loaded.", file=self.stream)
675 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000676 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchling0a628232010-04-02 17:02:57 +0000677 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000678 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000679 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000680 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000681 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000682 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000683 return 0
684 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000685 print("Sort profile data according to specified keys.", file=self.stream)
686 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000687 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000688 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000689
690 def do_stats(self, line):
691 return self.generic('print_stats', line)
692 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000693 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000694 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000695
696 def do_strip(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000697 if self.stats:
698 self.stats.strip_dirs()
699 else:
700 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000701 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000702 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000703
Georg Brandlf02e7362010-08-01 07:57:47 +0000704 def help_help(self):
705 print("Show help for a given command.", file=self.stream)
706
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000707 def postcmd(self, stop, line):
708 if stop:
709 return stop
710 return None
711
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000712 if len(sys.argv) > 1:
713 initprofile = sys.argv[1]
714 else:
715 initprofile = None
716 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000717 browser = ProfileBrowser(initprofile)
Antoine Pitrou9d8c1862012-03-14 17:47:11 +0100718 for profile in sys.argv[2:]:
719 browser.do_add(profile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000720 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000721 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000722 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000723 except KeyboardInterrupt:
724 pass
725
726# That's all, folks.