blob: 3dc61d69ee9dd820a069389912eaa92f81d27715 [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):
63 # I can't figure out how to explictly specify a stream keyword arg
64 # 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:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000123 raise TypeError, "Cannot create or construct a %r object from '%r''" % (
124 self.__class__, arg)
Tim Peters2344fae2001-01-15 00:50:52 +0000125 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000126
Tim Peters2344fae2001-01-15 00:50:52 +0000127 def get_top_level_stats(self):
Tim Peters7d016852001-10-08 06:13:19 +0000128 for func, (cc, nc, tt, ct, callers) in self.stats.items():
129 self.total_calls += nc
130 self.prim_calls += cc
131 self.total_tt += tt
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"),
175 "cumulative": (((3,-1), ), "cumulative time"),
176 "file" : (((4, 1), ), "file name"),
177 "line" : (((5, 1), ), "line number"),
178 "module" : (((4, 1), ), "file name"),
179 "name" : (((6, 1), ), "function name"),
180 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
181 "pcalls" : (((0,-1), ), "call count"),
182 "stdname" : (((7, 1), ), "standard name"),
183 "time" : (((2,-1), ), "internal time"),
Tim Peters2344fae2001-01-15 00:50:52 +0000184 }
Guido van Rossumadb31051994-06-23 11:42:52 +0000185
Tim Peters2344fae2001-01-15 00:50:52 +0000186 def get_sort_arg_defs(self):
187 """Expand all abbreviations that are unique."""
188 if not self.sort_arg_dict:
189 self.sort_arg_dict = dict = {}
Tim Peters2344fae2001-01-15 00:50:52 +0000190 bad_list = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000191 for word, tup in self.sort_arg_dict_default.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000192 fragment = word
193 while fragment:
194 if not fragment:
195 break
Raymond Hettinger54f02222002-06-01 14:18:47 +0000196 if fragment in dict:
Tim Peters2344fae2001-01-15 00:50:52 +0000197 bad_list[fragment] = 0
198 break
Raymond Hettingere0d49722002-06-02 18:55:56 +0000199 dict[fragment] = tup
Tim Peters2344fae2001-01-15 00:50:52 +0000200 fragment = fragment[:-1]
Raymond Hettingere0d49722002-06-02 18:55:56 +0000201 for word in bad_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000202 del dict[word]
203 return self.sort_arg_dict
Guido van Rossumadb31051994-06-23 11:42:52 +0000204
Tim Peters2344fae2001-01-15 00:50:52 +0000205 def sort_stats(self, *field):
206 if not field:
207 self.fcn_list = 0
208 return self
Georg Brandlfb2c4592010-08-02 21:36:12 +0000209 if len(field) == 1 and isinstance(field[0], (int, long)):
Tim Peters2344fae2001-01-15 00:50:52 +0000210 # Be compatible with old profiler
Tim Peters7d016852001-10-08 06:13:19 +0000211 field = [ {-1: "stdname",
Georg Brandl9c5efad2010-08-02 21:29:14 +0000212 0: "calls",
213 1: "time",
214 2: "cumulative"}[field[0]] ]
Tim Peters2344fae2001-01-15 00:50:52 +0000215
216 sort_arg_defs = self.get_sort_arg_defs()
217 sort_tuple = ()
218 self.sort_type = ""
219 connector = ""
220 for word in field:
221 sort_tuple = sort_tuple + sort_arg_defs[word][0]
Tim Peters7d016852001-10-08 06:13:19 +0000222 self.sort_type += connector + sort_arg_defs[word][1]
Tim Peters2344fae2001-01-15 00:50:52 +0000223 connector = ", "
224
225 stats_list = []
Raymond Hettingere0d49722002-06-02 18:55:56 +0000226 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Tim Peters7d016852001-10-08 06:13:19 +0000227 stats_list.append((cc, nc, tt, ct) + func +
228 (func_std_string(func), func))
Tim Peters2344fae2001-01-15 00:50:52 +0000229
Raymond Hettingerbb006cf2010-04-04 21:45:01 +0000230 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
Tim Peters2344fae2001-01-15 00:50:52 +0000231
232 self.fcn_list = fcn_list = []
233 for tuple in stats_list:
234 fcn_list.append(tuple[-1])
235 return self
236
Tim Peters2344fae2001-01-15 00:50:52 +0000237 def reverse_order(self):
Tim Peters7d016852001-10-08 06:13:19 +0000238 if self.fcn_list:
239 self.fcn_list.reverse()
Tim Peters2344fae2001-01-15 00:50:52 +0000240 return self
241
242 def strip_dirs(self):
243 oldstats = self.stats
244 self.stats = newstats = {}
245 max_name_len = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000246 for func, (cc, nc, tt, ct, callers) in oldstats.iteritems():
Tim Peters2344fae2001-01-15 00:50:52 +0000247 newfunc = func_strip_path(func)
248 if len(func_std_string(newfunc)) > max_name_len:
249 max_name_len = len(func_std_string(newfunc))
250 newcallers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000251 for func2, caller in callers.iteritems():
252 newcallers[func_strip_path(func2)] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000253
Raymond Hettinger54f02222002-06-01 14:18:47 +0000254 if newfunc in newstats:
Tim Peters7d016852001-10-08 06:13:19 +0000255 newstats[newfunc] = add_func_stats(
256 newstats[newfunc],
257 (cc, nc, tt, ct, newcallers))
Tim Peters2344fae2001-01-15 00:50:52 +0000258 else:
259 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
260 old_top = self.top_level
261 self.top_level = new_top = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000262 for func in old_top:
Tim Peters2344fae2001-01-15 00:50:52 +0000263 new_top[func_strip_path(func)] = None
264
265 self.max_name_len = max_name_len
266
267 self.fcn_list = None
268 self.all_callees = None
269 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000270
Tim Peters2344fae2001-01-15 00:50:52 +0000271 def calc_callees(self):
272 if self.all_callees: return
273 self.all_callees = all_callees = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000274 for func, (cc, nc, tt, ct, callers) in self.stats.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000275 if not func in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000276 all_callees[func] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000277 for func2, caller in callers.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000278 if not func2 in all_callees:
Tim Peters2344fae2001-01-15 00:50:52 +0000279 all_callees[func2] = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000280 all_callees[func2][func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000281 return
Guido van Rossumadb31051994-06-23 11:42:52 +0000282
Tim Peters2344fae2001-01-15 00:50:52 +0000283 #******************************************************************
284 # The following functions support actual printing of reports
285 #******************************************************************
Guido van Rossumadb31051994-06-23 11:42:52 +0000286
Tim Peters2344fae2001-01-15 00:50:52 +0000287 # Optional "amount" is either a line count, or a percentage of lines.
Guido van Rossumadb31051994-06-23 11:42:52 +0000288
Tim Peters2344fae2001-01-15 00:50:52 +0000289 def eval_print_amount(self, sel, list, msg):
290 new_list = list
Georg Brandlfb2c4592010-08-02 21:36:12 +0000291 if isinstance(sel, basestring):
Georg Brandl9c5efad2010-08-02 21:29:14 +0000292 try:
293 rex = re.compile(sel)
294 except re.error:
295 msg += " <Invalid regular expression %r>\n" % sel
296 return new_list, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000297 new_list = []
298 for func in list:
Georg Brandl9c5efad2010-08-02 21:29:14 +0000299 if rex.search(func_std_string(func)):
Tim Peters2344fae2001-01-15 00:50:52 +0000300 new_list.append(func)
301 else:
302 count = len(list)
Georg Brandl9c5efad2010-08-02 21:29:14 +0000303 if isinstance(sel, float) and 0.0 <= sel < 1.0:
Tim Peters7d016852001-10-08 06:13:19 +0000304 count = int(count * sel + .5)
Tim Peters2344fae2001-01-15 00:50:52 +0000305 new_list = list[:count]
Georg Brandlfb2c4592010-08-02 21:36:12 +0000306 elif isinstance(sel, (int, long)) and 0 <= sel < count:
Tim Peters2344fae2001-01-15 00:50:52 +0000307 count = sel
308 new_list = list[:count]
309 if len(list) != len(new_list):
Georg Brandl9c5efad2010-08-02 21:29:14 +0000310 msg += " List reduced from %r to %r due to restriction <%r>\n" % (
311 len(list), len(new_list), sel)
Guido van Rossumadb31051994-06-23 11:42:52 +0000312
Tim Peters2344fae2001-01-15 00:50:52 +0000313 return new_list, msg
Guido van Rossumadb31051994-06-23 11:42:52 +0000314
Tim Peters2344fae2001-01-15 00:50:52 +0000315 def get_print_list(self, sel_list):
316 width = self.max_name_len
317 if self.fcn_list:
Georg Brandl9c5efad2010-08-02 21:29:14 +0000318 stat_list = self.fcn_list[:]
Tim Peters2344fae2001-01-15 00:50:52 +0000319 msg = " Ordered by: " + self.sort_type + '\n'
320 else:
Georg Brandlfb2c4592010-08-02 21:36:12 +0000321 stat_list = self.stats.keys()
Tim Peters2344fae2001-01-15 00:50:52 +0000322 msg = " Random listing order was used\n"
323
324 for selection in sel_list:
Georg Brandl9c5efad2010-08-02 21:29:14 +0000325 stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
Tim Peters2344fae2001-01-15 00:50:52 +0000326
Georg Brandl9c5efad2010-08-02 21:29:14 +0000327 count = len(stat_list)
Tim Peters2344fae2001-01-15 00:50:52 +0000328
Georg Brandl9c5efad2010-08-02 21:29:14 +0000329 if not stat_list:
330 return 0, stat_list
Skip Montanaro262fb922006-04-21 02:31:07 +0000331 print >> self.stream, msg
Tim Peters2344fae2001-01-15 00:50:52 +0000332 if count < len(self.stats):
333 width = 0
Georg Brandl9c5efad2010-08-02 21:29:14 +0000334 for func in stat_list:
Tim Peters2344fae2001-01-15 00:50:52 +0000335 if len(func_std_string(func)) > width:
336 width = len(func_std_string(func))
Georg Brandl9c5efad2010-08-02 21:29:14 +0000337 return width+2, stat_list
Tim Peters2344fae2001-01-15 00:50:52 +0000338
339 def print_stats(self, *amount):
340 for filename in self.files:
Skip Montanaro262fb922006-04-21 02:31:07 +0000341 print >> self.stream, filename
342 if self.files: print >> self.stream
Tim Peters7d016852001-10-08 06:13:19 +0000343 indent = ' ' * 8
Raymond Hettingere0d49722002-06-02 18:55:56 +0000344 for func in self.top_level:
Skip Montanaro262fb922006-04-21 02:31:07 +0000345 print >> self.stream, indent, func_get_function_name(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000346
Skip Montanaro262fb922006-04-21 02:31:07 +0000347 print >> self.stream, indent, self.total_calls, "function calls",
Tim Peters2344fae2001-01-15 00:50:52 +0000348 if self.total_calls != self.prim_calls:
Skip Montanaro262fb922006-04-21 02:31:07 +0000349 print >> self.stream, "(%d primitive calls)" % self.prim_calls,
Senthil Kumaran8284bd62010-11-20 17:09:14 +0000350 print >> self.stream, "in %.3f seconds" % self.total_tt
Skip Montanaro262fb922006-04-21 02:31:07 +0000351 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000352 width, list = self.get_print_list(amount)
353 if list:
354 self.print_title()
355 for func in list:
356 self.print_line(func)
Skip Montanaro262fb922006-04-21 02:31:07 +0000357 print >> self.stream
358 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000359 return self
Guido van Rossumadb31051994-06-23 11:42:52 +0000360
Tim Peters2344fae2001-01-15 00:50:52 +0000361 def print_callees(self, *amount):
362 width, list = self.get_print_list(amount)
363 if list:
364 self.calc_callees()
365
366 self.print_call_heading(width, "called...")
367 for func in list:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000368 if func in self.all_callees:
Tim Peters7d016852001-10-08 06:13:19 +0000369 self.print_call_line(width, func, self.all_callees[func])
Tim Peters2344fae2001-01-15 00:50:52 +0000370 else:
371 self.print_call_line(width, func, {})
Skip Montanaro262fb922006-04-21 02:31:07 +0000372 print >> self.stream
373 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000374 return self
375
376 def print_callers(self, *amount):
377 width, list = self.get_print_list(amount)
378 if list:
379 self.print_call_heading(width, "was called by...")
380 for func in list:
381 cc, nc, tt, ct, callers = self.stats[func]
Armin Rigoa871ef22006-02-08 12:53:56 +0000382 self.print_call_line(width, func, callers, "<-")
Skip Montanaro262fb922006-04-21 02:31:07 +0000383 print >> self.stream
384 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000385 return self
386
387 def print_call_heading(self, name_size, column_title):
Skip Montanaro262fb922006-04-21 02:31:07 +0000388 print >> self.stream, "Function ".ljust(name_size) + column_title
Armin Rigoa871ef22006-02-08 12:53:56 +0000389 # print sub-header only if we have new-style callers
390 subheader = False
391 for cc, nc, tt, ct, callers in self.stats.itervalues():
392 if callers:
393 value = callers.itervalues().next()
394 subheader = isinstance(value, tuple)
395 break
396 if subheader:
Skip Montanaro262fb922006-04-21 02:31:07 +0000397 print >> self.stream, " "*name_size + " ncalls tottime cumtime"
Guido van Rossumadb31051994-06-23 11:42:52 +0000398
Armin Rigoa871ef22006-02-08 12:53:56 +0000399 def print_call_line(self, name_size, source, call_dict, arrow="->"):
Skip Montanaro262fb922006-04-21 02:31:07 +0000400 print >> self.stream, func_std_string(source).ljust(name_size) + arrow,
Tim Peters2344fae2001-01-15 00:50:52 +0000401 if not call_dict:
Skip Montanaro262fb922006-04-21 02:31:07 +0000402 print >> self.stream
Tim Peters2344fae2001-01-15 00:50:52 +0000403 return
404 clist = call_dict.keys()
405 clist.sort()
Tim Peters2344fae2001-01-15 00:50:52 +0000406 indent = ""
407 for func in clist:
408 name = func_std_string(func)
Armin Rigoa871ef22006-02-08 12:53:56 +0000409 value = call_dict[func]
410 if isinstance(value, tuple):
411 nc, cc, tt, ct = value
412 if nc != cc:
413 substats = '%d/%d' % (nc, cc)
414 else:
415 substats = '%d' % (nc,)
416 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
417 f8(tt), f8(ct), name)
418 left_width = name_size + 1
419 else:
420 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
421 left_width = name_size + 3
Skip Montanaro262fb922006-04-21 02:31:07 +0000422 print >> self.stream, indent*left_width + substats
Tim Peters2344fae2001-01-15 00:50:52 +0000423 indent = " "
424
Tim Peters2344fae2001-01-15 00:50:52 +0000425 def print_title(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000426 print >> self.stream, ' ncalls tottime percall cumtime percall',
427 print >> self.stream, 'filename:lineno(function)'
Tim Peters2344fae2001-01-15 00:50:52 +0000428
429 def print_line(self, func): # hack : should print percentages
430 cc, nc, tt, ct, callers = self.stats[func]
Tim Peters7d016852001-10-08 06:13:19 +0000431 c = str(nc)
Tim Peters2344fae2001-01-15 00:50:52 +0000432 if nc != cc:
Tim Peters7d016852001-10-08 06:13:19 +0000433 c = c + '/' + str(cc)
Skip Montanaro262fb922006-04-21 02:31:07 +0000434 print >> self.stream, c.rjust(9),
435 print >> self.stream, f8(tt),
Tim Peters2344fae2001-01-15 00:50:52 +0000436 if nc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000437 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000438 else:
Antoine Pitroub9d49632010-01-04 23:22:44 +0000439 print >> self.stream, f8(float(tt)/nc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000440 print >> self.stream, f8(ct),
Tim Peters2344fae2001-01-15 00:50:52 +0000441 if cc == 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000442 print >> self.stream, ' '*8,
Tim Peters2344fae2001-01-15 00:50:52 +0000443 else:
Antoine Pitroub9d49632010-01-04 23:22:44 +0000444 print >> self.stream, f8(float(ct)/cc),
Skip Montanaro262fb922006-04-21 02:31:07 +0000445 print >> self.stream, func_std_string(func)
Tim Peters2344fae2001-01-15 00:50:52 +0000446
Guido van Rossumadb31051994-06-23 11:42:52 +0000447class TupleComp:
Tim Peters2344fae2001-01-15 00:50:52 +0000448 """This class provides a generic function for comparing any two tuples.
449 Each instance records a list of tuple-indices (from most significant
450 to least significant), and sort direction (ascending or decending) for
451 each tuple-index. The compare functions can then be used as the function
452 argument to the system sort() function when a list of tuples need to be
453 sorted in the instances order."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000454
Tim Peters2344fae2001-01-15 00:50:52 +0000455 def __init__(self, comp_select_list):
456 self.comp_select_list = comp_select_list
Guido van Rossumadb31051994-06-23 11:42:52 +0000457
Tim Peters2344fae2001-01-15 00:50:52 +0000458 def compare (self, left, right):
459 for index, direction in self.comp_select_list:
460 l = left[index]
461 r = right[index]
462 if l < r:
463 return -direction
464 if l > r:
465 return direction
466 return 0
Guido van Rossumadb31051994-06-23 11:42:52 +0000467
Guido van Rossumadb31051994-06-23 11:42:52 +0000468#**************************************************************************
Tim Peters7d016852001-10-08 06:13:19 +0000469# func_name is a triple (file:string, line:int, name:string)
Guido van Rossumadb31051994-06-23 11:42:52 +0000470
471def func_strip_path(func_name):
Fred Drake9c439102003-05-14 14:28:09 +0000472 filename, line, name = func_name
473 return os.path.basename(filename), line, name
Guido van Rossumadb31051994-06-23 11:42:52 +0000474
475def func_get_function_name(func):
Tim Peters2344fae2001-01-15 00:50:52 +0000476 return func[2]
Guido van Rossumadb31051994-06-23 11:42:52 +0000477
478def func_std_string(func_name): # match what old profile produced
Armin Rigoa871ef22006-02-08 12:53:56 +0000479 if func_name[:2] == ('~', 0):
480 # special case for built-in functions
481 name = func_name[2]
482 if name.startswith('<') and name.endswith('>'):
483 return '{%s}' % name[1:-1]
484 else:
485 return name
486 else:
487 return "%s:%d(%s)" % func_name
Guido van Rossumadb31051994-06-23 11:42:52 +0000488
489#**************************************************************************
490# The following functions combine statists for pairs functions.
491# The bulk of the processing involves correctly handling "call" lists,
Tim Peters2344fae2001-01-15 00:50:52 +0000492# such as callers and callees.
Guido van Rossumadb31051994-06-23 11:42:52 +0000493#**************************************************************************
494
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000495def add_func_stats(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000496 """Add together all the stats for two profile entries."""
497 cc, nc, tt, ct, callers = source
498 t_cc, t_nc, t_tt, t_ct, t_callers = target
Tim Peters7d016852001-10-08 06:13:19 +0000499 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
Tim Peters2344fae2001-01-15 00:50:52 +0000500 add_callers(t_callers, callers))
Guido van Rossumadb31051994-06-23 11:42:52 +0000501
Guido van Rossumadb31051994-06-23 11:42:52 +0000502def add_callers(target, source):
Tim Peters2344fae2001-01-15 00:50:52 +0000503 """Combine two caller lists in a single list."""
504 new_callers = {}
Raymond Hettingere0d49722002-06-02 18:55:56 +0000505 for func, caller in target.iteritems():
506 new_callers[func] = caller
507 for func, caller in source.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +0000508 if func in new_callers:
Georg Brandla2a92cf2010-08-02 17:34:58 +0000509 if isinstance(caller, tuple):
510 # format used by cProfile
511 new_callers[func] = tuple([i[0] + i[1] for i in
512 zip(caller, new_callers[func])])
513 else:
514 # format used by profile
515 new_callers[func] += caller
Tim Peters2344fae2001-01-15 00:50:52 +0000516 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +0000517 new_callers[func] = caller
Tim Peters2344fae2001-01-15 00:50:52 +0000518 return new_callers
Guido van Rossumadb31051994-06-23 11:42:52 +0000519
Guido van Rossumadb31051994-06-23 11:42:52 +0000520def count_calls(callers):
Tim Peters2344fae2001-01-15 00:50:52 +0000521 """Sum the caller statistics to get total number of calls received."""
522 nc = 0
Raymond Hettingere0d49722002-06-02 18:55:56 +0000523 for calls in callers.itervalues():
524 nc += calls
Tim Peters2344fae2001-01-15 00:50:52 +0000525 return nc
Guido van Rossumadb31051994-06-23 11:42:52 +0000526
527#**************************************************************************
528# The following functions support printing of reports
529#**************************************************************************
530
531def f8(x):
Tim Peters7d016852001-10-08 06:13:19 +0000532 return "%8.3f" % x
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000533
534#**************************************************************************
535# Statistics browser added by ESR, April 2001
536#**************************************************************************
537
538if __name__ == '__main__':
539 import cmd
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000540 try:
541 import readline
Fred Drakee8187612001-05-11 19:21:41 +0000542 except ImportError:
Eric S. Raymond9cb98572001-04-14 01:48:41 +0000543 pass
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000544
545 class ProfileBrowser(cmd.Cmd):
546 def __init__(self, profile=None):
Martin v. Löwis66b6e192001-07-28 14:44:03 +0000547 cmd.Cmd.__init__(self)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000548 self.prompt = "% "
Georg Brandl9c5efad2010-08-02 21:29:14 +0000549 self.stats = None
550 self.stream = sys.stdout
Raymond Hettinger16e3c422002-06-01 16:07:16 +0000551 if profile is not None:
Georg Brandl9c5efad2010-08-02 21:29:14 +0000552 self.do_read(profile)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000553
554 def generic(self, fn, line):
555 args = line.split()
556 processed = []
557 for term in args:
558 try:
559 processed.append(int(term))
560 continue
561 except ValueError:
562 pass
563 try:
564 frac = float(term)
565 if frac > 1 or frac < 0:
Skip Montanaro262fb922006-04-21 02:31:07 +0000566 print >> self.stream, "Fraction argument must be in [0, 1]"
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:
Skip Montanaro262fb922006-04-21 02:31:07 +0000576 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000577 return 0
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000578 def generic_help(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000579 print >> self.stream, "Arguments may be:"
580 print >> self.stream, "* An integer maximum number of entries to print."
581 print >> self.stream, "* A decimal fractional number between 0 and 1, controlling"
582 print >> self.stream, " what fraction of selected entries to print."
583 print >> self.stream, "* A regular expression; only entries with function names"
584 print >> self.stream, " that match it are printed."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000585
586 def do_add(self, line):
Georg Brandl0347c712010-08-01 19:02:09 +0000587 if self.stats:
588 self.stats.add(line)
589 else:
590 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000591 return 0
592 def help_add(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000593 print >> self.stream, "Add profile info from given file to current statistics object."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000594
595 def do_callees(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000596 return self.generic('print_callees', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000597 def help_callees(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000598 print >> self.stream, "Print callees statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000599 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000600
601 def do_callers(self, line):
Eric S. Raymond3c1858a2001-04-14 15:16:05 +0000602 return self.generic('print_callers', line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000603 def help_callers(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000604 print >> self.stream, "Print callers statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000605 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000606
607 def do_EOF(self, line):
Skip Montanaro262fb922006-04-21 02:31:07 +0000608 print >> self.stream, ""
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000609 return 1
610 def help_EOF(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000611 print >> self.stream, "Leave the profile brower."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000612
613 def do_quit(self, line):
614 return 1
615 def help_quit(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000616 print >> self.stream, "Leave the profile brower."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000617
618 def do_read(self, line):
619 if line:
620 try:
621 self.stats = Stats(line)
622 except IOError, args:
Skip Montanaro262fb922006-04-21 02:31:07 +0000623 print >> self.stream, args[1]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000624 return
Georg Brandl0347c712010-08-01 19:02:09 +0000625 except Exception as err:
626 print >> self.stream, err.__class__.__name__ + ':', err
627 return
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000628 self.prompt = line + "% "
Martin v. Löwis22adac52001-06-07 05:49:05 +0000629 elif len(self.prompt) > 2:
Georg Brandl0347c712010-08-01 19:02:09 +0000630 line = self.prompt[:-2]
631 self.do_read(line)
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000632 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000633 print >> self.stream, "No statistics object is current -- cannot reload."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000634 return 0
635 def help_read(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000636 print >> self.stream, "Read in profile data from a specified file."
Georg Brandl0347c712010-08-01 19:02:09 +0000637 print >> self.stream, "Without argument, reload the current file."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000638
639 def do_reverse(self, line):
Georg Brandl0347c712010-08-01 19:02:09 +0000640 if self.stats:
641 self.stats.reverse_order()
642 else:
643 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000644 return 0
645 def help_reverse(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000646 print >> self.stream, "Reverse the sort order of the profiling report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000647
648 def do_sort(self, line):
Georg Brandl0347c712010-08-01 19:02:09 +0000649 if not self.stats:
650 print >> self.stream, "No statistics object is loaded."
651 return
Raymond Hettingere0d49722002-06-02 18:55:56 +0000652 abbrevs = self.stats.get_sort_arg_defs()
Andrew M. Kuchlingd54e6992010-04-02 16:59:16 +0000653 if line and all((x in abbrevs) for x in line.split()):
Guido van Rossum68468eb2003-02-27 20:14:51 +0000654 self.stats.sort_stats(*line.split())
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000655 else:
Skip Montanaro262fb922006-04-21 02:31:07 +0000656 print >> self.stream, "Valid sort keys (unique prefixes are accepted):"
Raymond Hettingere0d49722002-06-02 18:55:56 +0000657 for (key, value) in Stats.sort_arg_dict_default.iteritems():
Skip Montanaro262fb922006-04-21 02:31:07 +0000658 print >> self.stream, "%s -- %s" % (key, value[1])
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000659 return 0
660 def help_sort(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000661 print >> self.stream, "Sort profile data according to specified keys."
662 print >> self.stream, "(Typing `sort' without arguments lists valid keys.)"
Martin v. Löwis27c430e2001-07-30 10:21:13 +0000663 def complete_sort(self, text, *args):
Raymond Hettingere0d49722002-06-02 18:55:56 +0000664 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000665
666 def do_stats(self, line):
667 return self.generic('print_stats', line)
668 def help_stats(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000669 print >> self.stream, "Print statistics from the current stat object."
Eric S. Raymond53b809d2001-04-26 07:32:38 +0000670 self.generic_help()
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000671
672 def do_strip(self, line):
Georg Brandl0347c712010-08-01 19:02:09 +0000673 if self.stats:
674 self.stats.strip_dirs()
675 else:
676 print >> self.stream, "No statistics object is loaded."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000677 def help_strip(self):
Skip Montanaro262fb922006-04-21 02:31:07 +0000678 print >> self.stream, "Strip leading path information from filenames in the report."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000679
Georg Brandl0347c712010-08-01 19:02:09 +0000680 def help_help(self):
681 print >> self.stream, "Show help for a given command."
682
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000683 def postcmd(self, stop, line):
684 if stop:
685 return stop
686 return None
687
688 import sys
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000689 if len(sys.argv) > 1:
690 initprofile = sys.argv[1]
691 else:
692 initprofile = None
693 try:
Neal Norwitze588c2b2006-06-11 07:27:56 +0000694 browser = ProfileBrowser(initprofile)
695 print >> browser.stream, "Welcome to the profile statistics browser."
696 browser.cmdloop()
697 print >> browser.stream, "Goodbye."
Eric S. Raymond4f3980d2001-04-13 00:23:01 +0000698 except KeyboardInterrupt:
699 pass
700
701# That's all, folks.