blob: fe7f225cc1c6f6ebc180609c29b907c3b660d8c2 [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
Guido van Rossumadb31051994-06-23 11:42:52 +000040
Skip Montanaroc62c81e2001-02-12 02:00:42 +000041__all__ = ["Stats"]
42
Guido van Rossumadb31051994-06-23 11:42:52 +000043class Stats:
Tim Peters2344fae2001-01-15 00:50:52 +000044 """This class is used for creating reports from data generated by the
45 Profile class. It is a "friend" of that class, and imports data either
46 by direct access to members of Profile class, or by reading in a dictionary
47 that was emitted (via marshal) from the Profile class.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000048
Tim Peters2344fae2001-01-15 00:50:52 +000049 The big change from the previous Profiler (in terms of raw functionality)
50 is that an "add()" method has been provided to combine Stats from
51 several distinct profile runs. Both the constructor and the add()
52 method now take arbitrarily many file names as arguments.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000053
Tim Peters2344fae2001-01-15 00:50:52 +000054 All the print methods now take an argument that indicates how many lines
55 to print. If the arg is a floating point number between 0 and 1.0, then
56 it is taken as a decimal percentage of the available lines to be printed
57 (e.g., .1 means print 10% of all available lines). If it is an integer,
58 it is taken to mean the number of lines of data that you wish to have
59 printed.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000060
Tim Peters2344fae2001-01-15 00:50:52 +000061 The sort_stats() method now processes some additional options (i.e., in
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000062 addition to the old -1, 0, 1, or 2). It takes an arbitrary number of
63 quoted strings to select the sort order. For example sort_stats('time',
64 'name') sorts on the major key of 'internal function time', and on the
65 minor key of 'the name of the function'. Look at the two tables in
66 sort_stats() and get_sort_arg_defs(self) for more examples.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000067
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000068 All methods return self, so you can string together commands like:
Tim Peters2344fae2001-01-15 00:50:52 +000069 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
70 print_stats(5).print_callers(5)
71 """
72
Georg Brandl7837a962009-09-02 20:33:30 +000073 def __init__(self, *args, stream=None):
74 self.stream = stream or sys.stdout
Tim Peters2344fae2001-01-15 00:50:52 +000075 if not len(args):
76 arg = None
77 else:
78 arg = args[0]
79 args = args[1:]
80 self.init(arg)
Guido van Rossum68468eb2003-02-27 20:14:51 +000081 self.add(*args)
Tim Peters2344fae2001-01-15 00:50:52 +000082
83 def init(self, arg):
84 self.all_callees = None # calc only if needed
85 self.files = []
86 self.fcn_list = None
87 self.total_tt = 0
88 self.total_calls = 0
89 self.prim_calls = 0
90 self.max_name_len = 0
91 self.top_level = {}
92 self.stats = {}
93 self.sort_arg_dict = {}
94 self.load_stats(arg)
95 trouble = 1
96 try:
97 self.get_top_level_stats()
98 trouble = 0
99 finally:
100 if trouble:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000101 print("Invalid timing data", end=' ', file=self.stream)
102 if self.files: print(self.files[-1], end=' ', file=self.stream)
103 print(file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000104
Tim Peters2344fae2001-01-15 00:50:52 +0000105 def load_stats(self, arg):
106 if not arg: self.stats = {}
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000107 elif isinstance(arg, str):
Tim Peters2344fae2001-01-15 00:50:52 +0000108 f = open(arg, 'rb')
109 self.stats = marshal.load(f)
110 f.close()
111 try:
112 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000113 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000114 except: # in case this is not unix
115 pass
116 self.files = [ arg ]
117 elif hasattr(arg, 'create_stats'):
118 arg.create_stats()
119 self.stats = arg.stats
120 arg.stats = {}
121 if not self.stats:
Collin Winterce36ad82007-08-30 01:19:48 +0000122 raise TypeError("Cannot create or construct a %r object from '%r''"
123 % (self.__class__, arg))
Tim Peters2344fae2001-01-15 00:50:52 +0000124 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000125
Tim Peters2344fae2001-01-15 00:50:52 +0000126 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000127 for func, (cc, nc, tt, ct, callers) in self.stats.items():
128 self.total_calls += nc
129 self.prim_calls += cc
130 self.total_tt += tt
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000131 if ("jprofile", 0, "profiler") in callers:
Tim Peters2344fae2001-01-15 00:50:52 +0000132 self.top_level[func] = None
133 if len(func_std_string(func)) > self.max_name_len:
134 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000135
Tim Peters2344fae2001-01-15 00:50:52 +0000136 def add(self, *arg_list):
137 if not arg_list: return self
Guido van Rossum68468eb2003-02-27 20:14:51 +0000138 if len(arg_list) > 1: self.add(*arg_list[1:])
Tim Peters2344fae2001-01-15 00:50:52 +0000139 other = arg_list[0]
Tim Peters7d016852001-10-08 06:13:19 +0000140 if type(self) != type(other) or self.__class__ != other.__class__:
Tim Peters2344fae2001-01-15 00:50:52 +0000141 other = Stats(other)
Tim Peters7d016852001-10-08 06:13:19 +0000142 self.files += other.files
143 self.total_calls += other.total_calls
144 self.prim_calls += other.prim_calls
145 self.total_tt += other.total_tt
Raymond Hettingere0d49722002-06-02 18:55:56 +0000146 for func in other.top_level:
Tim Peters2344fae2001-01-15 00:50:52 +0000147 self.top_level[func] = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000148
Tim Peters2344fae2001-01-15 00:50:52 +0000149 if self.max_name_len < other.max_name_len:
150 self.max_name_len = other.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000151
Tim Peters2344fae2001-01-15 00:50:52 +0000152 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000153
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000154 for func, stat in other.stats.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000155 if func in self.stats:
Tim Peters2344fae2001-01-15 00:50:52 +0000156 old_func_stat = self.stats[func]
157 else:
158 old_func_stat = (0, 0, 0, 0, {},)
Raymond Hettingere0d49722002-06-02 18:55:56 +0000159 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000160 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000161
Fred Drake9c439102003-05-14 14:28:09 +0000162 def dump_stats(self, filename):
163 """Write the profile data to a file we know how to load back."""
Alex Martelli01c77c62006-08-24 02:58:11 +0000164 f = open(filename, 'wb')
Fred Drake9c439102003-05-14 14:28:09 +0000165 try:
166 marshal.dump(self.stats, f)
167 finally:
168 f.close()
169
Tim Peters2344fae2001-01-15 00:50:52 +0000170 # list the tuple indices and directions for sorting,
171 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000172 sort_arg_dict_default = {
173 "calls" : (((1,-1), ), "call count"),
174 "cumulative": (((3,-1), ), "cumulative time"),
175 "file" : (((4, 1), ), "file name"),
176 "line" : (((5, 1), ), "line number"),
177 "module" : (((4, 1), ), "file name"),
178 "name" : (((6, 1), ), "function name"),
179 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
180 "pcalls" : (((0,-1), ), "call count"),
181 "stdname" : (((7, 1), ), "standard name"),
182 "time" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000183 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000184
Tim Peters2344fae2001-01-15 00:50:52 +0000185 def get_sort_arg_defs(self):
186 """Expand all abbreviations that are unique."""
187 if not self.sort_arg_dict:
188 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000189 bad_list = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000190 for word, tup in self.sort_arg_dict_default.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000191 fragment = word
192 while fragment:
193 if not fragment:
194 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000195 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000196 bad_list[fragment] = 0
197 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000198 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000199 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000200 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000201 del dict[word]
202 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000203
Tim Peters2344fae2001-01-15 00:50:52 +0000204 def sort_stats(self, *field):
205 if not field:
206 self.fcn_list = 0
207 return self
208 if len(field) == 1 and type(field[0]) == type(1):
209 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000210 field = [ {-1: "stdname",
211 0:"calls",
212 1:"time",
Tim Peters2344fae2001-01-15 00:50:52 +0000213 2: "cumulative" } [ field[0] ] ]
214
215 sort_arg_defs = self.get_sort_arg_defs()
216 sort_tuple = ()
217 self.sort_type = ""
218 connector = ""
219 for word in field:
220 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000221 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000222 connector = ", "
223
224 stats_list = []
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000225 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Tim Peters7d016852001-10-08 06:13:19 +0000226 stats_list.append((cc, nc, tt, ct) + func +
227 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000228
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000229 stats_list.sort(key=CmpToKey(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000230
231 self.fcn_list = fcn_list = []
232 for tuple in stats_list:
233 fcn_list.append(tuple[-1])
234 return self
235
Tim Peters2344fae2001-01-15 00:50:52 +0000236 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000237 if self.fcn_list:
238 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000239 return self
240
241 def strip_dirs(self):
242 oldstats = self.stats
243 self.stats = newstats = {}
244 max_name_len = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000245 for func, (cc, nc, tt, ct, callers) in oldstats.items():
Tim Peters2344fae2001-01-15 00:50:52 +0000246 newfunc = func_strip_path(func)
247 if len(func_std_string(newfunc)) > max_name_len:
248 max_name_len = len(func_std_string(newfunc))
249 newcallers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000250 for func2, caller in callers.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000251 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000252
Raymond Hettinger54f02222002-06-01 14:18:47 +0000253 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000254 newstats[newfunc] = add_func_stats(
255 newstats[newfunc],
256 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000257 else:
258 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
259 old_top = self.top_level
260 self.top_level = new_top = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000261 for func in old_top:
Tim Peters2344fae2001-01-15 00:50:52 +0000262 new_top[func_strip_path(func)] = None
263
264 self.max_name_len = max_name_len
265
266 self.fcn_list = None
267 self.all_callees = None
268 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000269
Tim Peters2344fae2001-01-15 00:50:52 +0000270 def calc_callees(self):
271 if self.all_callees: return
272 self.all_callees = all_callees = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000273 for func, (cc, nc, tt, ct, callers) in self.stats.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000274 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000275 all_callees[func] = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000276 for func2, caller in callers.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000277 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000278 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000279 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000280 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000281
Tim Peters2344fae2001-01-15 00:50:52 +0000282 #******************************************************************
283 # The following functions support actual printing of reports
284 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000285
Tim Peters2344fae2001-01-15 00:50:52 +0000286 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000287
Tim Peters2344fae2001-01-15 00:50:52 +0000288 def eval_print_amount(self, sel, list, msg):
289 new_list = list
290 if type(sel) == type(""):
291 new_list = []
292 for func in list:
293 if re.search(sel, func_std_string(func)):
294 new_list.append(func)
295 else:
296 count = len(list)
297 if type(sel) == type(1.0) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000298 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000299 new_list = list[:count]
300 elif type(sel) == type(1) and 0 <= sel < count:
301 count = sel
302 new_list = list[:count]
303 if len(list) != len(new_list):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000304 msg = msg + " List reduced from %r to %r due to restriction <%r>\n" % (
305 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000306
Tim Peters2344fae2001-01-15 00:50:52 +0000307 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000308
Tim Peters2344fae2001-01-15 00:50:52 +0000309 def get_print_list(self, sel_list):
310 width = self.max_name_len
311 if self.fcn_list:
312 list = self.fcn_list[:]
313 msg = " Ordered by: " + self.sort_type + '\n'
314 else:
315 list = self.stats.keys()
316 msg = " Random listing order was used\n"
317
318 for selection in sel_list:
Tim Peters7d016852001-10-08 06:13:19 +0000319 list, msg = self.eval_print_amount(selection, list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000320
321 count = len(list)
322
323 if not list:
324 return 0, list
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000325 print(msg, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000326 if count < len(self.stats):
327 width = 0
328 for func in list:
329 if len(func_std_string(func)) > width:
330 width = len(func_std_string(func))
331 return width+2, list
332
333 def print_stats(self, *amount):
334 for filename in self.files:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000335 print(filename, file=self.stream)
336 if self.files: print(file=self.stream)
Tim Peters7d016852001-10-08 06:13:19 +0000337 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000338 for func in self.top_level:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000339 print(indent, func_get_function_name(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000340
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000341 print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000342 if self.total_calls != self.prim_calls:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000343 print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
344 print("in %.3f CPU seconds" % self.total_tt, file=self.stream)
345 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000346 width, list = self.get_print_list(amount)
347 if list:
348 self.print_title()
349 for func in list:
350 self.print_line(func)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000351 print(file=self.stream)
352 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000353 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000354
Tim Peters2344fae2001-01-15 00:50:52 +0000355 def print_callees(self, *amount):
356 width, list = self.get_print_list(amount)
357 if list:
358 self.calc_callees()
359
360 self.print_call_heading(width, "called...")
361 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000362 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000363 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000364 else:
365 self.print_call_line(width, func, {})
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000366 print(file=self.stream)
367 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000368 return self
369
370 def print_callers(self, *amount):
371 width, list = self.get_print_list(amount)
372 if list:
373 self.print_call_heading(width, "was called by...")
374 for func in list:
375 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000376 self.print_call_line(width, func, callers, "<-")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000377 print(file=self.stream)
378 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000379 return self
380
381 def print_call_heading(self, name_size, column_title):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000382 print("Function ".ljust(name_size) + column_title, file=self.stream)
Armin Rigoa871ef22006-02-08 12:53:56 +0000383 # print sub-header only if we have new-style callers
384 subheader = False
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000385 for cc, nc, tt, ct, callers in self.stats.values():
Armin Rigoa871ef22006-02-08 12:53:56 +0000386 if callers:
Georg Brandla18af4e2007-04-21 15:47:16 +0000387 value = next(iter(callers.values()))
Armin Rigoa871ef22006-02-08 12:53:56 +0000388 subheader = isinstance(value, tuple)
389 break
390 if subheader:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000391 print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
Guido van Rossumadb31051994-06-23 11:42:52 +0000392
Armin Rigoa871ef22006-02-08 12:53:56 +0000393 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000394 print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000395 if not call_dict:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000396 print(file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000397 return
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000398 clist = sorted(call_dict.keys())
Tim Peters2344fae2001-01-15 00:50:52 +0000399 indent = ""
400 for func in clist:
401 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000402 value = call_dict[func]
403 if isinstance(value, tuple):
404 nc, cc, tt, ct = value
405 if nc != cc:
406 substats = '%d/%d' % (nc, cc)
407 else:
408 substats = '%d' % (nc,)
409 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
410 f8(tt), f8(ct), name)
411 left_width = name_size + 1
412 else:
413 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
414 left_width = name_size + 3
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000415 print(indent*left_width + substats, file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000416 indent = " "
417
Tim Peters2344fae2001-01-15 00:50:52 +0000418 def print_title(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000419 print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
420 print('filename:lineno(function)', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000421
422 def print_line(self, func): # hack : should print percentages
423 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000424 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000425 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000426 c = c + '/' + str(cc)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000427 print(c.rjust(9), end=' ', file=self.stream)
428 print(f8(tt), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000429 if nc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000430 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000431 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000432 print(f8(tt/nc), end=' ', file=self.stream)
433 print(f8(ct), end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000434 if cc == 0:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000435 print(' '*8, end=' ', file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000436 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000437 print(f8(ct/cc), end=' ', file=self.stream)
438 print(func_std_string(func), file=self.stream)
Tim Peters2344fae2001-01-15 00:50:52 +0000439
Guido van Rossumadb31051994-06-23 11:42:52 +0000440class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000441 """This class provides a generic function for comparing any two tuples.
442 Each instance records a list of tuple-indices (from most significant
443 to least significant), and sort direction (ascending or decending) for
444 each tuple-index. The compare functions can then be used as the function
445 argument to the system sort() function when a list of tuples need to be
446 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000447
Tim Peters2344fae2001-01-15 00:50:52 +0000448 def __init__(self, comp_select_list):
449 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000450
Tim Peters2344fae2001-01-15 00:50:52 +0000451 def compare (self, left, right):
452 for index, direction in self.comp_select_list:
453 l = left[index]
454 r = right[index]
455 if l < r:
456 return -direction
457 if l > r:
458 return direction
459 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000460
Raymond Hettinger70b64fc2008-01-30 20:15:17 +0000461def CmpToKey(mycmp):
462 'Convert a cmp= function into a key= function'
463 class K(object):
464 def __init__(self, obj):
465 self.obj = obj
466 def __lt__(self, other):
467 return mycmp(self.obj, other.obj) == -1
468 return K
469
470
Guido van Rossumadb31051994-06-23 11:42:52 +0000471#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000472# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000473
474def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000475 filename, line, name = func_name
476 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000477
478def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000479 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000480
481def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000482 if func_name[:2] == ('~', 0):
483 # special case for built-in functions
484 name = func_name[2]
485 if name.startswith('<') and name.endswith('>'):
486 return '{%s}' % name[1:-1]
487 else:
488 return name
489 else:
490 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000491
492#**************************************************************************
493# The following functions combine statists for pairs functions.
494# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000495# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000496#**************************************************************************
497
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000498def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000499 """Add together all the stats for two profile entries."""
500 cc, nc, tt, ct, callers = source
501 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000502 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000503 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000504
Guido van Rossumadb31051994-06-23 11:42:52 +0000505def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000506 """Combine two caller lists in a single list."""
507 new_callers = {}
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000508 for func, caller in target.items():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000509 new_callers[func] = caller
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000510 for func, caller in source.items():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000511 if func in new_callers:
Christian Heimese1c98112008-01-21 11:20:28 +0000512 new_callers[func] = tuple([i[0] + i[1] for i in
513 zip(caller, new_callers[func])])
Tim Peters2344fae2001-01-15 00:50:52 +0000514 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000515 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000516 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000517
Guido van Rossumadb31051994-06-23 11:42:52 +0000518def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000519 """Sum the caller statistics to get total number of calls received."""
520 nc = 0
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000521 for calls in callers.values():
Raymond Hettingere0d49722002-06-02 18:55:56 +0000522 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000523 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000524
525#**************************************************************************
526# The following functions support printing of reports
527#**************************************************************************
528
529def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000530 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000531
532#**************************************************************************
533# Statistics browser added by ESR, April 2001
534#**************************************************************************
535
536if __name__ == '__main__':
537 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000538 try:
539 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000540 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000541 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000542
543 class ProfileBrowser(cmd.Cmd):
544 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000545 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000546 self.prompt = "% "
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000547 if profile is not None:
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000548 self.stats = Stats(profile)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000549 self.stream = self.stats.stream
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000550 else:
551 self.stats = None
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000552 self.stream = sys.stdout
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):
587 self.stats.add(line)
588 return 0
589 def help_add(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000590 print("Add profile info from given file to current statistics object.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000591
592 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000593 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000594 def help_callees(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000595 print("Print callees statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000596 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000597
598 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000599 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000600 def help_callers(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000601 print("Print callers statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000602 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000603
604 def do_EOF(self, line):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000605 print("", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000606 return 1
607 def help_EOF(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000608 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000609
610 def do_quit(self, line):
611 return 1
612 def help_quit(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000613 print("Leave the profile brower.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000614
615 def do_read(self, line):
616 if line:
617 try:
618 self.stats = Stats(line)
Georg Brandl50da60c2008-01-06 21:38:54 +0000619 except IOError as err:
620 print(err.args[1], file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000621 return
622 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000623 elif len(self.prompt) > 2:
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000624 line = self.prompt[-2:]
625 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000626 print("No statistics object is current -- cannot reload.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000627 return 0
628 def help_read(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000629 print("Read in profile data from a specified file.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000630
631 def do_reverse(self, line):
632 self.stats.reverse_order()
633 return 0
634 def help_reverse(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000635 print("Reverse the sort order of the profiling report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000636
637 def do_sort(self, line):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000638 abbrevs = self.stats.get_sort_arg_defs()
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000639 if line and not filter(lambda x,a=abbrevs: x not in a,line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000640 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000641 else:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000642 print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000643 for (key, value) in Stats.sort_arg_dict_default.items():
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000644 print("%s -- %s" % (key, value[1]), file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000645 return 0
646 def help_sort(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000647 print("Sort profile data according to specified keys.", file=self.stream)
648 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000649 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000650 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000651
652 def do_stats(self, line):
653 return self.generic('print_stats', line)
654 def help_stats(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000655 print("Print statistics from the current stat object.", file=self.stream)
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000656 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000657
658 def do_strip(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000659 self.stats.strip_dirs()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000660 return 0
661 def help_strip(self):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000662 print("Strip leading path information from filenames in the report.", file=self.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000663
664 def postcmd(self, stop, line):
665 if stop:
666 return stop
667 return None
668
669 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000670 if len(sys.argv) > 1:
671 initprofile = sys.argv[1]
672 else:
673 initprofile = None
674 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000675 browser = ProfileBrowser(initprofile)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000676 print("Welcome to the profile statistics browser.", file=browser.stream)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000677 browser.cmdloop()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000678 print("Goodbye.", file=browser.stream)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000679 except KeyboardInterrupt:
680 pass
681
682# That's all, folks.