blob: 14c460680cd8a8bd4dece5be96be87350ebf7199 [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#
Guido van Rossumdabcd001999-04-13 04:24:22 +00008# see profile.doc and 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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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]
Tim Peters7d016852001-10-08 06:13:19 +0000141 if type(self) != type(other) or self.__class__ != other.__class__:
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
209 if len(field) == 1 and type(field[0]) == type(1):
210 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000211 field = [ {-1: "stdname",
212 0:"calls",
213 1:"time",
Tim Peters2344fae2001-01-15 00:50:52 +0000214 2: "cumulative" } [ field[0] ] ]
215
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
291 if type(sel) == type(""):
292 new_list = []
293 for func in list:
294 if re.search(sel, func_std_string(func)):
295 new_list.append(func)
296 else:
297 count = len(list)
298 if type(sel) == type(1.0) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000299 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000300 new_list = list[:count]
301 elif type(sel) == type(1) and 0 <= sel < count:
302 count = sel
303 new_list = list[:count]
304 if len(list) != len(new_list):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000305 msg = msg + " List reduced from %r to %r due to restriction <%r>\n" % (
306 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000307
Tim Peters2344fae2001-01-15 00:50:52 +0000308 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000309
Tim Peters2344fae2001-01-15 00:50:52 +0000310 def get_print_list(self, sel_list):
311 width = self.max_name_len
312 if self.fcn_list:
313 list = self.fcn_list[:]
314 msg = " Ordered by: " + self.sort_type + '\n'
315 else:
316 list = self.stats.keys()
317 msg = " Random listing order was used\n"
318
319 for selection in sel_list:
Tim Peters7d016852001-10-08 06:13:19 +0000320 list, msg = self.eval_print_amount(selection, list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000321
322 count = len(list)
323
324 if not list:
325 return 0, list
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000326 print(msg, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000327 if count < len(self.stats):
328 width = 0
329 for func in list:
330 if len(func_std_string(func)) > width:
331 width = len(func_std_string(func))
332 return width+2, list
333
334 def print_stats(self, *amount):
335 for filename in self.files:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000336 print(filename, file=self.stream)
337 if self.files: print(file=self.stream)
Tim Peters7d016852001-10-08 06:13:19 +0000338 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000339 for func in self.top_level:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000340 print(indent, func_get_function_name(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000341
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000342 print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000343 if self.total_calls != self.prim_calls:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000344 print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
345 print("in %.3f CPU seconds" % self.total_tt, file=self.stream)
346 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000347 width, list = self.get_print_list(amount)
348 if list:
349 self.print_title()
350 for func in list:
351 self.print_line(func)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000352 print(file=self.stream)
353 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000354 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000355
Tim Peters2344fae2001-01-15 00:50:52 +0000356 def print_callees(self, *amount):
357 width, list = self.get_print_list(amount)
358 if list:
359 self.calc_callees()
360
361 self.print_call_heading(width, "called...")
362 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000363 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000364 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000365 else:
366 self.print_call_line(width, func, {})
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000367 print(file=self.stream)
368 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000369 return self
370
371 def print_callers(self, *amount):
372 width, list = self.get_print_list(amount)
373 if list:
374 self.print_call_heading(width, "was called by...")
375 for func in list:
376 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000377 self.print_call_line(width, func, callers, "<-")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000378 print(file=self.stream)
379 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000380 return self
381
382 def print_call_heading(self, name_size, column_title):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000383 print("Function ".ljust(name_size) + column_title, file=self.stream)
Armin Rigoa871ef22006-02-08 12:53:56 +0000384 # print sub-header only if we have new-style callers
385 subheader = False
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000386 for cc, nc, tt, ct, callers in self.stats.values():
Armin Rigoa871ef22006-02-08 12:53:56 +0000387 if callers:
Georg Brandla18af4e2007-04-21 15:47:16 +0000388 value = next(iter(callers.values()))
Armin Rigoa871ef22006-02-08 12:53:56 +0000389 subheader = isinstance(value, tuple)
390 break
391 if subheader:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000392 print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000393
Armin Rigoa871ef22006-02-08 12:53:56 +0000394 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000395 print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000396 if not call_dict:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000397 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000398 return
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000399 clist = sorted(call_dict.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000400 indent = ""
401 for func in clist:
402 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000403 value = call_dict[func]
404 if isinstance(value, tuple):
405 nc, cc, tt, ct = value
406 if nc != cc:
407 substats = '%d/%d' % (nc, cc)
408 else:
409 substats = '%d' % (nc,)
410 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
411 f8(tt), f8(ct), name)
412 left_width = name_size + 1
413 else:
414 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
415 left_width = name_size + 3
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000416 print(indent*left_width + substats, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000417 indent = " "
418
Tim Peters2344fae2001-01-15 00:50:52 +0000419 def print_title(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000420 print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
421 print('filename:lineno(function)', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000422
423 def print_line(self, func): # hack : should print percentages
424 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000425 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000426 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000427 c = c + '/' + str(cc)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000428 print(c.rjust(9), end=' ', file=self.stream)
429 print(f8(tt), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000430 if nc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000431 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000432 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000433 print(f8(tt/nc), end=' ', file=self.stream)
434 print(f8(ct), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000435 if cc == 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(ct/cc), end=' ', file=self.stream)
439 print(func_std_string(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000440
Guido van Rossumadb31051994-06-23 11:42:52 +0000441class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000442 """This class provides a generic function for comparing any two tuples.
443 Each instance records a list of tuple-indices (from most significant
444 to least significant), and sort direction (ascending or decending) for
445 each tuple-index. The compare functions can then be used as the function
446 argument to the system sort() function when a list of tuples need to be
447 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000448
Tim Peters2344fae2001-01-15 00:50:52 +0000449 def __init__(self, comp_select_list):
450 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000451
Tim Peters2344fae2001-01-15 00:50:52 +0000452 def compare (self, left, right):
453 for index, direction in self.comp_select_list:
454 l = left[index]
455 r = right[index]
456 if l < r:
457 return -direction
458 if l > r:
459 return direction
460 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000461
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000462
Guido van Rossumadb31051994-06-23 11:42:52 +0000463#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000464# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000465
466def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000467 filename, line, name = func_name
468 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000469
470def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000471 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000472
473def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000474 if func_name[:2] == ('~', 0):
475 # special case for built-in functions
476 name = func_name[2]
477 if name.startswith('<') and name.endswith('>'):
478 return '{%s}' % name[1:-1]
479 else:
480 return name
481 else:
482 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000483
484#**************************************************************************
485# The following functions combine statists for pairs functions.
486# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000487# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000488#**************************************************************************
489
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000490def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000491 """Add together all the stats for two profile entries."""
492 cc, nc, tt, ct, callers = source
493 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000494 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000495 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000496
Guido van Rossumadb31051994-06-23 11:42:52 +0000497def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000498 """Combine two caller lists in a single list."""
499 new_callers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000500 for func, caller in target.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000501 new_callers[func] = caller
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000502 for func, caller in source.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000503 if func in new_callers:
Christian Heimese1c98112008-01-21 11:20:28 +0000504 new_callers[func] = tuple([i[0] + i[1] for i in
505 zip(caller, new_callers[func])])
Tim Peters2344fae2001-01-15 00:50:52 +0000506 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000507 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000508 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000509
Guido van Rossumadb31051994-06-23 11:42:52 +0000510def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000511 """Sum the caller statistics to get total number of calls received."""
512 nc = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000513 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000514 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000515 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000516
517#**************************************************************************
518# The following functions support printing of reports
519#**************************************************************************
520
521def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000522 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000523
524#**************************************************************************
525# Statistics browser added by ESR, April 2001
526#**************************************************************************
527
528if __name__ == '__main__':
529 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000530 try:
531 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000532 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000533 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000534
535 class ProfileBrowser(cmd.Cmd):
536 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000537 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000538 self.prompt = "% "
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000539 if profile is not None:
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000540 self.stats = Stats(profile)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000541 self.stream = self.stats.stream
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000542 else:
543 self.stats = None
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000544 self.stream = sys.stdout
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000545
546 def generic(self, fn, line):
547 args = line.split()
548 processed = []
549 for term in args:
550 try:
551 processed.append(int(term))
552 continue
553 except ValueError:
554 pass
555 try:
556 frac = float(term)
557 if frac > 1 or frac < 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000558 print("Fraction argument must be in [0, 1]", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000559 continue
560 processed.append(frac)
561 continue
562 except ValueError:
563 pass
564 processed.append(term)
565 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000566 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000567 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000568 print("No statistics object is loaded.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000569 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000570 def generic_help(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000571 print("Arguments may be:", file=self.stream)
572 print("* An integer maximum number of entries to print.", file=self.stream)
573 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
574 print(" what fraction of selected entries to print.", file=self.stream)
575 print("* A regular expression; only entries with function names", file=self.stream)
576 print(" that match it are printed.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000577
578 def do_add(self, line):
579 self.stats.add(line)
580 return 0
581 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000582 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000583
584 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000585 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000586 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000587 print("Print callees statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000588 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000589
590 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000591 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000592 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000593 print("Print callers 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_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000597 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000598 return 1
599 def help_EOF(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000600 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000601
602 def do_quit(self, line):
603 return 1
604 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000605 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000606
607 def do_read(self, line):
608 if line:
609 try:
610 self.stats = Stats(line)
Georg Brandl50da60c2008-01-06 21:38:54 +0000611 except IOError as err:
612 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000613 return
614 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000615 elif len(self.prompt) > 2:
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000616 line = self.prompt[-2:]
617 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000618 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000619 return 0
620 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000621 print("Read in profile data from a specified file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000622
623 def do_reverse(self, line):
624 self.stats.reverse_order()
625 return 0
626 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000627 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000628
629 def do_sort(self, line):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000630 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchling0a628232010-04-02 17:02:57 +0000631 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000632 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000633 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000634 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000635 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000636 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000637 return 0
638 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000639 print("Sort profile data according to specified keys.", file=self.stream)
640 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000641 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000642 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000643
644 def do_stats(self, line):
645 return self.generic('print_stats', line)
646 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000647 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000648 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000649
650 def do_strip(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000651 self.stats.strip_dirs()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000652 return 0
653 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000654 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000655
656 def postcmd(self, stop, line):
657 if stop:
658 return stop
659 return None
660
661 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000662 if len(sys.argv) > 1:
663 initprofile = sys.argv[1]
664 else:
665 initprofile = None
666 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000667 browser = ProfileBrowser(initprofile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000668 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000669 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000670 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000671 except KeyboardInterrupt:
672 pass
673
674# That's all, folks.