blob: c019cd1fd1a99d65de64aa6953b523a35d899742 [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 Brandl9c5efad2010-08-02 21:29:14 +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
Skip Montanaro262fb922006-04-21 02:31:07 +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 Hettingerbb006cf2010-04-04 21:45:01 +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
Skip Montanaro262fb922006-04-21 02:31:07 +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 Brandl9c5efad2010-08-02 21:29:14 +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
Skip Montanaro262fb922006-04-21 02:31:07 +000074 def __init__(self, *args, **kwds):
75 # I can't figure out how to explictly specify a stream keyword arg
76 # with *args:
77 # def __init__(self, *args, stream=sys.stdout): ...
78 # so I use **kwds and sqauwk if something unexpected is passed in.
79 self.stream = sys.stdout
80 if "stream" in kwds:
81 self.stream = kwds["stream"]
82 del kwds["stream"]
83 if kwds:
84 keys = kwds.keys()
85 keys.sort()
86 extras = ", ".join(["%s=%s" % (k, kwds[k]) for k in keys])
87 raise ValueError, "unrecognized keyword args: %s" % extras
Tim Peters2344fae2001-01-15 00:50:52 +000088 if not len(args):
89 arg = None
90 else:
91 arg = args[0]
92 args = args[1:]
93 self.init(arg)
Guido van Rossum68468eb2003-02-27 20:14:51 +000094 self.add(*args)
Tim Peters2344fae2001-01-15 00:50:52 +000095
96 def init(self, arg):
97 self.all_callees = None # calc only if needed
98 self.files = []
99 self.fcn_list = None
100 self.total_tt = 0
101 self.total_calls = 0
102 self.prim_calls = 0
103 self.max_name_len = 0
104 self.top_level = {}
105 self.stats = {}
106 self.sort_arg_dict = {}
107 self.load_stats(arg)
108 trouble = 1
109 try:
110 self.get_top_level_stats()
111 trouble = 0
112 finally:
113 if trouble:
Skip Montanaro262fb922006-04-21 02:31:07 +0000114 print >> self.stream, "Invalid timing data",
115 if self.files: print >> self.stream, self.files[-1],
116 print >> self.stream
Guido van Rossumadb31051994-06-23 11:42:52 +0000117
Tim Peters2344fae2001-01-15 00:50:52 +0000118 def load_stats(self, arg):
119 if not arg: self.stats = {}
Georg Brandl21d900f2006-11-26 19:27:47 +0000120 elif isinstance(arg, basestring):
Tim Peters2344fae2001-01-15 00:50:52 +0000121 f = open(arg, 'rb')
122 self.stats = marshal.load(f)
123 f.close()
124 try:
125 file_stats = os.stat(arg)
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000126 arg = time.ctime(file_stats.st_mtime) + " " + arg
Tim Peters2344fae2001-01-15 00:50:52 +0000127 except: # in case this is not unix
128 pass
129 self.files = [ arg ]
130 elif hasattr(arg, 'create_stats'):
131 arg.create_stats()
132 self.stats = arg.stats
133 arg.stats = {}
134 if not self.stats:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000135 raise TypeError, "Cannot create or construct a %r object from '%r''" % (
136 self.__class__, arg)
Tim Peters2344fae2001-01-15 00:50:52 +0000137 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000138
Tim Peters2344fae2001-01-15 00:50:52 +0000139 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000140 for func, (cc, nc, tt, ct, callers) in self.stats.items():
141 self.total_calls += nc
142 self.prim_calls += cc
143 self.total_tt += tt
Brett Cannonc3ce0e52008-08-03 22:52:42 +0000144 if ("jprofile", 0, "profiler") in callers:
Tim Peters2344fae2001-01-15 00:50:52 +0000145 self.top_level[func] = None
146 if len(func_std_string(func)) > self.max_name_len:
147 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000148
Tim Peters2344fae2001-01-15 00:50:52 +0000149 def add(self, *arg_list):
150 if not arg_list: return self
Guido van Rossum68468eb2003-02-27 20:14:51 +0000151 if len(arg_list) > 1: self.add(*arg_list[1:])
Tim Peters2344fae2001-01-15 00:50:52 +0000152 other = arg_list[0]
Georg Brandlfb2c4592010-08-02 21:36:12 +0000153 if type(self) != type(other) or self.__class__ != other.__class__:
Tim Peters2344fae2001-01-15 00:50:52 +0000154 other = Stats(other)
Tim Peters7d016852001-10-08 06:13:19 +0000155 self.files += other.files
156 self.total_calls += other.total_calls
157 self.prim_calls += other.prim_calls
158 self.total_tt += other.total_tt
Raymond Hettingere0d49722002-06-02 18:55:56 +0000159 for func in other.top_level:
Tim Peters2344fae2001-01-15 00:50:52 +0000160 self.top_level[func] = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000161
Tim Peters2344fae2001-01-15 00:50:52 +0000162 if self.max_name_len < other.max_name_len:
163 self.max_name_len = other.max_name_len
Guido van Rossumadb31051994-06-23 11:42:52 +0000164
Tim Peters2344fae2001-01-15 00:50:52 +0000165 self.fcn_list = None
Guido van Rossumadb31051994-06-23 11:42:52 +0000166
Raymond Hettingere0d49722002-06-02 18:55:56 +0000167 for func, stat in other.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000168 if func in self.stats:
Tim Peters2344fae2001-01-15 00:50:52 +0000169 old_func_stat = self.stats[func]
170 else:
171 old_func_stat = (0, 0, 0, 0, {},)
Raymond Hettingere0d49722002-06-02 18:55:56 +0000172 self.stats[func] = add_func_stats(old_func_stat, stat)
Tim Peters2344fae2001-01-15 00:50:52 +0000173 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000174
Fred Drake9c439102003-05-14 14:28:09 +0000175 def dump_stats(self, filename):
176 """Write the profile data to a file we know how to load back."""
177 f = file(filename, 'wb')
178 try:
179 marshal.dump(self.stats, f)
180 finally:
181 f.close()
182
Tim Peters2344fae2001-01-15 00:50:52 +0000183 # list the tuple indices and directions for sorting,
184 # along with some printable description
Tim Peters7d016852001-10-08 06:13:19 +0000185 sort_arg_dict_default = {
186 "calls" : (((1,-1), ), "call count"),
187 "cumulative": (((3,-1), ), "cumulative time"),
188 "file" : (((4, 1), ), "file name"),
189 "line" : (((5, 1), ), "line number"),
190 "module" : (((4, 1), ), "file name"),
191 "name" : (((6, 1), ), "function name"),
192 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
193 "pcalls" : (((0,-1), ), "call count"),
194 "stdname" : (((7, 1), ), "standard name"),
195 "time" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000196 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000197
Tim Peters2344fae2001-01-15 00:50:52 +0000198 def get_sort_arg_defs(self):
199 """Expand all abbreviations that are unique."""
200 if not self.sort_arg_dict:
201 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000202 bad_list = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000203 for word, tup in self.sort_arg_dict_default.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000204 fragment = word
205 while fragment:
206 if not fragment:
207 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000208 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000209 bad_list[fragment] = 0
210 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000211 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000212 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000213 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000214 del dict[word]
215 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000216
Tim Peters2344fae2001-01-15 00:50:52 +0000217 def sort_stats(self, *field):
218 if not field:
219 self.fcn_list = 0
220 return self
Georg Brandlfb2c4592010-08-02 21:36:12 +0000221 if len(field) == 1 and isinstance(field[0], (int, long)):
Tim Peters2344fae2001-01-15 00:50:52 +0000222 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000223 field = [ {-1: "stdname",
Georg Brandl9c5efad2010-08-02 21:29:14 +0000224 0: "calls",
225 1: "time",
226 2: "cumulative"}[field[0]] ]
Tim Peters2344fae2001-01-15 00:50:52 +0000227
228 sort_arg_defs = self.get_sort_arg_defs()
229 sort_tuple = ()
230 self.sort_type = ""
231 connector = ""
232 for word in field:
233 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000234 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000235 connector = ", "
236
237 stats_list = []
Raymond Hettingere0d49722002-06-02 18:55:56 +0000238 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Tim Peters7d016852001-10-08 06:13:19 +0000239 stats_list.append((cc, nc, tt, ct) + func +
240 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000241
Raymond Hettingerbb006cf2010-04-04 21:45:01 +0000242 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000243
244 self.fcn_list = fcn_list = []
245 for tuple in stats_list:
246 fcn_list.append(tuple[-1])
247 return self
248
Tim Peters2344fae2001-01-15 00:50:52 +0000249 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000250 if self.fcn_list:
251 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000252 return self
253
254 def strip_dirs(self):
255 oldstats = self.stats
256 self.stats = newstats = {}
257 max_name_len = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000258 for func, (cc, nc, tt, ct, callers) in oldstats.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000259 newfunc = func_strip_path(func)
260 if len(func_std_string(newfunc)) > max_name_len:
261 max_name_len = len(func_std_string(newfunc))
262 newcallers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000263 for func2, caller in callers.iteritems():
264 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000265
Raymond Hettinger54f02222002-06-01 14:18:47 +0000266 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000267 newstats[newfunc] = add_func_stats(
268 newstats[newfunc],
269 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000270 else:
271 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
272 old_top = self.top_level
273 self.top_level = new_top = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000274 for func in old_top:
Tim Peters2344fae2001-01-15 00:50:52 +0000275 new_top[func_strip_path(func)] = None
276
277 self.max_name_len = max_name_len
278
279 self.fcn_list = None
280 self.all_callees = None
281 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000282
Tim Peters2344fae2001-01-15 00:50:52 +0000283 def calc_callees(self):
284 if self.all_callees: return
285 self.all_callees = all_callees = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000286 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000287 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000288 all_callees[func] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000289 for func2, caller in callers.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000290 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000291 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000292 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000293 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000294
Tim Peters2344fae2001-01-15 00:50:52 +0000295 #******************************************************************
296 # The following functions support actual printing of reports
297 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000298
Tim Peters2344fae2001-01-15 00:50:52 +0000299 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000300
Tim Peters2344fae2001-01-15 00:50:52 +0000301 def eval_print_amount(self, sel, list, msg):
302 new_list = list
Georg Brandlfb2c4592010-08-02 21:36:12 +0000303 if isinstance(sel, basestring):
Georg Brandl9c5efad2010-08-02 21:29:14 +0000304 try:
305 rex = re.compile(sel)
306 except re.error:
307 msg += " <Invalid regular expression %r>\n" % sel
308 return new_list, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000309 new_list = []
310 for func in list:
Georg Brandl9c5efad2010-08-02 21:29:14 +0000311 if rex.search(func_std_string(func)):
Tim Peters2344fae2001-01-15 00:50:52 +0000312 new_list.append(func)
313 else:
314 count = len(list)
Georg Brandl9c5efad2010-08-02 21:29:14 +0000315 if isinstance(sel, float) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000316 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000317 new_list = list[:count]
Georg Brandlfb2c4592010-08-02 21:36:12 +0000318 elif isinstance(sel, (int, long)) and 0 <= sel < count:
Tim Peters2344fae2001-01-15 00:50:52 +0000319 count = sel
320 new_list = list[:count]
321 if len(list) != len(new_list):
Georg Brandl9c5efad2010-08-02 21:29:14 +0000322 msg += " List reduced from %r to %r due to restriction <%r>\n" % (
323 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000324
Tim Peters2344fae2001-01-15 00:50:52 +0000325 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000326
Tim Peters2344fae2001-01-15 00:50:52 +0000327 def get_print_list(self, sel_list):
328 width = self.max_name_len
329 if self.fcn_list:
Georg Brandl9c5efad2010-08-02 21:29:14 +0000330 stat_list = self.fcn_list[:]
Tim Peters2344fae2001-01-15 00:50:52 +0000331 msg = " Ordered by: " + self.sort_type + '\n'
332 else:
Georg Brandlfb2c4592010-08-02 21:36:12 +0000333 stat_list = self.stats.keys()
Tim Peters2344fae2001-01-15 00:50:52 +0000334 msg = " Random listing order was used\n"
335
336 for selection in sel_list:
Georg Brandl9c5efad2010-08-02 21:29:14 +0000337 stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000338
Georg Brandl9c5efad2010-08-02 21:29:14 +0000339 count = len(stat_list)
Tim Peters2344fae2001-01-15 00:50:52 +0000340
Georg Brandl9c5efad2010-08-02 21:29:14 +0000341 if not stat_list:
342 return 0, stat_list
Skip Montanaro262fb922006-04-21 02:31:07 +0000343 print >> self.stream, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000344 if count < len(self.stats):
345 width = 0
Georg Brandl9c5efad2010-08-02 21:29:14 +0000346 for func in stat_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000347 if len(func_std_string(func)) > width:
348 width = len(func_std_string(func))
Georg Brandl9c5efad2010-08-02 21:29:14 +0000349 return width+2, stat_list
Tim Peters2344fae2001-01-15 00:50:52 +0000350
351 def print_stats(self, *amount):
352 for filename in self.files:
Skip Montanaro262fb922006-04-21 02:31:07 +0000353 print >> self.stream, filename
354 if self.files: print >> self.stream
Tim Peters7d016852001-10-08 06:13:19 +0000355 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000356 for func in self.top_level:
Skip Montanaro262fb922006-04-21 02:31:07 +0000357 print >> self.stream, indent, func_get_function_name(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000358
Skip Montanaro262fb922006-04-21 02:31:07 +0000359 print >> self.stream, indent, self.total_calls, "function calls",
Tim Peters2344fae2001-01-15 00:50:52 +0000360 if self.total_calls != self.prim_calls:
Skip Montanaro262fb922006-04-21 02:31:07 +0000361 print >> self.stream, "(%d primitive calls)" % self.prim_calls,
362 print >> self.stream, "in %.3f CPU seconds" % self.total_tt
363 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000364 width, list = self.get_print_list(amount)
365 if list:
366 self.print_title()
367 for func in list:
368 self.print_line(func)
Skip Montanaro262fb922006-04-21 02:31:07 +0000369 print >> self.stream
370 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000371 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000372
Tim Peters2344fae2001-01-15 00:50:52 +0000373 def print_callees(self, *amount):
374 width, list = self.get_print_list(amount)
375 if list:
376 self.calc_callees()
377
378 self.print_call_heading(width, "called...")
379 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000380 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000381 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000382 else:
383 self.print_call_line(width, func, {})
Skip Montanaro262fb922006-04-21 02:31:07 +0000384 print >> self.stream
385 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000386 return self
387
388 def print_callers(self, *amount):
389 width, list = self.get_print_list(amount)
390 if list:
391 self.print_call_heading(width, "was called by...")
392 for func in list:
393 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000394 self.print_call_line(width, func, callers, "<-")
Skip Montanaro262fb922006-04-21 02:31:07 +0000395 print >> self.stream
396 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000397 return self
398
399 def print_call_heading(self, name_size, column_title):
Skip Montanaro262fb922006-04-21 02:31:07 +0000400 print >> self.stream, "Function ".ljust(name_size) + column_title
Armin Rigoa871ef22006-02-08 12:53:56 +0000401 # print sub-header only if we have new-style callers
402 subheader = False
403 for cc, nc, tt, ct, callers in self.stats.itervalues():
404 if callers:
405 value = callers.itervalues().next()
406 subheader = isinstance(value, tuple)
407 break
408 if subheader:
Skip Montanaro262fb922006-04-21 02:31:07 +0000409 print >> self.stream, " "*name_size + " ncalls tottime cumtime"
Guido van Rossumadb31051994-06-23 11:42:52 +0000410
Armin Rigoa871ef22006-02-08 12:53:56 +0000411 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Skip Montanaro262fb922006-04-21 02:31:07 +0000412 print >> self.stream, func_std_string(source).ljust(name_size) + arrow,
Tim Peters2344fae2001-01-15 00:50:52 +0000413 if not call_dict:
Skip Montanaro262fb922006-04-21 02:31:07 +0000414 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000415 return
416 clist = call_dict.keys()
417 clist.sort()
Tim Peters2344fae2001-01-15 00:50:52 +0000418 indent = ""
419 for func in clist:
420 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000421 value = call_dict[func]
422 if isinstance(value, tuple):
423 nc, cc, tt, ct = value
424 if nc != cc:
425 substats = '%d/%d' % (nc, cc)
426 else:
427 substats = '%d' % (nc,)
428 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
429 f8(tt), f8(ct), name)
430 left_width = name_size + 1
431 else:
432 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
433 left_width = name_size + 3
Skip Montanaro262fb922006-04-21 02:31:07 +0000434 print >> self.stream, indent*left_width + substats
Tim Peters2344fae2001-01-15 00:50:52 +0000435 indent = " "
436
Tim Peters2344fae2001-01-15 00:50:52 +0000437 def print_title(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000438 print >> self.stream, ' ncalls tottime percall cumtime percall',
439 print >> self.stream, 'filename:lineno(function)'
Tim Peters2344fae2001-01-15 00:50:52 +0000440
441 def print_line(self, func): # hack : should print percentages
442 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000443 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000444 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000445 c = c + '/' + str(cc)
Skip Montanaro262fb922006-04-21 02:31:07 +0000446 print >> self.stream, c.rjust(9),
447 print >> self.stream, f8(tt),
Tim Peters2344fae2001-01-15 00:50:52 +0000448 if nc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000449 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000450 else:
Antoine Pitroub9d49632010-01-04 23:22:44 +0000451 print >> self.stream, f8(float(tt)/nc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000452 print >> self.stream, f8(ct),
Tim Peters2344fae2001-01-15 00:50:52 +0000453 if cc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000454 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000455 else:
Antoine Pitroub9d49632010-01-04 23:22:44 +0000456 print >> self.stream, f8(float(ct)/cc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000457 print >> self.stream, func_std_string(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000458
Guido van Rossumadb31051994-06-23 11:42:52 +0000459class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000460 """This class provides a generic function for comparing any two tuples.
461 Each instance records a list of tuple-indices (from most significant
462 to least significant), and sort direction (ascending or decending) for
463 each tuple-index. The compare functions can then be used as the function
464 argument to the system sort() function when a list of tuples need to be
465 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000466
Tim Peters2344fae2001-01-15 00:50:52 +0000467 def __init__(self, comp_select_list):
468 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000469
Tim Peters2344fae2001-01-15 00:50:52 +0000470 def compare (self, left, right):
471 for index, direction in self.comp_select_list:
472 l = left[index]
473 r = right[index]
474 if l < r:
475 return -direction
476 if l > r:
477 return direction
478 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000479
Guido van Rossumadb31051994-06-23 11:42:52 +0000480#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000481# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000482
483def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000484 filename, line, name = func_name
485 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000486
487def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000488 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000489
490def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000491 if func_name[:2] == ('~', 0):
492 # special case for built-in functions
493 name = func_name[2]
494 if name.startswith('<') and name.endswith('>'):
495 return '{%s}' % name[1:-1]
496 else:
497 return name
498 else:
499 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000500
501#**************************************************************************
502# The following functions combine statists for pairs functions.
503# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000504# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000505#**************************************************************************
506
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000507def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000508 """Add together all the stats for two profile entries."""
509 cc, nc, tt, ct, callers = source
510 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000511 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000512 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000513
Guido van Rossumadb31051994-06-23 11:42:52 +0000514def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000515 """Combine two caller lists in a single list."""
516 new_callers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000517 for func, caller in target.iteritems():
518 new_callers[func] = caller
519 for func, caller in source.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000520 if func in new_callers:
Georg Brandla2a92cf2010-08-02 17:34:58 +0000521 if isinstance(caller, tuple):
522 # format used by cProfile
523 new_callers[func] = tuple([i[0] + i[1] for i in
524 zip(caller, new_callers[func])])
525 else:
526 # format used by profile
527 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000528 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000529 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000530 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000531
Guido van Rossumadb31051994-06-23 11:42:52 +0000532def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000533 """Sum the caller statistics to get total number of calls received."""
534 nc = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000535 for calls in callers.itervalues():
536 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000537 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000538
539#**************************************************************************
540# The following functions support printing of reports
541#**************************************************************************
542
543def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000544 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000545
546#**************************************************************************
547# Statistics browser added by ESR, April 2001
548#**************************************************************************
549
550if __name__ == '__main__':
551 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000552 try:
553 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000554 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000555 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000556
557 class ProfileBrowser(cmd.Cmd):
558 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000559 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000560 self.prompt = "% "
Georg Brandl9c5efad2010-08-02 21:29:14 +0000561 self.stats = None
562 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000563 if profile is not None:
Georg Brandl9c5efad2010-08-02 21:29:14 +0000564 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000565
566 def generic(self, fn, line):
567 args = line.split()
568 processed = []
569 for term in args:
570 try:
571 processed.append(int(term))
572 continue
573 except ValueError:
574 pass
575 try:
576 frac = float(term)
577 if frac > 1 or frac < 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000578 print >> self.stream, "Fraction argument must be in [0, 1]"
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000579 continue
580 processed.append(frac)
581 continue
582 except ValueError:
583 pass
584 processed.append(term)
585 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000586 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000587 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000588 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000589 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000590 def generic_help(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000591 print >> self.stream, "Arguments may be:"
592 print >> self.stream, "* An integer maximum number of entries to print."
593 print >> self.stream, "* A decimal fractional number between 0 and 1, controlling"
594 print >> self.stream, " what fraction of selected entries to print."
595 print >> self.stream, "* A regular expression; only entries with function names"
596 print >> self.stream, " that match it are printed."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000597
598 def do_add(self, line):
Georg Brandl0347c712010-08-01 19:02:09 +0000599 if self.stats:
600 self.stats.add(line)
601 else:
602 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000603 return 0
604 def help_add(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000605 print >> self.stream, "Add profile info from given file to current statistics object."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000606
607 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000608 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000609 def help_callees(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000610 print >> self.stream, "Print callees statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000611 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000612
613 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000614 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000615 def help_callers(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000616 print >> self.stream, "Print callers statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000617 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000618
619 def do_EOF(self, line):
Skip Montanaro262fb922006-04-21 02:31:07 +0000620 print >> self.stream, ""
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000621 return 1
622 def help_EOF(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000623 print >> self.stream, "Leave the profile brower."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000624
625 def do_quit(self, line):
626 return 1
627 def help_quit(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000628 print >> self.stream, "Leave the profile brower."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000629
630 def do_read(self, line):
631 if line:
632 try:
633 self.stats = Stats(line)
634 except IOError, args:
Skip Montanaro262fb922006-04-21 02:31:07 +0000635 print >> self.stream, args[1]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000636 return
Georg Brandl0347c712010-08-01 19:02:09 +0000637 except Exception as err:
638 print >> self.stream, err.__class__.__name__ + ':', err
639 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000640 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000641 elif len(self.prompt) > 2:
Georg Brandl0347c712010-08-01 19:02:09 +0000642 line = self.prompt[:-2]
643 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000644 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000645 print >> self.stream, "No statistics object is current -- cannot reload."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000646 return 0
647 def help_read(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000648 print >> self.stream, "Read in profile data from a specified file."
Georg Brandl0347c712010-08-01 19:02:09 +0000649 print >> self.stream, "Without argument, reload the current file."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000650
651 def do_reverse(self, line):
Georg Brandl0347c712010-08-01 19:02:09 +0000652 if self.stats:
653 self.stats.reverse_order()
654 else:
655 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000656 return 0
657 def help_reverse(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000658 print >> self.stream, "Reverse the sort order of the profiling report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000659
660 def do_sort(self, line):
Georg Brandl0347c712010-08-01 19:02:09 +0000661 if not self.stats:
662 print >> self.stream, "No statistics object is loaded."
663 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000664 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchlingd54e6992010-04-02 16:59:16 +0000665 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000666 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000667 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000668 print >> self.stream, "Valid sort keys (unique prefixes are accepted):"
Raymond Hettingere0d49722002-06-02 18:55:56 +0000669 for (key, value) in Stats.sort_arg_dict_default.iteritems():
Skip Montanaro262fb922006-04-21 02:31:07 +0000670 print >> self.stream, "%s -- %s" % (key, value[1])
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000671 return 0
672 def help_sort(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000673 print >> self.stream, "Sort profile data according to specified keys."
674 print >> self.stream, "(Typing `sort' without arguments lists valid keys.)"
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000675 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000676 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000677
678 def do_stats(self, line):
679 return self.generic('print_stats', line)
680 def help_stats(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000681 print >> self.stream, "Print statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000682 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000683
684 def do_strip(self, line):
Georg Brandl0347c712010-08-01 19:02:09 +0000685 if self.stats:
686 self.stats.strip_dirs()
687 else:
688 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000689 def help_strip(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000690 print >> self.stream, "Strip leading path information from filenames in the report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000691
Georg Brandl0347c712010-08-01 19:02:09 +0000692 def help_help(self):
693 print >> self.stream, "Show help for a given command."
694
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000695 def postcmd(self, stop, line):
696 if stop:
697 return stop
698 return None
699
700 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000701 if len(sys.argv) > 1:
702 initprofile = sys.argv[1]
703 else:
704 initprofile = None
705 try:
Neal Norwitze588c2b2006-06-11 07:27:56 +0000706 browser = ProfileBrowser(initprofile)
707 print >> browser.stream, "Welcome to the profile statistics browser."
708 browser.cmdloop()
709 print >> browser.stream, "Goodbye."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000710 except KeyboardInterrupt:
711 pass
712
713# That's all, folks.