blob: f5c592b81ba2a29359d0cd84dc97d556a47cd11a [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""Class for printing reports on profiled python code."""
2
Guido van Rossumadb31051994-06-23 11:42:52 +00003# Class for printing reports on profiled python code. rev 1.0 4/1/94
4#
5# Based on prior profile module by Sjoerd Mullender...
6# which was hacked somewhat by: Guido van Rossum
7#
Georg Brandlb1a97af2010-08-02 12:06:18 +00008# see profile.py for more info.
Guido van Rossumadb31051994-06-23 11:42:52 +00009
10# Copyright 1994, by InfoSeek Corporation, all rights reserved.
11# Written by James Roskind
Tim Peters2344fae2001-01-15 00:50:52 +000012#
Guido van Rossumadb31051994-06-23 11:42:52 +000013# Permission to use, copy, modify, and distribute this Python software
14# and its associated documentation for any purpose (subject to the
15# restriction in the following sentence) without fee is hereby granted,
16# provided that the above copyright notice appears in all copies, and
17# that both that copyright notice and this permission notice appear in
18# supporting documentation, and that the name of InfoSeek not be used in
19# advertising or publicity pertaining to distribution of the software
20# without specific, written prior permission. This permission is
21# explicitly restricted to the copying and modification of the software
22# to remain in Python, compiled Python, or other languages (such as C)
23# wherein the modified or derived code is exclusively imported into a
24# Python module.
Tim Peters2344fae2001-01-15 00:50:52 +000025#
Guido van Rossumadb31051994-06-23 11:42:52 +000026# INFOSEEK CORPORATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
27# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
28# FITNESS. IN NO EVENT SHALL INFOSEEK CORPORATION BE LIABLE FOR ANY
29# SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
30# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
31# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
32# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
33
34
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000035import sys
Guido van Rossumadb31051994-06-23 11:42:52 +000036import os
37import time
Guido van Rossumadb31051994-06-23 11:42:52 +000038import marshal
Guido van Rossum9694fca1997-10-22 21:00:49 +000039import re
Raymond Hettingerc50846a2010-04-05 18:56:31 +000040from functools import cmp_to_key
Guido van Rossumadb31051994-06-23 11:42:52 +000041
Skip Montanaroc62c81e2001-02-12 02:00:42 +000042__all__ = ["Stats"]
43
Guido van Rossumadb31051994-06-23 11:42:52 +000044class Stats:
Tim Peters2344fae2001-01-15 00:50:52 +000045 """This class is used for creating reports from data generated by the
46 Profile class. It is a "friend" of that class, and imports data either
47 by direct access to members of Profile class, or by reading in a dictionary
48 that was emitted (via marshal) from the Profile class.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000049
Tim Peters2344fae2001-01-15 00:50:52 +000050 The big change from the previous Profiler (in terms of raw functionality)
51 is that an "add()" method has been provided to combine Stats from
52 several distinct profile runs. Both the constructor and the add()
53 method now take arbitrarily many file names as arguments.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000054
Tim Peters2344fae2001-01-15 00:50:52 +000055 All the print methods now take an argument that indicates how many lines
56 to print. If the arg is a floating point number between 0 and 1.0, then
57 it is taken as a decimal percentage of the available lines to be printed
58 (e.g., .1 means print 10% of all available lines). If it is an integer,
59 it is taken to mean the number of lines of data that you wish to have
60 printed.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000061
Tim Peters2344fae2001-01-15 00:50:52 +000062 The sort_stats() method now processes some additional options (i.e., in
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000063 addition to the old -1, 0, 1, or 2). It takes an arbitrary number of
64 quoted strings to select the sort order. For example sort_stats('time',
65 'name') sorts on the major key of 'internal function time', and on the
66 minor key of 'the name of the function'. Look at the two tables in
67 sort_stats() and get_sort_arg_defs(self) for more examples.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000068
Georg Brandlb1a97af2010-08-02 12:06:18 +000069 All methods return self, so you can string together commands like:
Tim Peters2344fae2001-01-15 00:50:52 +000070 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
71 print_stats(5).print_callers(5)
72 """
73
Georg Brandl7837a962009-09-02 20:33:30 +000074 def __init__(self, *args, stream=None):
75 self.stream = stream or sys.stdout
Tim Peters2344fae2001-01-15 00:50:52 +000076 if not len(args):
77 arg = None
78 else:
79 arg = args[0]
80 args = args[1:]
81 self.init(arg)
Guido van Rossum68468eb2003-02-27 20:14:51 +000082 self.add(*args)
Tim Peters2344fae2001-01-15 00:50:52 +000083
84 def init(self, arg):
85 self.all_callees = None # calc only if needed
86 self.files = []
87 self.fcn_list = None
88 self.total_tt = 0
89 self.total_calls = 0
90 self.prim_calls = 0
91 self.max_name_len = 0
Georg Brandleb7e5692010-10-22 06:29:21 +000092 self.top_level = set()
Tim Peters2344fae2001-01-15 00:50:52 +000093 self.stats = {}
94 self.sort_arg_dict = {}
95 self.load_stats(arg)
Tim Peters2344fae2001-01-15 00:50:52 +000096 try:
97 self.get_top_level_stats()
Georg Brandl9a8439d2010-10-22 06:35:59 +000098 except Exception:
99 print("Invalid timing data %s" %
100 (self.files[-1] if self.files else ''), file=self.stream)
101 raise
Guido van Rossumadb31051994-06-23 11:42:52 +0000102
Tim Peters2344fae2001-01-15 00:50:52 +0000103 def load_stats(self, arg):
Georg Brandl83938432010-10-22 06:28:01 +0000104 if arg is None:
105 self.stats = {}
106 return
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000107 elif isinstance(arg, str):
Tim Peters2344fae2001-01-15 00:50:52 +0000108 f = open(arg, 'rb')
109 self.stats = marshal.load(f)
110 f.close()
111 try:
112 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000113 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000114 except: # in case this is not unix
115 pass
Georg Brandl83938432010-10-22 06:28:01 +0000116 self.files = [arg]
Tim Peters2344fae2001-01-15 00:50:52 +0000117 elif hasattr(arg, 'create_stats'):
118 arg.create_stats()
119 self.stats = arg.stats
120 arg.stats = {}
121 if not self.stats:
Georg Brandl83938432010-10-22 06:28:01 +0000122 raise TypeError("Cannot create or construct a %r object from %r"
Collin Winterce36ad82007-08-30 01:19:48 +0000123 % (self.__class__, arg))
Tim Peters2344fae2001-01-15 00:50:52 +0000124 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000125
Tim Peters2344fae2001-01-15 00:50:52 +0000126 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000127 for func, (cc, nc, tt, ct, callers) in self.stats.items():
128 self.total_calls += nc
129 self.prim_calls += cc
130 self.total_tt += tt
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000131 if ("jprofile", 0, "profiler") in callers:
Georg Brandleb7e5692010-10-22 06:29:21 +0000132 self.top_level.add(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000133 if len(func_std_string(func)) > self.max_name_len:
134 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000135
Tim Peters2344fae2001-01-15 00:50:52 +0000136 def add(self, *arg_list):
Georg Brandl83938432010-10-22 06:28:01 +0000137 if not arg_list:
138 return self
139 for item in reversed(arg_list):
140 if type(self) != type(item):
141 item = Stats(item)
142 self.files += item.files
143 self.total_calls += item.total_calls
144 self.prim_calls += item.prim_calls
145 self.total_tt += item.total_tt
146 for func in item.top_level:
Georg Brandleb7e5692010-10-22 06:29:21 +0000147 self.top_level.add(func)
Guido van Rossumadb31051994-06-23 11:42:52 +0000148
Georg Brandl83938432010-10-22 06:28:01 +0000149 if self.max_name_len < item.max_name_len:
150 self.max_name_len = item.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000151
Georg Brandl83938432010-10-22 06:28:01 +0000152 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000153
Georg Brandl83938432010-10-22 06:28:01 +0000154 for func, stat in item.stats.items():
155 if func in self.stats:
156 old_func_stat = self.stats[func]
157 else:
158 old_func_stat = (0, 0, 0, 0, {},)
159 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000160 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000161
Fred Drake9c439102003-05-14 14:28:09 +0000162 def dump_stats(self, filename):
163 """Write the profile data to a file we know how to load back."""
Alex Martelli01c77c62006-08-24 02:58:11 +0000164 f = open(filename, 'wb')
Fred Drake9c439102003-05-14 14:28:09 +0000165 try:
166 marshal.dump(self.stats, f)
167 finally:
168 f.close()
169
Tim Peters2344fae2001-01-15 00:50:52 +0000170 # list the tuple indices and directions for sorting,
171 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000172 sort_arg_dict_default = {
173 "calls" : (((1,-1), ), "call count"),
174 "cumulative": (((3,-1), ), "cumulative time"),
175 "file" : (((4, 1), ), "file name"),
176 "line" : (((5, 1), ), "line number"),
177 "module" : (((4, 1), ), "file name"),
178 "name" : (((6, 1), ), "function name"),
179 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
180 "pcalls" : (((0,-1), ), "call count"),
181 "stdname" : (((7, 1), ), "standard name"),
182 "time" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000183 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000184
Tim Peters2344fae2001-01-15 00:50:52 +0000185 def get_sort_arg_defs(self):
186 """Expand all abbreviations that are unique."""
187 if not self.sort_arg_dict:
188 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000189 bad_list = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000190 for word, tup in self.sort_arg_dict_default.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000191 fragment = word
192 while fragment:
193 if not fragment:
194 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000195 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000196 bad_list[fragment] = 0
197 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000198 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000199 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000200 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000201 del dict[word]
202 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000203
Tim Peters2344fae2001-01-15 00:50:52 +0000204 def sort_stats(self, *field):
205 if not field:
206 self.fcn_list = 0
207 return self
Georg Brandlb1a97af2010-08-02 12:06:18 +0000208 if len(field) == 1 and isinstance(field[0], int):
Tim Peters2344fae2001-01-15 00:50:52 +0000209 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000210 field = [ {-1: "stdname",
Georg Brandlb1a97af2010-08-02 12:06:18 +0000211 0: "calls",
212 1: "time",
213 2: "cumulative"}[field[0]] ]
Tim Peters2344fae2001-01-15 00:50:52 +0000214
215 sort_arg_defs = self.get_sort_arg_defs()
216 sort_tuple = ()
217 self.sort_type = ""
218 connector = ""
219 for word in field:
220 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000221 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000222 connector = ", "
223
224 stats_list = []
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000225 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Tim Peters7d016852001-10-08 06:13:19 +0000226 stats_list.append((cc, nc, tt, ct) + func +
227 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000228
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000229 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000230
231 self.fcn_list = fcn_list = []
232 for tuple in stats_list:
233 fcn_list.append(tuple[-1])
234 return self
235
Tim Peters2344fae2001-01-15 00:50:52 +0000236 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000237 if self.fcn_list:
238 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000239 return self
240
241 def strip_dirs(self):
242 oldstats = self.stats
243 self.stats = newstats = {}
244 max_name_len = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000245 for func, (cc, nc, tt, ct, callers) in oldstats.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000246 newfunc = func_strip_path(func)
247 if len(func_std_string(newfunc)) > max_name_len:
248 max_name_len = len(func_std_string(newfunc))
249 newcallers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000250 for func2, caller in callers.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000251 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000252
Raymond Hettinger54f02222002-06-01 14:18:47 +0000253 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000254 newstats[newfunc] = add_func_stats(
255 newstats[newfunc],
256 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000257 else:
258 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
259 old_top = self.top_level
Georg Brandleb7e5692010-10-22 06:29:21 +0000260 self.top_level = new_top = set()
Raymond Hettingere0d49722002-06-02 18:55:56 +0000261 for func in old_top:
Georg Brandleb7e5692010-10-22 06:29:21 +0000262 new_top.add(func_strip_path(func))
Tim Peters2344fae2001-01-15 00:50:52 +0000263
264 self.max_name_len = max_name_len
265
266 self.fcn_list = None
267 self.all_callees = None
268 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000269
Tim Peters2344fae2001-01-15 00:50:52 +0000270 def calc_callees(self):
Georg Brandl9a8439d2010-10-22 06:35:59 +0000271 if self.all_callees:
272 return
Tim Peters2344fae2001-01-15 00:50:52 +0000273 self.all_callees = all_callees = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000274 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000275 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000276 all_callees[func] = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000277 for func2, caller in callers.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000278 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000279 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000280 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000281 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000282
Tim Peters2344fae2001-01-15 00:50:52 +0000283 #******************************************************************
284 # The following functions support actual printing of reports
285 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000286
Tim Peters2344fae2001-01-15 00:50:52 +0000287 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000288
Tim Peters2344fae2001-01-15 00:50:52 +0000289 def eval_print_amount(self, sel, list, msg):
290 new_list = list
Georg Brandlb1a97af2010-08-02 12:06:18 +0000291 if isinstance(sel, str):
292 try:
293 rex = re.compile(sel)
294 except re.error:
295 msg += " <Invalid regular expression %r>\n" % sel
296 return new_list, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000297 new_list = []
298 for func in list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000299 if rex.search(func_std_string(func)):
Tim Peters2344fae2001-01-15 00:50:52 +0000300 new_list.append(func)
301 else:
302 count = len(list)
Georg Brandlb1a97af2010-08-02 12:06:18 +0000303 if isinstance(sel, float) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000304 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000305 new_list = list[:count]
Georg Brandlb1a97af2010-08-02 12:06:18 +0000306 elif isinstance(sel, int) and 0 <= sel < count:
Tim Peters2344fae2001-01-15 00:50:52 +0000307 count = sel
308 new_list = list[:count]
309 if len(list) != len(new_list):
Georg Brandlb1a97af2010-08-02 12:06:18 +0000310 msg += " List reduced from %r to %r due to restriction <%r>\n" % (
311 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000312
Tim Peters2344fae2001-01-15 00:50:52 +0000313 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000314
Tim Peters2344fae2001-01-15 00:50:52 +0000315 def get_print_list(self, sel_list):
316 width = self.max_name_len
317 if self.fcn_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000318 stat_list = self.fcn_list[:]
Tim Peters2344fae2001-01-15 00:50:52 +0000319 msg = " Ordered by: " + self.sort_type + '\n'
320 else:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000321 stat_list = list(self.stats.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000322 msg = " Random listing order was used\n"
323
324 for selection in sel_list:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000325 stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000326
Georg Brandlb1a97af2010-08-02 12:06:18 +0000327 count = len(stat_list)
Tim Peters2344fae2001-01-15 00:50:52 +0000328
Georg Brandlb1a97af2010-08-02 12:06:18 +0000329 if not stat_list:
330 return 0, stat_list
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000331 print(msg, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000332 if count < len(self.stats):
333 width = 0
Georg Brandlb1a97af2010-08-02 12:06:18 +0000334 for func in stat_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000335 if len(func_std_string(func)) > width:
336 width = len(func_std_string(func))
Georg Brandlb1a97af2010-08-02 12:06:18 +0000337 return width+2, stat_list
Tim Peters2344fae2001-01-15 00:50:52 +0000338
339 def print_stats(self, *amount):
340 for filename in self.files:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000341 print(filename, file=self.stream)
Georg Brandl9a8439d2010-10-22 06:35:59 +0000342 if self.files:
343 print(file=self.stream)
Tim Peters7d016852001-10-08 06:13:19 +0000344 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000345 for func in self.top_level:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000346 print(indent, func_get_function_name(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000347
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000348 print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000349 if self.total_calls != self.prim_calls:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000350 print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
Senthil Kumaran5e703cf2010-11-20 17:02:50 +0000351 print("in %.3f seconds" % self.total_tt, file=self.stream)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000352 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000353 width, list = self.get_print_list(amount)
354 if list:
355 self.print_title()
356 for func in list:
357 self.print_line(func)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000358 print(file=self.stream)
359 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000360 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000361
Tim Peters2344fae2001-01-15 00:50:52 +0000362 def print_callees(self, *amount):
363 width, list = self.get_print_list(amount)
364 if list:
365 self.calc_callees()
366
367 self.print_call_heading(width, "called...")
368 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000369 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000370 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000371 else:
372 self.print_call_line(width, func, {})
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000373 print(file=self.stream)
374 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000375 return self
376
377 def print_callers(self, *amount):
378 width, list = self.get_print_list(amount)
379 if list:
380 self.print_call_heading(width, "was called by...")
381 for func in list:
382 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000383 self.print_call_line(width, func, callers, "<-")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000384 print(file=self.stream)
385 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000386 return self
387
388 def print_call_heading(self, name_size, column_title):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000389 print("Function ".ljust(name_size) + column_title, file=self.stream)
Armin Rigoa871ef22006-02-08 12:53:56 +0000390 # print sub-header only if we have new-style callers
391 subheader = False
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000392 for cc, nc, tt, ct, callers in self.stats.values():
Armin Rigoa871ef22006-02-08 12:53:56 +0000393 if callers:
Georg Brandla18af4e2007-04-21 15:47:16 +0000394 value = next(iter(callers.values()))
Armin Rigoa871ef22006-02-08 12:53:56 +0000395 subheader = isinstance(value, tuple)
396 break
397 if subheader:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000398 print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000399
Armin Rigoa871ef22006-02-08 12:53:56 +0000400 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000401 print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000402 if not call_dict:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000403 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000404 return
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000405 clist = sorted(call_dict.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000406 indent = ""
407 for func in clist:
408 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000409 value = call_dict[func]
410 if isinstance(value, tuple):
411 nc, cc, tt, ct = value
412 if nc != cc:
413 substats = '%d/%d' % (nc, cc)
414 else:
415 substats = '%d' % (nc,)
416 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
417 f8(tt), f8(ct), name)
418 left_width = name_size + 1
419 else:
420 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
421 left_width = name_size + 3
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000422 print(indent*left_width + substats, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000423 indent = " "
424
Tim Peters2344fae2001-01-15 00:50:52 +0000425 def print_title(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000426 print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
427 print('filename:lineno(function)', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000428
Georg Brandl9a8439d2010-10-22 06:35:59 +0000429 def print_line(self, func): # hack: should print percentages
Tim Peters2344fae2001-01-15 00:50:52 +0000430 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000431 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000432 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000433 c = c + '/' + str(cc)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000434 print(c.rjust(9), end=' ', file=self.stream)
435 print(f8(tt), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000436 if nc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000437 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000438 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000439 print(f8(tt/nc), end=' ', file=self.stream)
440 print(f8(ct), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000441 if cc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000442 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000443 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000444 print(f8(ct/cc), end=' ', file=self.stream)
445 print(func_std_string(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000446
Guido van Rossumadb31051994-06-23 11:42:52 +0000447class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000448 """This class provides a generic function for comparing any two tuples.
449 Each instance records a list of tuple-indices (from most significant
450 to least significant), and sort direction (ascending or decending) for
451 each tuple-index. The compare functions can then be used as the function
452 argument to the system sort() function when a list of tuples need to be
453 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000454
Tim Peters2344fae2001-01-15 00:50:52 +0000455 def __init__(self, comp_select_list):
456 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000457
Tim Peters2344fae2001-01-15 00:50:52 +0000458 def compare (self, left, right):
459 for index, direction in self.comp_select_list:
460 l = left[index]
461 r = right[index]
462 if l < r:
463 return -direction
464 if l > r:
465 return direction
466 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000467
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000468
Guido van Rossumadb31051994-06-23 11:42:52 +0000469#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000470# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000471
472def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000473 filename, line, name = func_name
474 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000475
476def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000477 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000478
479def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000480 if func_name[:2] == ('~', 0):
481 # special case for built-in functions
482 name = func_name[2]
483 if name.startswith('<') and name.endswith('>'):
484 return '{%s}' % name[1:-1]
485 else:
486 return name
487 else:
488 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000489
490#**************************************************************************
491# The following functions combine statists for pairs functions.
492# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000493# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000494#**************************************************************************
495
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000496def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000497 """Add together all the stats for two profile entries."""
498 cc, nc, tt, ct, callers = source
499 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000500 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000501 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000502
Guido van Rossumadb31051994-06-23 11:42:52 +0000503def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000504 """Combine two caller lists in a single list."""
505 new_callers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000506 for func, caller in target.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000507 new_callers[func] = caller
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000508 for func, caller in source.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000509 if func in new_callers:
Georg Brandl2d3c4e72010-08-02 17:24:49 +0000510 if isinstance(caller, tuple):
511 # format used by cProfile
512 new_callers[func] = tuple([i[0] + i[1] for i in
513 zip(caller, new_callers[func])])
514 else:
515 # format used by profile
516 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000517 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000518 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000519 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000520
Guido van Rossumadb31051994-06-23 11:42:52 +0000521def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000522 """Sum the caller statistics to get total number of calls received."""
523 nc = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000524 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000525 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000526 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000527
528#**************************************************************************
529# The following functions support printing of reports
530#**************************************************************************
531
532def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000533 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000534
535#**************************************************************************
536# Statistics browser added by ESR, April 2001
537#**************************************************************************
538
539if __name__ == '__main__':
540 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000541 try:
542 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000543 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000544 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000545
546 class ProfileBrowser(cmd.Cmd):
547 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000548 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000549 self.prompt = "% "
Georg Brandlb1a97af2010-08-02 12:06:18 +0000550 self.stats = None
551 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000552 if profile is not None:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000553 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000554
555 def generic(self, fn, line):
556 args = line.split()
557 processed = []
558 for term in args:
559 try:
560 processed.append(int(term))
561 continue
562 except ValueError:
563 pass
564 try:
565 frac = float(term)
566 if frac > 1 or frac < 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000567 print("Fraction argument must be in [0, 1]", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000568 continue
569 processed.append(frac)
570 continue
571 except ValueError:
572 pass
573 processed.append(term)
574 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000575 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000576 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000577 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000578 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000579 def generic_help(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000580 print("Arguments may be:", file=self.stream)
581 print("* An integer maximum number of entries to print.", file=self.stream)
582 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
583 print(" what fraction of selected entries to print.", file=self.stream)
584 print("* A regular expression; only entries with function names", file=self.stream)
585 print(" that match it are printed.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000586
587 def do_add(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000588 if self.stats:
589 self.stats.add(line)
590 else:
591 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000592 return 0
593 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000594 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000595
596 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000597 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000598 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000599 print("Print callees 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_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000603 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000604 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000605 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000606 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000607
608 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000609 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000610 return 1
611 def help_EOF(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000612 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000613
614 def do_quit(self, line):
615 return 1
616 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000617 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000618
619 def do_read(self, line):
620 if line:
621 try:
622 self.stats = Stats(line)
Georg Brandl50da60c2008-01-06 21:38:54 +0000623 except IOError as err:
624 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000625 return
Georg Brandlf02e7362010-08-01 07:57:47 +0000626 except Exception as err:
627 print(err.__class__.__name__ + ':', err, file=self.stream)
628 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000629 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000630 elif len(self.prompt) > 2:
Georg Brandlf02e7362010-08-01 07:57:47 +0000631 line = self.prompt[:-2]
632 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000633 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000634 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000635 return 0
636 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000637 print("Read in profile data from a specified file.", file=self.stream)
Georg Brandlf02e7362010-08-01 07:57:47 +0000638 print("Without argument, reload the current file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000639
640 def do_reverse(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000641 if self.stats:
642 self.stats.reverse_order()
643 else:
644 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000645 return 0
646 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000647 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000648
649 def do_sort(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000650 if not self.stats:
651 print("No statistics object is loaded.", file=self.stream)
652 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000653 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchling0a628232010-04-02 17:02:57 +0000654 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000655 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000656 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000657 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000658 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000659 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000660 return 0
661 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000662 print("Sort profile data according to specified keys.", file=self.stream)
663 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000664 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000665 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000666
667 def do_stats(self, line):
668 return self.generic('print_stats', line)
669 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000670 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000671 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000672
673 def do_strip(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000674 if self.stats:
675 self.stats.strip_dirs()
676 else:
677 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000678 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000679 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000680
Georg Brandlf02e7362010-08-01 07:57:47 +0000681 def help_help(self):
682 print("Show help for a given command.", file=self.stream)
683
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000684 def postcmd(self, stop, line):
685 if stop:
686 return stop
687 return None
688
689 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000690 if len(sys.argv) > 1:
691 initprofile = sys.argv[1]
692 else:
693 initprofile = None
694 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000695 browser = ProfileBrowser(initprofile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000696 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000697 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000698 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000699 except KeyboardInterrupt:
700 pass
701
702# That's all, folks.