blob: 3740d92c53e88601442355848493562f4ace8a25 [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
92 self.top_level = {}
93 self.stats = {}
94 self.sort_arg_dict = {}
95 self.load_stats(arg)
96 trouble = 1
97 try:
98 self.get_top_level_stats()
99 trouble = 0
100 finally:
101 if trouble:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000102 print("Invalid timing data", end=' ', file=self.stream)
103 if self.files: print(self.files[-1], end=' ', file=self.stream)
104 print(file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000105
Tim Peters2344fae2001-01-15 00:50:52 +0000106 def load_stats(self, arg):
107 if not arg: self.stats = {}
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000108 elif isinstance(arg, str):
Tim Peters2344fae2001-01-15 00:50:52 +0000109 f = open(arg, 'rb')
110 self.stats = marshal.load(f)
111 f.close()
112 try:
113 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000114 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000115 except: # in case this is not unix
116 pass
117 self.files = [ arg ]
118 elif hasattr(arg, 'create_stats'):
119 arg.create_stats()
120 self.stats = arg.stats
121 arg.stats = {}
122 if not self.stats:
Collin Winterce36ad82007-08-30 01:19:48 +0000123 raise TypeError("Cannot create or construct a %r object from '%r''"
124 % (self.__class__, arg))
Tim Peters2344fae2001-01-15 00:50:52 +0000125 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000126
Tim Peters2344fae2001-01-15 00:50:52 +0000127 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000128 for func, (cc, nc, tt, ct, callers) in self.stats.items():
129 self.total_calls += nc
130 self.prim_calls += cc
131 self.total_tt += tt
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000132 if ("jprofile", 0, "profiler") in callers:
Tim Peters2344fae2001-01-15 00:50:52 +0000133 self.top_level[func] = None
134 if len(func_std_string(func)) > self.max_name_len:
135 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000136
Tim Peters2344fae2001-01-15 00:50:52 +0000137 def add(self, *arg_list):
138 if not arg_list: return self
Guido van Rossum68468eb2003-02-27 20:14:51 +0000139 if len(arg_list) > 1: self.add(*arg_list[1:])
Tim Peters2344fae2001-01-15 00:50:52 +0000140 other = arg_list[0]
Georg Brandlb1a97af2010-08-02 12:06:18 +0000141 if type(self) != type(other):
Tim Peters2344fae2001-01-15 00:50:52 +0000142 other = Stats(other)
Tim Peters7d016852001-10-08 06:13:19 +0000143 self.files += other.files
144 self.total_calls += other.total_calls
145 self.prim_calls += other.prim_calls
146 self.total_tt += other.total_tt
Raymond Hettingere0d49722002-06-02 18:55:56 +0000147 for func in other.top_level:
Tim Peters2344fae2001-01-15 00:50:52 +0000148 self.top_level[func] = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000149
Tim Peters2344fae2001-01-15 00:50:52 +0000150 if self.max_name_len < other.max_name_len:
151 self.max_name_len = other.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000152
Tim Peters2344fae2001-01-15 00:50:52 +0000153 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000154
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000155 for func, stat in other.stats.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000156 if func in self.stats:
Tim Peters2344fae2001-01-15 00:50:52 +0000157 old_func_stat = self.stats[func]
158 else:
159 old_func_stat = (0, 0, 0, 0, {},)
Raymond Hettingere0d49722002-06-02 18:55:56 +0000160 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000161 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000162
Fred Drake9c439102003-05-14 14:28:09 +0000163 def dump_stats(self, filename):
164 """Write the profile data to a file we know how to load back."""
Alex Martelli01c77c62006-08-24 02:58:11 +0000165 f = open(filename, 'wb')
Fred Drake9c439102003-05-14 14:28:09 +0000166 try:
167 marshal.dump(self.stats, f)
168 finally:
169 f.close()
170
Tim Peters2344fae2001-01-15 00:50:52 +0000171 # list the tuple indices and directions for sorting,
172 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000173 sort_arg_dict_default = {
174 "calls" : (((1,-1), ), "call count"),
175 "cumulative": (((3,-1), ), "cumulative time"),
176 "file" : (((4, 1), ), "file name"),
177 "line" : (((5, 1), ), "line number"),
178 "module" : (((4, 1), ), "file name"),
179 "name" : (((6, 1), ), "function name"),
180 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
181 "pcalls" : (((0,-1), ), "call count"),
182 "stdname" : (((7, 1), ), "standard name"),
183 "time" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000184 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000185
Tim Peters2344fae2001-01-15 00:50:52 +0000186 def get_sort_arg_defs(self):
187 """Expand all abbreviations that are unique."""
188 if not self.sort_arg_dict:
189 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000190 bad_list = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000191 for word, tup in self.sort_arg_dict_default.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000192 fragment = word
193 while fragment:
194 if not fragment:
195 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000196 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000197 bad_list[fragment] = 0
198 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000199 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000200 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000201 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000202 del dict[word]
203 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000204
Tim Peters2344fae2001-01-15 00:50:52 +0000205 def sort_stats(self, *field):
206 if not field:
207 self.fcn_list = 0
208 return self
Georg Brandlb1a97af2010-08-02 12:06:18 +0000209 if len(field) == 1 and isinstance(field[0], int):
Tim Peters2344fae2001-01-15 00:50:52 +0000210 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000211 field = [ {-1: "stdname",
Georg Brandlb1a97af2010-08-02 12:06:18 +0000212 0: "calls",
213 1: "time",
214 2: "cumulative"}[field[0]] ]
Tim Peters2344fae2001-01-15 00:50:52 +0000215
216 sort_arg_defs = self.get_sort_arg_defs()
217 sort_tuple = ()
218 self.sort_type = ""
219 connector = ""
220 for word in field:
221 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000222 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000223 connector = ", "
224
225 stats_list = []
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000226 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Tim Peters7d016852001-10-08 06:13:19 +0000227 stats_list.append((cc, nc, tt, ct) + func +
228 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000229
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000230 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000231
232 self.fcn_list = fcn_list = []
233 for tuple in stats_list:
234 fcn_list.append(tuple[-1])
235 return self
236
Tim Peters2344fae2001-01-15 00:50:52 +0000237 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000238 if self.fcn_list:
239 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000240 return self
241
242 def strip_dirs(self):
243 oldstats = self.stats
244 self.stats = newstats = {}
245 max_name_len = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000246 for func, (cc, nc, tt, ct, callers) in oldstats.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000247 newfunc = func_strip_path(func)
248 if len(func_std_string(newfunc)) > max_name_len:
249 max_name_len = len(func_std_string(newfunc))
250 newcallers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000251 for func2, caller in callers.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000252 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000253
Raymond Hettinger54f02222002-06-01 14:18:47 +0000254 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000255 newstats[newfunc] = add_func_stats(
256 newstats[newfunc],
257 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000258 else:
259 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
260 old_top = self.top_level
261 self.top_level = new_top = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000262 for func in old_top:
Tim Peters2344fae2001-01-15 00:50:52 +0000263 new_top[func_strip_path(func)] = None
264
265 self.max_name_len = max_name_len
266
267 self.fcn_list = None
268 self.all_callees = None
269 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000270
Tim Peters2344fae2001-01-15 00:50:52 +0000271 def calc_callees(self):
272 if self.all_callees: return
273 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)
342 if self.files: print(file=self.stream)
Tim Peters7d016852001-10-08 06:13:19 +0000343 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000344 for func in self.top_level:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000345 print(indent, func_get_function_name(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000346
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000347 print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000348 if self.total_calls != self.prim_calls:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000349 print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
350 print("in %.3f CPU seconds" % self.total_tt, file=self.stream)
351 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000352 width, list = self.get_print_list(amount)
353 if list:
354 self.print_title()
355 for func in list:
356 self.print_line(func)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000357 print(file=self.stream)
358 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000359 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000360
Tim Peters2344fae2001-01-15 00:50:52 +0000361 def print_callees(self, *amount):
362 width, list = self.get_print_list(amount)
363 if list:
364 self.calc_callees()
365
366 self.print_call_heading(width, "called...")
367 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000368 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000369 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000370 else:
371 self.print_call_line(width, func, {})
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000372 print(file=self.stream)
373 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000374 return self
375
376 def print_callers(self, *amount):
377 width, list = self.get_print_list(amount)
378 if list:
379 self.print_call_heading(width, "was called by...")
380 for func in list:
381 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000382 self.print_call_line(width, func, callers, "<-")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000383 print(file=self.stream)
384 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000385 return self
386
387 def print_call_heading(self, name_size, column_title):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000388 print("Function ".ljust(name_size) + column_title, file=self.stream)
Armin Rigoa871ef22006-02-08 12:53:56 +0000389 # print sub-header only if we have new-style callers
390 subheader = False
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000391 for cc, nc, tt, ct, callers in self.stats.values():
Armin Rigoa871ef22006-02-08 12:53:56 +0000392 if callers:
Georg Brandla18af4e2007-04-21 15:47:16 +0000393 value = next(iter(callers.values()))
Armin Rigoa871ef22006-02-08 12:53:56 +0000394 subheader = isinstance(value, tuple)
395 break
396 if subheader:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000397 print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000398
Armin Rigoa871ef22006-02-08 12:53:56 +0000399 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000400 print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000401 if not call_dict:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000402 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000403 return
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000404 clist = sorted(call_dict.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000405 indent = ""
406 for func in clist:
407 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000408 value = call_dict[func]
409 if isinstance(value, tuple):
410 nc, cc, tt, ct = value
411 if nc != cc:
412 substats = '%d/%d' % (nc, cc)
413 else:
414 substats = '%d' % (nc,)
415 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
416 f8(tt), f8(ct), name)
417 left_width = name_size + 1
418 else:
419 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
420 left_width = name_size + 3
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000421 print(indent*left_width + substats, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000422 indent = " "
423
Tim Peters2344fae2001-01-15 00:50:52 +0000424 def print_title(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000425 print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
426 print('filename:lineno(function)', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000427
428 def print_line(self, func): # hack : should print percentages
429 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000430 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000431 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000432 c = c + '/' + str(cc)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000433 print(c.rjust(9), end=' ', file=self.stream)
434 print(f8(tt), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000435 if nc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000436 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000437 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000438 print(f8(tt/nc), end=' ', file=self.stream)
439 print(f8(ct), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000440 if cc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000441 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000442 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000443 print(f8(ct/cc), end=' ', file=self.stream)
444 print(func_std_string(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000445
Guido van Rossumadb31051994-06-23 11:42:52 +0000446class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000447 """This class provides a generic function for comparing any two tuples.
448 Each instance records a list of tuple-indices (from most significant
449 to least significant), and sort direction (ascending or decending) for
450 each tuple-index. The compare functions can then be used as the function
451 argument to the system sort() function when a list of tuples need to be
452 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000453
Tim Peters2344fae2001-01-15 00:50:52 +0000454 def __init__(self, comp_select_list):
455 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000456
Tim Peters2344fae2001-01-15 00:50:52 +0000457 def compare (self, left, right):
458 for index, direction in self.comp_select_list:
459 l = left[index]
460 r = right[index]
461 if l < r:
462 return -direction
463 if l > r:
464 return direction
465 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000466
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000467
Guido van Rossumadb31051994-06-23 11:42:52 +0000468#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000469# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000470
471def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000472 filename, line, name = func_name
473 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000474
475def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000476 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000477
478def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000479 if func_name[:2] == ('~', 0):
480 # special case for built-in functions
481 name = func_name[2]
482 if name.startswith('<') and name.endswith('>'):
483 return '{%s}' % name[1:-1]
484 else:
485 return name
486 else:
487 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000488
489#**************************************************************************
490# The following functions combine statists for pairs functions.
491# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000492# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000493#**************************************************************************
494
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000495def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000496 """Add together all the stats for two profile entries."""
497 cc, nc, tt, ct, callers = source
498 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000499 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000500 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000501
Guido van Rossumadb31051994-06-23 11:42:52 +0000502def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000503 """Combine two caller lists in a single list."""
504 new_callers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000505 for func, caller in target.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000506 new_callers[func] = caller
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000507 for func, caller in source.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000508 if func in new_callers:
Christian Heimese1c98112008-01-21 11:20:28 +0000509 new_callers[func] = tuple([i[0] + i[1] for i in
510 zip(caller, new_callers[func])])
Tim Peters2344fae2001-01-15 00:50:52 +0000511 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000512 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000513 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000514
Guido van Rossumadb31051994-06-23 11:42:52 +0000515def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000516 """Sum the caller statistics to get total number of calls received."""
517 nc = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000518 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000519 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000520 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000521
522#**************************************************************************
523# The following functions support printing of reports
524#**************************************************************************
525
526def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000527 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000528
529#**************************************************************************
530# Statistics browser added by ESR, April 2001
531#**************************************************************************
532
533if __name__ == '__main__':
534 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000535 try:
536 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000537 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000538 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000539
540 class ProfileBrowser(cmd.Cmd):
541 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000542 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000543 self.prompt = "% "
Georg Brandlb1a97af2010-08-02 12:06:18 +0000544 self.stats = None
545 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000546 if profile is not None:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000547 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000548
549 def generic(self, fn, line):
550 args = line.split()
551 processed = []
552 for term in args:
553 try:
554 processed.append(int(term))
555 continue
556 except ValueError:
557 pass
558 try:
559 frac = float(term)
560 if frac > 1 or frac < 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000561 print("Fraction argument must be in [0, 1]", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000562 continue
563 processed.append(frac)
564 continue
565 except ValueError:
566 pass
567 processed.append(term)
568 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000569 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000570 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000571 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000572 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000573 def generic_help(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000574 print("Arguments may be:", file=self.stream)
575 print("* An integer maximum number of entries to print.", file=self.stream)
576 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
577 print(" what fraction of selected entries to print.", file=self.stream)
578 print("* A regular expression; only entries with function names", file=self.stream)
579 print(" that match it are printed.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000580
581 def do_add(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000582 if self.stats:
583 self.stats.add(line)
584 else:
585 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000586 return 0
587 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000588 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000589
590 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000591 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000592 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000593 print("Print callees statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000594 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000595
596 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000597 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000598 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000599 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000600 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000601
602 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000603 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000604 return 1
605 def help_EOF(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000606 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000607
608 def do_quit(self, line):
609 return 1
610 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000611 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000612
613 def do_read(self, line):
614 if line:
615 try:
616 self.stats = Stats(line)
Georg Brandl50da60c2008-01-06 21:38:54 +0000617 except IOError as err:
618 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000619 return
Georg Brandlf02e7362010-08-01 07:57:47 +0000620 except Exception as err:
621 print(err.__class__.__name__ + ':', err, file=self.stream)
622 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000623 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000624 elif len(self.prompt) > 2:
Georg Brandlf02e7362010-08-01 07:57:47 +0000625 line = self.prompt[:-2]
626 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000627 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000628 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000629 return 0
630 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000631 print("Read in profile data from a specified file.", file=self.stream)
Georg Brandlf02e7362010-08-01 07:57:47 +0000632 print("Without argument, reload the current file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000633
634 def do_reverse(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000635 if self.stats:
636 self.stats.reverse_order()
637 else:
638 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000639 return 0
640 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000641 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000642
643 def do_sort(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000644 if not self.stats:
645 print("No statistics object is loaded.", file=self.stream)
646 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000647 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchling0a628232010-04-02 17:02:57 +0000648 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000649 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000650 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000651 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000652 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000653 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000654 return 0
655 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000656 print("Sort profile data according to specified keys.", file=self.stream)
657 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000658 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000659 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000660
661 def do_stats(self, line):
662 return self.generic('print_stats', line)
663 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000664 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000665 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000666
667 def do_strip(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000668 if self.stats:
669 self.stats.strip_dirs()
670 else:
671 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000672 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000673 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000674
Georg Brandlf02e7362010-08-01 07:57:47 +0000675 def help_help(self):
676 print("Show help for a given command.", file=self.stream)
677
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000678 def postcmd(self, stop, line):
679 if stop:
680 return stop
681 return None
682
683 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000684 if len(sys.argv) > 1:
685 initprofile = sys.argv[1]
686 else:
687 initprofile = None
688 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000689 browser = ProfileBrowser(initprofile)
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.