blob: 4bb5cf170648bae475fe9f84c393b1c0fdf59358 [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:
Georg Brandl2d3c4e72010-08-02 17:24:49 +0000509 if isinstance(caller, tuple):
510 # format used by cProfile
511 new_callers[func] = tuple([i[0] + i[1] for i in
512 zip(caller, new_callers[func])])
513 else:
514 # format used by profile
515 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000516 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000517 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000518 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000519
Guido van Rossumadb31051994-06-23 11:42:52 +0000520def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000521 """Sum the caller statistics to get total number of calls received."""
522 nc = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000523 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000524 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000525 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000526
527#**************************************************************************
528# The following functions support printing of reports
529#**************************************************************************
530
531def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000532 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000533
534#**************************************************************************
535# Statistics browser added by ESR, April 2001
536#**************************************************************************
537
538if __name__ == '__main__':
539 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000540 try:
541 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000542 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000543 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000544
545 class ProfileBrowser(cmd.Cmd):
546 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000547 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000548 self.prompt = "% "
Georg Brandlb1a97af2010-08-02 12:06:18 +0000549 self.stats = None
550 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000551 if profile is not None:
Georg Brandlb1a97af2010-08-02 12:06:18 +0000552 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000553
554 def generic(self, fn, line):
555 args = line.split()
556 processed = []
557 for term in args:
558 try:
559 processed.append(int(term))
560 continue
561 except ValueError:
562 pass
563 try:
564 frac = float(term)
565 if frac > 1 or frac < 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000566 print("Fraction argument must be in [0, 1]", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000567 continue
568 processed.append(frac)
569 continue
570 except ValueError:
571 pass
572 processed.append(term)
573 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000574 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000575 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000576 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000577 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000578 def generic_help(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000579 print("Arguments may be:", file=self.stream)
580 print("* An integer maximum number of entries to print.", file=self.stream)
581 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
582 print(" what fraction of selected entries to print.", file=self.stream)
583 print("* A regular expression; only entries with function names", file=self.stream)
584 print(" that match it are printed.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000585
586 def do_add(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000587 if self.stats:
588 self.stats.add(line)
589 else:
590 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000591 return 0
592 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000593 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000594
595 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000596 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000597 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000598 print("Print callees statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000599 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000600
601 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000602 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000603 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000604 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000605 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000606
607 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000608 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000609 return 1
610 def help_EOF(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_quit(self, line):
614 return 1
615 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000616 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000617
618 def do_read(self, line):
619 if line:
620 try:
621 self.stats = Stats(line)
Georg Brandl50da60c2008-01-06 21:38:54 +0000622 except IOError as err:
623 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000624 return
Georg Brandlf02e7362010-08-01 07:57:47 +0000625 except Exception as err:
626 print(err.__class__.__name__ + ':', err, file=self.stream)
627 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000628 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000629 elif len(self.prompt) > 2:
Georg Brandlf02e7362010-08-01 07:57:47 +0000630 line = self.prompt[:-2]
631 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000632 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000633 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000634 return 0
635 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000636 print("Read in profile data from a specified file.", file=self.stream)
Georg Brandlf02e7362010-08-01 07:57:47 +0000637 print("Without argument, reload the current file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000638
639 def do_reverse(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000640 if self.stats:
641 self.stats.reverse_order()
642 else:
643 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000644 return 0
645 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000646 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000647
648 def do_sort(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000649 if not self.stats:
650 print("No statistics object is loaded.", file=self.stream)
651 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000652 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchling0a628232010-04-02 17:02:57 +0000653 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000654 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000655 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000656 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000657 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000658 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000659 return 0
660 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000661 print("Sort profile data according to specified keys.", file=self.stream)
662 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000663 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000664 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000665
666 def do_stats(self, line):
667 return self.generic('print_stats', line)
668 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000669 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000670 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000671
672 def do_strip(self, line):
Georg Brandl3e4f2ec2010-08-01 07:48:43 +0000673 if self.stats:
674 self.stats.strip_dirs()
675 else:
676 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000677 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000678 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000679
Georg Brandlf02e7362010-08-01 07:57:47 +0000680 def help_help(self):
681 print("Show help for a given command.", file=self.stream)
682
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000683 def postcmd(self, stop, line):
684 if stop:
685 return stop
686 return None
687
688 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000689 if len(sys.argv) > 1:
690 initprofile = sys.argv[1]
691 else:
692 initprofile = None
693 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000694 browser = ProfileBrowser(initprofile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000695 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000696 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000697 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000698 except KeyboardInterrupt:
699 pass
700
701# That's all, folks.