blob: 3908f47c0be87dc6287946f5841e2a837e5fdc2c [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""Class for printing reports on profiled python code."""
2
Benjamin Peterson7e6b3aa2011-06-27 09:14:34 -05003# Written by James Roskind
Guido van Rossumadb31051994-06-23 11:42:52 +00004# Based on prior profile module by Sjoerd Mullender...
5# which was hacked somewhat by: Guido van Rossum
Guido van Rossumadb31051994-06-23 11:42:52 +00006
Benjamin Peterson7e6b3aa2011-06-27 09:14:34 -05007# Copyright Disney Enterprises, Inc. All Rights Reserved.
8# Licensed to PSF under a Contributor Agreement
Benjamin Petersoncfb77312011-06-27 09:18:46 -05009#
Benjamin Peterson7e6b3aa2011-06-27 09:14:34 -050010# Licensed under the Apache License, Version 2.0 (the "License");
11# you may not use this file except in compliance with the License.
12# You may obtain a copy of the License at
Benjamin Petersoncfb77312011-06-27 09:18:46 -050013#
Benjamin Peterson7e6b3aa2011-06-27 09:14:34 -050014# http://www.apache.org/licenses/LICENSE-2.0
Benjamin Petersoncfb77312011-06-27 09:18:46 -050015#
Benjamin Peterson7e6b3aa2011-06-27 09:14:34 -050016# Unless required by applicable law or agreed to in writing, software
17# distributed under the License is distributed on an "AS IS" BASIS,
18# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
19# either express or implied. See the License for the specific language
20# governing permissions and limitations under the License.
Guido van Rossumadb31051994-06-23 11:42:52 +000021
22
Skip Montanaro262fb922006-04-21 02:31:07 +000023import sys
Guido van Rossumadb31051994-06-23 11:42:52 +000024import os
25import time
Guido van Rossumadb31051994-06-23 11:42:52 +000026import marshal
Guido van Rossum9694fca1997-10-22 21:00:49 +000027import re
Raymond Hettingerbb006cf2010-04-04 21:45:01 +000028from functools import cmp_to_key
Guido van Rossumadb31051994-06-23 11:42:52 +000029
Skip Montanaroc62c81e2001-02-12 02:00:42 +000030__all__ = ["Stats"]
31
Guido van Rossumadb31051994-06-23 11:42:52 +000032class Stats:
Tim Peters2344fae2001-01-15 00:50:52 +000033 """This class is used for creating reports from data generated by the
34 Profile class. It is a "friend" of that class, and imports data either
35 by direct access to members of Profile class, or by reading in a dictionary
36 that was emitted (via marshal) from the Profile class.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000037
Tim Peters2344fae2001-01-15 00:50:52 +000038 The big change from the previous Profiler (in terms of raw functionality)
39 is that an "add()" method has been provided to combine Stats from
40 several distinct profile runs. Both the constructor and the add()
41 method now take arbitrarily many file names as arguments.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000042
Tim Peters2344fae2001-01-15 00:50:52 +000043 All the print methods now take an argument that indicates how many lines
44 to print. If the arg is a floating point number between 0 and 1.0, then
45 it is taken as a decimal percentage of the available lines to be printed
46 (e.g., .1 means print 10% of all available lines). If it is an integer,
47 it is taken to mean the number of lines of data that you wish to have
48 printed.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000049
Tim Peters2344fae2001-01-15 00:50:52 +000050 The sort_stats() method now processes some additional options (i.e., in
Skip Montanaro262fb922006-04-21 02:31:07 +000051 addition to the old -1, 0, 1, or 2). It takes an arbitrary number of
52 quoted strings to select the sort order. For example sort_stats('time',
53 'name') sorts on the major key of 'internal function time', and on the
54 minor key of 'the name of the function'. Look at the two tables in
55 sort_stats() and get_sort_arg_defs(self) for more examples.
Guido van Rossum54f22ed2000-02-04 15:10:34 +000056
Georg Brandl9c5efad2010-08-02 21:29:14 +000057 All methods return self, so you can string together commands like:
Tim Peters2344fae2001-01-15 00:50:52 +000058 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
59 print_stats(5).print_callers(5)
60 """
61
Skip Montanaro262fb922006-04-21 02:31:07 +000062 def __init__(self, *args, **kwds):
Martin Panterb1d867f2016-05-26 05:28:50 +000063 # I can't figure out how to explicitly specify a stream keyword arg
Skip Montanaro262fb922006-04-21 02:31:07 +000064 # with *args:
65 # def __init__(self, *args, stream=sys.stdout): ...
66 # so I use **kwds and sqauwk if something unexpected is passed in.
67 self.stream = sys.stdout
68 if "stream" in kwds:
69 self.stream = kwds["stream"]
70 del kwds["stream"]
71 if kwds:
72 keys = kwds.keys()
73 keys.sort()
74 extras = ", ".join(["%s=%s" % (k, kwds[k]) for k in keys])
75 raise ValueError, "unrecognized keyword args: %s" % extras
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:
Skip Montanaro262fb922006-04-21 02:31:07 +0000102 print >> self.stream, "Invalid timing data",
103 if self.files: print >> self.stream, self.files[-1],
104 print >> 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 = {}
Georg Brandl21d900f2006-11-26 19:27:47 +0000108 elif isinstance(arg, basestring):
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:
Ezio Melottib74e02e2012-10-24 23:43:02 +0300123 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
Brett Cannonc3ce0e52008-08-03 22:52:42 +0000132 if ("jprofile", 0, "profiler") in callers:
Tim Peters2344fae2001-01-15 00:50:52 +0000133 self.top_level[func] = None
134 if len(func_std_string(func)) > self.max_name_len:
135 self.max_name_len = len(func_std_string(func))
Guido van Rossumadb31051994-06-23 11:42:52 +0000136
Tim Peters2344fae2001-01-15 00:50:52 +0000137 def add(self, *arg_list):
138 if not arg_list: return self
Guido van Rossum68468eb2003-02-27 20:14:51 +0000139 if len(arg_list) > 1: self.add(*arg_list[1:])
Tim Peters2344fae2001-01-15 00:50:52 +0000140 other = arg_list[0]
Georg Brandlfb2c4592010-08-02 21:36:12 +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
Raymond Hettingere0d49722002-06-02 18:55:56 +0000155 for func, stat in other.stats.iteritems():
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."""
165 f = file(filename, 'wb')
166 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"),
Andrew Svetlove4831f62012-10-07 19:17:15 +0300175 "ncalls" : (((1,-1), ), "call count"),
176 "cumtime" : (((3,-1), ), "cumulative time"),
Tim Peters7d016852001-10-08 06:13:19 +0000177 "cumulative": (((3,-1), ), "cumulative time"),
178 "file" : (((4, 1), ), "file name"),
Andrew Svetlove4831f62012-10-07 19:17:15 +0300179 "filename" : (((4, 1), ), "file name"),
Tim Peters7d016852001-10-08 06:13:19 +0000180 "line" : (((5, 1), ), "line number"),
181 "module" : (((4, 1), ), "file name"),
182 "name" : (((6, 1), ), "function name"),
183 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
Andrew Svetlov75033a32012-10-07 18:57:21 +0300184 "pcalls" : (((0,-1), ), "primitive call count"),
Tim Peters7d016852001-10-08 06:13:19 +0000185 "stdname" : (((7, 1), ), "standard name"),
186 "time" : (((2,-1), ), "internal time"),
Andrew Svetlove4831f62012-10-07 19:17:15 +0300187 "tottime" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000188 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000189
Tim Peters2344fae2001-01-15 00:50:52 +0000190 def get_sort_arg_defs(self):
191 """Expand all abbreviations that are unique."""
192 if not self.sort_arg_dict:
193 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000194 bad_list = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000195 for word, tup in self.sort_arg_dict_default.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000196 fragment = word
197 while fragment:
198 if not fragment:
199 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000200 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000201 bad_list[fragment] = 0
202 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000203 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000204 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000205 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000206 del dict[word]
207 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000208
Tim Peters2344fae2001-01-15 00:50:52 +0000209 def sort_stats(self, *field):
210 if not field:
211 self.fcn_list = 0
212 return self
Georg Brandlfb2c4592010-08-02 21:36:12 +0000213 if len(field) == 1 and isinstance(field[0], (int, long)):
Tim Peters2344fae2001-01-15 00:50:52 +0000214 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000215 field = [ {-1: "stdname",
Georg Brandl9c5efad2010-08-02 21:29:14 +0000216 0: "calls",
217 1: "time",
218 2: "cumulative"}[field[0]] ]
Tim Peters2344fae2001-01-15 00:50:52 +0000219
220 sort_arg_defs = self.get_sort_arg_defs()
221 sort_tuple = ()
222 self.sort_type = ""
223 connector = ""
224 for word in field:
225 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000226 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000227 connector = ", "
228
229 stats_list = []
Raymond Hettingere0d49722002-06-02 18:55:56 +0000230 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Tim Peters7d016852001-10-08 06:13:19 +0000231 stats_list.append((cc, nc, tt, ct) + func +
232 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000233
Raymond Hettingerbb006cf2010-04-04 21:45:01 +0000234 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000235
236 self.fcn_list = fcn_list = []
237 for tuple in stats_list:
238 fcn_list.append(tuple[-1])
239 return self
240
Tim Peters2344fae2001-01-15 00:50:52 +0000241 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000242 if self.fcn_list:
243 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000244 return self
245
246 def strip_dirs(self):
247 oldstats = self.stats
248 self.stats = newstats = {}
249 max_name_len = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000250 for func, (cc, nc, tt, ct, callers) in oldstats.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000251 newfunc = func_strip_path(func)
252 if len(func_std_string(newfunc)) > max_name_len:
253 max_name_len = len(func_std_string(newfunc))
254 newcallers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000255 for func2, caller in callers.iteritems():
256 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000257
Raymond Hettinger54f02222002-06-01 14:18:47 +0000258 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000259 newstats[newfunc] = add_func_stats(
260 newstats[newfunc],
261 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000262 else:
263 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
264 old_top = self.top_level
265 self.top_level = new_top = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000266 for func in old_top:
Tim Peters2344fae2001-01-15 00:50:52 +0000267 new_top[func_strip_path(func)] = None
268
269 self.max_name_len = max_name_len
270
271 self.fcn_list = None
272 self.all_callees = None
273 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000274
Tim Peters2344fae2001-01-15 00:50:52 +0000275 def calc_callees(self):
276 if self.all_callees: return
277 self.all_callees = all_callees = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000278 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000279 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000280 all_callees[func] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000281 for func2, caller in callers.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000282 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000283 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000284 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000285 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000286
Tim Peters2344fae2001-01-15 00:50:52 +0000287 #******************************************************************
288 # The following functions support actual printing of reports
289 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000290
Tim Peters2344fae2001-01-15 00:50:52 +0000291 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000292
Tim Peters2344fae2001-01-15 00:50:52 +0000293 def eval_print_amount(self, sel, list, msg):
294 new_list = list
Georg Brandlfb2c4592010-08-02 21:36:12 +0000295 if isinstance(sel, basestring):
Georg Brandl9c5efad2010-08-02 21:29:14 +0000296 try:
297 rex = re.compile(sel)
298 except re.error:
299 msg += " <Invalid regular expression %r>\n" % sel
300 return new_list, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000301 new_list = []
302 for func in list:
Georg Brandl9c5efad2010-08-02 21:29:14 +0000303 if rex.search(func_std_string(func)):
Tim Peters2344fae2001-01-15 00:50:52 +0000304 new_list.append(func)
305 else:
306 count = len(list)
Georg Brandl9c5efad2010-08-02 21:29:14 +0000307 if isinstance(sel, float) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000308 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000309 new_list = list[:count]
Georg Brandlfb2c4592010-08-02 21:36:12 +0000310 elif isinstance(sel, (int, long)) and 0 <= sel < count:
Tim Peters2344fae2001-01-15 00:50:52 +0000311 count = sel
312 new_list = list[:count]
313 if len(list) != len(new_list):
Georg Brandl9c5efad2010-08-02 21:29:14 +0000314 msg += " List reduced from %r to %r due to restriction <%r>\n" % (
315 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000316
Tim Peters2344fae2001-01-15 00:50:52 +0000317 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000318
Tim Peters2344fae2001-01-15 00:50:52 +0000319 def get_print_list(self, sel_list):
320 width = self.max_name_len
321 if self.fcn_list:
Georg Brandl9c5efad2010-08-02 21:29:14 +0000322 stat_list = self.fcn_list[:]
Tim Peters2344fae2001-01-15 00:50:52 +0000323 msg = " Ordered by: " + self.sort_type + '\n'
324 else:
Georg Brandlfb2c4592010-08-02 21:36:12 +0000325 stat_list = self.stats.keys()
Tim Peters2344fae2001-01-15 00:50:52 +0000326 msg = " Random listing order was used\n"
327
328 for selection in sel_list:
Georg Brandl9c5efad2010-08-02 21:29:14 +0000329 stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000330
Georg Brandl9c5efad2010-08-02 21:29:14 +0000331 count = len(stat_list)
Tim Peters2344fae2001-01-15 00:50:52 +0000332
Georg Brandl9c5efad2010-08-02 21:29:14 +0000333 if not stat_list:
334 return 0, stat_list
Skip Montanaro262fb922006-04-21 02:31:07 +0000335 print >> self.stream, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000336 if count < len(self.stats):
337 width = 0
Georg Brandl9c5efad2010-08-02 21:29:14 +0000338 for func in stat_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000339 if len(func_std_string(func)) > width:
340 width = len(func_std_string(func))
Georg Brandl9c5efad2010-08-02 21:29:14 +0000341 return width+2, stat_list
Tim Peters2344fae2001-01-15 00:50:52 +0000342
343 def print_stats(self, *amount):
344 for filename in self.files:
Skip Montanaro262fb922006-04-21 02:31:07 +0000345 print >> self.stream, filename
346 if self.files: print >> self.stream
Tim Peters7d016852001-10-08 06:13:19 +0000347 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000348 for func in self.top_level:
Skip Montanaro262fb922006-04-21 02:31:07 +0000349 print >> self.stream, indent, func_get_function_name(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000350
Skip Montanaro262fb922006-04-21 02:31:07 +0000351 print >> self.stream, indent, self.total_calls, "function calls",
Tim Peters2344fae2001-01-15 00:50:52 +0000352 if self.total_calls != self.prim_calls:
Skip Montanaro262fb922006-04-21 02:31:07 +0000353 print >> self.stream, "(%d primitive calls)" % self.prim_calls,
Senthil Kumaran8284bd62010-11-20 17:09:14 +0000354 print >> self.stream, "in %.3f seconds" % self.total_tt
Skip Montanaro262fb922006-04-21 02:31:07 +0000355 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000356 width, list = self.get_print_list(amount)
357 if list:
358 self.print_title()
359 for func in list:
360 self.print_line(func)
Skip Montanaro262fb922006-04-21 02:31:07 +0000361 print >> self.stream
362 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000363 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000364
Tim Peters2344fae2001-01-15 00:50:52 +0000365 def print_callees(self, *amount):
366 width, list = self.get_print_list(amount)
367 if list:
368 self.calc_callees()
369
370 self.print_call_heading(width, "called...")
371 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000372 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000373 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000374 else:
375 self.print_call_line(width, func, {})
Skip Montanaro262fb922006-04-21 02:31:07 +0000376 print >> self.stream
377 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000378 return self
379
380 def print_callers(self, *amount):
381 width, list = self.get_print_list(amount)
382 if list:
383 self.print_call_heading(width, "was called by...")
384 for func in list:
385 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000386 self.print_call_line(width, func, callers, "<-")
Skip Montanaro262fb922006-04-21 02:31:07 +0000387 print >> self.stream
388 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000389 return self
390
391 def print_call_heading(self, name_size, column_title):
Skip Montanaro262fb922006-04-21 02:31:07 +0000392 print >> self.stream, "Function ".ljust(name_size) + column_title
Armin Rigoa871ef22006-02-08 12:53:56 +0000393 # print sub-header only if we have new-style callers
394 subheader = False
395 for cc, nc, tt, ct, callers in self.stats.itervalues():
396 if callers:
397 value = callers.itervalues().next()
398 subheader = isinstance(value, tuple)
399 break
400 if subheader:
Skip Montanaro262fb922006-04-21 02:31:07 +0000401 print >> self.stream, " "*name_size + " ncalls tottime cumtime"
Guido van Rossumadb31051994-06-23 11:42:52 +0000402
Armin Rigoa871ef22006-02-08 12:53:56 +0000403 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Skip Montanaro262fb922006-04-21 02:31:07 +0000404 print >> self.stream, func_std_string(source).ljust(name_size) + arrow,
Tim Peters2344fae2001-01-15 00:50:52 +0000405 if not call_dict:
Skip Montanaro262fb922006-04-21 02:31:07 +0000406 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000407 return
408 clist = call_dict.keys()
409 clist.sort()
Tim Peters2344fae2001-01-15 00:50:52 +0000410 indent = ""
411 for func in clist:
412 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000413 value = call_dict[func]
414 if isinstance(value, tuple):
415 nc, cc, tt, ct = value
416 if nc != cc:
417 substats = '%d/%d' % (nc, cc)
418 else:
419 substats = '%d' % (nc,)
420 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
421 f8(tt), f8(ct), name)
422 left_width = name_size + 1
423 else:
424 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
425 left_width = name_size + 3
Skip Montanaro262fb922006-04-21 02:31:07 +0000426 print >> self.stream, indent*left_width + substats
Tim Peters2344fae2001-01-15 00:50:52 +0000427 indent = " "
428
Tim Peters2344fae2001-01-15 00:50:52 +0000429 def print_title(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000430 print >> self.stream, ' ncalls tottime percall cumtime percall',
431 print >> self.stream, 'filename:lineno(function)'
Tim Peters2344fae2001-01-15 00:50:52 +0000432
433 def print_line(self, func): # hack : should print percentages
434 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000435 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000436 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000437 c = c + '/' + str(cc)
Skip Montanaro262fb922006-04-21 02:31:07 +0000438 print >> self.stream, c.rjust(9),
439 print >> self.stream, f8(tt),
Tim Peters2344fae2001-01-15 00:50:52 +0000440 if nc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000441 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000442 else:
Antoine Pitroub9d49632010-01-04 23:22:44 +0000443 print >> self.stream, f8(float(tt)/nc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000444 print >> self.stream, f8(ct),
Tim Peters2344fae2001-01-15 00:50:52 +0000445 if cc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000446 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000447 else:
Antoine Pitroub9d49632010-01-04 23:22:44 +0000448 print >> self.stream, f8(float(ct)/cc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000449 print >> self.stream, func_std_string(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000450
Guido van Rossumadb31051994-06-23 11:42:52 +0000451class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000452 """This class provides a generic function for comparing any two tuples.
453 Each instance records a list of tuple-indices (from most significant
454 to least significant), and sort direction (ascending or decending) for
455 each tuple-index. The compare functions can then be used as the function
456 argument to the system sort() function when a list of tuples need to be
457 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000458
Tim Peters2344fae2001-01-15 00:50:52 +0000459 def __init__(self, comp_select_list):
460 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000461
Tim Peters2344fae2001-01-15 00:50:52 +0000462 def compare (self, left, right):
463 for index, direction in self.comp_select_list:
464 l = left[index]
465 r = right[index]
466 if l < r:
467 return -direction
468 if l > r:
469 return direction
470 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000471
Guido van Rossumadb31051994-06-23 11:42:52 +0000472#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000473# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000474
475def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000476 filename, line, name = func_name
477 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000478
479def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000480 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000481
482def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000483 if func_name[:2] == ('~', 0):
484 # special case for built-in functions
485 name = func_name[2]
486 if name.startswith('<') and name.endswith('>'):
487 return '{%s}' % name[1:-1]
488 else:
489 return name
490 else:
491 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000492
493#**************************************************************************
494# The following functions combine statists for pairs functions.
495# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000496# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000497#**************************************************************************
498
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000499def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000500 """Add together all the stats for two profile entries."""
501 cc, nc, tt, ct, callers = source
502 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000503 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000504 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000505
Guido van Rossumadb31051994-06-23 11:42:52 +0000506def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000507 """Combine two caller lists in a single list."""
508 new_callers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000509 for func, caller in target.iteritems():
510 new_callers[func] = caller
511 for func, caller in source.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000512 if func in new_callers:
Georg Brandla2a92cf2010-08-02 17:34:58 +0000513 if isinstance(caller, tuple):
514 # format used by cProfile
515 new_callers[func] = tuple([i[0] + i[1] for i in
516 zip(caller, new_callers[func])])
517 else:
518 # format used by profile
519 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000520 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000521 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000522 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000523
Guido van Rossumadb31051994-06-23 11:42:52 +0000524def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000525 """Sum the caller statistics to get total number of calls received."""
526 nc = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000527 for calls in callers.itervalues():
528 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000529 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000530
531#**************************************************************************
532# The following functions support printing of reports
533#**************************************************************************
534
535def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000536 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000537
538#**************************************************************************
539# Statistics browser added by ESR, April 2001
540#**************************************************************************
541
542if __name__ == '__main__':
543 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000544 try:
545 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000546 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000547 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000548
549 class ProfileBrowser(cmd.Cmd):
550 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000551 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000552 self.prompt = "% "
Georg Brandl9c5efad2010-08-02 21:29:14 +0000553 self.stats = None
554 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000555 if profile is not None:
Georg Brandl9c5efad2010-08-02 21:29:14 +0000556 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000557
558 def generic(self, fn, line):
559 args = line.split()
560 processed = []
561 for term in args:
562 try:
563 processed.append(int(term))
564 continue
565 except ValueError:
566 pass
567 try:
568 frac = float(term)
569 if frac > 1 or frac < 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000570 print >> self.stream, "Fraction argument must be in [0, 1]"
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000571 continue
572 processed.append(frac)
573 continue
574 except ValueError:
575 pass
576 processed.append(term)
577 if self.stats:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000578 getattr(self.stats, fn)(*processed)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000579 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000580 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000581 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000582 def generic_help(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000583 print >> self.stream, "Arguments may be:"
584 print >> self.stream, "* An integer maximum number of entries to print."
585 print >> self.stream, "* A decimal fractional number between 0 and 1, controlling"
586 print >> self.stream, " what fraction of selected entries to print."
587 print >> self.stream, "* A regular expression; only entries with function names"
588 print >> self.stream, " that match it are printed."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000589
590 def do_add(self, line):
Georg Brandl0347c712010-08-01 19:02:09 +0000591 if self.stats:
592 self.stats.add(line)
593 else:
594 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000595 return 0
596 def help_add(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000597 print >> self.stream, "Add profile info from given file to current statistics object."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000598
599 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000600 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000601 def help_callees(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000602 print >> self.stream, "Print callees statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000603 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000604
605 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000606 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000607 def help_callers(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000608 print >> self.stream, "Print callers statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000609 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000610
611 def do_EOF(self, line):
Skip Montanaro262fb922006-04-21 02:31:07 +0000612 print >> self.stream, ""
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000613 return 1
614 def help_EOF(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000615 print >> self.stream, "Leave the profile brower."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000616
617 def do_quit(self, line):
618 return 1
619 def help_quit(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000620 print >> self.stream, "Leave the profile brower."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000621
622 def do_read(self, line):
623 if line:
624 try:
625 self.stats = Stats(line)
626 except IOError, args:
Skip Montanaro262fb922006-04-21 02:31:07 +0000627 print >> self.stream, args[1]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000628 return
Georg Brandl0347c712010-08-01 19:02:09 +0000629 except Exception as err:
630 print >> self.stream, err.__class__.__name__ + ':', err
631 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000632 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000633 elif len(self.prompt) > 2:
Georg Brandl0347c712010-08-01 19:02:09 +0000634 line = self.prompt[:-2]
635 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000636 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000637 print >> self.stream, "No statistics object is current -- cannot reload."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000638 return 0
639 def help_read(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000640 print >> self.stream, "Read in profile data from a specified file."
Georg Brandl0347c712010-08-01 19:02:09 +0000641 print >> self.stream, "Without argument, reload the current file."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000642
643 def do_reverse(self, line):
Georg Brandl0347c712010-08-01 19:02:09 +0000644 if self.stats:
645 self.stats.reverse_order()
646 else:
647 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000648 return 0
649 def help_reverse(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000650 print >> self.stream, "Reverse the sort order of the profiling report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000651
652 def do_sort(self, line):
Georg Brandl0347c712010-08-01 19:02:09 +0000653 if not self.stats:
654 print >> self.stream, "No statistics object is loaded."
655 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000656 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchlingd54e6992010-04-02 16:59:16 +0000657 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000658 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000659 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000660 print >> self.stream, "Valid sort keys (unique prefixes are accepted):"
Raymond Hettingere0d49722002-06-02 18:55:56 +0000661 for (key, value) in Stats.sort_arg_dict_default.iteritems():
Skip Montanaro262fb922006-04-21 02:31:07 +0000662 print >> self.stream, "%s -- %s" % (key, value[1])
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000663 return 0
664 def help_sort(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000665 print >> self.stream, "Sort profile data according to specified keys."
666 print >> self.stream, "(Typing `sort' without arguments lists valid keys.)"
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000667 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000668 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000669
670 def do_stats(self, line):
671 return self.generic('print_stats', line)
672 def help_stats(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000673 print >> self.stream, "Print statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000674 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000675
676 def do_strip(self, line):
Georg Brandl0347c712010-08-01 19:02:09 +0000677 if self.stats:
678 self.stats.strip_dirs()
679 else:
680 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000681 def help_strip(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000682 print >> self.stream, "Strip leading path information from filenames in the report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000683
Georg Brandl0347c712010-08-01 19:02:09 +0000684 def help_help(self):
685 print >> self.stream, "Show help for a given command."
686
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000687 def postcmd(self, stop, line):
688 if stop:
689 return stop
690 return None
691
692 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000693 if len(sys.argv) > 1:
694 initprofile = sys.argv[1]
695 else:
696 initprofile = None
697 try:
Neal Norwitze588c2b2006-06-11 07:27:56 +0000698 browser = ProfileBrowser(initprofile)
699 print >> browser.stream, "Welcome to the profile statistics browser."
700 browser.cmdloop()
701 print >> browser.stream, "Goodbye."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000702 except KeyboardInterrupt:
703 pass
704
705# That's all, folks.